Compare commits

...

29 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
38 changed files with 2714 additions and 485 deletions
+30
View File
@@ -72,6 +72,10 @@ func boolFunc(ctx ExprContext, name string, args map[string]any) (result any, er
result = v
case string:
result = len(v) > 0
case *ListType:
result = len(*v) > 0
case *DictType:
result = len(*v) > 0
default:
err = ErrCantConvert(name, v, "bool")
}
@@ -95,6 +99,8 @@ func intFunc(ctx ExprContext, name string, args map[string]any) (result any, err
if i, err = strconv.Atoi(v); err == nil {
result = int64(i)
}
case *FractionType:
result = int64(v.num / v.den)
default:
err = ErrCantConvert(name, v, "int")
}
@@ -211,6 +217,25 @@ func evalFunc(ctx ExprContext, name string, args map[string]any) (result any, er
return
}
func varFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var varName string
var ok bool
if varName, ok = args[ParamName].(string); !ok {
return nil, ErrWrongParamType(name, ParamName, TypeString, args[ParamName])
}
if result, ok = args[ParamValue]; ok && result != nil {
ctx.GetParent().UnsafeSetVar(varName, result)
// } else {
// err = ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
// }
} else if result, ok = ctx.GetVar(varName); !ok {
err = ErrUnknownVar(name, varName)
}
return
}
//// import
func ImportBuiltinsFuncs(ctx ExprContext) {
@@ -240,6 +265,11 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("eval", NewGolangFunctor(evalFunc), TypeAny, []ExprFuncParam{
NewFuncParam(ParamSource),
})
ctx.RegisterFunc("var", NewGolangFunctor(varFunc), TypeAny, []ExprFuncParam{
NewFuncParam(ParamName),
NewFuncParamFlagDef(ParamValue, PfDefault, nil),
})
}
func init() {
+26
View File
@@ -200,6 +200,24 @@ func splitStrFunc(ctx ExprContext, name string, args map[string]any) (result any
return
}
func upperStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
if source, ok := args[ParamSource].(string); ok {
result = strings.ToUpper(source)
} else {
err = ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
}
return
}
func lowerStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
if source, ok := args[ParamSource].(string); ok {
result = strings.ToLower(source)
} else {
err = ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
}
return
}
// --- End of function definitions
// Import above functions in the context
@@ -236,6 +254,14 @@ func ImportStringFuncs(ctx ExprContext) {
NewFuncParam(ParamSuffix),
NewFuncParamFlag(strParamOther, PfRepeat),
})
ctx.RegisterFunc("strUpper", NewGolangFunctor(upperStrFunc), TypeString, []ExprFuncParam{
NewFuncParam(ParamSource),
})
ctx.RegisterFunc("strLower", NewGolangFunctor(lowerStrFunc), TypeString, []ExprFuncParam{
NewFuncParam(ParamSource),
})
}
// Register the import function in the import-register.
+4
View File
@@ -80,6 +80,10 @@ func ErrUnknownParam(funcName, paramName string) error {
return fmt.Errorf("%s(): unknown parameter %q", funcName, paramName)
}
func ErrUnknownVar(funcName, varName string) error {
return fmt.Errorf("%s(): unknown variable %q", funcName, varName)
}
// --- Operator errors
func ErrLeftOperandMustBeVariable(leftTerm, opTerm *term) error {
+209
View File
@@ -0,0 +1,209 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// dict-iterator.go
package expr
import (
"fmt"
"io"
"slices"
"strings"
)
type dictIterMode int
const (
dictIterModeKeys dictIterMode = iota
dictIterModeValues
dictIterModeItems
)
type DictIterator struct {
a *DictType
count int
index int
keys []any
iterMode dictIterMode
}
type sortType int
const (
sortTypeNone sortType = iota
sortTypeAsc
sortTypeDesc
sortTypeDefault = sortTypeAsc
)
func (it *DictIterator) makeKeys(m map[any]any, sort sortType) {
it.keys = make([]any, 0, len(m))
if sort == sortTypeNone {
for keyAny := range m {
it.keys = append(it.keys, keyAny)
}
} else {
scalarMap := make(map[string]any, len(m))
scalerKeys := make([]string, 0, len(m))
for keyAny := range m {
keyStr := fmt.Sprint(keyAny)
scalarMap[keyStr] = keyAny
scalerKeys = append(scalerKeys, keyStr)
}
switch sort {
case sortTypeAsc:
slices.Sort(scalerKeys)
case sortTypeDesc:
slices.Sort(scalerKeys)
slices.Reverse(scalerKeys)
}
for _, keyStr := range scalerKeys {
it.keys = append(it.keys, scalarMap[keyStr])
}
}
}
func NewDictIterator(dict *DictType, args []any) (it *DictIterator, err error) {
var sortType = sortTypeNone
var s string
it = &DictIterator{a: dict, count: 0, index: -1, keys: nil, iterMode: dictIterModeKeys}
if len(args) > 0 {
if s, err = ToGoString(args[0], "sort type"); err == nil {
switch strings.ToLower(s) {
case "a", "asc":
sortType = sortTypeAsc
case "d", "desc":
sortType = sortTypeDesc
case "n", "none", "nosort", "no-sort":
sortType = sortTypeNone
case "", "default":
sortType = sortTypeDefault
default:
err = fmt.Errorf("invalid sort type %q", s)
}
if err == nil && len(args) > 1 {
if s, err = ToGoString(args[1], "iteration mode"); err == nil {
switch strings.ToLower(s) {
case "k", "key", "keys":
it.iterMode = dictIterModeKeys
case "v", "value", "values":
it.iterMode = dictIterModeValues
case "i", "item", "items":
it.iterMode = dictIterModeItems
case "", "default":
it.iterMode = dictIterModeKeys
default:
err = fmt.Errorf("invalid iteration mode %q", s)
}
}
}
}
}
it.makeKeys(*dict, sortType)
return
}
func NewMapIterator(m map[any]any) (it *DictIterator) {
it = &DictIterator{a: (*DictType)(&m), count: 0, index: -1, keys: nil}
it.makeKeys(m, sortTypeNone)
return
}
func (it *DictIterator) String() string {
var l = 0
if it.a != nil {
l = len(*it.a)
}
return fmt.Sprintf("$(#%d)", l)
}
func (it *DictIterator) TypeName() string {
return "DictIterator"
}
func (it *DictIterator) HasOperation(name string) bool {
// yes := name == NextName || name == ResetName || name == IndexName || name == CountName || name == CurrentName
yes := slices.Contains([]string{NextName, ResetName, IndexName, CountName, CurrentName, CleanName, KeyName, ValueName}, name)
return yes
}
func (it *DictIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case NextName:
v, err = it.Next()
case ResetName:
err = it.Reset()
case CleanName:
err = it.Clean()
case IndexName:
v = int64(it.Index())
case CurrentName:
v, err = it.Current()
case CountName:
v = it.count
case KeyName:
if it.index >= 0 && it.index < len(it.keys) {
v = it.keys[it.index]
} else {
err = io.EOF
}
case ValueName:
if it.index >= 0 && it.index < len(it.keys) {
a := *(it.a)
v = a[it.keys[it.index]]
} else {
err = io.EOF
}
default:
err = errNoOperation(name)
}
return
}
func (it *DictIterator) Current() (item any, err error) {
if it.index >= 0 && it.index < len(it.keys) {
switch it.iterMode {
case dictIterModeKeys:
item = it.keys[it.index]
case dictIterModeValues:
a := *(it.a)
item = a[it.keys[it.index]]
case dictIterModeItems:
a := *(it.a)
pair := []any{it.keys[it.index], a[it.keys[it.index]]}
item = newList(pair)
}
} else {
err = io.EOF
}
return
}
func (it *DictIterator) Next() (item any, err error) {
it.index++
if item, err = it.Current(); err != io.EOF {
it.count++
}
return
}
func (it *DictIterator) Index() int {
return it.index
}
func (it *DictIterator) Count() int {
return it.count
}
func (it *DictIterator) Reset() error {
it.index = -1
it.count = 0
return nil
}
func (it *DictIterator) Clean() error {
return nil
}
+1 -1
View File
@@ -33,7 +33,7 @@ func NewDict(dictAny map[any]any) (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
if dictAny != nil {
d = make(DictType, len(dictAny))
+642 -45
View File
@@ -20,15 +20,24 @@ Expressions calculator
:rouge-style: gruvbox
// :rouge-style: colorful
//:rouge-style: monokay
// Work around to manage double-column in back-tick quotes
// Workaround to manage double-column in back-tick quotes
:2c: ::
// Workaround to manage double-plus in back-tick quotes
:plusplus: ++
// Workaround to manage asterisk in back-tick quotes
:star: *
:sp: &#160;
:2sp: &#160;&#160;
:4sp: &#160;&#160;&#160;&#160;
toc::[]
#TODO: Work in progress (last update on 2024/06/21, 05:40 a.m.)#
#TODO: Work in progress (last update on 2026/04/15, 6:02 p.m.)#
== Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
_Expr_ is a GO package that can analyze, interpret and calculate expressions.
=== Concepts and terminology
@@ -42,7 +51,7 @@ Expressions are texts containing sequences of operations represented by a syntax
image::expression-diagram.png[]
==== Variables
_Expr_ supports variables. The result of an expression can be stored in a variable and reused in other espressions simply specifying the name of the variable as an operand.
_Expr_ supports variables. The result of an expression can be stored in a variable and reused in other espressions by simply specifying the name of the variable as an operand.
==== Multi-expression
An input text valid for _Expr_ can contain more than an expression. Expressions are separated by [blue]`;` (semicolon). When an input contains two or more expressions it is called _multi-expression_.
@@ -58,9 +67,40 @@ The expression context is analogous to the stack-frame of other programming lang
Function contexts are created by cloning the calling context. More details on this topic are given later in this document.
_Expr_ creates and keeps a inner _global context_ where it stores imported functions, either from builtin or plugin modules. To perform calculations, the user program must provide its own context; this is the _main context_. All calculations take place in this context. As mentioned eralier, when a function is called, a new context is created by cloning the calling context. The created context can be called _function context_.
_Expr_ creates and keeps a inner _global context_ where it stores imported functions, either from builtin or plugin modules. To perform calculations, the user program must provide its own context; this is the _main context_. All calculations take place in this context. As mentioned earlier, when a function is called, a new context is created by cloning the calling context. The created context can be called _function context_.
Imported functions are registerd in the _global context_. When an expression first calls an imported function, that function is linked to the current context; this can be the _main context_ or a _function context_.
Imported functions are registered in the _global context_. When an expression first calls an imported function, that function is linked to the current context; this can be the _main context_ or a _function context_.
===== Inspecting contexts
_Expr_ provides the operator [blue]_$$_ that returns the current context. This can be used to inspect the content of the context, for example to check the value of a variable or to see which functions are currently linked to the context. This operator is primarily intended for debugging purposes.
An interactive tool could like `dev-expr` (see <<_dev-expr_test_tool>>) can be used to inspect contexts interactively.
.Example: inspecting contexts
`>>>` [blue]`$$` +
[green]`{"variables": {"ls": "[10, 20, 30]", "it": "$(#3)", "last": "-1"}, "functions": {"about": "about():string{}", "bin": "bin(value, digits=8):integer{}", "ctrl": "ctrl(prop, value):any{}", "ctrlList": "ctrlList():list-of-strings{}", "envGet": "envGet(name):string{}", "envSet": "envSet(name, value):string{}"}}` +
`>>>` [gray]_// Let use the *ml* command to activate multi-line output of contexts, which is more readable._ +
`>>>` [blue]`ml` +
`>>>` [blue]`$$` +
[green]`{` +
[green]`{2sp}"variables": {` +
[green]`{4sp}"last": {"variables": {"last": "-1", "ls": "[10, 20, 30]", "it": "$(#3)"}, "functions": {"ctrlList": "ctrlList():list-of-strings{}", "envGet": "envGet(name):string{}", "envSet": "envSet(name, value):string{}", "about": "about():string{}", "bin": "bin(value, digits=8):integer{}", "ctrl": "ctrl(prop, value):any{}"}},` +
[green]`{4sp}"ls": [10, 20, 30],` +
[green]`{4sp}"it": $(#3)` +
[green]`{2sp}},` +
[green]`{2sp}"functions": {` +
[green]`{4sp}"ctrlList": ctrlList():list-of-strings{},` +
[green]`{4sp}"envGet": envGet(name):string{},` +
[green]`{4sp}"envSet": envSet(name, value):string{},` +
[green]`{4sp}"about": about():string{},` +
[green]`{4sp}"bin": bin(value, digits=8):integer{},` +
[green]`{4sp}"ctrl": ctrl(prop, value):any{}` +
[green]`{2sp}}` +
[green]`}`
In order to inspect the global context issue the [blue]`$$global` operator.
=== `dev-expr` test tool
Before we begin to describe the syntax of _Expr_, it is worth introducing _dev-expr_ because it will be used to show many examples of expressions.
@@ -145,7 +185,7 @@ dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoro
<2> Fractions: _numerator_ : _denominator_.
<3> Activate multi-line output of fractions.
<4> But operator, see <<_but_operator>>.
<5> Multi-expression: the same result of the previous single expression but this it is obtained with two separated calculations.
<5> Multi-expression: the same result as the previous single expression, but this time it is obtained with two separate calculations.
== Data types
_Expr_ has its type system which is a subset of Golang's type system. It supports numerical, string, relational, boolean expressions, and mixed-type lists and maps.
@@ -240,10 +280,11 @@ _Expr_ also supports fractions. Fraction literals are made with two integers sep
.Fraction literal syntax
====
*_fraction_* = [__sign__] (_num-den-spec_ "**:**" _float-spec_) +
*_fraction_* = [__sign__] (_num-den-spec_ | _float-spec_) +
_sign_ = "**+**" | "**-**" +
_num-den-spec_ = _digit-seq_ "**|**" _digit-seq_ +
_float-spec_ = _dec-seq_ "**.**" [_dec-seq_] "**(**" _dec-seq_ "**)**" +
_num-den-spec_ = _digit-seq_ "**:**" _digit-seq_ +
_float-spec_ = _dec-seq_ "**.**" [_dec-seq_] "**(**" _repetend_ "**)**" +
_repetend_ = _dec-seq_ +
_dec-seq_ = _see-integer-literal-syntax_ +
_digit-seq_ = _see-integer-literal-syntax_
====
@@ -276,6 +317,8 @@ _digit-seq_ = _see-integer-literal-syntax_
`>>>` [blue]`1:(-2)` +
[green]`-1:2`
`>>>` [blue]`1.(3)` // 1.33333... +
[green]`4:3`
Fractions can be used together with integers and floats in expressions.
@@ -395,12 +438,13 @@ Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relationa
[CAUTION]
====
Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of the [blue]`and` and [blue]`or` operators is sufficient to establish the result of the whole operation, the right expression would not be evaluated at all.
.Example
[source,go]
----
2 > (a=1) or (a=8) > 0; a // <1>
----
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and, as a conseguence, its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
TIP: `dev-expr` provides the _ctrl()_ function that allows to change this behaviour.
@@ -492,10 +536,10 @@ Array's items can be accessed using the index `[]` operator.
[green]`3`
.Examples: Element insertion
`>>>` [blue]`"first" >> list` +
`>>>` [blue]`"first" +> list` +
[green]`["first", "one", "six", "three"]`
`>>>` [blue]`list << "last"` +
`>>>` [blue]`list <+ "last"` +
[green]`["first", "one", "six", "three", "last"]`
.Examples: Element in list
@@ -509,7 +553,7 @@ Array's items can be accessed using the index `[]` operator.
`>>>` [blue]`[1,2,3] + ["one", "two", "three"]` +
[green]`[1, 2, 3, "one", "two", "three"]`
`>>>` [blue]`[1,2,3,4] - [2,4]` +
`>>>` [blue]`[1,2,3,2,4] - [2,4]` +
[green]`[1, 3]`
@@ -570,7 +614,7 @@ _Expr_, like most programming languages, supports variables. A variable is an id
.Variable literal syntax
====
*_variable_* = _identifier_ "*=*" _any-value_ +
_identifier_ = _alpha_ {(_alpha_)|_dec-digit_|"*_*"} +
_identifier_ = _alpha_ {_alpha_|_dec-digit_|"*_*"} +
__alpha__ = "*a*"|"*b*"|..."*z*"|"*A*"|"*B*"|..."*Z*"
====
@@ -695,7 +739,7 @@ The [blue]`:` symbol (colon) is the separator of the selector-cases. Note that i
=== Variable default value [blue]`??`, [blue]`?=`, and [blue]`?!`
The left operand of first two operators, [blue]`??` and [blue]`?=`, must be a variable. The right operator can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.
The left operand of the first two operators, [blue]`??` and [blue]`?=`, must be a variable. The right operator can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.
IMPORTANT: If the left variable is defined, the right expression is not evaluated at all.
@@ -737,12 +781,15 @@ The table below shows all supported operators by decreasing priorities.
.Operators priorities
[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_
| [blue]`[`...`]` | _Postfix_ | _Dict item_ | _dict_ `[` _any_ `]` -> _any_
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `++` -> _integer_
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `++` -> _any_
.5+|*INC/DEC*| [blue]`++` | _Postfix_ | _Iterator next item_ | _iterator_ `++` -> _any_
| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `++` -> _integer_
| [blue]`++` | _Prefix_ | _Pre increment_ | `++` _integer-variable_ -> _integer_
| [blue]`--` | _Postfix_ | _Post decrement_ | _integer-variable_ `--` -> _integer_
| [blue]`--` | _Prefix_ | _Pre decrement_ | `--` _integer-variable_ -> _integer_
.3+|*DEFAULT*| [blue]`??` | _Infix_ | _Default value_| _variable_ `??` _any-expr_ -> _any_
| [blue]`?=` | _Infix_ | _Default/assign value_| _variable_ `?=` _any-expr_ -> _any_
| [blue]`?!` | _Infix_ | _Alternate value_| _variable_ `?!` _any-expr_ -> _any_
@@ -751,13 +798,17 @@ The table below shows all supported operators by decreasing priorities.
.3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`+`\|`-`) _number_ -> _number_
| [blue]`#` | _Prefix_ | _Lenght-of_ | `#` _collection_ -> _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_
| [blue]`? : ::` | _Multi-Infix_ | _Index-Selector_ | _int-expr_ `?` _case-expr_ `:` _case-expr_ ... `::` _default-expr_ -> _any_
.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_ | _Division_ | _number_ `/` _number_ -> _number_
| [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_
.6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `+` _number_ -> _number_
| [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_ | _Subtraction_ | _number_ `-` _number_ -> _number_
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `-` _list_ -> _list_
.1+|*BINARY NOT*| [blue]`~` | _Prefix_ | _Binary Not_ | `~` _number_ -> _number_
.1+|*BINARY AND*| [blue]`&` | _Infix_ | _Binary And_ | _number_ `&` _number_ -> _number_
.1+|*BINARY OR*| [blue]`\|` | _Infix_ | _Binary Or_ | _number_ `\|` _number_ -> _number_
.1+|*BITWISE NOT*| [blue]`~` | _Prefix_ | _Binary Not_ | `~` _integer_ -> _integer_
.1+|*BITWISE AND*| [blue]`&` | _Infix_ | _Binary And_ | _integer_ `&` _integer_ -> _integer_
.2+|*BITWISE OR*| [blue]`\|` | _Infix_ | _Binary Or_ | _integer_ `\|` _integer_ -> _integer_
| [blue]`^` | _Infix_ | _Binary Xor_ | _integer_ `^` _integer_ -> _integer_
.8+|*RELATION*| [blue]`<` | _Infix_ | _Less_ | _comparable_ `<` _comparable_ -> _boolean_
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `\<=` _comparable_ -> _boolean_
| [blue]`>` | _Infix_ | _Greater_ | _comparable_ `>` _comparable_ -> _boolean_
@@ -783,15 +835,47 @@ The table below shows all supported operators by decreasing priorities.
| [blue]`\|\|` | _Infix_ | _Or_ | _boolean_ `\|\|` _boolean_ -> _boolean_
.2+|*INSERT*| [blue]`+>` | _Infix_ | _Prepend_ | _any_ `+>` _list_ -> _list_
| [blue]`<+` | _Infix_ | _Append_ | _list_ `<+` _any_ -> _list_
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _Assignment_ | _identifier_ `=` _any_ -> _any_
| [blue]`>>` | _Infix_ | _Front-insert_ | _any_ `>>` _list_ -> _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+|*RANGE*| [blue]`:` | _Infix_ | _Index-range_ | _integer_ `:` _integer_ -> _integer-pair_
|===
//^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 in _Expr_ are very similar to functions available in many programming languages. Currently, _Expr_ supports two types of function, _expr-functions_ and _go-functions_.
@@ -801,12 +885,12 @@ Functions in _Expr_ are very similar to functions available in many programming
=== _Expr_ function definition
A function is identified and referenced by its name. It can have zero or more parameter. _Expr_ functions also support optional parameters and passing paramters by name.
An expr-function is identified and referenced by its name. It can have zero or more parameter. expr-functions also support optional parameters and passing paramters by name.
.Expr's function definition syntax
====
*_function-definition_* = _identifier_ "**=**" "**func(**" [_formal-param-list_] "**)**" "**{**" _multi-expression_ "**}**" +
_formal-param_list_ = _required-param-list_ [ "**,**" _optional-param-list_ ] +
_formal-param-list_ = _required-param-list_ [ "**,**" _optional-param-list_ ] +
_required-param-list_ = _identifier_ { "**,**" _identifier_ } +
_optional-param-list_ = _optional-parm_ { "**,**" _optional-param_ } +
_optional-param_ = _param-name_ "**=**" _any-expr_ +
@@ -818,7 +902,7 @@ _param-name_ = _identifier_
`>>>` [blue]`sum = func(a, b){ a + b }` +
[green]`sum(a, b):any{}`
^(\*)^ Since the plus, *+*, operator is defined for multiple data-types, the _sum()_ function can be used for any pair of that types.
^(*)^ Since the plus, **+**, operator is defined for multiple data-types, the _sum()_ function can be used for any pair of that types.
`>>>` [gray]_// A more complex example: recursive calculation of the n-th value of Fibonacci's sequence_ +
`>>>` [blue]`fib = func(n){ n ? [0] {0}: [1] {1} :: {fib(n-1)+fib(n-2)} }` +
@@ -842,7 +926,7 @@ _param-name_ = _identifier_
=== _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
To call a function, either Expr or Golang type, it is necessary to specify its name and, at least, its required parameters.
@@ -919,9 +1003,9 @@ _param-name_ = _identifier_
=== Function context
Functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the _clone_ modifier [blue]`@` it is possibile to export local definition to the calling context. The clone modifier must be used as prefix to variable names and it is part of the name. E.g. [blue]`@x` is not the same as [blue]`x`; they are two different and un related variables.
Functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the _clone_ modifier [blue]`@` it is possibile to export local definition to the calling context. The clone modifier must be used as prefix to variable names and it is part of the name. E.g. [blue]`@x` is not the same as [blue]`x`; they are two different and unrelated variables.
Clone variables are normal local variables. The only diffence will appear when the defining function terminate, just before the destruction of its local context. At that point, all local clone variables are cloned in the calling context with the same names but the [blue]`@` symbol.
Clone variables are normal local variables. The only diffence will appear when the defining function ends, just before the destruction of its local context. At that point, all local clone variables are cloned in the calling context with the same names but the [blue]`@` symbol.
.Example
`>>>` [blue]`f = func() { @x = 3; x = 5 }` [gray]_// f() declares two *different* local variables: ``@x`` and ``x``_ +
@@ -938,24 +1022,537 @@ NOTE: The clone modifier [blue]`@` does not make a variable a reference variable
The clone modifier can also be used to declare the formal parameters of functions, because they are local variables too.
.Example
`>>>` [blue]`g = func(@p) {2+@p}`
g(@p):any{}`
`>>>` [blue]`g(9)`
11`
`>>>` [blue]`p
9
`>>>` [blue]`g = func(@p) {2+@p}` +
[green]`g(@p):any{}` +
`>>>` [blue]`g(9)` +
[green]`11` +
`>>>` [blue]`p` +
[green]`9`
====
== Iterators
#TODO: function calls operations#
== Builtins
#TODO: builtins#
Builtins are collection of function dedicated to specific domains of application. They are defined in Golang source files called _modules_ and compiled within the _Expr_ package. To make builtins available in _Expr_ contextes, it is required to activate the builtin module in which they are defined.
=== Builtin functions
There are currently several builtin modules. More builtin modules will be added in the future.
.Available builtin modules
* *base*: Base expression tools like isNil(), int(), etc.
* *fmt*: String and console formatting functions
* *import*: Functions import() and include()
* *iterator*: Iterator helper functions
* *math.arith*: Functions add() and mul()
* *os.file*: Operating system file functions
* *string*: string utilities
Builtins activation is done by using the [blue]`BUILTIN` operator. All modules except "base" must be explicitly enabled. The syntax is as follows.
.Builtin activation syntax
====
*_builtin-activation_* = [blue]`BUILTIN` (_builtin-name_ | _list-of-builtin-names_) +
_builtin-name_ = _string_ +
_list-of-builtin-names_ = **[** _string_ { "**,**" _string_ } **]**
====
The following example shows how to activate the builtin module "math.arith" and then use the function add() defined in that module.
.Example: using built functions
`>>>` [blue]`BUILTIN "math.arith"` +
[green]`1` +
`>>>` [blue]`add(5, 3, -2)` +
[green]`6`
TIP: To avoid the need to activate builtin modules one by one, it is possible to activate all builtin modules at once by using the [blue]`BUILTIN "*"` syntax.
=== Builtin modules
==== Module "base"
The "base" builtin module provides functions for type checking and type conversion. These functions are always available in _Expr_ contexts without the need to activate the "base" module.
.Checking functions
* <<_isbool,isBool()>>
* <<_isdict,isDict()>>
* <<_isfloat,isFloat()>>
* <<_isfract,isFract()>>
* <<_islist,isList()>>
* <<_isnil,isNil()>>
* <<_isrational,isRational()>>
* <<_isstring,isString()>>
.Conversion functions
* <<_bool,bool()>>
* <<_int,int()>>
* <<_dec,dec()>>
* <<_string,string()>>
* <<_fract,fract()>>
.Other functions
* <<_eval,eval()>>
* <<_var,var()>>
===== isBool()
Syntax: `isBool(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is boolean, false otherwise.
.Examples
>>> isBool(true) +
true +
>>> isBool(3==2) +
true
===== isDict()
Syntax: `isDict(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is dictionary, false otherwise.
.Examples
>>> isDict({}) +
true +
>>> isDict({1: "one", 2: "two"}) +
true +
>>> isDict(1:"one") +
Eval Error: denominator must be integer, got string (one)
===== isFloat()
Syntax: `isFloat(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is float, false otherwise.
.Examples
>>> isFloat(4.) +
true +
>>> isFloat(4) +
false +
>>> isFloat("2.1") +
false
===== isFract()
Syntax: `isFract(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is fraction, false otherwise.
.Examples
>>> isFract(4.5) +
false +
>>> isFract(4:5) +
true +
>>> isFract(4) +
**false** +
>>> isFract(1.(3)) +
true
===== isList()
Syntax: `isList(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is list, false otherwise.
.Examples
>>> isList([]) +
true +
>>> isList([1, "2"])
true
>>> isList(1,2)
Eval Error: isList(): too many params -- expected 1, got 2
===== isNil()
Syntax: `isNil(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is nil, false otherwise.
.Examples
>>> isNil(nil)
true
>>> isNil(1)
false
===== isRational()
Syntax: `isRational(<expr>) -> bool` +
Returns _true_ if the value type of _<expr>_ is fraction or int, false otherwise.
.Examples
>>> isRational(4.5) +
false +
>>> isRational(4:5) +
true +
>>> isRational(4) +
**true** +
>>> isRational(1.(3)) +
true
===== isString()
Syntax: `isString(<expr>) -> bool` +
Returns a boolean value , false otherwise.
.Examples
>>> isString("ciao") +
true +
>>> isString(2) +
false +
>>> isString(2+"2") +
true
===== bool()
Syntax: `bool(<expr>) -> bool` +
Returns a _boolean_ value consisent to the value of the expression.
.Examples
>>> bool(1)
true
>>> bool(0)
false
>>> bool("")
false
>>> bool([])
false
>>> bool([1])
true
>>> bool({})
false
>>> bool({1: "one"})
true
===== int()
Syntax: `int(<expr>) -> int` +
Returns an _integer_ value consistent with the value of the expression.
.Examples
>>> int(2) +
2 +
>>> int("2") +
2 +
>>> int("0x1") +
Eval Error: strconv.Atoi: parsing "0x1": invalid syntax +
>>> int(0b10) +
2 +
>>> int(0o2) +
2 +
>>> int(0x2) +
2 +
>>> int(1.8) +
1 +
>>> int(5:2) +
2 +
>>> int([]) +
Eval Error: int(): can't convert list to int+
>>> int(true) +
1 +
>>> int(false) +
0
===== dec()
Syntax: `dec(<expr>) -> float` +
Returns a _float_ value consistent with the value of the expression.
.Examples
>>> dec(2) +
2 +
>>> dec(2.1) +
2.1 +
>>> dec(2.3(1)) +
2.311111111111111 +
>>> dec("3.14") +
3.14 +
>>> dec(3:4) +
0.75 +
>>> dec(true) +
1 +
>>> dec(false) +
0
===== string()
Syntax: `string(<expr>) -> string` +
Returns a _string_ value consistent with the value of the expression.
.Examples
>>> string(2) +
"2" +
>>> string(0.8) +
"0.8" +
>>> string([1,2]) +
"[1, 2]" +
>>> string({1: "one", 2: "two"}) +
"{1: "one", 2: "two"}" +
>>> string(2:5) +
"2:5" +
>>> string(3==2) +
"false"
===== fract()
Syntax: `fract(<expr>) -> fraction` +
Returns a _fraction_ value consistent with the value of the expression.
.Examples
>>> fract(2) +
2:1 +
>>> fract(2.5) +
5:2 +
>>> fract("2.5") +
5:2 +
>>> fract(1.(3)) +
4:3 +
>>> fract([2]) +
Eval Error: fract(): can't convert list to float +
>>> fract(false) +
0:1 +
>>> fract(true) +
1:1
===== eval()
Syntax: `eval(<string-expr>) -> any` +
Computes and returns the value of the [.underline]#string# expression.
.Examples
>>> eval( "2 + fract(1.(3))" ) +
10:3
===== var()
Syntax: +
`{4sp}var(<string-expr>, <expr>) -> any` +
`{4sp}var(<string-expr>) -> any`
This function allows you to define variables whose names must include special characters. The first form of the function allows you to define a variable with a name specified by the first parameter and assign it the value of the second parameter. The second form only returns the value of the variable with the specified name.
.Examples
>>> var("$x", 3+9) +
12 +
>>> var("$x") +
12 +
>>> var("gain%", var("$x")) +
12 +
>>> var("gain%", var("$x")+1) +
13
==== Module "fmt"
===== print()
===== println()
==== Module "import"
===== _import()_
[blue]_import([grey]#<source-file>#)_ -- loads the multi-expression contained in the specified source and returns its value.
===== _importAll()_
==== Module "iterator"
===== run()
==== Module "math.arith"
Currently, the "math.arith" module provides two functions, add() and mul(), that perform addition and multiplication of an arbitrary number of parameters. More functions will be added in the future.
===== add()
Syntax: +
`{4sp}add(<num-expr1>, <num-expr2>, ...) -> any` +
`{4sp}add(<list-of-num-expr>]) -> any` +
`{4sp}add(<iterator-over-num-values>) -> any`
Returns the sum of the values of the parameters. The parameters can be of any numeric type for which the [blue]`+` operator is defined. The result type depends on the types of the parameters. If all parameters are of the same type, the result is of that type. If the parameters are of different types, the result is of the type that can represent all the parameter types without loss of information. For example, if the parameters are a mix of integers and floats, the result is a float. If the parameters are a mix of number types, the result has the type of the most general one.
.Examples
>>> add(1,2,3) +
6
>>> add(1.1,2.1,3.1) +
6.300000000000001 +
>>> add("1","2","3") +
Eval Error: add(): param nr 1 (1 in 0) has wrong type string, number expected +
>>> add(1:3, 2:3, 3:3) +
2:1 +
>>> add(1, "2") +
Eval Error: add(): param nr 2 (2 in 0) has wrong type string, number expected +
>>> add([1,2,3]) +
6 +
>>> iterator=$([1,2,3]); add(iterator) +
6 +
>>> add($([1,2,3])) +
6
===== mul()
Syntax: +
`{4sp}mul(<num-expr1>, <num-expr2>, ...) -> any` +
`{4sp}mul(<list-of-num-expr>]) -> any` +
`{4sp}mul(<iterator-over-num-values>) -> any`
Same as add() but returns the product of the values of the parameters.
==== Module "os.file"
===== fileOpen()
===== fileAppend()
===== fileCreate()
===== fileClose()
===== fileWriteText()
===== fileReadText()
===== fileReadTextAll()
==== Module "string"
===== strJoin()
===== strSub()
===== strSplit()
===== strTrim()
===== strStartsWith()
===== strEndsWith()
===== strUpper()
===== strLower()
== Iterators
Iterators are objects that can be used to traverse collections, such as lists and dictionaries. They are created by providing a _data-source_ object, the collection, in a `$(<data-source>)` expression. Once an iterator is created, it can be used to access the elements of the collection one by one.
In general, data-sources are objects that can be iterated over. They are defined as dictionaries having the key `next` whose value is a function that returns the next element of the collection and updates the state of the iterator. The _next_ function must return a special value, [blue]_nil_, when there are no more elements to iterate over.
Lists and, soon, dictionaries, are implicit data-sources. The syntax for creating an iterator is as follows.
.Iterator creation syntax
====
*_iterator_* = "**$(**" _data-source_ "**)**" +
_data-source_ = _explicit_ | _list-spec_ | _dict-spec_ | _custom-data-source_
_explicit_ = _any-expr_ { "," _any-expr_ }
_list-spec_ = _list_ _range-options_ +
_list_ = "**[**" _any-expr_ { "," _any-expr_ } "**]**" +
_range-options_ = [ "," _start-index_ [ "," _end-index_ [ "," _step_ ]]] +
_start-index_, _end-index_, _step_ = _integer-expr_
_dict-spec_ = _dict_ _dict-options_ +
_dict_ = "**{**" _key-value-pair_ { "," _key-value-pair_ } "**}**" +
_key-value-pair_ = _scalar-value_ ":" _any-expr_ +
_scalar-value_ = _string_ | _number_ | _boolean_ +
_dict-options_ = [ "," _sort-order_ [ "," _iter-mode_ ] ] +
_sort-order_ = _asc-order_ | _desc-order_ | _no-sort_ | _default-order_ +
_asc-order_ = "**asc**" | "**a**" +
_desc-order_ = "**desc**" | "**d**" +
_no-sort_ = "**none**" | "**nosort**" | "**no-sort**" | "**n**" +
_default-order_ = "**default**" | "" +
_iter-mode_ = _keys-iter_ | _values-iter_ | _items-iter_ | _default-iter_ +
_keys-iter_ = "**key**" | "**keys**" | "**k**" +
_values-iter_ = "**value**" | "**values**" | "**v**" +
_items-iter_ = "**item**" | "**items**" | "**i**" +
_default-iter_ = "**default**" | ""
_custom-data-source_ = _dict_ having the key `next` whose value is a function that returns the next element of the collection and updates the state of the iterator.
====
NOTE: Currently, _default-order_ is the same as _asc-order_. In the future, it will be possible to specify a custom sorting function to define the default order.
NOTE: Currently, _default-iter_ is the same as _keys-iter_. In the future, it will be possible to specify a custom iteration function to define the default iteration mode.
.Example: iterator over an explicit list of elements
`>>>` [blue]`it = $("A", "B", "C")` +
[green]`$(#3)` +
`>>>` [blue]`it{plusplus}` +
[green]`"A"` +
`>>>` [blue]`it{plusplus}` +
[green]`"B"` +
`>>>` [blue]`it{plusplus}` +
[green]`"C"` +
`>>>` [blue]`it{plusplus}` +
[red]`Eval Error: EOF`
.Example: iterator over a list
`>>>` [blue]`it = $(["one", "two", "three"])` +
[green]`$(#3)` +
`>>>` [blue]`it{plusplus}` +
[green]`"one"` +
`>>>` [blue]`it{plusplus}` +
[green]`"two"` +
`>>>` [blue]`it{plusplus}` +
[green]`"three"` +
`>>>` [blue]`it{plusplus}` +
[red]`Eval Error: EOF`
On a list-type iterator creation expression, it is possible to specify an index range and a step to iterate over a subset of the list.
Indexing starts at 0. If the start index is not specified, it defaults to 0. If the end index is not specified, it defaults to the index of the last element of the list. If the step is not specified, it defaults to 1.
Negative indexes are allowed. They are interpreted as offsets from the end of the list. For example, an end index of -1 means the index of the last element of the list, an end index of -2 means the index of the second to last element of the list, and so on.
Negative steps are also allowed. They are interpreted as reverse iteration. For example, a step of -1 means to iterate over the list in reverse order.
.Example: iterator over a list with index range and step
`>>>` [blue]`it = $([1, 2, 3, 4, 5], 1, 4, 2)` +
[green]`$(#5)` +
`>>>` [blue]`it{plusplus}` +
[green]`2` +
`>>>` [blue]`it{plusplus}` +
[green]`4` +
`>>>` [blue]`it{plusplus}` +
[red]`Eval Error: EOF`
.Example: iterator over a list in reverse order
`>>>` [blue]`it = $([1, 2, 3], -1, 0, -1)` +
[green]`$(#3)` +
`>>>` [blue]`it{plusplus}` +
[green]`3` +
`>>>` [blue]`it{plusplus}` +
[green]`2` +
`>>>` [blue]`it{plusplus}` +
[green]`1` +
`>>>` [blue]`it{plusplus}` +
[red]`Eval Error: EOF`
=== Operators on iterators
The above example shows the use of the [blue]`{plusplus}` operator to get the next element of an iterator. The [blue]`{plusplus}` operator is a postfix operator that can be used with iterators. It returns the next element of the collection and updates the state of the iterator. When there are no more elements to iterate over, it returns the error [red]_Eval Error: EOF_.
After the first use of the [blue]`{plusplus}` operator, the prefixed operato [blue]`{star}` operator can be used to get the current element of the collection without updating the state of the iterator. When there are no more elements to iterate over, it returns the error [red]_Eval Error: EOF_. Same error is returned if the [blue]`{star}` operator is used before the first use of the [blue]`{plusplus}` operator.
.Example: using the [blue]`{star}` operator
`>>>` [blue]`it = $(["one", "two", "three"])` +
[green]`$(#3)` +
`>>>` [blue]`{star}it` +
[red]`Eval Error: EOF` +
`>>>` [blue]`it{plusplus}` +
[green]`"one"` +
`>>>` [blue]`{star}it` +
[green]`"one"`
==== Named operators
Named operators are operators that are identified by a name instead of a symbol. They are implicitly defined and can be called using a special syntax. For example, the [blue]`{plusplus}` has the equivalent named operator [blue]`.next`.
.Available named operators
* *_.next_*: same as [blue]`{plusplus}`.
* *_.current_*: same as [blue]`{star}`.
* *_.reset_*: resets the state of the iterator to the initial state.
* *_.count_*: returns the number of elements in the iterator already visited.
* *_.index_*: returns the index of the current element in the iterator. Before the first use of the [blue]`{plusplus}` operator, it returns the error [red]_-1_.
TIP: Iterators built on custom data-sources can provide additional named operators, depending on the functionality they want to expose. For example, an iterator over a list could provide a named operator called [blue]`.reverse` that returns the next element of the collection in reverse order.
.Example: using the named operators
`>>>` [blue]`it = $(["one", "two", "three"])` +
[green]`$(#3)` +
`>>>` [blue]`it.next` +
[green]`"one"` +
`>>>` [blue]`it.current` +
[green]`"one"` +
`>>>` [blue]`it.next` +
[green]`"two"` +
`>>>` [blue]`it.reset` +
[green]`<nil>` +
`>>>` [blue]`it.current` +
[red]`Eval Error: EOF` +
`>>>` [blue]`it.next` +
[green]`"one"`
=== Iterator over custom data-sources
It is possible to create iterators over custom data-sources by defining a dictionary that has the key `next` whose value is a function that returns the next element of the collection and updates the state of the iterator. The syntax for creating an iterator over a custom data-source is as follows.
#TODO: custom data-sources#
=== [blue]_import()_
[blue]_import([grey]#<source-file>#)_ loads the multi-expression contained in the specified source and returns its value.
== Plugins
+1089 -72
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -21,6 +21,8 @@ const (
CountName = "count"
FilterName = "filter"
MapName = "map"
KeyName = "key"
ValueName = "value"
)
type Iterator interface {
@@ -29,14 +31,14 @@ type Iterator interface {
Current() (item any, err error)
Index() int
Count() int
HasOperation(name string) bool
CallOperation(name string, args map[string]any) (value any, err error)
}
type ExtIterator interface {
Iterator
Reset() error
Clean() error
HasOperation(name string) bool
CallOperation(name string, args map[string]any) (value any, err error)
}
func errNoOperation(name string) error {
+2 -2
View File
@@ -149,12 +149,12 @@ func (it *ListIterator) Count() int {
return it.count
}
func (it *ListIterator) Reset() (error) {
func (it *ListIterator) Reset() error {
it.index = it.start - it.step
it.count = 0
return nil
}
func (it *ListIterator) Clean() (error) {
func (it *ListIterator) Clean() error {
return nil
}
+32 -20
View File
@@ -86,37 +86,49 @@ func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
return
}
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil {
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil && ds == nil {
return
}
err = nil
if ds != nil {
var dc *dataCursor
dcCtx := ctx.Clone()
if initFunc, exists := ds[InitName]; exists && initFunc != nil {
var args []any
var resource any
if len(opTerm.children) > 1 {
if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil {
if len(ds) > 0 {
var dc *dataCursor
dcCtx := ctx.Clone()
if initFunc, exists := ds[InitName]; exists && initFunc != nil {
var args []any
var resource any
if len(opTerm.children) > 1 {
if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil {
return
}
} else {
args = []any{}
}
actualParams := bindActualParams(initFunc, args)
initCtx := ctx.Clone()
if resource, err = initFunc.InvokeNamed(initCtx, InitName, actualParams); err != nil {
return
}
exportObjects(dcCtx, initCtx)
dc = NewDataCursor(dcCtx, ds, resource)
} else {
args = []any{}
dc = NewDataCursor(dcCtx, ds, nil)
}
actualParams := bindActualParams(initFunc, args)
initCtx := ctx.Clone()
if resource, err = initFunc.InvokeNamed(initCtx, InitName, actualParams); err != nil {
return
}
exportObjects(dcCtx, initCtx)
dc = NewDataCursor(dcCtx, ds, resource)
v = dc
} else {
dc = NewDataCursor(dcCtx, ds, nil)
if dictIt, ok := firstChildValue.(*DictType); ok {
var args []any
if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
v, err = NewDictIterator(dictIt, args)
}
} else {
err = opTerm.children[0].Errorf("the data-source must be a dictionary")
}
}
v = dc
} else if list, ok := firstChildValue.(*ListType); ok {
var args []any
if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
+16
View File
@@ -183,6 +183,16 @@ func evalOpAssign(ctx ExprContext, opTerm *term) (v any, err error) {
v, err = divValues(opTerm, leftValue, rightValue)
case SymPercEqual:
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:
err = opTerm.Errorf("unsupported assign operator %q", opTerm.source())
}
@@ -201,4 +211,10 @@ func init() {
registerTermConstructor(SymMinusEqual, newOpAssignTerm)
registerTermConstructor(SymStarEqual, 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: priBinNot,
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: priBinAnd,
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: priBinOr,
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
import (
"errors"
"fmt"
)
@@ -41,7 +40,7 @@ func evalFraction(ctx ExprContext, opTerm *term) (v any, err error) {
return
}
if den == 0 {
err = errors.New("division by zero")
err = opTerm.errDivisionByZero()
return
}
+3 -3
View File
@@ -11,7 +11,7 @@ func newIterValueTerm(tk *Token) (inst *term) {
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priIterValue,
priority: priDereference,
evalFunc: evalIterValue,
}
}
@@ -33,6 +33,6 @@ func evalIterValue(ctx ExprContext, opTerm *term) (v any, err error) {
// init
func init() {
// registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
registerTermConstructor(SymCaret, newIterValueTerm)
// registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
registerTermConstructor(SymDereference, newIterValueTerm)
}
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-post-inc.go
// operator-post-inc-dec.go
package expr
// -------- post increment term
@@ -24,7 +24,27 @@ func evalPostInc(ctx ExprContext, opTerm *term) (v any, err error) {
}
if it, ok := childValue.(Iterator); ok {
var namePrefix string
v, err = it.Next()
if opTerm.children[0].symbol() == SymVariable {
namePrefix = opTerm.children[0].source()
}
ctx.UnsafeSetVar(namePrefix+"_index", it.Index())
if c, err1 := it.Current(); err1 == nil {
ctx.UnsafeSetVar(namePrefix+"_current", c)
}
if it.HasOperation(KeyName) {
if k, err1 := it.CallOperation(KeyName, nil); err1 == nil {
ctx.UnsafeSetVar(namePrefix+"_key", k)
}
}
if it.HasOperation(ValueName) {
if v1, err1 := it.CallOperation(ValueName, nil); err1 == nil {
ctx.UnsafeSetVar(namePrefix+"_value", v1)
}
}
} else if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
v = childValue
i, _ := childValue.(int64)
+69
View File
@@ -0,0 +1,69 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-pre-inc-dec.go
package expr
// -------- pre increment term
func newPreIncTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priIncDec,
evalFunc: evalPreInc,
}
}
func evalPreInc(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
i := childValue.(int64) + 1
ctx.SetVar(opTerm.children[0].source(), i)
v = i
} else {
err = opTerm.errIncompatibleType(childValue)
}
return
}
// -------- pre decrement term
func newPreDecTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priIncDec,
evalFunc: evalPreDec,
}
}
func evalPreDec(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
i := childValue.(int64) - 1
ctx.SetVar(opTerm.children[0].source(), i)
v = i
} else {
err = opTerm.errIncompatibleType(childValue)
}
return
}
// init
func init() {
registerTermConstructor(SymPreInc, newPreIncTerm)
registerTermConstructor(SymPreDec, newPreDecTerm)
}
+29 -5
View File
@@ -5,7 +5,6 @@
package expr
import (
"errors"
"strings"
)
@@ -69,7 +68,7 @@ func divValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
if IsFloat(leftValue) || IsFloat(rightValue) {
d := numAsFloat(rightValue)
if d == 0.0 {
err = errors.New("division by zero")
err = opTerm.errDivisionByZero()
} else {
v = numAsFloat(leftValue) / d
}
@@ -78,11 +77,36 @@ func divValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
} else {
leftInt, _ := leftValue.(int64)
if rightInt, _ := rightValue.(int64); rightInt == 0 {
err = errors.New("division by zero")
err = opTerm.errDivisionByZero()
} else {
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 {
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
@@ -121,7 +145,7 @@ func evalDivideAsFloat(ctx ExprContext, floatDivTerm *term) (v any, err error) {
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
d := numAsFloat(rightValue)
if d == 0.0 {
err = errors.New("division by zero")
err = floatDivTerm.errDivisionByZero()
} else {
v = numAsFloat(leftValue) / d
}
@@ -146,7 +170,7 @@ func remainderValues(opTerm *term, leftValue, rightValue any) (v any, err error)
if IsInteger(leftValue) && IsInteger(rightValue) {
rightInt, _ := rightValue.(int64)
if rightInt == 0 {
err = errors.New("division by zero")
err = opTerm.errDivisionByZero()
} else {
leftInt, _ := leftValue.(int64)
v = leftInt % rightInt
+22 -30
View File
@@ -4,7 +4,7 @@
// operator-shift.go
package expr
//-------- shift term
//-------- bit right shift term
func newRightShiftTerm(tk *Token) (inst *term) {
return &term{
@@ -16,13 +16,7 @@ func newRightShiftTerm(tk *Token) (inst *term) {
}
}
func evalRightShift(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
func bitRightShift(opTerm *term, leftValue, rightValue any) (v any, err error) {
if IsInteger(leftValue) && IsInteger(rightValue) {
leftInt := leftValue.(int64)
rightInt := rightValue.(int64)
@@ -33,6 +27,17 @@ func evalRightShift(ctx ExprContext, opTerm *term) (v any, err error) {
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,
@@ -43,13 +48,7 @@ func newLeftShiftTerm(tk *Token) (inst *term) {
}
}
func evalLeftShift(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
func bitLeftShift(opTerm *term, leftValue, rightValue any) (v any, err error) {
if IsInteger(leftValue) && IsInteger(rightValue) {
leftInt := leftValue.(int64)
rightInt := rightValue.(int64)
@@ -60,23 +59,16 @@ func evalLeftShift(ctx ExprContext, opTerm *term) (v any, err error) {
return
}
// func evalAssignAppend(ctx ExprContext, self *term) (v any, err error) {
// var leftValue, rightValue any
func evalLeftShift(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
// return
// }
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
// if IsList(leftValue) {
// list, _ := leftValue.(*ListType)
// newList := append(*list, rightValue)
// v = &newList
// if
// } else {
// err = self.errIncompatibleTypes(leftValue, rightValue)
// }
// return
// }
v, err = bitLeftShift(opTerm, leftValue, rightValue)
return
}
// init
func init() {
+38 -19
View File
@@ -409,6 +409,23 @@ func listSubTree(tree *ast, listTerm *term, allowIndeces bool) (root *term, err
return
}
func changePrefix(tk *Token) {
switch tk.Sym {
case SymMinus:
tk.SetSymbol(SymChangeSign)
case SymPlus:
tk.SetSymbol(SymUnchangeSign)
case SymStar:
tk.SetSymbol(SymDereference)
case SymExclamation:
tk.SetSymbol(SymNot)
case SymDoublePlus:
tk.SetSymbol(SymPreInc)
case SymDoubleMinus:
tk.SetSymbol(SymPreDec)
}
}
func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymbols ...Symbol) (tree *ast, err error) {
var selectorTerm *term = nil
var currentTerm *term = nil
@@ -437,14 +454,16 @@ func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymb
//fmt.Println("Token:", tk)
if firstToken {
if tk.Sym == SymMinus {
tk.Sym = SymChangeSign
} else if tk.Sym == SymPlus {
tk.Sym = SymUnchangeSign
} else if tk.IsSymbol(SymExclamation) {
err = tk.Errorf("postfix opertor %q requires an operand on its left", tk)
break
}
changePrefix(tk)
// if tk.Sym == SymMinus {
// tk.Sym = SymChangeSign
// } else if tk.Sym == SymPlus {
// tk.Sym = SymUnchangeSign
// } else if tk.IsSymbol(SymStar) {
// tk.SetSymbol(SymDereference)
// } else if tk.IsSymbol(SymExclamation) {
// tk.SetSymbol(SymNot)
// }
firstToken = false
}
@@ -452,9 +471,12 @@ func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymb
case SymOpenRound:
var subTree *ast
if subTree, err = parser.parseGeneral(scanner, ctx, SymClosedRound); err == nil {
subTree.root.priority = priValue
err = tree.addTerm(newExprTerm(subTree.root))
currentTerm = subTree.root
exprTerm := newExprTerm(subTree.root)
err = tree.addTerm(exprTerm)
currentTerm = exprTerm
// subTree.root.priority = priValue
// err = tree.addTerm(newExprTerm(subTree.root))
// currentTerm = subTree.root
}
case SymFuncCall:
var funcCallTerm *term
@@ -478,7 +500,7 @@ func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymb
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)
firstToken = true
case SymFuncDef:
@@ -515,14 +537,11 @@ func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymb
}
}
} else {
// if hasFlag(ctx, allowIndex) {
// tk.Sym = SymRange
// }
currentTerm, err = tree.addToken(tk)
}
if tk.IsOneOfA(SymColon, SymRange) {
// Colon outside a selector term acts like a separator
firstToken = true
if tk.IsOneOfA(SymColon, SymRange) {
// Colon outside a selector term acts like a separator
firstToken = true
}
}
default:
currentTerm, err = tree.addToken(tk)
+38 -5
View File
@@ -169,13 +169,19 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
case '|':
if next, _ := scanner.peek(); next == '|' {
tk = scanner.moveOn(SymDoubleVertBar, ch, next)
} else if next, _ = scanner.peek(); next == '=' {
tk = scanner.moveOn(SymVertBarEqual, ch, next)
} else {
tk = scanner.makeToken(SymVertBar, ch)
}
case ',':
tk = scanner.makeToken(SymComma, ch)
case '^':
tk = scanner.makeToken(SymCaret, ch)
if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymCaretEqual, ch, next)
} else {
tk = scanner.makeToken(SymCaret, ch)
}
case ':':
if next, _ := scanner.peek(); next == ':' {
tk = scanner.moveOn(SymDoubleColon, ch, next)
@@ -234,11 +240,17 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
case '&':
if next, _ := scanner.peek(); next == '&' {
tk = scanner.moveOn(SymDoubleAmpersand, ch, next)
} else if next, _ = scanner.peek(); next == '=' {
tk = scanner.moveOn(SymAmpersandEqual, ch, next)
} else {
tk = scanner.makeToken(SymAmpersand, ch)
}
case '%':
tk = scanner.makeToken(SymPercent, ch)
if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymPercEqual, ch, next)
} else {
tk = scanner.makeToken(SymPercent, ch)
}
case '#':
tk = scanner.makeToken(SymHash, ch)
case '@':
@@ -267,7 +279,14 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymLessOrEqual, ch, next)
} else if next == '<' {
tk = scanner.moveOn(SymDoubleLess, 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 == '>' {
tk = scanner.moveOn(SymLessGreater, ch, next)
} else if next == '+' {
@@ -279,7 +298,14 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymGreaterOrEqual, ch, next)
} else if next == '>' {
tk = scanner.moveOn(SymDoubleGreater, 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 {
tk = scanner.makeToken(SymGreater, ch)
}
@@ -634,9 +660,16 @@ func (scanner *scanner) translate(sym Symbol) Symbol {
func (scanner *scanner) moveOn(sym Symbol, chars ...byte) (tk *Token) {
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()
}
// }
return
}
func (scanner *scanner) accept(sym Symbol, chars ...byte) (tk *Token) {
tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), string(chars))
return
}
+5
View File
@@ -24,6 +24,11 @@ func NewSimpleStore() *SimpleStore {
return ctx
}
func (ss *SimpleStore) Init() {
ss.varStore = make(map[string]any)
ss.funcStore = make(map[string]ExprFunc)
}
func filterRefName(name string) bool { return name[0] != '@' }
//func filterPrivName(name string) bool { return name[0] != '_' }
+99 -84
View File
@@ -14,7 +14,7 @@ type symbolClass int16
const (
symClassOperator symbolClass = iota
symClassPostOp
symClassCommand
symClassIdentifier
symClassDelimiter
symClassParenthesis
@@ -24,79 +24,87 @@ const (
)
type symbolSpec struct {
repr string
kind symbolClass
repr string
kind symbolClass
opType termPosition
}
func init() {
symbolMap = map[Symbol]symbolSpec{
SymUnknown: {"<unknown>", symClassOther}, // -1: Unknown symbol
SymNone: {"<null>", symClassOther}, // 0: Null value for variable of type symbol
SymError: {"<error>", symClassOther}, // 1: Error reading from stream
SymEos: {"<eos>", symClassOther}, // 2: End of stream
SymMinus: {"-", symClassOperator}, // 3: '-'
SymMinusEqual: {"-=", symClassOperator}, // 4: '-='
SymDoubleMinus: {"--", symClassOperator}, // 5: '--'
SymPlus: {"+", symClassOperator}, // 6: '+'
SymPlusEqual: {"+=", symClassOperator}, // 7: '+='
SymDoublePlus: {"++", symClassOperator}, // 8: '++'
SymStar: {"*", symClassOperator}, // 9: '*'
SymDoubleStar: {"**", symClassOperator}, // 10: '**'
SymSlash: {"/", symClassOperator}, // 11: '/'
SymBackSlash: {"\\", symClassOperator}, // 12: '\'
SymVertBar: {"|", symClassOperator}, // 13: '|'
SymDoubleVertBar: {"||", symClassOperator}, // 14: '||'
SymComma: {",", symClassOperator}, // 15: ','
SymColon: {":", symClassOperator}, // 16: ':'
SymSemiColon: {";", symClassOperator}, // 17: ';'
SymDot: {".", symClassOperator}, // 18: '.'
SymDotSlash: {"./", symClassOperator}, // 19: './'
SymQuote: {"'", symClassDelimiter}, // 20: '\''
SymDoubleQuote: {"\"", symClassDelimiter}, // 21: '"'
SymBackTick: {"`", symClassOperator}, // 22: '`'
SymExclamation: {"!", symClassPostOp}, // 23: '!'
SymQuestion: {"?", symClassOperator}, // 24: '?'
SymAmpersand: {"&", symClassOperator}, // 25: '&'
SymDoubleAmpersand: {"&&", symClassOperator}, // 26: '&&'
SymPercent: {"%", symClassOperator}, // 27: '%'
SymAt: {"@", symClassOperator}, // 28: '@'
SymUndescore: {"_", symClassOperator}, // 29: '_'
SymEqual: {"=", symClassOperator}, // 30: '='
SymDoubleEqual: {"==", symClassOperator}, // 31: '=='
SymLess: {"<", symClassOperator}, // 32: '<'
SymLessOrEqual: {"<=", symClassOperator}, // 33: '<='
SymGreater: {">", symClassOperator}, // 34: '>'
SymGreaterOrEqual: {">=", symClassOperator}, // 35: '>='
SymLessGreater: {"<>", symClassOperator}, // 36: '<>'
SymNotEqual: {"!=", symClassOperator}, // 37: '!='
SymDollar: {"$", symClassOperator}, // 38: '$'
SymHash: {"#", symClassOperator}, // 39: '#'
SymOpenRound: {"(", symClassParenthesis}, // 40: '('
SymClosedRound: {")", symClassParenthesis}, // 41: ')'
SymOpenSquare: {"[", symClassParenthesis}, // 42: '['
SymClosedSquare: {"]", symClassParenthesis}, // 43: ']'
SymOpenBrace: {"{", symClassParenthesis}, // 44: '{'
SymClosedBrace: {"}", symClassParenthesis}, // 45: '}'
SymTilde: {"~", symClassOperator}, // 46: '~'
SymDoubleQuestion: {"??", symClassOperator}, // 47: '??'
SymQuestionEqual: {"?=", symClassOperator}, // 48: '?='
SymQuestionExclam: {"?!", symClassOperator}, // 49: '?!'
SymDoubleAt: {"@@", symClassOperator}, // 50: '@@'
SymDoubleColon: {"::", symClassOperator}, // 51: '::'
SymDoubleGreater: {">>", symClassOperator}, // 52: '>>'
SymDoubleLess: {"<<", symClassOperator}, // 53: '<<'
SymCaret: {"^", symClassOperator}, // 54: '^'
SymDollarRound: {"$(", symClassOperator}, // 55: '$('
SymOpenClosedRound: {"()", symClassPostOp}, // 56: '()'
SymDoubleDollar: {"$$", symClassOperator}, // 57: '$$'
SymDoubleDot: {"..", symClassOperator}, // 58: '..'
SymTripleDot: {"...", symClassOperator}, // 59: '...'
SymStarEqual: {"*=", symClassOperator}, // 60: '*='
SymSlashEqual: {"/=", symClassOperator}, // 61: '/='
SymPercEqual: {"%=", symClassOperator}, // 62: '%='
SymPlusGreater: {"+>", symClassOperator}, // 63: '+>'
SymLessPlus: {"<+", symClassOperator}, // 64: '<+'
SymUnknown: {"<unknown>", symClassOther, posLeaf}, // -1: Unknown symbol
SymNone: {"<null>", symClassOther, posLeaf}, // 0: Null value for variable of type symbol
SymError: {"<error>", symClassOther, posLeaf}, // 1: Error reading from stream
SymEos: {"<eos>", symClassOther, posLeaf}, // 2: End of stream
SymMinus: {"-", symClassOperator, posInfix}, // 3: '-'
SymMinusEqual: {"-=", symClassOperator, posInfix}, // 4: '-='
SymDoubleMinus: {"--", symClassOperator, posPostfix}, // 5: '--'
SymPlus: {"+", symClassOperator, posInfix}, // 6: '+'
SymPlusEqual: {"+=", symClassOperator, posInfix}, // 7: '+='
SymDoublePlus: {"++", symClassOperator, posPostfix}, // 8: '++'
SymStar: {"*", symClassOperator, posInfix}, // 9: '*'
SymDoubleStar: {"**", symClassOperator, posInfix}, // 10: '**'
SymSlash: {"/", symClassOperator, posInfix}, // 11: '/'
SymBackSlash: {"\\", symClassOperator, posLeaf}, // 12: '\'
SymVertBar: {"|", symClassOperator, posInfix}, // 13: '|'
SymDoubleVertBar: {"||", symClassOperator, posInfix}, // 14: '||'
SymComma: {",", symClassOperator, posInfix}, // 15: ','
SymColon: {":", symClassOperator, posInfix}, // 16: ':'
SymSemiColon: {";", symClassOperator, posInfix}, // 17: ';'
SymDot: {".", symClassOperator, posInfix}, // 18: '.'
SymDotSlash: {"./", symClassOperator, posInfix}, // 19: './'
SymQuote: {"'", symClassDelimiter, posLeaf}, // 20: '\''
SymDoubleQuote: {"\"", symClassDelimiter, posLeaf}, // 21: '"'
SymBackTick: {"`", symClassDelimiter, posLeaf}, // 22: '`'
SymExclamation: {"!", symClassOperator, posPostfix}, // 23: '!'
SymQuestion: {"?", symClassOperator, posInfix}, // 24: '?'
SymAmpersand: {"&", symClassOperator, posInfix}, // 25: '&'
SymDoubleAmpersand: {"&&", symClassOperator, posInfix}, // 26: '&&'
SymPercent: {"%", symClassOperator, posInfix}, // 27: '%'
SymAt: {"@", symClassOperator, posPrefix}, // 28: '@'
SymUndescore: {"_", symClassIdentifier, posLeaf}, // 29: '_'
SymEqual: {"=", symClassOperator, posInfix}, // 30: '='
SymDoubleEqual: {"==", symClassOperator, posInfix}, // 31: '=='
SymLess: {"<", symClassOperator, posInfix}, // 32: '<'
SymLessOrEqual: {"<=", symClassOperator, posInfix}, // 33: '<='
SymGreater: {">", symClassOperator, posInfix}, // 34: '>'
SymGreaterOrEqual: {">=", symClassOperator, posInfix}, // 35: '>='
SymLessGreater: {"<>", symClassOperator, posInfix}, // 36: '<>'
SymNotEqual: {"!=", symClassOperator, posInfix}, // 37: '!='
SymDollar: {"$", symClassOperator, posPrefix}, // 38: '$'
SymHash: {"#", symClassOperator, posPrefix}, // 39: '#'
SymOpenRound: {"(", symClassParenthesis, posPrefix}, // 40: '('
SymClosedRound: {")", symClassParenthesis, posPostfix}, // 41: ')'
SymOpenSquare: {"[", symClassParenthesis, posPrefix}, // 42: '['
SymClosedSquare: {"]", symClassParenthesis, posPostfix}, // 43: ']'
SymOpenBrace: {"{", symClassParenthesis, posPrefix}, // 44: '{'
SymClosedBrace: {"}", symClassParenthesis, posPostfix}, // 45: '}'
SymTilde: {"~", symClassOperator, posPrefix}, // 46: '~'
SymDoubleQuestion: {"??", symClassOperator, posInfix}, // 47: '??'
SymQuestionEqual: {"?=", symClassOperator, posInfix}, // 48: '?='
SymQuestionExclam: {"?!", symClassOperator, posInfix}, // 49: '?!'
SymDoubleAt: {"@@", symClassCommand, posLeaf}, // 50: '@@'
SymDoubleColon: {"::", symClassOperator, posInfix}, // 51: '::'
SymDoubleGreater: {">>", symClassOperator, posInfix}, // 52: '>>'
SymDoubleLess: {"<<", symClassOperator, posInfix}, // 53: '<<'
SymCaret: {"^", symClassOperator, posInfix}, // 54: '^'
SymDollarRound: {"$(", symClassOperator, posPrefix}, // 55: '$('
SymOpenClosedRound: {"()", symClassOperator, posPostfix}, // 56: '()'
SymDoubleDollar: {"$$", symClassCommand, posLeaf}, // 57: '$$'
SymDoubleDot: {"..", symClassOperator, posInfix}, // 58: '..'
SymTripleDot: {"...", symClassOperator, posPostfix}, // 59: '...'
SymStarEqual: {"*=", symClassOperator, posInfix}, // 60: '*='
SymSlashEqual: {"/=", symClassOperator, posInfix}, // 61: '/='
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
// SymUnchangeSign
// SymIdentifier
@@ -123,17 +131,17 @@ func init() {
// // SymClosedComment // 0: '*/'
// // SymOneLineComment // 0: '//'
// keywordBase
SymKwAnd: {"and", symClassOperator},
SymKwNot: {"not", symClassOperator},
SymKwOr: {"or", symClassOperator},
SymKwBut: {"but", symClassOperator},
SymKwFunc: {"func(", symClassDeclaration},
SymKwBuiltin: {"builtin", symClassOperator},
SymKwPlugin: {"plugin", symClassOperator},
SymKwIn: {"in", symClassOperator},
SymKwInclude: {"include", symClassOperator},
SymKwNil: {"nil", symClassValue},
SymKwUnset: {"unset", symClassOperator},
SymKwAnd: {"and", symClassOperator, posInfix},
SymKwNot: {"not", symClassOperator, posInfix},
SymKwOr: {"or", symClassOperator, posInfix},
SymKwBut: {"but", symClassOperator, posInfix},
SymKwFunc: {"func(", symClassDeclaration, posPrefix},
SymKwBuiltin: {"builtin", symClassOperator, posPrefix},
SymKwPlugin: {"plugin", symClassOperator, posPrefix},
SymKwIn: {"in", symClassOperator, posInfix},
SymKwInclude: {"include", symClassOperator, posPrefix},
SymKwNil: {"nil", symClassValue, posLeaf},
SymKwUnset: {"unset", symClassOperator, posPrefix},
}
}
@@ -171,12 +179,19 @@ func StringEndsWithOperator(s string) bool {
}
func endingOperator(s string) (sym Symbol) {
var matchLength = 0
sym = SymNone
lower := strings.ToLower(s)
lower := strings.TrimRight(strings.ToLower(s), " \t")
for symbol, spec := range symbolMap {
if spec.kind == symClassOperator && strings.HasSuffix(lower, spec.repr) {
sym = symbol
break
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
} else {
sym = SymNone
}
}
}
}
return
+74 -66
View File
@@ -7,74 +7,82 @@ package expr
type Symbol int16
const (
SymUnknown Symbol = iota - 1 // -1: Unknown symbol
SymNone // 0: Null value for variable of type symbol
SymError // 1: Error reading from stream
SymEos // 2: End of stream
SymMinus // 3: '-'
SymMinusEqual // 4: '-='
SymDoubleMinus // 5: '--'
SymPlus // 6: '+'
SymPlusEqual // 7: '+='
SymDoublePlus // 8: '++'
SymStar // 9: '*'
SymDoubleStar // 10: '**'
SymSlash // 11: '/'
SymBackSlash // 12: '\'
SymVertBar // 13: '|'
SymDoubleVertBar // 14: '||'
SymComma // 15: ','
SymColon // 16: ':'
SymSemiColon // 17: ';'
SymDot // 18: '.'
SymDotSlash // 19: './'
SymQuote // 20: '\''
SymDoubleQuote // 21: '"'
SymBackTick // 22: '`'
SymExclamation // 23: '!'
SymQuestion // 24: '?'
SymAmpersand // 25: '&'
SymDoubleAmpersand // 26: '&&'
SymPercent // 27: '%'
SymAt // 28: '@'
SymUndescore // 29: '_'
SymEqual // 30: '='
SymDoubleEqual // 31: '=='
SymLess // 32: '<'
SymLessOrEqual // 33: '<='
SymGreater // 34: '>'
SymGreaterOrEqual // 35: '>='
SymLessGreater // 36: '<>'
SymNotEqual // 37: '!='
SymDollar // 38: '$'
SymHash // 39: '#'
SymOpenRound // 40: '('
SymClosedRound // 41: ')'
SymOpenSquare // 42: '['
SymClosedSquare // 43: ']'
SymOpenBrace // 44: '{'
SymClosedBrace // 45: '}'
SymTilde // 46: '~'
SymDoubleQuestion // 47: '??'
SymQuestionEqual // 48: '?='
SymQuestionExclam // 49: '?!'
SymDoubleAt // 50: '@@'
SymDoubleColon // 51: '::'
SymDoubleGreater // 52: '>>'
SymDoubleLess // 53: '<<'
SymCaret // 54: '^'
SymDollarRound // 55: '$('
SymOpenClosedRound // 56: '()'
SymDoubleDollar // 57: '$$'
SymDoubleDot // 58: '..'
SymTripleDot // 59: '...'
SymStarEqual // 60: '*='
SymSlashEqual // 61: '/='
SymPercEqual // 62: '%='
SymPlusGreater // 63: '+>'
SymLessPlus // 64: '<+'
SymUnknown Symbol = iota - 1 // -1: Unknown symbol
SymNone // 0: Null value for variable of type symbol
SymError // 1: Error reading from stream
SymEos // 2: End of stream
SymMinus // 3: '-'
SymMinusEqual // 4: '-='
SymDoubleMinus // 5: '--'
SymPlus // 6: '+'
SymPlusEqual // 7: '+='
SymDoublePlus // 8: '++'
SymStar // 9: '*'
SymDoubleStar // 10: '**'
SymSlash // 11: '/'
SymBackSlash // 12: '\'
SymVertBar // 13: '|'
SymDoubleVertBar // 14: '||'
SymComma // 15: ','
SymColon // 16: ':'
SymSemiColon // 17: ';'
SymDot // 18: '.'
SymDotSlash // 19: './'
SymQuote // 20: '\''
SymDoubleQuote // 21: '"'
SymBackTick // 22: '`'
SymExclamation // 23: '!'
SymQuestion // 24: '?'
SymAmpersand // 25: '&'
SymDoubleAmpersand // 26: '&&'
SymPercent // 27: '%'
SymAt // 28: '@'
SymUndescore // 29: '_'
SymEqual // 30: '='
SymDoubleEqual // 31: '=='
SymLess // 32: '<'
SymLessOrEqual // 33: '<='
SymGreater // 34: '>'
SymGreaterOrEqual // 35: '>='
SymLessGreater // 36: '<>'
SymNotEqual // 37: '!='
SymDollar // 38: '$'
SymHash // 39: '#'
SymOpenRound // 40: '('
SymClosedRound // 41: ')'
SymOpenSquare // 42: '['
SymClosedSquare // 43: ']'
SymOpenBrace // 44: '{'
SymClosedBrace // 45: '}'
SymTilde // 46: '~'
SymDoubleQuestion // 47: '??'
SymQuestionEqual // 48: '?='
SymQuestionExclam // 49: '?!'
SymDoubleAt // 50: '@@'
SymDoubleColon // 51: '::'
SymDoubleGreater // 52: '>>'
SymDoubleLess // 53: '<<'
SymCaret // 54: '^'
SymDollarRound // 55: '$('
SymOpenClosedRound // 56: '()'
SymDoubleDollar // 57: '$$'
SymDoubleDot // 58: '..'
SymTripleDot // 59: '...'
SymStarEqual // 60: '*='
SymSlashEqual // 61: '/='
SymPercEqual // 62: '%='
SymDoubleLessEqual // 63: '<<='
SymDoubleGreaterEqual // 64: '>>='
SymAmpersandEqual // 65: '&='
SymVertBarEqual // 65: '|='
SymCaretEqual // 66: '^='
SymPlusGreater // 67: '+>'
SymLessPlus // 68: '<+'
SymChangeSign
SymUnchangeSign
SymDereference
SymPreInc
SymPreDec
SymIdentifier
SymBool
SymInteger
+2 -1
View File
@@ -26,12 +26,13 @@ func TestBool(t *testing.T) {
/* 12 */ {`true or false`, true, nil},
/* 13 */ {`true or []`, true, nil},
/* 14 */ {`[] or false`, nil, errors.New(`got list as left operand type of 'OR' operator, it must be bool`)},
/* 15 */ {`!true`, false, nil},
/* 13 */ //{`true or []`, nil, errors.New(`[1:8] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "OR"`)},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 1)
// runTestSuiteSpec(t, section, inputs, 15)
runTestSuite(t, section, inputs)
}
+11 -6
View File
@@ -52,16 +52,21 @@ func TestFuncBase(t *testing.T) {
/* 38 */ {`bool(1.0)`, true, nil},
/* 39 */ {`bool("1")`, true, nil},
/* 40 */ {`bool(false)`, false, nil},
/* 41 */ {`bool([1])`, nil, `bool(): can't convert list to bool`},
/* 42 */ {`dec(false)`, float64(0), nil},
/* 43 */ {`dec(1:2)`, float64(0.5), nil},
/* 44 */ {`dec([1])`, nil, `dec(): can't convert list to float`},
/* 45 */ {`eval("a=3"); a`, int64(3), nil},
/* 41 */ {`bool([1])`, true, nil},
/* 42 */ {`bool([])`, false, nil},
/* 43 */ {`bool({})`, false, nil},
/* 44 */ {`bool({1:"one"})`, true, nil},
/* 45 */ {`dec(false)`, float64(0), nil},
/* 46 */ {`dec(1:2)`, float64(0.5), nil},
/* 47 */ {`dec([1])`, nil, `dec(): can't convert list to float`},
/* 48 */ {`eval("a=3"); a`, int64(3), nil},
/* 49 */ {`int(5:2)`, int64(2), nil},
// /* 45 */ {`string([1])`, nil, `string(): can't convert list to string`},
}
t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 45)
// runTestSuiteSpec(t, section, inputs, 49)
runTestSuite(t, section, inputs)
}
+2
View File
@@ -31,6 +31,8 @@ func TestFuncString(t *testing.T) {
/* 17 */ {`builtin "string"; strSplit("one-two-three", "-")`, newListA("one", "two", "three"), nil},
/* 18 */ {`builtin "string"; strJoin("-", [1, "two", "three"])`, nil, `strJoin(): expected string, got integer (1)`},
/* 19 */ {`builtin "string"; strJoin()`, nil, `strJoin(): too few params -- expected 1 or more, got 0`},
/* 20 */ {`builtin "string"; strUpper("StOp")`, "STOP", nil},
/* 21 */ {`builtin "string"; strLower("StOp")`, "stop", nil},
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
}
+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 ")"`},
/* 16 */ {`v=[2]; a=1; v[a-=1]=5; v[0]`, int64(5), 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={
"init":func(@end){@current=0 but true},
//"current":func(){current},
@@ -44,6 +46,6 @@ func TestExpr(t *testing.T) {
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 6, 7, 8, 9)
// runTestSuiteSpec(t, section, inputs, 18)
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},
/* 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},
/* 15 */ {`1:0`, nil, `division by zero`},
/* 15 */ {`1:0`, nil, `[1:3] division by zero`},
/* 16 */ {`fract(-0.5)`, newFraction(-1, 2), nil},
/* 17 */ {`fract("")`, (*FractionType)(nil), `bad syntax`},
/* 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`},
/* 22 */ {`string(1:2)`, "1: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},
}
// 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)
}
}
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) {
section := "Iterator"
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},
/* 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},
/* 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},
@@ -27,8 +27,12 @@ func TestIteratorParser(t *testing.T) {
/* 16 */ {`include "test-resources/filter.expr"; it=$(ds,10); it++`, int64(2), nil},
/* 17 */ {`it=$({"next":func(){5}}); it++`, int64(5), nil},
/* 18 */ {`it=$({"next":func(){5}}); it.clean`, nil, nil},
/* 19 */ {`it=$({1:"one",2:"two",3:"three"}); it++`, int64(1), nil},
/* 20 */ {`it=$({1:"one",2:"two",3:"three"}, "default", "value"); it++`, "one", nil},
/* 21 */ {`it=$({1:"one",2:"two",3:"three"}, "desc", "key"); it++`, int64(3), nil},
/* 22 */ {`it=$({1:"one",2:"two",3:"three"}, "asc", "item"); it++`, NewList([]any{int64(1), "one"}), nil},
}
//runTestSuiteSpec(t, section, inputs, 18)
// runTestSuiteSpec(t, section, inputs, 20)
runTestSuite(t, section, inputs)
}
+2 -2
View File
@@ -38,9 +38,9 @@ func TestListParser(t *testing.T) {
/* 24 */ {`["a","b","c","d"][1]`, "b", nil},
/* 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},
/* 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},
/* 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},
/* 31 */ {`a=[1,2]; a<+3`, newListA(int64(1), int64(2), int64(3)), nil},
/* 32 */ {`a=[1,2]; 5+>a`, newListA(int64(5), int64(1), int64(2)), nil},
+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"`},
/* 2 */ {`a=1; unset ["a", "b"]`, 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", ".")
//runTestSuiteSpec(t, section, inputs, 3)
// runTestSuiteSpec(t, section, inputs, 4)
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`},
/* 65 */ {"+1.5", float64(1.5), nil},
/* 66 */ {"+", nil, `[1:2] prefix operator "+" requires one non-nil operand`},
/* 67 */ {"4 / 0", nil, `division by zero`},
/* 68 */ {"4.0 / 0", nil, `division by zero`},
/* 67 */ {"4 / 0", nil, `[1:4] division by zero`},
/* 68 */ {"4.0 / 0", nil, `[1:6] division by zero`},
/* 69 */ {"4.0 / \n2", float64(2.0), nil},
/* 70 */ {`123`, int64(123), nil},
/* 71 */ {`1.`, float64(1.0), nil},
@@ -142,6 +142,6 @@ func TestGeneralParser(t *testing.T) {
}
// t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 130)
// runTestSuiteSpec(t, section, inputs, 114)
runTestSuite(t, section, inputs)
}
+8 -1
View File
@@ -9,6 +9,7 @@ import (
)
func TestStringsParser(t *testing.T) {
section := "String"
inputs := []inputType{
/* 1 */ {`"uno" + "due"`, `unodue`, nil},
/* 2 */ {`"uno" + 2`, `uno2`, nil},
@@ -16,6 +17,12 @@ func TestStringsParser(t *testing.T) {
/* 4 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 5 */ {`"abc"[1]`, `b`, 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)
}
+8 -3
View File
@@ -20,9 +20,9 @@ const (
priAnd
priNot
priRelational
priBinOr
priBinAnd
priBinNot
priBitwiseOr
priBitwiseAnd
priBitwiseNot
priSum
priProduct
priFraction
@@ -34,6 +34,7 @@ const (
priDefault
priIncDec
priDot
priDereference
priValue
)
@@ -203,6 +204,10 @@ func (term *term) errIncompatibleType(value any) error {
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) {
err = term.tk.Errorf(template, args...)
return
+4
View File
@@ -79,6 +79,10 @@ func (tk *Token) IsSymbol(sym Symbol) bool {
return tk.Sym == sym
}
func (tk *Token) SetSymbol(sym Symbol) {
tk.Sym = sym
}
func (tk *Token) Errorf(template string, args ...any) (err error) {
err = fmt.Errorf(fmt.Sprintf("[%d:%d] ", tk.row, tk.col)+template, args...)
return
+9
View File
@@ -218,6 +218,15 @@ func ToGoInt(value any, description string) (i int, err error) {
return
}
func ToGoString(value any, description string) (s string, err error) {
if s, ok := value.(string); ok {
return s, nil
} else {
err = fmt.Errorf("%s expected string, got %s (%v)", description, TypeName(value), value)
}
return
}
func ForAll[T, V any](ts []T, fn func(T) V) []V {
result := make([]V, len(ts))
for i, t := range ts {