Compare commits

...

18 Commits

Author SHA1 Message Date
camoroso b99ad5def1 Merge branch 'iterator/dict' 2026-04-19 15:18:07 +02:00
camoroso 0d44b8697b doc: more details about iterators over dicts 2026-04-19 15:17:21 +02:00
camoroso 1a7e537921 more tests on iterator over dicts 2026-04-19 15:16:25 +02:00
camoroso 807df0f3a8 Iterator: added support for iterators over dictionaries 2026-04-19 15:08:14 +02:00
camoroso d7dd628fc9 doc: further details about iterator definition 2026-04-19 08:20:40 +02:00
camoroso 1f57ba28dd Doc: progress about builtin modules 2026-04-18 12:21:42 +02:00
camoroso 92bd366380 builtin-base: bool() supports lists and dicts, int() supports fractions 2026-04-18 12:20:05 +02:00
camoroso 7b93c5b4ac doc: begin of iterators description 2026-04-16 10:13:41 +02:00
camoroso 3ba8194ddb Expr.doc, a lot of fixes 2026-04-15 18:17:27 +02:00
camoroso 037565c41e New var() function added to the builtin set 2026-04-15 16:04:12 +02:00
camoroso f8d12b1a93 added TestGoFunction() to test Go functions 2026-04-15 16:03:41 +02:00
camoroso 518d4d8d65 simple-store.go: added function Init() 2026-01-03 09:11:43 +01:00
camoroso d64602cb00 builtin-string.go: fix return type of strLower() and strUpper() 2025-11-15 06:18:38 +01:00
camoroso 4709248828 string builtin: strUpper() and strLower() added 2025-11-13 20:53:07 +01:00
camoroso 5ecf81412e Doc: pre & post incremente/decrement 2025-01-05 12:53:50 +01:00
camoroso ff4db34f7b t_operator_test.go: test on -- and ++ prefix operators 2025-01-05 12:49:36 +01:00
camoroso 0f848071c2 New prefix operators ++ and -- 2025-01-04 18:12:38 +01:00
camoroso 6b3351b324 new prefix operator "!" as logic NOT 2025-01-04 17:47:59 +01:00
22 changed files with 2064 additions and 140 deletions
+30
View File
@@ -72,6 +72,10 @@ func boolFunc(ctx ExprContext, name string, args map[string]any) (result any, er
result = v
case string:
result = len(v) > 0
case *ListType:
result = len(*v) > 0
case *DictType:
result = len(*v) > 0
default:
err = ErrCantConvert(name, v, "bool")
}
@@ -95,6 +99,8 @@ func intFunc(ctx ExprContext, name string, args map[string]any) (result any, err
if i, err = strconv.Atoi(v); err == nil {
result = int64(i)
}
case *FractionType:
result = int64(v.num / v.den)
default:
err = ErrCantConvert(name, v, "int")
}
@@ -211,6 +217,25 @@ func evalFunc(ctx ExprContext, name string, args map[string]any) (result any, er
return
}
func varFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var varName string
var ok bool
if varName, ok = args[ParamName].(string); !ok {
return nil, ErrWrongParamType(name, ParamName, TypeString, args[ParamName])
}
if result, ok = args[ParamValue]; ok && result != nil {
ctx.GetParent().UnsafeSetVar(varName, result)
// } else {
// err = ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
// }
} else if result, ok = ctx.GetVar(varName); !ok {
err = ErrUnknownVar(name, varName)
}
return
}
//// import
func ImportBuiltinsFuncs(ctx ExprContext) {
@@ -240,6 +265,11 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("eval", NewGolangFunctor(evalFunc), TypeAny, []ExprFuncParam{
NewFuncParam(ParamSource),
})
ctx.RegisterFunc("var", NewGolangFunctor(varFunc), TypeAny, []ExprFuncParam{
NewFuncParam(ParamName),
NewFuncParamFlagDef(ParamValue, PfDefault, nil),
})
}
func init() {
+26
View File
@@ -200,6 +200,24 @@ func splitStrFunc(ctx ExprContext, name string, args map[string]any) (result any
return
}
func upperStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
if source, ok := args[ParamSource].(string); ok {
result = strings.ToUpper(source)
} else {
err = ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
}
return
}
func lowerStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
if source, ok := args[ParamSource].(string); ok {
result = strings.ToLower(source)
} else {
err = ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
}
return
}
// --- End of function definitions
// Import above functions in the context
@@ -236,6 +254,14 @@ func ImportStringFuncs(ctx ExprContext) {
NewFuncParam(ParamSuffix),
NewFuncParamFlag(strParamOther, PfRepeat),
})
ctx.RegisterFunc("strUpper", NewGolangFunctor(upperStrFunc), TypeString, []ExprFuncParam{
NewFuncParam(ParamSource),
})
ctx.RegisterFunc("strLower", NewGolangFunctor(lowerStrFunc), TypeString, []ExprFuncParam{
NewFuncParam(ParamSource),
})
}
// Register the import function in the import-register.
+4
View File
@@ -80,6 +80,10 @@ func ErrUnknownParam(funcName, paramName string) error {
return fmt.Errorf("%s(): unknown parameter %q", funcName, paramName)
}
func ErrUnknownVar(funcName, varName string) error {
return fmt.Errorf("%s(): unknown variable %q", funcName, varName)
}
// --- Operator errors
func ErrLeftOperandMustBeVariable(leftTerm, opTerm *term) error {
+209
View File
@@ -0,0 +1,209 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// dict-iterator.go
package expr
import (
"fmt"
"io"
"slices"
"strings"
)
type dictIterMode int
const (
dictIterModeKeys dictIterMode = iota
dictIterModeValues
dictIterModeItems
)
type DictIterator struct {
a *DictType
count int
index int
keys []any
iterMode dictIterMode
}
type sortType int
const (
sortTypeNone sortType = iota
sortTypeAsc
sortTypeDesc
sortTypeDefault = sortTypeAsc
)
func (it *DictIterator) makeKeys(m map[any]any, sort sortType) {
it.keys = make([]any, 0, len(m))
if sort == sortTypeNone {
for keyAny := range m {
it.keys = append(it.keys, keyAny)
}
} else {
scalarMap := make(map[string]any, len(m))
scalerKeys := make([]string, 0, len(m))
for keyAny := range m {
keyStr := fmt.Sprint(keyAny)
scalarMap[keyStr] = keyAny
scalerKeys = append(scalerKeys, keyStr)
}
switch sort {
case sortTypeAsc:
slices.Sort(scalerKeys)
case sortTypeDesc:
slices.Sort(scalerKeys)
slices.Reverse(scalerKeys)
}
for _, keyStr := range scalerKeys {
it.keys = append(it.keys, scalarMap[keyStr])
}
}
}
func NewDictIterator(dict *DictType, args []any) (it *DictIterator, err error) {
var sortType = sortTypeNone
var s string
it = &DictIterator{a: dict, count: 0, index: -1, keys: nil, iterMode: dictIterModeKeys}
if len(args) > 0 {
if s, err = ToGoString(args[0], "sort type"); err == nil {
switch strings.ToLower(s) {
case "a", "asc":
sortType = sortTypeAsc
case "d", "desc":
sortType = sortTypeDesc
case "n", "none", "nosort", "no-sort":
sortType = sortTypeNone
case "", "default":
sortType = sortTypeDefault
default:
err = fmt.Errorf("invalid sort type %q", s)
}
if err == nil && len(args) > 1 {
if s, err = ToGoString(args[1], "iteration mode"); err == nil {
switch strings.ToLower(s) {
case "k", "key", "keys":
it.iterMode = dictIterModeKeys
case "v", "value", "values":
it.iterMode = dictIterModeValues
case "i", "item", "items":
it.iterMode = dictIterModeItems
case "", "default":
it.iterMode = dictIterModeKeys
default:
err = fmt.Errorf("invalid iteration mode %q", s)
}
}
}
}
}
it.makeKeys(*dict, sortType)
return
}
func NewMapIterator(m map[any]any) (it *DictIterator) {
it = &DictIterator{a: (*DictType)(&m), count: 0, index: -1, keys: nil}
it.makeKeys(m, sortTypeNone)
return
}
func (it *DictIterator) String() string {
var l = 0
if it.a != nil {
l = len(*it.a)
}
return fmt.Sprintf("$(#%d)", l)
}
func (it *DictIterator) TypeName() string {
return "DictIterator"
}
func (it *DictIterator) HasOperation(name string) bool {
// yes := name == NextName || name == ResetName || name == IndexName || name == CountName || name == CurrentName
yes := slices.Contains([]string{NextName, ResetName, IndexName, CountName, CurrentName, CleanName, KeyName, ValueName}, name)
return yes
}
func (it *DictIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case NextName:
v, err = it.Next()
case ResetName:
err = it.Reset()
case CleanName:
err = it.Clean()
case IndexName:
v = int64(it.Index())
case CurrentName:
v, err = it.Current()
case CountName:
v = it.count
case KeyName:
if it.index >= 0 && it.index < len(it.keys) {
v = it.keys[it.index]
} else {
err = io.EOF
}
case ValueName:
if it.index >= 0 && it.index < len(it.keys) {
a := *(it.a)
v = a[it.keys[it.index]]
} else {
err = io.EOF
}
default:
err = errNoOperation(name)
}
return
}
func (it *DictIterator) Current() (item any, err error) {
if it.index >= 0 && it.index < len(it.keys) {
switch it.iterMode {
case dictIterModeKeys:
item = it.keys[it.index]
case dictIterModeValues:
a := *(it.a)
item = a[it.keys[it.index]]
case dictIterModeItems:
a := *(it.a)
pair := []any{it.keys[it.index], a[it.keys[it.index]]}
item = newList(pair)
}
} else {
err = io.EOF
}
return
}
func (it *DictIterator) Next() (item any, err error) {
it.index++
if item, err = it.Current(); err != io.EOF {
it.count++
}
return
}
func (it *DictIterator) Index() int {
return it.index
}
func (it *DictIterator) Count() int {
return it.count
}
func (it *DictIterator) Reset() error {
it.index = -1
it.count = 0
return nil
}
func (it *DictIterator) Clean() error {
return nil
}
+601 -40
View File
@@ -20,15 +20,24 @@ Expressions calculator
:rouge-style: gruvbox
// :rouge-style: colorful
//:rouge-style: monokay
// Work around to manage double-column in back-tick quotes
// Workaround to manage double-column in back-tick quotes
:2c: ::
// Workaround to manage double-plus in back-tick quotes
:plusplus: ++
// Workaround to manage asterisk in back-tick quotes
:star: *
:sp: &#160;
:2sp: &#160;&#160;
:4sp: &#160;&#160;&#160;&#160;
toc::[]
#TODO: Work in progress (last update on 2024/06/21, 05:40 a.m.)#
#TODO: Work in progress (last update on 2026/04/15, 6:02 p.m.)#
== Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
_Expr_ is a GO package that can analyze, interpret and calculate expressions.
=== Concepts and terminology
@@ -42,7 +51,7 @@ Expressions are texts containing sequences of operations represented by a syntax
image::expression-diagram.png[]
==== Variables
_Expr_ supports variables. The result of an expression can be stored in a variable and reused in other espressions simply specifying the name of the variable as an operand.
_Expr_ supports variables. The result of an expression can be stored in a variable and reused in other espressions by simply specifying the name of the variable as an operand.
==== Multi-expression
An input text valid for _Expr_ can contain more than an expression. Expressions are separated by [blue]`;` (semicolon). When an input contains two or more expressions it is called _multi-expression_.
@@ -58,9 +67,40 @@ The expression context is analogous to the stack-frame of other programming lang
Function contexts are created by cloning the calling context. More details on this topic are given later in this document.
_Expr_ creates and keeps a inner _global context_ where it stores imported functions, either from builtin or plugin modules. To perform calculations, the user program must provide its own context; this is the _main context_. All calculations take place in this context. As mentioned eralier, when a function is called, a new context is created by cloning the calling context. The created context can be called _function context_.
_Expr_ creates and keeps a inner _global context_ where it stores imported functions, either from builtin or plugin modules. To perform calculations, the user program must provide its own context; this is the _main context_. All calculations take place in this context. As mentioned earlier, when a function is called, a new context is created by cloning the calling context. The created context can be called _function context_.
Imported functions are registerd in the _global context_. When an expression first calls an imported function, that function is linked to the current context; this can be the _main context_ or a _function context_.
Imported functions are registered in the _global context_. When an expression first calls an imported function, that function is linked to the current context; this can be the _main context_ or a _function context_.
===== Inspecting contexts
_Expr_ provides the operator [blue]_$$_ that returns the current context. This can be used to inspect the content of the context, for example to check the value of a variable or to see which functions are currently linked to the context. This operator is primarily intended for debugging purposes.
An interactive tool could like `dev-expr` (see <<_dev-expr_test_tool>>) can be used to inspect contexts interactively.
.Example: inspecting contexts
`>>>` [blue]`$$` +
[green]`{"variables": {"ls": "[10, 20, 30]", "it": "$(#3)", "last": "-1"}, "functions": {"about": "about():string{}", "bin": "bin(value, digits=8):integer{}", "ctrl": "ctrl(prop, value):any{}", "ctrlList": "ctrlList():list-of-strings{}", "envGet": "envGet(name):string{}", "envSet": "envSet(name, value):string{}"}}` +
`>>>` [gray]_// Let use the *ml* command to activate multi-line output of contexts, which is more readable._ +
`>>>` [blue]`ml` +
`>>>` [blue]`$$` +
[green]`{` +
[green]`{2sp}"variables": {` +
[green]`{4sp}"last": {"variables": {"last": "-1", "ls": "[10, 20, 30]", "it": "$(#3)"}, "functions": {"ctrlList": "ctrlList():list-of-strings{}", "envGet": "envGet(name):string{}", "envSet": "envSet(name, value):string{}", "about": "about():string{}", "bin": "bin(value, digits=8):integer{}", "ctrl": "ctrl(prop, value):any{}"}},` +
[green]`{4sp}"ls": [10, 20, 30],` +
[green]`{4sp}"it": $(#3)` +
[green]`{2sp}},` +
[green]`{2sp}"functions": {` +
[green]`{4sp}"ctrlList": ctrlList():list-of-strings{},` +
[green]`{4sp}"envGet": envGet(name):string{},` +
[green]`{4sp}"envSet": envSet(name, value):string{},` +
[green]`{4sp}"about": about():string{},` +
[green]`{4sp}"bin": bin(value, digits=8):integer{},` +
[green]`{4sp}"ctrl": ctrl(prop, value):any{}` +
[green]`{2sp}}` +
[green]`}`
In order to inspect the global context issue the [blue]`$$global` operator.
=== `dev-expr` test tool
Before we begin to describe the syntax of _Expr_, it is worth introducing _dev-expr_ because it will be used to show many examples of expressions.
@@ -145,7 +185,7 @@ dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoro
<2> Fractions: _numerator_ : _denominator_.
<3> Activate multi-line output of fractions.
<4> But operator, see <<_but_operator>>.
<5> Multi-expression: the same result of the previous single expression but this it is obtained with two separated calculations.
<5> Multi-expression: the same result as the previous single expression, but this time it is obtained with two separate calculations.
== Data types
_Expr_ has its type system which is a subset of Golang's type system. It supports numerical, string, relational, boolean expressions, and mixed-type lists and maps.
@@ -240,10 +280,11 @@ _Expr_ also supports fractions. Fraction literals are made with two integers sep
.Fraction literal syntax
====
*_fraction_* = [__sign__] (_num-den-spec_ "**:**" _float-spec_) +
*_fraction_* = [__sign__] (_num-den-spec_ | _float-spec_) +
_sign_ = "**+**" | "**-**" +
_num-den-spec_ = _digit-seq_ "**|**" _digit-seq_ +
_float-spec_ = _dec-seq_ "**.**" [_dec-seq_] "**(**" _dec-seq_ "**)**" +
_num-den-spec_ = _digit-seq_ "**:**" _digit-seq_ +
_float-spec_ = _dec-seq_ "**.**" [_dec-seq_] "**(**" _repetend_ "**)**" +
_repetend_ = _dec-seq_ +
_dec-seq_ = _see-integer-literal-syntax_ +
_digit-seq_ = _see-integer-literal-syntax_
====
@@ -276,6 +317,8 @@ _digit-seq_ = _see-integer-literal-syntax_
`>>>` [blue]`1:(-2)` +
[green]`-1:2`
`>>>` [blue]`1.(3)` // 1.33333... +
[green]`4:3`
Fractions can be used together with integers and floats in expressions.
@@ -395,12 +438,13 @@ Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relationa
[CAUTION]
====
Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of the [blue]`and` and [blue]`or` operators is sufficient to establish the result of the whole operation, the right expression would not be evaluated at all.
.Example
[source,go]
----
2 > (a=1) or (a=8) > 0; a // <1>
----
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and, as a conseguence, its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
TIP: `dev-expr` provides the _ctrl()_ function that allows to change this behaviour.
@@ -492,10 +536,10 @@ Array's items can be accessed using the index `[]` operator.
[green]`3`
.Examples: Element insertion
`>>>` [blue]`"first" >> list` +
`>>>` [blue]`"first" +> list` +
[green]`["first", "one", "six", "three"]`
`>>>` [blue]`list << "last"` +
`>>>` [blue]`list <+ "last"` +
[green]`["first", "one", "six", "three", "last"]`
.Examples: Element in list
@@ -509,7 +553,7 @@ Array's items can be accessed using the index `[]` operator.
`>>>` [blue]`[1,2,3] + ["one", "two", "three"]` +
[green]`[1, 2, 3, "one", "two", "three"]`
`>>>` [blue]`[1,2,3,4] - [2,4]` +
`>>>` [blue]`[1,2,3,2,4] - [2,4]` +
[green]`[1, 3]`
@@ -570,7 +614,7 @@ _Expr_, like most programming languages, supports variables. A variable is an id
.Variable literal syntax
====
*_variable_* = _identifier_ "*=*" _any-value_ +
_identifier_ = _alpha_ {(_alpha_)|_dec-digit_|"*_*"} +
_identifier_ = _alpha_ {_alpha_|_dec-digit_|"*_*"} +
__alpha__ = "*a*"|"*b*"|..."*z*"|"*A*"|"*B*"|..."*Z*"
====
@@ -695,7 +739,7 @@ The [blue]`:` symbol (colon) is the separator of the selector-cases. Note that i
=== Variable default value [blue]`??`, [blue]`?=`, and [blue]`?!`
The left operand of first two operators, [blue]`??` and [blue]`?=`, must be a variable. The right operator can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.
The left operand of the first two operators, [blue]`??` and [blue]`?=`, must be a variable. The right operator can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.
IMPORTANT: If the left variable is defined, the right expression is not evaluated at all.
@@ -741,8 +785,11 @@ The table below shows all supported operators by decreasing priorities.
.2+|*ITEM*| [blue]`[`...`]` | _Postfix_ | _List item_| _list_ `[` _integer_ `]` -> _any_
| [blue]`[`...`]` | _Postfix_ | _Dict item_ | _dict_ `[` _any_ `]` -> _any_
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `++` -> _integer_
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `++` -> _any_
.5+|*INC/DEC*| [blue]`++` | _Postfix_ | _Iterator next item_ | _iterator_ `++` -> _any_
| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `++` -> _integer_
| [blue]`++` | _Prefix_ | _Pre increment_ | `++` _integer-variable_ -> _integer_
| [blue]`--` | _Postfix_ | _Post decrement_ | _integer-variable_ `--` -> _integer_
| [blue]`--` | _Prefix_ | _Pre decrement_ | `--` _integer-variable_ -> _integer_
.3+|*DEFAULT*| [blue]`??` | _Infix_ | _Default value_| _variable_ `??` _any-expr_ -> _any_
| [blue]`?=` | _Infix_ | _Default/assign value_| _variable_ `?=` _any-expr_ -> _any_
| [blue]`?!` | _Infix_ | _Alternate value_| _variable_ `?!` _any-expr_ -> _any_
@@ -769,9 +816,10 @@ The table below shows all supported operators by decreasing priorities.
| [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `+` _dict_ -> _dict_
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `-` _number_ -> _number_
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `-` _list_ -> _list_
.1+|*BITWISE NOT*| [blue]`~` | _Prefix_ | _Binary Not_ | `~` _number_ -> _number_
.1+|*BITWISE AND*| [blue]`&` | _Infix_ | _Binary And_ | _number_ `&` _number_ -> _number_
.1+|*BITWISE OR*| [blue]`\|` | _Infix_ | _Binary Or_ | _number_ `\|` _number_ -> _number_
.1+|*BITWISE NOT*| [blue]`~` | _Prefix_ | _Binary Not_ | `~` _integer_ -> _integer_
.1+|*BITWISE AND*| [blue]`&` | _Infix_ | _Binary And_ | _integer_ `&` _integer_ -> _integer_
.2+|*BITWISE OR*| [blue]`\|` | _Infix_ | _Binary Or_ | _integer_ `\|` _integer_ -> _integer_
| [blue]`^` | _Infix_ | _Binary Xor_ | _integer_ `^` _integer_ -> _integer_
.8+|*RELATION*| [blue]`<` | _Infix_ | _Less_ | _comparable_ `<` _comparable_ -> _boolean_
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `\<=` _comparable_ -> _boolean_
| [blue]`>` | _Infix_ | _Greater_ | _comparable_ `>` _comparable_ -> _boolean_
@@ -788,7 +836,7 @@ The table below shows all supported operators by decreasing priorities.
.2+|*INSERT*| [blue]`+>` | _Infix_ | _Prepend_ | _any_ `+>` _list_ -> _list_
| [blue]`<+` | _Infix_ | _Append_ | _list_ `<+` _any_ -> _list_
.2+|*ASSIGN*| [blue]`=` | _Infix_ | _Assignment_ | _identifier_ `=` _any_ -> _any_
4+| _See also the special assignment operators table below_
4+| _See also the table of special allocation operators below_
.1+|*BUT*| [blue]`but` | _Infix_ | _But_ | _any_ `but` _any_ -> _any_
.1+|*RANGE*| [blue]`:` | _Infix_ | _Index-range_ | _integer_ `:` _integer_ -> _integer-pair_
|===
@@ -837,12 +885,12 @@ Functions in _Expr_ are very similar to functions available in many programming
=== _Expr_ function definition
A function is identified and referenced by its name. It can have zero or more parameter. _Expr_ functions also support optional parameters and passing paramters by name.
An expr-function is identified and referenced by its name. It can have zero or more parameter. expr-functions also support optional parameters and passing paramters by name.
.Expr's function definition syntax
====
*_function-definition_* = _identifier_ "**=**" "**func(**" [_formal-param-list_] "**)**" "**{**" _multi-expression_ "**}**" +
_formal-param_list_ = _required-param-list_ [ "**,**" _optional-param-list_ ] +
_formal-param-list_ = _required-param-list_ [ "**,**" _optional-param-list_ ] +
_required-param-list_ = _identifier_ { "**,**" _identifier_ } +
_optional-param-list_ = _optional-parm_ { "**,**" _optional-param_ } +
_optional-param_ = _param-name_ "**=**" _any-expr_ +
@@ -854,7 +902,7 @@ _param-name_ = _identifier_
`>>>` [blue]`sum = func(a, b){ a + b }` +
[green]`sum(a, b):any{}`
^(\*)^ Since the plus, *+*, operator is defined for multiple data-types, the _sum()_ function can be used for any pair of that types.
^(*)^ Since the plus, **+**, operator is defined for multiple data-types, the _sum()_ function can be used for any pair of that types.
`>>>` [gray]_// A more complex example: recursive calculation of the n-th value of Fibonacci's sequence_ +
`>>>` [blue]`fib = func(n){ n ? [0] {0}: [1] {1} :: {fib(n-1)+fib(n-2)} }` +
@@ -955,9 +1003,9 @@ _param-name_ = _identifier_
=== Function context
Functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the _clone_ modifier [blue]`@` it is possibile to export local definition to the calling context. The clone modifier must be used as prefix to variable names and it is part of the name. E.g. [blue]`@x` is not the same as [blue]`x`; they are two different and un related variables.
Functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the _clone_ modifier [blue]`@` it is possibile to export local definition to the calling context. The clone modifier must be used as prefix to variable names and it is part of the name. E.g. [blue]`@x` is not the same as [blue]`x`; they are two different and unrelated variables.
Clone variables are normal local variables. The only diffence will appear when the defining function terminate, just before the destruction of its local context. At that point, all local clone variables are cloned in the calling context with the same names but the [blue]`@` symbol.
Clone variables are normal local variables. The only diffence will appear when the defining function ends, just before the destruction of its local context. At that point, all local clone variables are cloned in the calling context with the same names but the [blue]`@` symbol.
.Example
`>>>` [blue]`f = func() { @x = 3; x = 5 }` [gray]_// f() declares two *different* local variables: ``@x`` and ``x``_ +
@@ -974,24 +1022,537 @@ NOTE: The clone modifier [blue]`@` does not make a variable a reference variable
The clone modifier can also be used to declare the formal parameters of functions, because they are local variables too.
.Example
`>>>` [blue]`g = func(@p) {2+@p}`
g(@p):any{}`
`>>>` [blue]`g(9)`
11`
`>>>` [blue]`p
9
`>>>` [blue]`g = func(@p) {2+@p}` +
[green]`g(@p):any{}` +
`>>>` [blue]`g(9)` +
[green]`11` +
`>>>` [blue]`p` +
[green]`9`
====
== Iterators
#TODO: function calls operations#
== Builtins
#TODO: builtins#
Builtins are collection of function dedicated to specific domains of application. They are defined in Golang source files called _modules_ and compiled within the _Expr_ package. To make builtins available in _Expr_ contextes, it is required to activate the builtin module in which they are defined.
=== Builtin functions
There are currently several builtin modules. More builtin modules will be added in the future.
.Available builtin modules
* *base*: Base expression tools like isNil(), int(), etc.
* *fmt*: String and console formatting functions
* *import*: Functions import() and include()
* *iterator*: Iterator helper functions
* *math.arith*: Functions add() and mul()
* *os.file*: Operating system file functions
* *string*: string utilities
Builtins activation is done by using the [blue]`BUILTIN` operator. All modules except "base" must be explicitly enabled. The syntax is as follows.
.Builtin activation syntax
====
*_builtin-activation_* = [blue]`BUILTIN` (_builtin-name_ | _list-of-builtin-names_) +
_builtin-name_ = _string_ +
_list-of-builtin-names_ = **[** _string_ { "**,**" _string_ } **]**
====
The following example shows how to activate the builtin module "math.arith" and then use the function add() defined in that module.
.Example: using built functions
`>>>` [blue]`BUILTIN "math.arith"` +
[green]`1` +
`>>>` [blue]`add(5, 3, -2)` +
[green]`6`
TIP: To avoid the need to activate builtin modules one by one, it is possible to activate all builtin modules at once by using the [blue]`BUILTIN "*"` syntax.
=== Builtin modules
==== Module "base"
The "base" builtin module provides functions for type checking and type conversion. These functions are always available in _Expr_ contexts without the need to activate the "base" module.
.Checking functions
* <<_isbool,isBool()>>
* <<_isdict,isDict()>>
* <<_isfloat,isFloat()>>
* <<_isfract,isFract()>>
* <<_islist,isList()>>
* <<_isnil,isNil()>>
* <<_isrational,isRational()>>
* <<_isstring,isString()>>
.Conversion functions
* <<_bool,bool()>>
* <<_int,int()>>
* <<_dec,dec()>>
* <<_string,string()>>
* <<_fract,fract()>>
.Other functions
* <<_eval,eval()>>
* <<_var,var()>>
===== isBool()
Syntax: `isBool(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is boolean, false otherwise.
.Examples
>>> isBool(true) +
true +
>>> isBool(3==2) +
true
===== isDict()
Syntax: `isDict(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is dictionary, false otherwise.
.Examples
>>> isDict({}) +
true +
>>> isDict({1: "one", 2: "two"}) +
true +
>>> isDict(1:"one") +
Eval Error: denominator must be integer, got string (one)
===== isFloat()
Syntax: `isFloat(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is float, false otherwise.
.Examples
>>> isFloat(4.) +
true +
>>> isFloat(4) +
false +
>>> isFloat("2.1") +
false
===== isFract()
Syntax: `isFract(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is fraction, false otherwise.
.Examples
>>> isFract(4.5) +
false +
>>> isFract(4:5) +
true +
>>> isFract(4) +
**false** +
>>> isFract(1.(3)) +
true
===== isList()
Syntax: `isList(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is list, false otherwise.
.Examples
>>> isList([]) +
true +
>>> isList([1, "2"])
true
>>> isList(1,2)
Eval Error: isList(): too many params -- expected 1, got 2
===== isNil()
Syntax: `isNil(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is nil, false otherwise.
.Examples
>>> isNil(nil)
true
>>> isNil(1)
false
===== isRational()
Syntax: `isRational(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is fraction or int, false otherwise.
.Examples
>>> isRational(4.5) +
false +
>>> isRational(4:5) +
true +
>>> isRational(4) +
**true** +
>>> isRational(1.(3)) +
true
===== isString()
Syntax: `isString(<expr>) -> bool` +
Returns a boolean value , false otherwise.
.Examples
>>> isString("ciao") +
true +
>>> isString(2) +
false +
>>> isString(2+"2") +
true
===== bool()
Syntax: `bool(<expr>) -> bool` +
Returns a _boolean_ value consisent to the value of the expression.
.Examples
>>> bool(1)
true
>>> bool(0)
false
>>> bool("")
false
>>> bool([])
false
>>> bool([1])
true
>>> bool({})
false
>>> bool({1: "one"})
true
===== int()
Syntax: `int(<expr>) -> int` +
Returns an _integer_ value consistent with the value of the expression.
.Examples
>>> int(2) +
2 +
>>> int("2") +
2 +
>>> int("0x1") +
Eval Error: strconv.Atoi: parsing "0x1": invalid syntax +
>>> int(0b10) +
2 +
>>> int(0o2) +
2 +
>>> int(0x2) +
2 +
>>> int(1.8) +
1 +
>>> int(5:2) +
2 +
>>> int([]) +
Eval Error: int(): can't convert list to int+
>>> int(true) +
1 +
>>> int(false) +
0
===== dec()
Syntax: `dec(<expr>) -> float` +
Returns a _float_ value consistent with the value of the expression.
.Examples
>>> dec(2) +
2 +
>>> dec(2.1) +
2.1 +
>>> dec(2.3(1)) +
2.311111111111111 +
>>> dec("3.14") +
3.14 +
>>> dec(3:4) +
0.75 +
>>> dec(true) +
1 +
>>> dec(false) +
0
===== string()
Syntax: `string(<expr>) -> string` +
Returns a _string_ value consistent with the value of the expression.
.Examples
>>> string(2) +
"2" +
>>> string(0.8) +
"0.8" +
>>> string([1,2]) +
"[1, 2]" +
>>> string({1: "one", 2: "two"}) +
"{1: "one", 2: "two"}" +
>>> string(2:5) +
"2:5" +
>>> string(3==2) +
"false"
===== fract()
Syntax: `fract(<expr>) -> fraction` +
Returns a _fraction_ value consistent with the value of the expression.
.Examples
>>> fract(2) +
2:1 +
>>> fract(2.5) +
5:2 +
>>> fract("2.5") +
5:2 +
>>> fract(1.(3)) +
4:3 +
>>> fract([2]) +
Eval Error: fract(): can't convert list to float +
>>> fract(false) +
0:1 +
>>> fract(true) +
1:1
===== eval()
Syntax: `eval(<string-expr>) -> any` +
Computes and returns the value of the [.underline]#string# expression.
.Examples
>>> eval( "2 + fract(1.(3))" ) +
10:3
===== var()
Syntax: +
`{4sp}var(<string-expr>, <expr>) -> any` +
`{4sp}var(<string-expr>) -> any`
This function allows you to define variables whose names must include special characters. The first form of the function allows you to define a variable with a name specified by the first parameter and assign it the value of the second parameter. The second form only returns the value of the variable with the specified name.
.Examples
>>> var("$x", 3+9) +
12 +
>>> var("$x") +
12 +
>>> var("gain%", var("$x")) +
12 +
>>> var("gain%", var("$x")+1) +
13
==== Module "fmt"
===== print()
===== println()
==== Module "import"
===== _import()_
[blue]_import([grey]#<source-file>#)_ -- loads the multi-expression contained in the specified source and returns its value.
===== _importAll()_
==== Module "iterator"
===== run()
==== Module "math.arith"
Currently, the "math.arith" module provides two functions, add() and mul(), that perform addition and multiplication of an arbitrary number of parameters. More functions will be added in the future.
===== add()
Syntax: +
`{4sp}add(<num-expr1>, <num-expr2>, ...) -> any` +
`{4sp}add(<list-of-num-expr>]) -> any` +
`{4sp}add(<iterator-over-num-values>) -> any`
Returns the sum of the values of the parameters. The parameters can be of any numeric type for which the [blue]`+` operator is defined. The result type depends on the types of the parameters. If all parameters are of the same type, the result is of that type. If the parameters are of different types, the result is of the type that can represent all the parameter types without loss of information. For example, if the parameters are a mix of integers and floats, the result is a float. If the parameters are a mix of number types, the result has the type of the most general one.
.Examples
>>> add(1,2,3) +
6
>>> add(1.1,2.1,3.1) +
6.300000000000001 +
>>> add("1","2","3") +
Eval Error: add(): param nr 1 (1 in 0) has wrong type string, number expected +
>>> add(1:3, 2:3, 3:3) +
2:1 +
>>> add(1, "2") +
Eval Error: add(): param nr 2 (2 in 0) has wrong type string, number expected +
>>> add([1,2,3]) +
6 +
>>> iterator=$([1,2,3]); add(iterator) +
6 +
>>> add($([1,2,3])) +
6
===== mul()
Syntax: +
`{4sp}mul(<num-expr1>, <num-expr2>, ...) -> any` +
`{4sp}mul(<list-of-num-expr>]) -> any` +
`{4sp}mul(<iterator-over-num-values>) -> any`
Same as add() but returns the product of the values of the parameters.
==== Module "os.file"
===== fileOpen()
===== fileAppend()
===== fileCreate()
===== fileClose()
===== fileWriteText()
===== fileReadText()
===== fileReadTextAll()
==== Module "string"
===== strJoin()
===== strSub()
===== strSplit()
===== strTrim()
===== strStartsWith()
===== strEndsWith()
===== strUpper()
===== strLower()
== Iterators
Iterators are objects that can be used to traverse collections, such as lists and dictionaries. They are created by providing a _data-source_ object, the collection, in a `$(<data-source>)` expression. Once an iterator is created, it can be used to access the elements of the collection one by one.
In general, data-sources are objects that can be iterated over. They are defined as dictionaries having the key `next` whose value is a function that returns the next element of the collection and updates the state of the iterator. The _next_ function must return a special value, [blue]_nil_, when there are no more elements to iterate over.
Lists and, soon, dictionaries, are implicit data-sources. The syntax for creating an iterator is as follows.
.Iterator creation syntax
====
*_iterator_* = "**$(**" _data-source_ "**)**" +
_data-source_ = _explicit_ | _list-spec_ | _dict-spec_ | _custom-data-source_
_explicit_ = _any-expr_ { "," _any-expr_ }
_list-spec_ = _list_ _range-options_ +
_list_ = "**[**" _any-expr_ { "," _any-expr_ } "**]**" +
_range-options_ = [ "," _start-index_ [ "," _end-index_ [ "," _step_ ]]] +
_start-index_, _end-index_, _step_ = _integer-expr_
_dict-spec_ = _dict_ _dict-options_ +
_dict_ = "**{**" _key-value-pair_ { "," _key-value-pair_ } "**}**" +
_key-value-pair_ = _scalar-value_ ":" _any-expr_ +
_scalar-value_ = _string_ | _number_ | _boolean_ +
_dict-options_ = [ "," _sort-order_ [ "," _iter-mode_ ] ] +
_sort-order_ = _asc-order_ | _desc-order_ | _no-sort_ | _default-order_ +
_asc-order_ = "**asc**" | "**a**" +
_desc-order_ = "**desc**" | "**d**" +
_no-sort_ = "**none**" | "**nosort**" | "**no-sort**" | "**n**" +
_default-order_ = "**default**" | "" +
_iter-mode_ = _keys-iter_ | _values-iter_ | _items-iter_ | _default-iter_ +
_keys-iter_ = "**key**" | "**keys**" | "**k**" +
_values-iter_ = "**value**" | "**values**" | "**v**" +
_items-iter_ = "**item**" | "**items**" | "**i**" +
_default-iter_ = "**default**" | ""
_custom-data-source_ = _dict_ having the key `next` whose value is a function that returns the next element of the collection and updates the state of the iterator.
====
NOTE: Currently, _default-order_ is the same as _asc-order_. In the future, it will be possible to specify a custom sorting function to define the default order.
NOTE: Currently, _default-iter_ is the same as _keys-iter_. In the future, it will be possible to specify a custom iteration function to define the default iteration mode.
.Example: iterator over an explicit list of elements
`>>>` [blue]`it = $("A", "B", "C")` +
[green]`$(#3)` +
`>>>` [blue]`it{plusplus}` +
[green]`"A"` +
`>>>` [blue]`it{plusplus}` +
[green]`"B"` +
`>>>` [blue]`it{plusplus}` +
[green]`"C"` +
`>>>` [blue]`it{plusplus}` +
[red]`Eval Error: EOF`
.Example: iterator over a list
`>>>` [blue]`it = $(["one", "two", "three"])` +
[green]`$(#3)` +
`>>>` [blue]`it{plusplus}` +
[green]`"one"` +
`>>>` [blue]`it{plusplus}` +
[green]`"two"` +
`>>>` [blue]`it{plusplus}` +
[green]`"three"` +
`>>>` [blue]`it{plusplus}` +
[red]`Eval Error: EOF`
On a list-type iterator creation expression, it is possible to specify an index range and a step to iterate over a subset of the list.
Indexing starts at 0. If the start index is not specified, it defaults to 0. If the end index is not specified, it defaults to the index of the last element of the list. If the step is not specified, it defaults to 1.
Negative indexes are allowed. They are interpreted as offsets from the end of the list. For example, an end index of -1 means the index of the last element of the list, an end index of -2 means the index of the second to last element of the list, and so on.
Negative steps are also allowed. They are interpreted as reverse iteration. For example, a step of -1 means to iterate over the list in reverse order.
.Example: iterator over a list with index range and step
`>>>` [blue]`it = $([1, 2, 3, 4, 5], 1, 4, 2)` +
[green]`$(#5)` +
`>>>` [blue]`it{plusplus}` +
[green]`2` +
`>>>` [blue]`it{plusplus}` +
[green]`4` +
`>>>` [blue]`it{plusplus}` +
[red]`Eval Error: EOF`
.Example: iterator over a list in reverse order
`>>>` [blue]`it = $([1, 2, 3], -1, 0, -1)` +
[green]`$(#3)` +
`>>>` [blue]`it{plusplus}` +
[green]`3` +
`>>>` [blue]`it{plusplus}` +
[green]`2` +
`>>>` [blue]`it{plusplus}` +
[green]`1` +
`>>>` [blue]`it{plusplus}` +
[red]`Eval Error: EOF`
=== Operators on iterators
The above example shows the use of the [blue]`{plusplus}` operator to get the next element of an iterator. The [blue]`{plusplus}` operator is a postfix operator that can be used with iterators. It returns the next element of the collection and updates the state of the iterator. When there are no more elements to iterate over, it returns the error [red]_Eval Error: EOF_.
After the first use of the [blue]`{plusplus}` operator, the prefixed operato [blue]`{star}` operator can be used to get the current element of the collection without updating the state of the iterator. When there are no more elements to iterate over, it returns the error [red]_Eval Error: EOF_. Same error is returned if the [blue]`{star}` operator is used before the first use of the [blue]`{plusplus}` operator.
.Example: using the [blue]`{star}` operator
`>>>` [blue]`it = $(["one", "two", "three"])` +
[green]`$(#3)` +
`>>>` [blue]`{star}it` +
[red]`Eval Error: EOF` +
`>>>` [blue]`it{plusplus}` +
[green]`"one"` +
`>>>` [blue]`{star}it` +
[green]`"one"`
==== Named operators
Named operators are operators that are identified by a name instead of a symbol. They are implicitly defined and can be called using a special syntax. For example, the [blue]`{plusplus}` has the equivalent named operator [blue]`.next`.
.Available named operators
* *_.next_*: same as [blue]`{plusplus}`.
* *_.current_*: same as [blue]`{star}`.
* *_.reset_*: resets the state of the iterator to the initial state.
* *_.count_*: returns the number of elements in the iterator already visited.
* *_.index_*: returns the index of the current element in the iterator. Before the first use of the [blue]`{plusplus}` operator, it returns the error [red]_-1_.
TIP: Iterators built on custom data-sources can provide additional named operators, depending on the functionality they want to expose. For example, an iterator over a list could provide a named operator called [blue]`.reverse` that returns the next element of the collection in reverse order.
.Example: using the named operators
`>>>` [blue]`it = $(["one", "two", "three"])` +
[green]`$(#3)` +
`>>>` [blue]`it.next` +
[green]`"one"` +
`>>>` [blue]`it.current` +
[green]`"one"` +
`>>>` [blue]`it.next` +
[green]`"two"` +
`>>>` [blue]`it.reset` +
[green]`<nil>` +
`>>>` [blue]`it.current` +
[red]`Eval Error: EOF` +
`>>>` [blue]`it.next` +
[green]`"one"`
=== Iterator over custom data-sources
It is possible to create iterators over custom data-sources by defining a dictionary that has the key `next` whose value is a function that returns the next element of the collection and updates the state of the iterator. The syntax for creating an iterator over a custom data-source is as follows.
#TODO: custom data-sources#
=== [blue]_import()_
[blue]_import([grey]#<source-file>#)_ loads the multi-expression contained in the specified source and returns its value.
== Plugins
+975 -57
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -21,6 +21,8 @@ const (
CountName = "count"
FilterName = "filter"
MapName = "map"
KeyName = "key"
ValueName = "value"
)
type Iterator interface {
@@ -29,14 +31,14 @@ type Iterator interface {
Current() (item any, err error)
Index() int
Count() int
HasOperation(name string) bool
CallOperation(name string, args map[string]any) (value any, err error)
}
type ExtIterator interface {
Iterator
Reset() error
Clean() error
HasOperation(name string) bool
CallOperation(name string, args map[string]any) (value any, err error)
}
func errNoOperation(name string) error {
+2 -2
View File
@@ -149,12 +149,12 @@ func (it *ListIterator) Count() int {
return it.count
}
func (it *ListIterator) Reset() (error) {
func (it *ListIterator) Reset() error {
it.index = it.start - it.step
it.count = 0
return nil
}
func (it *ListIterator) Clean() (error) {
func (it *ListIterator) Clean() error {
return nil
}
+32 -20
View File
@@ -86,37 +86,49 @@ func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
return
}
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil {
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil && ds == nil {
return
}
err = nil
if ds != nil {
var dc *dataCursor
dcCtx := ctx.Clone()
if initFunc, exists := ds[InitName]; exists && initFunc != nil {
var args []any
var resource any
if len(opTerm.children) > 1 {
if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil {
if len(ds) > 0 {
var dc *dataCursor
dcCtx := ctx.Clone()
if initFunc, exists := ds[InitName]; exists && initFunc != nil {
var args []any
var resource any
if len(opTerm.children) > 1 {
if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil {
return
}
} else {
args = []any{}
}
actualParams := bindActualParams(initFunc, args)
initCtx := ctx.Clone()
if resource, err = initFunc.InvokeNamed(initCtx, InitName, actualParams); err != nil {
return
}
exportObjects(dcCtx, initCtx)
dc = NewDataCursor(dcCtx, ds, resource)
} else {
args = []any{}
dc = NewDataCursor(dcCtx, ds, nil)
}
actualParams := bindActualParams(initFunc, args)
initCtx := ctx.Clone()
if resource, err = initFunc.InvokeNamed(initCtx, InitName, actualParams); err != nil {
return
}
exportObjects(dcCtx, initCtx)
dc = NewDataCursor(dcCtx, ds, resource)
v = dc
} else {
dc = NewDataCursor(dcCtx, ds, nil)
if dictIt, ok := firstChildValue.(*DictType); ok {
var args []any
if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
v, err = NewDictIterator(dictIt, args)
}
} else {
err = opTerm.children[0].Errorf("the data-source must be a dictionary")
}
}
v = dc
} else if list, ok := firstChildValue.(*ListType); ok {
var args []any
if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-post-inc.go
// operator-post-inc-dec.go
package expr
// -------- post increment term
@@ -24,7 +24,27 @@ func evalPostInc(ctx ExprContext, opTerm *term) (v any, err error) {
}
if it, ok := childValue.(Iterator); ok {
var namePrefix string
v, err = it.Next()
if opTerm.children[0].symbol() == SymVariable {
namePrefix = opTerm.children[0].source()
}
ctx.UnsafeSetVar(namePrefix+"_index", it.Index())
if c, err1 := it.Current(); err1 == nil {
ctx.UnsafeSetVar(namePrefix+"_current", c)
}
if it.HasOperation(KeyName) {
if k, err1 := it.CallOperation(KeyName, nil); err1 == nil {
ctx.UnsafeSetVar(namePrefix+"_key", k)
}
}
if it.HasOperation(ValueName) {
if v1, err1 := it.CallOperation(ValueName, nil); err1 == nil {
ctx.UnsafeSetVar(namePrefix+"_value", v1)
}
}
} else if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
v = childValue
i, _ := childValue.(int64)
+69
View File
@@ -0,0 +1,69 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-pre-inc-dec.go
package expr
// -------- pre increment term
func newPreIncTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priIncDec,
evalFunc: evalPreInc,
}
}
func evalPreInc(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
i := childValue.(int64) + 1
ctx.SetVar(opTerm.children[0].source(), i)
v = i
} else {
err = opTerm.errIncompatibleType(childValue)
}
return
}
// -------- pre decrement term
func newPreDecTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priIncDec,
evalFunc: evalPreDec,
}
}
func evalPreDec(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
i := childValue.(int64) - 1
ctx.SetVar(opTerm.children[0].source(), i)
v = i
} else {
err = opTerm.errIncompatibleType(childValue)
}
return
}
// init
func init() {
registerTermConstructor(SymPreInc, newPreIncTerm)
registerTermConstructor(SymPreDec, newPreDecTerm)
}
+27 -10
View File
@@ -409,6 +409,23 @@ func listSubTree(tree *ast, listTerm *term, allowIndeces bool) (root *term, err
return
}
func changePrefix(tk *Token) {
switch tk.Sym {
case SymMinus:
tk.SetSymbol(SymChangeSign)
case SymPlus:
tk.SetSymbol(SymUnchangeSign)
case SymStar:
tk.SetSymbol(SymDereference)
case SymExclamation:
tk.SetSymbol(SymNot)
case SymDoublePlus:
tk.SetSymbol(SymPreInc)
case SymDoubleMinus:
tk.SetSymbol(SymPreDec)
}
}
func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymbols ...Symbol) (tree *ast, err error) {
var selectorTerm *term = nil
var currentTerm *term = nil
@@ -437,16 +454,16 @@ func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymb
//fmt.Println("Token:", tk)
if firstToken {
if tk.Sym == SymMinus {
tk.Sym = SymChangeSign
} else if tk.Sym == SymPlus {
tk.Sym = SymUnchangeSign
} else if tk.IsSymbol(SymStar) {
tk.SetSymbol(SymDereference)
} else if tk.IsSymbol(SymExclamation) {
err = tk.Errorf("postfix opertor %q requires an operand on its left side", tk)
break
}
changePrefix(tk)
// if tk.Sym == SymMinus {
// tk.Sym = SymChangeSign
// } else if tk.Sym == SymPlus {
// tk.Sym = SymUnchangeSign
// } else if tk.IsSymbol(SymStar) {
// tk.SetSymbol(SymDereference)
// } else if tk.IsSymbol(SymExclamation) {
// tk.SetSymbol(SymNot)
// }
firstToken = false
}
+5
View File
@@ -24,6 +24,11 @@ func NewSimpleStore() *SimpleStore {
return ctx
}
func (ss *SimpleStore) Init() {
ss.varStore = make(map[string]any)
ss.funcStore = make(map[string]ExprFunc)
}
func filterRefName(name string) bool { return name[0] != '@' }
//func filterPrivName(name string) bool { return name[0] != '_' }
+2
View File
@@ -103,6 +103,8 @@ func init() {
SymCaretEqual: {"^=", symClassOperator, posInfix}, // 66: '^='
SymPlusGreater: {"+>", symClassOperator, posInfix}, // 67: '+>'
SymLessPlus: {"<+", symClassOperator, posInfix}, // 68: '<+'
SymPreInc: {"++", symClassOperator, posPrefix}, // : '++'
SymPreDec: {"--", symClassOperator, posPrefix}, // : '--'
// SymChangeSign
// SymUnchangeSign
// SymIdentifier
+2
View File
@@ -81,6 +81,8 @@ const (
SymChangeSign
SymUnchangeSign
SymDereference
SymPreInc
SymPreDec
SymIdentifier
SymBool
SymInteger
+2 -1
View File
@@ -26,12 +26,13 @@ func TestBool(t *testing.T) {
/* 12 */ {`true or false`, true, nil},
/* 13 */ {`true or []`, true, nil},
/* 14 */ {`[] or false`, nil, errors.New(`got list as left operand type of 'OR' operator, it must be bool`)},
/* 15 */ {`!true`, false, nil},
/* 13 */ //{`true or []`, nil, errors.New(`[1:8] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "OR"`)},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 1)
// runTestSuiteSpec(t, section, inputs, 15)
runTestSuite(t, section, inputs)
}
+11 -6
View File
@@ -52,16 +52,21 @@ func TestFuncBase(t *testing.T) {
/* 38 */ {`bool(1.0)`, true, nil},
/* 39 */ {`bool("1")`, true, nil},
/* 40 */ {`bool(false)`, false, nil},
/* 41 */ {`bool([1])`, nil, `bool(): can't convert list to bool`},
/* 42 */ {`dec(false)`, float64(0), nil},
/* 43 */ {`dec(1:2)`, float64(0.5), nil},
/* 44 */ {`dec([1])`, nil, `dec(): can't convert list to float`},
/* 45 */ {`eval("a=3"); a`, int64(3), nil},
/* 41 */ {`bool([1])`, true, nil},
/* 42 */ {`bool([])`, false, nil},
/* 43 */ {`bool({})`, false, nil},
/* 44 */ {`bool({1:"one"})`, true, nil},
/* 45 */ {`dec(false)`, float64(0), nil},
/* 46 */ {`dec(1:2)`, float64(0.5), nil},
/* 47 */ {`dec([1])`, nil, `dec(): can't convert list to float`},
/* 48 */ {`eval("a=3"); a`, int64(3), nil},
/* 49 */ {`int(5:2)`, int64(2), nil},
// /* 45 */ {`string([1])`, nil, `string(): can't convert list to string`},
}
t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 45)
// runTestSuiteSpec(t, section, inputs, 49)
runTestSuite(t, section, inputs)
}
+2
View File
@@ -31,6 +31,8 @@ func TestFuncString(t *testing.T) {
/* 17 */ {`builtin "string"; strSplit("one-two-three", "-")`, newListA("one", "two", "three"), nil},
/* 18 */ {`builtin "string"; strJoin("-", [1, "two", "three"])`, nil, `strJoin(): expected string, got integer (1)`},
/* 19 */ {`builtin "string"; strJoin()`, nil, `strJoin(): too few params -- expected 1 or more, got 0`},
/* 20 */ {`builtin "string"; strUpper("StOp")`, "STOP", nil},
/* 21 */ {`builtin "string"; strLower("StOp")`, "stop", nil},
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
}
+24
View File
@@ -68,3 +68,27 @@ func TestFunctionGetFunc(t *testing.T) {
t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want)
}
}
func TestGoFunction(t *testing.T) {
section := "Funcs"
inputs := []inputType{
/* 1 */ {`myName()`, "Celestino Amoroso", nil},
/* 2 */ {`myName("Peppino")`, "Peppino", nil},
}
myName := func(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var ok bool
if result, ok = args["name"].(string); !ok {
err = ErrWrongParamType(name, "name", TypeString, args["name"])
}
return
}
ctx := NewSimpleStore()
ctx.RegisterFunc("myName", NewGolangFunctor(myName), TypeString, []ExprFuncParam{
NewFuncParamFlagDef("name", PfOptional|PfDefault, "Celestino Amoroso"),
})
runCtxTestSuite(t, ctx, section, inputs)
}
+5 -1
View File
@@ -27,8 +27,12 @@ func TestIteratorParser(t *testing.T) {
/* 16 */ {`include "test-resources/filter.expr"; it=$(ds,10); it++`, int64(2), nil},
/* 17 */ {`it=$({"next":func(){5}}); it++`, int64(5), nil},
/* 18 */ {`it=$({"next":func(){5}}); it.clean`, nil, nil},
/* 19 */ {`it=$({1:"one",2:"two",3:"three"}); it++`, int64(1), nil},
/* 20 */ {`it=$({1:"one",2:"two",3:"three"}, "default", "value"); it++`, "one", nil},
/* 21 */ {`it=$({1:"one",2:"two",3:"three"}, "desc", "key"); it++`, int64(3), nil},
/* 22 */ {`it=$({1:"one",2:"two",3:"three"}, "asc", "item"); it++`, NewList([]any{int64(1), "one"}), nil},
}
//runTestSuiteSpec(t, section, inputs, 18)
// runTestSuiteSpec(t, section, inputs, 20)
runTestSuite(t, section, inputs)
}
+2
View File
@@ -31,6 +31,8 @@ func TestOperator(t *testing.T) {
/* 18 */ {`1^2`, int64(3), nil},
/* 19 */ {`3^2`, int64(1), nil},
/* 19 */ {`a=1; a^=2`, int64(3), nil},
/* 20 */ {`a=1; ++a`, int64(2), nil},
/* 21 */ {`a=1; --a`, int64(0), nil},
}
// t.Setenv("EXPR_PATH", ".")
+9
View File
@@ -218,6 +218,15 @@ func ToGoInt(value any, description string) (i int, err error) {
return
}
func ToGoString(value any, description string) (s string, err error) {
if s, ok := value.(string); ok {
return s, nil
} else {
err = fmt.Errorf("%s expected string, got %s (%v)", description, TypeName(value), value)
}
return
}
func ForAll[T, V any](ts []T, fn func(T) V) []V {
result := make([]V, len(ts))
for i, t := range ts {