Skip to content

Olivine

pf4 edited this page Dec 27, 2024 · 5 revisions

Quick Presentation

Olivine is a command line programming language inspired by bash and developed for profanOS.

Although Olivine is developed for the profan environment, it can be used on a lot of systems (see Cross Platform Build).

Olivine uses substitution to execute lines of code, for example in the code echo Hello !user, !user will be literally replaced by the value of the variable !user. And the process is the same for function calls and pseudos.

Synopsis

olivine [options] [file] [arg1 arg2 ...]

Options

  • -c execute argument as code line
  • -i start a shell after executing
  • -h show this help message and exit
  • -n don't execute the init program
  • -p show file with syntax highlighting
  • -v display program's version number

Without any arguments, Olivine will start in interactive mode.

Main Features

Quoting

Olivine supports quoting using single quotes '. Inside quotes, the string can contain spaces and will not be split into multiple arguments.

Note: Even words are quoted, variables and subfunctions will still be substituted

Indentation and Line Breaks

Indentation will be ignored by Olivine. Each line of code must be separated by a line break or a semicolon ;.

echo first line
echo second line

echo first line; echo second line

Comments

Comments are defined using the // syntax. Everything after the // will be ignored by Olivine. Comments are disabled between quotes.

// This is a comment
echo Hello // This is also a comment

Functions Calls

If a line does not start with a keyword, it is considered as a function call. The function name is the first word of the line and the arguments are the following words.

function arg1 arg2 arg3
function 'arg1 with spaces' arg2

Two types of function exist, the internal functions which are in the source code in C as well as the classic functions which are coded in Olivine. (see Internal Functions and Classic Functions)

If a function returns a non-empty string, and it is not in a subfunction, it will be printed.

Variables

Variables are set using the set internal function which takes two arguments, the first one being the name of the variable (without the ! prefix) and the second one being the value of the variable.

Variable names (fonctions and pseudos too) can contain letters, numbers and underscores.

set var 42

Variables can be accessed using the ! prefix.

echo !var

Variables can be deleted using the del internal function which takes one argument, the name of the variable to delete.

del var

Variables on several layers are supported like in php or bash.

set a 42
set b a

echo !!b

If the variable was not found among the local variables, Olivine will also search in the enviroment (can be deactivated during build).

A environment variable can be set using the export internal function which takes two arguments, the first one being the name of the variable and the second one being the value of the variable.

export var 42

A variable defined outside of a function or declared using the global / export keyword will be accessible from all the functions (see Classic Functions).

Subfunctions Calls

Subfunctions are functions that are called inside a line of code. They are called using the !(...) syntax. Arguments are passed in the same way as classic function calls. Like variables, subfunctions also work in strings.

echo !(subfunction arg1 arg2)

Pseudos

Pseudos (aka Nicknames) are replaced between all the substitution steps and the final call of the function. They have been created in order to simplify the function calls.

Pseudos are defined with the internal function pseudo which takes two arguments, the first one being the name of the pseudo and the second one being the value of the pseudo.

pseudo pseudo_name value
pseudo ll 'ls -l'

Pseudos are accessible without a prefix like a function call.

ll user

// Will be automatically replaced by
ls -l user

Binaries Execution

The . internal function can be used to execute a binary file. The first argument is the path to the binary file and the following arguments are the arguments passed to the binary.

If BIN_AS_PSEUDO is set to 1 and the binary exists in a directory listed in the PATH environment variable, the binary can be executed without ..

// Using dot
. /bin/ls -l

// Without dot
ls -l

If the last argument of . is the symbol &, the file will be launched in the background, and the PID of the process will be stored in the variable !spi.

// Launches the cube in the background
. /bin/games/cube.elf &

To redirect stdin and stdout of a binary, the > and < symbols can be used.

// Redirects the output of ls to a file
ls -l > file.txt

// Redirects the input of more from a file
more < file.txt

// Redirects the output and input of lua
lua < program.lua > output.txt

See Pipes section for information about pipes.

Olivine Scripts Execution

The exec internal function can be used to execute an Olivine file in the current instance of Olivine. The file will be executed as if it was a function and the arguments will be passed to the file. A variable named filename will be set to the name of the file.

exec /path/to/file.olv arg1 arg2

Note: The . will have to same effect as exec if the file ends with the .olv extension and contains //olivine:exec at the beginning of the first line.

Features Priority

Olivine will always execute in the following order even if two elements have exactly the same name.

KEYWORD > PSEUDO > FUNCTION > INTERNAL FUNCTION > BINARY

Write Special Characters

Defore execution, the characters ' (quote), ! (variable access), * (wildcard) will be replaced by another character, to actually write ', ! or * it is possible to add a backslash just before.

// Will print 'Hello World!'
echo \'Hello World\!\'

To transform a character ! into its representative so that it can be used as access to a variable, it is possible to use the inter function (same for ' and *) which takes a string as an argument and returns the string with the characters transformed.

set var Hi

// Will print Hi
echo !var

// Will print !var
echo \!var

// Will print Hi
echo !(inter \!var)

Pipe Processor

If during execution one of the arguments is | (without quotes), the command will be split into two parts and the pipe processor will be used to redirect the output of the first command to the input of the second. Unlike POSIX-style shells, the pipe processor will not create a new process for each command but will execute all the commands one by one in the same process.

echo Hello | rev

ls | rev | cat -e

The pipe processor can be used in a subfunction call, if there is a pipe in the last argument, the contents of the pipe will be returned as a string.

echo arg1 !(echo 2gra | rev |) arg3

Wildcards

The * character can be used as a wildcard to match multiple files. The wildcard will be replaced by the list of files that match the pattern. Wildcards can be mixed with other characters to create more complex patterns.

echo *
cc /path/to/*/*.c

If no file or directory matches the pattern, the wildcard will raise an error.

Eval Function

The eval internal function can be used to evaluate an expression.

Number operators:

  • + Addition
  • - Substraction
  • x Multiplication
  • / Division
  • ^ Modulo
  • < Less than
  • > Greater than
  • = Equal
  • ~ Not equal

String operators:

  • + Concatenation
  • . Concatenation (with number)
  • x Repeat
  • @ Get character
  • < String shift (left)
  • > String shift (right)
  • = Equal
  • ~ Not equal

Operators priority (from lowest to highest): = ~ < > @ . + - * / ^

Parenthesis can be used to change the order of the operations.

Numbers, strings and operators must be separated by spaces (even parentheses), arguments which are not convertible into integers and which are not operators either, must be enclosed in double quotes.

eval 1 + 2 x ( 3 - 1 )
eval 42 = 41 + 1
eval "Hi" x 3
eval "Hello" @ 1 = "e"
eval "!var" = "/path/to/file"

The < and > operators for strings allow you to cut the start or end.

// Will print He
eval "Hello" < 3

// Will print llo
eval "Hello" > 2

Keywords

Keywords allow you to create conditions, loops and functions to extend the possibilities of Olivine. Capital letters don't matter, ELSE, else and eLsE are the same.

Conditionals

Conditionals are defined using the IF keyword followed by the condition. The END keyword must be used to close the conditional.

IF !(eval 1 = 1)
    echo 1 is equal to 1
END

The ELSE keyword can be used after the IF .. END block and will be executed if the condition is false.

IF !(eval 1 = 2)
    echo 1 is equal to 2
END; ELSE
    echo 1 is not equal to 2
END

Note: The ELSE keyword does not have to follow the END of the condition but can be separated by code 🫠

For Loops

For loops are defined using the FOR keyword followed by the variable name and the list of values. The END keyword must be used to close the loop.

FOR i hi hello hey
    echo !i
END

Note: For loops can be slow if the list of values is too long. In this case, it is recommended to use a while loop.

While Loops

While loops are defined using the WHILE keyword followed by the condition. The END keyword must be used to close the loop.

set i 0
WHILE !(eval !i < 5)
    echo !i
    set i !(eval !i + 1)
END

Break and Continue

The BREAK keyword can be used to exit a loop.

// Will print 1 2

FOR i 1 2 3 4 5
    IF !(eval !i = 3)
        BREAK
    END
    echo !i
END

The CONTINUE keyword can be used to skip the current iteration of a loop.

// Will print 1 2 4 5

FOR i 1 2 3 4 5
    IF !(eval !i = 3)
        CONTINUE
    END
    echo !i
END

Classic Functions

Classic functions are functions that are coded in Olivine. They are defined using the FUNC keyword followed by the name of the function.

A variable named !# is automatically set to the number of arguments passed to the function, and the arguments are accessible using the !<number> syntax.

FUNC my_function
    echo !# arguments passed
    echo first argument:  !0
    echo second argument: !1
END

The function can return a value using the RETURN keyword.

FUNC double
    RETURN !(eval !0 * 2)
END

We can note an important difference between bash (like all /bin/sh compatible shells) and Olivine, in Olivine the stdout is not used in functions as an output value. In bash there is no keyword to return a value, it is the stdout content. In Olivine, the stdout is always printed (except in pipes).

All the variables defined inside a function are local to the function and cannot be accessed from outside.

FUNC func1
    set var 42
END

FUNC func2
    set var 21
    func1
    RETURN !var
END

// Will print 21
echo !(func2)

The global keyword can be used to set a value of a variable globally, global variables should always be assigned with global keyword, otherwise they will be considered as local variables (to avoid collisions). If a variable is declared as global in a function, it will be accessible from all the code.

FUNC func3
    global var 42
END

func3

// Will print 42
echo !var

Global variables override local variables, if a variable is declared as global and local, the global variable will be used.

FUNC func4
    global var 2
    set var 3
    echo !var // Will print 3
END

set var 1
func4
echo !var // Will print 3

Internal Functions

Note: This section is of no importance if you only use Olivine as your shell language.

Internal functions are functions that are coded in C and are compiled with the Olivine binary.

char *if_demo(char **input) {
    // input[0] is the first argument
    // input[1] is the second...

    // The function must return a freeable string.
    // if the function returns NULL, the output
    // will be treated as an empty string.

    // return ERROR_CODE; can be used to raise an
    // error and stop the execution of the line.

    return strdup("Hello World");
}

// The function must be registered using the
// internal_functions array:

internal_function_t internal_functions[] = {
    ...
    {"demo", if_demo},
    ...
    {NULL, NULL}
};

Built-in Elements

Internal Functions

Name Arguments Description
. file [args] Executes the binary file
cd dir Changes the current directory
debug [arg] Prints all variables, pseudos and functions
del name [arg] Deletes variables, pseudos or functions
eval ... Evaluates an expression
exec file Executes the file as Olivine code
exit [code] Exits the program with the code
export name var Sets the env variable name to var
fsize file Returns the size or null if not found
global name var Sets the variable name to var globally
inter str Returns the string with special characters
print ... Prints the arguments withouth separators
pseudo name val Creates a pseudo
range [start] end Returns a list of numbers
rep str ic [oc] Replaces characters in a string
set name var Sets the variable name to var
split str [sep] [n] Splits a string into a list of strings
sprintf fmt [...] Returns a formatted string
strlen str Returns the length of a string
ticks Returns the ms since the OS boot

Default Variables

Name Description
version The version of Olivine
profan 1 if build for profan, 0 otherwise
path The current directory
exit The exit code of the last command
spi The PID of the the last . command
prompt The prompt of the shell

Cross Platform Build

The Olivine source code is available on the profanOS repository.

At the top of the file we find defined to facilitate the build on different platforms.

Name Description
PROFANBUILD enable profan features
UNIXBUILD enable unix features (recommended for linux)
USE_READLINE readline library for input (recommended)
BIN_AS_PSEUDO check for binaries in path
USE_ENVVARS enable environment variables
ENABLE_DEBUG print function calls in stderr
STOP_ON_ERROR stop execution at the first error
ENABLE_WILDC enable wildcard expansion

To build Olivine on linux, it is recommended to set PROFANBUILD to 0 and UNIXBUILD to 1. The USE_READLINE option is also recommended to use the readline library for input.

Build on Linux

# Install readline library (if not already installed)
sudo apt-get install libreadline-dev

# Download the olivine source code
wget https://raw.githubusercontent.com/elydre/profanOS/main/zapps/fatpath/olivine.c

# Compile olivine (Use -DolvUnix to enable unix features)
# The -O3 option is recommended for better performance
gcc -DolvUnix olivine.c -O3 -lreadline -o olivine

# [optional] Move the olivine binary to /usr/bin
sudo mv olivine /usr/bin

Build with minimal features

It is possible to build Olivine by deactivating PROFANBUILD and UNIXBUILD to have a minimal version of Olivine. Only the programming language will be available, without the shell features.

Only the headers stdarg.h, stdlib.h, string.h, stdio.h are required to compile Olivine in minimal configuration.

# Download the olivine source code
wget https://raw.githubusercontent.com/elydre/profanOS/main/zapps/fatpath/olivine.c

# Edit the olivine.c file and set PROFANBUILD and UNIXBUILD to 0
# vim olivine.c / nano olivine.c / code olivine.c

# Compile olivine
gcc olivine.c -o olivine

Updated for Olivine 1.5 rev 6