TODO: Work in progress (last update on 2024/05/17, 7:30 a.m.)

1. Expr

Expr is a GO package capable of analysing, interpreting and calculating expressions.

1.2. dev-expr test tool

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 provides 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
expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@portale-stac.it)
	Based on the Expr package v0.10.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:
      source -- Load a file as input
         tty -- Enable/Disable ansi output (1)
        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

--- 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
    -p                 Print prefix form
    -t                 Print tree form (2)

>>>
1 Only available for single fraction values
2 Work in progress
REPL examples
[user]$ ./dev-expr
expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@portale-stac.it)
	Based on the Expr package v0.10.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
>>> 1+2 but 5|2+0.5 (4)
3
>>> 1+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 of the previous single expression but this it is obtained with two separated calculations.

2. Data types

Expr supports numerical, string, relational, boolean expressions, and mixed-type lists.

2.1. Numbers

Expr supports three type of numbers:

  1. Integers

  2. Floats

  3. Factions

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

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

/

Division

Divide the left value by the right one(*)

-10 / 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

/

Division

Divide the left value by the right one

1.0 / 2 → 0.5

./

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 vertical bar |.

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] "(" 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

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 can also be used with 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 items of strings can be accessed using the dot . operator.

Example 4. Item access syntax

item = string-expr "." integer-expr

String examples

>>> s="abc" assign the string to variable s
abc
>>> s.1 char at position 1 (starting from 0)
b
>>> s.(-1) char at position -1, the rightmost one
c
>>> #s number of chars
3
>>> #"abc" number of chars
3

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 == 2false
"a" == "a"true

!=

Not Equal

True if the left value is NOT equal to the right one

5 != 2true
"a" != "a"false

<

Less

True if the left value is less than the right one

5 < 2false
"a" < "b"true

<=

Less or Equal

True if the left value is less than or equal to the right one

5 <= 2false
"b" <= "b"true

>

Greater

True if the left value is greater than the right one

5 > 2true
"a" < "b"false

>=

Greater or Equal

True if the left value is greater than or equal to the right one

5 >= 2true
"b" <= "b"true

Table 5. Boolean operators
Symbol Operation Description Examples

NOT

Not

True if the right value is false

NOT truefalse
NOT (2 < 1)true

AND / &&

And

True if both left and right values are true

false && truefalse
"a" < "b" AND NOT (2 < 1)true

OR / ||

Or

True if at least one of the left and right values integers true

false or truetrue
"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 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.

2.4. Lists

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

Example 5. 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]

The items of array can be accessed using the dot . operator.

Example 6. Item access syntax

item = list-expr "." list-expr

Items of list

>>> [1,2,3].1
2
>>> list=[1,2,3]; list.1
2
>>> ["one","two","three"].1
two
>>> list=["one","two","three"]; list.(2-1)
two
>>> list.(-1)
three
>>> list.(10)
Eval Error: [1:9] index 10 out of bounds
>>> #list
3

2.5. Dictionaries

Support for dictionaries is still ongoing.

The dictionary, or dict, data-type is set of pairs key/value. It is also known as map or associative array. Dictionary literals are sequences of pairs separated by comma ,; sequences are enclosed between brace brackets.

Example 7. Dict literal syntax

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

Examples

>>> {1:"one", 2:"two"}
>>> {"one":1, "two": 2}
>>> {"sum":1+2+3, "prod":1*2*3}

3. Variables

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

Example 8. 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
1+2
>>> a_b
3
>>> x = 5.2 * (9-3) // The assigned value has the approximation error typical 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.

An expression that contains ; is called a multi-expression and each component expressione 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 automatica 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 returns 2, x=2*3 but x-1 returns 5.

but is very similar to ;. The only difference is that ; can’t be used inside parenthesis ( and ).

4.3. Assignment operator =

The assignment operator = is used to define variables in the evaluation context or to change their value (see ExprContext). The value on the left side of = must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.

Example

>>> a=15+1 16

4.4. Selector operator ? : ::

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

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'
[green]
>>>` 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.5. Variable default value ?? and ?=

The left operand of these two operators must be a variable. The right operator can be any expression. They return the value of the variable if this is define; otherwise they return the value of the right expression.

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

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

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

Examples

>>> var ?? (1+2)’ [green]`3
>>> var
Eval Error: undefined variable or function "var"
>>> var ?= (1+2)
3
>>> var
3

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 7. Operators priorities
Priority Operators Position Operation Operands and results

ITEM

.

Infix

Item

collection "." keyany

INC

++

Postfix

Post increment

integer-variable "++"integer

++

Postfix

Next item

iterator "++"any

FACT

!

Postfix

Factorial

integer "!"integer

SIGN

+, -

Prefix

Change-sign

("+“|”-") numbernumber

#

Prefix

Lenght-of

"#" collectioninteger

#

Prefix

Size-of

"#" iteratorinteger

PROD

*

Infix

Product

number "*" numbernumber

*

Infix

String-repeat

string "*" integerstring

/

Infix

Division

number "/" numbernumber

./

Infix

Float-division

number "./" numberfloat

%

Infix

Integer-remainder

integer "%" integerinteger

SUM

+

Infix

Sum

number "+" numbernumber

+

Infix

String-concat

(string|number) "+" (string|number) → string

+

Infix

List-join

list "+" listlist

-

Infix

Subtraction

number "-" numbernumber

-

Infix

List-difference

list "-" listlist

RELATION

<

Infix

less

comparable "<" comparableboolean

<=

Infix

less-equal

comparable "<=" comparableboolean

>

Infix

greater

comparable ">" comparableboolean

>=

Infix

greater-equal

comparable ">=" comparableboolean

==

Infix

equal

comparable "==" comparableboolean

!=

Infix

not-equal

comparable "!=" comparableboolean

NOT

not

Prefix

not

"not" booleanboolean

AND

and

Infix

and

boolean "and" booleanboolean

&&

Infix

and

boolean "&&" booleanboolean

OR

or

Infix

or

boolean "or" booleanboolean

||

Infix

or

boolean "||" booleanboolean

ASSIGN

=

Infix

assignment

identifier "=" anyany

BUT

but

Infix

but

any "but" anyany

6. Functions

Functions in Expr are very similar to functions in many programming languages.

In Expr functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator @ it is possibile to export local definition to the calling context.

6.1. Function calls

TODO: function calls operations

6.2. Function definitions

TODO: function definitions operations

7. Builtins

TODO: builtins

7.2. import()

import(<source-file>) loads the multi-expression contained in the specified source and returns its value.