# Expr

Celestino Amoroso

TODO: Work in progress (last update on 2026/05/08)

## 1. Expr

Expr is a GO package that can analyze, interpret and calculate expressions.

### 1.1. Concepts and terminology

Expressions are texts containing sequences of operations represented by a syntax very similar to that of most programming languages. Expr package provides these macro functions:

- Scanner— Its input is a text. It scans expression text characters to produce a flow of logical symbol and related attributes, aka tokens.

- Parser— Parser input is the token flow coming from the scanner. It analyses the token flow verifyng if it complies with the Expr syntax. If that is the case, the Parser generates the Abstract Syntax Tree (AST). This is tree data structure that represents the components of an expressions and how they are related one each other.

- Calculator. Its input is the AST. It computes the parsed expression contained in the AST and returns the result or an error.

#### 1.1.1. Variables

Expr supports variables. The result of an expression can be stored in a variable and reused in other espressions by simply specifying the name of the variable as an operand.

#### 1.1.2. Multi-expression

An input text valid for Expr can contain more than an expression. Expressions are separated by `;` (semicolon). When an input contains two or more expressions it is called multi-expression.

Expr parses and computes each expression of a multi-espression, from the left to the right. If all expressions are computed without errors, it only returns the value of the last, the right most.

The result of each expression of a multi-expression is stored in an automatic variable named last. In this way, each expression can refer to the result of the previous one without the need to assign that value to a new dedicated variable.

#### 1.1.3. Calculation context

All objects, such as variables and functions, created during the calculation of an expression are stored in a memory called context.

The expression context is analogous to the stack-frame of other programming languages. When a function is called, a new context is allocated to store local definitions.

Function contexts are created by cloning the calling context. More details on this topic are given later in this document.

Expr creates and keeps a inner global context where it stores imported functions, either from builtin or plugin modules. To perform calculations, the user program must provide its own context; this is the main context. All calculations take place in this context. As mentioned earlier, when a function is called, a new context is created by cloning the calling context. The created context can be called function context.

Imported functions are registered in the global context. When an expression first calls an imported function, that function is linked to the current context; this can be the main context or a function context.

##### Inspecting contexts

Expr provides the operator $$ that returns the current context. This can be used to inspect the content of the context, for example to check the value of a variable or to see which functions are currently linked to the context. This operator is primarily intended for debugging purposes.

An interactive tool could like `dev-expr` (see [_dev-expr_test_tool]) can be used to inspect contexts interactively.

Example: inspecting contexts

`>>>` `$$`
`{"variables": {"ls": "[10, 20, 30]", "it": "$(#3)", "last": "-1"}, "functions": {"about": "about():string{}", "bin": "bin(value, digits=8):integer{}", "ctrl": "ctrl(prop, value):any{}", "ctrlList": "ctrlList():list-of-strings{}", "envGet": "envGet(name):string{}", "envSet": "envSet(name, value):string{}"}}`

`>>>` // Let use the ml command to activate multi-line output of contexts, which is more readable.
`>>>` `ml`
`>>>` `$$`
`{`
`"variables": {`
`"last": {"variables": {"last": "-1", "ls": "[10, 20, 30]", "it": "$(#3)"}, "functions": {"ctrlList": "ctrlList():list-of-strings{}", "envGet": "envGet(name):string{}", "envSet": "envSet(name, value):string{}", "about": "about():string{}", "bin": "bin(value, digits=8):integer{}", "ctrl": "ctrl(prop, value):any{}"}},`
`"ls": [10, 20, 30],`
`"it": $(#3)`
`},`
`"functions": {`
`"ctrlList": ctrlList():list-of-strings{},`
`"envGet": envGet(name):string{},`
`"envSet": envSet(name, value):string{},`
`"about": about():string{},`
`"bin": bin(value, digits=8):integer{},`
`"ctrl": ctrl(prop, value):any{}`
`}`
`}`

In order to inspect the global context issue the `$$ global` operation.

Example: list all functions whose name starts with "str"

`>>>` `builtin "string` // most function in the builtin module string have names starting with "str".
`1`

`>>>` `($$$global).functions) filter strStartsWith($_, "str"`
`[`
`"strEndsWith",`
`"strJoin",`
`"strLower",`
`"strSplit",`
`"strStartsWith",`
`"strSub",`
`"strTrim",`
`"strUpper",`
`"string"`
`]`

### 1.2. `dev-expr` test tool

Before we begin to describe the syntax of Expr, it is worth introducing dev-expr because it will be used to show many examples of expressions.

`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in additon to the automatic verification test suite based on the Go test framework, `dev-expr` provided an important aid for quickly testing of new features during their development.

`dev-expr` can work as a REPL, Read-Execute-Print-Loop, or it can process expression acquired from files or standard input.

The program can be downloaded from dev-expr.

Here are some examples of execution.

Run `dev-expr` in REPL mode and ask for help

    # Type 'exit' or Ctrl+D to quit the program.

    [user]$ ./dev-expr
    dev-expr -- Expressions calculator v1.12.0(build 1),2024/09/14 (celestino.amoroso@portale-stac.it)
    	Based on the Expr package v0.26.0
    	Type help to get the list of available commands
    	See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
    >>> help
    --- REPL commands:
            base -- Set the integer output base: 2, 8, 10, or 16
            exit -- Exit the program
            help -- Show command list
              ml -- Enable/Disable multi-line output
            mods -- List builtin modules
          output -- Enable/Disable printing expression results. Options 'on', 'off', 'status'
          source -- Load a file as input
             tty -- Enable/Disable ansi output (1)

    --- Command line options:
        -b <builtin>       Import builtin modules.
                           <builtin> can be a list of module names or a glob-pattern.
                           Use the special value 'all' or the pattern '*' to import all modules.
        -e <expression>    Evaluate <expression> instead of standard-input
        -i                 Force REPL operation when all -e occurences have been processed
        -h, --help, help   Show this help menu
        -m, --modules      List all builtin modules
        --noout            Disable printing of expression results
        -p                 Print prefix form
        -t                 Print tree form (2)
        -v, --version      Show program version
    >>>

1 | Only available for single fraction values
2 | Work in progress

REPL examples

    [user]$ ./dev-expr
    dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoroso@portale-stac.it)
    	Based on the Expr package v0.19.0
    	Type help to get the list of available commands
    	See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc

    >>> 2+3
    5
    >>> 2+3*(4-1.5)
    9.5
    >>> 0xFD + 0b1 + 0o1 (1)
    255
    >>> 1:2 + 2:3 (2)
    7:6
    >>> ml (3)
    >>> 1:2 + 2:3
    7
    -
    6
    >>> 4+2 but 5:2+0.5 (4)
    3
    >>> 4+2; 5:2+0.5 (5)
    3
    >>>

1 | Number bases: 0x = hexadecimal, 0o = octal, 0b = binary.
2 | Fractions: numerator : denominator.
3 | Activate multi-line output of fractions.
4 | But operator, see `but` operator.
5 | Multi-expression: the same result as the previous single expression, but this time it is obtained with two separate calculations.

## 2. Data types

Expr has its type system which is a subset of Golang’s type system. It supports numerical, string, relational, boolean expressions, and mixed-type lists and maps.

### 2.1. Numbers

Expr supports three type of numbers:

1. Integers

2. Floats

3. Fractions

In mixed operations involving integers, fractions and floats, automatic type promotion to the largest type is performed.

#### 2.1.1. Integers

Expr's integers are a subset of the integer set. Internally they are stored as Golang int64 values.

Example 1. Integer literal syntax

integer = [sign] digit-seq
sign = "+" | "-"
digit-seq = dec-seq | bin-seq | oct-seq | hex-seq
dec-seq = {dec-digit}
dec-digit = "0"|"1"|…​|"9"
bin-seq = "0b"{bin-digit}
bin-digit = "0"|"1"
oct-seq = "0o"{oct-digit}
oct-digit = "0"|"1"|…​|"7"
hex-seq = "0x"{hex-digit}
hex-digit = "0"|"1"|…​|"9"|"a"|…​|"z"|"A"|…​|"Z"

Value range: -9223372036854775808 to 9223372036854775807

Table 1. Arithmetic operators
Symbol | Operation | Description | Examples
`+` | Sum | Add two values | `-1 + 2` → 1
`-` | Subtraction | Subtract the right value from the left one | `3 - 1` → 2
`*` | Product | Multiply two values | `-1 * 2` → -2
`/` | Integer division | Divide the left value by the right one(*) | `-11 / 2` → -5
`%` | Modulo | Remainder of the integer division | `5 % 2` → 1

(*) See also the float division `./` below.

#### 2.1.2. Floats

Expr's floats are a subset of the rational number set. Note that they can’t hold the exact value of an unlimited number; floats can only approximate them. Internally floats are stored as Golang’s float64 values.

Example 2. Float literal syntax

float = [sign] dec-seq "." [dec-seq] [("e"|"E") [sign] dec-seq]
sign = "+" | "-"
dec-seq = see-integer-literal-syntax

Examples

`>>>` `1.0`
`1`

`>>>` `0.123`
`0.123`

`>>>` `4.5e+3`
`4500`

`>>>` `4.5E-33`
`4.5e-33`

`>>>` `4.5E-3`
`0.0045`

`>>>` `4.5E10`
`4.5e+10`

Table 2. Arithmetic operators
Symbol | Operation | Description | Examples
`+` | Sum | Add two values | `4 + 0.5` → 4.5
`-` | Subtraction | Subtract the right value from the left one | `4 - 0.5` → 3.5
`*` | Product | Multiply two values | `4 * 0.5` → 2.0
`/` | Float division | Divide the left value by the right one | `1.0 / 2` → 0.5
`./` | Forced float division | Force float division | `-1 ./ 2` → -0.5

#### 2.1.3. Fractions

Expr also supports fractions. Fraction literals are made with two integers separated by a colon character `:`.

Example 3. Fraction literal syntax

fraction = [sign] (num-den-spec | float-spec)
sign = "+" | "-"
num-den-spec = digit-seq ":" digit-seq
float-spec = dec-seq "." [dec-seq] "(" repetend ")"
repetend = dec-seq
dec-seq = see-integer-literal-syntax
digit-seq = see-integer-literal-syntax

Examples

`>>>` `1 : 2`
`1:2`

`>>>` `4:6` // Fractions are always reduced to their lowest terms
`2:3`

`>>>` `1:2 + 2:3`
`7:6`

`>>>` `1:2 * 2:3`
`1:3`

`>>>` `1:2 / 1:3`
`3:2`

`>>>` `1:2 ./ 1:3` // Force decimal division
`1.5`

`>>>` `-1:2`
`-1:2`

`>>>` `1:-2` // Invalid sign specification
Eval Error: [1:3] infix operator ":" requires two non-nil operands, got 1

`>>>` `1:(-2)`
`-1:2`

`>>>` `1.(3)` // 1.33333…​
`4:3`

Fractions can be used together with integers and floats in expressions.

Examples

`>>>` `1:2 + 5`
`11:2`

`>>>` `4 - 1:2`
`7:2`

`>>>` `1.0 + 1:2`
`1.5`

### 2.2. Strings

Strings are character sequences enclosed between two double quote `"`.

Examples

`>>>` `"I’m a string"`
`I’m a string`

`>>>` `"123abc?!"`
`123abc?!`

`>>>` `"123\nabc"`
`123`
`abc`

`>>>` `"123\tabc"`
`123 abc`

Some arithmetic operators also apply to strings.

Table 3. String operators
Symbol | Operation | Description | Examples
--- | --- | --- | ---
`+` | concatenation | Join two strings or two stringable values | `"one" + "two"` → "onetwo" `"one" + 2` → "one2"
`*` | repeat | Make n copy of a string | `"one" * 2` → "oneone"

The charanters in a string can be accessed using the square `[]` operator.

Example 4. Item access syntax

item = string-expr "[" integer-expr "]"

Example 5. Sub-string syntax

sub-string = string-expr "[" integer-expr ":" integer-expr "]"

String examples

`>>>` `s="abcd"` // assign the string to variable s
`"abcd"`

`>>>` `s[1]` // char at position 1 (starting from 0)
`"b"`

`>>>` `s[-1]` // char at position -1, the rightmost one
`"d"`

`>>>` `#s` // number of chars
`4`

`>>>` `#"abc"` // number of chars
`3`

`>>>` `s[1:3]` // chars from position 1 to position 3 excluded
`"bc"`

### 2.3. Booleans

Boolean data type has two values only: true and false. Relational and boolean expressions result in boolean values.

Table 4. Relational operators(*)
Symbol | Operation | Description | Examples
--- | --- | --- | ---
`==` | Equal | True if the left value is equal to the right one | `5 == 2` → false `"a" == "a"` → true
`!=` | Not Equal | True if the left value is NOT equal to the right one | `5 != 2` → true `"a" != "a"` → false
`<` | Less | True if the left value is less than the right one | `5 < 2` → false `"a" < "b"` → true
`<=` | Less or Equal | True if the left value is less than or equal to the right one | `5 <= 2` → false `"b" <= "b"` → true
`>` | Greater | True if the left value is greater than the right one | `5 > 2` → true `"a" > "b"` → false
`>=` | Greater or Equal | True if the left value is greater than or equal to the right one | `5 >= 2` → true `"b" >= "b"` → true

(*) See also the `in` operator in the list and dictionary sections.

Table 5. Boolean operators
Symbol | Operation | Description | Examples
--- | --- | --- | ---
`NOT` | Not | True if the right value is false | `NOT true` → false `NOT (2 < 1)` → true
`AND` / `&&` | And | True if both left and right values are true | `false && true` → false `"a" < "b" AND NOT (2 < 1)` → true
`OR` / `||` | Or | True if at least one of the left and right values integers is true | `false or true` → true `"a" == "b" OR (2 == 1)` → false

Currently, boolean operations are evaluated using short cut evaluation. This means that, if the left expression of the `and` and `or` operators is sufficient to establish the result of the whole operation, the right expression would not be evaluated at all.

Example

    2 > (a=1) or (a=8) > 0; a (1)

1 | This multi-expression returns 1 because in the first expression the left value of `or` is true and, as a conseguence, its right value is not computed. Therefore the a variable only receives the integer 1.

| `dev-expr` provides the ctrl() function that allows to change this behaviour.

### 2.4. Lists

Expr supports list of mixed-type values, also specified by normal expressions. Internally, Expr's lists are Go arrays.

Example 6. List literal syntax

list = empty-list | non-empty-list
empty-list = "[]"
non-empty-list = "[" any-value {"," any-value} "]"

Examples

`>>>` `[1,2,3]` // List of integers
`[1, 2, 3]`

`>>>` `["one", "two", "three"]` // List of strings
`["one", "two", "three"]`

`>>>` `["one", 2, false, 4.1]` // List of mixed-types
`["one", 2, false, 4.1]`

`>>>` `["one"+1, 2.0*(9-2)]` // List of expressions
`["one1", 14]`

`>>>` `[ [1,"one"], [2,"two"]]` // List of lists
`[[1, "one"], [2, "two"]]`

Table 6. List operators
Symbol | Operation | Description | Examples
--- | --- | --- | ---
`+` | Join | Joins two lists | `[1,2] + [3]` → [1,2,3]
`-` | Difference | Left list without elements in the right list | `[1,2,3] - [2]` → [1,3]
`+>` | Front insertion | Insert an item in front | `0 +> [1,2]` → [0,1,2]
`<+` | Back insertion | Insert an item at end | `[1,2] <+ 3` → [1,2,3]
`[]` | Item at index | Item at given position | `[1,2,3][1]` → 2
`in` | Item in list | True if item is in list | `2 in [1,2,3]` → true `6 in [1,2,3]` → false
`#` | Size | Number of items in a list | `#[1,2,3]` → 3

Array’s items can be accessed using the index `[]` operator.

Example 7. Item access syntax

item = list-expr "[" integer-expr "]"

Example 8. Sub-array (or slice of array) syntax

slice = string-expr "[" integer-expr ":" integer-expr "]"

Examples: Getting items from lists

`>>>` `[1,2,3][1]`
`2`

`>>>` `index=2; ["a", "b", "c", "d"][index]`
`c`

`>>>` `["a", "b", "c", "d"][2:]`
`["c", "d"]`

`>>>` `list=[1,2,3]; list[1]`
`2`

`>>>` `["one","two","three"][1]`
`two`

`>>>` `list=["one","two","three"]; list[2-1]`
`two`

`>>>` `list[1]="six"; list`
`["one", "six", "three"]`

`>>>` `list[-1]`
`three`

`>>>` `list[10]`
`Eval Error: [1:9] index 10 out of bounds`

Example: Number of elements in a list

`>>>` `#list`
`3`

Examples: Element insertion

`>>>` `"first" +> list`
`["first", "one", "six", "three"]`

`>>>` `list <+ "last"`
`["first", "one", "six", "three", "last"]`

Examples: Element in list

`>>>` `"six" in list`
`true`

`>>>` `"ten" in list`
`false`

Examples: Concatenation and filtering

`>>>` `[1,2,3] + ["one", "two", "three"]`
`[1, 2, 3, "one", "two", "three"]`

`>>>` `[1,2,3,2,4] - [2,4]`
`[1, 3]`

### 2.5. Dictionaries

The dictionary, or dict, data-type represents sets of pairs key/value. It is also known as map or associative array.

Dictionary literals are sequences of pairs separated by comma `,` enclosed between brace brackets.

Example 9. Dict literal syntax

dict = empty-dict | non-empty-dict
empty-dict = "{}"
non-empty-dict = "{" key-scalar ":" any-value {"," key-scalar ":" any-value} "}"

Example 10. Item access syntax

item = dict-expr "[" key-expr "]"

Table 7. Dict operators
Symbol | Operation | Description | Examples
--- | --- | --- | ---
`+` | Join | Joins two dicts | `{1:"one"}+{6:"six"}` → {1: "one", 6: "six"}
`[]` | Dict item value | Item value of given key | `{"one":1, "two":2}["two"]` → 2
`in` | Key in dict | True if key is in dict | `"one" in {"one":1, "two":2}` → true `"six" in {"one":1, "two":2}` → false
`#` | Size | Number of items in a dict | `#{1:"a",2:"b",3:"c"}` → 3

Examples

`>>>` `{1:"one", 2:"two"}`
`{1: "one", 2: "two"}`

`>>>` `{"one":1, "two": 2}`
`{"one": 1, "two": 2}`

`>>>` `{"sum":1+2+3, "prod":1*2*3}`
`{"sum": 6, "prod": 6}`

`>>>` `{"one":1, "two":2}["two"]`
`2`

`>>>` `d={"one":1, "two":2}; d["six"]=6; d`
`{"two": 2, "one": 1, "six": 6}`

`>>>` `#d`
`3`

## 3. Variables

Expr, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in contexts.

Example 11. Variable literal syntax

variable = identifier "=" any-value
identifier = alpha {alpha|dec-digit|"_"}
alpha = "a"|"b"|…​"z"|"A"|"B"|…​"Z"

| The assign operator `=` returns the value assigned to the variable.

Examples

`>>>` `a=1`
`1`

`>>>` `a_b=1+2`
`3`

`>>>` `a_b`
`3`

`>>>` `x = 5.2 * (9-3)` // The assigned value here has the typical approximation error of the float data-type
`31.200000000000003`

`>>>` `x = 1; y = 2*x`
`2`

`>>>` `_a=2`
`Parse Error: [1:2] unexpected token "_"`

`>>>` `1=2`
`Parse Error: assign operator ("=") must be preceded by a variable`

## 4. Other operations

### 4.1. `;` operator

The semicolon operator `;` is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The value of the latter is the final result.

Example 12. Multi-expression syntax

multi-expression = expression {";" expression }

An expression that contains `;` is called a multi-expression and each component expression is called a sub-expression.

| Technically `;` is not treated as a real operator. It acts as a separator in lists of expressions.

| `;` can be used to set some variables before the final calculation.

Example

`>>>` `a=1; b=2; c=3; a+b+c`
`6`

The value of each sub-expression is stored in the automatic variable last.

Example

`>>>` `2+3; b=last+10; last`
`15`

### 4.2. `but` operator

`but` is an infixed operator. Its operands can be expressions of any type. It evaluates the left expression first, then the right expression. The value of the right expression is the final result.

Examples

`>>>` `5 but 2`
`2`

`>>>` `x=2*3 but x-1`
`5`.

`but` behavior is very similar to `;`. The only difference is that `;` is not a true operator and can’t be used inside parenthesis `(` and `)`.

### 4.3. Assignment operator `=`

The assignment operator `=` is used to define variables or to change their value in the evaluation context (see ExprContext).

The value on the left side of `=` must be a variable identifier or an expression that evalutes to a variable. The value on the right side can be any expression and it becomes the result of the assignment operation.

Examples

`>>>` `a=15+1`
`16`

`>>>` `L=[1,2,3]; L[1]=5; L`
`[1, 5, 3]`

### 4.4. Selector operator `? : ::`

The selector operator is very similar to the switch/case/default statement available in many programming languages.

Example 13. Selector literal syntax

selector-operator = select-expression "?" selector-case { ":" selector-case } ["::" default-multi-expression]
selector-case = [match-list] case-value
match-list = "[" item {"," items} "]"
item = expression
case-multi-expression = "{" multi-expression "}"
multi-expression = expression { ";" expression }
default-multi-expression = multi-expression

In other words, the selector operator evaluates the select-expression on the left-hand side of the `?` symbol; it then compares the result obtained with the values listed in the match-list's, from left to right. If the comparision finds a match with a value in a match-list, the associated case-multi-expression is evaluted, and its result will be the final result of the selection operation.

The match lists are optional. In that case, the position, from left to right, of the selector-case is used as match-list. Of course, that only works if the select-expression results in an integer.

The `:` symbol (colon) is the separator of the selector-cases. Note that if the value of the select-expression does not match any match-list, an error will be issued. Therefore, it is strongly recommended to provide a default (multi-)expression introduced by the `::` symbol (double-colon). Also note that the default expression has no match-list.

Examples

`>>>` `1 ? {"a"} : {"b"}`
`b`

`>>>` `10 ? {"a"} : {"b"} :: {"c"}`
`c`

`>>>` `10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`
`b`

`>>>` `10 ? {"a"} :[true, 2+8] {"b"} :: [10] {"c"}`
`Parse Error: [1:34] case list in default clause`

`>>>` `10 ? {"a"} :[10] {x="b" but x} :: {"c"}`
`b`

`>>>` `10 ? {"a"} :[10] {x="b"; x} :: {"c"}`
`b`

`>>>` `10 ? {"a"} : {"b"}`
`Eval Error: [1:3] no case catches the value (10) of the selection expression`

#### 4.4.1. Triple special case of the selector operator

If the select-expression is a boolean expression, the selector operator can be used as a sort of if-then-else statement. In this case, the first case is evaluated if the select-expression is true, and the second case is evaluated if the select-expression is false. In this special case, the match-list of both cases must be empty.

Example

`>>>` `(true) ? {"T"}: {"F"}`
`T`
`>>>` `(2 > 1) ? {"a"} : {"b"}`
`a`
`>>>` `(2 < 1) ? {"a"} : {"b"}`
`b`

| The triple special case of the selector operator is very useful, but it only works with boolean expressions. Example of confusion `>>>` `int(true) ? {"T"}: {"F"}` `F`

### 4.5. Variable default value `??`, `?=`, and `?!`

The left operand of the first two operators, `??` and `?=`, must be a variable. The right operatand can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.

| If the left variable is defined, the right expression is not evaluated at all.

The `??` operator do not change the status of the left variable.

The `?=` assigns the calculated value of the right expression to the variable on the left side.

The third one, `?!`, is the alternate operator. If the variable on the left size is not defined, it returns nil. Otherwise it returns the result of the expressione on the right side.

| If the variable `?!` is NOT defined, the expression is not evaluated at all.

Examples

`>>>` `var ?? (1+2)`
`3`

`>>>` `var`
`Eval Error: undefined variable or function "var"`

`>>>` `var ?= (1+2)`
`3`

`>>>` `var`
`3`

`>>>` `x ?! 5`
`nil`

`>>>` `x=1; x ?! 5`
`5`

`>>>` `y ?! (c=5); c`
`Eval Error: undefined variable or function "c"`

| These operators have a high priority, in particular higher than the operator `=`.

## 5. Priorities of operators

The table below shows all supported operators by decreasing priorities.

Table 8. Operators priorities
Priority | Operator | Position | Operation | Operands and results
--- | --- | --- | --- | ---
ITEM | `[`…​`]` | Postfix | List item | list `[` integer `]` → any
`[`…​`]` | Postfix | Dict item | dict `[` any `]` → any
INC/DEC | `++` | Postfix | Iterator next item | iterator `++` → any
`++` | Postfix | Post increment | integer-variable `++` → integer
`++` | Prefix | Pre increment | `++` integer-variable → integer
`--` | Postfix | Post decrement | integer-variable `--` → integer
`--` | Prefix | Pre decrement | `--` integer-variable → integer
DEFAULT | `??` | Infix | Default value | variable `??` any-expr → any
`?=` | Infix | Default/assign value | variable `?=` any-expr → any
`?!` | Infix | Alternate value | variable `?!` any-expr → any
FACT | `!` | Postfix | Factorial | integer `!` → integer
SIGN | `+`, `-` | Prefix | Change-sign | (`+`|`-`) number → number
`#` | Prefix | Lenght-of | `#` collection → integer
`#` | Prefix | Size-of | `#` iterator → integer
BIT SHIFT | `<<` | Infix | Left-Shift | integer `<<` integer → integer
`>>` | Infix | Right-Shift | integer `>>` iterator → integer
SELECT | `? : ::` | Multi-Infix | Case-Selector | any-expr `?` case-list case-expr `:` case-list case-expr …​ `::` default-expr → any
`? : ::` | Multi-Infix | Index-Selector | int-expr `?` case-expr `:` case-expr …​ `::` default-expr → any
FRACT | `:` | Infix | Fraction | integer `:` integer → fraction
PROD | `*` | Infix | Product | number `*` number → number
`*` | Infix | String-repeat | string `*` integer → string
`/` | Infix | Division | number `/` number → number
`./` | Infix | Float-division | number `./` number → float
`/` | Infix | Split | string `/` string → list
`/` | Infix | Split | string `/` integer → list
`%` | Infix | Integer-remainder | integer `%` integer → integer
SUM | `+` | Infix | Sum | number `+` number → number
`+` | Infix | String-concat | (string|number) `+` (string|number) → string
`+` | Infix | List-join | list `+` list → list
`+` | Infix | Dict-join | dict `+` dict → dict
`-` | Infix | Subtraction | number `-` number → number
`-` | Infix | List-difference | list `-` list → list
BITWISE NOT | `~` | Prefix | Binary Not | `~` integer → integer
BITWISE AND | `&` | Infix | Binary And | integer `&` integer → integer
BITWISE OR | `|` | Infix | Binary Or | integer `|` integer → integer
`^` | Infix | Binary Xor | integer `^` integer → integer
RELATION | `<` | Infix | Less | comparable `<` comparable → boolean
`<=` | Infix | less-equal | comparable `<=` comparable → boolean
`>` | Infix | Greater | comparable `>` comparable → boolean
`>=` | Infix | Greater-equal | comparable `>=` comparable → boolean
`==` | Infix | Equal | comparable `==` comparable → boolean
`!=` | Infix | Not-equal | comparable `!=` comparable → boolean
`in` | Infix | Member-of-list | any `in` list → boolean
`in` | Infix | Key-of-dict | any `in` dict → boolean
LOGIC NOT | `not` | Prefix | Not | `not` boolean → boolean
LOGIC AND | `and` | Infix | And | boolean `and` boolean → boolean
`&&` | Infix | And | boolean `&&` boolean → boolean
LOGIC OR | `or` | Infix | Or | boolean `or` boolean → boolean
`||` | Infix | Or | boolean `||` boolean → boolean
INSERT | `+>` | Infix | Prepend | any `+>` list → list
`<+` | Infix | Append | list `<+` any → list
ASSIGN | `=` | Infix | Assignment | identifier `=` any → any
See also the table of special assignment operators below
BUT | `but` | Infix | But | any `but` any → any
ITER-OP | `digest` | Infix | Item-digesting | iterable `digest` expr → any
`filter` | Infix | Item-filtering | iterable `filter` expr → list
`groupby` | Infix | Dict-grouping | iterable `groupby` key-expr → dict
`cat` | Infix | Item-concatenation | iterable `cat ` iterable → list
`map` | Infix | Item-mapping | iterable `map` -expr → list
See iterators section for examples
RANGE | `:` | Infix | Index-range | integer `:` integer → integer-pair

Table 9. Special assignment perators
Priority | Operator | Operation | Equivalent operation
--- | --- | --- | ---
ASSIGN | `+=` | Sum & Assign | var `+=` expr short for var `=` value-of-var `+` expr
`-=` | Subtract & Assign | var `-=` expr short for var `=` value-of-var `-` expr
`*=` | Multiply & Assign | var `*=` expr short for var `=` value-of-var `*` expr
`/=` | Divide & Assign | var `/=` expr short for var `=` value-of-var `/` expr
`%=` | Remainder & Assign | var `%=` expr short for var `=` value-of-var `%` expr
`&=` | Bitwise and & Assign | var `&=` expr short for var `=` value-of-var `&` expr
`|=` | Bitwise or & Assign | var `|=` expr short for var `=` value-of-var `|` expr
`<<=` | Left shift & Assign | var `<<=` expr short for var `=` value-of-var `<<` expr
`>>=` | Right shift & Assign | var `>>=` expr short for var `=` value-of-var `>>` expr

## 6. Functions

Functions in Expr are very similar to functions available in many programming languages. Currently, Expr supports two types of function, expr-functions and go-functions.

- expr-functions are defined using Expr's syntax. They can be passed as arguments to other functions and can be returned from functions. Moreover, they bind themselves to the defining context, thus becoming closures.

- go-functions are regular Golang functions callable from Expr expressions. They are defined in Golang source files called modules and compiled within the Expr package. To make Golang functions available in Expr contextes, it is required to activate the builtin module or to load the plugin module in which they are defined.

### 6.1. Expr function definition

An expr-function is identified and referenced by its name. It can have zero or more parameter. expr-functions also support optional parameters and passing paramters by name.

Example 14. Expr’s function definition syntax

function-definition = identifier "=" "func(" [formal-param-list] ")" "{" multi-expression "}"
formal-param-list = required-param-list [ "," optional-param-list ]
required-param-list = identifier { "," identifier }
optional-param-list = optional-parm { "," optional-param }
optional-param = param-name "=" any-expr
param-name = identifier

Examples

`>>>` // A simple function: it takes two parameters and returns their "sum"(*)
`>>>` `sum = func(a, b){ a + b }`
`sum(a, b):any{}`

(*) Since the plus, +, operator is defined for multiple data-types, the sum() function can be used for any pair of that types.

`>>>` // A more complex example: recursive calculation of the n-th value of Fibonacci’s sequence
`>>>` `fib = func(n){ n ? [0] {0}: [1] {1} :: {fib(n-1)+fib(n-2)} }`
`fib(n):any{}`

`>>>` // Same function fib() but entered by splitting it over mulple text lines
`>>>` `fib = func(n){ \`
`...` `n ? \`
`...` `[0] {0} : \`
`...` `[1] {1} :: \`
`...` `{ \`
`...` `fib(n-1) + fib(n-2) \`
`...` `} \`
`...` `}`
`fib(n):any{}`

`>>>` // Required and optional parameters
`>>>` `measure = func(value, unit="meter"){ value + " " + unit + (value > 1) ? {"s"} :: {""}}`
`measure(value, unit="meter"):any{}`

### 6.2. Golang function definition

Description of how to define Golang functions and how to bind them to Expr are topics covered in another documents that I’ll write, one day, maybe.

### 6.3. Function calls

To call a function, either Expr or Golang type, it is necessary to specify its name and, at least, its required parameters.

Example 15. Function invocation syntax

function-call = identifier "(" actual-param-list ")"
actual-param-list = [positional-params] [named-parameters]
positional-params = any-value { "," any-value }
named-params = param-name "=" any-value { "," param-name "=" any-value }
param-name = identifier

Examples of calling the `sum()` functions defined above

`>>>` // sum of two integers
`>>>` `sum(-6, 2)`
`-4`
`>>>` // same as above but passing the parameters by name
`>>>` `sum(a=-6, b=2)`
`-4`
`>>>` // again, but swapping parameter positions (see the diff() examples below)
`>>>` `sum(b=2, a=-6)`
`-4`
`>>>` // sum of a fraction and an integer
`>>>` `sum(3|2, 2)`
`7|2`
`>>>` // sum of two strings
`>>>` `sum("bye", "-bye")`
`"bye-bye"`
`>>>` // sum of two lists
`>>>` `sum(["one", 1], ["two", 2])`
`["one", 1, "two", 2]`

Examples of calling a function with parameters passed by name

`>>>` // diff(a,b) calculates a-b
`>>>` `diff = func(a,b){a-b}`
`diff(a, b):any{}`
`>>>` // simple invocation
`>>>` `diff(10,8)`
`2`
`>>>` // swapped parameters passed by name
`>>>` `diff(b=8,a=10)`
`2`

Examples of calling the `fib()` function defined above

`>>>` // Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …​
`>>>` `fib(6)`
`8`
`>>>` `fib(9)`
`34`

Examples of calling the `measure()` functions defined above

`>>>` // simple call
`>>>` `measure(10,"litre")`
`"10 litres"`
`>>>` // accept the default unit
`>>>` `measure(8)`
`"8 meters"`
`>>>` // without the required parameter 'value'
`>>>` `measure(unit="degrees"))`
`Eval Error: measure(): missing params — value`

Examples of context binding (closures)

`>>>` `factory = func(n=2){ func(x){x*n} }`
`factory(n=2):any{}`
`>>>` `double = factory()`
`double(x):any{}`
`>>>` `triple = factory(3)`
`triple(x):any{}`
`>>>` `double(5)`
`10`
`>>>` `triple(5)`
`15`

### 6.4. Function context

Functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the clone modifier `@` it is possibile to export local definition to the calling context. The clone modifier must be used as prefix to variable names and it is part of the name. E.g. `@x` is not the same as `x`; they are two different and unrelated variables.

Clone variables are normal local variables. The only diffence will appear when the defining function ends, just before the destruction of its local context. At that point, all local clone variables are cloned in the calling context with the same names but the `@` symbol.

Example

`>>>` `f = func() { @x = 3; x = 5 }` // f() declares two different local variables: `@x` and `x`
`f():any{}`
`>>>` `f()` // The multi-expression (two expressions) in f() is calculated and the last result is returned
`5`
`>>>` `x` // The `x` variable was not defined in the main context before the f() invocation. It appears in the main context by cloning the `@x` variable, local to f() after its termnation.
`3`

| The clone modifier `@` does not make a variable a reference variable, as the ampersand character `&` does in languages such as C and C++.

| The clone modifier can also be used to declare the formal parameters of functions, because they are local variables too. Example `>>>` `g = func(@p) {2+@p}` `g(@p):any{}` `>>>` `g(9)` `11` `>>>` `p` `9`

## 7. Builtins

Builtins are collection of function dedicated to specific domains of application. They are defined in Golang source files called modules and compiled within the Expr package. To make builtins available in Expr contextes, it is required to activate the builtin module in which they are defined.

There are currently several builtin modules. More builtin modules will be added in the future.

Available builtin modules

- base: Base expression tools like isNil(), int(), etc.

- fmt: String and console formatting functions

- import: Functions import() and include()

- iterator: Iterator helper functions

- math.arith: Functions add() and mul()

- os.file: Operating system file functions

- string: string utilities

Builtins activation is done by using the `BUILTIN` operator. All modules except "base" must be explicitly enabled. The syntax is as follows.

Example 16. Builtin activation syntax

builtin-activation = `BUILTIN` (builtin-name | list-of-builtin-names | "*")
builtin-name = string
list-of-builtin-names = [ string { "," string } ]

The following example shows how to activate the builtin module "math.arith" and then use the function add() defined in that module.

Example: using built functions

`>>>` `BUILTIN "math.arith"`
`1`
`>>>` `add(5, 3, -2)`
`6`

| To avoid the need to activate builtin modules one by one, it is possible to activate all builtin modules at once by using the `BUILTIN "*"` syntax.

### 7.1. Builtin modules

#### 7.1.1. Module "base"

The "base" builtin module provides functions for type checking and type conversion. These functions are always available in Expr contexts without the need to activate the "base" module.

Checking functions

- isBool()

- isDict()

- isFloat()

- isFract()

- isList()

- isNil()

- isRational()

- isString()

Conversion functions

- bool()

- int()

- dec()

- string()

- fract()

Other functions

- char()

- eval()

- set()

- var()

##### isBool()

Syntax: `isBool(<expr>) → bool`
Returns true if the value type of <expr> is boolean, false otherwise.

Examples

`>>>` `isBool(true)`
`true`
`>>>` `isBool(3==2)`
`true`
`>>>` `isBool(3 + 2)`
`false`

##### isDict()

Syntax: `isDict(<expr>) → bool`
Returns true if the value type of <expr> is dictionary, false otherwise.

Examples

`>>>` `isDict({})`
`true`
`>>>` `isDict({1: "one", 2: "two"})`
`true`
`>>>` `isDict(1:"one")`
`Eval Error: denominator must be integer, got string (one)`

##### isFloat()

Syntax: `isFloat(<expr>) → bool`
Returns true if the value type of <expr> is float, false otherwise.

Examples

`>>>` `isFloat(4.)`
`true`
`>>>` `isFloat(4)`
`false`
`>>>` `isFloat("2.1")`
`false`

##### isFract()

Syntax: `isFract(<expr>) → bool`
Returns true if the value type of <expr> is fraction, false otherwise.

Examples

`>>>` `isFract(4.5)`
`false`
`>>>` `isFract(4:5)`
`true`
`>>>` `isFract(4)`
`false`
`>>>` `isFract(1.(3))`
`true`

##### isList()

Syntax: `isList(<expr>) → bool`
Returns true if the value type of <expr> is list, false otherwise.

Examples

`>>>` `isList([])`
`true`
`>>>` `isList([1, "2"])`
`true`
`>>>` `isList(1,2)`
`Eval Error: isList(): too many params — expected 1, got 2`

##### isNil()

Syntax: `isNil(<expr>) → bool`
Returns true if the value type of <expr> is nil, false otherwise.

Examples

`>>>` `isNil(nil)`
`true`
`>>>` `isNil(1)`
`false`

##### isRational()

Syntax: `isRational(<expr>) → bool`
Returns true if the value type of <expr> is fraction or int, false otherwise.

Examples

`>>>` `isRational(4.5)`
`false`
`>>>` `isRational(4:5)`
`true`
`>>>` `isRational(4)`
`true`
`>>>` `isRational(1.(3))`
`true`

##### isString()

Syntax: `isString(<expr>) → bool`
Returns true if the value type of <expr> is string, false otherwise.

Examples

`>>>` `isString("ciao")`
`true`
`>>>` `isString(2)`
`false`
`>>>` `isString(2+"2")`
`true`

##### bool()

Syntax: `bool(<expr>) → bool`
Returns a boolean value consisent with the value of the expression.

Examples

`>>>` `bool(1)`
`true`
`>>>` `bool(0)`
`false`
`>>>` `bool("")`
`false`
`>>>` `bool([])`
`false`
`>>>` `bool([1])`
`true`
`>>>` `bool({})`
`false`
`>>>` `bool({1: "one"})`
`true`

##### int()

Syntax: `int(<expr>) → int`
Returns an integer value consistent with the value of the expression.

Examples

`>>>` `int(2)`
`2`
`>>>` `int("2")`
`2`
`>>>` `int("0x1")`
`Eval Error: strconv.Atoi: parsing "0x1": invalid syntax`
`>>>` `int(0b10)`
`2`
`>>>` `int(0o2)`
`2`
`>>>` `int(0x2)`
`2`
`>>>` `int(1.8)`
`1`
`>>>` `int(5:2)`
`2`
`>>>` `int([])`
`Eval Error: int(): can’t convert list to int`
`>>>` `int(true)`
`1`
`>>>` `int(false)`
`0`

##### dec()

Syntax: `dec(<expr>) → float`
Returns a float value consistent with the value of the expression.

Examples

`>>>` `dec(2)`
`2`
`>>>` `dec(2.1)`
`2.1`
`>>>` `dec(2.3(1))`
`2.311111111111111`
`>>>` `dec("3.14")`
`3.14`
`>>>` `dec(3:4)`
`0.75`
`>>>` `dec(true)`
`1`
`>>>` `dec(false)`
`0`

##### string()

Syntax: `string(<expr>) → string`
Returns a string value consistent with the value of the expression.

Examples

`>>>` `string(2)`
`"2"`
`>>>` `string(0.8)`
`"0.8"`
`>>>` `string([1,2])`
`"[1, 2]"`
`>>>` `string({1: "one", 2: "two"})`
`"{1: "one", 2: "two"}"`
`>>>` `string(2:5)`
`"2:5"`
`>>>` `string(3==2)`
`"false"`

##### fract()

Syntax: `fract(<expr>) → fraction`
Returns a fraction value consistent with the value of the expression.

Examples

`>>>` `fract(2)`
`2:1`
`>>>` `fract(2.5)`
`5:2`
`>>>` `fract("2.5")`
`5:2`
`>>>` `fract(1.(3))`
`4:3`
`>>>` `fract([2])`
`Eval Error: fract(): can’t convert list to float`
`>>>` `fract(false)`
`0:1`
`>>>` `fract(true)`
`1:1`

##### char()

Syntax: `char(<intexpr>) → string`
Returns the character whose ASCII (soon Unicode too) code point is specified by the integer expression.

Examples

`>>>` `char(65)`
`"A"`
`>>>` `char(97)`
`"a"`

##### eval()

Syntax: `eval(<string-expr>) → any`
Computes and returns the value of the string expression.

Examples

`>>>` `eval( "2 + fract(1.(3))" )`
`10:3`

##### var()

Syntax:
`var(<string-expr>, <expr>) → any`
`var(<string-expr>) → any`

This function allows you to define variables whose names can include special characters. The first form of the function allows you to define a variable with a name specified by the first parameter and assign it the value of the second parameter. The second form only returns the value of the variable with the specified name.

Examples

`>>>` `var("$x", 3+9)`
`12`
`>>>` `var("$"+"x")`
`12`
`>>>` `var("gain%", var("$x"))`
`12`
`>>>` `var("gain%", var("$x")+1)`
`13`

##### set

Syntax:
`set(<string-expr>, <expr>) → any`

This function allows you to set the value of a variable whose name can include special characters. The first parameter is the name of the variable and the second parameter is the new value to assign to that variable.

It is equivalent to the first form of the var() function, but it is more explicit about the intent of changing the value of an existing variable.

Examples

`>>>` `set("$x", 100)`
`100`
`>>>` `var("$x")`
`100`

#### 7.1.2. Module "fmt"

to-do

##### print()

##### println()

#### 7.1.3. Module "import"

Module activation:
`BUILTIN "import"`

##### import()

Syntax:
`import(<source-file>)`

Loads the multi-expression contained in the specified source and returns its value.

##### importAll()

#### 7.1.4. Module "iterator"

Module activation:
`BUILTIN "iterator"`

##### run()

Syntax:
`run(<iterator>, <operator>, <vars>) → any`

Iterates over the specified iterator and applies the specified operator to the current value of the iterator.

#### 7.1.5. Module "math.arith"

Module activation:
`BUILTIN "math.arith"`

Currently, the "math.arith" module provides two functions, add() and mul(), that perform addition and multiplication of an arbitrary number of parameters. More functions will be added in the future.

- add()

- mul()

##### add()

Syntax:
`add(<num-expr1>, <num-expr2>, …​) → any`
`add(<list-of-num-expr>]) → any`
`add(<iterator-over-num-values>) → any`

Returns the sum of the values of the parameters. The parameters can be of any numeric type for which the `+` operator is defined. The result type depends on the types of the parameters. If all parameters are of the same type, the result is of that type. If the parameters are of different types, the result is of the type that can represent all the parameter types without loss of information. For example, if the parameters are a mix of integers and floats, the result is a float. If the parameters are a mix of number types, the result has the type of the most general one.

Examples

`>>>` `builtin "math.arith"`
`1`
`>>>` `add(1,2,3)`
`6`
`>>>` `add(1.1,2.1,3.1)`
`6.300000000000001`
`>>>` `add("1","2","3")`
`Eval Error: add(): param nr 1 (1 in 0) has wrong type string, number expected`
`>>>` `add(1:3, 2:3, 3:3)`
`2:1`
`>>>` `add(1, "2")`
`Eval Error: add(): param nr 2 (2 in 0) has wrong type string, number expected`
`>>>` `add([1,2,3])`
`6`
`>>>` `iterator=$([1,2,3]); add(iterator)`
`6`
`>>>` `add($([1,2,3]))`
`6`

##### mul()

Syntax:
`mul(<num-expr1>, <num-expr2>, …​) → any`
`mul(<list-of-num-expr>]) → any`
`mul(<iterator-over-num-values>) → any`

Same as add() but returns the product of the values of the parameters.

Examples

`>>>` `builtin "math.arith"`
`1`
`>>>` `mul(2,3,4)`
`24`

#### 7.1.6. Module "os.file"

Module activation:
`BUILTIN "os.file"`

The "os.file" module provides functions for working with files.

File related functions

- fileOpen()

- fileAppend()

- fileCreate()

- fileClose()

- fileWriteText()

- fileReadText()

- fileReadTextAll()

Iterator functions for files

- fileByteIterator()

- fileLineIterator()

More functions will be added in the future.

##### fileOpen()

Syntax:
`fileOpen(<file-path>) → file-handle`

Returns a file handle for the specified file path. The file is opened in read-write mode. If the file does not exist, it is created.

##### fileAppend()

Syntax:
`fileAppend(<file-path>) → any`

Like fileCreate() but write operations happen at the end of the file.

##### fileCreate()

Syntax:
`fileCreate(<file-path>) → file-handle`

Creates or truncates the named <file-path>. If the file already exists, it is truncated. If the file does not exist, it is created with mode 0o666 (before umask). The associated file descriptor has mode [O_RDWR]. The directory containing the file must already exist.

##### fileClose()

to-do

##### fileWriteText()

to-do

##### fileReadText()

to-do

##### fileReadTextAll()

to-do

##### fileByteIterator()

Syntax:
`fileByteIterator(handle-or-path) → iterator`

Returns an iterator that produces the bytes of the specified file. The parameter can be either a file handle or a file path. If a file path is provided, the file is opened and closed automatically by the iterator.

Examples

>>> builtin "os.file"
`1`
`>>>` `fileByteIterator("test-file.txt") map $_`
`[ 117, 110, 111, 10, 100, 117, 101, 10 ]`

>>> builtin ["os.file", "string"]
`2`
`>>>` `fileByteIterator("test-file.txt") map char($_)`
`[ "u", "n", "o", " ", "d", "u", "e", " ", ]`

##### fileLineIterator()

Syntax:
`fileLineIterator(handle-or-path) → iterator`

Returns an iterator that produces the lines of the specified file. The parameter can be either a file handle or a file path. If a file path is provided, the file is opened and closed automatically by the iterator.

Examples

`>>>` `builtin "os.file"`
`1`
`>>>` `fileLineIterator("test-file.txt") map $_`
`[ "uno", "due" ]`

#### 7.1.7. Module "string"

Module activation:
`BUILTIN "string"`

This module provides functions for working with strings.

Currently available functions:

- strJoin()

- strSub()

- strSplit()

- strTrim()

- strStartsWith()

- strEndsWith()

- strUpper()

- strLower()

##### strJoin()

Syntax:
`strJoin(<separator>, <item> …​) → string`

Returns a string obtained by concatenating the string items (sarting from the second argument), separated by the string value of the separator.

Examples

`>>>` `strJoin(", ", "one", "two", "three")`
`"one, two, three"`
`>>>` `strJoin(", ", ["one", "two", "three"])`
`"one, two, three"`

##### strSub()

Syntax:
`strSub(<string>, <start-index>=0, <count>=-1) → string`

Returns a substring of the specified string starting at the specified index and having the specified length. If the start index is negative, it is interpreted as an offset from the end of the string. If the count is negative, it means to take all characters until the end of the string.

Examples

`>>>` `strSub("Hello, world!", 7, 5)`
`"world"`
`>>>` `strSub("Hello, world!", -6, 5)`
`"world"`
`>>>` `strSub("Hello, world!", 7)`
`"world!"`
`>>>` `strSub("Hello, world!", -6)`
`"world!"`

##### strSplit()

Syntax:
`strSplit(<string>, <separator>="", <count>=-1) → list`

Returns a list of substrings obtained by splitting the specified string using the specified separator. If the separator is an empty string, the string is split into individual characters. If the count is negative, it means to split all occurrences of the separator.

Examples

`>>>` `strSplit("one, two, three", ", ")`
`["one", "two", "three"]`
`>>>` `strSplit("one, two, three", ", ", 2)`
`["one", "two, three"]`
`>>>` `strSplit("one, two, three")`
`["o", "n", "e", ",", " ", "t", "w", "o", ",", " ", "t", "h", "r", "e", "e"]`

##### strTrim()

Syntax:
`strTrim(<string>) → string`

Returns a string obtained by removing leading and trailing whitespace characters from the specified string.

Examples

`>>>` `strTrim(" Hello, world! ")`
`"Hello, world!"`

##### strStartsWith()

Syntax:
`strStartsWith(<string>, <prefix> …​) → bool`

Returns a boolean indicating whether the specified string starts with any of the given prefixes.

Examples

`>>>` `strStartsWith("Hello, world!", "Hello")`
`true`
`>>>` `strStartsWith("Hello, world!", "world")`
`false`
`>>>` `strStartsWith("Hello, world!", "Hi", "He")`
`true`

##### strEndsWith()

Syntax:
`strEndsWith(<string>, <suffix> …​) → bool`

Returns a boolean indicating whether the specified string ends with any of the given suffixes.

Examples

`>>>` `strEndsWith("Hello, world!", "world!")`
`true`
`>>>` `strEndsWith("Hello, world!", "Hello")`
`false`
`>>>` `strEndsWith("Hello, world!", "Hi", "world!")`
`true`

##### strUpper()

Syntax:
`strUpper(<string>) → string`

Returns a string obtained by converting all characters of the specified string to uppercase.

Examples

`>>>` `strUpper("Hello, world!")`
`"HELLO, WORLD!"`

##### strLower()

Syntax:
`strLower(<string>) → string`

Returns a string obtained by converting all characters of the specified string to lowercase.

Examples

`>>>` `strLower("Hello, world!")`
`"hello, world!"`

## 8. Iterators

Iterators are objects that can be used to traverse collections, such as lists and dictionaries. They are created by providing a data-source object, the collection, in a `$(<data-source>)` expression. Once an iterator is created, it can be used to access the elements of the collection one by one.

In general, data-sources are objects that can be iterated over. They are defined as dictionaries having the key `next` whose value is a function that returns the next element of the collection and updates the state of the iterator. The next function must return a special value, nil, when there are no more elements to iterate over.

Lists and dictionaries are implicit data-sources. The syntax for creating an iterator is as follows.

Example 17. Iterator creation syntax

iterator = "$(" data-source ")"
data-source = explicit | list-spec | dict-spec | custom-data-source

explicit = any-expr { "," any-expr }

list-spec = list range-options
list = "[" any-expr { "," any-expr } "]"
range-options = [ "," start-index [ "," end-index [ "," step ]]]
start-index, end-index, step = integer-expr

dict-spec = dict dict-options
dict = "{" key-value-pair { "," key-value-pair } "}"
key-value-pair = scalar-value ":" any-expr
scalar-value = string | number | boolean
dict-options = [ "," sort-order [ "," iter-mode ] ]
sort-order = asc-order | desc-order | no-sort | default-order
asc-order = "asc" | "a"
desc-order = "desc" | "d"
no-sort = "none" | "nosort" | "no-sort" | "n"
default-order = "default" | ""
iter-mode = keys-iter | values-iter | items-iter | default-iter
keys-iter = "key" | "keys" | "k"
values-iter = "value" | "values" | "v"
items-iter = "item" | "items" | "i"
default-iter = "default" | ""

custom-data-source = dict having the key `next` whose value is a function that returns the next element of the collection and updates the state of the iterator.

| Currently, default-order is the same as asc-order. In the future, it will be possible to specify a custom sorting function to define the default order.

| Currently, default-iter is the same as keys-iter. In the future, it will be possible to specify a custom iteration function to define the default iteration mode.

Example: iterator over an explicit list of elements

`>>>` `it = $("A", "B", "C")`
`$(#3)`
`>>>` `it++`
`"A"`
`>>>` `it++`
`"B"`
`>>>` `it++`
`"C"`
`>>>` `it++`
`Eval Error: EOF`

Example: iterator over a list

`>>>` `it = $(["one", "two", "three"])`
`$(#3)`
`>>>` `it++`
`"one"`
`>>>` `it++`
`"two"`
`>>>` `it++`
`"three"`
`>>>` `it++`
`Eval Error: EOF`

When creating a list-type iterator expression, you can specify a range of indices and a step to iterate over a subset of the list.

Indexing starts at 0. If the start index is not specified, it defaults to 0. If the end index is not specified, it defaults to the index of the last element of the list. If the step is not specified, it defaults to 1.

Negative indexes are allowed. They are interpreted as offsets from the end of the list. For example, an end index of -1 means the index of the last element of the list, an end index of -2 means the index of the second to last element of the list, and so on.

Negative steps are also allowed. They are interpreted as reverse iteration. For example, a step of -1 means to iterate over the list in reverse order.

Example: iterator over a list with index range and step

`>>>` `it = $([1, 2, 3, 4, 5], 1, 4, 2)`
`$(#5)`
`>>>` `it++`
`2`
`>>>` `it++`
`4`
`>>>` `it++`
`Eval Error: EOF`

Example: iterator over a list in reverse order

`>>>` `it = $([1, 2, 3], -1, 0, -1)`
`$(#3)`
`>>>` `it++`
`3`
`>>>` `it++`
`2`
`>>>` `it++`
`1`
`>>>` `it++`
`Eval Error: EOF`

### 8.1. Operators on iterators

The above example shows the use of the `++` operator to get the next element of an iterator. The `++` operator is a postfix operator that can be used with iterators. It returns the next element of the collection and updates the state of the iterator. When there are no more elements to iterate over, it returns the error Eval Error: EOF.

After the first use of the `++` operator, the prefixed operato `*` operator can be used to get the current element of the collection without updating the state of the iterator. When there are no more elements to iterate over, it returns the error Eval Error: EOF. Same error is returned if the `*` operator is used before the first use of the `++` operator.

Example: using the `*` operator

`>>>` `it = $(["one", "two", "three"])`
`$(#3)`
`>>>` `*it`
`Eval Error: EOF`
`>>>` `it++`
`"one"`
`>>>` `*it`
`"one"`

#### 8.1.1. Named operators

Named operators are operators that are identified by a name instead of a symbol. They are implicitly defined and can be called using a special syntax. For example, the `++` has the equivalent named operator `.next`.

Available named operators

- .next: same as `++`.

- .current: same as `*`.

- .reset: resets the state of the iterator to the initial state.

- .count: returns the number of elements in the iterator already visited.

- .index: returns the index of the current element in the iterator. Before the first use of the `++` operator, it returns the error -1.

| Iterators built on custom data-sources can provide additional named operators, depending on the functionality they want to expose. For example, an iterator over a list could provide a named operator called `.reverse` that returns the next element of the collection in reverse order.

Example: using the named operators

`>>>` `it = $(["one", "two", "three"])`
`$(#3)`
`>>>` `it.next`
`"one"`
`>>>` `it.current`
`"one"`
`>>>` `it.next`
`"two"`
`>>>` `it.reset`
`<nil>`
`>>>` `it.current`
`Eval Error: EOF`
`>>>` `it.next`
`"one"`

### 8.2. Infixed operators on iterators

There are also some infixed operators that can be used with iterators. They are defined as follows.

- cat operator

- digest operator

- filter operator

- groupby operator

- map operator

#### 8.2.1. `cat` operator

Syntax:
`<iterable> cat <iterable> → <iterator>`

`cat` operator takes two iterators or iterables and returns a new iterator that produces the elements of the first iterator followed by the elements of the second iterator.

#### 8.2.2. `filter` operator

Examples

Syntax:
`<iterable> filter <expr> → <iterator>`

`filter` applies a boolean expression to each element of the iterator and returns a list of the elements for which the expression evaluates to true.

#### 8.2.3. `groupby` operator

Syntax:
`<dict> groupby <key> → <dict>`

`groupby` operator groups the elements of the iterator based on the value of a specified expression and returns a dictionary where the keys are the group values and the values are lists of the elements in each group.

#### 8.2.4. `map` operator

Syntax:
`<iterable> map <expr> → <list>`

`map` operator iterates over the elements of the iterator and evaluates the expressions provided on the right side for each element. Its result is a list of the computed values of the that expression. The current element of the iterator is available in the expression as the variable `$_`.

Example: using the `map` operator

`>>>` `it = $(["one", "two", "three"])`
`$(#3)`
`>>>` `it map $_ + "!"`
`["one!", "two!", "three!"]`

### 8.3. Iterator over custom data-sources

It is possible to create iterators over custom data-sources by defining a dictionary that has the key `next` whose value is a function that returns the next element of the collection and updates the state of the iterator. The syntax for creating an iterator over a custom data-source is as follows.

TODO: custom data-sources

## 9. Plugins

TODO: plugins