Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e5f63c3d9d | |||
| d545a35acf | |||
| e4275e2cb6 | |||
| 1ff5770264 | |||
| ba32e2dccf | |||
| f22b5a6f0b | |||
| 7c8dbb0ac7 | |||
| e5c5920db0 | |||
| 61efdb4eef | |||
| 82ec78719d | |||
| 554ff1a9dd | |||
| 6bb891e09d | |||
| 1c4ffd7d64 | |||
| b92b19e1dd | |||
| 9967918418 | |||
| 6c14c07d66 | |||
| 9ea170e53b | |||
| a543360151 | |||
| 24a25bbf94 | |||
| d6a1607041 | |||
| 4d43ab2c2f | |||
| 9bd4a0ba23 | |||
| 2b184cf3f2 | |||
| 263e419d9a |
@@ -119,7 +119,7 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
|
|||||||
if self.forest != nil {
|
if self.forest != nil {
|
||||||
for _, root := range self.forest {
|
for _, root := range self.forest {
|
||||||
if result, err = root.compute(ctx); err == nil {
|
if result, err = root.compute(ctx); err == nil {
|
||||||
ctx.setVar(ControlLastResult, result)
|
ctx.UnsafeSetVar(ControlLastResult, result)
|
||||||
} else {
|
} else {
|
||||||
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
|
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
|
||||||
break
|
break
|
||||||
@@ -128,7 +128,7 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
|
|||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
result, err = self.root.compute(ctx)
|
result, err = self.root.compute(ctx)
|
||||||
ctx.setVar(ControlLastResult, result)
|
ctx.UnsafeSetVar(ControlLastResult, result)
|
||||||
}
|
}
|
||||||
// } else {
|
// } else {
|
||||||
// err = errors.New("empty expression")
|
// err = errors.New("empty expression")
|
||||||
|
|||||||
@@ -5,7 +5,26 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
paramCount = "count"
|
||||||
|
paramItem = "item"
|
||||||
paramParts = "parts"
|
paramParts = "parts"
|
||||||
paramSeparator = "separator"
|
paramSeparator = "separator"
|
||||||
paramSource = "source"
|
paramSource = "source"
|
||||||
|
paramSuffix = "suffix"
|
||||||
|
paramPrefix = "prefix"
|
||||||
|
paramStart = "start"
|
||||||
|
paramEnd = "end"
|
||||||
|
paramValue = "value"
|
||||||
|
paramEllipsis = "..."
|
||||||
|
typeFilepath = "filepath"
|
||||||
|
typeDirpath = "dirpath"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// const (
|
||||||
|
// typeInteger = "int"
|
||||||
|
// typeFloat = "float"
|
||||||
|
// typeString = "string"
|
||||||
|
// typeFraction = "fract"
|
||||||
|
// typeList = "list"
|
||||||
|
// typeDict = "dict"
|
||||||
|
// )
|
||||||
|
|||||||
@@ -5,10 +5,13 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
typeAny = "any"
|
||||||
typeBoolean = "boolean"
|
typeBoolean = "boolean"
|
||||||
typeFloat = "decimal"
|
typeFloat = "float"
|
||||||
typeFraction = "fraction"
|
typeFraction = "fraction"
|
||||||
|
typeHandle = "handle"
|
||||||
typeInt = "integer"
|
typeInt = "integer"
|
||||||
|
typeItem = "item"
|
||||||
typeNumber = "number"
|
typeNumber = "number"
|
||||||
typeString = "string"
|
typeString = "string"
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-2
@@ -15,14 +15,16 @@ func exportVar(ctx ExprContext, name string, value any) {
|
|||||||
if name[0] == '@' {
|
if name[0] == '@' {
|
||||||
name = name[1:]
|
name = name[1:]
|
||||||
}
|
}
|
||||||
ctx.setVar(name, value)
|
ctx.UnsafeSetVar(name, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
||||||
if name[0] == '@' {
|
if name[0] == '@' {
|
||||||
name = name[1:]
|
name = name[1:]
|
||||||
}
|
}
|
||||||
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
// ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
||||||
|
// ctx.RegisterFuncInfo(name, info)
|
||||||
|
ctx.RegisterFunc(name, info.Functor(), info.ReturnType(), info.Params())
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportObjects(destCtx, sourceCtx ExprContext) {
|
func exportObjects(destCtx, sourceCtx ExprContext) {
|
||||||
|
|||||||
+16
-11
@@ -4,28 +4,31 @@
|
|||||||
// context.go
|
// context.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
// ---- Function template
|
|
||||||
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
|
|
||||||
|
|
||||||
// ---- Functor interface
|
// ---- Functor interface
|
||||||
type Functor interface {
|
type Functor interface {
|
||||||
Invoke(ctx ExprContext, name string, args []any) (result any, err error)
|
Invoke(ctx ExprContext, name string, args []any) (result any, err error)
|
||||||
|
SetFunc(info ExprFunc)
|
||||||
|
GetFunc() ExprFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
type simpleFunctor struct {
|
// ---- Function Param Info
|
||||||
f FuncTemplate
|
type ExprFuncParam interface {
|
||||||
}
|
Name() string
|
||||||
|
Type() string
|
||||||
func (functor *simpleFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
IsOptional() bool
|
||||||
return functor.f(ctx, name, args)
|
IsRepeat() bool
|
||||||
|
DefaultValue() any
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Function Info
|
// ---- Function Info
|
||||||
type ExprFunc interface {
|
type ExprFunc interface {
|
||||||
|
Formatter
|
||||||
Name() string
|
Name() string
|
||||||
MinArgs() int
|
MinArgs() int
|
||||||
MaxArgs() int
|
MaxArgs() int
|
||||||
Functor() Functor
|
Functor() Functor
|
||||||
|
Params() []ExprFuncParam
|
||||||
|
ReturnType() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----Expression Context
|
// ----Expression Context
|
||||||
@@ -33,10 +36,12 @@ type ExprContext interface {
|
|||||||
Clone() ExprContext
|
Clone() ExprContext
|
||||||
GetVar(varName string) (value any, exists bool)
|
GetVar(varName string) (value any, exists bool)
|
||||||
SetVar(varName string, value any)
|
SetVar(varName string, value any)
|
||||||
setVar(varName string, value any)
|
UnsafeSetVar(varName string, value any)
|
||||||
EnumVars(func(name string) (accept bool)) (varNames []string)
|
EnumVars(func(name string) (accept bool)) (varNames []string)
|
||||||
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
|
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
|
||||||
GetFuncInfo(name string) (item ExprFunc, exists bool)
|
GetFuncInfo(name string) (item ExprFunc, exists bool)
|
||||||
Call(name string, args []any) (result any, err error)
|
Call(name string, args []any) (result any, err error)
|
||||||
RegisterFunc(name string, f Functor, minArgs, maxArgs int)
|
// RegisterFunc(name string, f Functor, minArgs, maxArgs int)
|
||||||
|
RegisterFuncInfo(info ExprFunc)
|
||||||
|
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-1
@@ -42,7 +42,7 @@ func TestDictParser(t *testing.T) {
|
|||||||
var gotResult any
|
var gotResult any
|
||||||
var gotErr error
|
var gotErr error
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
ctx := NewSimpleStore()
|
||||||
ctx.SetVar("var1", int64(123))
|
ctx.SetVar("var1", int64(123))
|
||||||
ctx.SetVar("var2", "abc")
|
ctx.SetVar("var2", "abc")
|
||||||
ImportMathFuncs(ctx)
|
ImportMathFuncs(ctx)
|
||||||
@@ -102,3 +102,49 @@ func TestDictParser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDictToStringMultiLine(t *testing.T) {
|
||||||
|
var good bool
|
||||||
|
section := "dict-ToString-ML"
|
||||||
|
want := `{
|
||||||
|
"first": 1
|
||||||
|
}`
|
||||||
|
args := map[any]*term{
|
||||||
|
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
|
||||||
|
}
|
||||||
|
dict := newDict(args)
|
||||||
|
got := dict.ToString(MultiLine)
|
||||||
|
// fmt.Printf("got=%q\n", got)
|
||||||
|
|
||||||
|
if good = got == want; !good {
|
||||||
|
t.Errorf("ToString(MultiLine): got = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if good {
|
||||||
|
t.Logf("%s -- succeeded", section)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s -- failed", section)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDictToString(t *testing.T) {
|
||||||
|
var good bool
|
||||||
|
section := "dict-ToString-SL"
|
||||||
|
want := `{"first": 1}`
|
||||||
|
args := map[any]*term{
|
||||||
|
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
|
||||||
|
}
|
||||||
|
dict := newDict(args)
|
||||||
|
got := dict.ToString(0)
|
||||||
|
// fmt.Printf("got=%q\n", got)
|
||||||
|
|
||||||
|
if good = got == want; !good {
|
||||||
|
t.Errorf("ToString(0): got = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if good {
|
||||||
|
t.Logf("%s -- succeeded", section)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s -- failed", section)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+63
-25
@@ -22,7 +22,7 @@ Expressions calculator
|
|||||||
|
|
||||||
toc::[]
|
toc::[]
|
||||||
|
|
||||||
#TODO: Work in progress (last update on 2024/05/17, 15:47 p.m.)#
|
#TODO: Work in progress (last update on 2024/05/20, 06:58 a.m.)#
|
||||||
|
|
||||||
== Expr
|
== Expr
|
||||||
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
|
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
|
||||||
@@ -266,10 +266,10 @@ Some arithmetic operators can also be used with strings.
|
|||||||
|===
|
|===
|
||||||
| Symbol | Operation | Description | Examples
|
| Symbol | Operation | Description | Examples
|
||||||
|
|
||||||
| [blue]`+` | _concatenation_ | Join two strings or two _stringable_ values | [blue]`"one" + "two"` _["onetwo"]_ +
|
| [blue]`+` | _concatenation_ | Join two strings or two _stringable_ values | [blue]`"one" + "two"` -> _"onetwo"_ +
|
||||||
[blue]`"one" + 2` _["one2"]_
|
[blue]`"one" + 2` -> _"one2"_
|
||||||
|
|
||||||
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` _["oneone"]_
|
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` -> _"oneone"_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
The items of strings can be accessed using the dot `.` operator.
|
The items of strings can be accessed using the dot `.` operator.
|
||||||
@@ -280,22 +280,22 @@ _item_ = _string-expr_ "**.**" _integer-expr_
|
|||||||
====
|
====
|
||||||
|
|
||||||
.String examples
|
.String examples
|
||||||
`>>>` [blue]`s="abc"` [gray]_assign the string to variable s_ +
|
`>>>` [blue]`s="abc"` [gray]_// assign the string to variable s_ +
|
||||||
[green]`abc` +
|
[green]`abc` +
|
||||||
`>>>` [blue]`s.1` [gray]_char at position 1 (starting from 0)_ +
|
`>>>` [blue]`s.1` [gray]_// char at position 1 (starting from 0)_ +
|
||||||
[green]`b` +
|
[green]`b` +
|
||||||
`>>>` [blue]`s.(-1)` [gray]_char at position -1, the rightmost one_ +
|
`>>>` [blue]`s.(-1)` [gray]_// char at position -1, the rightmost one_ +
|
||||||
[green]`c` +
|
[green]`c` +
|
||||||
`>>>` [blue]`\#s` [gray]_number of chars_ +
|
`>>>` [blue]`\#s` [gray]_// number of chars_ +
|
||||||
[gren]`3` +
|
[gren]`3` +
|
||||||
`>>>` [blue]`#"abc"` [gray]_number of chars_ +
|
`>>>` [blue]`#"abc"` [gray]_// number of chars_ +
|
||||||
[green]`3`
|
[green]`3`
|
||||||
|
|
||||||
|
|
||||||
=== Booleans
|
=== Booleans
|
||||||
Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values.
|
Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values.
|
||||||
|
|
||||||
.Relational operators
|
.Relational operators^(*)^
|
||||||
[cols="^1,^2,6,4"]
|
[cols="^1,^2,6,4"]
|
||||||
|===
|
|===
|
||||||
| Symbol | Operation | Description | Examples
|
| Symbol | Operation | Description | Examples
|
||||||
@@ -314,6 +314,8 @@ Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relationa
|
|||||||
[blue]`"b" \<= "b"` -> _true_
|
[blue]`"b" \<= "b"` -> _true_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
^(*)^ See also the [blue]`in` operator in the _list_ and _dictionary_ sections.
|
||||||
|
|
||||||
|
|
||||||
.Boolean operators
|
.Boolean operators
|
||||||
[cols="^2,^2,5,4"]
|
[cols="^2,^2,5,4"]
|
||||||
@@ -371,13 +373,18 @@ _non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value} "**]**" +
|
|||||||
|
|
||||||
| [blue]`+` | _Join_ | Joins two lists | [blue]`[1,2] + [3]` -> _[1,2,3]_
|
| [blue]`+` | _Join_ | Joins two lists | [blue]`[1,2] + [3]` -> _[1,2,3]_
|
||||||
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` -> _[1,3]_
|
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` -> _[1,3]_
|
||||||
|
| [blue]`>>` | _Front insertion_ | Insert an item in front | [blue]`0 >> [1,2]` -> _[0,1,2]_
|
||||||
|
| [blue]`<<` | _Back insertion_ | Insert an item at end | [blue]`[1,2] << 3` -> _[1,2,3]_
|
||||||
|
| [blue]`.` | _List item_ | Item at given position | [blue]`[1,2.3].1` -> _2_
|
||||||
|
| [blue]`in` | _Item in list_ | True if item is in list | [blue]`2 in [1,2,3]` -> _true_ +
|
||||||
|
[blue]`6 in [1,2,3]` -> _false_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
The items of array can be accessed using the dot `.` operator.
|
The items of array can be accessed using the dot `.` operator.
|
||||||
|
|
||||||
.Item access syntax
|
.Item access syntax
|
||||||
====
|
====
|
||||||
_item_ = _list-expr_ "**.**" _list-expr_
|
_item_ = _list-expr_ "**.**" _integer-expr_
|
||||||
====
|
====
|
||||||
|
|
||||||
.Items of list
|
.Items of list
|
||||||
@@ -394,14 +401,16 @@ _item_ = _list-expr_ "**.**" _list-expr_
|
|||||||
`>>>` [blue]`list.(10)` +
|
`>>>` [blue]`list.(10)` +
|
||||||
[red]`Eval Error: [1:9] index 10 out of bounds` +
|
[red]`Eval Error: [1:9] index 10 out of bounds` +
|
||||||
`>>>` [blue]`#list` +
|
`>>>` [blue]`#list` +
|
||||||
[green]`3`
|
[green]`3` +
|
||||||
|
`>>>` [blue]`index=2; ["a", "b", "c", "d"].index` +
|
||||||
|
[green]`c`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
=== Dictionaries
|
=== Dictionaries
|
||||||
WARNING: Support for dictionaries is still ongoing.
|
The _dictionary_, or _dict_, data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_.
|
||||||
|
|
||||||
The _dictionary_, or _dict_, data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_. Dictionary literals are sequences of pairs separated by comma `,`; sequences are enclosed between brace brackets.
|
Dictionary literals are sequences of pairs separated by comma [blue]`,` enclosed between brace brackets.
|
||||||
|
|
||||||
.Dict literal syntax
|
.Dict literal syntax
|
||||||
====
|
====
|
||||||
@@ -412,11 +421,27 @@ _non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar
|
|||||||
|
|
||||||
.Examples
|
.Examples
|
||||||
`>>>` [blue]`{1:"one", 2:"two"}` +
|
`>>>` [blue]`{1:"one", 2:"two"}` +
|
||||||
|
[green]`{1: "one", 2: "two"}` +
|
||||||
`>>>` [blue]`{"one":1, "two": 2}` +
|
`>>>` [blue]`{"one":1, "two": 2}` +
|
||||||
`>>>` [blue]`{"sum":1+2+3, "prod":1*2*3}`
|
[green]`{"one": 1, "two": 2}` +
|
||||||
|
`>>>` [blue]`{"sum":1+2+3, "prod":1*2*3}` +
|
||||||
|
[green]`{"sum": 6, "prod": 6}`
|
||||||
|
|
||||||
|
|
||||||
|
.Dict operators
|
||||||
|
[cols="^2,^2,4,5"]
|
||||||
|
|===
|
||||||
|
| Symbol | Operation | Description | Examples
|
||||||
|
|
||||||
|
| [blue]`+` | _Join_ | Joins two dicts | [blue]`{1:"one"}+{6:"six"}` -> _{1: "one", 6: "six"}_
|
||||||
|
| [blue]`.` | _Dict item value_ | Item value of given key | [blue]`{"one":1, "two":2}."two"` -> _2_
|
||||||
|
| [blue]`in` | _Key in dict_ | True if key is in dict | [blue]`"one" in {"one":1, "two":2}` -> _true_ +
|
||||||
|
[blue]`"six" in {"one":1, "two":2}` -> _false_
|
||||||
|
|===
|
||||||
|
|
||||||
|
|
||||||
== Variables
|
== Variables
|
||||||
_Expr_ supports variables like most programming languages. A variable is an identifier with an assigned value. Variables are stored in _contexts_.
|
_Expr_, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in _contexts_.
|
||||||
|
|
||||||
.Variable literal syntax
|
.Variable literal syntax
|
||||||
====
|
====
|
||||||
@@ -467,12 +492,19 @@ The value of each sub-expression is stored in the automatica variable _last_.
|
|||||||
|
|
||||||
|
|
||||||
=== [blue]`but` operator
|
=== [blue]`but` operator
|
||||||
[blue]`but` is an infixed operator. Its operands can be expressions of any type. It evaluates the left expression first, then the right expression. The value of the right expression is the final result. Examples: [blue]`5 but 2` returns 2, [blue]`x=2*3 but x-1` returns 5.
|
[blue]`but` is an infixed operator. Its operands can be expressions of any type. It evaluates the left expression first, then the right expression. The value of the right expression is the final result.
|
||||||
|
|
||||||
[blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`(` and [blue]`)`.
|
.Examples
|
||||||
|
[blue]`5 but 2` +
|
||||||
|
[green]`2` +
|
||||||
|
[blue]`x=2*3 but x-1` +
|
||||||
|
[green]`5`.
|
||||||
|
|
||||||
|
[blue]`but` behavior is very similar to [blue]`;`. The only difference is that [blue]`;` is not a true operator and can't be used inside parenthesis [blue]`(` and [blue]`)`.
|
||||||
|
|
||||||
=== Assignment operator [blue]`=`
|
=== Assignment operator [blue]`=`
|
||||||
The assignment operator [blue]`=` is used to define variables in the evaluation context or to change their value (see _ExprContext_).
|
The assignment operator [blue]`=` is used to define variables or to change their value in the evaluation context (see _ExprContext_).
|
||||||
|
|
||||||
The value on the left side of [blue]`=` must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.
|
The value on the left side of [blue]`=` must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.
|
||||||
|
|
||||||
.Example
|
.Example
|
||||||
@@ -540,11 +572,12 @@ NOTE: These operators have a high priority, in particular higher than the operat
|
|||||||
The table below shows all supported operators by decreasing priorities.
|
The table below shows all supported operators by decreasing priorities.
|
||||||
|
|
||||||
.Operators priorities
|
.Operators priorities
|
||||||
[cols="^2,^2,^2,^5,^5"]
|
[cols="^2,^2,^2,^5,^6"]
|
||||||
|===
|
|===
|
||||||
| Priority | Operators | Position | Operation | Operands and results
|
| Priority | Operators | Position | Operation | Operands and results
|
||||||
|
|
||||||
.1+|*ITEM*| [blue]`.` | _Infix_ | _Item_| _collection_ `"."` _key_ -> _any_
|
.2+|*ITEM*| [blue]`.` | _Infix_ | _List item_| _list_ `"."` _integer_ -> _any_
|
||||||
|
| [blue]`.` | _Infix_ | _Dict item_ | _dict_ `"."` _any_ -> _any_
|
||||||
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_
|
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_
|
||||||
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_
|
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_
|
||||||
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_
|
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_
|
||||||
@@ -556,24 +589,29 @@ The table below shows all supported operators by decreasing priorities.
|
|||||||
| [blue]`/` | _Infix_ | _Division_ | _number_ `"/"` _number_ -> _number_
|
| [blue]`/` | _Infix_ | _Division_ | _number_ `"/"` _number_ -> _number_
|
||||||
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `"./"` _number_ -> _float_
|
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `"./"` _number_ -> _float_
|
||||||
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `"%"` _integer_ -> _integer_
|
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `"%"` _integer_ -> _integer_
|
||||||
.5+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_
|
.6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_
|
||||||
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `"+"` (_string_\|_number_) -> _string_
|
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `"+"` (_string_\|_number_) -> _string_
|
||||||
| [blue]`+` | _Infix_ | _List-join_ | _list_ `"+"` _list_ -> _list_
|
| [blue]`+` | _Infix_ | _List-join_ | _list_ `"+"` _list_ -> _list_
|
||||||
|
| [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `"+"` _dict_ -> _dict_
|
||||||
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `"-"` _number_ -> _number_
|
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `"-"` _number_ -> _number_
|
||||||
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `"-"` _list_ -> _list_
|
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `"-"` _list_ -> _list_
|
||||||
.6+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_
|
.8+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_
|
||||||
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `"\<="` _comparable_ -> _boolean_
|
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `"\<="` _comparable_ -> _boolean_
|
||||||
| [blue]`>` | _Infix_ | _greater_ | _comparable_ `">"` _comparable_ -> _boolean_
|
| [blue]`>` | _Infix_ | _greater_ | _comparable_ `">"` _comparable_ -> _boolean_
|
||||||
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ `">="` _comparable_ -> _boolean_
|
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ `">="` _comparable_ -> _boolean_
|
||||||
| [blue]`==` | _Infix_ | _equal_ | _comparable_ `"=="` _comparable_ -> _boolean_
|
| [blue]`==` | _Infix_ | _equal_ | _comparable_ `"=="` _comparable_ -> _boolean_
|
||||||
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ `"!="` _comparable_ -> _boolean_
|
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ `"!="` _comparable_ -> _boolean_
|
||||||
|
| [blue]`in` | _Infix_ | _member-of-list_ | _any_ `"in"` _list_ -> _boolean_
|
||||||
|
| [blue]`in` | _Infix_ | _key-of-dict_ | _any_ `"in"` _dict_ -> _boolean_
|
||||||
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | `"not"` _boolean_ -> _boolean_
|
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | `"not"` _boolean_ -> _boolean_
|
||||||
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ `"and"` _boolean_ -> _boolean_
|
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ `"and"` _boolean_ -> _boolean_
|
||||||
| [blue]`&&` | _Infix_ | _and_ | _boolean_ `"&&"` _boolean_ -> _boolean_
|
| [blue]`&&` | _Infix_ | _and_ | _boolean_ `"&&"` _boolean_ -> _boolean_
|
||||||
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ `"or"` _boolean_ -> _boolean_
|
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ `"or"` _boolean_ -> _boolean_
|
||||||
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ `"\|\|"` _boolean_ -> _boolean_
|
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ `"\|\|"` _boolean_ -> _boolean_
|
||||||
.1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
|
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
|
||||||
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|
| [blue]`>>` | _Infix_ | _front-insert_ | _any_ ">>" _list_ -> _list_
|
||||||
|
| [blue]`<<` | _Infix_ | _back-insert_ | _list_ "<<" _any_ -> _list_
|
||||||
|
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
== Functions
|
== Functions
|
||||||
|
|||||||
+150
-43
@@ -585,7 +585,7 @@ pre.rouge .ss {
|
|||||||
<div class="sectionbody">
|
<div class="sectionbody">
|
||||||
<!-- toc disabled -->
|
<!-- toc disabled -->
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><mark>TODO: Work in progress (last update on 2024/05/17, 15:47 p.m.)</mark></p>
|
<p><mark>TODO: Work in progress (last update on 2024/05/20, 06:58 a.m.)</mark></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -999,14 +999,14 @@ pre.rouge .ss {
|
|||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">+</code></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">+</code></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>concatenation</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>concatenation</em></p></td>
|
||||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Join two strings or two <em>stringable</em> values</p></td>
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Join two strings or two <em>stringable</em> values</p></td>
|
||||||
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">"one" + "two"</code> <em>["onetwo"]</em><br>
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">"one" + "two"</code> → <em>"onetwo"</em><br>
|
||||||
<code class="blue">"one" + 2</code> <em>["one2"]</em></p></td>
|
<code class="blue">"one" + 2</code> → <em>"one2"</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">*</code></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">*</code></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>repeat</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>repeat</em></p></td>
|
||||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Make <em>n</em> copy of a string</p></td>
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Make <em>n</em> copy of a string</p></td>
|
||||||
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">"one" * 2</code> <em>["oneone"]</em></p></td>
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">"one" * 2</code> → <em>"oneone"</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -1023,15 +1023,15 @@ pre.rouge .ss {
|
|||||||
</div>
|
</div>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<div class="title">String examples</div>
|
<div class="title">String examples</div>
|
||||||
<p><code>>>></code> <code class="blue">s="abc"</code> <em class="gray">assign the string to variable s</em><br>
|
<p><code>>>></code> <code class="blue">s="abc"</code> <em class="gray">// assign the string to variable s</em><br>
|
||||||
<code class="green">abc</code><br>
|
<code class="green">abc</code><br>
|
||||||
<code>>>></code> <code class="blue">s.1</code> <em class="gray">char at position 1 (starting from 0)</em><br>
|
<code>>>></code> <code class="blue">s.1</code> <em class="gray">// char at position 1 (starting from 0)</em><br>
|
||||||
<code class="green">b</code><br>
|
<code class="green">b</code><br>
|
||||||
<code>>>></code> <code class="blue">s.(-1)</code> <em class="gray">char at position -1, the rightmost one</em><br>
|
<code>>>></code> <code class="blue">s.(-1)</code> <em class="gray">// char at position -1, the rightmost one</em><br>
|
||||||
<code class="green">c</code><br>
|
<code class="green">c</code><br>
|
||||||
<code>>>></code> <code class="blue">#s</code> <em class="gray">number of chars</em><br>
|
<code>>>></code> <code class="blue">#s</code> <em class="gray">// number of chars</em><br>
|
||||||
<code class="gren">3</code><br>
|
<code class="gren">3</code><br>
|
||||||
<code>>>></code> <code class="blue">#"abc"</code> <em class="gray">number of chars</em><br>
|
<code>>>></code> <code class="blue">#"abc"</code> <em class="gray">// number of chars</em><br>
|
||||||
<code class="green">3</code></p>
|
<code class="green">3</code></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1041,7 +1041,7 @@ pre.rouge .ss {
|
|||||||
<p>Boolean data type has two values only: <em class="blue">true</em> and <em class="blue">false</em>. Relational and boolean expressions result in boolean values.</p>
|
<p>Boolean data type has two values only: <em class="blue">true</em> and <em class="blue">false</em>. Relational and boolean expressions result in boolean values.</p>
|
||||||
</div>
|
</div>
|
||||||
<table class="tableblock frame-all grid-all stretch">
|
<table class="tableblock frame-all grid-all stretch">
|
||||||
<caption class="title">Table 4. Relational operators</caption>
|
<caption class="title">Table 4. Relational operators<sup>(*)</sup></caption>
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col style="width: 7.6923%;">
|
<col style="width: 7.6923%;">
|
||||||
<col style="width: 15.3846%;">
|
<col style="width: 15.3846%;">
|
||||||
@@ -1101,6 +1101,9 @@ pre.rouge .ss {
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p><sup>(*)</sup> See also the <code class="blue">in</code> operator in the <em>list</em> and <em>dictionary</em> sections.</p>
|
||||||
|
</div>
|
||||||
<table class="tableblock frame-all grid-all stretch">
|
<table class="tableblock frame-all grid-all stretch">
|
||||||
<caption class="title">Table 5. Boolean operators</caption>
|
<caption class="title">Table 5. Boolean operators</caption>
|
||||||
<colgroup>
|
<colgroup>
|
||||||
@@ -1227,6 +1230,31 @@ pre.rouge .ss {
|
|||||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Left list without elements in the right list</p></td>
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Left list without elements in the right list</p></td>
|
||||||
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2,3] - [2]</code> → <em>[1,3]</em></p></td>
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2,3] - [2]</code> → <em>[1,3]</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">>></code></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Front insertion</em></p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Insert an item in front</p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">0 >> [1,2]</code> → <em>[0,1,2]</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue"><<</code></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Back insertion</em></p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Insert an item at end</p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2] << 3</code> → <em>[1,2,3]</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">.</code></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>List item</em></p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Item at given position</p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2.3].1</code> → <em>2</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">in</code></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Item in list</em></p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock">True if item is in list</p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">2 in [1,2,3]</code> → <em>true</em><br>
|
||||||
|
<code class="blue">6 in [1,2,3]</code> → <em>false</em></p></td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
@@ -1236,7 +1264,7 @@ pre.rouge .ss {
|
|||||||
<div class="title">Example 6. Item access syntax</div>
|
<div class="title">Example 6. Item access syntax</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><em>item</em> = <em>list-expr</em> "<strong>.</strong>" <em>list-expr</em></p>
|
<p><em>item</em> = <em>list-expr</em> "<strong>.</strong>" <em>integer-expr</em></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1255,25 +1283,18 @@ pre.rouge .ss {
|
|||||||
<code>>>></code> <code class="blue">list.(10)</code><br>
|
<code>>>></code> <code class="blue">list.(10)</code><br>
|
||||||
<code class="red">Eval Error: [1:9] index 10 out of bounds</code><br>
|
<code class="red">Eval Error: [1:9] index 10 out of bounds</code><br>
|
||||||
<code>>>></code> <code class="blue">#list</code><br>
|
<code>>>></code> <code class="blue">#list</code><br>
|
||||||
<code class="green">3</code></p>
|
<code class="green">3</code><br>
|
||||||
|
<code>>>></code> <code class="blue">index=2; ["a", "b", "c", "d"].index</code><br>
|
||||||
|
<code class="green">c</code></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect2">
|
<div class="sect2">
|
||||||
<h3 id="_dictionaries"><a class="anchor" href="#_dictionaries"></a><a class="link" href="#_dictionaries">2.5. Dictionaries</a></h3>
|
<h3 id="_dictionaries"><a class="anchor" href="#_dictionaries"></a><a class="link" href="#_dictionaries">2.5. Dictionaries</a></h3>
|
||||||
<div class="admonitionblock warning">
|
<div class="paragraph">
|
||||||
<table>
|
<p>The <em>dictionary</em>, or <em>dict</em>, data-type is set of pairs <em>key/value</em>. It is also known as <em>map</em> or <em>associative array</em>.</p>
|
||||||
<tr>
|
|
||||||
<td class="icon">
|
|
||||||
<i class="fa icon-warning" title="Warning"></i>
|
|
||||||
</td>
|
|
||||||
<td class="content">
|
|
||||||
Support for dictionaries is still ongoing.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p>The <em>dictionary</em>, or <em>dict</em>, data-type is set of pairs <em>key/value</em>. It is also known as <em>map</em> or <em>associative array</em>. Dictionary literals are sequences of pairs separated by comma <code>,</code>; sequences are enclosed between brace brackets.</p>
|
<p>Dictionary literals are sequences of pairs separated by comma <code class="blue">,</code> enclosed between brace brackets.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="exampleblock">
|
<div class="exampleblock">
|
||||||
<div class="title">Example 7. Dict literal syntax</div>
|
<div class="title">Example 7. Dict literal syntax</div>
|
||||||
@@ -1288,9 +1309,50 @@ Support for dictionaries is still ongoing.
|
|||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<div class="title">Examples</div>
|
<div class="title">Examples</div>
|
||||||
<p><code>>>></code> <code class="blue">{1:"one", 2:"two"}</code><br>
|
<p><code>>>></code> <code class="blue">{1:"one", 2:"two"}</code><br>
|
||||||
|
<code class="green">{1: "one", 2: "two"}</code><br>
|
||||||
<code>>>></code> <code class="blue">{"one":1, "two": 2}</code><br>
|
<code>>>></code> <code class="blue">{"one":1, "two": 2}</code><br>
|
||||||
<code>>>></code> <code class="blue">{"sum":1+2+3, "prod":1*2*3}</code></p>
|
<code class="green">{"one": 1, "two": 2}</code><br>
|
||||||
|
<code>>>></code> <code class="blue">{"sum":1+2+3, "prod":1*2*3}</code><br>
|
||||||
|
<code class="green">{"sum": 6, "prod": 6}</code></p>
|
||||||
</div>
|
</div>
|
||||||
|
<table class="tableblock frame-all grid-all stretch">
|
||||||
|
<caption class="title">Table 7. Dict operators</caption>
|
||||||
|
<colgroup>
|
||||||
|
<col style="width: 15.3846%;">
|
||||||
|
<col style="width: 15.3846%;">
|
||||||
|
<col style="width: 30.7692%;">
|
||||||
|
<col style="width: 38.4616%;">
|
||||||
|
</colgroup>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="tableblock halign-center valign-top">Symbol</th>
|
||||||
|
<th class="tableblock halign-center valign-top">Operation</th>
|
||||||
|
<th class="tableblock halign-left valign-top">Description</th>
|
||||||
|
<th class="tableblock halign-left valign-top">Examples</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">+</code></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Join</em></p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Joins two dicts</p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">{1:"one"}+{6:"six"}</code> → <em>{1: "one", 6: "six"}</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">.</code></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Dict item value</em></p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Item value of given key</p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">{"one":1, "two":2}."two"</code> → <em>2</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">in</code></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Key in dict</em></p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock">True if key is in dict</p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">"one" in {"one":1, "two":2}</code> → <em>true</em><br>
|
||||||
|
<code class="blue">"six" in {"one":1, "two":2}</code> → <em>false</em></p></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1298,7 +1360,7 @@ Support for dictionaries is still ongoing.
|
|||||||
<h2 id="_variables"><a class="anchor" href="#_variables"></a><a class="link" href="#_variables">3. Variables</a></h2>
|
<h2 id="_variables"><a class="anchor" href="#_variables"></a><a class="link" href="#_variables">3. Variables</a></h2>
|
||||||
<div class="sectionbody">
|
<div class="sectionbody">
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><em>Expr</em> supports variables like most programming languages. A variable is an identifier with an assigned value. Variables are stored in <em>contexts</em>.</p>
|
<p><em>Expr</em>, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in <em>contexts</em>.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="exampleblock">
|
<div class="exampleblock">
|
||||||
<div class="title">Example 8. Variable literal syntax</div>
|
<div class="title">Example 8. Variable literal syntax</div>
|
||||||
@@ -1393,17 +1455,26 @@ Technically <code class="blue">;</code> is not treated as a real operator. It ac
|
|||||||
<div class="sect2">
|
<div class="sect2">
|
||||||
<h3 id="_but_operator"><a class="anchor" href="#_but_operator"></a><a class="link" href="#_but_operator">4.2. <code class="blue">but</code> operator</a></h3>
|
<h3 id="_but_operator"><a class="anchor" href="#_but_operator"></a><a class="link" href="#_but_operator">4.2. <code class="blue">but</code> operator</a></h3>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><code class="blue">but</code> is an infixed operator. Its operands can be expressions of any type. It evaluates the left expression first, then the right expression. The value of the right expression is the final result. Examples: <code class="blue">5 but 2</code> returns 2, <code class="blue">x=2*3 but x-1</code> returns 5.</p>
|
<p><code class="blue">but</code> is an infixed operator. Its operands can be expressions of any type. It evaluates the left expression first, then the right expression. The value of the right expression is the final result.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><code class="blue">but</code> is very similar to <code class="blue">;</code>. The only difference is that <code class="blue">;</code> can’t be used inside parenthesis <code class="blue">(</code> and <code class="blue">)</code>.</p>
|
<div class="title">Examples</div>
|
||||||
|
<p><code class="blue">5 but 2</code><br>
|
||||||
|
<code class="green">2</code><br>
|
||||||
|
<code class="blue">x=2*3 but x-1</code><br>
|
||||||
|
<code class="green">5</code>.</p>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p><code class="blue">but</code> behavior is very similar to <code class="blue">;</code>. The only difference is that <code class="blue">;</code> is not a true operator and can’t be used inside parenthesis <code class="blue">(</code> and <code class="blue">)</code>.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect2">
|
<div class="sect2">
|
||||||
<h3 id="_assignment_operator"><a class="anchor" href="#_assignment_operator"></a><a class="link" href="#_assignment_operator">4.3. Assignment operator <code class="blue">=</code></a></h3>
|
<h3 id="_assignment_operator"><a class="anchor" href="#_assignment_operator"></a><a class="link" href="#_assignment_operator">4.3. Assignment operator <code class="blue">=</code></a></h3>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p>The assignment operator <code class="blue">=</code> is used to define variables in the evaluation context or to change their value (see <em>ExprContext</em>).
|
<p>The assignment operator <code class="blue">=</code> is used to define variables or to change their value in the evaluation context (see <em>ExprContext</em>).</p>
|
||||||
The value on the left side of <code class="blue">=</code> must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.</p>
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p>The value on the left side of <code class="blue">=</code> must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<div class="title">Example</div>
|
<div class="title">Example</div>
|
||||||
@@ -1509,13 +1580,13 @@ These operators have a high priority, in particular higher than the operator <co
|
|||||||
<p>The table below shows all supported operators by decreasing priorities.</p>
|
<p>The table below shows all supported operators by decreasing priorities.</p>
|
||||||
</div>
|
</div>
|
||||||
<table class="tableblock frame-all grid-all stretch">
|
<table class="tableblock frame-all grid-all stretch">
|
||||||
<caption class="title">Table 7. Operators priorities</caption>
|
<caption class="title">Table 8. Operators priorities</caption>
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col style="width: 12.5%;">
|
<col style="width: 11.7647%;">
|
||||||
<col style="width: 12.5%;">
|
<col style="width: 11.7647%;">
|
||||||
<col style="width: 12.5%;">
|
<col style="width: 11.7647%;">
|
||||||
<col style="width: 31.25%;">
|
<col style="width: 29.4117%;">
|
||||||
<col style="width: 31.25%;">
|
<col style="width: 35.2942%;">
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -1528,11 +1599,17 @@ These operators have a high priority, in particular higher than the operator <co
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>ITEM</strong></p></td>
|
<td class="tableblock halign-center valign-top" rowspan="2"><p class="tableblock"><strong>ITEM</strong></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">.</code></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">.</code></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Item</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>List item</em></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>collection</em> <code>"."</code> <em>key</em> → <em>any</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> <code>"."</code> <em>integer</em> → <em>any</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">.</code></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Dict item</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>dict</em> <code>""</code> <em>any</em> → <em>any</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tableblock halign-center valign-top" rowspan="2"><p class="tableblock"><strong>INC</strong></p></td>
|
<td class="tableblock halign-center valign-top" rowspan="2"><p class="tableblock"><strong>INC</strong></p></td>
|
||||||
@@ -1605,7 +1682,7 @@ These operators have a high priority, in particular higher than the operator <co
|
|||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>integer</em> <code>"%"</code> <em>integer</em> → <em>integer</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>integer</em> <code>"%"</code> <em>integer</em> → <em>integer</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tableblock halign-center valign-top" rowspan="5"><p class="tableblock"><strong>SUM</strong></p></td>
|
<td class="tableblock halign-center valign-top" rowspan="6"><p class="tableblock"><strong>SUM</strong></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">+</code></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">+</code></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Sum</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Sum</em></p></td>
|
||||||
@@ -1624,6 +1701,12 @@ These operators have a high priority, in particular higher than the operator <co
|
|||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> <code>"+"</code> <em>list</em> → <em>list</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> <code>"+"</code> <em>list</em> → <em>list</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">+</code></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Dict-join</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>dict</em> <code>"+"</code> <em>dict</em> → <em>dict</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">-</code></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">-</code></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Subtraction</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Subtraction</em></p></td>
|
||||||
@@ -1636,7 +1719,7 @@ These operators have a high priority, in particular higher than the operator <co
|
|||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> <code>"-"</code> <em>list</em> → <em>list</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> <code>"-"</code> <em>list</em> → <em>list</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tableblock halign-center valign-top" rowspan="6"><p class="tableblock"><strong>RELATION</strong></p></td>
|
<td class="tableblock halign-center valign-top" rowspan="8"><p class="tableblock"><strong>RELATION</strong></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue"><</code></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue"><</code></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>less</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>less</em></p></td>
|
||||||
@@ -1673,6 +1756,18 @@ These operators have a high priority, in particular higher than the operator <co
|
|||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>comparable</em> <code>"!="</code> <em>comparable</em> → <em>boolean</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>comparable</em> <code>"!="</code> <em>comparable</em> → <em>boolean</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">in</code></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>member-of-list</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>any</em> <code>"in"</code> <em>list</em> → <em>boolean</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">in</code></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>key-of-dict</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>any</em> <code>"in"</code> <em>dict</em> → <em>boolean</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>NOT</strong></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>NOT</strong></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">not</code></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">not</code></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Prefix</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Prefix</em></p></td>
|
||||||
@@ -1706,13 +1801,25 @@ These operators have a high priority, in particular higher than the operator <co
|
|||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>boolean</em> <code>"||"</code> <em>boolean</em> → <em>boolean</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>boolean</em> <code>"||"</code> <em>boolean</em> → <em>boolean</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>ASSIGN</strong></p></td>
|
<td class="tableblock halign-center valign-top" rowspan="3"><p class="tableblock"><strong>ASSIGN</strong></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">=</code></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">=</code></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>assignment</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>assignment</em></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>identifier</em> "=" <em>any</em> → <em>any</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>identifier</em> "=" <em>any</em> → <em>any</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">>></code></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>front-insert</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>any</em> ">>" <em>list</em> → <em>list</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue"><<</code></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>back-insert</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> "<<" <em>any</em> → <em>list</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>BUT</strong></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>BUT</strong></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">but</code></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">but</code></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
||||||
@@ -1767,7 +1874,7 @@ These operators have a high priority, in particular higher than the operator <co
|
|||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<div id="footer-text">
|
<div id="footer-text">
|
||||||
Last updated 2024-05-17 15:47:29 +0200
|
Last updated 2024-05-20 09:40:23 +0200
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+2
-2
@@ -22,7 +22,7 @@ func TestExpr(t *testing.T) {
|
|||||||
/* 3 */ {`f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil},
|
/* 3 */ {`f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil},
|
||||||
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
|
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
|
||||||
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
|
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
|
||||||
/* 10 */ {`
|
/* 6 */ {`
|
||||||
ds={
|
ds={
|
||||||
"init":func(end){@end=end; @current=0 but true},
|
"init":func(end){@end=end; @current=0 but true},
|
||||||
"current":func(){current},
|
"current":func(){current},
|
||||||
@@ -44,7 +44,7 @@ func TestExpr(t *testing.T) {
|
|||||||
var gotResult any
|
var gotResult any
|
||||||
var gotErr error
|
var gotErr error
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
ctx := NewSimpleStore()
|
||||||
// ImportMathFuncs(ctx)
|
// ImportMathFuncs(ctx)
|
||||||
// ImportImportFunc(ctx)
|
// ImportImportFunc(ctx)
|
||||||
ImportOsFuncs(ctx)
|
ImportOsFuncs(ctx)
|
||||||
|
|||||||
@@ -4,17 +4,62 @@
|
|||||||
// formatter.go
|
// formatter.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
type FmtOpt uint16
|
type FmtOpt uint16
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TTY FmtOpt = 1 << iota
|
TTY FmtOpt = 1 << iota
|
||||||
MultiLine
|
MultiLine
|
||||||
|
Truncate
|
||||||
Base2
|
Base2
|
||||||
Base8
|
Base8
|
||||||
Base10
|
Base10
|
||||||
Base16
|
Base16
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TruncateEllipsis = "(...)"
|
||||||
|
MinTruncateSize = 10
|
||||||
|
TruncateSize = MinTruncateSize + 15
|
||||||
|
)
|
||||||
|
|
||||||
|
func TruncateString(s string) (trunc string) {
|
||||||
|
finalPart := len(s) - (MinTruncateSize - len(TruncateEllipsis))
|
||||||
|
trunc = s[0:len(s)-MinTruncateSize] + TruncateEllipsis + s[finalPart:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
ToString(options FmtOpt) string
|
ToString(options FmtOpt) string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getFormatted(v any, opt FmtOpt) (text string) {
|
||||||
|
if v == nil {
|
||||||
|
text = "(nil)"
|
||||||
|
} else if s, ok := v.(string); ok {
|
||||||
|
text = s
|
||||||
|
} else if formatter, ok := v.(Formatter); ok {
|
||||||
|
text = formatter.ToString(opt)
|
||||||
|
} else {
|
||||||
|
text = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Typer interface {
|
||||||
|
TypeName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTypeName(v any) (name string) {
|
||||||
|
if typer, ok := v.(Typer); ok {
|
||||||
|
name = typer.TypeName()
|
||||||
|
} else if IsInteger(v) {
|
||||||
|
name = "integer"
|
||||||
|
} else if IsFloat(v) {
|
||||||
|
name = "float"
|
||||||
|
} else {
|
||||||
|
name = fmt.Sprintf("%T", v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
+39
-28
@@ -54,6 +54,24 @@ func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func boolFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
switch v := args[0].(type) {
|
||||||
|
case int64:
|
||||||
|
result = (v != 0)
|
||||||
|
case *fraction:
|
||||||
|
result = v.num != 0
|
||||||
|
case float64:
|
||||||
|
result = v != 0.0
|
||||||
|
case bool:
|
||||||
|
result = v
|
||||||
|
case string:
|
||||||
|
result = len(v) > 0
|
||||||
|
default:
|
||||||
|
err = errCantConvert(name, v, "bool")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
switch v := args[0].(type) {
|
switch v := args[0].(type) {
|
||||||
case int64:
|
case int64:
|
||||||
@@ -119,10 +137,6 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
|
|||||||
}
|
}
|
||||||
case float64:
|
case float64:
|
||||||
result, err = float64ToFraction(v)
|
result, err = float64ToFraction(v)
|
||||||
// var n, d int64
|
|
||||||
// if n, d, err = float64ToFraction(v); err == nil {
|
|
||||||
// result = newFraction(n, d)
|
|
||||||
// }
|
|
||||||
case bool:
|
case bool:
|
||||||
if v {
|
if v {
|
||||||
result = newFraction(1, 1)
|
result = newFraction(1, 1)
|
||||||
@@ -131,16 +145,6 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
|
|||||||
}
|
}
|
||||||
case string:
|
case string:
|
||||||
result, err = makeGeneratingFraction(v)
|
result, err = makeGeneratingFraction(v)
|
||||||
// var f float64
|
|
||||||
// // TODO temporary implementation
|
|
||||||
// if f, err = strconv.ParseFloat(v, 64); err == nil {
|
|
||||||
// var n, d int64
|
|
||||||
// if n, d, err = float64ToFraction(f); err == nil {
|
|
||||||
// result = newFraction(n, d)
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// errors.New("convertion from string to float is ongoing")
|
|
||||||
// }
|
|
||||||
case *fraction:
|
case *fraction:
|
||||||
result = v
|
result = v
|
||||||
default:
|
default:
|
||||||
@@ -154,20 +158,27 @@ func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ImportBuiltinsFuncs(ctx ExprContext) {
|
func ImportBuiltinsFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, 1)
|
anyParams := []ExprFuncParam{
|
||||||
ctx.RegisterFunc("isInt", &simpleFunctor{f: isIntFunc}, 1, 1)
|
newFuncParam(paramValue),
|
||||||
ctx.RegisterFunc("isFloat", &simpleFunctor{f: isFloatFunc}, 1, 1)
|
}
|
||||||
ctx.RegisterFunc("isBool", &simpleFunctor{f: isBoolFunc}, 1, 1)
|
|
||||||
ctx.RegisterFunc("isString", &simpleFunctor{f: isStringFunc}, 1, 1)
|
ctx.RegisterFunc("isNil", newGolangFunctor(isNilFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("isFraction", &simpleFunctor{f: isFractionFunc}, 1, 1)
|
ctx.RegisterFunc("isInt", newGolangFunctor(isIntFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("isFract", &simpleFunctor{f: isFractionFunc}, 1, 1)
|
ctx.RegisterFunc("isFloat", newGolangFunctor(isFloatFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("isRational", &simpleFunctor{f: isRationalFunc}, 1, 1)
|
ctx.RegisterFunc("isBool", newGolangFunctor(isBoolFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("isList", &simpleFunctor{f: isListFunc}, 1, 1)
|
ctx.RegisterFunc("isString", newGolangFunctor(isStringFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("isDictionary", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
|
ctx.RegisterFunc("isFract", newGolangFunctor(isFractionFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("isDict", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
|
ctx.RegisterFunc("isRational", newGolangFunctor(isRationalFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, 1)
|
ctx.RegisterFunc("isList", newGolangFunctor(isListFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("dec", &simpleFunctor{f: decFunc}, 1, 1)
|
ctx.RegisterFunc("isDict", newGolangFunctor(isDictionaryFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("fract", &simpleFunctor{f: fractFunc}, 1, 2)
|
|
||||||
|
ctx.RegisterFunc("bool", newGolangFunctor(boolFunc), typeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("int", newGolangFunctor(intFunc), typeInt, anyParams)
|
||||||
|
ctx.RegisterFunc("dec", newGolangFunctor(decFunc), typeFloat, anyParams)
|
||||||
|
ctx.RegisterFunc("fract", newGolangFunctor(fractFunc), typeFraction, []ExprFuncParam{
|
||||||
|
newFuncParam(paramValue),
|
||||||
|
newFuncParamFlagDef("denominator", pfOptional, 1),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
+6
-2
@@ -137,8 +137,12 @@ func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (resu
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ImportImportFuncs(ctx ExprContext) {
|
func ImportImportFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
|
ctx.RegisterFunc("import", newGolangFunctor(importFunc), typeAny, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("importAll", &simpleFunctor{f: importAllFunc}, 1, -1)
|
newFuncParamFlag(typeFilepath, pfRepeat),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("importAll", newGolangFunctor(importAllFunc), typeAny, []ExprFuncParam{
|
||||||
|
newFuncParamFlag(typeFilepath, pfRepeat),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
+7
-2
@@ -167,8 +167,13 @@ func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ImportMathFuncs(ctx ExprContext) {
|
func ImportMathFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
|
ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, typeNumber, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
|
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, typeNumber, []ExprFuncParam{
|
||||||
|
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 1),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
+20
-6
@@ -158,12 +158,26 @@ func readFileFunc(ctx ExprContext, name string, args []any) (result any, err err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ImportOsFuncs(ctx ExprContext) {
|
func ImportOsFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("openFile", &simpleFunctor{f: openFileFunc}, 1, 1)
|
ctx.RegisterFunc("openFile", newGolangFunctor(openFileFunc), typeHandle, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1)
|
newFuncParam(typeFilepath),
|
||||||
ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1)
|
})
|
||||||
ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1)
|
ctx.RegisterFunc("appendFile", newGolangFunctor(appendFileFunc), typeHandle, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
|
newFuncParam(typeFilepath),
|
||||||
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
|
})
|
||||||
|
ctx.RegisterFunc("createFile", newGolangFunctor(createFileFunc), typeHandle, []ExprFuncParam{
|
||||||
|
newFuncParam(typeFilepath),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("writeFile", newGolangFunctor(writeFileFunc), typeInt, []ExprFuncParam{
|
||||||
|
newFuncParam(typeHandle),
|
||||||
|
newFuncParamFlagDef(typeItem, pfOptional|pfRepeat, ""),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("readFile", newGolangFunctor(readFileFunc), typeString, []ExprFuncParam{
|
||||||
|
newFuncParam(typeHandle),
|
||||||
|
newFuncParamFlagDef("limitCh", pfOptional, "\\n"),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("closeFile", newGolangFunctor(closeFileFunc), typeBoolean, []ExprFuncParam{
|
||||||
|
newFuncParam(typeHandle),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
+35
-24
@@ -33,9 +33,9 @@ func doJoinStr(funcName string, sep string, it Iterator) (result any, err error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
// if len(args) < 1 {
|
// if len(args) < 1 {
|
||||||
// return nil, errMissingRequiredParameter(name, paramSeparator)
|
// return nil, errMissingRequiredParameter(name, paramSeparator)
|
||||||
// }
|
// }
|
||||||
if sep, ok := args[0].(string); ok {
|
if sep, ok := args[0].(string); ok {
|
||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
result = ""
|
result = ""
|
||||||
@@ -62,9 +62,6 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
|
|||||||
var source string
|
var source string
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
// if len(args) < 1 {
|
|
||||||
// return nil, errMissingRequiredParameter(name, paramSource)
|
|
||||||
// }
|
|
||||||
if source, ok = args[0].(string); !ok {
|
if source, ok = args[0].(string); !ok {
|
||||||
return nil, errWrongParamType(name, paramSource, typeString, args[0])
|
return nil, errWrongParamType(name, paramSource, typeString, args[0])
|
||||||
}
|
}
|
||||||
@@ -93,9 +90,6 @@ func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err erro
|
|||||||
var source string
|
var source string
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
// if len(args) < 1 {
|
|
||||||
// return nil, errMissingRequiredParameter(name, paramSource)
|
|
||||||
// }
|
|
||||||
if source, ok = args[0].(string); !ok {
|
if source, ok = args[0].(string); !ok {
|
||||||
return nil, errWrongParamType(name, paramSource, typeString, args[0])
|
return nil, errWrongParamType(name, paramSource, typeString, args[0])
|
||||||
}
|
}
|
||||||
@@ -108,9 +102,7 @@ func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, er
|
|||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
result = false
|
result = false
|
||||||
// if len(args) < 1 {
|
|
||||||
// return result, errMissingRequiredParameter(name, paramSource)
|
|
||||||
// }
|
|
||||||
if source, ok = args[0].(string); !ok {
|
if source, ok = args[0].(string); !ok {
|
||||||
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
||||||
}
|
}
|
||||||
@@ -133,9 +125,7 @@ func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err
|
|||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
result = false
|
result = false
|
||||||
// if len(args) < 1 {
|
|
||||||
// return result, errMissingRequiredParameter(name, paramSource)
|
|
||||||
// }
|
|
||||||
if source, ok = args[0].(string); !ok {
|
if source, ok = args[0].(string); !ok {
|
||||||
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
||||||
}
|
}
|
||||||
@@ -159,9 +149,6 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
|
|||||||
var parts []string
|
var parts []string
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
// if len(args) < 1 {
|
|
||||||
// return result, errMissingRequiredParameter(name, paramSource)
|
|
||||||
// }
|
|
||||||
if source, ok = args[0].(string); !ok {
|
if source, ok = args[0].(string); !ok {
|
||||||
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
||||||
}
|
}
|
||||||
@@ -196,12 +183,36 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
|
|||||||
|
|
||||||
// Import above functions in the context
|
// Import above functions in the context
|
||||||
func ImportStringFuncs(ctx ExprContext) {
|
func ImportStringFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("joinStr", &simpleFunctor{f: joinStrFunc}, 1, -1)
|
ctx.RegisterFunc("joinStr", newGolangFunctor(joinStrFunc), typeString, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("subStr", &simpleFunctor{f: subStrFunc}, 1, -1)
|
newFuncParam(paramSeparator),
|
||||||
ctx.RegisterFunc("splitStr", &simpleFunctor{f: splitStrFunc}, 2, -1)
|
newFuncParamFlagDef(paramItem, pfOptional|pfRepeat, ""),
|
||||||
ctx.RegisterFunc("trimStr", &simpleFunctor{f: trimStrFunc}, 1, -1)
|
})
|
||||||
ctx.RegisterFunc("startsWithStr", &simpleFunctor{f: startsWithStrFunc}, 2, -1)
|
|
||||||
ctx.RegisterFunc("endsWithStr", &simpleFunctor{f: endsWithStrFunc}, 2, -1)
|
ctx.RegisterFunc("subStr", newGolangFunctor(subStrFunc), typeString, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSource),
|
||||||
|
newFuncParamFlagDef(paramStart, pfOptional, 0),
|
||||||
|
newFuncParamFlagDef(paramCount, pfOptional, -1),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("splitStr", newGolangFunctor(splitStrFunc), "list of "+typeString, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSource),
|
||||||
|
newFuncParamFlagDef(paramSeparator, pfOptional, ""),
|
||||||
|
newFuncParamFlagDef(paramCount, pfOptional, -1),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("trimStr", newGolangFunctor(trimStrFunc), typeString, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSource),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("startsWithStr", newGolangFunctor(startsWithStrFunc), typeBoolean, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSource),
|
||||||
|
newFuncParamFlag(paramPrefix, pfRepeat),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("endsWithStr", newGolangFunctor(endsWithStrFunc), typeBoolean, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSource),
|
||||||
|
newFuncParamFlag(paramSuffix, pfRepeat),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the import function in the import-register.
|
// Register the import function in the import-register.
|
||||||
|
|||||||
+32
-5
@@ -76,14 +76,41 @@ func TestFuncs(t *testing.T) {
|
|||||||
/* 63 */ {`dec(true)`, float64(1), nil},
|
/* 63 */ {`dec(true)`, float64(1), nil},
|
||||||
/* 64 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")},
|
/* 64 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")},
|
||||||
/* 65 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got int64 (1)`)},
|
/* 65 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got int64 (1)`)},
|
||||||
/* 65 */ {`dec()`, nil, errors.New(`too few params -- expected 1, got 0`)},
|
/* 66 */ {`dec()`, nil, errors.New(`too few params -- expected 1, got 0`)},
|
||||||
/* 66 */ {`dec(1,2,3)`, nil, errors.New(`too much params -- expected 1, got 3`)},
|
/* 67 */ {`dec(1,2,3)`, nil, errors.New(`too much params -- expected 1, got 3`)},
|
||||||
/* 67 */ {`builtin "string"; joinStr()`, nil, errors.New(`too few params -- expected 1 or more, got 0`)},
|
/* 68 */ {`builtin "string"; joinStr()`, nil, errors.New(`too few params -- expected 1 or more, got 0`)},
|
||||||
// /* 64 */ {`string(true)`, "true", nil},
|
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
|
||||||
|
|
||||||
|
}
|
||||||
|
funcs: {
|
||||||
|
add(any=0 ...) -> number,
|
||||||
|
dec(any) -> decimal,
|
||||||
|
endsWithStr(source, suffix) -> boolean,
|
||||||
|
fract(any, denominator=1) -> fraction,
|
||||||
|
import( ...) -> any,
|
||||||
|
importAll( ...) -> any,
|
||||||
|
int(any) -> integer,
|
||||||
|
isBool(any) -> boolean,
|
||||||
|
isDec(any) -> boolean,
|
||||||
|
isDict(any) -> boolean,
|
||||||
|
isFloat(any) -> boolean,
|
||||||
|
isFract(any) -> boolean,
|
||||||
|
isInt(any) -> boolean,
|
||||||
|
isList(any) -> boolean,
|
||||||
|
isNil(any) -> boolean,
|
||||||
|
isString(any) -> boolean,
|
||||||
|
joinStr(separator, item="" ...) -> string,
|
||||||
|
mul(any=1 ...) -> number,
|
||||||
|
splitStr(source, separator="", count=-1) -> list of string,
|
||||||
|
startsWithStr(source, prefix) -> boolean,
|
||||||
|
subStr(source, start=0, count=-1) -> string,
|
||||||
|
trimStr(source) -> string
|
||||||
|
}
|
||||||
|
`, nil},*/
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Setenv("EXPR_PATH", ".")
|
t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
//parserTest(t, "Func", inputs[54:55])
|
// parserTestSpec(t, "Func", inputs, 69)
|
||||||
parserTest(t, "Func", inputs)
|
parserTest(t, "Func", inputs)
|
||||||
}
|
}
|
||||||
|
|||||||
+232
@@ -0,0 +1,232 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// function.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---- Function template
|
||||||
|
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
|
||||||
|
|
||||||
|
// ---- Common functor definition
|
||||||
|
type baseFunctor struct {
|
||||||
|
info ExprFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *baseFunctor) ToString(opt FmtOpt) (s string) {
|
||||||
|
if functor.info != nil {
|
||||||
|
s = functor.info.ToString(opt)
|
||||||
|
} else {
|
||||||
|
s = "func() {<body>}"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *baseFunctor) SetFunc(info ExprFunc) {
|
||||||
|
functor.info = info
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *baseFunctor) GetFunc() ExprFunc {
|
||||||
|
return functor.info
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Linking with the functions of Go
|
||||||
|
type golangFunctor struct {
|
||||||
|
baseFunctor
|
||||||
|
f FuncTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGolangFunctor(f FuncTemplate) *golangFunctor {
|
||||||
|
return &golangFunctor{f: f}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
return functor.f(ctx, name, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Linking with the functions of Expr
|
||||||
|
type exprFunctor struct {
|
||||||
|
baseFunctor
|
||||||
|
params []string
|
||||||
|
expr Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExprFunctor(e Expr, params []string) *exprFunctor {
|
||||||
|
return &exprFunctor{expr: e, params: params}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
for i, p := range functor.params {
|
||||||
|
if i < len(args) {
|
||||||
|
arg := args[i]
|
||||||
|
if funcArg, ok := arg.(Functor); ok {
|
||||||
|
// ctx.RegisterFunc(p, functor, 0, -1)
|
||||||
|
ctx.RegisterFunc(p, funcArg, typeAny, []ExprFuncParam{
|
||||||
|
newFuncParam(paramValue),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ctx.UnsafeSetVar(p, arg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.UnsafeSetVar(p, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result, err = functor.expr.eval(ctx, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Function Parameters
|
||||||
|
type paramFlags uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
pfOptional paramFlags = 1 << iota
|
||||||
|
pfRepeat
|
||||||
|
)
|
||||||
|
|
||||||
|
type funcParamInfo struct {
|
||||||
|
name string
|
||||||
|
flags paramFlags
|
||||||
|
defaultValue any
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncParam(name string) ExprFuncParam {
|
||||||
|
return &funcParamInfo{name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncParamFlag(name string, flags paramFlags) ExprFuncParam {
|
||||||
|
return &funcParamInfo{name: name, flags: flags}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo {
|
||||||
|
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) Name() string {
|
||||||
|
return param.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) Type() string {
|
||||||
|
return "any"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) IsOptional() bool {
|
||||||
|
return (param.flags & pfOptional) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) IsRepeat() bool {
|
||||||
|
return (param.flags & pfRepeat) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) DefaultValue() any {
|
||||||
|
return param.defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Functions
|
||||||
|
type funcInfo struct {
|
||||||
|
name string
|
||||||
|
minArgs int
|
||||||
|
maxArgs int
|
||||||
|
functor Functor
|
||||||
|
params []ExprFuncParam
|
||||||
|
returnType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
|
||||||
|
var minArgs = 0
|
||||||
|
var maxArgs = 0
|
||||||
|
if params != nil {
|
||||||
|
for _, p := range params {
|
||||||
|
if maxArgs == -1 {
|
||||||
|
return nil, fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
|
||||||
|
}
|
||||||
|
if p.IsOptional() {
|
||||||
|
maxArgs++
|
||||||
|
} else if maxArgs == minArgs {
|
||||||
|
minArgs++
|
||||||
|
maxArgs++
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
|
||||||
|
}
|
||||||
|
if p.IsRepeat() {
|
||||||
|
maxArgs = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info = &funcInfo{
|
||||||
|
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params,
|
||||||
|
}
|
||||||
|
functor.SetFunc(info)
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUnnamedFuncInfo(functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
|
||||||
|
return newFuncInfo("unnamed", functor, returnType, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) Params() []ExprFuncParam {
|
||||||
|
return info.params
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) ReturnType() string {
|
||||||
|
return info.returnType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
if len(info.Name()) == 0 {
|
||||||
|
sb.WriteString("func")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(info.Name())
|
||||||
|
}
|
||||||
|
sb.WriteByte('(')
|
||||||
|
if info.params != nil {
|
||||||
|
for i, p := range info.params {
|
||||||
|
if i > 0 {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
sb.WriteString(p.Name())
|
||||||
|
|
||||||
|
if p.IsOptional() {
|
||||||
|
sb.WriteByte('=')
|
||||||
|
if s, ok := p.DefaultValue().(string); ok {
|
||||||
|
sb.WriteByte('"')
|
||||||
|
sb.WriteString(s)
|
||||||
|
sb.WriteByte('"')
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", p.DefaultValue()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if info.maxArgs < 0 {
|
||||||
|
sb.WriteString(" ...")
|
||||||
|
}
|
||||||
|
sb.WriteString("): ")
|
||||||
|
if len(info.returnType) > 0 {
|
||||||
|
sb.WriteString(info.returnType)
|
||||||
|
} else {
|
||||||
|
sb.WriteString(typeAny)
|
||||||
|
}
|
||||||
|
sb.WriteString(" {<body>}")
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) Name() string {
|
||||||
|
return info.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) MinArgs() int {
|
||||||
|
return info.minArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) MaxArgs() int {
|
||||||
|
return info.maxArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) Functor() Functor {
|
||||||
|
return info.functor
|
||||||
|
}
|
||||||
+3
-2
@@ -6,7 +6,7 @@ package expr
|
|||||||
|
|
||||||
import "path/filepath"
|
import "path/filepath"
|
||||||
|
|
||||||
var globalCtx *SimpleFuncStore
|
var globalCtx *SimpleStore
|
||||||
|
|
||||||
func ImportInContext(name string) (exists bool) {
|
func ImportInContext(name string) (exists bool) {
|
||||||
var mod *module
|
var mod *module
|
||||||
@@ -50,5 +50,6 @@ func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, owne
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
globalCtx = NewSimpleFuncStore()
|
globalCtx = NewSimpleStore()
|
||||||
|
ImportBuiltinsFuncs(globalCtx)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -34,12 +34,15 @@ func EvalStringA(source string, args ...Arg) (result any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EvalStringV(source string, args []Arg) (result any, err error) {
|
func EvalStringV(source string, args []Arg) (result any, err error) {
|
||||||
ctx := NewSimpleFuncStore()
|
ctx := NewSimpleStore()
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
if isFunc(arg.Value) {
|
if isFunc(arg.Value) {
|
||||||
if f, ok := arg.Value.(FuncTemplate); ok {
|
if f, ok := arg.Value.(FuncTemplate); ok {
|
||||||
functor := &simpleFunctor{f: f}
|
functor := newGolangFunctor(f)
|
||||||
ctx.RegisterFunc(arg.Name, functor, 0, -1)
|
// ctx.RegisterFunc(arg.Name, functor, 0, -1)
|
||||||
|
ctx.RegisterFunc(arg.Name, functor, typeAny, []ExprFuncParam{
|
||||||
|
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("invalid function specification: %q", arg.Name)
|
err = fmt.Errorf("invalid function specification: %q", arg.Name)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -52,7 +52,7 @@ func TestEvalStringA(t *testing.T) {
|
|||||||
|
|
||||||
func TestEvalString(t *testing.T) {
|
func TestEvalString(t *testing.T) {
|
||||||
|
|
||||||
ctx := NewSimpleVarStore()
|
ctx := NewSimpleStore()
|
||||||
ctx.SetVar("a", uint8(1))
|
ctx.SetVar("a", uint8(1))
|
||||||
ctx.SetVar("b", int8(2))
|
ctx.SetVar("b", int8(2))
|
||||||
ctx.SetVar("f", 2.0)
|
ctx.SetVar("f", 2.0)
|
||||||
|
|||||||
+3
-1
@@ -41,6 +41,8 @@ func TestListParser(t *testing.T) {
|
|||||||
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
|
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
|
||||||
/* 20 */ {`#["a", "b", "c"]`, int64(3), nil},
|
/* 20 */ {`#["a", "b", "c"]`, int64(3), nil},
|
||||||
/* 21 */ {`"b" in ["a", "b", "c"]`, true, nil},
|
/* 21 */ {`"b" in ["a", "b", "c"]`, true, nil},
|
||||||
|
/* 22 */ {`a=[1,2]; (a)<<3`, []any{1, 2, 3}, nil},
|
||||||
|
/* 23 */ {`a=[1,2]; (a)<<3; 1`, []any{1, 2}, nil},
|
||||||
|
|
||||||
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil},
|
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil},
|
||||||
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},
|
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},
|
||||||
@@ -58,7 +60,7 @@ func TestListParser(t *testing.T) {
|
|||||||
var gotResult any
|
var gotResult any
|
||||||
var gotErr error
|
var gotErr error
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
ctx := NewSimpleStore()
|
||||||
// ctx.SetVar("var1", int64(123))
|
// ctx.SetVar("var1", int64(123))
|
||||||
// ctx.SetVar("var2", "abc")
|
// ctx.SetVar("var2", "abc")
|
||||||
ImportMathFuncs(ctx)
|
ImportMathFuncs(ctx)
|
||||||
|
|||||||
+121
-2
@@ -4,6 +4,125 @@
|
|||||||
// operand-dict.go
|
// operand-dict.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DictType map[any]any
|
||||||
|
|
||||||
|
func newDict(dictAny map[any]*term) (dict *DictType) {
|
||||||
|
var d DictType
|
||||||
|
if dictAny != nil {
|
||||||
|
d = make(DictType, len(dictAny))
|
||||||
|
for i, item := range dictAny {
|
||||||
|
d[i] = item
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d = make(DictType)
|
||||||
|
}
|
||||||
|
dict = &d
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) toMultiLine(sb *strings.Builder, indent int) {
|
||||||
|
sb.WriteString(strings.Repeat("\t", indent))
|
||||||
|
sb.WriteString("{\n")
|
||||||
|
|
||||||
|
first := true
|
||||||
|
for name, value := range *dict {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
sb.WriteByte(',')
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||||
|
if key, ok := name.(string); ok {
|
||||||
|
sb.WriteString(string('"') + key + string('"'))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", name))
|
||||||
|
}
|
||||||
|
sb.WriteString(": ")
|
||||||
|
if f, ok := value.(Formatter); ok {
|
||||||
|
sb.WriteString(f.ToString(MultiLine))
|
||||||
|
} else if _, ok = value.(Functor); ok {
|
||||||
|
sb.WriteString("func(){}")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString(strings.Repeat("\t", indent))
|
||||||
|
sb.WriteString("\n}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
if opt&MultiLine != 0 {
|
||||||
|
dict.toMultiLine(&sb, 0)
|
||||||
|
} else {
|
||||||
|
sb.WriteByte('{')
|
||||||
|
first := true
|
||||||
|
for key, value := range *dict {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
if s, ok := key.(string); ok {
|
||||||
|
sb.WriteString(string('"') + s + string('"'))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", key))
|
||||||
|
}
|
||||||
|
sb.WriteString(": ")
|
||||||
|
if formatter, ok := value.(Formatter); ok {
|
||||||
|
sb.WriteString(formatter.ToString(opt))
|
||||||
|
} else if t, ok := value.(*term); ok {
|
||||||
|
sb.WriteString(t.String())
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%#v", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteByte('}')
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) String() string {
|
||||||
|
return dict.ToString(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) TypeName() string {
|
||||||
|
return "dict"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) hasKey(target any) (ok bool) {
|
||||||
|
for key := range *dict {
|
||||||
|
if ok = reflect.DeepEqual(key, target); ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) clone() (c *DictType) {
|
||||||
|
c = newDict(nil)
|
||||||
|
for k, v := range *dict {
|
||||||
|
(*c)[k] = v
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) merge(second *DictType) {
|
||||||
|
if second != nil {
|
||||||
|
for k, v := range *second {
|
||||||
|
(*dict)[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -------- dict term
|
// -------- dict term
|
||||||
func newDictTerm(args map[any]*term) *term {
|
func newDictTerm(args map[any]*term) *term {
|
||||||
return &term{
|
return &term{
|
||||||
@@ -19,7 +138,7 @@ func newDictTerm(args map[any]*term) *term {
|
|||||||
// -------- dict func
|
// -------- dict func
|
||||||
func evalDict(ctx ExprContext, self *term) (v any, err error) {
|
func evalDict(ctx ExprContext, self *term) (v any, err error) {
|
||||||
dict, _ := self.value().(map[any]*term)
|
dict, _ := self.value().(map[any]*term)
|
||||||
items := make(map[any]any, len(dict))
|
items := make(DictType, len(dict))
|
||||||
for key, tree := range dict {
|
for key, tree := range dict {
|
||||||
var param any
|
var param any
|
||||||
if param, err = tree.compute(ctx); err != nil {
|
if param, err = tree.compute(ctx); err != nil {
|
||||||
@@ -28,7 +147,7 @@ func evalDict(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
items[key] = param
|
items[key] = param
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
v = items
|
v = &items
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -7,7 +7,8 @@ package expr
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// -------- expr term
|
// -------- expr term
|
||||||
func newExprTerm(tk *Token) *term {
|
func newExprTerm(root *term) *term {
|
||||||
|
tk := NewValueToken(root.tk.row, root.tk.col, SymExpression, root.source(), root)
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
parent: nil,
|
parent: nil,
|
||||||
@@ -20,8 +21,8 @@ func newExprTerm(tk *Token) *term {
|
|||||||
|
|
||||||
// -------- eval expr
|
// -------- eval expr
|
||||||
func evalExpr(ctx ExprContext, self *term) (v any, err error) {
|
func evalExpr(ctx ExprContext, self *term) (v any, err error) {
|
||||||
if expr, ok := self.value().(Expr); ok {
|
if expr, ok := self.value().(*term); ok {
|
||||||
v, err = expr.eval(ctx, false)
|
v, err = expr.compute(ctx)
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("expression expected, got %T", self.value())
|
err = fmt.Errorf("expression expected, got %T", self.value())
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-25
@@ -31,7 +31,8 @@ func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
|
|||||||
err = errTooMuchParams(info.MaxArgs(), len(params))
|
err = errTooMuchParams(info.MaxArgs(), len(params))
|
||||||
}
|
}
|
||||||
if err == nil && owner != ctx {
|
if err == nil && owner != ctx {
|
||||||
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
// ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
||||||
|
ctx.RegisterFuncInfo(info)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("unknown function %s()", name)
|
err = fmt.Errorf("unknown function %s()", name)
|
||||||
@@ -75,27 +76,30 @@ func newFuncDefTerm(tk *Token, args []*term) *term {
|
|||||||
|
|
||||||
// -------- eval func def
|
// -------- eval func def
|
||||||
// TODO
|
// TODO
|
||||||
type funcDefFunctor struct {
|
// type funcDefFunctor struct {
|
||||||
params []string
|
// params []string
|
||||||
expr Expr
|
// expr Expr
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (functor *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
// func (funcDef *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
for i, p := range functor.params {
|
// for i, p := range funcDef.params {
|
||||||
if i < len(args) {
|
// if i < len(args) {
|
||||||
arg := args[i]
|
// arg := args[i]
|
||||||
if functor, ok := arg.(Functor); ok {
|
// if functor, ok := arg.(Functor); ok {
|
||||||
ctx.RegisterFunc(p, functor, 0, -1)
|
// // ctx.RegisterFunc(p, functor, 0, -1)
|
||||||
} else {
|
// ctx.RegisterFunc2(p, functor, typeAny, []ExprFuncParam{
|
||||||
ctx.setVar(p, arg)
|
// newFuncParam(paramValue),
|
||||||
}
|
// })
|
||||||
} else {
|
// } else {
|
||||||
ctx.setVar(p, nil)
|
// ctx.UnsafeSetVar(p, arg)
|
||||||
}
|
// }
|
||||||
}
|
// } else {
|
||||||
result, err = functor.expr.eval(ctx, false)
|
// ctx.UnsafeSetVar(p, nil)
|
||||||
return
|
// }
|
||||||
}
|
// }
|
||||||
|
// result, err = funcDef.expr.eval(ctx, false)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
|
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
|
||||||
bodySpec := self.value()
|
bodySpec := self.value()
|
||||||
@@ -104,10 +108,11 @@ func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
for _, param := range self.children {
|
for _, param := range self.children {
|
||||||
paramList = append(paramList, param.source())
|
paramList = append(paramList, param.source())
|
||||||
}
|
}
|
||||||
v = &funcDefFunctor{
|
v = newExprFunctor(expr, paramList)
|
||||||
params: paramList,
|
// v = &funcDefFunctor{
|
||||||
expr: expr,
|
// params: paramList,
|
||||||
}
|
// expr: expr,
|
||||||
|
// }
|
||||||
} else {
|
} else {
|
||||||
err = errors.New("invalid function definition: the body specification must be an expression")
|
err = errors.New("invalid function definition: the body specification must be an expression")
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -66,14 +66,16 @@ func evalFirstChild(ctx ExprContext, self *term) (value any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map[string]Functor, err error) {
|
func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map[string]Functor, err error) {
|
||||||
if dictAny, ok := firstChildValue.(map[any]any); ok {
|
// if dictAny, ok := firstChildValue.(map[any]any); ok {
|
||||||
|
if dictAny, ok := firstChildValue.(*DictType); ok {
|
||||||
requiredFields := []string{currentName, nextName}
|
requiredFields := []string{currentName, nextName}
|
||||||
fieldsMask := 0b11
|
fieldsMask := 0b11
|
||||||
foundFields := 0
|
foundFields := 0
|
||||||
ds = make(map[string]Functor)
|
ds = make(map[string]Functor)
|
||||||
for keyAny, item := range dictAny {
|
for keyAny, item := range *dictAny {
|
||||||
if key, ok := keyAny.(string); ok {
|
if key, ok := keyAny.(string); ok {
|
||||||
if functor, ok := item.(*funcDefFunctor); ok {
|
//if functor, ok := item.(*funcDefFunctor); ok {
|
||||||
|
if functor, ok := item.(Functor); ok {
|
||||||
ds[key] = functor
|
ds[key] = functor
|
||||||
if index := slices.Index(requiredFields, key); index >= 0 {
|
if index := slices.Index(requiredFields, key); index >= 0 {
|
||||||
foundFields |= 1 << index
|
foundFields |= 1 << index
|
||||||
|
|||||||
+23
-15
@@ -12,7 +12,22 @@ import (
|
|||||||
|
|
||||||
type ListType []any
|
type ListType []any
|
||||||
|
|
||||||
func (ls *ListType) ToString(opt FmtOpt) string {
|
func newListA(listAny ...any) (list *ListType) {
|
||||||
|
return newList(listAny)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newList(listAny []any) (list *ListType) {
|
||||||
|
if listAny != nil {
|
||||||
|
ls := make(ListType, len(listAny))
|
||||||
|
for i, item := range listAny {
|
||||||
|
ls[i] = item
|
||||||
|
}
|
||||||
|
list = &ls
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *ListType) ToString(opt FmtOpt) (s string) {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.WriteByte('[')
|
sb.WriteByte('[')
|
||||||
if len(*ls) > 0 {
|
if len(*ls) > 0 {
|
||||||
@@ -40,26 +55,19 @@ func (ls *ListType) ToString(opt FmtOpt) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sb.WriteByte(']')
|
sb.WriteByte(']')
|
||||||
return sb.String()
|
s = sb.String()
|
||||||
|
if opt&Truncate != 0 && len(s) > TruncateSize {
|
||||||
|
s = TruncateString(s)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ls *ListType) String() string {
|
func (ls *ListType) String() string {
|
||||||
return ls.ToString(0)
|
return ls.ToString(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newListA(listAny ...any) (list *ListType) {
|
func (ls *ListType) TypeName() string {
|
||||||
return newList(listAny)
|
return "list"
|
||||||
}
|
|
||||||
|
|
||||||
func newList(listAny []any) (list *ListType) {
|
|
||||||
if listAny != nil {
|
|
||||||
ls := make(ListType, len(listAny))
|
|
||||||
for i, item := range listAny {
|
|
||||||
ls[i] = item
|
|
||||||
}
|
|
||||||
list = &ls
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *ListType) indexDeepCmp(target any) (index int) {
|
func (list *ListType) indexDeepCmp(target any) (index int) {
|
||||||
|
|||||||
+3
-1
@@ -8,7 +8,7 @@ import "fmt"
|
|||||||
|
|
||||||
// -------- variable term
|
// -------- variable term
|
||||||
func newVarTerm(tk *Token) *term {
|
func newVarTerm(tk *Token) *term {
|
||||||
return &term{
|
t := &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classVar,
|
// class: classVar,
|
||||||
// kind: kindUnknown,
|
// kind: kindUnknown,
|
||||||
@@ -18,6 +18,8 @@ func newVarTerm(tk *Token) *term {
|
|||||||
priority: priValue,
|
priority: priValue,
|
||||||
evalFunc: evalVar,
|
evalFunc: evalVar,
|
||||||
}
|
}
|
||||||
|
t.tk.Sym = SymVariable
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- eval func
|
// -------- eval func
|
||||||
|
|||||||
+16
-13
@@ -22,7 +22,7 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
leftTerm := self.children[0]
|
leftTerm := self.children[0]
|
||||||
if leftTerm.tk.Sym != SymIdentifier {
|
if leftTerm.tk.Sym != SymVariable {
|
||||||
err = leftTerm.tk.Errorf("left operand of %q must be a variable", self.tk.source)
|
err = leftTerm.tk.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -31,20 +31,23 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
|
|
||||||
if v, err = rightChild.compute(ctx); err == nil {
|
if v, err = rightChild.compute(ctx); err == nil {
|
||||||
if functor, ok := v.(Functor); ok {
|
if functor, ok := v.(Functor); ok {
|
||||||
var minArgs, maxArgs int = 0, -1
|
|
||||||
|
|
||||||
funcName := rightChild.source()
|
funcName := rightChild.source()
|
||||||
if info, exists := ctx.GetFuncInfo(funcName); exists {
|
if info, exists, _ := GetFuncInfo(ctx, funcName); exists {
|
||||||
minArgs = info.MinArgs()
|
// ctx.RegisterFuncInfo(info)
|
||||||
maxArgs = info.MaxArgs()
|
ctx.RegisterFunc(leftTerm.source(), info.Functor(), info.ReturnType(), info.Params())
|
||||||
} else if funcDef, ok := functor.(*funcDefFunctor); ok {
|
} else if funcDef, ok := functor.(*exprFunctor); ok {
|
||||||
l := len(funcDef.params)
|
paramSpecs := ForAll(funcDef.params, newFuncParam)
|
||||||
minArgs = l
|
// paramCount := len(funcDef.params)
|
||||||
maxArgs = l
|
// paramSpecs := make([]ExprFuncParam, paramCount)
|
||||||
}
|
// for i := range paramSpecs {
|
||||||
ctx.RegisterFunc(leftTerm.source(), functor, minArgs, maxArgs)
|
// paramSpecs[i] = newFuncParam(funcDef.params[i])
|
||||||
|
// }
|
||||||
|
ctx.RegisterFunc(leftTerm.source(), functor, typeAny, paramSpecs)
|
||||||
} else {
|
} else {
|
||||||
ctx.setVar(leftTerm.source(), v)
|
err = self.Errorf("unknown function %s()", funcName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.UnsafeSetVar(leftTerm.source(), v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
+7
-15
@@ -9,8 +9,6 @@ package expr
|
|||||||
func newNullCoalesceTerm(tk *Token) (inst *term) {
|
func newNullCoalesceTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindUnknown,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priCoalesce,
|
priority: priCoalesce,
|
||||||
@@ -26,7 +24,7 @@ func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
leftTerm := self.children[0]
|
leftTerm := self.children[0]
|
||||||
if leftTerm.tk.Sym != SymIdentifier {
|
if leftTerm.tk.Sym != SymVariable {
|
||||||
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -34,11 +32,7 @@ func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
||||||
v = leftValue
|
v = leftValue
|
||||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||||
// if _, ok := rightValue.(Functor); ok {
|
|
||||||
// err = errCoalesceNoFunc(self.children[1])
|
|
||||||
// } else {
|
|
||||||
v = rightValue
|
v = rightValue
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -63,7 +57,7 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
leftTerm := self.children[0]
|
leftTerm := self.children[0]
|
||||||
if leftTerm.tk.Sym != SymIdentifier {
|
if leftTerm.tk.Sym != SymVariable {
|
||||||
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -72,20 +66,18 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
v = leftValue
|
v = leftValue
|
||||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||||
if functor, ok := rightValue.(Functor); ok {
|
if functor, ok := rightValue.(Functor); ok {
|
||||||
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
|
//ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
|
||||||
|
ctx.RegisterFunc(leftTerm.source(), functor, typeAny, []ExprFuncParam{
|
||||||
|
newFuncParamFlag(paramValue, pfOptional|pfRepeat),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
v = rightValue
|
v = rightValue
|
||||||
ctx.setVar(leftTerm.source(), rightValue)
|
ctx.UnsafeSetVar(leftTerm.source(), rightValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// utils
|
|
||||||
// func errCoalesceNoFunc(t *term) error {
|
|
||||||
// return t.Errorf("the right operand of a coalescing operation cannot be a function definition")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// init
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
registerTermConstructor(SymDoubleQuestion, newNullCoalesceTerm)
|
registerTermConstructor(SymDoubleQuestion, newNullCoalesceTerm)
|
||||||
|
|||||||
+4
-4
@@ -20,18 +20,18 @@ func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
var childValue any
|
var childValue any
|
||||||
|
|
||||||
var sourceCtx ExprContext
|
var sourceCtx ExprContext
|
||||||
if len(self.children) == 0 {
|
if self.children == nil || len(self.children) == 0 {
|
||||||
sourceCtx = ctx
|
sourceCtx = ctx
|
||||||
|
} else if self.children[0].symbol() == SymVariable && self.children[0].source() == "global" {
|
||||||
|
sourceCtx = globalCtx
|
||||||
} else if childValue, err = self.evalPrefix(ctx); err == nil {
|
} else if childValue, err = self.evalPrefix(ctx); err == nil {
|
||||||
if dc, ok := childValue.(*dataCursor); ok {
|
if dc, ok := childValue.(*dataCursor); ok {
|
||||||
sourceCtx = dc.ctx
|
sourceCtx = dc.ctx
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if sourceCtx != nil {
|
if sourceCtx != nil {
|
||||||
if formatter, ok := ctx.(Formatter); ok {
|
if formatter, ok := sourceCtx.(Formatter); ok {
|
||||||
v = formatter.ToString(0)
|
v = formatter.ToString(0)
|
||||||
} else {
|
} else {
|
||||||
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
|
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
|
||||||
|
|||||||
+3
-15
@@ -59,28 +59,16 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
|
if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
|
||||||
v = string(unboxedValue[index])
|
v = string(unboxedValue[index])
|
||||||
}
|
}
|
||||||
case map[any]any:
|
case *DictType:
|
||||||
var ok bool
|
var ok bool
|
||||||
var indexValue any
|
var indexValue any
|
||||||
if indexValue, err = indexTerm.compute(ctx); err == nil {
|
if indexValue, err = indexTerm.compute(ctx); err == nil {
|
||||||
if v, ok = unboxedValue[indexValue]; !ok {
|
if v, ok = (*unboxedValue)[indexValue]; !ok {
|
||||||
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
|
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// case *dataCursor:
|
|
||||||
// if indexTerm.symbol() == SymIdentifier {
|
|
||||||
// opName := indexTerm.source()
|
|
||||||
// if opName == resetName {
|
|
||||||
// _, err = unboxedValue.Reset()
|
|
||||||
// } else if opName == cleanName {
|
|
||||||
// _, err = unboxedValue.Clean()
|
|
||||||
// } else {
|
|
||||||
// err = indexTerm.Errorf("iterators do not support command %q", opName)
|
|
||||||
// }
|
|
||||||
// v = err == nil
|
|
||||||
// }
|
|
||||||
case ExtIterator:
|
case ExtIterator:
|
||||||
if indexTerm.symbol() == SymIdentifier {
|
if indexTerm.symbol() == SymVariable {
|
||||||
opName := indexTerm.source()
|
opName := indexTerm.source()
|
||||||
if unboxedValue.HasOperation(opName) {
|
if unboxedValue.HasOperation(opName) {
|
||||||
v, err = unboxedValue.CallOperation(opName, []any{})
|
v, err = unboxedValue.CallOperation(opName, []any{})
|
||||||
|
|||||||
@@ -204,6 +204,10 @@ func (f *fraction) ToString(opt FmtOpt) string {
|
|||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fraction) TypeName() string {
|
||||||
|
return "fraction"
|
||||||
|
}
|
||||||
|
|
||||||
// -------- fraction term
|
// -------- fraction term
|
||||||
func newFractionTerm(tk *Token) *term {
|
func newFractionTerm(tk *Token) *term {
|
||||||
return &term{
|
return &term{
|
||||||
|
|||||||
+6
-6
@@ -16,10 +16,10 @@ func newInTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasKey(d map[any]any, target any) (ok bool) {
|
// func hasKey(d map[any]any, target any) (ok bool) {
|
||||||
_, ok = d[target]
|
// _, ok = d[target]
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
func evalIn(ctx ExprContext, self *term) (v any, err error) {
|
func evalIn(ctx ExprContext, self *term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
@@ -32,8 +32,8 @@ func evalIn(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
list, _ := rightValue.(*ListType)
|
list, _ := rightValue.(*ListType)
|
||||||
v = list.indexDeepCmp(leftValue) >= 0
|
v = list.indexDeepCmp(leftValue) >= 0
|
||||||
} else if IsDict(rightValue) {
|
} else if IsDict(rightValue) {
|
||||||
d, _ := rightValue.(map[any]any)
|
dict, _ := rightValue.(*DictType)
|
||||||
v = hasKey(d, leftValue)
|
v = dict.hasKey(leftValue)
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ func evalInsert(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
list, _ := rightValue.(*ListType)
|
list, _ := rightValue.(*ListType)
|
||||||
newList := append(ListType{leftValue}, *list...)
|
newList := append(ListType{leftValue}, *list...)
|
||||||
v = &newList
|
v = &newList
|
||||||
|
if self.children[1].symbol() == SymVariable {
|
||||||
|
ctx.UnsafeSetVar(self.children[1].source(), v)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
@@ -54,12 +57,33 @@ func evalAppend(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
list, _ := leftValue.(*ListType)
|
list, _ := leftValue.(*ListType)
|
||||||
newList := append(*list, rightValue)
|
newList := append(*list, rightValue)
|
||||||
v = &newList
|
v = &newList
|
||||||
|
if self.children[0].symbol() == SymVariable {
|
||||||
|
ctx.UnsafeSetVar(self.children[0].source(), v)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func evalAssignAppend(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
// var leftValue, rightValue any
|
||||||
|
|
||||||
|
// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if IsList(leftValue) {
|
||||||
|
// list, _ := leftValue.(*ListType)
|
||||||
|
// newList := append(*list, rightValue)
|
||||||
|
// v = &newList
|
||||||
|
// if
|
||||||
|
// } else {
|
||||||
|
// err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
// init
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
registerTermConstructor(SymInsert, newInsertTerm)
|
registerTermConstructor(SymInsert, newInsertTerm)
|
||||||
|
|||||||
+3
-2
@@ -30,8 +30,9 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
s, _ := childValue.(string)
|
s, _ := childValue.(string)
|
||||||
v = int64(len(s))
|
v = int64(len(s))
|
||||||
} else if IsDict(childValue) {
|
} else if IsDict(childValue) {
|
||||||
m, _ := childValue.(map[any]any)
|
// m, _ := childValue.(map[any]any)
|
||||||
v = int64(len(m))
|
m, _ := childValue.(*DictType)
|
||||||
|
v = int64(len(*m))
|
||||||
} else if it, ok := childValue.(Iterator); ok {
|
} else if it, ok := childValue.(Iterator); ok {
|
||||||
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
|
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
|
||||||
count, _ := extIt.CallOperation(countName, nil)
|
count, _ := extIt.CallOperation(countName, nil)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
|
|
||||||
if it, ok := childValue.(Iterator); ok {
|
if it, ok := childValue.(Iterator); ok {
|
||||||
v, err = it.Next()
|
v, err = it.Next()
|
||||||
} else if IsInteger(childValue) && self.children[0].symbol() == SymIdentifier {
|
} else if IsInteger(childValue) && self.children[0].symbol() == SymVariable {
|
||||||
v = childValue
|
v = childValue
|
||||||
i, _ := childValue.(int64)
|
i, _ := childValue.(int64)
|
||||||
ctx.SetVar(self.children[0].source(), i+1)
|
ctx.SetVar(self.children[0].source(), i+1)
|
||||||
|
|||||||
+26
-15
@@ -38,15 +38,28 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
rightInt, _ := rightValue.(int64)
|
rightInt, _ := rightValue.(int64)
|
||||||
v = leftInt + rightInt
|
v = leftInt + rightInt
|
||||||
}
|
}
|
||||||
} else if IsList(leftValue) || IsList(rightValue) {
|
// } else if IsList(leftValue) || IsList(rightValue) {
|
||||||
|
// var leftList, rightList *ListType
|
||||||
|
// var ok bool
|
||||||
|
// if leftList, ok = leftValue.(*ListType); !ok {
|
||||||
|
// leftList = &ListType{leftValue}
|
||||||
|
// }
|
||||||
|
// if rightList, ok = rightValue.(*ListType); !ok {
|
||||||
|
// rightList = &ListType{rightValue}
|
||||||
|
// }
|
||||||
|
// sumList := make(ListType, 0, len(*leftList)+len(*rightList))
|
||||||
|
// for _, item := range *leftList {
|
||||||
|
// sumList = append(sumList, item)
|
||||||
|
// }
|
||||||
|
// for _, item := range *rightList {
|
||||||
|
// sumList = append(sumList, item)
|
||||||
|
// }
|
||||||
|
// v = &sumList
|
||||||
|
} else if IsList(leftValue) && IsList(rightValue) {
|
||||||
var leftList, rightList *ListType
|
var leftList, rightList *ListType
|
||||||
var ok bool
|
leftList, _ = leftValue.(*ListType)
|
||||||
if leftList, ok = leftValue.(*ListType); !ok {
|
rightList, _ = rightValue.(*ListType)
|
||||||
leftList = &ListType{leftValue}
|
|
||||||
}
|
|
||||||
if rightList, ok = rightValue.(*ListType); !ok {
|
|
||||||
rightList = &ListType{rightValue}
|
|
||||||
}
|
|
||||||
sumList := make(ListType, 0, len(*leftList)+len(*rightList))
|
sumList := make(ListType, 0, len(*leftList)+len(*rightList))
|
||||||
for _, item := range *leftList {
|
for _, item := range *leftList {
|
||||||
sumList = append(sumList, item)
|
sumList = append(sumList, item)
|
||||||
@@ -55,19 +68,17 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
sumList = append(sumList, item)
|
sumList = append(sumList, item)
|
||||||
}
|
}
|
||||||
v = &sumList
|
v = &sumList
|
||||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
} else if (isFraction(leftValue) && IsNumber(rightValue)) || (isFraction(rightValue) && IsNumber(leftValue)) {
|
||||||
if IsFloat(leftValue) || IsFloat(rightValue) {
|
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||||
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
||||||
} else {
|
} else {
|
||||||
v, err = sumAnyFract(leftValue, rightValue)
|
v, err = sumAnyFract(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
} else if IsDict(leftValue) && IsDict(rightValue) {
|
} else if IsDict(leftValue) && IsDict(rightValue) {
|
||||||
leftDict, _ := leftValue.(map[any]any)
|
leftDict, _ := leftValue.(*DictType)
|
||||||
rightDict, _ := rightValue.(map[any]any)
|
rightDict, _ := rightValue.(*DictType)
|
||||||
c := CloneMap(leftDict)
|
c := leftDict.clone()
|
||||||
for key, value := range rightDict {
|
c.merge(rightDict)
|
||||||
c[key] = value
|
|
||||||
}
|
|
||||||
v = c
|
v = c
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
|||||||
@@ -277,6 +277,7 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
|
|||||||
err = scanner.Previous().ErrorExpectedGot("}")
|
err = scanner.Previous().ErrorExpectedGot("}")
|
||||||
} else {
|
} else {
|
||||||
subtree = newDictTerm(args)
|
subtree = newDictTerm(args)
|
||||||
|
// subtree = newMapTerm(args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -389,7 +390,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
var subTree *ast
|
var subTree *ast
|
||||||
if subTree, err = self.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil {
|
if subTree, err = self.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil {
|
||||||
subTree.root.priority = priValue
|
subTree.root.priority = priValue
|
||||||
err = tree.addTerm(subTree.root)
|
err = tree.addTerm(newExprTerm(subTree.root))
|
||||||
currentTerm = subTree.root
|
currentTerm = subTree.root
|
||||||
}
|
}
|
||||||
case SymFuncCall:
|
case SymFuncCall:
|
||||||
|
|||||||
+53
-33
@@ -98,7 +98,7 @@ func TestGeneralParser(t *testing.T) {
|
|||||||
/* 77 */ {`5 % 2`, int64(1), nil},
|
/* 77 */ {`5 % 2`, int64(1), nil},
|
||||||
/* 78 */ {`5 % (-2)`, int64(1), nil},
|
/* 78 */ {`5 % (-2)`, int64(1), nil},
|
||||||
/* 79 */ {`-5 % 2`, int64(-1), nil},
|
/* 79 */ {`-5 % 2`, int64(-1), nil},
|
||||||
/* 80 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [int64] and right operand '2' [float64] are not compatible with operator "%"`)},
|
/* 80 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [integer] and right operand '2' [float] are not compatible with operator "%"`)},
|
||||||
/* 81 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
|
/* 81 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
|
||||||
/* 82 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
|
/* 82 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
|
||||||
/* 83 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
|
/* 83 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
|
||||||
@@ -137,7 +137,7 @@ func TestGeneralParser(t *testing.T) {
|
|||||||
/* 116 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
|
/* 116 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
|
||||||
/* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
|
/* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
|
||||||
/* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)},
|
/* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)},
|
||||||
/* 119 */ {`{}`, map[any]any{}, nil},
|
/* 119 */ {`{}`, &DictType{}, nil},
|
||||||
/* 120 */ {`1|2`, newFraction(1, 2), nil},
|
/* 120 */ {`1|2`, newFraction(1, 2), nil},
|
||||||
/* 121 */ {`1|2 + 1`, newFraction(3, 2), nil},
|
/* 121 */ {`1|2 + 1`, newFraction(3, 2), nil},
|
||||||
/* 122 */ {`1|2 - 1`, newFraction(-1, 2), nil},
|
/* 122 */ {`1|2 - 1`, newFraction(-1, 2), nil},
|
||||||
@@ -163,46 +163,32 @@ func TestGeneralParser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// t.Setenv("EXPR_PATH", ".")
|
// t.Setenv("EXPR_PATH", ".")
|
||||||
|
// parserTestSpec(t, "General", inputs, 102)
|
||||||
parserTest(t, "General", inputs)
|
parserTest(t, "General", inputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parserTestSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
|
||||||
|
succeeded := 0
|
||||||
|
failed := 0
|
||||||
|
for _, count := range spec {
|
||||||
|
good := doTest(t, section, &inputs[count-1], count)
|
||||||
|
|
||||||
|
if good {
|
||||||
|
succeeded++
|
||||||
|
} else {
|
||||||
|
failed++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
|
||||||
|
}
|
||||||
|
|
||||||
func parserTest(t *testing.T, section string, inputs []inputType) {
|
func parserTest(t *testing.T, section string, inputs []inputType) {
|
||||||
|
|
||||||
succeeded := 0
|
succeeded := 0
|
||||||
failed := 0
|
failed := 0
|
||||||
|
|
||||||
for i, input := range inputs {
|
for i, input := range inputs {
|
||||||
var expr Expr
|
good := doTest(t, section, &input, i+1)
|
||||||
var gotResult any
|
|
||||||
var gotErr error
|
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
|
||||||
parser := NewParser(ctx)
|
|
||||||
|
|
||||||
logTest(t, i+1, section, input.source, input.wantResult, input.wantErr)
|
|
||||||
|
|
||||||
r := strings.NewReader(input.source)
|
|
||||||
scanner := NewScanner(r, DefaultTranslations())
|
|
||||||
|
|
||||||
good := true
|
|
||||||
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
|
|
||||||
gotResult, gotErr = expr.Eval(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
eq := reflect.DeepEqual(gotResult, input.wantResult)
|
|
||||||
|
|
||||||
if !eq /*gotResult != input.wantResult*/ {
|
|
||||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
|
||||||
good = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if gotErr != input.wantErr {
|
|
||||||
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
|
||||||
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
|
|
||||||
good = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if good {
|
if good {
|
||||||
succeeded++
|
succeeded++
|
||||||
} else {
|
} else {
|
||||||
@@ -212,6 +198,40 @@ func parserTest(t *testing.T, section string, inputs []inputType) {
|
|||||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func doTest(t *testing.T, section string, input *inputType, count int) (good bool) {
|
||||||
|
var expr Expr
|
||||||
|
var gotResult any
|
||||||
|
var gotErr error
|
||||||
|
|
||||||
|
ctx := NewSimpleStore()
|
||||||
|
parser := NewParser(ctx)
|
||||||
|
|
||||||
|
logTest(t, count, section, input.source, input.wantResult, input.wantErr)
|
||||||
|
|
||||||
|
r := strings.NewReader(input.source)
|
||||||
|
scanner := NewScanner(r, DefaultTranslations())
|
||||||
|
|
||||||
|
good = true
|
||||||
|
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
|
||||||
|
gotResult, gotErr = expr.Eval(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
eq := reflect.DeepEqual(gotResult, input.wantResult)
|
||||||
|
|
||||||
|
if !eq /*gotResult != input.wantResult*/ {
|
||||||
|
t.Errorf("%d: %q -> result = %v [%T], want = %v [%T]", count, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||||
|
good = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotErr != input.wantErr {
|
||||||
|
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
||||||
|
t.Errorf("%d: %q -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, input.wantErr)
|
||||||
|
good = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
|
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
|
||||||
if wantErr == nil {
|
if wantErr == nil {
|
||||||
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)
|
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// simple-func-store.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SimpleFuncStore struct {
|
|
||||||
SimpleVarStore
|
|
||||||
funcStore map[string]*funcInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
type funcInfo struct {
|
|
||||||
name string
|
|
||||||
minArgs int
|
|
||||||
maxArgs int
|
|
||||||
functor Functor
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *funcInfo) ToString(opt FmtOpt) string {
|
|
||||||
var sb strings.Builder
|
|
||||||
var i int
|
|
||||||
sb.WriteString("func(")
|
|
||||||
for i = 0; i < info.minArgs; i++ {
|
|
||||||
if i > 0 {
|
|
||||||
sb.WriteString(", ")
|
|
||||||
}
|
|
||||||
sb.WriteString(fmt.Sprintf("arg%d", i+1))
|
|
||||||
}
|
|
||||||
for ; i < info.maxArgs; i++ {
|
|
||||||
sb.WriteString(fmt.Sprintf("arg%d", i+1))
|
|
||||||
}
|
|
||||||
if info.maxArgs < 0 {
|
|
||||||
if info.minArgs > 0 {
|
|
||||||
sb.WriteString(", ")
|
|
||||||
}
|
|
||||||
sb.WriteString("...")
|
|
||||||
}
|
|
||||||
sb.WriteString(") {...}")
|
|
||||||
return sb.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *funcInfo) Name() string {
|
|
||||||
return info.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *funcInfo) MinArgs() int {
|
|
||||||
return info.minArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *funcInfo) MaxArgs() int {
|
|
||||||
return info.maxArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *funcInfo) Functor() Functor {
|
|
||||||
return info.functor
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSimpleFuncStore() *SimpleFuncStore {
|
|
||||||
ctx := &SimpleFuncStore{
|
|
||||||
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
|
|
||||||
funcStore: make(map[string]*funcInfo),
|
|
||||||
}
|
|
||||||
ImportBuiltinsFuncs(ctx)
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
|
||||||
svs := ctx.SimpleVarStore
|
|
||||||
return &SimpleFuncStore{
|
|
||||||
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
|
|
||||||
SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()},
|
|
||||||
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
|
||||||
sb.WriteString("funcs: {\n")
|
|
||||||
first := true
|
|
||||||
for _, name := range ctx.EnumFuncs(func(name string) bool { return true }) {
|
|
||||||
if first {
|
|
||||||
first = false
|
|
||||||
} else {
|
|
||||||
sb.WriteByte(',')
|
|
||||||
sb.WriteByte('\n')
|
|
||||||
}
|
|
||||||
value, _ := ctx.GetFuncInfo(name)
|
|
||||||
sb.WriteString(strings.Repeat("\t", indent+1))
|
|
||||||
sb.WriteString(name)
|
|
||||||
sb.WriteString("=")
|
|
||||||
if formatter, ok := value.(Formatter); ok {
|
|
||||||
sb.WriteString(formatter.ToString(0))
|
|
||||||
} else {
|
|
||||||
sb.WriteString(fmt.Sprintf("%v", value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sb.WriteString("\n}\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) ToString(opt FmtOpt) string {
|
|
||||||
var sb strings.Builder
|
|
||||||
sb.WriteString(ctx.SimpleVarStore.ToString(opt))
|
|
||||||
funcsCtxToBuilder(&sb, ctx, 0)
|
|
||||||
return sb.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
|
|
||||||
info, exists = ctx.funcStore[name]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
|
|
||||||
ctx.funcStore[name] = &funcInfo{name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
|
||||||
funcNames = make([]string, 0)
|
|
||||||
for name := range ctx.funcStore {
|
|
||||||
if acceptor != nil {
|
|
||||||
if acceptor(name) {
|
|
||||||
funcNames = append(funcNames, name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
funcNames = append(funcNames, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *SimpleFuncStore) Call(name string, args []any) (result any, err error) {
|
|
||||||
if info, exists := ctx.funcStore[name]; exists {
|
|
||||||
functor := info.functor
|
|
||||||
result, err = functor.Invoke(ctx, name, args)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("unknown function %s()", name)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// simple-func-store.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SimpleFuncStore struct {
|
||||||
|
SimpleVarStore
|
||||||
|
funcStore map[string]*funcInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type paramFlags uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
pfOptional paramFlags = 1 << iota
|
||||||
|
pfRepeat
|
||||||
|
)
|
||||||
|
|
||||||
|
type funcParamInfo struct {
|
||||||
|
name string
|
||||||
|
flags paramFlags
|
||||||
|
defaultValue any
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncParam(name string) *funcParamInfo {
|
||||||
|
return &funcParamInfo{name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncParamFlag(name string, flags paramFlags) *funcParamInfo {
|
||||||
|
return &funcParamInfo{name: name, flags: flags}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo {
|
||||||
|
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) Name() string {
|
||||||
|
return param.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) Type() string {
|
||||||
|
return "any"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) IsOptional() bool {
|
||||||
|
return (param.flags & pfOptional) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) IsRepeat() bool {
|
||||||
|
return (param.flags & pfRepeat) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) DefaultValue() any {
|
||||||
|
return param.defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcInfo struct {
|
||||||
|
name string
|
||||||
|
minArgs int
|
||||||
|
maxArgs int
|
||||||
|
functor Functor
|
||||||
|
params []ExprFuncParam
|
||||||
|
returnType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) Params() []ExprFuncParam {
|
||||||
|
return info.params
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) ReturnType() string {
|
||||||
|
return info.returnType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteByte('(')
|
||||||
|
if info.params != nil {
|
||||||
|
for i, p := range info.params {
|
||||||
|
if i > 0 {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
sb.WriteString(p.Name())
|
||||||
|
if p.IsOptional() {
|
||||||
|
sb.WriteByte('=')
|
||||||
|
if s, ok := p.DefaultValue().(string); ok {
|
||||||
|
sb.WriteByte('"')
|
||||||
|
sb.WriteString(s)
|
||||||
|
sb.WriteByte('"')
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", p.DefaultValue()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if info.maxArgs < 0 {
|
||||||
|
sb.WriteString(" ...")
|
||||||
|
}
|
||||||
|
sb.WriteString(") -> ")
|
||||||
|
if len(info.returnType) > 0 {
|
||||||
|
sb.WriteString(info.returnType)
|
||||||
|
} else {
|
||||||
|
sb.WriteString(typeAny)
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) Name() string {
|
||||||
|
return info.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) MinArgs() int {
|
||||||
|
return info.minArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) MaxArgs() int {
|
||||||
|
return info.maxArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) Functor() Functor {
|
||||||
|
return info.functor
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSimpleFuncStore() *SimpleFuncStore {
|
||||||
|
ctx := &SimpleFuncStore{
|
||||||
|
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
|
||||||
|
funcStore: make(map[string]*funcInfo),
|
||||||
|
}
|
||||||
|
//ImportBuiltinsFuncs(ctx)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
||||||
|
svs := ctx.SimpleVarStore
|
||||||
|
return &SimpleFuncStore{
|
||||||
|
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
|
||||||
|
SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()},
|
||||||
|
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||||
|
sb.WriteString("funcs: {\n")
|
||||||
|
first := true
|
||||||
|
names := ctx.EnumFuncs(func(name string) bool { return true })
|
||||||
|
slices.Sort(names)
|
||||||
|
for _, name := range names {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
sb.WriteByte(',')
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
}
|
||||||
|
value, _ := ctx.GetFuncInfo(name)
|
||||||
|
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||||
|
sb.WriteString(name)
|
||||||
|
//sb.WriteString("=")
|
||||||
|
if formatter, ok := value.(Formatter); ok {
|
||||||
|
sb.WriteString(formatter.ToString(0))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString("\n}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleFuncStore) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString(ctx.SimpleVarStore.ToString(opt))
|
||||||
|
funcsCtxToBuilder(&sb, ctx, 0)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
|
||||||
|
info, exists = ctx.funcStore[name]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (ctx *SimpleFuncStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
|
||||||
|
// ctx.funcStore[name] = &funcInfo{name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor}
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (ctx *SimpleFuncStore) RegisterFuncInfo(info ExprFunc) {
|
||||||
|
ctx.funcStore[info.Name()], _ = info.(*funcInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleFuncStore) RegisterFunc2(name string, functor Functor, returnType string, params []ExprFuncParam) error {
|
||||||
|
var minArgs = 0
|
||||||
|
var maxArgs = 0
|
||||||
|
if params != nil {
|
||||||
|
for _, p := range params {
|
||||||
|
if maxArgs == -1 {
|
||||||
|
return fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
|
||||||
|
// } else if p.IsRepeat() {
|
||||||
|
// maxArgs = -1
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
if p.IsOptional() {
|
||||||
|
maxArgs++
|
||||||
|
} else if maxArgs == minArgs {
|
||||||
|
minArgs++
|
||||||
|
maxArgs++
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
|
||||||
|
}
|
||||||
|
if p.IsRepeat() {
|
||||||
|
maxArgs = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.funcStore[name] = &funcInfo{
|
||||||
|
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleFuncStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
||||||
|
funcNames = make([]string, 0)
|
||||||
|
for name := range ctx.funcStore {
|
||||||
|
if acceptor != nil {
|
||||||
|
if acceptor(name) {
|
||||||
|
funcNames = append(funcNames, name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
funcNames = append(funcNames, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleFuncStore) Call(name string, args []any) (result any, err error) {
|
||||||
|
if info, exists := ctx.funcStore[name]; exists {
|
||||||
|
functor := info.functor
|
||||||
|
result, err = functor.Invoke(ctx, name, args)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unknown function %s()", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
+173
@@ -0,0 +1,173 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// simple-store.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SimpleStore struct {
|
||||||
|
varStore map[string]any
|
||||||
|
funcStore map[string]*funcInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSimpleStore() *SimpleStore {
|
||||||
|
ctx := &SimpleStore{
|
||||||
|
varStore: make(map[string]any),
|
||||||
|
funcStore: make(map[string]*funcInfo),
|
||||||
|
}
|
||||||
|
//ImportBuiltinsFuncs(ctx)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) Clone() ExprContext {
|
||||||
|
return &SimpleStore{
|
||||||
|
varStore: CloneFilteredMap(ctx.varStore, func(name string) bool { return name[0] != '@' }),
|
||||||
|
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||||
|
sb.WriteString("vars: {\n")
|
||||||
|
first := true
|
||||||
|
for _, name := range ctx.EnumVars(func(name string) bool { return name[0] != '_' }) {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
sb.WriteByte(',')
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
value, _ := ctx.GetVar(name)
|
||||||
|
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||||
|
sb.WriteString(name)
|
||||||
|
sb.WriteString(": ")
|
||||||
|
if f, ok := value.(Formatter); ok {
|
||||||
|
sb.WriteString(f.ToString(0))
|
||||||
|
} else if _, ok = value.(Functor); ok {
|
||||||
|
sb.WriteString("func(){}")
|
||||||
|
// } else if _, ok = value.(map[any]any); ok {
|
||||||
|
// sb.WriteString("dict{}")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString(strings.Repeat("\t", indent))
|
||||||
|
sb.WriteString("\n}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func varsCtxToString(ctx ExprContext, indent int) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
varsCtxToBuilder(&sb, ctx, indent)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||||
|
sb.WriteString("funcs: {\n")
|
||||||
|
first := true
|
||||||
|
names := ctx.EnumFuncs(func(name string) bool { return true })
|
||||||
|
slices.Sort(names)
|
||||||
|
for _, name := range names {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
sb.WriteByte(',')
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
}
|
||||||
|
value, _ := ctx.GetFuncInfo(name)
|
||||||
|
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||||
|
sb.WriteString(name)
|
||||||
|
//sb.WriteString("=")
|
||||||
|
if formatter, ok := value.(Formatter); ok {
|
||||||
|
sb.WriteString(formatter.ToString(0))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString("\n}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
varsCtxToBuilder(&sb, ctx, 0)
|
||||||
|
funcsCtxToBuilder(&sb, ctx, 0)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) {
|
||||||
|
v, exists = ctx.varStore[varName]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) UnsafeSetVar(varName string, value any) {
|
||||||
|
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
|
||||||
|
ctx.varStore[varName] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) SetVar(varName string, value any) {
|
||||||
|
// fmt.Printf("[%p] SetVar(%v, %v)\n", ctx, varName, value)
|
||||||
|
if allowedValue, ok := fromGenericAny(value); ok {
|
||||||
|
ctx.varStore[varName] = allowedValue
|
||||||
|
} else {
|
||||||
|
panic(fmt.Errorf("unsupported type %T of value %v", value, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) EnumVars(acceptor func(name string) (accept bool)) (varNames []string) {
|
||||||
|
varNames = make([]string, 0)
|
||||||
|
for name := range ctx.varStore {
|
||||||
|
if acceptor != nil {
|
||||||
|
if acceptor(name) {
|
||||||
|
varNames = append(varNames, name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
varNames = append(varNames, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
|
||||||
|
info, exists = ctx.funcStore[name]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) RegisterFuncInfo(info ExprFunc) {
|
||||||
|
ctx.funcStore[info.Name()], _ = info.(*funcInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) RegisterFunc(name string, functor Functor, returnType string, params []ExprFuncParam) (err error) {
|
||||||
|
var info *funcInfo
|
||||||
|
if info, err = newFuncInfo(name, functor, returnType, params); err == nil {
|
||||||
|
ctx.funcStore[name] = info
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
||||||
|
funcNames = make([]string, 0)
|
||||||
|
for name := range ctx.funcStore {
|
||||||
|
if acceptor != nil {
|
||||||
|
if acceptor(name) {
|
||||||
|
funcNames = append(funcNames, name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
funcNames = append(funcNames, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) Call(name string, args []any) (result any, err error) {
|
||||||
|
if info, exists := ctx.funcStore[name]; exists {
|
||||||
|
functor := info.functor
|
||||||
|
result, err = functor.Invoke(ctx, name, args)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unknown function %s()", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) setVar(varName string, value any) {
|
func (ctx *SimpleVarStore) UnsafeSetVar(varName string, value any) {
|
||||||
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
|
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
|
||||||
ctx.varStore[varName] = value
|
ctx.varStore[varName] = value
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,12 @@ func (ctx *SimpleVarStore) Call(name string, args []any) (result any, err error)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
|
// func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
|
||||||
|
// }
|
||||||
|
func (ctx *SimpleVarStore) RegisterFuncInfo(info ExprFunc) {
|
||||||
|
}
|
||||||
|
func (ctx *SimpleVarStore) RegisterFunc2(name string, f Functor, returnType string, param []ExprFuncParam) error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
||||||
@@ -98,8 +103,8 @@ func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
|||||||
sb.WriteString(f.ToString(0))
|
sb.WriteString(f.ToString(0))
|
||||||
} else if _, ok = value.(Functor); ok {
|
} else if _, ok = value.(Functor); ok {
|
||||||
sb.WriteString("func(){}")
|
sb.WriteString("func(){}")
|
||||||
} else if _, ok = value.(map[any]any); ok {
|
// } else if _, ok = value.(map[any]any); ok {
|
||||||
sb.WriteString("dict{}")
|
// sb.WriteString("dict{}")
|
||||||
} else {
|
} else {
|
||||||
sb.WriteString(fmt.Sprintf("%v", value))
|
sb.WriteString(fmt.Sprintf("%v", value))
|
||||||
}
|
}
|
||||||
@@ -72,6 +72,7 @@ const (
|
|||||||
SymIdentifier
|
SymIdentifier
|
||||||
SymBool
|
SymBool
|
||||||
SymInteger
|
SymInteger
|
||||||
|
SymVariable
|
||||||
SymFloat
|
SymFloat
|
||||||
SymFraction
|
SymFraction
|
||||||
SymString
|
SymString
|
||||||
|
|||||||
@@ -161,10 +161,14 @@ func (self *term) toInt(computedValue any, valueDescription string) (i int, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
|
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
|
||||||
|
leftType := getTypeName(leftValue)
|
||||||
|
leftText := getFormatted(leftValue, Truncate)
|
||||||
|
rightType := getTypeName(rightValue)
|
||||||
|
rightText := getFormatted(rightValue, Truncate)
|
||||||
return self.tk.Errorf(
|
return self.tk.Errorf(
|
||||||
"left operand '%v' [%T] and right operand '%v' [%T] are not compatible with operator %q",
|
"left operand '%s' [%s] and right operand '%s' [%s] are not compatible with operator %q",
|
||||||
leftValue, leftValue,
|
leftText, leftType,
|
||||||
rightValue, rightValue,
|
rightText, rightType,
|
||||||
self.source())
|
self.source())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func IsList(v any) (ok bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsDict(v any) (ok bool) {
|
func IsDict(v any) (ok bool) {
|
||||||
_, ok = v.(map[any]any)
|
_, ok = v.(*DictType)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +147,12 @@ func fromGenericAny(v any) (exprAny any, ok bool) {
|
|||||||
if exprAny, ok = anyFloat(v); ok {
|
if exprAny, ok = anyFloat(v); ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if exprAny, ok = v.(*DictType); ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if exprAny, ok = v.(*ListType); ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,3 +209,11 @@ func toInt(value any, description string) (i int, err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ForAll[T, V any](ts []T, fn func(T) V) []V {
|
||||||
|
result := make([]V, len(ts))
|
||||||
|
for i, t := range ts {
|
||||||
|
result[i] = fn(t)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user