Compare commits

...

31 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
camoroso 760c1ee6da New bitwise XOR operator specified by symbol ^ (caret). Iterator dereference is now done by prefixed * (star) 2025-01-03 07:33:17 +01:00
camoroso 5ab6876ea1 term.go: new priority priDereference 2025-01-03 07:28:30 +01:00
camoroso 6268abda8c token.go: new member function Token.SetSymbol() 2025-01-03 07:27:49 +01:00
camoroso d25bd325b7 symbol-map.go: improved detection of incomplete operations 2025-01-03 06:32:55 +01:00
camoroso 01c04feea5 Doc: bitwise operators in the main operator table and special assignment operators table 2025-01-03 05:43:50 +01:00
camoroso 6fc689c46c Added a test to the context type 2025-01-03 05:40:24 +01:00
camoroso eccb0c4dc9 Added new special operators like &= and <<=.
Also made a litle change to scanner function moveOn(): now it moves on
the last char passed and only if there are more than one chars.
2024-12-29 19:26:02 +01:00
camoroso e43823740f Doc: added string splitter operator '/' 2024-12-28 19:22:26 +01:00
camoroso 526982a564 new string operator '/' 2024-12-28 19:16:45 +01:00
camoroso 252514943e parser.go: fix currentItem after parse a subexpr 2024-12-28 19:16:03 +01:00
camoroso 32686fac62 term.go: new member function errDivisionByZero() 2024-12-28 19:14:34 +01:00
camoroso 52a627c747 Edited according to preious commit 2024-12-28 09:28:12 +01:00
camoroso d91e7eb979 The list operator '<<' (append) and '>>' (prepend) have been replaced
with the new operators '<+' and '+>' respectively.
'<<' and '>>' are now used only for left and right binary shit
respectively. Further, their priority has been increased moving them to
a higher priority than that of the assignment operator.
2024-12-28 09:17:27 +01:00
39 changed files with 2840 additions and 505 deletions
+30
View File
@@ -72,6 +72,10 @@ func boolFunc(ctx ExprContext, name string, args map[string]any) (result any, er
result = v result = v
case string: case string:
result = len(v) > 0 result = len(v) > 0
case *ListType:
result = len(*v) > 0
case *DictType:
result = len(*v) > 0
default: default:
err = ErrCantConvert(name, v, "bool") 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 { if i, err = strconv.Atoi(v); err == nil {
result = int64(i) result = int64(i)
} }
case *FractionType:
result = int64(v.num / v.den)
default: default:
err = ErrCantConvert(name, v, "int") err = ErrCantConvert(name, v, "int")
} }
@@ -211,6 +217,25 @@ func evalFunc(ctx ExprContext, name string, args map[string]any) (result any, er
return 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 //// import
func ImportBuiltinsFuncs(ctx ExprContext) { func ImportBuiltinsFuncs(ctx ExprContext) {
@@ -240,6 +265,11 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("eval", NewGolangFunctor(evalFunc), TypeAny, []ExprFuncParam{ ctx.RegisterFunc("eval", NewGolangFunctor(evalFunc), TypeAny, []ExprFuncParam{
NewFuncParam(ParamSource), NewFuncParam(ParamSource),
}) })
ctx.RegisterFunc("var", NewGolangFunctor(varFunc), TypeAny, []ExprFuncParam{
NewFuncParam(ParamName),
NewFuncParamFlagDef(ParamValue, PfDefault, nil),
})
} }
func init() { func init() {
+26
View File
@@ -200,6 +200,24 @@ func splitStrFunc(ctx ExprContext, name string, args map[string]any) (result any
return 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 // --- End of function definitions
// Import above functions in the context // Import above functions in the context
@@ -236,6 +254,14 @@ func ImportStringFuncs(ctx ExprContext) {
NewFuncParam(ParamSuffix), NewFuncParam(ParamSuffix),
NewFuncParamFlag(strParamOther, PfRepeat), 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. // 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) 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 // --- Operator errors
func ErrLeftOperandMustBeVariable(leftTerm, opTerm *term) error { 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
}
+1 -1
View File
@@ -33,7 +33,7 @@ func NewDict(dictAny map[any]any) (dict *DictType) {
} }
func newDict(dictAny map[any]*term) (dict *DictType) { func newDict(dictAny map[any]*term) (dict *DictType) {
// TODO Change wi a call to NewDict() // TODO Change with a call to NewDict()
var d DictType var d DictType
if dictAny != nil { if dictAny != nil {
d = make(DictType, len(dictAny)) d = make(DictType, len(dictAny))
+650 -51
View File
@@ -20,15 +20,24 @@ Expressions calculator
:rouge-style: gruvbox :rouge-style: gruvbox
// :rouge-style: colorful // :rouge-style: colorful
//:rouge-style: monokay //:rouge-style: monokay
// Work around to manage double-column in back-tick quotes // Workaround to manage double-column in back-tick quotes
:2c: :: :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::[] 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
_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 === Concepts and terminology
@@ -42,7 +51,7 @@ Expressions are texts containing sequences of operations represented by a syntax
image::expression-diagram.png[] image::expression-diagram.png[]
==== Variables ==== 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 ==== 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_. 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. 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 === `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. 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_. <2> Fractions: _numerator_ : _denominator_.
<3> Activate multi-line output of fractions. <3> Activate multi-line output of fractions.
<4> But operator, see <<_but_operator>>. <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 == 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. _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 literal syntax
==== ====
*_fraction_* = [__sign__] (_num-den-spec_ "**:**" _float-spec_) + *_fraction_* = [__sign__] (_num-den-spec_ | _float-spec_) +
_sign_ = "**+**" | "**-**" + _sign_ = "**+**" | "**-**" +
_num-den-spec_ = _digit-seq_ "**|**" _digit-seq_ + _num-den-spec_ = _digit-seq_ "**:**" _digit-seq_ +
_float-spec_ = _dec-seq_ "**.**" [_dec-seq_] "**(**" _dec-seq_ "**)**" + _float-spec_ = _dec-seq_ "**.**" [_dec-seq_] "**(**" _repetend_ "**)**" +
_repetend_ = _dec-seq_ +
_dec-seq_ = _see-integer-literal-syntax_ + _dec-seq_ = _see-integer-literal-syntax_ +
_digit-seq_ = _see-integer-literal-syntax_ _digit-seq_ = _see-integer-literal-syntax_
==== ====
@@ -276,6 +317,8 @@ _digit-seq_ = _see-integer-literal-syntax_
`>>>` [blue]`1:(-2)` + `>>>` [blue]`1:(-2)` +
[green]`-1:2` [green]`-1:2`
`>>>` [blue]`1.(3)` // 1.33333... +
[green]`4:3`
Fractions can be used together with integers and floats in expressions. 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] [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. 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 .Example
[source,go] [source,go]
---- ----
2 > (a=1) or (a=8) > 0; a // <1> 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. TIP: `dev-expr` provides the _ctrl()_ function that allows to change this behaviour.
@@ -439,8 +483,8 @@ _non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value_} "**]**" +
| [blue]`+` | _Join_ | Joins two lists | [blue]`[1,2] + [3]` -> _[1,2,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]`-` | _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]`+>` | _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]`<+` | _Back insertion_ | Insert an item at end | [blue]`[1,2] <+ 3` -> _[1,2,3]_
| [blue]`[]` | _Item at index_ | Item at given position | [blue]`[1,2,3][1]` -> _2_ | [blue]`[]` | _Item at index_ | 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]`in` | _Item in list_ | True if item is in list | [blue]`2 in [1,2,3]` -> _true_ +
[blue]`6 in [1,2,3]` -> _false_ [blue]`6 in [1,2,3]` -> _false_
@@ -492,10 +536,10 @@ Array's items can be accessed using the index `[]` operator.
[green]`3` [green]`3`
.Examples: Element insertion .Examples: Element insertion
`>>>` [blue]`"first" >> list` + `>>>` [blue]`"first" +> list` +
[green]`["first", "one", "six", "three"]` [green]`["first", "one", "six", "three"]`
`>>>` [blue]`list << "last"` + `>>>` [blue]`list <+ "last"` +
[green]`["first", "one", "six", "three", "last"]` [green]`["first", "one", "six", "three", "last"]`
.Examples: Element in list .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"]` + `>>>` [blue]`[1,2,3] + ["one", "two", "three"]` +
[green]`[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]` [green]`[1, 3]`
@@ -570,7 +614,7 @@ _Expr_, like most programming languages, supports variables. A variable is an id
.Variable literal syntax .Variable literal syntax
==== ====
*_variable_* = _identifier_ "*=*" _any-value_ + *_variable_* = _identifier_ "*=*" _any-value_ +
_identifier_ = _alpha_ {(_alpha_)|_dec-digit_|"*_*"} + _identifier_ = _alpha_ {_alpha_|_dec-digit_|"*_*"} +
__alpha__ = "*a*"|"*b*"|..."*z*"|"*A*"|"*B*"|..."*Z*" __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]`?!` === 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. IMPORTANT: If the left variable is defined, the right expression is not evaluated at all.
@@ -735,14 +779,17 @@ NOTE: These operators have a high priority, in particular higher than the operat
The table below shows all supported operators by decreasing priorities. The table below shows all supported operators by decreasing priorities.
.Operators priorities .Operators priorities
[cols="^2,^2,^2,^5,^6"] [cols="^3,^2,^2,^5,^6"]
|=== |===
| Priority | Operators | Position | Operation | Operands and results | Priority | Operator | Position | Operation | Operands and results
.2+|*ITEM*| [blue]`[`...`]` | _Postfix_ | _List item_| _list_ `[` _integer_ `]` -> _any_ .2+|*ITEM*| [blue]`[`...`]` | _Postfix_ | _List item_| _list_ `[` _integer_ `]` -> _any_
| [blue]`[`...`]` | _Postfix_ | _Dict item_ | _dict_ `[` _any_ `]` -> _any_ | [blue]`[`...`]` | _Postfix_ | _Dict item_ | _dict_ `[` _any_ `]` -> _any_
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `++` -> _integer_ .5+|*INC/DEC*| [blue]`++` | _Postfix_ | _Iterator next item_ | _iterator_ `++` -> _any_
| [blue]`++` | _Postfix_ | _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_ .3+|*DEFAULT*| [blue]`??` | _Infix_ | _Default value_| _variable_ `??` _any-expr_ -> _any_
| [blue]`?=` | _Infix_ | _Default/assign value_| _variable_ `?=` _any-expr_ -> _any_ | [blue]`?=` | _Infix_ | _Default/assign value_| _variable_ `?=` _any-expr_ -> _any_
| [blue]`?!` | _Infix_ | _Alternate value_| _variable_ `?!` _any-expr_ -> _any_ | [blue]`?!` | _Infix_ | _Alternate value_| _variable_ `?!` _any-expr_ -> _any_
@@ -751,13 +798,17 @@ The table below shows all supported operators by decreasing priorities.
.3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`+`\|`-`) _number_ -> _number_ .3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`+`\|`-`) _number_ -> _number_
| [blue]`#` | _Prefix_ | _Lenght-of_ | `#` _collection_ -> _integer_ | [blue]`#` | _Prefix_ | _Lenght-of_ | `#` _collection_ -> _integer_
| [blue]`#` | _Prefix_ | _Size-of_ | `#` _iterator_ -> _integer_ | [blue]`#` | _Prefix_ | _Size-of_ | `#` _iterator_ -> _integer_
.2+|*BIT SHIFT*| [blue]`<<` | _Infix_ | _Left-Shift_ | _integer_ `<<` _integer_ -> _integer_
| [blue]`>>` | _Infix_ | _Right-Shift_ | _integer_ `>>` _iterator_ -> _integer_
.2+|*SELECT*| [blue]`? : ::` | _Multi-Infix_ | _Case-Selector_ | _any-expr_ `?` _case-list_ _case-expr_ `:` _case-list_ _case-expr_ ... `::` _default-expr_ -> _any_ .2+|*SELECT*| [blue]`? : ::` | _Multi-Infix_ | _Case-Selector_ | _any-expr_ `?` _case-list_ _case-expr_ `:` _case-list_ _case-expr_ ... `::` _default-expr_ -> _any_
| [blue]`? : ::` | _Multi-Infix_ | _Index-Selector_ | _int-expr_ `?` _case-expr_ `:` _case-expr_ ... `::` _default-expr_ -> _any_ | [blue]`? : ::` | _Multi-Infix_ | _Index-Selector_ | _int-expr_ `?` _case-expr_ `:` _case-expr_ ... `::` _default-expr_ -> _any_
.1+|*FRACT*| [blue]`:` | _Infix_ | _Fraction_ | _integer_ `:` _integer_ -> _fraction_ .1+|*FRACT*| [blue]`:` | _Infix_ | _Fraction_ | _integer_ `:` _integer_ -> _fraction_
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `*` _number_ -> _number_ .7+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `*` _number_ -> _number_
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ `*` _integer_ -> _string_ | [blue]`*` | _Infix_ | _String-repeat_ | _string_ `*` _integer_ -> _string_
| [blue]`/` | _Infix_ | _Division_ | _number_ `/` _number_ -> _number_ | [blue]`/` | _Infix_ | _Division_ | _number_ `/` _number_ -> _number_
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `./` _number_ -> _float_ | [blue]`./` | _Infix_ | _Float-division_ | __number__ `./` _number_ -> _float_
| [blue]`/` | _Infix_ | _Split_ | _string_ `/` _string_ -> _list_
| [blue]`/` | _Infix_ | _Split_ | _string_ `/` integer -> _list_
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `%` _integer_ -> _integer_ | [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `%` _integer_ -> _integer_
.6+|*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_ | _String-concat_ | (_string_\|_number_) `+` (_string_\|_number_) -> _string_
@@ -765,9 +816,10 @@ The table below shows all supported operators by decreasing priorities.
| [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `+` _dict_ -> _dict_ | [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `+` _dict_ -> _dict_
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `-` _number_ -> _number_ | [blue]`-` | _Infix_ | _Subtraction_ | _number_ `-` _number_ -> _number_
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `-` _list_ -> _list_ | [blue]`-` | _Infix_ | _List-difference_ | _list_ `-` _list_ -> _list_
.3+|*BINARY*| [blue]`&` | _Infix_ | _Binary And_ | _number_ `&` _number_ -> _number_ .1+|*BITWISE NOT*| [blue]`~` | _Prefix_ | _Binary Not_ | `~` _integer_ -> _integer_
| [blue]`\|` | _Infix_ | _Binary Or_ | _number_ `\|` _number_ -> _number_ .1+|*BITWISE AND*| [blue]`&` | _Infix_ | _Binary And_ | _integer_ `&` _integer_ -> _integer_
| [blue]`~` | _Prefix_ | _Binary Not_ | `~` _number_ -> _number_ .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_ .8+|*RELATION*| [blue]`<` | _Infix_ | _Less_ | _comparable_ `<` _comparable_ -> _boolean_
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `\<=` _comparable_ -> _boolean_ | [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `\<=` _comparable_ -> _boolean_
| [blue]`>` | _Infix_ | _Greater_ | _comparable_ `>` _comparable_ -> _boolean_ | [blue]`>` | _Infix_ | _Greater_ | _comparable_ `>` _comparable_ -> _boolean_
@@ -776,20 +828,54 @@ The table below shows all supported operators by decreasing priorities.
| [blue]`!=` | _Infix_ | _Not-equal_ | _comparable_ `!=` _comparable_ -> _boolean_ | [blue]`!=` | _Infix_ | _Not-equal_ | _comparable_ `!=` _comparable_ -> _boolean_
| [blue]`in` | _Infix_ | _Member-of-list_ | _any_ `in` _list_ -> _boolean_ | [blue]`in` | _Infix_ | _Member-of-list_ | _any_ `in` _list_ -> _boolean_
| [blue]`in` | _Infix_ | _Key-of-dict_ | _any_ `in` _dict_ -> _boolean_ | [blue]`in` | _Infix_ | _Key-of-dict_ | _any_ `in` _dict_ -> _boolean_
.1+|*NOT*| [blue]`not` | _Prefix_ | _Not_ | `not` _boolean_ -> _boolean_ .1+|*LOGIC NOT*| [blue]`not` | _Prefix_ | _Not_ | `not` _boolean_ -> _boolean_
.2+|*AND*| [blue]`and` | _Infix_ | _And_ | _boolean_ `and` _boolean_ -> _boolean_ .2+|*LOGIC AND*| [blue]`and` | _Infix_ | _And_ | _boolean_ `and` _boolean_ -> _boolean_
| [blue]`&&` | _Infix_ | _And_ | _boolean_ `&&` _boolean_ -> _boolean_ | [blue]`&&` | _Infix_ | _And_ | _boolean_ `&&` _boolean_ -> _boolean_
.2+|*OR*| [blue]`or` | _Infix_ | _Or_ | _boolean_ `or` _boolean_ -> _boolean_ .2+|*LOGIC OR*| [blue]`or` | _Infix_ | _Or_ | _boolean_ `or` _boolean_ -> _boolean_
| [blue]`\|\|` | _Infix_ | _Or_ | _boolean_ `\|\|` _boolean_ -> _boolean_ | [blue]`\|\|` | _Infix_ | _Or_ | _boolean_ `\|\|` _boolean_ -> _boolean_
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _Assignment_ | _identifier_ `=` _any_ -> _any_ .2+|*INSERT*| [blue]`+>` | _Infix_ | _Prepend_ | _any_ `+>` _list_ -> _list_
| [blue]`>>` | _Infix_ | _Front-insert_ | _any_ `>>` _list_ -> _list_ | [blue]`<+` | _Infix_ | _Append_ | _list_ `<+` _any_ -> _list_
| [blue]`<<` | _Infix_ | _Back-insert_ | _list_ `<<` _any_ -> _list_ .2+|*ASSIGN*| [blue]`=` | _Infix_ | _Assignment_ | _identifier_ `=` _any_ -> _any_
4+| _See also the table of special allocation operators below_
.1+|*BUT*| [blue]`but` | _Infix_ | _But_ | _any_ `but` _any_ -> _any_ .1+|*BUT*| [blue]`but` | _Infix_ | _But_ | _any_ `but` _any_ -> _any_
.1+|*RANGE*| [blue]`:` | _Infix_ | _Index-range_ | _integer_ `:` _integer_ -> _integer-pair_ .1+|*RANGE*| [blue]`:` | _Infix_ | _Index-range_ | _integer_ `:` _integer_ -> _integer-pair_
|=== |===
//^1^ Experimental //^1^ Experimental
.Special assignment perators
[cols="^2,^2,^4,^6"]
|===
| Priority | Operator | Operation |Equivalent operation
.9+|*ASSIGN*| [blue]`+=` | _Sum & Assign_ | _var_ `\+=` _expr_ +
short for +
_var_ `=` _value-of-var_ `+` _expr_
| [blue]`-=` | _Subtract & Assign_ | _var_ `-=` _expr_ +
short for +
_var_ `=` _value-of-var_ `-` _expr_
| [blue]`*=` | _Multiply & Assign_ | _var_ `\*=` _expr_ +
short for +
_var_ `=` _value-of-var_ `*` _expr_
| [blue]`/=` | _Divide & Assign_ | _var_ `/=` _expr_ +
short for +
_var_ `=` _value-of-var_ `/` _expr_
| [blue]`%=` | _Remainder & Assign_ | _var_ `%=` _expr_ +
short for +
_var_ `=` _value-of-var_ `%` _expr_
| [blue]`&=` | _Bitwise and & Assign_ | _var_ `&=` _expr_ +
short for +
_var_ `=` _value-of-var_ `&` _expr_
| [blue]`\|=` | _Bitwise or & Assign_ | _var_ `\|=` _expr_ +
short for +
_var_ `=` _value-of-var_ `\|` _expr_
| [blue]`<\<=` | _Left shift & Assign_ | _var_ `<\<=` _expr_ +
short for +
_var_ `=` _value-of-var_ `<<` _expr_
| [blue]`>>=` | _Right shift & Assign_ | _var_ `>>=` _expr_ +
short for +
_var_ `=` _value-of-var_ `>>` _expr_
|===
== Functions == Functions
Functions in _Expr_ are very similar to functions available in many programming languages. Currently, _Expr_ supports two types of function, _expr-functions_ and _go-functions_. Functions in _Expr_ are very similar to functions available in many programming languages. Currently, _Expr_ supports two types of function, _expr-functions_ and _go-functions_.
@@ -799,12 +885,12 @@ Functions in _Expr_ are very similar to functions available in many programming
=== _Expr_ function definition === _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 .Expr's function definition syntax
==== ====
*_function-definition_* = _identifier_ "**=**" "**func(**" [_formal-param-list_] "**)**" "**{**" _multi-expression_ "**}**" + *_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_ } + _required-param-list_ = _identifier_ { "**,**" _identifier_ } +
_optional-param-list_ = _optional-parm_ { "**,**" _optional-param_ } + _optional-param-list_ = _optional-parm_ { "**,**" _optional-param_ } +
_optional-param_ = _param-name_ "**=**" _any-expr_ + _optional-param_ = _param-name_ "**=**" _any-expr_ +
@@ -816,7 +902,7 @@ _param-name_ = _identifier_
`>>>` [blue]`sum = func(a, b){ a + b }` + `>>>` [blue]`sum = func(a, b){ a + b }` +
[green]`sum(a, b):any{}` [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_ + `>>>` [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)} }` + `>>>` [blue]`fib = func(n){ n ? [0] {0}: [1] {1} :: {fib(n-1)+fib(n-2)} }` +
@@ -840,7 +926,7 @@ _param-name_ = _identifier_
=== _Golang_ function definition === _Golang_ function definition
Description of how to define Golan functions and how to bind them to _Expr_ are topics treated in another document that I'll write, one day, maybe. Description of how to define Golang functions and how to bind them to _Expr_ are topics covered in another document that I'll write, one day, maybe.
=== Function calls === Function calls
To call a function, either Expr or Golang type, it is necessary to specify its name and, at least, its required parameters. To call a function, either Expr or Golang type, it is necessary to specify its name and, at least, its required parameters.
@@ -917,9 +1003,9 @@ _param-name_ = _identifier_
=== Function context === 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 .Example
`>>>` [blue]`f = func() { @x = 3; x = 5 }` [gray]_// f() declares two *different* local variables: ``@x`` and ``x``_ + `>>>` [blue]`f = func() { @x = 3; x = 5 }` [gray]_// f() declares two *different* local variables: ``@x`` and ``x``_ +
@@ -936,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. The clone modifier can also be used to declare the formal parameters of functions, because they are local variables too.
.Example .Example
`>>>` [blue]`g = func(@p) {2+@p}` `>>>` [blue]`g = func(@p) {2+@p}` +
g(@p):any{}` [green]`g(@p):any{}` +
`>>>` [blue]`g(9)` `>>>` [blue]`g(9)` +
11` [green]`11` +
`>>>` [blue]`p `>>>` [blue]`p` +
9 [green]`9`
==== ====
== Iterators
#TODO: function calls operations#
== Builtins == 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 == Plugins
+1124 -92
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -21,6 +21,8 @@ const (
CountName = "count" CountName = "count"
FilterName = "filter" FilterName = "filter"
MapName = "map" MapName = "map"
KeyName = "key"
ValueName = "value"
) )
type Iterator interface { type Iterator interface {
@@ -29,14 +31,14 @@ type Iterator interface {
Current() (item any, err error) Current() (item any, err error)
Index() int Index() int
Count() int Count() int
HasOperation(name string) bool
CallOperation(name string, args map[string]any) (value any, err error)
} }
type ExtIterator interface { type ExtIterator interface {
Iterator Iterator
Reset() error Reset() error
Clean() error Clean() error
HasOperation(name string) bool
CallOperation(name string, args map[string]any) (value any, err error)
} }
func errNoOperation(name string) error { func errNoOperation(name string) error {
+2 -2
View File
@@ -149,12 +149,12 @@ func (it *ListIterator) Count() int {
return it.count return it.count
} }
func (it *ListIterator) Reset() (error) { func (it *ListIterator) Reset() error {
it.index = it.start - it.step it.index = it.start - it.step
it.count = 0 it.count = 0
return nil return nil
} }
func (it *ListIterator) Clean() (error) { func (it *ListIterator) Clean() error {
return nil return nil
} }
+13 -1
View File
@@ -86,11 +86,13 @@ func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
return return
} }
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil { if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil && ds == nil {
return return
} }
err = nil
if ds != nil { if ds != nil {
if len(ds) > 0 {
var dc *dataCursor var dc *dataCursor
dcCtx := ctx.Clone() dcCtx := ctx.Clone()
if initFunc, exists := ds[InitName]; exists && initFunc != nil { if initFunc, exists := ds[InitName]; exists && initFunc != nil {
@@ -117,6 +119,16 @@ func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
} }
v = dc v = dc
} else {
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")
}
}
} else if list, ok := firstChildValue.(*ListType); ok { } else if list, ok := firstChildValue.(*ListType); ok {
var args []any var args []any
if args, err = evalSibling(ctx, opTerm.children, nil); err == nil { if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
+16
View File
@@ -183,6 +183,16 @@ func evalOpAssign(ctx ExprContext, opTerm *term) (v any, err error) {
v, err = divValues(opTerm, leftValue, rightValue) v, err = divValues(opTerm, leftValue, rightValue)
case SymPercEqual: case SymPercEqual:
v, err = remainderValues(opTerm, leftValue, rightValue) v, err = remainderValues(opTerm, leftValue, rightValue)
case SymAmpersandEqual:
v, err = bitwiseAnd(opTerm, leftValue, rightValue)
case SymVertBarEqual:
v, err = bitwiseOr(opTerm, leftValue, rightValue)
case SymCaretEqual:
v, err = bitwiseXor(opTerm, leftValue, rightValue)
case SymDoubleLessEqual:
v, err = bitLeftShift(opTerm, leftValue, rightValue)
case SymDoubleGreaterEqual:
v, err = bitRightShift(opTerm, leftValue, rightValue)
default: default:
err = opTerm.Errorf("unsupported assign operator %q", opTerm.source()) err = opTerm.Errorf("unsupported assign operator %q", opTerm.source())
} }
@@ -201,4 +211,10 @@ func init() {
registerTermConstructor(SymMinusEqual, newOpAssignTerm) registerTermConstructor(SymMinusEqual, newOpAssignTerm)
registerTermConstructor(SymStarEqual, newOpAssignTerm) registerTermConstructor(SymStarEqual, newOpAssignTerm)
registerTermConstructor(SymSlashEqual, newOpAssignTerm) registerTermConstructor(SymSlashEqual, newOpAssignTerm)
registerTermConstructor(SymPercEqual, newOpAssignTerm)
registerTermConstructor(SymDoubleLessEqual, newOpAssignTerm)
registerTermConstructor(SymDoubleGreaterEqual, newOpAssignTerm)
registerTermConstructor(SymAmpersandEqual, newOpAssignTerm)
registerTermConstructor(SymVertBarEqual, newOpAssignTerm)
registerTermConstructor(SymCaretEqual, newOpAssignTerm)
} }
-104
View File
@@ -1,104 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-binary.go
package expr
//-------- NOT term
func newBinNotTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priBinary,
evalFunc: evalBinaryNot,
}
}
func evalBinaryNot(ctx ExprContext, opTerm *term) (v any, err error) {
var value any
if value, err = opTerm.evalPrefix(ctx); err != nil {
return
}
if IsInteger(value) {
i, _ := value.(int64)
v = ^i
} else {
err = opTerm.errIncompatibleType(value)
}
return
}
//-------- Binary AND term
func newBinAndTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBinary,
evalFunc: evalBinaryAnd,
}
}
func evalBinaryAnd(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
var leftInt, rightInt int64
var lok, rok bool
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
leftInt, lok = leftValue.(int64)
rightInt, rok = rightValue.(int64)
if lok && rok {
v = leftInt & rightInt
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
//-------- Binary OR term
func newBinOrTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBinary,
evalFunc: evalBinaryOr,
}
}
func evalBinaryOr(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
var leftInt, rightInt int64
var lok, rok bool
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
leftInt, lok = leftValue.(int64)
rightInt, rok = rightValue.(int64)
if lok && rok {
v = leftInt | rightInt
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymTilde, newBinNotTerm)
registerTermConstructor(SymAmpersand, newBinAndTerm)
registerTermConstructor(SymVertBar, newBinOrTerm)
}
+154
View File
@@ -0,0 +1,154 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-bitwise.go
package expr
//-------- Bitwise NOT term
func newBitwiseNotTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priBitwiseNot,
evalFunc: evalBitwiseNot,
}
}
func evalBitwiseNot(ctx ExprContext, opTerm *term) (v any, err error) {
var value any
if value, err = opTerm.evalPrefix(ctx); err != nil {
return
}
if IsInteger(value) {
i, _ := value.(int64)
v = ^i
} else {
err = opTerm.errIncompatibleType(value)
}
return
}
//-------- Bitwise AND term
func newBitwiseAndTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBitwiseAnd,
evalFunc: evalBitwiseAnd,
}
}
func bitwiseAnd(opTerm *term, leftValue, rightValue any) (v any, err error) {
var leftInt, rightInt int64
var lok, rok bool
leftInt, lok = leftValue.(int64)
rightInt, rok = rightValue.(int64)
if lok && rok {
v = leftInt & rightInt
} else {
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalBitwiseAnd(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
v, err = bitwiseAnd(opTerm, leftValue, rightValue)
return
}
//-------- Bitwise OR term
func newBitwiseOrTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBitwiseOr,
evalFunc: evalBitwiseOr,
}
}
func bitwiseOr(opTerm *term, leftValue, rightValue any) (v any, err error) {
var leftInt, rightInt int64
var lok, rok bool
leftInt, lok = leftValue.(int64)
rightInt, rok = rightValue.(int64)
if lok && rok {
v = leftInt | rightInt
} else {
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalBitwiseOr(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
v, err = bitwiseOr(opTerm, leftValue, rightValue)
return
}
//-------- Bitwise XOR term
func newBitwiseXorTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBitwiseOr,
evalFunc: evalBitwiseXor,
}
}
func bitwiseXor(opTerm *term, leftValue, rightValue any) (v any, err error) {
var leftInt, rightInt int64
var lok, rok bool
leftInt, lok = leftValue.(int64)
rightInt, rok = rightValue.(int64)
if lok && rok {
v = leftInt ^ rightInt
} else {
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalBitwiseXor(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
v, err = bitwiseXor(opTerm, leftValue, rightValue)
return
}
// init
func init() {
registerTermConstructor(SymTilde, newBitwiseNotTerm)
registerTermConstructor(SymAmpersand, newBitwiseAndTerm)
registerTermConstructor(SymVertBar, newBitwiseOrTerm)
registerTermConstructor(SymCaret, newBitwiseXorTerm)
}
+1 -2
View File
@@ -7,7 +7,6 @@ package expr
//https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html //https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html
import ( import (
"errors"
"fmt" "fmt"
) )
@@ -41,7 +40,7 @@ func evalFraction(ctx ExprContext, opTerm *term) (v any, err error) {
return return
} }
if den == 0 { if den == 0 {
err = errors.New("division by zero") err = opTerm.errDivisionByZero()
return return
} }
+8 -16
View File
@@ -4,15 +4,15 @@
// operator-insert.go // operator-insert.go
package expr package expr
//-------- insert term //-------- prepend term
func newInsertTerm(tk *Token) (inst *term) { func newPrependTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priAssign, priority: priInsert,
evalFunc: evalInsert, evalFunc: evalPrepend,
} }
} }
@@ -21,12 +21,12 @@ func newAppendTerm(tk *Token) (inst *term) {
tk: *tk, tk: *tk,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priAssign, priority: priInsert,
evalFunc: evalAppend, evalFunc: evalAppend,
} }
} }
func evalInsert(ctx ExprContext, opTerm *term) (v any, err error) { func evalPrepend(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil { if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
@@ -40,10 +40,6 @@ func evalInsert(ctx ExprContext, opTerm *term) (v any, err error) {
if opTerm.children[1].symbol() == SymVariable { if opTerm.children[1].symbol() == SymVariable {
ctx.UnsafeSetVar(opTerm.children[1].source(), v) ctx.UnsafeSetVar(opTerm.children[1].source(), v)
} }
} else if IsInteger(leftValue) && IsInteger(rightValue) {
leftInt := leftValue.(int64)
rightInt := rightValue.(int64)
v = leftInt >> rightInt
} else { } else {
err = opTerm.errIncompatibleTypes(leftValue, rightValue) err = opTerm.errIncompatibleTypes(leftValue, rightValue)
} }
@@ -64,10 +60,6 @@ func evalAppend(ctx ExprContext, opTerm *term) (v any, err error) {
if opTerm.children[0].symbol() == SymVariable { if opTerm.children[0].symbol() == SymVariable {
ctx.UnsafeSetVar(opTerm.children[0].source(), v) ctx.UnsafeSetVar(opTerm.children[0].source(), v)
} }
} else if IsInteger(leftValue) && IsInteger(rightValue) {
leftInt := leftValue.(int64)
rightInt := rightValue.(int64)
v = leftInt << rightInt
} else { } else {
err = opTerm.errIncompatibleTypes(leftValue, rightValue) err = opTerm.errIncompatibleTypes(leftValue, rightValue)
} }
@@ -94,6 +86,6 @@ func evalAppend(ctx ExprContext, opTerm *term) (v any, err error) {
// init // init
func init() { func init() {
registerTermConstructor(SymInsert, newInsertTerm) registerTermConstructor(SymPlusGreater, newPrependTerm)
registerTermConstructor(SymAppend, newAppendTerm) registerTermConstructor(SymLessPlus, newAppendTerm)
} }
+3 -3
View File
@@ -11,7 +11,7 @@ func newIterValueTerm(tk *Token) (inst *term) {
tk: *tk, tk: *tk,
children: make([]*term, 0, 1), children: make([]*term, 0, 1),
position: posPrefix, position: posPrefix,
priority: priIterValue, priority: priDereference,
evalFunc: evalIterValue, evalFunc: evalIterValue,
} }
} }
@@ -33,6 +33,6 @@ func evalIterValue(ctx ExprContext, opTerm *term) (v any, err error) {
// init // init
func init() { func init() {
// registerTermConstructor(SymOpenClosedRound, newIterValueTerm) // registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
registerTermConstructor(SymCaret, newIterValueTerm) registerTermConstructor(SymDereference, newIterValueTerm)
} }
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-post-inc.go // operator-post-inc-dec.go
package expr package expr
// -------- post increment term // -------- post increment term
@@ -24,7 +24,27 @@ func evalPostInc(ctx ExprContext, opTerm *term) (v any, err error) {
} }
if it, ok := childValue.(Iterator); ok { if it, ok := childValue.(Iterator); ok {
var namePrefix string
v, err = it.Next() 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 { } else if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
v = childValue v = childValue
i, _ := childValue.(int64) 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)
}
+29 -5
View File
@@ -5,7 +5,6 @@
package expr package expr
import ( import (
"errors"
"strings" "strings"
) )
@@ -69,7 +68,7 @@ func divValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
if IsFloat(leftValue) || IsFloat(rightValue) { if IsFloat(leftValue) || IsFloat(rightValue) {
d := numAsFloat(rightValue) d := numAsFloat(rightValue)
if d == 0.0 { if d == 0.0 {
err = errors.New("division by zero") err = opTerm.errDivisionByZero()
} else { } else {
v = numAsFloat(leftValue) / d v = numAsFloat(leftValue) / d
} }
@@ -78,11 +77,36 @@ func divValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
} else { } else {
leftInt, _ := leftValue.(int64) leftInt, _ := leftValue.(int64)
if rightInt, _ := rightValue.(int64); rightInt == 0 { if rightInt, _ := rightValue.(int64); rightInt == 0 {
err = errors.New("division by zero") err = opTerm.errDivisionByZero()
} else { } else {
v = leftInt / rightInt v = leftInt / rightInt
} }
} }
} else if IsString(leftValue) && IsString(rightValue) {
source := leftValue.(string)
sep := rightValue.(string)
v = ListFromStrings(strings.Split(source, sep))
} else if IsString(leftValue) && IsInteger(rightValue) {
source := leftValue.(string)
partSize := int(rightValue.(int64))
if partSize == 0 {
err = opTerm.errDivisionByZero()
} else {
partCount := len(source) / partSize
remainder := len(source) % partSize
listSize := partCount
if remainder > 0 {
listSize++
}
parts := make([]any, 0, listSize)
for i := 0; i < partCount; i++ {
parts = append(parts, source[i*partSize:(i+1)*partSize])
}
if remainder > 0 {
parts = append(parts, source[len(source)-remainder:])
}
v = newList(parts)
}
} else { } else {
err = opTerm.errIncompatibleTypes(leftValue, rightValue) err = opTerm.errIncompatibleTypes(leftValue, rightValue)
} }
@@ -121,7 +145,7 @@ func evalDivideAsFloat(ctx ExprContext, floatDivTerm *term) (v any, err error) {
if isNumOrFract(leftValue) && isNumOrFract(rightValue) { if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
d := numAsFloat(rightValue) d := numAsFloat(rightValue)
if d == 0.0 { if d == 0.0 {
err = errors.New("division by zero") err = floatDivTerm.errDivisionByZero()
} else { } else {
v = numAsFloat(leftValue) / d v = numAsFloat(leftValue) / d
} }
@@ -146,7 +170,7 @@ func remainderValues(opTerm *term, leftValue, rightValue any) (v any, err error)
if IsInteger(leftValue) && IsInteger(rightValue) { if IsInteger(leftValue) && IsInteger(rightValue) {
rightInt, _ := rightValue.(int64) rightInt, _ := rightValue.(int64)
if rightInt == 0 { if rightInt == 0 {
err = errors.New("division by zero") err = opTerm.errDivisionByZero()
} else { } else {
leftInt, _ := leftValue.(int64) leftInt, _ := leftValue.(int64)
v = leftInt % rightInt v = leftInt % rightInt
+77
View File
@@ -0,0 +1,77 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-shift.go
package expr
//-------- bit right shift term
func newRightShiftTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBinShift,
evalFunc: evalRightShift,
}
}
func bitRightShift(opTerm *term, leftValue, rightValue any) (v any, err error) {
if IsInteger(leftValue) && IsInteger(rightValue) {
leftInt := leftValue.(int64)
rightInt := rightValue.(int64)
v = leftInt >> rightInt
} else {
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalRightShift(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
v, err = bitRightShift(opTerm, leftValue, rightValue)
return
}
func newLeftShiftTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBinShift,
evalFunc: evalLeftShift,
}
}
func bitLeftShift(opTerm *term, leftValue, rightValue any) (v any, err error) {
if IsInteger(leftValue) && IsInteger(rightValue) {
leftInt := leftValue.(int64)
rightInt := rightValue.(int64)
v = leftInt << rightInt
} else {
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalLeftShift(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
v, err = bitLeftShift(opTerm, leftValue, rightValue)
return
}
// init
func init() {
registerTermConstructor(SymDoubleGreater, newRightShiftTerm)
registerTermConstructor(SymDoubleLess, newLeftShiftTerm)
}
+35 -16
View File
@@ -409,6 +409,23 @@ func listSubTree(tree *ast, listTerm *term, allowIndeces bool) (root *term, err
return 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) { func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymbols ...Symbol) (tree *ast, err error) {
var selectorTerm *term = nil var selectorTerm *term = nil
var currentTerm *term = nil var currentTerm *term = nil
@@ -437,14 +454,16 @@ func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymb
//fmt.Println("Token:", tk) //fmt.Println("Token:", tk)
if firstToken { if firstToken {
if tk.Sym == SymMinus { changePrefix(tk)
tk.Sym = SymChangeSign // if tk.Sym == SymMinus {
} else if tk.Sym == SymPlus { // tk.Sym = SymChangeSign
tk.Sym = SymUnchangeSign // } else if tk.Sym == SymPlus {
} else if tk.IsSymbol(SymExclamation) { // tk.Sym = SymUnchangeSign
err = tk.Errorf("postfix opertor %q requires an operand on its left", tk) // } else if tk.IsSymbol(SymStar) {
break // tk.SetSymbol(SymDereference)
} // } else if tk.IsSymbol(SymExclamation) {
// tk.SetSymbol(SymNot)
// }
firstToken = false firstToken = false
} }
@@ -452,9 +471,12 @@ func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymb
case SymOpenRound: case SymOpenRound:
var subTree *ast var subTree *ast
if subTree, err = parser.parseGeneral(scanner, ctx, SymClosedRound); err == nil { if subTree, err = parser.parseGeneral(scanner, ctx, SymClosedRound); err == nil {
subTree.root.priority = priValue exprTerm := newExprTerm(subTree.root)
err = tree.addTerm(newExprTerm(subTree.root)) err = tree.addTerm(exprTerm)
currentTerm = subTree.root currentTerm = exprTerm
// subTree.root.priority = priValue
// err = tree.addTerm(newExprTerm(subTree.root))
// currentTerm = subTree.root
} }
case SymFuncCall: case SymFuncCall:
var funcCallTerm *term var funcCallTerm *term
@@ -478,7 +500,7 @@ func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymb
currentTerm = mapTerm currentTerm = mapTerm
} }
} }
case SymEqual, SymPlusEqual, SymMinusEqual, SymStarEqual, SymSlashEqual, SymPercEqual: case SymEqual, SymPlusEqual, SymMinusEqual, SymStarEqual, SymSlashEqual, SymPercEqual, SymAmpersandEqual, SymVertBarEqual, SymDoubleLessEqual, SymDoubleGreaterEqual, SymCaretEqual:
currentTerm, err = tree.addToken(tk) currentTerm, err = tree.addToken(tk)
firstToken = true firstToken = true
case SymFuncDef: case SymFuncDef:
@@ -515,15 +537,12 @@ func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymb
} }
} }
} else { } else {
// if hasFlag(ctx, allowIndex) {
// tk.Sym = SymRange
// }
currentTerm, err = tree.addToken(tk) currentTerm, err = tree.addToken(tk)
}
if tk.IsOneOfA(SymColon, SymRange) { if tk.IsOneOfA(SymColon, SymRange) {
// Colon outside a selector term acts like a separator // Colon outside a selector term acts like a separator
firstToken = true firstToken = true
} }
}
default: default:
currentTerm, err = tree.addToken(tk) currentTerm, err = tree.addToken(tk)
} }
+40 -3
View File
@@ -124,6 +124,8 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
tk = scanner.moveOn(SymDoublePlus, ch, next) tk = scanner.moveOn(SymDoublePlus, ch, next)
} else if next == '=' { } else if next == '=' {
tk = scanner.moveOn(SymPlusEqual, ch, next) tk = scanner.moveOn(SymPlusEqual, ch, next)
} else if next == '>' {
tk = scanner.moveOn(SymPlusGreater, ch, next)
} else { } else {
tk = scanner.makeToken(SymPlus, ch) tk = scanner.makeToken(SymPlus, ch)
} }
@@ -167,13 +169,19 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
case '|': case '|':
if next, _ := scanner.peek(); next == '|' { if next, _ := scanner.peek(); next == '|' {
tk = scanner.moveOn(SymDoubleVertBar, ch, next) tk = scanner.moveOn(SymDoubleVertBar, ch, next)
} else if next, _ = scanner.peek(); next == '=' {
tk = scanner.moveOn(SymVertBarEqual, ch, next)
} else { } else {
tk = scanner.makeToken(SymVertBar, ch) tk = scanner.makeToken(SymVertBar, ch)
} }
case ',': case ',':
tk = scanner.makeToken(SymComma, ch) tk = scanner.makeToken(SymComma, ch)
case '^': case '^':
if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymCaretEqual, ch, next)
} else {
tk = scanner.makeToken(SymCaret, ch) tk = scanner.makeToken(SymCaret, ch)
}
case ':': case ':':
if next, _ := scanner.peek(); next == ':' { if next, _ := scanner.peek(); next == ':' {
tk = scanner.moveOn(SymDoubleColon, ch, next) tk = scanner.moveOn(SymDoubleColon, ch, next)
@@ -232,11 +240,17 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
case '&': case '&':
if next, _ := scanner.peek(); next == '&' { if next, _ := scanner.peek(); next == '&' {
tk = scanner.moveOn(SymDoubleAmpersand, ch, next) tk = scanner.moveOn(SymDoubleAmpersand, ch, next)
} else if next, _ = scanner.peek(); next == '=' {
tk = scanner.moveOn(SymAmpersandEqual, ch, next)
} else { } else {
tk = scanner.makeToken(SymAmpersand, ch) tk = scanner.makeToken(SymAmpersand, ch)
} }
case '%': case '%':
if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymPercEqual, ch, next)
} else {
tk = scanner.makeToken(SymPercent, ch) tk = scanner.makeToken(SymPercent, ch)
}
case '#': case '#':
tk = scanner.makeToken(SymHash, ch) tk = scanner.makeToken(SymHash, ch)
case '@': case '@':
@@ -265,9 +279,18 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
if next, _ := scanner.peek(); next == '=' { if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymLessOrEqual, ch, next) tk = scanner.moveOn(SymLessOrEqual, ch, next)
} else if next == '<' { } else if next == '<' {
tk = scanner.moveOn(SymAppend, ch, next) scanner.readChar()
next2, _ := scanner.readChar()
scanner.unreadChar()
if next2 == '=' {
tk = scanner.moveOn(SymDoubleLessEqual, ch, next, next2)
} else {
tk = scanner.accept(SymDoubleLess, ch, next)
}
} else if next == '>' { } else if next == '>' {
tk = scanner.moveOn(SymLessGreater, ch, next) tk = scanner.moveOn(SymLessGreater, ch, next)
} else if next == '+' {
tk = scanner.moveOn(SymLessPlus, ch, next)
} else { } else {
tk = scanner.makeToken(SymLess, ch) tk = scanner.makeToken(SymLess, ch)
} }
@@ -275,7 +298,14 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
if next, _ := scanner.peek(); next == '=' { if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymGreaterOrEqual, ch, next) tk = scanner.moveOn(SymGreaterOrEqual, ch, next)
} else if next == '>' { } else if next == '>' {
tk = scanner.moveOn(SymInsert, ch, next) scanner.readChar()
next2, _ := scanner.readChar()
scanner.unreadChar()
if next2 == '=' {
tk = scanner.moveOn(SymDoubleGreaterEqual, ch, next, next2)
} else {
tk = scanner.accept(SymDoubleGreater, ch, next)
}
} else { } else {
tk = scanner.makeToken(SymGreater, ch) tk = scanner.makeToken(SymGreater, ch)
} }
@@ -630,9 +660,16 @@ func (scanner *scanner) translate(sym Symbol) Symbol {
func (scanner *scanner) moveOn(sym Symbol, chars ...byte) (tk *Token) { func (scanner *scanner) moveOn(sym Symbol, chars ...byte) (tk *Token) {
tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), string(chars)) tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), string(chars))
for i := 1; i < len(chars); i++ { // for i := 1; i < len(chars); i++ {
if len(chars) > 1 {
scanner.readChar() scanner.readChar()
} }
// }
return
}
func (scanner *scanner) accept(sym Symbol, chars ...byte) (tk *Token) {
tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), string(chars))
return return
} }
+5
View File
@@ -24,6 +24,11 @@ func NewSimpleStore() *SimpleStore {
return ctx 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 filterRefName(name string) bool { return name[0] != '@' }
//func filterPrivName(name string) bool { return name[0] != '_' } //func filterPrivName(name string) bool { return name[0] != '_' }
+96 -79
View File
@@ -14,7 +14,7 @@ type symbolClass int16
const ( const (
symClassOperator symbolClass = iota symClassOperator symbolClass = iota
symClassPostOp symClassCommand
symClassIdentifier symClassIdentifier
symClassDelimiter symClassDelimiter
symClassParenthesis symClassParenthesis
@@ -26,75 +26,85 @@ const (
type symbolSpec struct { type symbolSpec struct {
repr string repr string
kind symbolClass kind symbolClass
opType termPosition
} }
func init() { func init() {
symbolMap = map[Symbol]symbolSpec{ symbolMap = map[Symbol]symbolSpec{
SymUnknown: {"<unknown>", symClassOther}, // -1: Unknown symbol SymUnknown: {"<unknown>", symClassOther, posLeaf}, // -1: Unknown symbol
SymNone: {"<null>", symClassOther}, // 0: Null value for variable of type symbol SymNone: {"<null>", symClassOther, posLeaf}, // 0: Null value for variable of type symbol
SymError: {"<error>", symClassOther}, // 1: Error reading from stream SymError: {"<error>", symClassOther, posLeaf}, // 1: Error reading from stream
SymEos: {"<eos>", symClassOther}, // 2: End of stream SymEos: {"<eos>", symClassOther, posLeaf}, // 2: End of stream
SymMinus: {"-", symClassOperator}, // 3: '-' SymMinus: {"-", symClassOperator, posInfix}, // 3: '-'
SymMinusEqual: {"-=", symClassOperator}, // 4: '-=' SymMinusEqual: {"-=", symClassOperator, posInfix}, // 4: '-='
SymDoubleMinus: {"--", symClassOperator}, // 5: '--' SymDoubleMinus: {"--", symClassOperator, posPostfix}, // 5: '--'
SymPlus: {"+", symClassOperator}, // 6: '+' SymPlus: {"+", symClassOperator, posInfix}, // 6: '+'
SymPlusEqual: {"+=", symClassOperator}, // 7: '+=' SymPlusEqual: {"+=", symClassOperator, posInfix}, // 7: '+='
SymDoublePlus: {"++", symClassOperator}, // 8: '++' SymDoublePlus: {"++", symClassOperator, posPostfix}, // 8: '++'
SymStar: {"*", symClassOperator}, // 9: '*' SymStar: {"*", symClassOperator, posInfix}, // 9: '*'
SymDoubleStar: {"**", symClassOperator}, // 10: '**' SymDoubleStar: {"**", symClassOperator, posInfix}, // 10: '**'
SymSlash: {"/", symClassOperator}, // 11: '/' SymSlash: {"/", symClassOperator, posInfix}, // 11: '/'
SymBackSlash: {"\\", symClassOperator}, // 12: '\' SymBackSlash: {"\\", symClassOperator, posLeaf}, // 12: '\'
SymVertBar: {"|", symClassOperator}, // 13: '|' SymVertBar: {"|", symClassOperator, posInfix}, // 13: '|'
SymDoubleVertBar: {"||", symClassOperator}, // 14: '||' SymDoubleVertBar: {"||", symClassOperator, posInfix}, // 14: '||'
SymComma: {",", symClassOperator}, // 15: ',' SymComma: {",", symClassOperator, posInfix}, // 15: ','
SymColon: {":", symClassOperator}, // 16: ':' SymColon: {":", symClassOperator, posInfix}, // 16: ':'
SymSemiColon: {";", symClassOperator}, // 17: ';' SymSemiColon: {";", symClassOperator, posInfix}, // 17: ';'
SymDot: {".", symClassOperator}, // 18: '.' SymDot: {".", symClassOperator, posInfix}, // 18: '.'
SymDotSlash: {"./", symClassOperator}, // 19: './' SymDotSlash: {"./", symClassOperator, posInfix}, // 19: './'
SymQuote: {"'", symClassDelimiter}, // 20: '\'' SymQuote: {"'", symClassDelimiter, posLeaf}, // 20: '\''
SymDoubleQuote: {"\"", symClassDelimiter}, // 21: '"' SymDoubleQuote: {"\"", symClassDelimiter, posLeaf}, // 21: '"'
SymBackTick: {"`", symClassOperator}, // 22: '`' SymBackTick: {"`", symClassDelimiter, posLeaf}, // 22: '`'
SymExclamation: {"!", symClassPostOp}, // 23: '!' SymExclamation: {"!", symClassOperator, posPostfix}, // 23: '!'
SymQuestion: {"?", symClassOperator}, // 24: '?' SymQuestion: {"?", symClassOperator, posInfix}, // 24: '?'
SymAmpersand: {"&", symClassOperator}, // 25: '&' SymAmpersand: {"&", symClassOperator, posInfix}, // 25: '&'
SymDoubleAmpersand: {"&&", symClassOperator}, // 26: '&&' SymDoubleAmpersand: {"&&", symClassOperator, posInfix}, // 26: '&&'
SymPercent: {"%", symClassOperator}, // 27: '%' SymPercent: {"%", symClassOperator, posInfix}, // 27: '%'
SymAt: {"@", symClassOperator}, // 28: '@' SymAt: {"@", symClassOperator, posPrefix}, // 28: '@'
SymUndescore: {"_", symClassOperator}, // 29: '_' SymUndescore: {"_", symClassIdentifier, posLeaf}, // 29: '_'
SymEqual: {"=", symClassOperator}, // 30: '=' SymEqual: {"=", symClassOperator, posInfix}, // 30: '='
SymDoubleEqual: {"==", symClassOperator}, // 31: '==' SymDoubleEqual: {"==", symClassOperator, posInfix}, // 31: '=='
SymLess: {"<", symClassOperator}, // 32: '<' SymLess: {"<", symClassOperator, posInfix}, // 32: '<'
SymLessOrEqual: {"<=", symClassOperator}, // 33: '<=' SymLessOrEqual: {"<=", symClassOperator, posInfix}, // 33: '<='
SymGreater: {">", symClassOperator}, // 34: '>' SymGreater: {">", symClassOperator, posInfix}, // 34: '>'
SymGreaterOrEqual: {">=", symClassOperator}, // 35: '>=' SymGreaterOrEqual: {">=", symClassOperator, posInfix}, // 35: '>='
SymLessGreater: {"<>", symClassOperator}, // 36: '<>' SymLessGreater: {"<>", symClassOperator, posInfix}, // 36: '<>'
SymNotEqual: {"!=", symClassOperator}, // 37: '!=' SymNotEqual: {"!=", symClassOperator, posInfix}, // 37: '!='
SymDollar: {"$", symClassOperator}, // 38: '$' SymDollar: {"$", symClassOperator, posPrefix}, // 38: '$'
SymHash: {"#", symClassOperator}, // 39: '#' SymHash: {"#", symClassOperator, posPrefix}, // 39: '#'
SymOpenRound: {"(", symClassParenthesis}, // 40: '(' SymOpenRound: {"(", symClassParenthesis, posPrefix}, // 40: '('
SymClosedRound: {")", symClassParenthesis}, // 41: ')' SymClosedRound: {")", symClassParenthesis, posPostfix}, // 41: ')'
SymOpenSquare: {"[", symClassParenthesis}, // 42: '[' SymOpenSquare: {"[", symClassParenthesis, posPrefix}, // 42: '['
SymClosedSquare: {"]", symClassParenthesis}, // 43: ']' SymClosedSquare: {"]", symClassParenthesis, posPostfix}, // 43: ']'
SymOpenBrace: {"{", symClassParenthesis}, // 44: '{' SymOpenBrace: {"{", symClassParenthesis, posPrefix}, // 44: '{'
SymClosedBrace: {"}", symClassParenthesis}, // 45: '}' SymClosedBrace: {"}", symClassParenthesis, posPostfix}, // 45: '}'
SymTilde: {"~", symClassOperator}, // 46: '~' SymTilde: {"~", symClassOperator, posPrefix}, // 46: '~'
SymDoubleQuestion: {"??", symClassOperator}, // 47: '??' SymDoubleQuestion: {"??", symClassOperator, posInfix}, // 47: '??'
SymQuestionEqual: {"?=", symClassOperator}, // 48: '?=' SymQuestionEqual: {"?=", symClassOperator, posInfix}, // 48: '?='
SymQuestionExclam: {"?!", symClassOperator}, // 49: '?!' SymQuestionExclam: {"?!", symClassOperator, posInfix}, // 49: '?!'
SymDoubleAt: {"@@", symClassOperator}, // 50: '@@' SymDoubleAt: {"@@", symClassCommand, posLeaf}, // 50: '@@'
SymDoubleColon: {"::", symClassOperator}, // 51: '::' SymDoubleColon: {"::", symClassOperator, posInfix}, // 51: '::'
SymInsert: {">>", symClassOperator}, // 52: '>>' SymDoubleGreater: {">>", symClassOperator, posInfix}, // 52: '>>'
SymAppend: {"<<", symClassOperator}, // 53: '<<' SymDoubleLess: {"<<", symClassOperator, posInfix}, // 53: '<<'
SymCaret: {"^", symClassOperator}, // 54: '^' SymCaret: {"^", symClassOperator, posInfix}, // 54: '^'
SymDollarRound: {"$(", symClassOperator}, // 55: '$(' SymDollarRound: {"$(", symClassOperator, posPrefix}, // 55: '$('
SymOpenClosedRound: {"()", symClassPostOp}, // 56: '()' SymOpenClosedRound: {"()", symClassOperator, posPostfix}, // 56: '()'
SymDoubleDollar: {"$$", symClassOperator}, // 57: '$$' SymDoubleDollar: {"$$", symClassCommand, posLeaf}, // 57: '$$'
SymDoubleDot: {"..", symClassOperator}, // 58: '..' SymDoubleDot: {"..", symClassOperator, posInfix}, // 58: '..'
SymTripleDot: {"...", symClassOperator}, // 59: '...' SymTripleDot: {"...", symClassOperator, posPostfix}, // 59: '...'
SymStarEqual: {"*=", symClassOperator}, // 60: '*=' SymStarEqual: {"*=", symClassOperator, posInfix}, // 60: '*='
SymSlashEqual: {"/=", symClassOperator}, // 61: '/=' SymSlashEqual: {"/=", symClassOperator, posInfix}, // 61: '/='
SymPercEqual: {"%=", symClassOperator}, // 62: '%=' SymPercEqual: {"%=", symClassOperator, posInfix}, // 62: '%='
SymDoubleLessEqual: {"<<=", symClassOperator, posInfix}, // 63: '<<='
SymDoubleGreaterEqual: {">>=", symClassOperator, posInfix}, // 64: '>>='
SymAmpersandEqual: {"&=", symClassOperator, posInfix}, // 65: '&='
SymVertBarEqual: {"|=", symClassOperator, posInfix}, // 65: '|='
SymCaretEqual: {"^=", symClassOperator, posInfix}, // 66: '^='
SymPlusGreater: {"+>", symClassOperator, posInfix}, // 67: '+>'
SymLessPlus: {"<+", symClassOperator, posInfix}, // 68: '<+'
SymPreInc: {"++", symClassOperator, posPrefix}, // : '++'
SymPreDec: {"--", symClassOperator, posPrefix}, // : '--'
// SymChangeSign // SymChangeSign
// SymUnchangeSign // SymUnchangeSign
// SymIdentifier // SymIdentifier
@@ -121,17 +131,17 @@ func init() {
// // SymClosedComment // 0: '*/' // // SymClosedComment // 0: '*/'
// // SymOneLineComment // 0: '//' // // SymOneLineComment // 0: '//'
// keywordBase // keywordBase
SymKwAnd: {"and", symClassOperator}, SymKwAnd: {"and", symClassOperator, posInfix},
SymKwNot: {"not", symClassOperator}, SymKwNot: {"not", symClassOperator, posInfix},
SymKwOr: {"or", symClassOperator}, SymKwOr: {"or", symClassOperator, posInfix},
SymKwBut: {"but", symClassOperator}, SymKwBut: {"but", symClassOperator, posInfix},
SymKwFunc: {"func(", symClassDeclaration}, SymKwFunc: {"func(", symClassDeclaration, posPrefix},
SymKwBuiltin: {"builtin", symClassOperator}, SymKwBuiltin: {"builtin", symClassOperator, posPrefix},
SymKwPlugin: {"plugin", symClassOperator}, SymKwPlugin: {"plugin", symClassOperator, posPrefix},
SymKwIn: {"in", symClassOperator}, SymKwIn: {"in", symClassOperator, posInfix},
SymKwInclude: {"include", symClassOperator}, SymKwInclude: {"include", symClassOperator, posPrefix},
SymKwNil: {"nil", symClassValue}, SymKwNil: {"nil", symClassValue, posLeaf},
SymKwUnset: {"unset", symClassOperator}, SymKwUnset: {"unset", symClassOperator, posPrefix},
} }
} }
@@ -169,12 +179,19 @@ func StringEndsWithOperator(s string) bool {
} }
func endingOperator(s string) (sym Symbol) { func endingOperator(s string) (sym Symbol) {
var matchLength = 0
sym = SymNone sym = SymNone
lower := strings.ToLower(s) lower := strings.TrimRight(strings.ToLower(s), " \t")
for symbol, spec := range symbolMap { for symbol, spec := range symbolMap {
if spec.kind == symClassOperator && strings.HasSuffix(lower, spec.repr) { if strings.HasSuffix(lower, spec.repr) {
if len(spec.repr) > matchLength {
matchLength = len(spec.repr)
if spec.kind == symClassOperator && (spec.opType == posInfix || spec.opType == posPrefix) {
sym = symbol sym = symbol
break } else {
sym = SymNone
}
}
} }
} }
return return
+12 -2
View File
@@ -60,8 +60,8 @@ const (
SymQuestionExclam // 49: '?!' SymQuestionExclam // 49: '?!'
SymDoubleAt // 50: '@@' SymDoubleAt // 50: '@@'
SymDoubleColon // 51: '::' SymDoubleColon // 51: '::'
SymInsert // 52: '>>' SymDoubleGreater // 52: '>>'
SymAppend // 53: '<<' SymDoubleLess // 53: '<<'
SymCaret // 54: '^' SymCaret // 54: '^'
SymDollarRound // 55: '$(' SymDollarRound // 55: '$('
SymOpenClosedRound // 56: '()' SymOpenClosedRound // 56: '()'
@@ -71,8 +71,18 @@ const (
SymStarEqual // 60: '*=' SymStarEqual // 60: '*='
SymSlashEqual // 61: '/=' SymSlashEqual // 61: '/='
SymPercEqual // 62: '%=' SymPercEqual // 62: '%='
SymDoubleLessEqual // 63: '<<='
SymDoubleGreaterEqual // 64: '>>='
SymAmpersandEqual // 65: '&='
SymVertBarEqual // 65: '|='
SymCaretEqual // 66: '^='
SymPlusGreater // 67: '+>'
SymLessPlus // 68: '<+'
SymChangeSign SymChangeSign
SymUnchangeSign SymUnchangeSign
SymDereference
SymPreInc
SymPreDec
SymIdentifier SymIdentifier
SymBool SymBool
SymInteger SymInteger
+2 -1
View File
@@ -26,12 +26,13 @@ func TestBool(t *testing.T) {
/* 12 */ {`true or false`, true, nil}, /* 12 */ {`true or false`, true, nil},
/* 13 */ {`true or []`, 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`)}, /* 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"`)}, /* 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", ".") // t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 1) // runTestSuiteSpec(t, section, inputs, 15)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
+11 -6
View File
@@ -52,16 +52,21 @@ func TestFuncBase(t *testing.T) {
/* 38 */ {`bool(1.0)`, true, nil}, /* 38 */ {`bool(1.0)`, true, nil},
/* 39 */ {`bool("1")`, true, nil}, /* 39 */ {`bool("1")`, true, nil},
/* 40 */ {`bool(false)`, false, nil}, /* 40 */ {`bool(false)`, false, nil},
/* 41 */ {`bool([1])`, nil, `bool(): can't convert list to bool`}, /* 41 */ {`bool([1])`, true, nil},
/* 42 */ {`dec(false)`, float64(0), nil}, /* 42 */ {`bool([])`, false, nil},
/* 43 */ {`dec(1:2)`, float64(0.5), nil}, /* 43 */ {`bool({})`, false, nil},
/* 44 */ {`dec([1])`, nil, `dec(): can't convert list to float`}, /* 44 */ {`bool({1:"one"})`, true, nil},
/* 45 */ {`eval("a=3"); a`, int64(3), 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`}, // /* 45 */ {`string([1])`, nil, `string(): can't convert list to string`},
} }
t.Setenv("EXPR_PATH", ".") t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 45) // runTestSuiteSpec(t, section, inputs, 49)
runTestSuite(t, section, inputs) 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}, /* 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)`}, /* 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`}, /* 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: { /* 69 */ /*{`builtin "string"; $$global`, `vars: {
} }
+4 -2
View File
@@ -29,7 +29,9 @@ func TestExpr(t *testing.T) {
/* 15 */ {`a=3; a*=2)+1; a`, nil, `[1:11] unexpected token ")"`}, /* 15 */ {`a=3; a*=2)+1; a`, nil, `[1:11] unexpected token ")"`},
/* 16 */ {`v=[2]; a=1; v[a-=1]=5; v[0]`, int64(5), nil}, /* 16 */ {`v=[2]; a=1; v[a-=1]=5; v[0]`, int64(5), nil},
/* 17 */ {`true ? {"a"} :: {"b"}`, "a", nil}, /* 17 */ {`true ? {"a"} :: {"b"}`, "a", nil},
/* 18 */ {` /* 18 */ {`$$`, NewDict(map[any]any{"variables": NewDict(nil), "functions": NewDict(nil)}), nil},
///* 19 */ {`$$global`, NewDict(map[any]any{"variables": NewDict(nil), "functions": NewDict(nil)}), nil},
/* 19 */ {`
ds={ ds={
"init":func(@end){@current=0 but true}, "init":func(@end){@current=0 but true},
//"current":func(){current}, //"current":func(){current},
@@ -44,6 +46,6 @@ func TestExpr(t *testing.T) {
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 6, 7, 8, 9) // runTestSuiteSpec(t, section, inputs, 18)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
+2 -2
View File
@@ -25,7 +25,7 @@ func TestFractionsParser(t *testing.T) {
/* 12 */ {`builtin "math.arith"; add(1:2, 1.0, 2)`, float64(3.5), nil}, /* 12 */ {`builtin "math.arith"; add(1:2, 1.0, 2)`, float64(3.5), nil},
/* 13 */ {`builtin "math.arith"; mul(1:2, 2:3)`, newFraction(2, 6), nil}, /* 13 */ {`builtin "math.arith"; mul(1:2, 2:3)`, newFraction(2, 6), nil},
/* 14 */ {`builtin "math.arith"; mul(1:2, 1.0, 2)`, float64(1.0), nil}, /* 14 */ {`builtin "math.arith"; mul(1:2, 1.0, 2)`, float64(1.0), nil},
/* 15 */ {`1:0`, nil, `division by zero`}, /* 15 */ {`1:0`, nil, `[1:3] division by zero`},
/* 16 */ {`fract(-0.5)`, newFraction(-1, 2), nil}, /* 16 */ {`fract(-0.5)`, newFraction(-1, 2), nil},
/* 17 */ {`fract("")`, (*FractionType)(nil), `bad syntax`}, /* 17 */ {`fract("")`, (*FractionType)(nil), `bad syntax`},
/* 18 */ {`fract("-1")`, newFraction(-1, 1), nil}, /* 18 */ {`fract("-1")`, newFraction(-1, 1), nil},
@@ -34,7 +34,7 @@ func TestFractionsParser(t *testing.T) {
/* 21 */ {`fract(1,0)`, nil, `fract(): division by zero`}, /* 21 */ {`fract(1,0)`, nil, `fract(): division by zero`},
/* 22 */ {`string(1:2)`, "1:2", nil}, /* 22 */ {`string(1:2)`, "1:2", nil},
/* 23 */ {`1+1:2+0.5`, float64(2), nil}, /* 23 */ {`1+1:2+0.5`, float64(2), nil},
/* 24 */ {`1:(2-2)`, nil, `division by zero`}, /* 24 */ {`1:(2-2)`, nil, `[1:3] division by zero`},
/* 25 */ {`[0,1][1-1]:1`, newFraction(0, 1), nil}, /* 25 */ {`[0,1][1-1]:1`, newFraction(0, 1), nil},
} }
// runTestSuiteSpec(t, section, inputs, 25) // runTestSuiteSpec(t, section, inputs, 25)
+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) 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)
}
+7 -3
View File
@@ -9,10 +9,10 @@ import "testing"
func TestIteratorParser(t *testing.T) { func TestIteratorParser(t *testing.T) {
section := "Iterator" section := "Iterator"
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`include "test-resources/iterator.expr"; it=$(ds,3); ^it`, int64(0), nil}, /* 1 */ {`include "test-resources/iterator.expr"; it=$(ds,3); *it`, int64(0), nil},
/* 2 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil}, /* 2 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil},
/* 3 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(3), nil}, /* 3 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(3), nil},
/* 4 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; it.reset; ^it`, int64(0), nil}, /* 4 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; it.reset; *it`, int64(0), nil},
/* 5 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil}, /* 5 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil},
/* 6 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil}, /* 6 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil},
/* 7 */ {`builtin "math.arith"; include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); mul(it)`, int64(12000), nil}, /* 7 */ {`builtin "math.arith"; include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); mul(it)`, int64(12000), nil},
@@ -27,8 +27,12 @@ func TestIteratorParser(t *testing.T) {
/* 16 */ {`include "test-resources/filter.expr"; it=$(ds,10); it++`, int64(2), nil}, /* 16 */ {`include "test-resources/filter.expr"; it=$(ds,10); it++`, int64(2), nil},
/* 17 */ {`it=$({"next":func(){5}}); it++`, int64(5), nil}, /* 17 */ {`it=$({"next":func(){5}}); it++`, int64(5), nil},
/* 18 */ {`it=$({"next":func(){5}}); it.clean`, nil, 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) runTestSuite(t, section, inputs)
} }
+16 -16
View File
@@ -23,8 +23,8 @@ func TestListParser(t *testing.T) {
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil}, /* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
/* 10 */ {`add([1,"hello"])`, nil, `add(): param nr 2 (2 in 1) has wrong type string, number expected`}, /* 10 */ {`add([1,"hello"])`, nil, `add(): param nr 2 (2 in 1) has wrong type string, number expected`},
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil}, /* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
/* 12 */ {`[1,2,3] << 2+2`, newListA(int64(1), int64(2), int64(3), int64(4)), nil}, /* 12 */ {`[1,2,3] <+ 2+2`, newListA(int64(1), int64(2), int64(3), int64(4)), nil},
/* 13 */ {`2-1 >> [2,3]`, newListA(int64(1), int64(2), int64(3)), nil}, /* 13 */ {`2-1 +> [2,3]`, newListA(int64(1), int64(2), int64(3)), nil},
/* 14 */ {`[1,2,3][1]`, int64(2), nil}, /* 14 */ {`[1,2,3][1]`, int64(2), nil},
/* 15 */ {`ls=[1,2,3] but ls[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}, /* 16 */ {`ls=[1,2,3] but ls[-1]`, int64(3), nil},
@@ -33,25 +33,25 @@ func TestListParser(t *testing.T) {
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil}, /* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
/* 20 */ {`#["a", "b", "c"]`, int64(3), nil}, /* 20 */ {`#["a", "b", "c"]`, int64(3), nil},
/* 21 */ {`"b" in ["a", "b", "c"]`, true, nil}, /* 21 */ {`"b" in ["a", "b", "c"]`, true, nil},
/* 22 */ {`a=[1,2]; (a)<<3`, newListA(int64(1), int64(2), int64(3)), nil}, /* 22 */ {`a=[1,2]; (a)<+3`, newListA(int64(1), int64(2), int64(3)), nil},
/* 23 */ {`a=[1,2]; (a)<<3; a`, newListA(int64(1), int64(2)), nil}, /* 23 */ {`a=[1,2]; (a)<+3; a`, newListA(int64(1), int64(2)), nil},
/* 24 */ {`["a","b","c","d"][1]`, "b", nil}, /* 24 */ {`["a","b","c","d"][1]`, "b", nil},
/* 25 */ {`["a","b","c","d"][1,1]`, nil, `[1:19] one index only is allowed`}, /* 25 */ {`["a","b","c","d"][1,1]`, nil, `[1:19] one index only is allowed`},
/* 26 */ {`[0,1,2,3,4][:]`, newListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil}, /* 26 */ {`[0,1,2,3,4][:]`, newListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil},
/* 27 */ {`["a", "b", "c"] << ;`, nil, `[1:18] infix operator "<<" requires two non-nil operands, got 1`}, /* 27 */ {`["a", "b", "c"] <+ ;`, nil, `[1:18] infix operator "<+" requires two non-nil operands, got 1`},
/* 28 */ {`2 << 3;`, int64(16), nil}, /* 28 */ {`2 << 3;`, int64(16), nil},
/* 29 */ {`but >> ["a", "b", "c"]`, nil, `[1:6] infix operator ">>" requires two non-nil operands, got 0`}, /* 29 */ {`but +> ["a", "b", "c"]`, nil, `[1:6] infix operator "+>" requires two non-nil operands, got 0`},
/* 30 */ {`2 >> 3;`, int64(0), nil}, /* 30 */ {`2 >> 3;`, int64(0), nil},
/* 31 */ {`a=[1,2]; a<<3`, newListA(int64(1), int64(2), int64(3)), nil}, /* 31 */ {`a=[1,2]; a<+3`, newListA(int64(1), int64(2), int64(3)), nil},
/* 33 */ {`a=[1,2]; 5>>a`, newListA(int64(5), int64(1), int64(2)), nil}, /* 32 */ {`a=[1,2]; 5+>a`, newListA(int64(5), int64(1), int64(2)), nil},
/* 34 */ {`L=[1,2]; L[0]=9; L`, newListA(int64(9), int64(2)), nil}, /* 33 */ {`L=[1,2]; L[0]=9; L`, newListA(int64(9), int64(2)), nil},
/* 35 */ {`L=[1,2]; L[5]=9; L`, nil, `index 5 out of bounds (0, 1)`}, /* 34 */ {`L=[1,2]; L[5]=9; L`, nil, `index 5 out of bounds (0, 1)`},
/* 36 */ {`L=[1,2]; L[]=9; L`, nil, `[1:12] index/key specification expected, got [] [list]`}, /* 35 */ {`L=[1,2]; L[]=9; L`, nil, `[1:12] index/key specification expected, got [] [list]`},
/* 37 */ {`L=[1,2]; L[nil]=9;`, nil, `[1:12] index/key is nil`}, /* 36 */ {`L=[1,2]; L[nil]=9;`, nil, `[1:12] index/key is nil`},
/* 38 */ {`[0,1,2,3,4][2:3]`, newListA(int64(2)), nil}, /* 37 */ {`[0,1,2,3,4][2:3]`, newListA(int64(2)), nil},
/* 39 */ {`[0,1,2,3,4][3:-1]`, newListA(int64(3)), nil}, /* 38 */ {`[0,1,2,3,4][3:-1]`, newListA(int64(3)), nil},
/* 40 */ {`[0,1,2,3,4][-3:-1]`, newListA(int64(2), int64(3)), nil}, /* 30 */ {`[0,1,2,3,4][-3:-1]`, newListA(int64(2), int64(3)), nil},
/* 41 */ {`[0,1,2,3,4][0:]`, newListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil}, /* 40 */ {`[0,1,2,3,4][0:]`, newListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
+20 -1
View File
@@ -14,10 +14,29 @@ func TestOperator(t *testing.T) {
/* 1 */ {`a=1; unset "a"; a`, nil, `undefined variable or function "a"`}, /* 1 */ {`a=1; unset "a"; a`, nil, `undefined variable or function "a"`},
/* 2 */ {`a=1; unset ["a", "b"]`, int64(1), nil}, /* 2 */ {`a=1; unset ["a", "b"]`, int64(1), nil},
/* 3 */ {`f=func(){3}; unset "f()"`, int64(1), nil}, /* 3 */ {`f=func(){3}; unset "f()"`, int64(1), nil},
/* 4 */ {`a=1; a<<=1+0`, int64(2), nil},
/* 5 */ {`a=2; a>>=1+0`, int64(1), nil},
/* 6 */ {`1<<1`, int64(2), nil},
/* 7 */ {`1>>1`, int64(0), nil},
/* 8 */ {`1|2`, int64(3), nil},
/* 9 */ {`a=1; a|=2`, int64(3), nil},
/* 10 */ {`3&1`, int64(1), nil},
/* 11 */ {`a=3; a&=1`, int64(1), nil},
/* 12 */ {`~1`, int64(-2), nil},
/* 13 */ {`0x10`, int64(16), nil},
/* 14 */ {`0x1X`, nil, `[1:5] two adjacent operators: "1" and "X"`},
/* 15 */ {`0o10`, int64(8), nil},
/* 16 */ {`0b10`, int64(2), nil},
/* 17 */ {`~true`, nil, `[1:2] prefix/postfix operator "~" do not support operand 'true' [bool]`},
/* 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", ".") // t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 3) // runTestSuiteSpec(t, section, inputs, 4)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
+3 -3
View File
@@ -78,8 +78,8 @@ func TestGeneralParser(t *testing.T) {
/* 64 */ {`"1.5" != `, nil, `[1:8] infix operator "!=" requires two non-nil operands, got 1`}, /* 64 */ {`"1.5" != `, nil, `[1:8] infix operator "!=" requires two non-nil operands, got 1`},
/* 65 */ {"+1.5", float64(1.5), nil}, /* 65 */ {"+1.5", float64(1.5), nil},
/* 66 */ {"+", nil, `[1:2] prefix operator "+" requires one non-nil operand`}, /* 66 */ {"+", nil, `[1:2] prefix operator "+" requires one non-nil operand`},
/* 67 */ {"4 / 0", nil, `division by zero`}, /* 67 */ {"4 / 0", nil, `[1:4] division by zero`},
/* 68 */ {"4.0 / 0", nil, `division by zero`}, /* 68 */ {"4.0 / 0", nil, `[1:6] division by zero`},
/* 69 */ {"4.0 / \n2", float64(2.0), nil}, /* 69 */ {"4.0 / \n2", float64(2.0), nil},
/* 70 */ {`123`, int64(123), nil}, /* 70 */ {`123`, int64(123), nil},
/* 71 */ {`1.`, float64(1.0), nil}, /* 71 */ {`1.`, float64(1.0), nil},
@@ -142,6 +142,6 @@ func TestGeneralParser(t *testing.T) {
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 130) // runTestSuiteSpec(t, section, inputs, 114)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
+8 -1
View File
@@ -9,6 +9,7 @@ import (
) )
func TestStringsParser(t *testing.T) { func TestStringsParser(t *testing.T) {
section := "String"
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`"uno" + "due"`, `unodue`, nil}, /* 1 */ {`"uno" + "due"`, `unodue`, nil},
/* 2 */ {`"uno" + 2`, `uno2`, nil}, /* 2 */ {`"uno" + 2`, `uno2`, nil},
@@ -16,6 +17,12 @@ func TestStringsParser(t *testing.T) {
/* 4 */ {`"uno" * (2+1)`, `unounouno`, nil}, /* 4 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 5 */ {`"abc"[1]`, `b`, nil}, /* 5 */ {`"abc"[1]`, `b`, nil},
/* 6 */ {`#"abc"`, int64(3), nil}, /* 6 */ {`#"abc"`, int64(3), nil},
/* 7 */ {`"192.168.0.240" / "."`, newListA("192", "168", "0", "240"), nil},
/* 8 */ {`("192.168.0.240" / ".")[1]`, "168", nil},
/* 9 */ {`"AF3B0Dz" / 2`, newListA("AF", "3B", "0D", "z"), nil},
/* 10 */ {`"AF3B0Dz" / 0`, nil, "[1:12] division by zero"},
} }
runTestSuite(t, "String", inputs)
// runTestSuiteSpec(t, section, inputs, 8)
runTestSuite(t, section, inputs)
} }
+10 -1
View File
@@ -15,21 +15,26 @@ const (
priRange priRange
priBut priBut
priAssign priAssign
priInsert
priOr priOr
priAnd priAnd
priNot priNot
priRelational priRelational
priBinary priBitwiseOr
priBitwiseAnd
priBitwiseNot
priSum priSum
priProduct priProduct
priFraction priFraction
priSelector priSelector
priBinShift
priSign priSign
priFact priFact
priIterValue priIterValue
priDefault priDefault
priIncDec priIncDec
priDot priDot
priDereference
priValue priValue
) )
@@ -199,6 +204,10 @@ func (term *term) errIncompatibleType(value any) error {
term.source(), value, TypeName(value)) term.source(), value, TypeName(value))
} }
func (term *term) errDivisionByZero() error {
return term.tk.Errorf("division by zero")
}
func (term *term) Errorf(template string, args ...any) (err error) { func (term *term) Errorf(template string, args ...any) (err error) {
err = term.tk.Errorf(template, args...) err = term.tk.Errorf(template, args...)
return return
+4
View File
@@ -79,6 +79,10 @@ func (tk *Token) IsSymbol(sym Symbol) bool {
return tk.Sym == sym return tk.Sym == sym
} }
func (tk *Token) SetSymbol(sym Symbol) {
tk.Sym = sym
}
func (tk *Token) Errorf(template string, args ...any) (err error) { func (tk *Token) Errorf(template string, args ...any) (err error) {
err = fmt.Errorf(fmt.Sprintf("[%d:%d] ", tk.row, tk.col)+template, args...) err = fmt.Errorf(fmt.Sprintf("[%d:%d] ", tk.row, tk.col)+template, args...)
return return
+9
View File
@@ -218,6 +218,15 @@ func ToGoInt(value any, description string) (i int, err error) {
return 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 { func ForAll[T, V any](ts []T, fn func(T) V) []V {
result := make([]V, len(ts)) result := make([]V, len(ts))
for i, t := range ts { for i, t := range ts {