# MyLang     [](https://codecov.io/gh/vvaltchev/mylang) ## What is MyLang? MyLang is a simple educational programming language inspired by `Python`, `JavaScript`, and `C`, written as a personal challenge, in a short time, mostly to have fun writing a *recursive descent parser* and explore the world of interpreters. Don't expect a full-blown scripting language with libraries and frameworks ready for production use. However, `MyLang` has a minimal set of builtins and, it *could* be used for practical purposes as well. ## Contents * [Maintainance](#maintainance) * [Building MyLang](#building-mylang) * [Testing MyLang](#testing-mylang) * [Syntax](#syntax) * [Core concepts](#core-concepts) * [Declaring variables](#declaring-variables) * [Declaring constants](#declaring-constants) * [Type system](#type-system) * [Conditional statements](#conditional-statements) - [Const evaluation](#const-evaluation-of-conditional-statements) * [Classic loop statements](#classic-loop-statements) * [The foreach loop](#the-foreach-loop) - [The "indexed" keyword](#extra-features-the-indexed-keyword) - [Array expansion](#extra-features-array-expansion) * [Functions and lambdas](#functions-and-lambdas) - [Lambda captures](#lambda-captures) - [Calling functions during const-evaluation](#calling-functions-during-const-evaluation) - [Pure functions](#pure-functions) * [Exceptions](#exceptions) - [Custom exceptions](#custom-exceptions) - [Re-throwing an exception](#re-throwing-an-exception) - [The finally clause](#the-finally-clause) * [Builtins](#builtins) * [Const builtins](#const-builtins) - [General builtins](#general-builtins) - [Array builtins](#array-builtins) - [Dictionary builtins](#dictionary-builtins) - [String builtins](#string-builtins) - [Generic-container builtins](#generic-container-builtins) - [Numeric builtins](#numeric-builtins) - [Numeric constants](#numeric-constants) * [Non-const builtins](#non-const-builtins) - [Misc builtins](#non-const-misc-builtins) - [Array builtins](#non-const-array-builtins) - [Generic-container builtins](#non-const-generic-container-builtins) - [Numeric builtins](#non-const-numeric-builtins) - [I/O builtins](#non-const-io-builtins) ## Maintainance ### Building MyLang MyLang is written in *portable* C++17: at the moment, the project has no dependencies other than the standard C++ library. To build it, if you have `GNU make` installed, just run: ``` $ make -j ``` Otherwise, just pass all the .cpp files to your compiler and add the `src/` directory to the include search path. One of the nicest things about *not* having dependecies is that there's no need for a *build system* for one-time builds. #### Out-of-tree builds Just pass the `BUILD_DIR` option to `make`: ``` $ make -j BUILD_DIR=other_build_directory ``` ### Testing MyLang If you want to run MyLang's tests as well, you need to just compile with TESTS=1 and disable the optimizations with OPT=0, for a better debugging experience: ``` $ make -j TESTS=1 OPT=0 ``` Then, run all the tests with: ``` $ ./build/mylang -rt ``` It's worth noticing that, while test frameworks like [GoogleTest] and [Boost.Test] are infinitely much more powerful and flexible than the trivial test engine we have in `src/tests.cpp`, they are *external* dependencies. The less dependencies, the better, right? :-) [GoogleTest]: https://github.com/google/googletest [Boost.Test]: https://www.boost.org/doc/libs/1_75_0/libs/test/doc/html/index.html ## Syntax The shortest way to describe `MyLang` is: *a C-looking dynamic python-ish language*. Probably, the fastest way to learn this language is to check out the scripts in the `samples/` directory while taking a look at the short documentation below. ### Core concepts `MyLang` is a dynamic duck-typing language, like `Python`. If you know `Python` and you're willing to use `{ }` braces, you'll be automatically able to use it. No surprises. Strings are immutable like in `Python`, arrays can be defined using `[ ]` like in `Python`, and dictionaries can be defined using `{ }`, as well. The language also supports array-slices using the same `[start:end]` syntax used by `Python`. Said that, `MyLang` differs from `Python` and other scripting languages in several aspects: - There's support for *parse-time* constants declared using `const`. - All variables *must be* declared using `var`. - Variables have a scope like in `C`. Shadowing is supported when a variable is explicitly re-declared using `var`, in a nested block. - All expression statements must end with `;` like in `C`, `C++`, and `Java`. - The keywords `true` and `false` exist, but there's no `boolean` type. Like in C, `0` is false, everything else is `true`. However, strings, arrays, and dictionaries have a boolean value, exactly like in `Python` (e.g. an empty array is considered `false`). The `true` builtin is just an alias for the integer `1`. - The assignment operator `=` can be used like in `C`, inside expressions, but there's no such thing as the comma operator, because of the array-expansion feature. - MyLang supports both the classic `for` loop and an explicit `foreach` loop. - MyLang does not support custom types, at the moment. However, dictionaries support a nice syntactic sugar: in addition to the main syntax `d["key"]`, for string keys the syntax `d.key` is supported as well. ### Declaring variables Variables are always declared with `var` and live in the scope they've been declared (while being visible in nested scopes). For example: ```C# # Variable declared in the global scope var a = 42; { var b = 12; # Here we can see both `a` and `b` print(a, b); } # But here we cannot see `b`. ``` It's possible to declare multiple variables using the following familiar syntax: ```C# var a,b,c; ``` But there's a caveat, probably the only "surprising" feature of `MyLang`: initializing variables doesn't work like in C. Consider the following statement: ```C# var a,b,c = 42; ``` In this case, instead of just declaring `a` and `b` and initializing to `c` to 42, we're initializing *all* the three variables to the value 42. To initialize each variable to a different value, use the array-expansion syntax: ```C# var a,b,c = [1,2,3]; ``` ### Declaring constants Constants are declared in a similar way as variables *but* they cannot be shadowed in nested scopes. For example: ```C# const c = 42; { # That's not allowed const c = 1; # That's not allowed as well var c = 99; } ``` In `MyLang` constants are evaluated at *parse-time*, in a similar fashion to `C++`'s *constexpr* declarations (but there we talk about *compile time*). While initializing a `const`, any kind of literal can be used in addition to the whole set of *const* builtins. For example: ```C# const val = sum([1,2,3]); const x = "hello" + " world" + " " + join(["a","b","c"], ","); ``` To understand how exactly a constant has been evaluated, run the interpreter with the `-s` option, to dump the *abstract syntax tree* before running the script. For the example above: ``` $ cat > t const val = sum([1,2,3]); const x = "hello" + " world" + " " + join(["a","b","c"], ","); $ ./build/mylang t $ ./build/mylang -s t Syntax tree -------------------------- Block( ) -------------------------- ``` Surprised? Well, constants other than arrays and dictionaries are not even instantiated as variables. They just don't exist at *runtime*. Let's add a statement using `x`: ``` $ cat >> t print(x); $ cat t const val = sum([1,2,3]); const x = "hello" + " world" + " " + join(["a","b","c"], ","); print(x); $ ./build/mylang -s t Syntax tree -------------------------- Block( CallExpr( Id("print") ExprList( "hello world a,b,c" ) ) ) -------------------------- hello world a,b,c ``` Now, everything should make sense. Almost the same thing happens with arrays and dictionaries with the exception that the latter ones are instanted as at runtime as well, in order to avoid having potentially huge literals everywhere. Consider the following example: ``` $ ./build/mylang -s -e 'const ar=range(4); const s=ar[2:]; print(ar, s, s[0]);' Syntax tree -------------------------- Block( ConstDecl( Id("ar") Op '=' LiteralArray( Int(0) Int(1) Int(2) Int(3) ) ) ConstDecl( Id("s") Op '=' LiteralArray( Int(2) Int(3) ) ) CallExpr( Id("print") ExprList( Id("ar") Id("s") Int(2) ) ) ) -------------------------- [0, 1, 2, 3] [2, 3] 2 ``` As you can see, the *slice* operation has been evaluated at *parse-time* while initializing the constant `s`, but both the arrays exist at runtime as well. The subscript operations on const expressions, instead, are converted to literals. That looks like a good trade-off for performance: small values like integers, floats, and strings are converted to literals during the *const evaluation*, while arrays and dictionaries (potentially big) are left as read-only symbols at runtime, but still allowing some operations on them (like `[index]` and `len(arr)`) to be const-evaluated. ### Type system `MyLang` supports, at the moment, only the following (builtin) types: * **None** The type of `none`, the equivalent of Python's `None`. Variables just declared without having a value assigned to them, have value `none` (e.g. `var x;`). The same applies to functions that don't have a return value. Also, it's used as a special value by builtins like `find()`, in case of failure. * **Integer** A *signed* pointer-size integer (e.g. `3`). * **Float** A floating-point number (e.g. `1.23`). Internally, it's a long double. * **String** A string like "hello". Strings are immutable and support slices (e.g. `s[3:5]` or `s[3:]` or `s[-2:]`, having the same meaning as in `Python`). * **Array** A mutable type for arrays and tuples (e.g. `[1,2,3]`). It can contain items of different type and it supports writable slices. Array slices behave like copies while, under the hood, they use copy-on-write techniques. * **Dictionary** Dictionaries are hash-maps defined using `Python`'s syntax: `{"a": 3, "b": 4}`. Elements are accessed with the familiar syntax `d["key-string"]` or `d[23]`, can be looked-up with `find()` and deleted with `erase()`. At the moment, only strings, integers, and floats can be used as keys of a dictionary. **Perks**: identifier-like string-keys can be accessed also with the "member of" syntax: `d.key`. * **Function** Both standalone functions and lambdas have the same object type and can be passed around like any other object (see below). But, only lambdas can have a capture list. Regular functions cannot be executed during const-evaluation, while `pure` functions can. Pure functions can only see consts and their arguments. * **Exception** The only type of objects that can be thrown. To create them, use the `exception()` builtin or its shortcut `ex()`. ### Conditional statements Conditional statements work exactly like in `C`. The syntax is: ```C# if (conditionExpr) { # Then block } else { # Else block } ``` And the `{ }` braces can be omitted like in `C`, in the case of single-statement blocks. `conditionExpr` can be any expression, for example: `(a=3)+b >= c && !d`. ### Const evaluation of conditional statements When `conditionExpr` is an expression that can be const-evaluated, the whole if-statement is replaced by the true-branch, while the false-branch is discarded. For example, consider the following script: ```C# const a = 3; const b = 4; if (a < b) { print("yes"); } else { print("no"); } ``` Not only it always prints "yes", but it does not even need to check anything before doing that. Check the abstract syntax tree: ``` $ ./build/mylang -s t Syntax tree -------------------------- Block( Block( CallExpr( Id("print") ExprList( "yes" ) ) ) ) -------------------------- yes ``` ### Classic loop statements `MyLang` supports the classic `while` and `for` loops. ```C# while (condition) { # body if (something) break; if (something_else) continue; } for (var i = 0; i < 10; i += 1) { # body if (something) break; if (something_else) continue; } ``` Here, the `{ }` braces can be omitted as in the case above. There are only a few difference from `C` worth pointing out: - The `++` and `--` operators do not exist in `MyLang`, at the moment. - To declare multiple variables, use the syntax: `var a, b = [3,4];` or just `var a,b,c,d = 0;` if you want all the variables to have the same initial value. - To increase the value of multiple variables use the syntax: `a, b += [1, 2]`. In the extremely rare and complex cases when in the *increment* statement of the for-loop we need to assign to each variable a new variable using different expressions, take advantage of the expansion syntax in assignment: `i, j = [i+2, my_next(i, j*3)]`. ### The foreach loop `MyLang` supports `foreach` loops using a pretty familiar syntax: ```C# var arr = [1,2,3]; foreach (var e in arr) { print("elem:", e); } ``` Foreach loops can be used for arrays, strings, and dictionaries. For example, iterating through each `<key, value>` pair in a dictionary is easy as: ```C# var d = { "a": 3, "b": 10, "c": 42 }; foreach (var k, v in d) { print(k + " => " + str(v)); } ``` To iterate only through each key, just use `var k in d` instead. #### Extra features: the "indexed" keyword `MyLang` supports enumeration in foreach loops as well. Check the following example: ```C# var arr = ["a", "b", "c"]; foreach (var i, elem in indexed arr) { print("elem["+str(i)+"] = "+elem); } ``` In other words, when the name of the container is preceded by the keyword `indexed`, the first variable gets assigned a progressive number at each iteration. #### Extra features: array expansion While iterating through an array of small fixed-size arrays (think about tuples), it's possible to directly expand those "tuples" in the foreach loop: ```C# var arr = [ [ "hello", 42 ], [ "world", 11 ] ]; foreach (var name, value in arr) { print(name, value); } # This is a shortcut for: foreach (var elem in arr) { # regular array expansion var name, value = elem; print(name, value); } # Which is a shortcut for: foreach (var elem in arr) { var name = elem[0]; var value = elem[1]; print(name, value); } ``` ### Functions and lambdas Declaring a function is simple as: ```C# func add(x, y) { return x+y; } ``` But several shortcuts are supported as well. For example, in the case of single-statement functions like the one above, the following syntax can be used: ```C# func add(x, y) => x + y; ``` Also, while it's a good practice to always write `()` for parameter-less functions, they're actually optional in this language: ```C# func do_something { print("hello"); } ``` Functions are treated as regular symbols in `MyLang` and there're no substantial differences between standalone functions and lambdas in this language. For example, we can declare the `add` function (above) as a lambda this way: ```C# var add = func (x, y) => x + y; ``` Note: when creating function objects in expressions, we're not allowed to assign them a name. #### Lambda captures Lambdas support a capture list as well, but implicit captures are not supported, in order to enforce clarity. Of course, lambdas can be returned as any other object. For example: ```C# func create_adder_func(val) => func [val] (x) => x + val; var f = create_adder_func(5); print(f(1)); # Will print 6 print(f(10)); # Will print 15 ``` Lambdas with captures have a *state*, as anyone would expect. Consider the following script: ```C# func gen_counter(val) => func [val] { val += 1; return val; }; var c1 = gen_counter(5); for (var i = 0; i < 3; i += 1) print("c1:", c1()); # Clone the `c1` lambda object as `c2`: now it will have # its own state, indipendent from `c1`. var c2 = clone(c1); print(); for (var i = 0; i < 3; i += 1) print("c2:", c2()); print(); for (var i = 0; i < 3; i += 1) print("c1:", c1()); ``` It generates the output: ``` c1: 6 c1: 7 c1: 8 c2: 9 c2: 10 c2: 11 c1: 9 c1: 10 c1: 11 ``` ### Calling functions during const-evaluation Regular user-defined function objects (including lambdas) are not considered `const` and, therefore, *cannot* be run during const-evaluation. That's a pretty strong limitation. Consider the following example: ```C# const people = [ ["jack", 3], ["alice", 11], ["mario", 42], ["bob", 38] ]; const sorted_people = sort(people, func(a, y) => a[0] < b[0]); ``` In this case, the script *cannot* create the `sorted_people` const array. Because we passed a function object to the const `sort()` builtin, we'll get an `ExpressionIsNotConstEx` error. Sure, if `sorted_people` were declared as `var`, the script would run, but the array won't be const anymore and, we won't be able to benefit from any *parse-time* optimization. Therefore, while the `sort()` builtin can be called during const-evaluation, when it has a custom `compare func` parameter, that's not possible anymore. ### Pure functions To overcome the just-described limitation, `MyLang` has a special syntax for *pure* functions. When a function is declared with the `pure` keyword preceding `func`, the interpreter treats it in a special way: it *can* be called anytime, *both* during const evaluation and during runtime *but* the function cannot see global variables, nor capture anything: it can only use constants and the value of its parameters: that's exactly what we need during const evaluation. For example, to generate `sorted_people` during const evaluation it's enough to write: ```C# const sorted_people = sort(people, pure func(a, b) => a[0] < b[0]); ``` Pure functions can be defined as standalone functions and *can* be used with non-const parameters as well. Therefore, if a function can be declared as `pure`, it should always be declared that way. For example, consider the following script: ```C# pure func add2(x) => x + 2; var non_const = 25; print(add2(non_const)); print(add2(5)); ``` The abstract syntax tree that language's engine will use at runtime will be: ``` $ ./build/mylang -s t Syntax tree -------------------------- Block( FuncDeclStmt( Id("add2") <NoCaptures> IdList( Id("x") ) Expr04( Id("x") Op '+' Int(2) ) ) VarDecl( Id("non_const") Op '=' Int(25) ) CallExpr( Id("print") ExprList( CallExpr( Id("add2") ExprList( Id("non_const") ) ) ) ) CallExpr( Id("print") ExprList( Int(7) ) ) ) -------------------------- 27 7 ``` As you can see, in the first case an actual function call happens because `non_const` is not a constant, while in the second case it's AS IF we passed a literal integer to `print()`. ### Exceptions Like for other constructs, `MyLang` has an exception handling similar to `Python`'s, but using a syntax similar to `C++`. The basic construct is the `try-catch` statement. Let's see an example: ```C# try { var input_str = "blah"; var a = int(input_str); } catch (TypeErrorEx) { print("Cannot convert the string to integer"); } ``` Note: if an exception is generated by constant expressions (e.g. `int("blah")`), during the const-evaluation, the error will be reported directly, bypassing any exception handling logic. The reason for that is to enforce *early failure*. Multiple `catch` statements are allowed as well: ```C# try { # body } catch (TypeErrorEx) { # error handling } catch (DivisionByZeroEx) { # error handling } ``` And, in case several exceptions can be handled in with the same code, a shorter syntax can be used as well: ```C# try { # body } catch (TypeErrorEx, DivisionByZeroEx as e) { # error handling print(e); } catch (OutOfBoundsEx) { # error handling } ``` Exceptions might contain data, but none of the built-in exceptions currently do. The list of builtin *runtime* exceptions that can be caught with `try-catch` blocks is: * DivisionByZeroEx * AssertionFailureEx * NotLValueEx * TypeErrorEx * NotCallableEx * OutOfBoundsEx * CannotOpenFileEx Other exceptions like `SyntaxErrorEx` cannot be caught, instead. It's also possible in `MyLang` to catch ANY exception use a catch-anything block: ```C# try { # body } catch { # Something went wrong. } ``` ### Custom exceptions This language does not support custom types, at the moment. Therefore, it's not possible to *throw* any kind of object like in some other languages. To throw an exception, it's necessary to use the special built-in function `exception()` or its shortcut, `ex()`. Consider the following example: ```C# try { throw ex("MyError", 1234); } catch (MyError as e) { print("Got MyError, data:", exdata(e)); } ``` As intuition suggests, with `ex()` we've created and later thrown an exception object called `MyError`, having `1234` as payload data. Later, in the `catch` block, we caught the exception and, we extracted the payload data using the `exdata()` builtin. In case a given exception doesn't need to have a payload, it's possible to just save the result of `ex()` in a variable and throw it later using a probably more pleasant syntax: ```C# var MyError = ex("MyError"); throw MyError; ``` ### Re-throwing an exception `MyLang` supports re-throwing an exception in the body of catch statements using the dedicated `rethrow` keyword: ```C# try { do_something(); } catch { print("Something went wrong!!"); rethrow; } ``` ### The finally clause In some cases, it might be necessary to do some clean-up, after executing a block of code that might throw an exception. For these cases, `MyLang` supports the well known `finally` clause, which works exactly as in `C#`: ```C# try { step1_might_throw(); step2_might_throw(); step3_might_throw(); step4_might_throw(); } catch (TypeErrorEx) { # some error handling } finally { # clean-up } ``` It's worth noting that `try-finally` constructs (without any `catch` clause) are allowed as well. ## Builtins ### Const builtins The following built-in functions will be evaluated during *parse-time* when const arguments are passed to them. ### General builtins #### `defined(symbol)` Check if `symbol` is defined. Returns 1 if the symbol is defined, 0 otherwise. #### `len(container)` Return the number of elements in the given container. #### `str(value, [decimal_digits])` Convert the given value to a string. If `value` is a float, the 2nd parameter indicates the desired number of decimal digits in the output string. #### `int(value)` Convert the given string to an integer. If the value is a float, it will be trucated. If the value is a string, it will be parsed and converted to an integer, if possible. If the value is already an integer, it will be returned as-it-is. #### `float(value)` Convert the given value to float. If the value is an integer, it will be converted to a floating-point number. If the value is a string, it will parsed and converted to float, if possible. If the value is already a float, it will be returned as-it-is. #### `clone(obj)` Clone the given object. Useful for non-trivial objects as arrays, dictionaries and lambda with captures. #### `type(value)` Return the name of the type of the given value in string-form. Useful for debugging. #### `hash(value)` Return the hash value used by dictionaries internally when `value` is used as a key. At the moment, only integers, floats and strings support `hash()`. ### Array builtins #### `array(N)` Return an array of `none` values with `N` elements. #### `top(array)` Return the last element of the array. This is an alias for `array[-1]`. It is useful when a given array is used as a stack, in combination with other builtins like `push()` and `pop()`. #### `range(n, [end, [step]])` When only one parameter is provided, it returns an array with numbers from 0 to `n`. When `end` is passed to the function, the array goes from `n` to `end-1`. When `step` is passed too, the array goes from `n` to `end-step` with each element being `step` bigger than the previous. `step` can be negative as well. This is equivalent to `Python 2.x`'s range() function. In `Python 3.x`, this is equivalent to: `list(range(...))`. Warning: while it might look pretty in foreach loops, that's typically not a good idea because it returns a whole array, not a generator object like in `Python 3.x`. Therefore, for small ranges is fine, but for larger ranges it's better to use the classic for-loop. #### `sort(array, [compare_func])` Sorts the given array *in-place* and returns the same array. Optionally, it supports a `compare_func` parameter: when passed, it's used to compare any two elements and it's supposed to return the logical value of `a < b`. Note: while `sort()` works in-place, it still can be used to sort arrays without altering them and to sort const arrays as well: in the first case, it's possible by calling it as `sort(clone(arr))` and storing its return value to a new variable, while in the second case (const arrays), not even `clone()` is required: in case of const arrays, it will just sort and return a clone of the given array. #### `rev_sort(array, [compare_func])` Behaves exactly like `sort()`, but sorts the array in descending order. #### `reverse(array)` Reverse the given array in-place and returns it. Like `sort()`, if the given argument is const, it will be cloned before reversing. Therefore, it can be used during const-evaluation. #### `sum(array, [key_func])` Apply the `+` operator sequentially to all elements in the given array and return the result. In case the optional argument `key_func` is passed to `sum()`, the operator `+` is applied to the result of `key_func(elem)`, for each element instead. ### Dictionary builtins #### `keys(dictionary)` Return an array containing all the keys of the given dictionary. #### `values(dictionary)` Return an array containing all the values of the given dictionary. #### `kvpairs(dictionary)` Return the contents of the given dictionary, in the form of an array of [key, value] arrays. #### `dict(array)` Build a dictionary from an array of [key, value] arrays. This is the counter-part function of `kvpairs()`. ### String builtins #### `split(string, delim)` Split the given string by the given delimiter. Returns an array. #### `join(array_of_strings, delim)` Join the given array of strings with the given delimiter. Returns a string. #### `ord(string)` Return the numeric value of the given 1-char string. Note: in MyLang chars are 8-bit wide and there's no Unicode support, to keep this educational project smaller and simpler. #### `chr(num)` Return a 1-char string containing the string representation of the given number in the range [0, 255]. Note: in MyLang chars are 8-bit wide and there's no Unicode support, to keep this educational project smaller and simpler. #### `splitlines(string)` Split the given string, line by line. Returns an array. It's different from `split(string, "\n")` because it handles multiple types of line ending sequences. #### `lpad(string, n, [char])` Add left-padding to the given string to make it long `n` chars. If len(string) >= n, return the input string as it is. If a 3rd argument is passed to `lpad()`, it will be used as padding-character. By default, the padding character is space. #### `rpad(string, n, [char])` The counter-part of `lpad()`: pad the string on the right. #### `lstrip(string)` Return a slice of the given string skipping any leading whitespace. #### `rstrip(string)` Return a slice of the given string skipping any trailing whitespace. #### `strip(string)` Return a slice of the given string skipping any leading or trailing whitespace. #### `startswith(string, sub_string)` Return true (1) if the given string starts with the given sub_string and false (0), otherwise. #### `endswith(string, sub_string)` Return true (1) if the given string ends with the given sub_string and false (0), otherwise. #### Generic-container builtins #### `find(container, what, [key_func])` Generic *find* function working with strings, arrays, and dictionaries. When `container` is a string, it returns the index of the first occurrence of the `what` substring in `container` or `none`. When `container` is an array, it returns the index of the first element equal to `what`. Also, when `container` is an array, a 3rd parameter (`key_func`) is supported: it's a function object accepting a value (element of the array) and returning the value that must be compared to `what`. It's useful when we're searching something in an array of composite elements (e.g. tuples). When `container` is a dictionary, it returns the value associated with the given key (`what`) or `none` otherwise. #### `map(func, container)` Map each element in `container` through `func(elem)` and return the resulting array. For example, the following identity holds: ``` map(func(x) => x+1, [1, 2, 3]) == [2, 3, 4] ``` In case the container is a dictionary, `func` is required to accept two arguments, a key and a value, but the result will still be an array. For example: ``` map(func(k, v) => [k, v+1], {"a": 3, "b": 4}) == [["a",4],["b",5]] ``` #### `filter(func, container)` Filter the elements of `container` through `func(elem)` and return a container of the same type. For example: ``` filter(func(x) => x > 3, [1, 2, 3, 4, 5]) == [4, 5] ``` In case the container is a dictionary, `func` is required to accept two parameters, a key and a value, but the behavior will be semantically the same (a dictionary will be returned). ### Numeric builtins #### `abs(num)` Return the absolute value of the given number. #### `min(a, b, [c, [...]])` Return the smallest value among the ones passed to it. #### `min(array)` Return the smallest value among the ones in the given array. #### `max(a, b, [c, [...]])` Return the largest value among the ones passed to it. #### `max(array)` Return the largest value among the ones in the given array. #### `exp(x)` Return e^x. #### `exp2(x)` Return 2^x. #### `log(x)` Return the natural logarithm of `x`. #### `log2(x)` Return the base-2 logarithm of `x`. #### `log10(x)` Return the base-10 logarithm of `x`. #### `sqrt(x)` Return the square root of `x`. #### `cbrt(x)` Return the cube root of `x`. #### `pow(x, y)` Return x^y. #### `sin(x)` Return `sin(x)`. #### `cos(x)` Return `cos(x)`. #### `tan(x)` Return `tan(x)`. #### `asin(x)` Return the arc sine of `x`. #### `acos(x)` Return the arc cosine of `x`. #### `atan(x)` Return the arc tangent of `x`. #### `ceil(x)` Return the smallest integral value that is not less than `x`. #### `floor(x)` Return the largest integral value that is not greater than `x`. #### `trunc(x)` Return the rounded integer value of `x` as float. #### `isinf(x)` Return true if `x` is `inf` or `-inf`. #### `isfinite(x)` Return true if `x` is a finite value. #### `isnormal(x)` Return true if `x` is a normal floating-point number. #### `isnan(x)` Return true if `x` is "Not a Number". #### `round(x, [precision])` Round `x` to the nearest integer or to a floating-point number with `precision` digits. ### Numeric constants Constant name | Value -----------------------|------------------------- math_e | Euler's number math_log2e | log2(e) math_log10e | log10(e) math_ln2 | log(2) math_ln10 | log(10) math_pi | pi math_pi2 | pi/2 math_pi4 | pi/4 math_1_pi | 1/pi math_2_pi | 2/pi math_2_sqrt_pi | 2/sqrt(pi) math_sqrt2 | sqrt(2) math_1_sqrt2 | 1/sqrt(2) nan | Not a Number inf | Infinity eps | Floating-point's epsilon ### Non-const builtins The following built-in functions will *not* be evaluated during *parse-time*, no matter if const arguments are passed to them or not. ### Non-const misc builtins #### `assert(expr)` Check `expr` and throw AssertionFailureEx if it's false. #### `exit(code)` Exit the program with the given numeric code #### `intptr(symbol)` Get the internal shared object pointer referred by `symbol`. It's currently used in tests to check if two array slices refer internally to the same object. #### `undef(symbol)` Undefine the given symbol from the current scope. Return true if the given symbol was actually defined in the given scope. #### `exception(type_string, [payload_data])` Create an exception object having the given type plus the optional payload data. #### `ex(type_string, [data])` A shortcut for `exception()`. #### `exdata(ex)` Get the payload data from the given exception object. ### Non-const array builtins #### `append(array, value)` Append `value` to the given array. #### `push(array, value)` An alias for `append()`. Useful for symmetry when used with `pop()`. #### `pop(array)` Pop (and return) the last element from the given array. ### Non-const generic-container builtins #### `erase(container, key_or_index)` Erase the element at the given index or the element indexed by the given key from the given container (array or dictionary). Return true (1) if the key existed. For arrays, it always returns true or throws `OutOfBoundsEx` in case of an invalid index. #### `insert(container, key_or_index, value)` Insert the given value in the given container at the given index or key, depending on the type of the container. As `erase()`, it returns true if the insertion was successful, in the case `container` is a dictionary. In the case `container` was an array, always return true or throws `OutOfBoundsEx`. ### Non-const numeric builtins #### `rand(a, b)` Generate a random integer in the range [a, b]. #### `randf(a, b)` Generate a random floating-point number in the range [a, b]. ### Non-const I/O builtins #### `print(a, [b, [c, [...]]])` Write to the standard output the string-versions of the given arguments, separated by a single space and terminated by a line ending. #### `readln()` Read a single line from the standard input. #### `writeln(string)` Write the given string to the standard output, plus a line ending sequence. #### `read([filename])` If no arguments were given to `read()`, it returns the whole data in the standard input. Otherwise, read the whole given file and returns it as a string. #### `write(string, [filename])` Write the given string to the standard output as it is, without a line ending. If the optional parameter `filename` is given, write the given string to the given file, as it is. #### `readlines([filename])` Similar to `read()`, but read line by line and return an array. #### `writelines(array_of_strings, [filename])` Similar to `write()`, but accept an array of strings and write them one per line.