Compare commits

...

40 Commits

Author SHA1 Message Date
camoroso d84e690ef3 deep list inclusion and item membership implemented 2024-05-29 13:03:58 +02:00
camoroso 4b25a07699 commented code removed 2024-05-28 07:28:33 +02:00
camoroso 3736214c5a A lot of changes. Main ones are:
- fraction type renamed as FractionType and moved from operator-fraction.go to fraction-type.go
- ListType moved from operator-list.go to list-type.go
- all test file were renamed adding the "t_" prefix
- defined a test template in file t_temple_test.go
- new test file t_relational_test.go where relational tests are collected
- lists can now compared as set using operators <, <=, >, and >= (IMPORTANT: here = menas same content, not same list)
2024-05-28 07:26:05 +02:00
camoroso 78cbb7b36f test index/5 moved to list/26 because reflection's deep-equal function returns false even though computed and wanted lists are equal 2024-05-26 06:30:42 +02:00
camoroso 2c87d6bf9e Eprx now supports range of indeces to extract parts of strings or lists 2024-05-26 06:19:08 +02:00
camoroso 691c213d17 operator-dot.go: the '.' (dot) operator can now only be used to call member functions of iterators 2024-05-25 03:35:17 +02:00
camoroso fa136cb70b parser.go: square brackets are also allowed after a variable 2024-05-25 03:32:13 +02:00
camoroso 76dd01afcd strings_test.go: test nr 5,6 fixed 2024-05-25 03:30:26 +02:00
camoroso 4283fab816 list_test.go: test nr 15,16,17 fixed 2024-05-25 03:28:01 +02:00
camoroso 03d4c192c2 new syntax to get items from collection: collection[index]. Supported collections are string, list and dict 2024-05-24 22:51:01 +02:00
camoroso e5f63c3d9d function definition and usage rationalized 2024-05-24 06:28:48 +02:00
camoroso d545a35acf local var renamed 2024-05-24 04:23:03 +02:00
camoroso e4275e2cb6 simple-var-store.go and simple-func-store.go merged in a single file named simple-store.go 2024-05-23 07:46:31 +02:00
camoroso 1ff5770264 New interface and implementation to model function parameters 2024-05-22 20:52:44 +02:00
camoroso ba32e2dccf Doc: more details on some operators 2024-05-20 15:50:45 +02:00
camoroso f22b5a6f0b Doc: more details on some operators 2024-05-20 06:59:49 +02:00
camoroso 7c8dbb0ac7 Added virtual symbol SymVariable translated from general real SymIdentifier symbol 2024-05-20 06:05:24 +02:00
camoroso e5c5920db0 parser_test.go: incompatible type error corrected 2024-05-20 05:32:28 +02:00
camoroso 61efdb4eef operator-fraction.go: Typer interface implementation 2024-05-20 05:31:20 +02:00
camoroso 82ec78719d operand-list.go: ToString() now can handle the Truncate option 2024-05-20 05:30:26 +02:00
camoroso 554ff1a9dd operator-sum.go: better type checking when adding fractions 2024-05-20 05:27:44 +02:00
camoroso 6bb891e09d term.go: Error messagge about incompatible types now truncates long values 2024-05-20 05:26:33 +02:00
camoroso 1c4ffd7d64 formatter.go: Truncate function and number type names 2024-05-20 05:25:04 +02:00
camoroso b92b19e1dd New interface to Typer: the function TypeName() returns a more readable type name 2024-05-19 02:23:28 +02:00
camoroso 9967918418 operator-sum.go: adding item to a list is no more allowed. The sum operator '+' now ca only join two list. 2024-05-19 02:20:36 +02:00
camoroso 6c14c07d66 operand-iterator.go: adapted to the new DictType 2024-05-19 01:47:06 +02:00
camoroso 9ea170e53b new operator 'in': it returns true if a item belongs to a list or if a key belongs to a dict 2024-05-19 01:44:50 +02:00
camoroso a543360151 when the list value involved in an insert or append operations (directly) comes from a variable, that variable receives the result list 2024-05-19 01:42:15 +02:00
camoroso 24a25bbf94 adapted and enhanced the dict operations to make them compatible with the new DictType 2024-05-19 01:38:07 +02:00
camoroso d6a1607041 The content of round bracket now returns an expressione term. This way the content is decoupled from external terms. 2024-05-19 01:34:07 +02:00
camoroso 4d43ab2c2f context.go: setVar() renamed as UnsafeSetVar() 2024-05-19 01:27:44 +02:00
camoroso 9bd4a0ba23 utils.go:fromGenericAny() now supports also ListType and DictType 2024-05-19 01:21:06 +02:00
camoroso 2b184cf3f2 operand-map.go replaced by operand-dict.go 2024-05-19 01:20:04 +02:00
camoroso 263e419d9a operand-map.go: to be removed 2024-05-18 08:54:18 +02:00
camoroso c39970fa7e new operator 'in' added. It check if an item is member of a list, or if a key is contained in a dictionary 2024-05-18 07:47:41 +02:00
camoroso 14bb9e942b operator-fraction.go: added link to a tutorial about fractions 2024-05-18 07:07:17 +02:00
camoroso 9451958218 utils_test.go: new test file 2024-05-18 07:06:06 +02:00
camoroso 91fdc1926e Doc: updated last change date and time 2024-05-17 15:48:17 +02:00
camoroso 9a3abdf1b6 Doc: more details on the syntax of the selector and variable default operators 2024-05-17 15:46:56 +02:00
camoroso ac3e690f87 Doc: more details on the syntax of the dictionaries, variables and multi-expressions 2024-05-17 07:31:13 +02:00
63 changed files with 3050 additions and 1222 deletions
+2 -2
View File
@@ -119,7 +119,7 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
if self.forest != nil {
for _, root := range self.forest {
if result, err = root.compute(ctx); err == nil {
ctx.setVar(ControlLastResult, result)
ctx.UnsafeSetVar(ControlLastResult, result)
} else {
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
break
@@ -128,7 +128,7 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
}
if err == nil {
result, err = self.root.compute(ctx)
ctx.setVar(ControlLastResult, result)
ctx.UnsafeSetVar(ControlLastResult, result)
}
// } else {
// err = errors.New("empty expression")
+19
View File
@@ -5,7 +5,26 @@
package expr
const (
paramCount = "count"
paramItem = "item"
paramParts = "parts"
paramSeparator = "separator"
paramSource = "source"
paramSuffix = "suffix"
paramPrefix = "prefix"
paramStart = "start"
paramEnd = "end"
paramValue = "value"
paramEllipsis = "..."
typeFilepath = "filepath"
typeDirpath = "dirpath"
)
// const (
// typeInteger = "int"
// typeFloat = "float"
// typeString = "string"
// typeFraction = "fract"
// typeList = "list"
// typeDict = "dict"
// )
+5 -1
View File
@@ -5,10 +5,14 @@
package expr
const (
typeAny = "any"
typeBoolean = "boolean"
typeFloat = "decimal"
typeFloat = "float"
typeFraction = "fraction"
typeHandle = "handle"
typeInt = "integer"
typeItem = "item"
typeNumber = "number"
typePair = "pair"
typeString = "string"
)
+4 -2
View File
@@ -15,14 +15,16 @@ func exportVar(ctx ExprContext, name string, value any) {
if name[0] == '@' {
name = name[1:]
}
ctx.setVar(name, value)
ctx.UnsafeSetVar(name, value)
}
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
if name[0] == '@' {
name = name[1:]
}
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
// ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
// ctx.RegisterFuncInfo(name, info)
ctx.RegisterFunc(name, info.Functor(), info.ReturnType(), info.Params())
}
func exportObjects(destCtx, sourceCtx ExprContext) {
+16 -11
View File
@@ -4,28 +4,31 @@
// context.go
package expr
// ---- Function template
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
// ---- Functor interface
type Functor interface {
Invoke(ctx ExprContext, name string, args []any) (result any, err error)
SetFunc(info ExprFunc)
GetFunc() ExprFunc
}
type simpleFunctor struct {
f FuncTemplate
}
func (functor *simpleFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
return functor.f(ctx, name, args)
// ---- Function Param Info
type ExprFuncParam interface {
Name() string
Type() string
IsOptional() bool
IsRepeat() bool
DefaultValue() any
}
// ---- Function Info
type ExprFunc interface {
Formatter
Name() string
MinArgs() int
MaxArgs() int
Functor() Functor
Params() []ExprFuncParam
ReturnType() string
}
// ----Expression Context
@@ -33,10 +36,12 @@ type ExprContext interface {
Clone() ExprContext
GetVar(varName string) (value any, exists bool)
SetVar(varName string, value any)
setVar(varName string, value any)
UnsafeSetVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string)
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
GetFuncInfo(name string) (item ExprFunc, exists bool)
Call(name string, args []any) (result any, err error)
RegisterFunc(name string, f Functor, minArgs, maxArgs int)
// RegisterFunc(name string, f Functor, minArgs, maxArgs int)
RegisterFuncInfo(info ExprFunc)
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error
}
+192 -101
View File
@@ -22,7 +22,7 @@ Expressions calculator
toc::[]
#TODO: Work in progress (last update on 2024/05/16, 7:08 a.m.)#
#TODO: Work in progress (last update on 2024/05/20, 06:58 a.m.)#
== Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
@@ -44,7 +44,7 @@ Here are some examples of execution.
.Run `dev-expr` in REPL mode and ask for help
[source,shell]
----
# Assume the expr source directory. Type 'exit' or Ctrl+D to quit the program.
# 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)
@@ -122,7 +122,7 @@ _Expr_ supports three type of numbers:
. [blue]#Integers#
. [blue]#Floats#
. [blue]#Factions# internally are stored as _pairs of_ Golang _int64_ values.
. [blue]#Factions#
In mixed operations involving integers, fractions and floats, automatic type promotion to the largest type take place.
@@ -183,7 +183,7 @@ _dec-seq_ = _see-integer-literal-syntax_
`>>>` [blue]`4.5E-3` +
[green]`0.0045` +
`>>>` [blue]`4.5E10` +
[green]`4.5e+10` +
[green]`4.5e+10`
.Arithmetic operators
@@ -207,7 +207,7 @@ _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_ +
_digit-seq_ = _see-integer-literal-syntax_
====
.Examples
@@ -235,12 +235,13 @@ _digit-seq_ = _see-integer-literal-syntax_ +
Fractions can be used together with integers and floats in expressions.
.Examples
`>>>` [blue]`1|2 + 5` +
[green]`11|2` +
`>>>` [blue]`4 - 1|2` +
[green]`7|2` +
`>>>` [blue]`1.0 + 1|2` +
[green]`1.5` +
[green]`1.5`
@@ -265,10 +266,10 @@ Some arithmetic operators can also be used with strings.
|===
| Symbol | Operation | Description | Examples
| [blue]`+` | _concatenation_ | Join two strings or two _stringable_ values | [blue]`"one" + "two"` _["onetwo"]_ +
[blue]`"one" + 2` _["one2"]_
| [blue]`+` | _concatenation_ | Join two strings or two _stringable_ values | [blue]`"one" + "two"` -> _"onetwo"_ +
[blue]`"one" + 2` -> _"one2"_
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` _["oneone"]_
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` -> _"oneone"_
|===
The items of strings can be accessed using the dot `.` operator.
@@ -279,21 +280,22 @@ _item_ = _string-expr_ "**.**" _integer-expr_
====
.String examples
`>>>` [blue]`s="abc"` [gray]_assign the string to variable s_ +
`>>>` [blue]`s="abc"` [gray]_// assign the string to variable s_ +
[green]`abc` +
`>>>` [blue]`s.1` [gray]_char at position 1 (starting from 0)_ +
`>>>` [blue]`s.1` [gray]_// char at position 1 (starting from 0)_ +
[green]`b` +
`>>>` [blue]`s.(-1)` [gray]_char at position -1, the rightmost one_ +
`>>>` [blue]`s.(-1)` [gray]_// char at position -1, the rightmost one_ +
[green]`c` +
`>>>` [blue]`\#s` [gray]_number of chars_ +
`>>>` [blue]`\#s` [gray]_// number of chars_ +
[gren]`3` +
`>>>` [blue]`#"abc"` [gray]_number of chars_ +
[green]`3` +
`>>>` [blue]`#"abc"` [gray]_// number of chars_ +
[green]`3`
=== Boolean
=== Booleans
Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values.
.Relational operators
.Relational operators^(*)^
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
@@ -312,6 +314,8 @@ Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relationa
[blue]`"b" \<= "b"` -> _true_
|===
^(*)^ See also the [blue]`in` operator in the _list_ and _dictionary_ sections.
.Boolean operators
[cols="^2,^2,5,4"]
@@ -341,35 +345,47 @@ Currently, boolean operations are evaluated using _short cut evaluation_. This m
====
=== Lists
_Expr_ supports list of mixed-type values, also specified by normal expressions.
_Expr_ supports list of mixed-type values, also specified by normal expressions. Internally, _Expr_'s lists are Go arrays.
.List examples
[source,go]
----
[1, 2, 3] // List of integers
["one", "two", "three"] // List of strings
["one", 2, false, 4.1] // List of mixed-types
["one"+1, 2.0*(9-2)] // List of expressions
[ [1,"one"], [2,"two"]] // List of lists
----
.List literal syntax
====
*_list_* = _empty-list_ | _non-empty-list_ +
_empty-list_ = "**[]**" +
_non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value} "**]**" +
====
.Examples
`>>>` [blue]`[1,2,3]` [gray]_// List of integers_ +
[green]`[1, 2, 3]` +
`>>>` [blue]`["one", "two", "three"]` [gray]_// List of strings_ +
[green]`["one", "two", "three"]` +
`>>>` [blue]`["one", 2, false, 4.1]` [gray]_// List of mixed-types_ +
[green]`["one", 2, false, 4.1]` +
`>>>` [blue]`["one"+1, 2.0*(9-2)]` [gray]_// List of expressions_ +
[green]`["one1", 14]` +
`>>>` [blue]`[ [1,"one"], [2,"two"]]` [gray]_// List of lists_ +
[green]`[[1, "one"], [2, "two"]]`
.List operators
[cols="^2,^2,5,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`+` | _Join_ | Joins two lists | [blue]`[1,2] + [3]` _[ [1,2,3] ]_
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` _[ [1,3] ]_
| [blue]`+` | _Join_ | Joins two lists | [blue]`[1,2] + [3]` -> _[1,2,3]_
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` -> _[1,3]_
| [blue]`>>` | _Front insertion_ | Insert an item in front | [blue]`0 >> [1,2]` -> _[0,1,2]_
| [blue]`<<` | _Back insertion_ | Insert an item at end | [blue]`[1,2] << 3` -> _[1,2,3]_
| [blue]`.` | _List item_ | Item at given position | [blue]`[1,2.3].1` -> _2_
| [blue]`in` | _Item in list_ | True if item is in list | [blue]`2 in [1,2,3]` -> _true_ +
[blue]`6 in [1,2,3]` -> _false_
|===
The items of array can be accessed using the dot `.` operator.
.Item access syntax
[source,bnf]
----
<item> ::= <list-expr>"."<index-expr>
----
====
_item_ = _list-expr_ "**.**" _integer-expr_
====
.Items of list
`>>>` [blue]`[1,2,3].1` +
@@ -385,113 +401,183 @@ The items of array can be accessed using the dot `.` operator.
`>>>` [blue]`list.(10)` +
[red]`Eval Error: [1:9] index 10 out of bounds` +
`>>>` [blue]`#list` +
[green]`3`
[green]`3` +
`>>>` [blue]`index=2; ["a", "b", "c", "d"].index` +
[green]`c`
== Dictionaries
The _dictionary_ 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.
=== Dictionaries
The _dictionary_, or _dict_, data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_.
.Dictionary examples
[source,go]
----
{1:"one", 2:"two"}
{"one":1, "two": 2}
{"sum":1+2+3, "prod":1*2*3}
----
Dictionary literals are sequences of pairs separated by comma [blue]`,` enclosed between brace brackets.
WARNING: Support for dictionaries is still ongoing.
== Variables
A variable is an identifier with an assigned value. Variables are stored in the object that implements the Go _ExprContext_ interface, e.g. _SimpleVarStore_ or _SimpleFuncStore_.
.Dict literal syntax
====
*_dict_* = _empty-dict_ | _non-empty-dict_ +
_empty-dict_ = "**{}**" +
_non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar_ "**:**" _any-value} "**}**" +
====
.Examples
[source,go]
----
a=1
x = 5.2 * (9-3)
x = 1; y = 2*x
----
`>>>` [blue]`{1:"one", 2:"two"}` +
[green]`{1: "one", 2: "two"}` +
`>>>` [blue]`{"one":1, "two": 2}` +
[green]`{"one": 1, "two": 2}` +
`>>>` [blue]`{"sum":1+2+3, "prod":1*2*3}` +
[green]`{"sum": 6, "prod": 6}`
.Dict operators
[cols="^2,^2,4,5"]
|===
| Symbol | Operation | Description | Examples
| [blue]`+` | _Join_ | Joins two dicts | [blue]`{1:"one"}+{6:"six"}` -> _{1: "one", 6: "six"}_
| [blue]`.` | _Dict item value_ | Item value of given key | [blue]`{"one":1, "two":2}."two"` -> _2_
| [blue]`in` | _Key in dict_ | True if key is in dict | [blue]`"one" in {"one":1, "two":2}` -> _true_ +
[blue]`"six" in {"one":1, "two":2}` -> _false_
|===
== Variables
_Expr_, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in _contexts_.
.Variable literal syntax
====
*_variable_* = _identifier_ "*=*" _any-value_ +
_identifier_ = _alpha_ {(_alpha_)|_dec-digit_|"*_*"} +
__alpha__ = "*a*"|"*b*"|..."*z*"|"*A*"|"*B*"|..."*Z*"
====
NOTE: The assign operator [blue]`=` returns the value assigned to the variable.
.Examples
`>>>` [blue]`a=1` +
[green]`1` +
`>>>` [blue]`a_b=1+2` +
[green]`1+2` +
`>>>` [blue]`a_b` +
[green]`3` +
`>>>` [blue]`x = 5.2 * (9-3)` [gray]_// The assigned value has the approximation error typical of the float data-type_ +
[green]`31.200000000000003` +
`>>>` [blue]`x = 1; y = 2*x` +
[green]`2` +
`>>>` [blue]`_a=2` +
[red]`Parse Error: [1:2] unexpected token "_"` +
`>>>` [blue]`1=2` +
[red]`Parse Error: assign operator ("=") must be preceded by a variable`
== Other operations
=== [blue]`;` operator
The semicolon operator [blue]`;` is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The latter is the final result.
The semicolon operator [blue]`;` 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 [blue]`;` is called a _multi-expression_ and each component expressione is called a _sub-expression_.
IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions.
TIP: [blue]`;` can be used to set some variables before the final calculation.
.Example
[source,go]
----
a=1; b=2; c=3; a+b+c // returns 6
----
`>>>` [blue]`a=1; b=2; c=3; a+b+c` +
[green]`6`
The value of each sub-expression is stored in the automatica variable _last_.
.Example
`>>>` [blue]`2+3; b=last+10; last` +
[green]`15`
=== [blue]`but` operator
[blue]`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: [blue]`5 but 2` returns 2, [blue]`x=2*3 but x-1` returns 5.
[blue]`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.
[blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`(` and [blue]`)`.
.Examples
[blue]`5 but 2` +
[green]`2` +
[blue]`x=2*3 but x-1` +
[green]`5`.
[blue]`but` behavior is very similar to [blue]`;`. The only difference is that [blue]`;` is not a true operator and can't be used inside parenthesis [blue]`(` and [blue]`)`.
=== Assignment operator [blue]`=`
The assignment operator [blue]`=` is used to define variables in the evaluation context or to change their value (see _ExprContext_).
The assignment operator [blue]`=` is used to define variables or to change their value in the evaluation context (see _ExprContext_).
The value on the left side of [blue]`=` must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.
.Example
[source,go]
----
a=15+1 // returns 16
----
`>>>` [blue]`a=15+1`
[green]`16`
=== Selector operator [blue]`? : ::`
The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages.
.Syntax
[source,bnf]
----
<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>}
----
.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 expression (`<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. 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.
In other words, the selector operator evaluates the _select-expression_ on the left-hand side of the [blue]`?` 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 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.
The [blue]`:` 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 [blue]`::` symbol (double-colon). Also note that the default expression has no _match-list_.
.Examples
[source,go]
----
`>>>` [blue]`1 ? {"a"} : {"b"}`
[green]`b`
`>>>` [blue]`10 ? {"a"} : {"b"} :: {"c"}`
[green]`c'
[green]`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`
[green]`b`
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`
[red]`Parse Error: [1:34] case list in default clause`
[green]`>>>` [blue]`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`
[green]`b`
`>>>` [blue]`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`
[green]`b`
`>>>` [blue]`10 ? {"a"} : {"b"}`
`>>>` [blue]`1 ? {"a"} : {"b"}` +
[green]`b` +
`>>>` [blue]`10 ? {"a"} : {"b"} :: {"c"}` +
[green]`c' +
[green]`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}` +
[green]`b` +
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}` +
[red]`Parse Error: [1:34] case list in default clause` +
[green]`>>>` [blue]`10 ? {"a"} :[10] {x="b" but x} :: {"c"}` +
[green]`b` +
`>>>` [blue]`10 ? {"a"} :[10] {x="b"; x} :: {"c"}` +
[green]`b` +
`>>>` [blue]`10 ? {"a"} : {"b"}` +
[red]`Eval Error: [1:3] no case catches the value (10) of the selection expression`
----
=== Variable default value [blue]`??` and [blue]`?=`
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.
IMPORTANT: If the left variable is defined, the right expression is not evuated at all.
The [blue]`??` do not change the status of the left variable.
The [blue]`?=` assigns the calculated value of the right expression to the left variable.
.Examples
`>>>` [blue]`var ?? (1+2)`'
[green]`3` +
`>>>` [blue]`var` +
[red]`Eval Error: undefined variable or function "var"` +
`>>>` [blue]`var ?= (1+2)` +
[green]`3` +
`>>>` [blue]`var` +
[green]`3`
NOTE: These operators have a high priority, in particular higher than the operator [blue]`=`.
== Priorities of operators
The table below shows all supported operators by decreasing priorities.
.Operators priorities
[cols="^2,^2,^2,^5,^5"]
[cols="^2,^2,^2,^5,^6"]
|===
| Priority | Operators | Position | Operation | Operands and results
.1+|*ITEM*| [blue]`.` | _Infix_ | _Item_| _collection_ `"."` _key_ -> _any_
.2+|*ITEM*| [blue]`.` | _Infix_ | _List item_| _list_ `"."` _integer_ -> _any_
| [blue]`.` | _Infix_ | _Dict item_ | _dict_ `"."` _any_ -> _any_
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_
@@ -503,24 +589,29 @@ The table below shows all supported operators by decreasing priorities.
| [blue]`/` | _Infix_ | _Division_ | _number_ `"/"` _number_ -> _number_
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `"./"` _number_ -> _float_
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `"%"` _integer_ -> _integer_
.5+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_
.6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `"+"` (_string_\|_number_) -> _string_
| [blue]`+` | _Infix_ | _List-join_ | _list_ `"+"` _list_ -> _list_
| [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `"+"` _dict_ -> _dict_
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `"-"` _number_ -> _number_
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `"-"` _list_ -> _list_
.6+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_
.8+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `"\<="` _comparable_ -> _boolean_
| [blue]`>` | _Infix_ | _greater_ | _comparable_ `">"` _comparable_ -> _boolean_
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ `">="` _comparable_ -> _boolean_
| [blue]`==` | _Infix_ | _equal_ | _comparable_ `"=="` _comparable_ -> _boolean_
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ `"!="` _comparable_ -> _boolean_
| [blue]`in` | _Infix_ | _member-of-list_ | _any_ `"in"` _list_ -> _boolean_
| [blue]`in` | _Infix_ | _key-of-dict_ | _any_ `"in"` _dict_ -> _boolean_
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | `"not"` _boolean_ -> _boolean_
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ `"and"` _boolean_ -> _boolean_
| [blue]`&&` | _Infix_ | _and_ | _boolean_ `"&&"` _boolean_ -> _boolean_
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ `"or"` _boolean_ -> _boolean_
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ `"\|\|"` _boolean_ -> _boolean_
.1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
| [blue]`>>` | _Infix_ | _front-insert_ | _any_ ">>" _list_ -> _list_
| [blue]`<<` | _Infix_ | _back-insert_ | _list_ "<<" _any_ -> _list_
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|===
== Functions
+360 -147
View File
@@ -549,31 +549,32 @@ pre.rouge .ss {
</ul>
</li>
<li><a href="#_strings">2.2. Strings</a></li>
<li><a href="#_boolean">2.3. Boolean</a></li>
<li><a href="#_booleans">2.3. Booleans</a></li>
<li><a href="#_lists">2.4. Lists</a></li>
<li><a href="#_dictionaries">2.5. Dictionaries</a></li>
</ul>
</li>
<li><a href="#_dictionaries">3. Dictionaries</a></li>
<li><a href="#_variables">4. Variables</a></li>
<li><a href="#_other_operations">5. Other operations</a>
<li><a href="#_variables">3. Variables</a></li>
<li><a href="#_other_operations">4. Other operations</a>
<ul class="sectlevel2">
<li><a href="#_operator">5.1. <code class="blue">;</code> operator</a></li>
<li><a href="#_but_operator">5.2. <code class="blue">but</code> operator</a></li>
<li><a href="#_assignment_operator">5.3. Assignment operator <code class="blue">=</code></a></li>
<li><a href="#_selector_operator">5.4. Selector operator <code class="blue">? : ::</code></a></li>
<li><a href="#_operator">4.1. <code class="blue">;</code> operator</a></li>
<li><a href="#_but_operator">4.2. <code class="blue">but</code> operator</a></li>
<li><a href="#_assignment_operator">4.3. Assignment operator <code class="blue">=</code></a></li>
<li><a href="#_selector_operator">4.4. Selector operator <code class="blue">? : ::</code></a></li>
<li><a href="#_variable_default_value_and">4.5. Variable default value <code class="blue">??</code> and <code class="blue">?=</code></a></li>
</ul>
</li>
<li><a href="#_priorities_of_operators">6. Priorities of operators</a></li>
<li><a href="#_functions">7. Functions</a>
<li><a href="#_priorities_of_operators">5. Priorities of operators</a></li>
<li><a href="#_functions">6. Functions</a>
<ul class="sectlevel2">
<li><a href="#_function_calls">7.1. Function calls</a></li>
<li><a href="#_function_definitions">7.2. Function definitions</a></li>
<li><a href="#_function_calls">6.1. Function calls</a></li>
<li><a href="#_function_definitions">6.2. Function definitions</a></li>
</ul>
</li>
<li><a href="#_builtins">8. Builtins</a>
<li><a href="#_builtins">7. Builtins</a>
<ul class="sectlevel2">
<li><a href="#_builtin_functions">8.1. Builtin functions</a></li>
<li><a href="#_import">8.2. <em class="blue">import()</em></a></li>
<li><a href="#_builtin_functions">7.1. Builtin functions</a></li>
<li><a href="#_import">7.2. <em class="blue">import()</em></a></li>
</ul>
</li>
</ul>
@@ -584,7 +585,7 @@ pre.rouge .ss {
<div class="sectionbody">
<!-- toc disabled -->
<div class="paragraph">
<p><mark>TODO: Work in progress (last update on 2024/05/16, 7:08 a.m.)</mark></p>
<p><mark>TODO: Work in progress (last update on 2024/05/20, 06:58 a.m.)</mark></p>
</div>
</div>
</div>
@@ -622,7 +623,7 @@ pre.rouge .ss {
<div class="listingblock">
<div class="title">Run <code>dev-expr</code> in REPL mode and ask for help</div>
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="c"># Assume the expr source directory. Type 'exit' or Ctrl+D to quit the program.</span>
<pre class="rouge highlight"><code data-lang="shell"><span class="c"># Type 'exit' or Ctrl+D to quit the program.</span>
<span class="o">[</span>user]<span class="nv">$ </span>./dev-expr
<span class="nb">expr</span> <span class="nt">--</span> Expressions calculator v1.7.1<span class="o">(</span>build 2<span class="o">)</span>,2024/05/16 <span class="o">(</span>celestino.amoroso@portale-stac.it<span class="o">)</span>
@@ -742,7 +743,7 @@ pre.rouge .ss {
<p><span class="blue">Floats</span></p>
</li>
<li>
<p><span class="blue">Factions</span> internally are stored as <em>pairs of</em> Golang <em>int64</em> values.</p>
<p><span class="blue">Factions</span></p>
</li>
</ol>
</div>
@@ -854,7 +855,7 @@ pre.rouge .ss {
<code>&gt;&gt;&gt;</code> <code class="blue">4.5E-3</code><br>
<code class="green">0.0045</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">4.5E10</code><br>
<code class="green">4.5e+10</code><br></p>
<code class="green">4.5e+10</code></p>
</div>
<table class="tableblock frame-all grid-all stretch">
<caption class="title">Table 2. Arithmetic operators</caption>
@@ -918,7 +919,7 @@ pre.rouge .ss {
<em>num-den-spec</em> = <em>digit-seq</em> "<strong>|</strong>" <em>digit-seq</em><br>
<em>float-spec</em> = <em>dec-seq</em> "<strong>.</strong>" [<em>dec-seq</em>] "<strong>(</strong>" <em>dec-seq</em> "<strong>)</strong>"<br>
<em>dec-seq</em> = <em>see-integer-literal-syntax</em><br>
<em>digit-seq</em> = <em>see-integer-literal-syntax</em><br></p>
<em>digit-seq</em> = <em>see-integer-literal-syntax</em></p>
</div>
</div>
</div>
@@ -947,12 +948,13 @@ pre.rouge .ss {
<p>Fractions can be used together with integers and floats in expressions.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">1|2 + 5</code><br>
<code class="green">11|2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">4 - 1|2</code><br>
<code class="green">7|2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">1.0 + 1|2</code><br>
<code class="green">1.5</code><br></p>
<code class="green">1.5</code></p>
</div>
</div>
</div>
@@ -997,14 +999,14 @@ pre.rouge .ss {
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">+</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>concatenation</em></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Join two strings or two <em>stringable</em> values</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">"one" + "two"</code> <em>["onetwo"]</em><br>
<code class="blue">"one" + 2</code> <em>["one2"]</em></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">"one" + "two"</code> &#8594; <em>"onetwo"</em><br>
<code class="blue">"one" + 2</code> &#8594; <em>"one2"</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">*</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>repeat</em></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Make <em>n</em> copy of a string</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">"one" * 2</code> <em>["oneone"]</em></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">"one" * 2</code> &#8594; <em>"oneone"</em></p></td>
</tr>
</tbody>
</table>
@@ -1021,25 +1023,25 @@ pre.rouge .ss {
</div>
<div class="paragraph">
<div class="title">String examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">s="abc"</code> <em class="gray">assign the string to variable s</em><br>
<p><code>&gt;&gt;&gt;</code> <code class="blue">s="abc"</code> <em class="gray">// assign the string to variable s</em><br>
<code class="green">abc</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">s.1</code> <em class="gray">char at position 1 (starting from 0)</em><br>
<code>&gt;&gt;&gt;</code> <code class="blue">s.1</code> <em class="gray">// char at position 1 (starting from 0)</em><br>
<code class="green">b</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">s.(-1)</code> <em class="gray">char at position -1, the rightmost one</em><br>
<code>&gt;&gt;&gt;</code> <code class="blue">s.(-1)</code> <em class="gray">// char at position -1, the rightmost one</em><br>
<code class="green">c</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">#s</code> <em class="gray">number of chars</em><br>
<code>&gt;&gt;&gt;</code> <code class="blue">#s</code> <em class="gray">// number of chars</em><br>
<code class="gren">3</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">#"abc"</code> <em class="gray">number of chars</em><br>
<code class="green">3</code><br></p>
<code>&gt;&gt;&gt;</code> <code class="blue">#"abc"</code> <em class="gray">// number of chars</em><br>
<code class="green">3</code></p>
</div>
</div>
<div class="sect2">
<h3 id="_boolean"><a class="anchor" href="#_boolean"></a><a class="link" href="#_boolean">2.3. Boolean</a></h3>
<h3 id="_booleans"><a class="anchor" href="#_booleans"></a><a class="link" href="#_booleans">2.3. Booleans</a></h3>
<div class="paragraph">
<p>Boolean data type has two values only: <em class="blue">true</em> and <em class="blue">false</em>. Relational and boolean expressions result in boolean values.</p>
</div>
<table class="tableblock frame-all grid-all stretch">
<caption class="title">Table 4. Relational operators</caption>
<caption class="title">Table 4. Relational operators<sup>(*)</sup></caption>
<colgroup>
<col style="width: 7.6923%;">
<col style="width: 15.3846%;">
@@ -1099,6 +1101,9 @@ pre.rouge .ss {
</tr>
</tbody>
</table>
<div class="paragraph">
<p><sup>(*)</sup> See also the <code class="blue">in</code> operator in the <em>list</em> and <em>dictionary</em> sections.</p>
</div>
<table class="tableblock frame-all grid-all stretch">
<caption class="title">Table 5. Boolean operators</caption>
<colgroup>
@@ -1171,18 +1176,31 @@ pre.rouge .ss {
<div class="sect2">
<h3 id="_lists"><a class="anchor" href="#_lists"></a><a class="link" href="#_lists">2.4. Lists</a></h3>
<div class="paragraph">
<p><em>Expr</em> supports list of mixed-type values, also specified by normal expressions.</p>
<p><em>Expr</em> supports list of mixed-type values, also specified by normal expressions. Internally, <em>Expr</em>'s lists are Go arrays.</p>
</div>
<div class="listingblock">
<div class="title">List examples</div>
<div class="exampleblock">
<div class="title">Example 5. List literal syntax</div>
<div class="content">
<pre class="rouge highlight"><code data-lang="go"><span class="p">[</span><span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="m">3</span><span class="p">]</span> <span class="c">// List of integers</span>
<span class="p">[</span><span class="s">"one"</span><span class="p">,</span> <span class="s">"two"</span><span class="p">,</span> <span class="s">"three"</span><span class="p">]</span> <span class="c">// List of strings</span>
<span class="p">[</span><span class="s">"one"</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="no">false</span><span class="p">,</span> <span class="m">4.1</span><span class="p">]</span> <span class="c">// List of mixed-types</span>
<span class="p">[</span><span class="s">"one"</span><span class="o">+</span><span class="m">1</span><span class="p">,</span> <span class="m">2.0</span><span class="o">*</span><span class="p">(</span><span class="m">9</span><span class="o">-</span><span class="m">2</span><span class="p">)]</span> <span class="c">// List of expressions</span>
<span class="p">[</span> <span class="p">[</span><span class="m">1</span><span class="p">,</span><span class="s">"one"</span><span class="p">],</span> <span class="p">[</span><span class="m">2</span><span class="p">,</span><span class="s">"two"</span><span class="p">]]</span> <span class="c">// List of lists</span></code></pre>
<div class="paragraph">
<p><strong><em>list</em></strong> = <em>empty-list</em> | <em>non-empty-list</em><br>
<em>empty-list</em> = "<strong>[]</strong>"<br>
<em>non-empty-list</em> = "<strong>[</strong>" <em>any-value</em> {"<strong>,</strong>" _any-value} "<strong>]</strong>"<br></p>
</div>
</div>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">[1,2,3]</code> <em class="gray">// List of integers</em><br>
<code class="green">[1, 2, 3]</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">["one", "two", "three"]</code> <em class="gray">// List of strings</em><br>
<code class="green">["one", "two", "three"]</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">["one", 2, false, 4.1]</code> <em class="gray">// List of mixed-types</em><br>
<code class="green">["one", 2, false, 4.1]</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">["one"+1, 2.0*(9-2)]</code> <em class="gray">// List of expressions</em><br>
<code class="green">["one1", 14]</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">[ [1,"one"], [2,"two"]]</code> <em class="gray">// List of lists</em><br>
<code class="green">[[1, "one"], [2, "two"]]</code></p>
</div>
<table class="tableblock frame-all grid-all stretch">
<caption class="title">Table 6. List operators</caption>
<colgroup>
@@ -1204,23 +1222,50 @@ pre.rouge .ss {
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">+</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Join</em></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Joins two lists</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2] + [3]</code> <em>[ [1,2,3] ]</em></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2] + [3]</code> &#8594; <em>[1,2,3]</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">-</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Difference</em></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Left list without elements in the right list</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2,3] - [2]</code> <em>[ [1,3] ]</em></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2,3] - [2]</code> &#8594; <em>[1,3]</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">&gt;&gt;</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Front insertion</em></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Insert an item in front</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">0 &gt;&gt; [1,2]</code> &#8594; <em>[0,1,2]</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">&lt;&lt;</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Back insertion</em></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Insert an item at end</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2] &lt;&lt; 3</code> &#8594; <em>[1,2,3]</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">.</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>List item</em></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Item at given position</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2.3].1</code> &#8594; <em>2</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">in</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Item in list</em></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">True if item is in list</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">2 in [1,2,3]</code> &#8594; <em>true</em><br>
<code class="blue">6 in [1,2,3]</code> &#8594; <em>false</em></p></td>
</tr>
</tbody>
</table>
<div class="paragraph">
<p>The items of array can be accessed using the dot <code>.</code> operator.</p>
</div>
<div class="listingblock">
<div class="title">Item access syntax</div>
<div class="exampleblock">
<div class="title">Example 6. Item access syntax</div>
<div class="content">
<pre class="rouge highlight"><code data-lang="bnf">&lt;item&gt; ::= &lt;list-expr&gt;"."&lt;index-expr&gt;</code></pre>
<div class="paragraph">
<p><em>item</em> = <em>list-expr</em> "<strong>.</strong>" <em>integer-expr</em></p>
</div>
</div>
</div>
<div class="paragraph">
@@ -1238,62 +1283,136 @@ pre.rouge .ss {
<code>&gt;&gt;&gt;</code> <code class="blue">list.(10)</code><br>
<code class="red">Eval Error: [1:9] index 10 out of bounds</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">#list</code><br>
<code class="green">3</code></p>
<code class="green">3</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">index=2; ["a", "b", "c", "d"].index</code><br>
<code class="green">c</code></p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_dictionaries"><a class="anchor" href="#_dictionaries"></a><a class="link" href="#_dictionaries">3. Dictionaries</a></h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="_dictionaries"><a class="anchor" href="#_dictionaries"></a><a class="link" href="#_dictionaries">2.5. Dictionaries</a></h3>
<div class="paragraph">
<p>The <em>dictionary</em> data-type is set of pairs <em>key/value</em>. It is also known as <em>map</em> or <em>associative array</em>. Dictionary literals are sequences of pairs separated by comma <code>,</code>; sequences are enclosed between brace brackets.</p>
<p>The <em>dictionary</em>, or <em>dict</em>, data-type is set of pairs <em>key/value</em>. It is also known as <em>map</em> or <em>associative array</em>.</p>
</div>
<div class="listingblock">
<div class="title">Dictionary examples</div>
<div class="paragraph">
<p>Dictionary literals are sequences of pairs separated by comma <code class="blue">,</code> enclosed between brace brackets.</p>
</div>
<div class="exampleblock">
<div class="title">Example 7. Dict literal syntax</div>
<div class="content">
<pre class="rouge highlight"><code data-lang="go"><span class="p">{</span><span class="m">1</span><span class="o">:</span><span class="s">"one"</span><span class="p">,</span> <span class="m">2</span><span class="o">:</span><span class="s">"two"</span><span class="p">}</span>
<span class="p">{</span><span class="s">"one"</span><span class="o">:</span><span class="m">1</span><span class="p">,</span> <span class="s">"two"</span><span class="o">:</span> <span class="m">2</span><span class="p">}</span>
<span class="p">{</span><span class="s">"sum"</span><span class="o">:</span><span class="m">1</span><span class="o">+</span><span class="m">2</span><span class="o">+</span><span class="m">3</span><span class="p">,</span> <span class="s">"prod"</span><span class="o">:</span><span class="m">1</span><span class="o">*</span><span class="m">2</span><span class="o">*</span><span class="m">3</span><span class="p">}</span></code></pre>
<div class="paragraph">
<p><strong><em>dict</em></strong> = <em>empty-dict</em> | <em>non-empty-dict</em><br>
<em>empty-dict</em> = "<strong>{}</strong>"<br>
<em>non-empty-dict</em> = "<strong>{</strong>" <em>key-scalar</em> "<strong>:</strong>" <em>any-value</em> {"<strong>,</strong>" <em>key-scalar</em> "<strong>:</strong>" _any-value} "<strong>}</strong>"<br></p>
</div>
</div>
<div class="admonitionblock warning">
<table>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">{1:"one", 2:"two"}</code><br>
<code class="green">{1: "one", 2: "two"}</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">{"one":1, "two": 2}</code><br>
<code class="green">{"one": 1, "two": 2}</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">{"sum":1+2+3, "prod":1*2*3}</code><br>
<code class="green">{"sum": 6, "prod": 6}</code></p>
</div>
<table class="tableblock frame-all grid-all stretch">
<caption class="title">Table 7. Dict operators</caption>
<colgroup>
<col style="width: 15.3846%;">
<col style="width: 15.3846%;">
<col style="width: 30.7692%;">
<col style="width: 38.4616%;">
</colgroup>
<thead>
<tr>
<td class="icon">
<i class="fa icon-warning" title="Warning"></i>
</td>
<td class="content">
Support for dictionaries is still ongoing.
</td>
<th class="tableblock halign-center valign-top">Symbol</th>
<th class="tableblock halign-center valign-top">Operation</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Examples</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">+</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Join</em></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Joins two dicts</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">{1:"one"}+{6:"six"}</code> &#8594; <em>{1: "one", 6: "six"}</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">.</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Dict item value</em></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Item value of given key</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">{"one":1, "two":2}."two"</code> &#8594; <em>2</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">in</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Key in dict</em></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">True if key is in dict</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">"one" in {"one":1, "two":2}</code> &#8594; <em>true</em><br>
<code class="blue">"six" in {"one":1, "two":2}</code> &#8594; <em>false</em></p></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_variables"><a class="anchor" href="#_variables"></a><a class="link" href="#_variables">4. Variables</a></h2>
<h2 id="_variables"><a class="anchor" href="#_variables"></a><a class="link" href="#_variables">3. Variables</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>A variable is an identifier with an assigned value. Variables are stored in the object that implements the Go <em>ExprContext</em> interface, e.g. <em>SimpleVarStore</em> or <em>SimpleFuncStore</em>.</p>
<p><em>Expr</em>, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in <em>contexts</em>.</p>
</div>
<div class="listingblock">
<div class="title">Examples</div>
<div class="exampleblock">
<div class="title">Example 8. Variable literal syntax</div>
<div class="content">
<pre class="rouge highlight"><code data-lang="go"><span class="n">a</span><span class="o">=</span><span class="m">1</span>
<span class="n">x</span> <span class="o">=</span> <span class="m">5.2</span> <span class="o">*</span> <span class="p">(</span><span class="m">9</span><span class="o">-</span><span class="m">3</span><span class="p">)</span>
<span class="n">x</span> <span class="o">=</span> <span class="m">1</span><span class="p">;</span> <span class="n">y</span> <span class="o">=</span> <span class="m">2</span><span class="o">*</span><span class="n">x</span></code></pre>
<div class="paragraph">
<p><strong><em>variable</em></strong> = <em>identifier</em> "<strong>=</strong>" <em>any-value</em><br>
<em>identifier</em> = <em>alpha</em> {(<em>alpha</em>)|<em>dec-digit</em>|"<strong>_</strong>"}<br>
<em>alpha</em> = "<strong>a</strong>"|"<strong>b</strong>"|&#8230;&#8203;"<strong>z</strong>"|"<strong>A</strong>"|"<strong>B</strong>"|&#8230;&#8203;"<strong>Z</strong>"</p>
</div>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
The assign operator <code class="blue">=</code> returns the value assigned to the variable.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">a=1</code><br>
<code class="green">1</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">a_b=1+2</code><br>
<code class="green">1+2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">a_b</code><br>
<code class="green">3</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">x = 5.2 * (9-3)</code> <em class="gray">// The assigned value has the approximation error typical of the float data-type</em><br>
<code class="green">31.200000000000003</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">x = 1; y = 2*x</code><br>
<code class="green">2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue"><em>a=2</code><br>
<code class="red">Parse Error: [1:2] unexpected token "</em>"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">1=2</code><br>
<code class="red">Parse Error: assign operator ("=") must be preceded by a variable</code></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_other_operations"><a class="anchor" href="#_other_operations"></a><a class="link" href="#_other_operations">5. Other operations</a></h2>
<h2 id="_other_operations"><a class="anchor" href="#_other_operations"></a><a class="link" href="#_other_operations">4. Other operations</a></h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="_operator"><a class="anchor" href="#_operator"></a><a class="link" href="#_operator">5.1. <code class="blue">;</code> operator</a></h3>
<h3 id="_operator"><a class="anchor" href="#_operator"></a><a class="link" href="#_operator">4.1. <code class="blue">;</code> operator</a></h3>
<div class="paragraph">
<p>The semicolon operator <code class="blue">;</code> is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The latter is the final result.</p>
<p>The semicolon operator <code class="blue">;</code> 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.</p>
</div>
<div class="paragraph">
<p>An expression that contains <code class="blue">;</code> is called a <em>multi-expression</em> and each component expressione is called a <em>sub-expression</em>.</p>
</div>
<div class="admonitionblock important">
<table>
@@ -1319,97 +1438,155 @@ Technically <code class="blue">;</code> is not treated as a real operator. It ac
</tr>
</table>
</div>
<div class="listingblock">
<div class="paragraph">
<div class="title">Example</div>
<div class="content">
<pre class="rouge highlight"><code data-lang="go"><span class="n">a</span><span class="o">=</span><span class="m">1</span><span class="p">;</span> <span class="n">b</span><span class="o">=</span><span class="m">2</span><span class="p">;</span> <span class="n">c</span><span class="o">=</span><span class="m">3</span><span class="p">;</span> <span class="n">a</span><span class="o">+</span><span class="n">b</span><span class="o">+</span><span class="n">c</span> <span class="c">// returns 6</span></code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_but_operator"><a class="anchor" href="#_but_operator"></a><a class="link" href="#_but_operator">5.2. <code class="blue">but</code> operator</a></h3>
<div class="paragraph">
<p><code class="blue">but</code> 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: <code class="blue">5 but 2</code> returns 2, <code class="blue">x=2*3 but x-1</code> returns 5.</p>
<p><code>&gt;&gt;&gt;</code> <code class="blue">a=1; b=2; c=3; a+b+c</code><br>
<code class="green">6</code></p>
</div>
<div class="paragraph">
<p><code class="blue">but</code> is very similar to <code class="blue">;</code>. The only difference is that <code class="blue">;</code> can&#8217;t be used inside parenthesis <code class="blue">(</code> and <code class="blue">)</code>.</p>
<p>The value of each sub-expression is stored in the automatica variable <em>last</em>.</p>
</div>
</div>
<div class="sect2">
<h3 id="_assignment_operator"><a class="anchor" href="#_assignment_operator"></a><a class="link" href="#_assignment_operator">5.3. Assignment operator <code class="blue">=</code></a></h3>
<div class="paragraph">
<p>The assignment operator <code class="blue">=</code> is used to define variables in the evaluation context or to change their value (see <em>ExprContext</em>).
The value on the left side of <code class="blue">=</code> must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.</p>
</div>
<div class="listingblock">
<div class="title">Example</div>
<div class="content">
<pre class="rouge highlight"><code data-lang="go"><span class="n">a</span><span class="o">=</span><span class="m">15</span><span class="o">+</span><span class="m">1</span> <span class="c">// returns 16</span></code></pre>
</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">2+3; b=last+10; last</code><br>
<code class="green">15</code></p>
</div>
</div>
<div class="sect2">
<h3 id="_selector_operator"><a class="anchor" href="#_selector_operator"></a><a class="link" href="#_selector_operator">5.4. Selector operator <code class="blue">? : ::</code></a></h3>
<h3 id="_but_operator"><a class="anchor" href="#_but_operator"></a><a class="link" href="#_but_operator">4.2. <code class="blue">but</code> operator</a></h3>
<div class="paragraph">
<p><code class="blue">but</code> 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.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code class="blue">5 but 2</code><br>
<code class="green">2</code><br>
<code class="blue">x=2*3 but x-1</code><br>
<code class="green">5</code>.</p>
</div>
<div class="paragraph">
<p><code class="blue">but</code> behavior is very similar to <code class="blue">;</code>. The only difference is that <code class="blue">;</code> is not a true operator and can&#8217;t be used inside parenthesis <code class="blue">(</code> and <code class="blue">)</code>.</p>
</div>
</div>
<div class="sect2">
<h3 id="_assignment_operator"><a class="anchor" href="#_assignment_operator"></a><a class="link" href="#_assignment_operator">4.3. Assignment operator <code class="blue">=</code></a></h3>
<div class="paragraph">
<p>The assignment operator <code class="blue">=</code> is used to define variables or to change their value in the evaluation context (see <em>ExprContext</em>).</p>
</div>
<div class="paragraph">
<p>The value on the left side of <code class="blue">=</code> must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.</p>
</div>
<div class="paragraph">
<div class="title">Example</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">a=15+1</code>
<code class="green">16</code></p>
</div>
</div>
<div class="sect2">
<h3 id="_selector_operator"><a class="anchor" href="#_selector_operator"></a><a class="link" href="#_selector_operator">4.4. Selector operator <code class="blue">? : ::</code></a></h3>
<div class="paragraph">
<p>The <em>selector operator</em> is very similar to the <em>switch/case/default</em> statement available in many programming languages.</p>
</div>
<div class="listingblock">
<div class="title">Syntax</div>
<div class="content">
<pre class="rouge highlight"><code data-lang="bnf">&lt;selector-operator&gt; ::= &lt;select-expression&gt; "?" &lt;selector-case&gt; { ":" &lt;selector-case&gt; } ["::" &lt;default-multi-expression&gt;]
&lt;selector-case&gt; ::= [&lt;match-list&gt;] &lt;case-value&gt;
&lt;match-list&gt; ::= "["&lt;item&gt;{","&lt;items&gt;}"]"
&lt;item&gt; ::= &lt;expression
&lt;case-multi-expression&gt; ::= "{" &lt;multi-expression&gt; "}"
&lt;multi-expression&gt; ::= &lt;expression&gt; {";" &lt;expression&gt;}</code></pre>
</div>
<div class="paragraph">
<div class="title">Selector literal Syntax</div>
<p><em>selector-operator</em> = <em>select-expression</em> "<strong>?</strong>" <em>selector-case</em> { "<strong>:</strong>" <em>selector-case</em> } ["<strong>::</strong>" <em>default-multi-expression</em>]<br>
<em>selector-case</em> = [<em>match-list</em>] <em>case-value</em><br>
<em>match-list</em> = "<strong>[</strong>" <em>item</em> {"<strong>,</strong>" <em>items</em>} "<strong>]</strong>"<br>
<em>item</em> = <em>expression</em><br>
<em>case-multi-expression</em> = "<strong>{</strong>" <em>multi-expression</em> "<strong>}</strong>"<br>
<em>multi-expression</em> = <em>expression</em> { "<strong>;</strong>" <em>expression</em> }<br>
<em>default-multi-expression</em> = <em>multi-expression</em></p>
</div>
<div class="paragraph">
<p>In other words, the selector operator evaluates the expression (<code>&lt;select-expression&gt;</code>) on the left-hand side of the <code>?</code> symbol; it then compares the result obtained with the values listed in the <code>&lt;match-list&gt;&#8217;s. If the comparision finds a match with a value in a match-list, the associated `&lt;case-multi-expression&gt;</code> is evaluted, and its result will be the final result of the selection operation.</p>
<p>In other words, the selector operator evaluates the <em>select-expression</em> on the left-hand side of the <code class="blue">?</code> symbol; it then compares the result obtained with the values listed in the <em>match-list</em>'s, from left to right. If the comparision finds a match with a value in a <em>match-list</em>, the associated <em>case-multi-expression</em> is evaluted, and its result will be the final result of the selection operation.</p>
</div>
<div class="paragraph">
<p>The match lists are optional. In that case, the position, from left to right, of the <code>&lt;selector-case&gt;</code> is used as match-list. Of course, that only works if the select-expression results in an integer.</p>
<p>The match lists are optional. In that case, the position, from left to right, of the <em>selector-case</em> is used as <em>match-list</em>. Of course, that only works if the <em>select-expression</em> results in an integer.</p>
</div>
<div class="paragraph">
<p>The <code>:</code> 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 <code>::</code> symbol (double-colon). Also note that the default expression has no match-list.</p>
<p>The <code class="blue">:</code> symbol (colon) is the separator of the selector-cases. Note that if the value of the <em>select-expression</em> does not match any <em>match-list</em>, an error will be issued. Therefore, it is strongly recommended to provide a default (multi-)expression introduced by the <code class="blue">::</code> symbol (double-colon). Also note that the default expression has no <em>match-list</em>.</p>
</div>
<div class="listingblock">
<div class="paragraph">
<div class="title">Examples</div>
<div class="content">
<pre class="rouge highlight"><code data-lang="go"><span class="s">`&gt;&gt;&gt;`</span> <span class="p">[</span><span class="n">blue</span><span class="p">]</span><span class="s">`1 ? {"a"} : {"b"}`</span>
<span class="p">[</span><span class="n">green</span><span class="p">]</span><span class="s">`b`</span>
<span class="s">`&gt;&gt;&gt;`</span> <span class="p">[</span><span class="n">blue</span><span class="p">]</span><span class="s">`10 ? {"a"} : {"b"} :: {"c"}`</span>
<span class="p">[</span><span class="n">green</span><span class="p">]</span><span class="s">`c'
[green]`</span><span class="o">&gt;&gt;&gt;</span><span class="s">` [blue]`</span><span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="no">true</span><span class="p">,</span> <span class="m">2</span><span class="o">+</span><span class="m">8</span><span class="p">]</span> <span class="p">{</span><span class="s">"b"</span><span class="p">}</span> <span class="o">::</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span><span class="s">`
[green]`</span><span class="n">b</span><span class="s">`
`</span><span class="o">&gt;&gt;&gt;</span><span class="s">` [blue]`</span><span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="no">true</span><span class="p">,</span> <span class="m">2</span><span class="o">+</span><span class="m">8</span><span class="p">]</span> <span class="p">{</span><span class="s">"b"</span><span class="p">}</span> <span class="o">::</span><span class="p">[</span><span class="m">10</span><span class="p">]</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span><span class="s">`
[red]`</span><span class="n">Parse</span> <span class="n">Error</span><span class="o">:</span> <span class="p">[</span><span class="m">1</span><span class="o">:</span><span class="m">34</span><span class="p">]</span> <span class="k">case</span> <span class="n">list</span> <span class="n">in</span> <span class="k">default</span> <span class="n">clause</span><span class="s">`
[green]`</span><span class="o">&gt;&gt;&gt;</span><span class="s">` [blue]`</span><span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="m">10</span><span class="p">]</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="s">"b"</span> <span class="n">but</span> <span class="n">x</span><span class="p">}</span> <span class="o">::</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span><span class="s">`
[green]`</span><span class="n">b</span><span class="s">`
`</span><span class="o">&gt;&gt;&gt;</span><span class="s">` [blue]`</span><span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="m">10</span><span class="p">]</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="s">"b"</span><span class="p">;</span> <span class="n">x</span><span class="p">}</span> <span class="o">::</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span><span class="s">`
[green]`</span><span class="n">b</span><span class="s">`
`</span><span class="o">&gt;&gt;&gt;</span><span class="s">` [blue]`</span><span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span> <span class="p">{</span><span class="s">"b"</span><span class="p">}</span><span class="s">`
[red]`</span><span class="n">Eval</span> <span class="n">Error</span><span class="o">:</span> <span class="p">[</span><span class="m">1</span><span class="o">:</span><span class="m">3</span><span class="p">]</span> <span class="n">no</span> <span class="k">case</span> <span class="n">catches</span> <span class="n">the</span> <span class="n">value</span> <span class="p">(</span><span class="m">10</span><span class="p">)</span> <span class="n">of</span> <span class="n">the</span> <span class="n">selection</span> <span class="n">expression</span><span class="s">`
</span></code></pre>
<p><code>&gt;&gt;&gt;</code> <code class="blue">1 ? {"a"} : {"b"}</code><br>
<code class="green">b</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">10 ? {"a"} : {"b"} :: {"c"}</code><br>
<code class="green">c'<br>
[green]</code>&gt;&gt;&gt;` <code class="blue">10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}</code><br>
<code class="green">b</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}</code><br>
<code class="red">Parse Error: [1:34] case list in default clause</code><br>
<code class="green">&gt;&gt;&gt;</code> <code class="blue">10 ? {"a"} :[10] {x="b" but x} :: {"c"}</code><br>
<code class="green">b</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">10 ? {"a"} :[10] {x="b"; x} :: {"c"}</code><br>
<code class="green">b</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">10 ? {"a"} : {"b"}</code><br>
<code class="red">Eval Error: [1:3] no case catches the value (10) of the selection expression</code></p>
</div>
</div>
<div class="sect2">
<h3 id="_variable_default_value_and"><a class="anchor" href="#_variable_default_value_and"></a><a class="link" href="#_variable_default_value_and">4.5. Variable default value <code class="blue">??</code> and <code class="blue">?=</code></a></h3>
<div class="paragraph">
<p>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.</p>
</div>
<div class="admonitionblock important">
<table>
<tr>
<td class="icon">
<i class="fa icon-important" title="Important"></i>
</td>
<td class="content">
If the left variable is defined, the right expression is not evuated at all.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>The <code class="blue">??</code> do not change the status of the left variable.</p>
</div>
<div class="paragraph">
<p>The <code class="blue">?=</code> assigns the calculated value of the right expression to the left variable.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">var ?? (1+2)&#8217;
[green]`3</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">var</code><br>
<code class="red">Eval Error: undefined variable or function "var"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">var ?= (1+2)</code><br>
<code class="green">3</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">var</code><br>
<code class="green">3</code></p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
These operators have a high priority, in particular higher than the operator <code class="blue">=</code>.
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_priorities_of_operators"><a class="anchor" href="#_priorities_of_operators"></a><a class="link" href="#_priorities_of_operators">6. Priorities of operators</a></h2>
<h2 id="_priorities_of_operators"><a class="anchor" href="#_priorities_of_operators"></a><a class="link" href="#_priorities_of_operators">5. Priorities of operators</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>The table below shows all supported operators by decreasing priorities.</p>
</div>
<table class="tableblock frame-all grid-all stretch">
<caption class="title">Table 7. Operators priorities</caption>
<caption class="title">Table 8. Operators priorities</caption>
<colgroup>
<col style="width: 12.5%;">
<col style="width: 12.5%;">
<col style="width: 12.5%;">
<col style="width: 31.25%;">
<col style="width: 31.25%;">
<col style="width: 11.7647%;">
<col style="width: 11.7647%;">
<col style="width: 11.7647%;">
<col style="width: 29.4117%;">
<col style="width: 35.2942%;">
</colgroup>
<thead>
<tr>
@@ -1422,11 +1599,17 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
</thead>
<tbody>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>ITEM</strong></p></td>
<td class="tableblock halign-center valign-top" rowspan="2"><p class="tableblock"><strong>ITEM</strong></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">.</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Item</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>collection</em> <code>"."</code> <em>key</em> &#8594; <em>any</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>List item</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> <code>"."</code> <em>integer</em> &#8594; <em>any</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">.</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Dict item</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>dict</em> <code>""</code> <em>any</em> &#8594; <em>any</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top" rowspan="2"><p class="tableblock"><strong>INC</strong></p></td>
@@ -1499,7 +1682,7 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>integer</em> <code>"%"</code> <em>integer</em> &#8594; <em>integer</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top" rowspan="5"><p class="tableblock"><strong>SUM</strong></p></td>
<td class="tableblock halign-center valign-top" rowspan="6"><p class="tableblock"><strong>SUM</strong></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">+</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Sum</em></p></td>
@@ -1518,6 +1701,12 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> <code>"+"</code> <em>list</em> &#8594; <em>list</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">+</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Dict-join</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>dict</em> <code>"+"</code> <em>dict</em> &#8594; <em>dict</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">-</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Subtraction</em></p></td>
@@ -1530,7 +1719,7 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> <code>"-"</code> <em>list</em> &#8594; <em>list</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top" rowspan="6"><p class="tableblock"><strong>RELATION</strong></p></td>
<td class="tableblock halign-center valign-top" rowspan="8"><p class="tableblock"><strong>RELATION</strong></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">&lt;</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>less</em></p></td>
@@ -1567,6 +1756,18 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>comparable</em> <code>"!="</code> <em>comparable</em> &#8594; <em>boolean</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">in</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>member-of-list</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>any</em> <code>"in"</code> <em>list</em> &#8594; <em>boolean</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">in</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>key-of-dict</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>any</em> <code>"in"</code> <em>dict</em> &#8594; <em>boolean</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>NOT</strong></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">not</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Prefix</em></p></td>
@@ -1600,13 +1801,25 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>boolean</em> <code>"||"</code> <em>boolean</em> &#8594; <em>boolean</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>ASSIGN</strong></p></td>
<td class="tableblock halign-center valign-top" rowspan="3"><p class="tableblock"><strong>ASSIGN</strong></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">=</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>assignment</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>identifier</em> "=" <em>any</em> &#8594; <em>any</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">&gt;&gt;</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>front-insert</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>any</em> "&gt;&gt;" <em>list</em> &#8594; <em>list</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">&lt;&lt;</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>back-insert</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> "&lt;&lt;" <em>any</em> &#8594; <em>list</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>BUT</strong></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">but</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
@@ -1618,7 +1831,7 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
</div>
</div>
<div class="sect1">
<h2 id="_functions"><a class="anchor" href="#_functions"></a><a class="link" href="#_functions">7. Functions</a></h2>
<h2 id="_functions"><a class="anchor" href="#_functions"></a><a class="link" href="#_functions">6. Functions</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>Functions in <em>Expr</em> are very similar to functions in many programming languages.</p>
@@ -1627,13 +1840,13 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
<p>In <em>Expr</em> 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 <code class="blue">@</code> it is possibile to export local definition to the calling context.</p>
</div>
<div class="sect2">
<h3 id="_function_calls"><a class="anchor" href="#_function_calls"></a><a class="link" href="#_function_calls">7.1. Function calls</a></h3>
<h3 id="_function_calls"><a class="anchor" href="#_function_calls"></a><a class="link" href="#_function_calls">6.1. Function calls</a></h3>
<div class="paragraph">
<p><mark>TODO: function calls operations</mark></p>
</div>
</div>
<div class="sect2">
<h3 id="_function_definitions"><a class="anchor" href="#_function_definitions"></a><a class="link" href="#_function_definitions">7.2. Function definitions</a></h3>
<h3 id="_function_definitions"><a class="anchor" href="#_function_definitions"></a><a class="link" href="#_function_definitions">6.2. Function definitions</a></h3>
<div class="paragraph">
<p><mark>TODO: function definitions operations</mark></p>
</div>
@@ -1641,17 +1854,17 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
</div>
</div>
<div class="sect1">
<h2 id="_builtins"><a class="anchor" href="#_builtins"></a><a class="link" href="#_builtins">8. Builtins</a></h2>
<h2 id="_builtins"><a class="anchor" href="#_builtins"></a><a class="link" href="#_builtins">7. Builtins</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p><mark>TODO: builtins</mark></p>
</div>
<div class="sect2">
<h3 id="_builtin_functions"><a class="anchor" href="#_builtin_functions"></a><a class="link" href="#_builtin_functions">8.1. Builtin functions</a></h3>
<h3 id="_builtin_functions"><a class="anchor" href="#_builtin_functions"></a><a class="link" href="#_builtin_functions">7.1. Builtin functions</a></h3>
</div>
<div class="sect2">
<h3 id="_import"><a class="anchor" href="#_import"></a><a class="link" href="#_import">8.2. <em class="blue">import()</em></a></h3>
<h3 id="_import"><a class="anchor" href="#_import"></a><a class="link" href="#_import">7.2. <em class="blue">import()</em></a></h3>
<div class="paragraph">
<p><em class="blue">import(<span class="grey">&lt;source-file&gt;</span>)</em> loads the multi-expression contained in the specified source and returns its value.</p>
</div>
@@ -1661,7 +1874,7 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
</div>
<div id="footer">
<div id="footer-text">
Last updated 2024-05-16 07:10:03 +0200
Last updated 2024-05-20 09:40:23 +0200
</div>
</div>
</body>
+45
View File
@@ -4,17 +4,62 @@
// formatter.go
package expr
import "fmt"
type FmtOpt uint16
const (
TTY FmtOpt = 1 << iota
MultiLine
Truncate
Base2
Base8
Base10
Base16
)
const (
TruncateEllipsis = "(...)"
MinTruncateSize = 10
TruncateSize = MinTruncateSize + 15
)
func TruncateString(s string) (trunc string) {
finalPart := len(s) - (MinTruncateSize - len(TruncateEllipsis))
trunc = s[0:len(s)-MinTruncateSize] + TruncateEllipsis + s[finalPart:]
return
}
type Formatter interface {
ToString(options FmtOpt) string
}
func getFormatted(v any, opt FmtOpt) (text string) {
if v == nil {
text = "(nil)"
} else if s, ok := v.(string); ok {
text = s
} else if formatter, ok := v.(Formatter); ok {
text = formatter.ToString(opt)
} else {
text = fmt.Sprintf("%v", v)
}
return
}
type Typer interface {
TypeName() string
}
func getTypeName(v any) (name string) {
if typer, ok := v.(Typer); ok {
name = typer.TypeName()
} else if IsInteger(v) {
name = "integer"
} else if IsFloat(v) {
name = "float"
} else {
name = fmt.Sprintf("%T", v)
}
return
}
+401
View File
@@ -0,0 +1,401 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// fraction-type.go
package expr
//https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html
import (
"errors"
"fmt"
"math"
"strconv"
"strings"
)
type FractionType struct {
num, den int64
}
func newFraction(num, den int64) *FractionType {
num, den = simplifyIntegers(num, den)
return &FractionType{num, den}
}
func float64ToFraction(f float64) (fract *FractionType, err error) {
var sign string
intPart, decPart := math.Modf(f)
if decPart < 0.0 {
sign = "-"
intPart = -intPart
decPart = -decPart
}
dec := fmt.Sprintf("%.12f", decPart)
s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:])
// fmt.Printf("S: '%s'\n",s)
return makeGeneratingFraction(s)
}
// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39
/*
func _float64ToFraction(f float64) (num, den int64, err error) {
const expMask = 1<<11 - 1
bits := math.Float64bits(f)
mantissa := bits & (1<<52 - 1)
exp := int((bits >> 52) & expMask)
switch exp {
case expMask: // non-finite
err = errors.New("infite")
return
case 0: // denormal
exp -= 1022
default: // normal
mantissa |= 1 << 52
exp -= 1023
}
shift := 52 - exp
// Optimization (?): partially pre-normalise.
for mantissa&1 == 0 && shift > 0 {
mantissa >>= 1
shift--
}
if f < 0 {
num = -int64(mantissa)
} else {
num = int64(mantissa)
}
den = int64(1)
if shift > 0 {
den = den << shift
} else {
num = num << (-shift)
}
return
}
*/
func makeGeneratingFraction(s string) (f *FractionType, err error) {
var num, den int64
var sign int64 = 1
var parts []string
if len(s) == 0 {
goto exit
}
if s[0] == '-' {
sign = int64(-1)
s = s[1:]
} else if s[0] == '+' {
s = s[1:]
}
if strings.HasSuffix(s, "()") {
s = s[0 : len(s)-2]
}
parts = strings.SplitN(s, ".", 2)
if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
return
}
if len(parts) == 1 {
f = newFraction(sign*num, 1)
} else if len(parts) == 2 {
subParts := strings.SplitN(parts[1], "(", 2)
if len(subParts) == 1 {
den = 1
dec := parts[1]
lsd := len(dec)
for i := lsd - 1; i >= 0 && dec[i] == '0'; i-- {
lsd--
}
for _, c := range dec[0:lsd] {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den * 10
}
f = newFraction(sign*num, den)
} else if len(subParts) == 2 {
sub := num
mul := int64(1)
for _, c := range subParts[0] {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
sub = sub*10 + int64(c-'0')
mul *= 10
}
if len(subParts) == 2 {
if s[len(s)-1] != ')' {
goto exit
}
p := subParts[1][0 : len(subParts[1])-1]
for _, c := range p {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den*10 + 9
}
den *= mul
}
num -= sub
f = newFraction(sign*num, den)
}
}
exit:
if f == nil {
err = errors.New("bad syntax")
}
return
}
func (f *FractionType) toFloat() float64 {
return float64(f.num) / float64(f.den)
}
func (f *FractionType) String() string {
return f.ToString(0)
}
func (f *FractionType) ToString(opt FmtOpt) string {
var sb strings.Builder
if opt&MultiLine == 0 {
sb.WriteString(fmt.Sprintf("%d|%d", f.num, f.den))
} else {
var s, num string
if f.num < 0 && opt&TTY == 0 {
num = strconv.FormatInt(-f.num, 10)
s = "-"
} else {
num = strconv.FormatInt(f.num, 10)
}
den := strconv.FormatInt(f.den, 10)
size := max(len(num), len(den))
if opt&TTY != 0 {
sb.WriteString(fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, s+num)))
} else {
if len(s) > 0 {
sb.WriteString(" ")
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
sb.WriteByte('\n')
if len(s) > 0 {
sb.WriteString(s)
sb.WriteByte(' ')
}
sb.WriteString(strings.Repeat("-", size))
sb.WriteByte('\n')
if len(s) > 0 {
sb.WriteString(" ")
}
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den)))
}
return sb.String()
}
func (f *FractionType) TypeName() string {
return "fraction"
}
// -------- fraction utility functions
// greatest common divider
func gcd(a, b int64) (g int64) {
if a < 0 {
a = -a
}
if b < 0 {
b = -b
}
if a < b {
a, b = b, a
}
r := a % b
for r > 0 {
a, b = b, r
r = a % b
}
g = b
return
}
// lower common multiple
func lcm(a, b int64) (l int64) {
g := gcd(a, b)
l = a * b / g
return
}
// Sum two fractions
func sumFract(f1, f2 *FractionType) (sum *FractionType) {
m := lcm(f1.den, f2.den)
sum = newFraction(f1.num*(m/f1.den)+f2.num*(m/f2.den), m)
return
}
// Multiply two fractions
func mulFract(f1, f2 *FractionType) (prod *FractionType) {
prod = newFraction(f1.num*f2.num, f1.den*f2.den)
return
}
func anyToFract(v any) (f *FractionType, err error) {
var ok bool
if f, ok = v.(*FractionType); !ok {
if n, ok := v.(int64); ok {
f = intToFraction(n)
}
}
if f == nil {
err = errExpectedGot("fract", typeFraction, v)
}
return
}
func anyPairToFract(v1, v2 any) (f1, f2 *FractionType, err error) {
if f1, err = anyToFract(v1); err != nil {
return
}
if f2, err = anyToFract(v2); err != nil {
return
}
return
}
func sumAnyFract(af1, af2 any) (sum any, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
f := sumFract(f1, f2)
if f.num == 0 {
sum = 0
} else {
sum = simplifyFraction(f)
}
return
}
// Returns
//
// <0 if af1 < af2
// =0 if af1 == af2
// >0 if af1 > af2
// err if af1 or af2 is not convertible to fraction
func cmpAnyFract(af1, af2 any) (result int, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
result = cmpFract(f1, f2)
return
}
// Returns
//
// <0 if af1 < af2
// =0 if af1 == af2
// >0 if af1 > af2
func cmpFract(f1, f2 *FractionType) (result int) {
f2.num = -f2.num
f := sumFract(f1, f2)
if f.num < 0 {
result = -1
} else if f.num > 0 {
result = 1
} else {
result = 0
}
return
}
func subAnyFract(af1, af2 any) (sum any, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
f2.num = -f2.num
f := sumFract(f1, f2)
if f.num == 0 {
sum = 0
} else {
sum = simplifyFraction(f)
}
return
}
func mulAnyFract(af1, af2 any) (prod any, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
if f1.num == 0 || f2.num == 0 {
prod = 0
} else {
f := &FractionType{f1.num * f2.num, f1.den * f2.den}
prod = simplifyFraction(f)
}
return
}
func divAnyFract(af1, af2 any) (quot any, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
if f2.num == 0 {
err = errors.New("division by zero")
return
return
}
if f1.num == 0 || f2.den == 0 {
quot = 0
} else {
f := &FractionType{f1.num * f2.den, f1.den * f2.num}
quot = simplifyFraction(f)
}
return
}
func simplifyFraction(f *FractionType) (v any) {
f.num, f.den = simplifyIntegers(f.num, f.den)
if f.den == 1 {
v = f.num
} else {
v = &FractionType{f.num, f.den}
}
return v
}
func simplifyIntegers(num, den int64) (a, b int64) {
if num == 0 {
return 0, 1
}
if den == 0 {
panic("fraction with denominator == 0")
}
if den < 0 {
den = -den
num = -num
}
g := gcd(num, den)
a = num / g
b = den / g
return
}
func intToFraction(n int64) *FractionType {
return &FractionType{n, 1}
}
func isFraction(v any) (ok bool) {
_, ok = v.(*FractionType)
return ok
}
+41 -30
View File
@@ -54,6 +54,24 @@ func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err
return
}
func boolFunc(ctx ExprContext, name string, args []any) (result any, err error) {
switch v := args[0].(type) {
case int64:
result = (v != 0)
case *FractionType:
result = v.num != 0
case float64:
result = v != 0.0
case bool:
result = v
case string:
result = len(v) > 0
default:
err = errCantConvert(name, v, "bool")
}
return
}
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
switch v := args[0].(type) {
case int64:
@@ -94,7 +112,7 @@ func decFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if f, err = strconv.ParseFloat(v, 64); err == nil {
result = f
}
case *fraction:
case *FractionType:
result = v.toFloat()
default:
err = errCantConvert(name, v, "float")
@@ -119,10 +137,6 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
}
case float64:
result, err = float64ToFraction(v)
// var n, d int64
// if n, d, err = float64ToFraction(v); err == nil {
// result = newFraction(n, d)
// }
case bool:
if v {
result = newFraction(1, 1)
@@ -131,17 +145,7 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
}
case string:
result, err = makeGeneratingFraction(v)
// var f float64
// // TODO temporary implementation
// if f, err = strconv.ParseFloat(v, 64); err == nil {
// var n, d int64
// if n, d, err = float64ToFraction(f); err == nil {
// result = newFraction(n, d)
// }
// } else {
// errors.New("convertion from string to float is ongoing")
// }
case *fraction:
case *FractionType:
result = v
default:
err = errCantConvert(name, v, "float")
@@ -154,20 +158,27 @@ func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err err
}
func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, 1)
ctx.RegisterFunc("isInt", &simpleFunctor{f: isIntFunc}, 1, 1)
ctx.RegisterFunc("isFloat", &simpleFunctor{f: isFloatFunc}, 1, 1)
ctx.RegisterFunc("isBool", &simpleFunctor{f: isBoolFunc}, 1, 1)
ctx.RegisterFunc("isString", &simpleFunctor{f: isStringFunc}, 1, 1)
ctx.RegisterFunc("isFraction", &simpleFunctor{f: isFractionFunc}, 1, 1)
ctx.RegisterFunc("isFract", &simpleFunctor{f: isFractionFunc}, 1, 1)
ctx.RegisterFunc("isRational", &simpleFunctor{f: isRationalFunc}, 1, 1)
ctx.RegisterFunc("isList", &simpleFunctor{f: isListFunc}, 1, 1)
ctx.RegisterFunc("isDictionary", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
ctx.RegisterFunc("isDict", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, 1)
ctx.RegisterFunc("dec", &simpleFunctor{f: decFunc}, 1, 1)
ctx.RegisterFunc("fract", &simpleFunctor{f: fractFunc}, 1, 2)
anyParams := []ExprFuncParam{
newFuncParam(paramValue),
}
ctx.RegisterFunc("isNil", newGolangFunctor(isNilFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isInt", newGolangFunctor(isIntFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isFloat", newGolangFunctor(isFloatFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isBool", newGolangFunctor(isBoolFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isString", newGolangFunctor(isStringFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isFract", newGolangFunctor(isFractionFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isRational", newGolangFunctor(isRationalFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isList", newGolangFunctor(isListFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isDict", newGolangFunctor(isDictionaryFunc), typeBoolean, anyParams)
ctx.RegisterFunc("bool", newGolangFunctor(boolFunc), typeBoolean, anyParams)
ctx.RegisterFunc("int", newGolangFunctor(intFunc), typeInt, anyParams)
ctx.RegisterFunc("dec", newGolangFunctor(decFunc), typeFloat, anyParams)
ctx.RegisterFunc("fract", newGolangFunctor(fractFunc), typeFraction, []ExprFuncParam{
newFuncParam(paramValue),
newFuncParamFlagDef("denominator", pfOptional, 1),
})
}
func init() {
+6 -2
View File
@@ -137,8 +137,12 @@ func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (resu
}
func ImportImportFuncs(ctx ExprContext) {
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
ctx.RegisterFunc("importAll", &simpleFunctor{f: importAllFunc}, 1, -1)
ctx.RegisterFunc("import", newGolangFunctor(importFunc), typeAny, []ExprFuncParam{
newFuncParamFlag(typeFilepath, pfRepeat),
})
ctx.RegisterFunc("importAll", newGolangFunctor(importAllFunc), typeAny, []ExprFuncParam{
newFuncParamFlag(typeFilepath, pfRepeat),
})
}
func init() {
+13 -8
View File
@@ -21,7 +21,7 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
var sumAsFloat, sumAsFract bool
var floatSum float64 = 0.0
var intSum int64 = 0
var fractSum *fraction
var fractSum *FractionType
var v any
level++
@@ -61,9 +61,9 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
if sumAsFloat {
floatSum += numAsFloat(v)
} else if sumAsFract {
var item *fraction
var item *FractionType
var ok bool
if item, ok = v.(*fraction); !ok {
if item, ok = v.(*FractionType); !ok {
iv, _ := v.(int64)
item = newFraction(iv, 1)
}
@@ -95,7 +95,7 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
var mulAsFloat, mulAsFract bool
var floatProd float64 = 1.0
var intProd int64 = 1
var fractProd *fraction
var fractProd *FractionType
var v any
level++
@@ -136,9 +136,9 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
if mulAsFloat {
floatProd *= numAsFloat(v)
} else if mulAsFract {
var item *fraction
var item *FractionType
var ok bool
if item, ok = v.(*fraction); !ok {
if item, ok = v.(*FractionType); !ok {
iv, _ := v.(int64)
item = newFraction(iv, 1)
}
@@ -167,8 +167,13 @@ func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
}
func ImportMathFuncs(ctx ExprContext) {
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, typeNumber, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0),
})
ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, typeNumber, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 1),
})
}
func init() {
+20 -6
View File
@@ -158,12 +158,26 @@ func readFileFunc(ctx ExprContext, name string, args []any) (result any, err err
}
func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("openFile", &simpleFunctor{f: openFileFunc}, 1, 1)
ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1)
ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1)
ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1)
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
ctx.RegisterFunc("openFile", newGolangFunctor(openFileFunc), typeHandle, []ExprFuncParam{
newFuncParam(typeFilepath),
})
ctx.RegisterFunc("appendFile", newGolangFunctor(appendFileFunc), typeHandle, []ExprFuncParam{
newFuncParam(typeFilepath),
})
ctx.RegisterFunc("createFile", newGolangFunctor(createFileFunc), typeHandle, []ExprFuncParam{
newFuncParam(typeFilepath),
})
ctx.RegisterFunc("writeFile", newGolangFunctor(writeFileFunc), typeInt, []ExprFuncParam{
newFuncParam(typeHandle),
newFuncParamFlagDef(typeItem, pfOptional|pfRepeat, ""),
})
ctx.RegisterFunc("readFile", newGolangFunctor(readFileFunc), typeString, []ExprFuncParam{
newFuncParam(typeHandle),
newFuncParamFlagDef("limitCh", pfOptional, "\\n"),
})
ctx.RegisterFunc("closeFile", newGolangFunctor(closeFileFunc), typeBoolean, []ExprFuncParam{
newFuncParam(typeHandle),
})
}
func init() {
+35 -24
View File
@@ -33,9 +33,9 @@ func doJoinStr(funcName string, sep string, it Iterator) (result any, err error)
}
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
// if len(args) < 1 {
// return nil, errMissingRequiredParameter(name, paramSeparator)
// }
// if len(args) < 1 {
// return nil, errMissingRequiredParameter(name, paramSeparator)
// }
if sep, ok := args[0].(string); ok {
if len(args) == 1 {
result = ""
@@ -62,9 +62,6 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
var source string
var ok bool
// if len(args) < 1 {
// return nil, errMissingRequiredParameter(name, paramSource)
// }
if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0])
}
@@ -93,9 +90,6 @@ func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err erro
var source string
var ok bool
// if len(args) < 1 {
// return nil, errMissingRequiredParameter(name, paramSource)
// }
if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0])
}
@@ -108,9 +102,7 @@ func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, er
var ok bool
result = false
// if len(args) < 1 {
// return result, errMissingRequiredParameter(name, paramSource)
// }
if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0])
}
@@ -133,9 +125,7 @@ func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err
var ok bool
result = false
// if len(args) < 1 {
// return result, errMissingRequiredParameter(name, paramSource)
// }
if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0])
}
@@ -159,9 +149,6 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
var parts []string
var ok bool
// if len(args) < 1 {
// return result, errMissingRequiredParameter(name, paramSource)
// }
if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0])
}
@@ -196,12 +183,36 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
// Import above functions in the context
func ImportStringFuncs(ctx ExprContext) {
ctx.RegisterFunc("joinStr", &simpleFunctor{f: joinStrFunc}, 1, -1)
ctx.RegisterFunc("subStr", &simpleFunctor{f: subStrFunc}, 1, -1)
ctx.RegisterFunc("splitStr", &simpleFunctor{f: splitStrFunc}, 2, -1)
ctx.RegisterFunc("trimStr", &simpleFunctor{f: trimStrFunc}, 1, -1)
ctx.RegisterFunc("startsWithStr", &simpleFunctor{f: startsWithStrFunc}, 2, -1)
ctx.RegisterFunc("endsWithStr", &simpleFunctor{f: endsWithStrFunc}, 2, -1)
ctx.RegisterFunc("joinStr", newGolangFunctor(joinStrFunc), typeString, []ExprFuncParam{
newFuncParam(paramSeparator),
newFuncParamFlagDef(paramItem, pfOptional|pfRepeat, ""),
})
ctx.RegisterFunc("subStr", newGolangFunctor(subStrFunc), typeString, []ExprFuncParam{
newFuncParam(paramSource),
newFuncParamFlagDef(paramStart, pfOptional, 0),
newFuncParamFlagDef(paramCount, pfOptional, -1),
})
ctx.RegisterFunc("splitStr", newGolangFunctor(splitStrFunc), "list of "+typeString, []ExprFuncParam{
newFuncParam(paramSource),
newFuncParamFlagDef(paramSeparator, pfOptional, ""),
newFuncParamFlagDef(paramCount, pfOptional, -1),
})
ctx.RegisterFunc("trimStr", newGolangFunctor(trimStrFunc), typeString, []ExprFuncParam{
newFuncParam(paramSource),
})
ctx.RegisterFunc("startsWithStr", newGolangFunctor(startsWithStrFunc), typeBoolean, []ExprFuncParam{
newFuncParam(paramSource),
newFuncParamFlag(paramPrefix, pfRepeat),
})
ctx.RegisterFunc("endsWithStr", newGolangFunctor(endsWithStrFunc), typeBoolean, []ExprFuncParam{
newFuncParam(paramSource),
newFuncParamFlag(paramSuffix, pfRepeat),
})
}
// Register the import function in the import-register.
+232
View File
@@ -0,0 +1,232 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// function.go
package expr
import (
"fmt"
"strings"
)
// ---- Function template
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
// ---- Common functor definition
type baseFunctor struct {
info ExprFunc
}
func (functor *baseFunctor) ToString(opt FmtOpt) (s string) {
if functor.info != nil {
s = functor.info.ToString(opt)
} else {
s = "func() {<body>}"
}
return s
}
func (functor *baseFunctor) SetFunc(info ExprFunc) {
functor.info = info
}
func (functor *baseFunctor) GetFunc() ExprFunc {
return functor.info
}
// ---- Linking with the functions of Go
type golangFunctor struct {
baseFunctor
f FuncTemplate
}
func newGolangFunctor(f FuncTemplate) *golangFunctor {
return &golangFunctor{f: f}
}
func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
return functor.f(ctx, name, args)
}
// ---- Linking with the functions of Expr
type exprFunctor struct {
baseFunctor
params []string
expr Expr
}
func newExprFunctor(e Expr, params []string) *exprFunctor {
return &exprFunctor{expr: e, params: params}
}
func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
for i, p := range functor.params {
if i < len(args) {
arg := args[i]
if funcArg, ok := arg.(Functor); ok {
// ctx.RegisterFunc(p, functor, 0, -1)
ctx.RegisterFunc(p, funcArg, typeAny, []ExprFuncParam{
newFuncParam(paramValue),
})
} else {
ctx.UnsafeSetVar(p, arg)
}
} else {
ctx.UnsafeSetVar(p, nil)
}
}
result, err = functor.expr.eval(ctx, false)
return
}
// ---- Function Parameters
type paramFlags uint16
const (
pfOptional paramFlags = 1 << iota
pfRepeat
)
type funcParamInfo struct {
name string
flags paramFlags
defaultValue any
}
func newFuncParam(name string) ExprFuncParam {
return &funcParamInfo{name: name}
}
func newFuncParamFlag(name string, flags paramFlags) ExprFuncParam {
return &funcParamInfo{name: name, flags: flags}
}
func newFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo {
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
}
func (param *funcParamInfo) Name() string {
return param.name
}
func (param *funcParamInfo) Type() string {
return "any"
}
func (param *funcParamInfo) IsOptional() bool {
return (param.flags & pfOptional) != 0
}
func (param *funcParamInfo) IsRepeat() bool {
return (param.flags & pfRepeat) != 0
}
func (param *funcParamInfo) DefaultValue() any {
return param.defaultValue
}
// --- Functions
type funcInfo struct {
name string
minArgs int
maxArgs int
functor Functor
params []ExprFuncParam
returnType string
}
func newFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
var minArgs = 0
var maxArgs = 0
if params != nil {
for _, p := range params {
if maxArgs == -1 {
return nil, fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
}
if p.IsOptional() {
maxArgs++
} else if maxArgs == minArgs {
minArgs++
maxArgs++
} else {
return nil, fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
}
if p.IsRepeat() {
maxArgs = -1
}
}
}
info = &funcInfo{
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params,
}
functor.SetFunc(info)
return info, nil
}
func newUnnamedFuncInfo(functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
return newFuncInfo("unnamed", functor, returnType, params)
}
func (info *funcInfo) Params() []ExprFuncParam {
return info.params
}
func (info *funcInfo) ReturnType() string {
return info.returnType
}
func (info *funcInfo) ToString(opt FmtOpt) string {
var sb strings.Builder
if len(info.Name()) == 0 {
sb.WriteString("func")
} else {
sb.WriteString(info.Name())
}
sb.WriteByte('(')
if info.params != nil {
for i, p := range info.params {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(p.Name())
if p.IsOptional() {
sb.WriteByte('=')
if s, ok := p.DefaultValue().(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else {
sb.WriteString(fmt.Sprintf("%v", p.DefaultValue()))
}
}
}
}
if info.maxArgs < 0 {
sb.WriteString(" ...")
}
sb.WriteString("): ")
if len(info.returnType) > 0 {
sb.WriteString(info.returnType)
} else {
sb.WriteString(typeAny)
}
sb.WriteString(" {<body>}")
return sb.String()
}
func (info *funcInfo) Name() string {
return info.name
}
func (info *funcInfo) MinArgs() int {
return info.minArgs
}
func (info *funcInfo) MaxArgs() int {
return info.maxArgs
}
func (info *funcInfo) Functor() Functor {
return info.functor
}
+3 -2
View File
@@ -6,7 +6,7 @@ package expr
import "path/filepath"
var globalCtx *SimpleFuncStore
var globalCtx *SimpleStore
func ImportInContext(name string) (exists bool) {
var mod *module
@@ -50,5 +50,6 @@ func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, owne
}
func init() {
globalCtx = NewSimpleFuncStore()
globalCtx = NewSimpleStore()
ImportBuiltinsFuncs(globalCtx)
}
+6 -3
View File
@@ -34,12 +34,15 @@ func EvalStringA(source string, args ...Arg) (result any, err error) {
}
func EvalStringV(source string, args []Arg) (result any, err error) {
ctx := NewSimpleFuncStore()
ctx := NewSimpleStore()
for _, arg := range args {
if isFunc(arg.Value) {
if f, ok := arg.Value.(FuncTemplate); ok {
functor := &simpleFunctor{f: f}
ctx.RegisterFunc(arg.Name, functor, 0, -1)
functor := newGolangFunctor(f)
// ctx.RegisterFunc(arg.Name, functor, 0, -1)
ctx.RegisterFunc(arg.Name, functor, typeAny, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0),
})
} else {
err = fmt.Errorf("invalid function specification: %q", arg.Name)
}
+149
View File
@@ -0,0 +1,149 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// list-type.go
package expr
import (
"fmt"
"reflect"
"strings"
)
type ListType []any
func newListA(listAny ...any) (list *ListType) {
return newList(listAny)
}
func newList(listAny []any) (list *ListType) {
if listAny != nil {
ls := make(ListType, len(listAny))
for i, item := range listAny {
ls[i] = item
}
list = &ls
}
return
}
func (ls *ListType) ToString(opt FmtOpt) (s string) {
var sb strings.Builder
sb.WriteByte('[')
if len(*ls) > 0 {
if opt&MultiLine != 0 {
sb.WriteString("\n ")
}
for i, item := range []any(*ls) {
if i > 0 {
if opt&MultiLine != 0 {
sb.WriteString(",\n ")
} else {
sb.WriteString(", ")
}
}
if s, ok := item.(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else {
sb.WriteString(fmt.Sprintf("%v", item))
}
}
if opt&MultiLine != 0 {
sb.WriteByte('\n')
}
}
sb.WriteByte(']')
s = sb.String()
if opt&Truncate != 0 && len(s) > TruncateSize {
s = TruncateString(s)
}
return
}
func (ls *ListType) String() string {
return ls.ToString(0)
}
func (ls *ListType) TypeName() string {
return "list"
}
func (list *ListType) indexDeepCmp(target any) (index int) {
index = -1
for i, item := range *list {
if reflect.DeepEqual(item, target) {
index = i
break
}
}
return
}
func (ls *ListType) contains(t *ListType) (answer bool) {
if len(*ls) >= len(*t) {
answer = true
for _, item := range *t {
if answer = ls.indexDeepSameCmp(item) >= 0; !answer {
break
}
}
}
return
}
func (list *ListType) indexDeepSameCmp(target any) (index int) {
var eq bool
var err error
index = -1
for i, item := range *list {
if eq, err = deepSame(item, target, sameContent); err != nil {
break
} else if eq {
index = i
break
}
}
return
}
func sameContent(a, b any) (same bool, err error) {
la, _ := a.(*ListType)
lb, _ := b.(*ListType)
if len(*la) == len(*lb) {
same = true
for _, item := range *la {
if pos := lb.indexDeepSameCmp(item); pos < 0 {
same = false
break
}
}
}
return
}
func deepSame(a, b any, deepCmp deepFuncTemplate) (eq bool, err error) {
if isNumOrFract(a) && isNumOrFract(b) {
if IsNumber(a) && IsNumber(b) {
if IsInteger(a) && IsInteger(b) {
li, _ := a.(int64)
ri, _ := b.(int64)
eq = li == ri
} else {
eq = numAsFloat(a) == numAsFloat(b)
}
} else {
var cmp int
if cmp, err = cmpAnyFract(a, b); err == nil {
eq = cmp == 0
}
}
} else if deepCmp != nil && IsList(a) && IsList(b) {
eq, err = deepCmp(a, b)
} else {
eq = reflect.DeepEqual(a, b)
}
return
}
+121 -2
View File
@@ -4,6 +4,125 @@
// operand-dict.go
package expr
import (
"fmt"
"reflect"
"strings"
)
type DictType map[any]any
func newDict(dictAny map[any]*term) (dict *DictType) {
var d DictType
if dictAny != nil {
d = make(DictType, len(dictAny))
for i, item := range dictAny {
d[i] = item
}
} else {
d = make(DictType)
}
dict = &d
return
}
func (dict *DictType) toMultiLine(sb *strings.Builder, indent int) {
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("{\n")
first := true
for name, value := range *dict {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
sb.WriteString(strings.Repeat("\t", indent+1))
if key, ok := name.(string); ok {
sb.WriteString(string('"') + key + string('"'))
} else {
sb.WriteString(fmt.Sprintf("%v", name))
}
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(MultiLine))
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("\n}")
}
func (dict *DictType) ToString(opt FmtOpt) string {
var sb strings.Builder
if opt&MultiLine != 0 {
dict.toMultiLine(&sb, 0)
} else {
sb.WriteByte('{')
first := true
for key, value := range *dict {
if first {
first = false
} else {
sb.WriteString(", ")
}
if s, ok := key.(string); ok {
sb.WriteString(string('"') + s + string('"'))
} else {
sb.WriteString(fmt.Sprintf("%v", key))
}
sb.WriteString(": ")
if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(opt))
} else if t, ok := value.(*term); ok {
sb.WriteString(t.String())
} else {
sb.WriteString(fmt.Sprintf("%#v", value))
}
}
sb.WriteByte('}')
}
return sb.String()
}
func (dict *DictType) String() string {
return dict.ToString(0)
}
func (dict *DictType) TypeName() string {
return "dict"
}
func (dict *DictType) hasKey(target any) (ok bool) {
for key := range *dict {
if ok = reflect.DeepEqual(key, target); ok {
break
}
}
return
}
func (dict *DictType) clone() (c *DictType) {
c = newDict(nil)
for k, v := range *dict {
(*c)[k] = v
}
return
}
func (dict *DictType) merge(second *DictType) {
if second != nil {
for k, v := range *second {
(*dict)[k] = v
}
}
}
// -------- dict term
func newDictTerm(args map[any]*term) *term {
return &term{
@@ -19,7 +138,7 @@ func newDictTerm(args map[any]*term) *term {
// -------- dict func
func evalDict(ctx ExprContext, self *term) (v any, err error) {
dict, _ := self.value().(map[any]*term)
items := make(map[any]any, len(dict))
items := make(DictType, len(dict))
for key, tree := range dict {
var param any
if param, err = tree.compute(ctx); err != nil {
@@ -28,7 +147,7 @@ func evalDict(ctx ExprContext, self *term) (v any, err error) {
items[key] = param
}
if err == nil {
v = items
v = &items
}
return
}
+4 -3
View File
@@ -7,7 +7,8 @@ package expr
import "fmt"
// -------- expr term
func newExprTerm(tk *Token) *term {
func newExprTerm(root *term) *term {
tk := NewValueToken(root.tk.row, root.tk.col, SymExpression, root.source(), root)
return &term{
tk: *tk,
parent: nil,
@@ -20,8 +21,8 @@ func newExprTerm(tk *Token) *term {
// -------- eval expr
func evalExpr(ctx ExprContext, self *term) (v any, err error) {
if expr, ok := self.value().(Expr); ok {
v, err = expr.eval(ctx, false)
if expr, ok := self.value().(*term); ok {
v, err = expr.compute(ctx)
} else {
err = fmt.Errorf("expression expected, got %T", self.value())
}
+30 -25
View File
@@ -31,7 +31,8 @@ func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
err = errTooMuchParams(info.MaxArgs(), len(params))
}
if err == nil && owner != ctx {
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
// ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
ctx.RegisterFuncInfo(info)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
@@ -75,27 +76,30 @@ func newFuncDefTerm(tk *Token, args []*term) *term {
// -------- eval func def
// TODO
type funcDefFunctor struct {
params []string
expr Expr
}
// type funcDefFunctor struct {
// params []string
// expr Expr
// }
func (functor *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
for i, p := range functor.params {
if i < len(args) {
arg := args[i]
if functor, ok := arg.(Functor); ok {
ctx.RegisterFunc(p, functor, 0, -1)
} else {
ctx.setVar(p, arg)
}
} else {
ctx.setVar(p, nil)
}
}
result, err = functor.expr.eval(ctx, false)
return
}
// func (funcDef *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
// for i, p := range funcDef.params {
// if i < len(args) {
// arg := args[i]
// if functor, ok := arg.(Functor); ok {
// // ctx.RegisterFunc(p, functor, 0, -1)
// ctx.RegisterFunc2(p, functor, typeAny, []ExprFuncParam{
// newFuncParam(paramValue),
// })
// } else {
// ctx.UnsafeSetVar(p, arg)
// }
// } else {
// ctx.UnsafeSetVar(p, nil)
// }
// }
// result, err = funcDef.expr.eval(ctx, false)
// return
// }
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
bodySpec := self.value()
@@ -104,10 +108,11 @@ func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
for _, param := range self.children {
paramList = append(paramList, param.source())
}
v = &funcDefFunctor{
params: paramList,
expr: expr,
}
v = newExprFunctor(expr, paramList)
// v = &funcDefFunctor{
// params: paramList,
// expr: expr,
// }
} else {
err = errors.New("invalid function definition: the body specification must be an expression")
}
+5 -3
View File
@@ -66,14 +66,16 @@ func evalFirstChild(ctx ExprContext, self *term) (value any, err error) {
}
func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map[string]Functor, err error) {
if dictAny, ok := firstChildValue.(map[any]any); ok {
// if dictAny, ok := firstChildValue.(map[any]any); ok {
if dictAny, ok := firstChildValue.(*DictType); ok {
requiredFields := []string{currentName, nextName}
fieldsMask := 0b11
foundFields := 0
ds = make(map[string]Functor)
for keyAny, item := range dictAny {
for keyAny, item := range *dictAny {
if key, ok := keyAny.(string); ok {
if functor, ok := item.(*funcDefFunctor); ok {
//if functor, ok := item.(*funcDefFunctor); ok {
if functor, ok := item.(Functor); ok {
ds[key] = functor
if index := slices.Index(requiredFields, key); index >= 0 {
foundFields |= 1 << index
+3 -61
View File
@@ -4,72 +4,14 @@
// operand-list.go
package expr
import (
"fmt"
"strings"
)
type ListType []any
func (ls *ListType) ToString(opt FmtOpt) string {
var sb strings.Builder
sb.WriteByte('[')
if len(*ls) > 0 {
if opt&MultiLine != 0 {
sb.WriteString("\n ")
}
for i, item := range []any(*ls) {
if i > 0 {
if opt&MultiLine != 0 {
sb.WriteString(",\n ")
} else {
sb.WriteString(", ")
}
}
if s, ok := item.(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else {
sb.WriteString(fmt.Sprintf("%v", item))
}
}
if opt&MultiLine != 0 {
sb.WriteByte('\n')
}
}
sb.WriteByte(']')
return sb.String()
}
func (ls *ListType) String() string {
return ls.ToString(0)
}
func newListA(listAny ...any) (list *ListType) {
return newList(listAny)
}
func newList(listAny []any) (list *ListType) {
if listAny != nil {
ls := make(ListType, len(listAny))
for i, item := range listAny {
ls[i] = item
}
list = &ls
}
return
}
// -------- list term
func newListTermA(args ...*term) *term {
return newListTerm(args)
return newListTerm(0, 0, args)
}
func newListTerm(args []*term) *term {
func newListTerm(row, col int, args []*term) *term {
return &term{
tk: *NewValueToken(0, 0, SymList, "[]", args),
tk: *NewValueToken(row, col, SymList, "[]", args),
parent: nil,
children: nil,
position: posLeaf,
+3 -1
View File
@@ -8,7 +8,7 @@ import "fmt"
// -------- variable term
func newVarTerm(tk *Token) *term {
return &term{
t := &term{
tk: *tk,
// class: classVar,
// kind: kindUnknown,
@@ -18,6 +18,8 @@ func newVarTerm(tk *Token) *term {
priority: priValue,
evalFunc: evalVar,
}
t.tk.Sym = SymVariable
return t
}
// -------- eval func
+15 -12
View File
@@ -22,7 +22,7 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
}
leftTerm := self.children[0]
if leftTerm.tk.Sym != SymIdentifier {
if leftTerm.tk.Sym != SymVariable {
err = leftTerm.tk.Errorf("left operand of %q must be a variable", self.tk.source)
return
}
@@ -31,20 +31,23 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
if v, err = rightChild.compute(ctx); err == nil {
if functor, ok := v.(Functor); ok {
var minArgs, maxArgs int = 0, -1
funcName := rightChild.source()
if info, exists := ctx.GetFuncInfo(funcName); exists {
minArgs = info.MinArgs()
maxArgs = info.MaxArgs()
} else if funcDef, ok := functor.(*funcDefFunctor); ok {
l := len(funcDef.params)
minArgs = l
maxArgs = l
if info, exists, _ := GetFuncInfo(ctx, funcName); exists {
// ctx.RegisterFuncInfo(info)
ctx.RegisterFunc(leftTerm.source(), info.Functor(), info.ReturnType(), info.Params())
} else if funcDef, ok := functor.(*exprFunctor); ok {
paramSpecs := ForAll(funcDef.params, newFuncParam)
// paramCount := len(funcDef.params)
// paramSpecs := make([]ExprFuncParam, paramCount)
// for i := range paramSpecs {
// paramSpecs[i] = newFuncParam(funcDef.params[i])
// }
ctx.RegisterFunc(leftTerm.source(), functor, typeAny, paramSpecs)
} else {
err = self.Errorf("unknown function %s()", funcName)
}
ctx.RegisterFunc(leftTerm.source(), functor, minArgs, maxArgs)
} else {
ctx.setVar(leftTerm.source(), v)
ctx.UnsafeSetVar(leftTerm.source(), v)
}
}
return
+8 -16
View File
@@ -8,9 +8,7 @@ package expr
func newNullCoalesceTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindUnknown,
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priCoalesce,
@@ -26,7 +24,7 @@ func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
}
leftTerm := self.children[0]
if leftTerm.tk.Sym != SymIdentifier {
if leftTerm.tk.Sym != SymVariable {
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
return
}
@@ -34,11 +32,7 @@ func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
v = leftValue
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
// if _, ok := rightValue.(Functor); ok {
// err = errCoalesceNoFunc(self.children[1])
// } else {
v = rightValue
// }
}
return
}
@@ -63,7 +57,7 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
}
leftTerm := self.children[0]
if leftTerm.tk.Sym != SymIdentifier {
if leftTerm.tk.Sym != SymVariable {
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
return
}
@@ -72,20 +66,18 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
v = leftValue
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
if functor, ok := rightValue.(Functor); ok {
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
//ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
ctx.RegisterFunc(leftTerm.source(), functor, typeAny, []ExprFuncParam{
newFuncParamFlag(paramValue, pfOptional|pfRepeat),
})
} else {
v = rightValue
ctx.setVar(leftTerm.source(), rightValue)
ctx.UnsafeSetVar(leftTerm.source(), rightValue)
}
}
return
}
// utils
// func errCoalesceNoFunc(t *term) error {
// return t.Errorf("the right operand of a coalescing operation cannot be a function definition")
// }
// init
func init() {
registerTermConstructor(SymDoubleQuestion, newNullCoalesceTerm)
+4 -4
View File
@@ -20,18 +20,18 @@ func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
var childValue any
var sourceCtx ExprContext
if len(self.children) == 0 {
if self.children == nil || len(self.children) == 0 {
sourceCtx = ctx
} else if self.children[0].symbol() == SymVariable && self.children[0].source() == "global" {
sourceCtx = globalCtx
} else if childValue, err = self.evalPrefix(ctx); err == nil {
if dc, ok := childValue.(*dataCursor); ok {
sourceCtx = dc.ctx
}
} else {
return
}
if sourceCtx != nil {
if formatter, ok := ctx.(Formatter); ok {
if formatter, ok := sourceCtx.(Formatter); ok {
v = formatter.ToString(0)
} else {
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
+6 -53
View File
@@ -4,8 +4,6 @@
// operator-dot.go
package expr
import "fmt"
// -------- dot term
func newDotTerm(tk *Token) (inst *term) {
return &term{
@@ -17,24 +15,6 @@ func newDotTerm(tk *Token) (inst *term) {
}
}
func verifyIndex(ctx ExprContext, indexTerm *term, maxValue int) (index int, err error) {
var v int
var indexValue any
if indexValue, err = indexTerm.compute(ctx); err == nil {
if v, err = indexTerm.toInt(indexValue, "index expression value must be integer"); err == nil {
if v < 0 && v >= -maxValue {
v = maxValue + v
}
if v >= 0 && v < maxValue {
index = v
} else {
err = indexTerm.Errorf("index %d out of bounds", v)
}
}
}
return
}
func evalDot(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
@@ -48,39 +28,8 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
indexTerm := self.children[1]
switch unboxedValue := leftValue.(type) {
case *ListType:
var index int
array := ([]any)(*unboxedValue)
if index, err = verifyIndex(ctx, indexTerm, len(array)); err == nil {
v = array[index]
}
case string:
var index int
if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
v = string(unboxedValue[index])
}
case map[any]any:
var ok bool
var indexValue any
if indexValue, err = indexTerm.compute(ctx); err == nil {
if v, ok = unboxedValue[indexValue]; !ok {
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
}
}
// case *dataCursor:
// if indexTerm.symbol() == SymIdentifier {
// opName := indexTerm.source()
// if opName == resetName {
// _, err = unboxedValue.Reset()
// } else if opName == cleanName {
// _, err = unboxedValue.Clean()
// } else {
// err = indexTerm.Errorf("iterators do not support command %q", opName)
// }
// v = err == nil
// }
case ExtIterator:
if indexTerm.symbol() == SymIdentifier {
if indexTerm.symbol() == SymVariable /*|| indexTerm.symbol() == SymString */ {
opName := indexTerm.source()
if unboxedValue.HasOperation(opName) {
v, err = unboxedValue.CallOperation(opName, []any{})
@@ -88,9 +37,13 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
err = indexTerm.Errorf("this iterator do not support the %q command", opName)
v = false
}
} else {
err = indexTerm.tk.ErrorExpectedGot("identifier")
}
default:
err = self.errIncompatibleTypes(leftValue, rightValue)
if rightValue, err = self.children[1].compute(ctx); err == nil {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
}
return
}
+3 -351
View File
@@ -4,204 +4,13 @@
// operand-fraction.go
package expr
//https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html
import (
"errors"
"fmt"
"math"
"strconv"
"strings"
)
type fraction struct {
num, den int64
}
func newFraction(num, den int64) *fraction {
/* if den < 0 {
den = -den
num = -num
}*/
num, den = simplifyIntegers(num, den)
return &fraction{num, den}
}
func float64ToFraction(f float64) (fract *fraction, err error) {
var sign string
intPart, decPart := math.Modf(f)
if decPart < 0.0 {
sign = "-"
intPart = -intPart
decPart = -decPart
}
dec := fmt.Sprintf("%.12f", decPart)
s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:])
// fmt.Printf("S: '%s'\n",s)
return makeGeneratingFraction(s)
}
// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39
/*
func _float64ToFraction(f float64) (num, den int64, err error) {
const expMask = 1<<11 - 1
bits := math.Float64bits(f)
mantissa := bits & (1<<52 - 1)
exp := int((bits >> 52) & expMask)
switch exp {
case expMask: // non-finite
err = errors.New("infite")
return
case 0: // denormal
exp -= 1022
default: // normal
mantissa |= 1 << 52
exp -= 1023
}
shift := 52 - exp
// Optimization (?): partially pre-normalise.
for mantissa&1 == 0 && shift > 0 {
mantissa >>= 1
shift--
}
if f < 0 {
num = -int64(mantissa)
} else {
num = int64(mantissa)
}
den = int64(1)
if shift > 0 {
den = den << shift
} else {
num = num << (-shift)
}
return
}
*/
func makeGeneratingFraction(s string) (f *fraction, err error) {
var num, den int64
var sign int64 = 1
var parts []string
if len(s) == 0 {
goto exit
}
if s[0] == '-' {
sign = int64(-1)
s = s[1:]
} else if s[0] == '+' {
s = s[1:]
}
if strings.HasSuffix(s, "()") {
s = s[0 : len(s)-2]
}
parts = strings.SplitN(s, ".", 2)
if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
return
}
if len(parts) == 1 {
f = newFraction(sign*num, 1)
} else if len(parts) == 2 {
subParts := strings.SplitN(parts[1], "(", 2)
if len(subParts) == 1 {
den = 1
dec := parts[1]
lsd := len(dec)
for i := lsd - 1; i >= 0 && dec[i] == '0'; i-- {
lsd--
}
for _, c := range dec[0:lsd] {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den * 10
}
f = newFraction(sign*num, den)
} else if len(subParts) == 2 {
sub := num
mul := int64(1)
for _, c := range subParts[0] {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
sub = sub*10 + int64(c-'0')
mul *= 10
}
if len(subParts) == 2 {
if s[len(s)-1] != ')' {
goto exit
}
p := subParts[1][0 : len(subParts[1])-1]
for _, c := range p {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den*10 + 9
}
den *= mul
}
num -= sub
f = newFraction(sign*num, den)
}
}
exit:
if f == nil {
err = errors.New("bad syntax")
}
return
}
func (f *fraction) toFloat() float64 {
return float64(f.num) / float64(f.den)
}
func (f *fraction) String() string {
return f.ToString(0)
}
func (f *fraction) ToString(opt FmtOpt) string {
var sb strings.Builder
if opt&MultiLine == 0 {
sb.WriteString(fmt.Sprintf("%d|%d", f.num, f.den))
} else {
var s, num string
if f.num < 0 && opt&TTY == 0 {
num = strconv.FormatInt(-f.num, 10)
s = "-"
} else {
num = strconv.FormatInt(f.num, 10)
}
den := strconv.FormatInt(f.den, 10)
size := max(len(num), len(den))
if opt&TTY != 0 {
sb.WriteString(fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, s+num)))
} else {
if len(s) > 0 {
sb.WriteString(" ")
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
sb.WriteByte('\n')
if len(s) > 0 {
sb.WriteString(s)
sb.WriteByte(' ')
}
sb.WriteString(strings.Repeat("-", size))
sb.WriteByte('\n')
if len(s) > 0 {
sb.WriteString(" ")
}
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den)))
}
return sb.String()
}
// -------- fraction term
func newFractionTerm(tk *Token) *term {
return &term{
@@ -246,168 +55,11 @@ func evalFraction(ctx ExprContext, self *term) (v any, err error) {
if den == 1 {
v = num
} else {
v = &fraction{num, den}
v = &FractionType{num, den}
}
return
}
func gcd(a, b int64) (g int64) {
if a < 0 {
a = -a
}
if b < 0 {
b = -b
}
if a < b {
a, b = b, a
}
r := a % b
for r > 0 {
a, b = b, r
r = a % b
}
g = b
return
}
func lcm(a, b int64) (l int64) {
g := gcd(a, b)
l = a * b / g
return
}
func sumFract(f1, f2 *fraction) (sum *fraction) {
m := lcm(f1.den, f2.den)
sum = newFraction(f1.num*(m/f1.den)+f2.num*(m/f2.den), m)
return
}
func mulFract(f1, f2 *fraction) (prod *fraction) {
prod = newFraction(f1.num*f2.num, f1.den*f2.den)
return
}
func anyToFract(v any) (f *fraction, err error) {
var ok bool
if f, ok = v.(*fraction); !ok {
if n, ok := v.(int64); ok {
f = intToFraction(n)
}
}
if f == nil {
err = errExpectedGot("fract", typeFraction, v)
}
return
}
func anyPairToFract(v1, v2 any) (f1, f2 *fraction, err error) {
if f1, err = anyToFract(v1); err != nil {
return
}
if f2, err = anyToFract(v2); err != nil {
return
}
return
}
func sumAnyFract(af1, af2 any) (sum any, err error) {
var f1, f2 *fraction
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
f := sumFract(f1, f2)
if f.num == 0 {
sum = 0
} else {
sum = simplifyFraction(f)
}
return
}
func subAnyFract(af1, af2 any) (sum any, err error) {
var f1, f2 *fraction
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
f2.num = -f2.num
f := sumFract(f1, f2)
if f.num == 0 {
sum = 0
} else {
sum = simplifyFraction(f)
}
return
}
func mulAnyFract(af1, af2 any) (prod any, err error) {
var f1, f2 *fraction
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
if f1.num == 0 || f2.num == 0 {
prod = 0
} else {
f := &fraction{f1.num * f2.num, f1.den * f2.den}
prod = simplifyFraction(f)
}
return
}
func divAnyFract(af1, af2 any) (quot any, err error) {
var f1, f2 *fraction
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
if f2.num == 0 {
err = errors.New("division by zero")
return
return
}
if f1.num == 0 || f2.den == 0 {
quot = 0
} else {
f := &fraction{f1.num * f2.den, f1.den * f2.num}
quot = simplifyFraction(f)
}
return
}
func simplifyFraction(f *fraction) (v any) {
f.num, f.den = simplifyIntegers(f.num, f.den)
if f.den == 1 {
v = f.num
} else {
v = &fraction{f.num, f.den}
}
return v
}
func simplifyIntegers(num, den int64) (a, b int64) {
if num == 0 {
return 0, 1
}
if den == 0 {
panic("fraction with denominator == 0")
}
if den < 0 {
den = -den
num = -num
}
g := gcd(num, den)
a = num / g
b = den / g
return
}
func intToFraction(n int64) *fraction {
return &fraction{n, 1}
}
func isFraction(v any) (ok bool) {
_, ok = v.(*fraction)
return ok
}
// init
func init() {
registerTermConstructor(SymVertBar, newFractionTerm)
+46
View File
@@ -0,0 +1,46 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-in.go
package expr
//-------- in term
func newInTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
evalFunc: evalIn,
}
}
// func hasKey(d map[any]any, target any) (ok bool) {
// _, ok = d[target]
// return
// }
func evalIn(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if IsList(rightValue) {
list, _ := rightValue.(*ListType)
v = list.indexDeepSameCmp(leftValue) >= 0
} else if IsDict(rightValue) {
dict, _ := rightValue.(*DictType)
v = dict.hasKey(leftValue)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymKwIn, newInTerm)
}
+123
View File
@@ -0,0 +1,123 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-index.go
package expr
// -------- index term
func newIndexTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priDot,
evalFunc: evalIndex,
}
}
func verifyKey(indexTerm *term, indexList *ListType) (index any, err error) {
index = (*indexList)[0]
return
}
func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int, err error) {
var v int
if v, err = toInt((*indexList)[0], "index expression"); err == nil {
if v < 0 && v >= -maxValue {
v = maxValue + v
}
if v >= 0 && v < maxValue {
index = v
} else {
err = indexTerm.Errorf("index %d out of bounds", v)
}
}
return
}
func verifyRange(indexTerm *term, indexList *ListType, maxValue int) (startIndex, endIndex int, err error) {
v, _ := ((*indexList)[0]).(*intPair)
startIndex = v.a
endIndex = v.b
if startIndex < 0 && startIndex >= -maxValue {
startIndex = maxValue + startIndex
}
if endIndex < 0 && endIndex >= -maxValue {
endIndex = maxValue + endIndex + 1
}
if startIndex < 0 || startIndex > maxValue {
err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex)
} else if endIndex < 0 || endIndex > maxValue {
err = indexTerm.Errorf("range end-index %d is out of bounds", endIndex)
} else if startIndex > endIndex {
err = indexTerm.Errorf("range start-index %d must not be greater than end-index %d", startIndex, endIndex)
}
return
}
func evalIndex(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
var indexList *ListType
var ok bool
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
indexTerm := self.children[1]
if indexList, ok = rightValue.(*ListType); !ok {
err = self.Errorf("invalid index expression")
return
} else if len(*indexList) != 1 {
err = indexTerm.Errorf("one index only is allowed")
return
}
if IsInteger((*indexList)[0]) {
switch unboxedValue := leftValue.(type) {
case *ListType:
var index int
if index, err = verifyIndex(indexTerm, indexList, len(*unboxedValue)); err == nil {
v = (*unboxedValue)[index]
}
case string:
var index int
if index, err = verifyIndex(indexTerm, indexList, len(unboxedValue)); err == nil {
v = string(unboxedValue[index])
}
case *DictType:
var ok bool
var indexValue any
if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
if v, ok = (*unboxedValue)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
}
}
default:
err = self.errIncompatibleTypes(leftValue, rightValue)
}
} else if isIntPair((*indexList)[0]) {
switch unboxedValue := leftValue.(type) {
case *ListType:
var start, end int
if start, end, err = verifyRange(indexTerm, indexList, len(*unboxedValue)); err == nil {
sublist := ListType((*unboxedValue)[start:end])
v = &sublist
}
case string:
var start, end int
if start, end, err = verifyRange(indexTerm, indexList, len(unboxedValue)); err == nil {
v = unboxedValue[start:end]
}
default:
err = self.errIncompatibleTypes(leftValue, rightValue)
}
}
return
}
// init
func init() {
registerTermConstructor(SymIndex, newIndexTerm)
}
+24
View File
@@ -37,6 +37,9 @@ func evalInsert(ctx ExprContext, self *term) (v any, err error) {
list, _ := rightValue.(*ListType)
newList := append(ListType{leftValue}, *list...)
v = &newList
if self.children[1].symbol() == SymVariable {
ctx.UnsafeSetVar(self.children[1].source(), v)
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
@@ -54,12 +57,33 @@ func evalAppend(ctx ExprContext, self *term) (v any, err error) {
list, _ := leftValue.(*ListType)
newList := append(*list, rightValue)
v = &newList
if self.children[0].symbol() == SymVariable {
ctx.UnsafeSetVar(self.children[0].source(), v)
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
// func evalAssignAppend(ctx ExprContext, self *term) (v any, err error) {
// var leftValue, rightValue any
// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
// return
// }
// if IsList(leftValue) {
// list, _ := leftValue.(*ListType)
// newList := append(*list, rightValue)
// v = &newList
// if
// } else {
// err = self.errIncompatibleTypes(leftValue, rightValue)
// }
// return
// }
// init
func init() {
registerTermConstructor(SymInsert, newInsertTerm)
+3 -2
View File
@@ -30,8 +30,9 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
s, _ := childValue.(string)
v = int64(len(s))
} else if IsDict(childValue) {
m, _ := childValue.(map[any]any)
v = int64(len(m))
// m, _ := childValue.(map[any]any)
m, _ := childValue.(*DictType)
v = int64(len(*m))
} else if it, ok := childValue.(Iterator); ok {
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
count, _ := extIt.CallOperation(countName, nil)
+1 -1
View File
@@ -25,7 +25,7 @@ func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
if it, ok := childValue.(Iterator); ok {
v, err = it.Next()
} else if IsInteger(childValue) && self.children[0].symbol() == SymIdentifier {
} else if IsInteger(childValue) && self.children[0].symbol() == SymVariable {
v = childValue
i, _ := childValue.(int64)
ctx.SetVar(self.children[0].source(), i+1)
+69
View File
@@ -0,0 +1,69 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-range.go
package expr
import "fmt"
// -------- range term
type intPair struct {
a, b int
}
func (p *intPair) TypeName() string {
return typePair
}
func (p *intPair) ToString(opt FmtOpt) string {
return fmt.Sprintf("(%d, %d)", p.a, p.b)
}
func isIntPair(v any) bool {
_, ok := v.(*intPair)
return ok
}
func newRangeTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priDot,
evalFunc: evalRange,
}
}
func evalRange(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
// if err = self.checkOperands(); err != nil {
// return
// }
if len(self.children) == 0 {
leftValue = int64(0)
rightValue = int64(-1)
} else if len(self.children) == 1 {
if leftValue, err = self.children[0].compute(ctx); err != nil {
return
}
rightValue = int64(-1)
} else if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if !(IsInteger(leftValue) && IsInteger(rightValue)) {
err = self.errIncompatibleTypes(leftValue, rightValue)
return
}
startIndex, _ := leftValue.(int64)
endIndex, _ := rightValue.(int64)
v = &intPair{int(startIndex), int(endIndex)}
return
}
// init
func init() {
registerTermConstructor(SymColon, newRangeTerm)
}
+94 -92
View File
@@ -4,13 +4,13 @@
// operator-rel.go
package expr
import "reflect"
//-------- equal term
func newEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindBool,
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
@@ -18,6 +18,33 @@ func newEqualTerm(tk *Token) (inst *term) {
}
}
type deepFuncTemplate func(a, b any) (eq bool, err error)
func equals(a, b any, deepCmp deepFuncTemplate) (eq bool, err error) {
if isNumOrFract(a) && isNumOrFract(b) {
if IsNumber(a) && IsNumber(b) {
if IsInteger(a) && IsInteger(b) {
li, _ := a.(int64)
ri, _ := b.(int64)
eq = li == ri
} else {
eq = numAsFloat(a) == numAsFloat(b)
}
} else {
var cmp int
if cmp, err = cmpAnyFract(a, b); err == nil {
eq = cmp == 0
}
}
} else if deepCmp != nil && IsList(a) && IsList(b) {
eq, err = deepCmp(a, b)
} else {
eq = reflect.DeepEqual(a, b)
}
return
}
func evalEqual(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
@@ -25,21 +52,7 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
return
}
if IsNumber(leftValue) && IsNumber(rightValue) {
if IsInteger(leftValue) && IsInteger(rightValue) {
li, _ := leftValue.(int64)
ri, _ := rightValue.(int64)
v = li == ri
} else {
v = numAsFloat(leftValue) == numAsFloat(rightValue)
}
} else if IsString(leftValue) && IsString(rightValue) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls == rs
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
v, err = equals(leftValue, rightValue, nil)
return
}
@@ -47,9 +60,7 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
func newNotEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindBool,
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
@@ -65,38 +76,11 @@ func evalNotEqual(ctx ExprContext, self *term) (v any, err error) {
return
}
// func evalNotEqual(ctx exprContext, self *term) (v any, err error) {
// var leftValue, rightValue any
// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
// return
// }
// if isNumber(leftValue) && isNumber(rightValue) {
// if isInteger(leftValue) && isInteger(rightValue) {
// li, _ := leftValue.(int64)
// ri, _ := rightValue.(int64)
// v = li != ri
// } else {
// v = numAsFloat(leftValue) != numAsFloat(rightValue)
// }
// } else if isString(leftValue) && isString(rightValue) {
// ls, _ := leftValue.(string)
// rs, _ := rightValue.(string)
// v = ls != rs
// } else {
// err = self.errIncompatibleTypes(leftValue, rightValue)
// }
// return
// }
//-------- less term
func newLessTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindBool,
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
@@ -104,28 +88,44 @@ func newLessTerm(tk *Token) (inst *term) {
}
}
func lessThan(self *term, a, b any) (isLess bool, err error) {
if isNumOrFract(a) && isNumOrFract(b) {
if IsNumber(a) && IsNumber(b) {
if IsInteger(a) && IsInteger(b) {
li, _ := a.(int64)
ri, _ := b.(int64)
isLess = li < ri
} else {
isLess = numAsFloat(a) < numAsFloat(b)
}
} else {
var cmp int
if cmp, err = cmpAnyFract(a, b); err == nil {
isLess = cmp < 0
}
}
} else if IsString(a) && IsString(b) {
ls, _ := a.(string)
rs, _ := b.(string)
isLess = ls < rs
// Inclusion test
} else if IsList(a) && IsList(b) {
aList, _ := a.(*ListType)
bList, _ := b.(*ListType)
isLess = len(*aList) < len(*bList) && bList.contains(aList)
} else {
err = self.errIncompatibleTypes(a, b)
}
return
}
func evalLess(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if IsNumber(leftValue) && IsNumber(rightValue) {
if IsInteger(leftValue) && IsInteger(rightValue) {
li, _ := leftValue.(int64)
ri, _ := rightValue.(int64)
v = li < ri
} else {
v = numAsFloat(leftValue) < numAsFloat(rightValue)
}
} else if IsString(leftValue) && IsString(rightValue) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls < rs
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
v, err = lessThan(self, leftValue, rightValue)
return
}
@@ -141,6 +141,19 @@ func newLessEqualTerm(tk *Token) (inst *term) {
}
}
func lessThanOrEqual(self *term, a, b any) (isLessEq bool, err error) {
if isLessEq, err = lessThan(self, a, b); err == nil {
if !isLessEq {
if IsList(a) && IsList(b) {
isLessEq, err = sameContent(a, b)
} else {
isLessEq, err = equals(a, b, nil)
}
}
}
return
}
func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
@@ -148,21 +161,8 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
return
}
if IsNumber(leftValue) && IsNumber(rightValue) {
if IsInteger(leftValue) && IsInteger(rightValue) {
li, _ := leftValue.(int64)
ri, _ := rightValue.(int64)
v = li <= ri
} else {
v = numAsFloat(leftValue) <= numAsFloat(rightValue)
}
} else if IsString(leftValue) && IsString(rightValue) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls <= rs
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
v, err = lessThanOrEqual(self, leftValue, rightValue)
return
}
@@ -170,9 +170,7 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
func newGreaterTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindBool,
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
@@ -181,10 +179,13 @@ func newGreaterTerm(tk *Token) (inst *term) {
}
func evalGreater(ctx ExprContext, self *term) (v any, err error) {
if v, err = evalLessEqual(ctx, self); err == nil {
b, _ := toBool(v)
v = !b
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
v, err = lessThan(self, rightValue, leftValue)
return
}
@@ -192,9 +193,7 @@ func evalGreater(ctx ExprContext, self *term) (v any, err error) {
func newGreaterEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindBool,
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
@@ -203,10 +202,13 @@ func newGreaterEqualTerm(tk *Token) (inst *term) {
}
func evalGreaterEqual(ctx ExprContext, self *term) (v any, err error) {
if v, err = evalLess(ctx, self); err == nil {
b, _ := toBool(v)
v = !b
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
v, err = lessThanOrEqual(self, rightValue, leftValue)
return
}
+9 -15
View File
@@ -38,15 +38,11 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
rightInt, _ := rightValue.(int64)
v = leftInt + rightInt
}
} else if IsList(leftValue) || IsList(rightValue) {
} else if IsList(leftValue) && IsList(rightValue) {
var leftList, rightList *ListType
var ok bool
if leftList, ok = leftValue.(*ListType); !ok {
leftList = &ListType{leftValue}
}
if rightList, ok = rightValue.(*ListType); !ok {
rightList = &ListType{rightValue}
}
leftList, _ = leftValue.(*ListType)
rightList, _ = rightValue.(*ListType)
sumList := make(ListType, 0, len(*leftList)+len(*rightList))
for _, item := range *leftList {
sumList = append(sumList, item)
@@ -55,19 +51,17 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
sumList = append(sumList, item)
}
v = &sumList
} else if isFraction(leftValue) || isFraction(rightValue) {
} else if (isFraction(leftValue) && IsNumber(rightValue)) || (isFraction(rightValue) && IsNumber(leftValue)) {
if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) + numAsFloat(rightValue)
} else {
v, err = sumAnyFract(leftValue, rightValue)
}
} else if IsDict(leftValue) && IsDict(rightValue) {
leftDict, _ := leftValue.(map[any]any)
rightDict, _ := rightValue.(map[any]any)
c := CloneMap(leftDict)
for key, value := range rightDict {
c[key] = value
}
leftDict, _ := leftValue.(*DictType)
rightDict, _ := rightValue.(*DictType)
c := leftDict.clone()
c.merge(rightDict)
v = c
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
+63 -21
View File
@@ -154,15 +154,34 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
return
}
func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
func (self *parser) parseList(scanner *scanner, parsingIndeces bool, allowVarRef bool) (subtree *term, err error) {
r, c := scanner.lastPos()
args := make([]*term, 0)
lastSym := SymUnknown
itemExpected := false
for lastSym != SymClosedSquare && lastSym != SymEos {
var subTree *ast
zeroRequired := scanner.current.Sym == SymColon
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
if subTree.root != nil {
args = append(args, subTree.root)
root := subTree.root
if root != nil {
if !parsingIndeces && root.symbol() == SymColon {
err = root.Errorf("unexpected range expression")
break
}
args = append(args, root)
if parsingIndeces && root.symbol() == SymColon && zeroRequired { //len(root.children) == 0 {
if len(root.children) == 1 {
root.children = append(root.children, root.children[0])
} else if len(root.children) > 1 {
err = root.Errorf("invalid range specification")
break
}
zeroTk := NewValueToken(root.tk.row, root.tk.col, SymInteger, "0", int64(0))
zeroTerm := newTerm(zeroTk)
zeroTerm.setParent(root)
root.children[0] = zeroTerm
}
} else if itemExpected {
prev := scanner.Previous()
err = prev.ErrorExpectedGot("list-item")
@@ -175,11 +194,10 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
itemExpected = lastSym == SymComma
}
if err == nil {
// TODO Check arguments
if lastSym != SymClosedSquare {
err = scanner.Previous().ErrorExpectedGot("]")
} else {
subtree = newListTerm(args)
subtree = newListTerm(r, c, args)
}
}
return
@@ -277,6 +295,7 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
err = scanner.Previous().ErrorExpectedGot("}")
} else {
subtree = newDictTerm(args)
// subtree = newMapTerm(args)
}
}
return
@@ -293,14 +312,14 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
err = tk.Errorf("case list in default clause")
return
}
if filterList, err = self.parseList(scanner, allowVarRef); err != nil {
if filterList, err = self.parseList(scanner, false, allowVarRef); err != nil {
return
}
tk = scanner.Next()
startRow = tk.row
startCol = tk.col
} else if !defaultCase {
filterList = newListTerm(make([]*term, 0))
filterList = newListTerm(startRow, startCol, make([]*term, 0))
}
if tk.Sym == SymOpenBrace {
@@ -351,6 +370,14 @@ func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, e
return self.parseGeneral(scanner, true, false, termSymbols...)
}
func couldBeACollection(t *term) bool {
var sym = SymUnknown
if t != nil {
sym = t.symbol()
}
return sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression || sym == SymVariable
}
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
var selectorTerm *term = nil
var currentTerm *term = nil
@@ -389,7 +416,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
var subTree *ast
if subTree, err = self.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil {
subTree.root.priority = priValue
err = tree.addTerm(subTree.root)
err = tree.addTerm(newExprTerm(subTree.root))
currentTerm = subTree.root
}
case SymFuncCall:
@@ -400,15 +427,28 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
}
case SymOpenSquare:
var listTerm *term
if listTerm, err = self.parseList(scanner, allowVarRef); err == nil {
err = tree.addTerm(listTerm)
parsingIndeces := couldBeACollection(currentTerm)
if listTerm, err = self.parseList(scanner, parsingIndeces, allowVarRef); err == nil {
if parsingIndeces {
indexTk := NewToken(listTerm.tk.row, listTerm.tk.col, SymIndex, listTerm.source())
indexTerm := newTerm(indexTk)
if err = tree.addTerm(indexTerm); err == nil {
err = tree.addTerm(listTerm)
}
} else {
err = tree.addTerm(listTerm)
}
currentTerm = listTerm
}
case SymOpenBrace:
var mapTerm *term
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
err = tree.addTerm(mapTerm)
currentTerm = mapTerm
if currentTerm != nil && currentTerm.symbol() == SymColon {
err = currentTerm.Errorf(`selector-case outside of a selector context`)
} else {
var mapTerm *term
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
err = tree.addTerm(mapTerm)
currentTerm = mapTerm
}
}
case SymEqual:
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
@@ -438,14 +478,16 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
}
case SymColon, SymDoubleColon:
var caseTerm *term
if selectorTerm == nil {
err = tk.Errorf("selector-case outside of a selector context")
} else if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
addSelectorCase(selectorTerm, caseTerm)
currentTerm = caseTerm
if tk.Sym == SymDoubleColon {
selectorTerm = nil
if selectorTerm != nil {
if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
addSelectorCase(selectorTerm, caseTerm)
currentTerm = caseTerm
if tk.Sym == SymDoubleColon {
selectorTerm = nil
}
}
} else {
currentTerm, err = tree.addToken2(tk)
}
default:
currentTerm, err = tree.addToken2(tk)
+8
View File
@@ -74,6 +74,14 @@ func (self *scanner) unreadChar() (err error) {
return
}
func (self *scanner) lastPos() (r, c int) {
if self.prev != nil {
r = self.prev.row
c = self.prev.col
}
return
}
func (self *scanner) Previous() *Token {
return self.prev
}
-142
View File
@@ -1,142 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// simple-func-store.go
package expr
import (
"fmt"
"strings"
)
type SimpleFuncStore struct {
SimpleVarStore
funcStore map[string]*funcInfo
}
type funcInfo struct {
name string
minArgs int
maxArgs int
functor Functor
}
func (info *funcInfo) ToString(opt FmtOpt) string {
var sb strings.Builder
var i int
sb.WriteString("func(")
for i = 0; i < info.minArgs; i++ {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(fmt.Sprintf("arg%d", i+1))
}
for ; i < info.maxArgs; i++ {
sb.WriteString(fmt.Sprintf("arg%d", i+1))
}
if info.maxArgs < 0 {
if info.minArgs > 0 {
sb.WriteString(", ")
}
sb.WriteString("...")
}
sb.WriteString(") {...}")
return sb.String()
}
func (info *funcInfo) Name() string {
return info.name
}
func (info *funcInfo) MinArgs() int {
return info.minArgs
}
func (info *funcInfo) MaxArgs() int {
return info.maxArgs
}
func (info *funcInfo) Functor() Functor {
return info.functor
}
func NewSimpleFuncStore() *SimpleFuncStore {
ctx := &SimpleFuncStore{
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
funcStore: make(map[string]*funcInfo),
}
ImportBuiltinsFuncs(ctx)
return ctx
}
func (ctx *SimpleFuncStore) Clone() ExprContext {
svs := ctx.SimpleVarStore
return &SimpleFuncStore{
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()},
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
}
}
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("funcs: {\n")
first := true
for _, name := range ctx.EnumFuncs(func(name string) bool { return true }) {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetFuncInfo(name)
sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
sb.WriteString("=")
if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(0))
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString("\n}\n")
}
func (ctx *SimpleFuncStore) ToString(opt FmtOpt) string {
var sb strings.Builder
sb.WriteString(ctx.SimpleVarStore.ToString(opt))
funcsCtxToBuilder(&sb, ctx, 0)
return sb.String()
}
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
info, exists = ctx.funcStore[name]
return
}
func (ctx *SimpleFuncStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
ctx.funcStore[name] = &funcInfo{name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor}
}
func (ctx *SimpleFuncStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
funcNames = make([]string, 0)
for name := range ctx.funcStore {
if acceptor != nil {
if acceptor(name) {
funcNames = append(funcNames, name)
}
} else {
funcNames = append(funcNames, name)
}
}
return
}
func (ctx *SimpleFuncStore) Call(name string, args []any) (result any, err error) {
if info, exists := ctx.funcStore[name]; exists {
functor := info.functor
result, err = functor.Invoke(ctx, name, args)
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
+244
View File
@@ -0,0 +1,244 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// simple-func-store.go
package expr
import (
"fmt"
"slices"
"strings"
)
type SimpleFuncStore struct {
SimpleVarStore
funcStore map[string]*funcInfo
}
type paramFlags uint16
const (
pfOptional paramFlags = 1 << iota
pfRepeat
)
type funcParamInfo struct {
name string
flags paramFlags
defaultValue any
}
func newFuncParam(name string) *funcParamInfo {
return &funcParamInfo{name: name}
}
func newFuncParamFlag(name string, flags paramFlags) *funcParamInfo {
return &funcParamInfo{name: name, flags: flags}
}
func newFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo {
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
}
func (param *funcParamInfo) Name() string {
return param.name
}
func (param *funcParamInfo) Type() string {
return "any"
}
func (param *funcParamInfo) IsOptional() bool {
return (param.flags & pfOptional) != 0
}
func (param *funcParamInfo) IsRepeat() bool {
return (param.flags & pfRepeat) != 0
}
func (param *funcParamInfo) DefaultValue() any {
return param.defaultValue
}
type funcInfo struct {
name string
minArgs int
maxArgs int
functor Functor
params []ExprFuncParam
returnType string
}
func (info *funcInfo) Params() []ExprFuncParam {
return info.params
}
func (info *funcInfo) ReturnType() string {
return info.returnType
}
func (info *funcInfo) ToString(opt FmtOpt) string {
var sb strings.Builder
sb.WriteByte('(')
if info.params != nil {
for i, p := range info.params {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(p.Name())
if p.IsOptional() {
sb.WriteByte('=')
if s, ok := p.DefaultValue().(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else {
sb.WriteString(fmt.Sprintf("%v", p.DefaultValue()))
}
}
}
}
if info.maxArgs < 0 {
sb.WriteString(" ...")
}
sb.WriteString(") -> ")
if len(info.returnType) > 0 {
sb.WriteString(info.returnType)
} else {
sb.WriteString(typeAny)
}
return sb.String()
}
func (info *funcInfo) Name() string {
return info.name
}
func (info *funcInfo) MinArgs() int {
return info.minArgs
}
func (info *funcInfo) MaxArgs() int {
return info.maxArgs
}
func (info *funcInfo) Functor() Functor {
return info.functor
}
func NewSimpleFuncStore() *SimpleFuncStore {
ctx := &SimpleFuncStore{
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
funcStore: make(map[string]*funcInfo),
}
//ImportBuiltinsFuncs(ctx)
return ctx
}
func (ctx *SimpleFuncStore) Clone() ExprContext {
svs := ctx.SimpleVarStore
return &SimpleFuncStore{
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()},
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
}
}
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("funcs: {\n")
first := true
names := ctx.EnumFuncs(func(name string) bool { return true })
slices.Sort(names)
for _, name := range names {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetFuncInfo(name)
sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
//sb.WriteString("=")
if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(0))
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString("\n}\n")
}
func (ctx *SimpleFuncStore) ToString(opt FmtOpt) string {
var sb strings.Builder
sb.WriteString(ctx.SimpleVarStore.ToString(opt))
funcsCtxToBuilder(&sb, ctx, 0)
return sb.String()
}
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
info, exists = ctx.funcStore[name]
return
}
// func (ctx *SimpleFuncStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
// ctx.funcStore[name] = &funcInfo{name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor}
// }
func (ctx *SimpleFuncStore) RegisterFuncInfo(info ExprFunc) {
ctx.funcStore[info.Name()], _ = info.(*funcInfo)
}
func (ctx *SimpleFuncStore) RegisterFunc2(name string, functor Functor, returnType string, params []ExprFuncParam) error {
var minArgs = 0
var maxArgs = 0
if params != nil {
for _, p := range params {
if maxArgs == -1 {
return fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
// } else if p.IsRepeat() {
// maxArgs = -1
// continue
}
if p.IsOptional() {
maxArgs++
} else if maxArgs == minArgs {
minArgs++
maxArgs++
} else {
return fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
}
if p.IsRepeat() {
maxArgs = -1
}
}
}
ctx.funcStore[name] = &funcInfo{
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params,
}
return nil
}
func (ctx *SimpleFuncStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
funcNames = make([]string, 0)
for name := range ctx.funcStore {
if acceptor != nil {
if acceptor(name) {
funcNames = append(funcNames, name)
}
} else {
funcNames = append(funcNames, name)
}
}
return
}
func (ctx *SimpleFuncStore) Call(name string, args []any) (result any, err error) {
if info, exists := ctx.funcStore[name]; exists {
functor := info.functor
result, err = functor.Invoke(ctx, name, args)
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
+173
View File
@@ -0,0 +1,173 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// simple-store.go
package expr
import (
"fmt"
"slices"
"strings"
)
type SimpleStore struct {
varStore map[string]any
funcStore map[string]*funcInfo
}
func NewSimpleStore() *SimpleStore {
ctx := &SimpleStore{
varStore: make(map[string]any),
funcStore: make(map[string]*funcInfo),
}
//ImportBuiltinsFuncs(ctx)
return ctx
}
func (ctx *SimpleStore) Clone() ExprContext {
return &SimpleStore{
varStore: CloneFilteredMap(ctx.varStore, func(name string) bool { return name[0] != '@' }),
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
}
}
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("vars: {\n")
first := true
for _, name := range ctx.EnumVars(func(name string) bool { return name[0] != '_' }) {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetVar(name)
sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(0))
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
// } else if _, ok = value.(map[any]any); ok {
// sb.WriteString("dict{}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("\n}\n")
}
func varsCtxToString(ctx ExprContext, indent int) string {
var sb strings.Builder
varsCtxToBuilder(&sb, ctx, indent)
return sb.String()
}
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("funcs: {\n")
first := true
names := ctx.EnumFuncs(func(name string) bool { return true })
slices.Sort(names)
for _, name := range names {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetFuncInfo(name)
sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
//sb.WriteString("=")
if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(0))
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString("\n}\n")
}
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
var sb strings.Builder
varsCtxToBuilder(&sb, ctx, 0)
funcsCtxToBuilder(&sb, ctx, 0)
return sb.String()
}
func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) {
v, exists = ctx.varStore[varName]
return
}
func (ctx *SimpleStore) UnsafeSetVar(varName string, value any) {
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
ctx.varStore[varName] = value
}
func (ctx *SimpleStore) SetVar(varName string, value any) {
// fmt.Printf("[%p] SetVar(%v, %v)\n", ctx, varName, value)
if allowedValue, ok := fromGenericAny(value); ok {
ctx.varStore[varName] = allowedValue
} else {
panic(fmt.Errorf("unsupported type %T of value %v", value, value))
}
}
func (ctx *SimpleStore) EnumVars(acceptor func(name string) (accept bool)) (varNames []string) {
varNames = make([]string, 0)
for name := range ctx.varStore {
if acceptor != nil {
if acceptor(name) {
varNames = append(varNames, name)
}
} else {
varNames = append(varNames, name)
}
}
return
}
func (ctx *SimpleStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
info, exists = ctx.funcStore[name]
return
}
func (ctx *SimpleStore) RegisterFuncInfo(info ExprFunc) {
ctx.funcStore[info.Name()], _ = info.(*funcInfo)
}
func (ctx *SimpleStore) RegisterFunc(name string, functor Functor, returnType string, params []ExprFuncParam) (err error) {
var info *funcInfo
if info, err = newFuncInfo(name, functor, returnType, params); err == nil {
ctx.funcStore[name] = info
}
return
}
func (ctx *SimpleStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
funcNames = make([]string, 0)
for name := range ctx.funcStore {
if acceptor != nil {
if acceptor(name) {
funcNames = append(funcNames, name)
}
} else {
funcNames = append(funcNames, name)
}
}
return
}
func (ctx *SimpleStore) Call(name string, args []any) (result any, err error) {
if info, exists := ctx.funcStore[name]; exists {
functor := info.functor
result, err = functor.Invoke(ctx, name, args)
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
@@ -36,7 +36,7 @@ func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
return
}
func (ctx *SimpleVarStore) setVar(varName string, value any) {
func (ctx *SimpleVarStore) UnsafeSetVar(varName string, value any) {
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
ctx.varStore[varName] = value
}
@@ -72,7 +72,12 @@ func (ctx *SimpleVarStore) Call(name string, args []any) (result any, err error)
return
}
func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
// func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
// }
func (ctx *SimpleVarStore) RegisterFuncInfo(info ExprFunc) {
}
func (ctx *SimpleVarStore) RegisterFunc2(name string, f Functor, returnType string, param []ExprFuncParam) error {
return nil
}
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
@@ -98,8 +103,8 @@ func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString(f.ToString(0))
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
} else if _, ok = value.(map[any]any); ok {
sb.WriteString("dict{}")
// } else if _, ok = value.(map[any]any); ok {
// sb.WriteString("dict{}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
+4
View File
@@ -72,6 +72,7 @@ const (
SymIdentifier
SymBool
SymInteger
SymVariable
SymFloat
SymFraction
SymString
@@ -84,6 +85,7 @@ const (
SymFuncDef
SymList
SymDict
SymIndex
SymExpression
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
@@ -99,6 +101,7 @@ const (
SymKwBut
SymKwFunc
SymKwBuiltin
SymKwIn
SymKwInclude
SymKwNil
)
@@ -112,6 +115,7 @@ func init() {
"BUILTIN": SymKwBuiltin,
"BUT": SymKwBut,
"FUNC": SymKwFunc,
"IN": SymKwIn,
"INCLUDE": SymKwInclude,
"NOT": SymKwNot,
"OR": SymKwOr,
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// ast_test.go
// t_ast_test.go
package expr
import (
+50 -3
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// dict_test.go
// t_dict_test.go
package expr
import (
@@ -24,9 +24,10 @@ func TestDictParser(t *testing.T) {
/* 1 */ {`{}`, map[any]any{}, nil},
/* 2 */ {`{123}`, nil, errors.New(`[1:6] expected ":", got "}"`)},
/* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1): "one", int64(2): "two", int64(3): "three"}, nil},
/* 4 */ {`{1:"one",2:"two",3:"three"}.2`, "three", nil},
/* 4 */ {`{1:"one",2:"two",3:"three"}[3]`, "three", nil},
/* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
/* 6 */ {`{1:"one"} + {2:"two"}`, map[any]any{1: "one", 2: "two"}, nil},
/* 7 */ {`2 in {1:"one", 2:"two"}`, true, nil},
}
succeeded := 0
@@ -41,7 +42,7 @@ func TestDictParser(t *testing.T) {
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
ctx := NewSimpleStore()
ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx)
@@ -101,3 +102,49 @@ func TestDictParser(t *testing.T) {
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
func TestDictToStringMultiLine(t *testing.T) {
var good bool
section := "dict-ToString-ML"
want := `{
"first": 1
}`
args := map[any]*term{
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
}
dict := newDict(args)
got := dict.ToString(MultiLine)
// fmt.Printf("got=%q\n", got)
if good = got == want; !good {
t.Errorf("ToString(MultiLine): got = %q, want %q", got, want)
}
if good {
t.Logf("%s -- succeeded", section)
} else {
t.Logf("%s -- failed", section)
}
}
func TestDictToString(t *testing.T) {
var good bool
section := "dict-ToString-SL"
want := `{"first": 1}`
args := map[any]*term{
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
}
dict := newDict(args)
got := dict.ToString(0)
// fmt.Printf("got=%q\n", got)
if good = got == want; !good {
t.Errorf("ToString(0): got = %q, want %q", got, want)
}
if good {
t.Logf("%s -- succeeded", section)
} else {
t.Logf("%s -- failed", section)
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// expr_test.go
// t_expr_test.go
package expr
import (
@@ -22,7 +22,7 @@ func TestExpr(t *testing.T) {
/* 3 */ {`f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil},
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
/* 10 */ {`
/* 6 */ {`
ds={
"init":func(end){@end=end; @current=0 but true},
"current":func(){current},
@@ -44,7 +44,7 @@ func TestExpr(t *testing.T) {
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
ctx := NewSimpleStore()
// ImportMathFuncs(ctx)
// ImportImportFunc(ctx)
ImportOsFuncs(ctx)
+33 -6
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// funcs_test.go
// t_funcs_test.go
package expr
import (
@@ -76,14 +76,41 @@ func TestFuncs(t *testing.T) {
/* 63 */ {`dec(true)`, float64(1), nil},
/* 64 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")},
/* 65 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got int64 (1)`)},
/* 65 */ {`dec()`, nil, errors.New(`too few params -- expected 1, got 0`)},
/* 66 */ {`dec(1,2,3)`, nil, errors.New(`too much params -- expected 1, got 3`)},
/* 67 */ {`builtin "string"; joinStr()`, nil, errors.New(`too few params -- expected 1 or more, got 0`)},
// /* 64 */ {`string(true)`, "true", nil},
/* 66 */ {`dec()`, nil, errors.New(`too few params -- expected 1, got 0`)},
/* 67 */ {`dec(1,2,3)`, nil, errors.New(`too much params -- expected 1, got 3`)},
/* 68 */ {`builtin "string"; joinStr()`, nil, errors.New(`too few params -- expected 1 or more, got 0`)},
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
}
funcs: {
add(any=0 ...) -> number,
dec(any) -> decimal,
endsWithStr(source, suffix) -> boolean,
fract(any, denominator=1) -> fraction,
import( ...) -> any,
importAll( ...) -> any,
int(any) -> integer,
isBool(any) -> boolean,
isDec(any) -> boolean,
isDict(any) -> boolean,
isFloat(any) -> boolean,
isFract(any) -> boolean,
isInt(any) -> boolean,
isList(any) -> boolean,
isNil(any) -> boolean,
isString(any) -> boolean,
joinStr(separator, item="" ...) -> string,
mul(any=1 ...) -> number,
splitStr(source, separator="", count=-1) -> list of string,
startsWithStr(source, prefix) -> boolean,
subStr(source, start=0, count=-1) -> string,
trimStr(source) -> string
}
`, nil},*/
}
t.Setenv("EXPR_PATH", ".")
//parserTest(t, "Func", inputs[54:55])
// parserTestSpec(t, "Func", inputs, 69)
parserTest(t, "Func", inputs)
}
+1 -4
View File
@@ -1,10 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// graph_test.go
// t_graph_test.go
package expr
import (
+2 -2
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// helpers_test.go
// t_helpers_test.go
package expr
import (
@@ -52,7 +52,7 @@ func TestEvalStringA(t *testing.T) {
func TestEvalString(t *testing.T) {
ctx := NewSimpleVarStore()
ctx := NewSimpleStore()
ctx.SetVar("a", uint8(1))
ctx.SetVar("b", int8(2))
ctx.SetVar("f", 2.0)
+27
View File
@@ -0,0 +1,27 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_index_test.go
package expr
import (
"errors"
"testing"
)
func TestCollections(t *testing.T) {
section := "Collection"
inputs := []inputType{
/* 1 */ {`"abcdef"[1:3]`, "bc", nil},
/* 2 */ {`"abcdef"[:3]`, "abc", nil},
/* 3 */ {`"abcdef"[1:]`, "bcdef", nil},
/* 4 */ {`"abcdef"[:]`, "abcdef", nil},
// /* 5 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil},
/* 5 */ {`"abcdef"[1:2:3]`, nil, errors.New(`[1:14] left operand '(1, 2)' [pair] and right operand '3' [integer] are not compatible with operator ":"`)},
}
t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 5)
parserTest(t, section, inputs)
}
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iterator_test.go
// t_iterator_test.go
package expr
import "testing"
+12 -8
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// list_test.go
// t_list_test.go
package expr
import (
@@ -33,13 +33,19 @@ func TestListParser(t *testing.T) {
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
/* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
/* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 14 */ {`[1,2,3].1`, int64(2), nil},
/* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
/* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
/* 17 */ {`list=["one","two","three"]; list.10`, nil, errors.New(`[1:36] index 10 out of bounds`)},
/* 14 */ {`[1,2,3][1]`, int64(2), nil},
/* 15 */ {`ls=[1,2,3] but ls[1]`, int64(2), nil},
/* 16 */ {`ls=[1,2,3] but ls[-1]`, int64(3), nil},
/* 17 */ {`list=["one","two","three"]; list[10]`, nil, errors.New(`[1:34] index 10 out of bounds`)},
/* 18 */ {`["a", "b", "c"]`, newListA("a", "b", "c"), nil},
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
/* 20 */ {`#["a", "b", "c"]`, int64(3), nil},
/* 21 */ {`"b" in ["a", "b", "c"]`, true, nil},
/* 22 */ {`a=[1,2]; (a)<<3`, []any{1, 2, 3}, nil},
/* 23 */ {`a=[1,2]; (a)<<3; 1`, []any{1, 2}, nil},
/* 24 */ {`["a","b","c","d"][1]`, "b", nil},
/* 25 */ {`["a","b","c","d"][1,1]`, nil, errors.New(`[1:19] one index only is allowed`)},
/* 26 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil},
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil},
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},
@@ -57,9 +63,7 @@ func TestListParser(t *testing.T) {
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc")
ctx := NewSimpleStore()
ImportMathFuncs(ctx)
parser := NewParser(ctx)
+54 -34
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// parser_test.go
// t_parser_test.go
package expr
import (
@@ -98,7 +98,7 @@ func TestGeneralParser(t *testing.T) {
/* 77 */ {`5 % 2`, int64(1), nil},
/* 78 */ {`5 % (-2)`, int64(1), nil},
/* 79 */ {`-5 % 2`, int64(-1), nil},
/* 80 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [int64] and right operand '2' [float64] are not compatible with operator "%"`)},
/* 80 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [integer] and right operand '2' [float] are not compatible with operator "%"`)},
/* 81 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
/* 82 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
/* 83 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
@@ -137,7 +137,7 @@ func TestGeneralParser(t *testing.T) {
/* 116 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
/* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
/* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)},
/* 119 */ {`{}`, map[any]any{}, nil},
/* 119 */ {`{}`, &DictType{}, nil},
/* 120 */ {`1|2`, newFraction(1, 2), nil},
/* 121 */ {`1|2 + 1`, newFraction(3, 2), nil},
/* 122 */ {`1|2 - 1`, newFraction(-1, 2), nil},
@@ -163,46 +163,32 @@ func TestGeneralParser(t *testing.T) {
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, "General", inputs, 102)
parserTest(t, "General", inputs)
}
func parserTestSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
succeeded := 0
failed := 0
for _, count := range spec {
good := doTest(t, section, &inputs[count-1], count)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
}
func parserTest(t *testing.T, section string, inputs []inputType) {
succeeded := 0
failed := 0
for i, input := range inputs {
var expr Expr
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
parser := NewParser(ctx)
logTest(t, i+1, section, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good := true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
eq := reflect.DeepEqual(gotResult, input.wantResult)
if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
good = false
}
}
good := doTest(t, section, &input, i+1)
if good {
succeeded++
} else {
@@ -212,6 +198,40 @@ func parserTest(t *testing.T, section string, inputs []inputType) {
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
func doTest(t *testing.T, section string, input *inputType, count int) (good bool) {
var expr Expr
var gotResult any
var gotErr error
ctx := NewSimpleStore()
parser := NewParser(ctx)
logTest(t, count, section, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good = true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
eq := reflect.DeepEqual(gotResult, input.wantResult)
if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: %q -> result = %v [%T], want = %v [%T]", count, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, input.wantErr)
good = false
}
}
return
}
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
if wantErr == nil {
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)
+52
View File
@@ -0,0 +1,52 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_template_test.go
package expr
import (
"testing"
)
func TestRelational(t *testing.T) {
section := "Relational"
inputs := []inputType{
/* 1 */ {`1 == 3-2`, true, nil},
/* 2 */ {`"a" == "a"`, true, nil},
/* 3 */ {`true == false`, false, nil},
/* 4 */ {`1.0 == 3.0-2`, true, nil},
/* 5 */ {`[1,2] == [2,1]`, false, nil},
/* 6 */ {`1 != 3-2`, false, nil},
/* 7 */ {`"a" != "a"`, false, nil},
/* 8 */ {`true != false`, true, nil},
/* 9 */ {`1.0 != 3.0-2`, false, nil},
/* 10 */ {`[1,2] != [2,1]`, true, nil},
/* 11 */ {`1|2 == 1|3`, false, nil},
/* 12 */ {`1|2 != 1|3`, true, nil},
/* 13 */ {`1|2 == 4|8`, true, nil},
/* 14 */ {`1 < 8`, true, nil},
/* 15 */ {`1 <= 8`, true, nil},
/* 16 */ {`"a" < "b"`, true, nil},
/* 17 */ {`"a" <= "b"`, true, nil},
/* 18 */ {`1.0 < 8`, true, nil},
/* 19 */ {`1.0 <= 8`, true, nil},
/* 20 */ {`1.0 <= 1.0`, true, nil},
/* 21 */ {`1.0 == 1`, true, nil},
/* 22 */ {`1|2 < 1|3`, false, nil},
/* 23 */ {`1|2 <= 1|3`, false, nil},
/* 24 */ {`1|2 > 1|3`, true, nil},
/* 25 */ {`1|2 >= 1|3`, true, nil},
/* 26 */ {`[1,2,3] > [2]`, true, nil},
/* 27 */ {`[1,2,3] > [9]`, false, nil},
/* 28 */ {`[1,2,3] >= [6]`, false, nil},
/* 29 */ {`[1,2,3] >= [2,6]`, false, nil},
/* 30 */ {`[1,[2,3],4] > [[3,2]]`, true, nil},
/* 31 */ {`[1,[2,[3,4]],5] > [[[4,3],2]]`, true, nil},
/* 32 */ {`[[4,3],2] IN [1,[2,[3,4]],5]`, true, nil},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 31)
parserTest(t, section, inputs)
}
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// scanner_test.go
// t_scanner_test.go
package expr
import (
+3 -3
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// strings_test.go
// t_strings_test.go
package expr
import (
@@ -14,8 +14,8 @@ func TestStringsParser(t *testing.T) {
/* 2 */ {`"uno" + 2`, `uno2`, nil},
/* 3 */ {`"uno" + (2+1)`, `uno3`, nil},
/* 4 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 5 */ {`"abc".1`, `b`, nil},
/* 5 */ {`#"abc"`, int64(3), nil},
/* 5 */ {`"abc"[1]`, `b`, nil},
/* 6 */ {`#"abc"`, int64(3), nil},
}
parserTest(t, "String", inputs)
}
+21
View File
@@ -0,0 +1,21 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_template_test.go
package expr
import (
"testing"
)
func TestSomething(t *testing.T) {
section := "Something"
inputs := []inputType{
/* 1 */ {`1`, int64(1), nil},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 1)
parserTest(t, section, inputs)
}
+2 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// term_test.go
// t_term_test.go
package expr
import (
@@ -31,6 +31,7 @@ func TestGetRoom(t *testing.T) {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
}
func TestGetChildrenCount(t *testing.T) {
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// token_test.go
// t_token_test.go
package expr
import (
+139
View File
@@ -0,0 +1,139 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_utils_test.go
package expr
import (
"errors"
"testing"
)
func TestIsString(t *testing.T) {
count := 0
succeeded := 0
failed := 0
count++
if !IsBool(true) {
t.Errorf("%d: IsBool(true) -> result = false, want true", count)
failed++
} else {
succeeded++
}
count++
if !IsString("abc") {
t.Errorf("%d: IsString(\"abc\") -> result = false, want true", count)
failed++
} else {
succeeded++
}
count++
if !IsInteger(int64(123)) {
t.Errorf("%d: IsInteger(123) -> result = false, want true", count)
failed++
} else {
succeeded++
}
count++
if !IsFloat(1.23) {
t.Errorf("%d: IsFloat(1.23) -> result = false, want true", count)
failed++
} else {
succeeded++
}
count++
if !IsFloat(numAsFloat(123)) {
t.Errorf("%d: IsFloat(numAsFloat(123)) -> result = false, want true", count)
failed++
} else {
succeeded++
}
count++
b, ok := toBool(true)
if !(ok && b) {
t.Errorf("%d: toBool(true) b=%v, ok=%v -> result = false, want true", count, b, ok)
failed++
} else {
succeeded++
}
count++
b, ok = toBool("abc")
if !(ok && b) {
t.Errorf("%d: toBool(\"abc\") b=%v, ok=%v -> result = false, want true", count, b, ok)
failed++
} else {
succeeded++
}
t.Logf("test count: %d, succeeded count: %d, failed count: %d", count, succeeded, failed)
}
func TestToIntOk(t *testing.T) {
source := int64(64)
wantValue := int(64)
wantErr := error(nil)
gotValue, gotErr := toInt(source, "test")
if gotErr != nil || gotValue != wantValue {
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
source, gotValue, gotErr, wantValue, wantErr)
}
}
func TestToIntErr(t *testing.T) {
source := uint64(64)
wantValue := 0
wantErr := errors.New(`test expected integer, got uint64 (64)`)
gotValue, gotErr := toInt(source, "test")
if gotErr.Error() != wantErr.Error() || gotValue != wantValue {
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
source, gotValue, gotErr, wantValue, wantErr)
}
}
func TestAnyInteger(t *testing.T) {
type inputType struct {
source any
wantValue int64
wantOk bool
}
section := "utils.anyInteger"
inputs := []inputType{
/* 1 */ {int8(-8), int64(-8), true},
/* 2 */ {int16(-16), int64(-16), true},
/* 3 */ {int32(-32), int64(-32), true},
/* 4 */ {int64(-64), int64(-64), true},
/* 5 */ {uint8(8), int64(8), true},
/* 6 */ {uint16(16), int64(16), true},
/* 7 */ {uint32(32), int64(32), true},
/* 8 */ {uint64(64), int64(64), true},
/* 9 */ {int(-1), int64(-1), true},
/* 10 */ {true, 0, false},
}
succeeded := 0
failed := 0
for i, input := range inputs {
gotValue, gotOk := anyInteger(input.source)
if gotOk != input.wantOk || gotValue != input.wantValue {
t.Errorf("%d: anyInteger(%v) -> gotOk = %t, wantOk = %t; gotValue = %v, wantValue = %v",
i+1, input.source, gotOk, input.wantOk, gotValue, input.wantValue)
failed++
} else {
succeeded++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
+8 -3
View File
@@ -12,6 +12,7 @@ type termPriority uint32
const (
priNone termPriority = iota
priRange
priBut
priAssign
priOr
@@ -161,10 +162,14 @@ func (self *term) toInt(computedValue any, valueDescription string) (i int, err
}
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
leftType := getTypeName(leftValue)
leftText := getFormatted(leftValue, Truncate)
rightType := getTypeName(rightValue)
rightText := getFormatted(rightValue, Truncate)
return self.tk.Errorf(
"left operand '%v' [%T] and right operand '%v' [%T] are not compatible with operator %q",
leftValue, leftValue,
rightValue, rightValue,
"left operand '%s' [%s] and right operand '%s' [%s] are not compatible with operator %q",
leftText, leftType,
rightText, rightType,
self.source())
}
+18 -4
View File
@@ -35,17 +35,17 @@ func IsList(v any) (ok bool) {
}
func IsDict(v any) (ok bool) {
_, ok = v.(map[any]any)
_, ok = v.(*DictType)
return ok
}
func IsFract(v any) (ok bool) {
_, ok = v.(*fraction)
_, ok = v.(*FractionType)
return ok
}
func IsRational(v any) (ok bool) {
if _, ok = v.(*fraction); !ok {
if _, ok = v.(*FractionType); !ok {
_, ok = v.(int64)
}
return ok
@@ -76,7 +76,7 @@ func isIterator(v any) (ok bool) {
func numAsFloat(v any) (f float64) {
var ok bool
if f, ok = v.(float64); !ok {
if fract, ok := v.(*fraction); ok {
if fract, ok := v.(*FractionType); ok {
f = fract.toFloat()
} else {
i, _ := v.(int64)
@@ -147,6 +147,12 @@ func fromGenericAny(v any) (exprAny any, ok bool) {
if exprAny, ok = anyFloat(v); ok {
return
}
if exprAny, ok = v.(*DictType); ok {
return
}
if exprAny, ok = v.(*ListType); ok {
return
}
return
}
@@ -203,3 +209,11 @@ func toInt(value any, description string) (i int, err error) {
}
return
}
func ForAll[T, V any](ts []T, fn func(T) V) []V {
result := make([]V, len(ts))
for i, t := range ts {
result[i] = fn(t)
}
return result
}