Compare commits

..

3 Commits

Author SHA1 Message Date
camoroso ba1d887a05 commit 2024-04-20 04:02:51 +02:00
camoroso faff5a7e2c hook_test next commit 2024-04-19 15:02:53 +02:00
camoroso 36f6846a3f hook_test first commit 2024-04-19 15:00:20 +02:00
69 changed files with 724 additions and 4476 deletions
+6 -6
View File
@@ -5,6 +5,7 @@
package expr package expr
import ( import (
"errors"
"strings" "strings"
) )
@@ -63,7 +64,7 @@ func (self *ast) addToken2(tk *Token) (t *term, err error) {
if t = newTerm(tk, nil); t != nil { if t = newTerm(tk, nil); t != nil {
err = self.addTerm(t) err = self.addTerm(t)
} else { } else {
err = tk.Errorf("unexpected token %q", tk.String()) err = tk.Errorf("No term constructor for token %q", tk.String())
} }
return return
} }
@@ -83,9 +84,8 @@ func (self *ast) insert(tree, node *term) (root *term, err error) {
if tree.isComplete() { if tree.isComplete() {
var subRoot *term var subRoot *term
last := tree.removeLastChild() last := tree.removeLastChild()
if subRoot, err = self.insert(last, node); err == nil { subRoot, err = self.insert(last, node)
subRoot.setParent(tree) subRoot.setParent(tree)
}
} else { } else {
node.setParent(tree) node.setParent(tree)
} }
@@ -129,8 +129,8 @@ 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)
} }
// } else { } else {
// err = errors.New("empty expression") err = errors.New("empty expression")
} }
return return
} }
+1 -1
View File
@@ -44,7 +44,7 @@ func TestAddTokensBad(t *testing.T) {
func TestAddUnknownTokens(t *testing.T) { func TestAddUnknownTokens(t *testing.T) {
tk0 := NewToken(0, 0, SymPercent, "%") tk0 := NewToken(0, 0, SymPercent, "%")
wantErr := errors.New(`unexpected token "%"`) wantErr := errors.New(`No term constructor for token "%"`)
tree := NewAst() tree := NewAst()
if gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() { if gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() {
-37
View File
@@ -1,37 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// common-errors.go
package expr
import (
"fmt"
)
// --- General errors
func errCantConvert(funcName string, value any, kind string) error {
return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind)
}
func errExpectedGot(funcName string, kind string, value any) error {
return fmt.Errorf("%s() expected %s, got %T (%v)", funcName, kind, value, value)
}
// --- Parameter errors
func errOneParam(funcName string) error {
return fmt.Errorf("%s() requires exactly one param", funcName)
}
func errMissingRequiredParameter(funcName, paramName string) error {
return fmt.Errorf("%s() missing required parameter %q", funcName, paramName)
}
func errInvalidParameterValue(funcName, paramName string, paramValue any) error {
return fmt.Errorf("%s() invalid value %T (%v) for parameter %q", funcName, paramValue, paramValue, paramName)
}
func errWrongParamType(funcName, paramName, paramType string, paramValue any) error {
return fmt.Errorf("%s() the %q parameter must be a %s, got a %T (%v)", funcName, paramName, paramType, paramValue, paramValue)
}
-11
View File
@@ -1,11 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// common-params.go
package expr
const (
paramParts = "parts"
paramSeparator = "separator"
paramSource = "source"
)
-14
View File
@@ -1,14 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// common-type-names.go
package expr
const (
typeBoolean = "boolean"
typeFloat = "decimal"
typeFraction = "fraction"
typeInt = "integer"
typeNumber = "number"
typeString = "string"
)
-43
View File
@@ -1,43 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// context-helpers.go
package expr
func cloneContext(sourceCtx ExprContext) (clonedCtx ExprContext) {
if sourceCtx != nil {
clonedCtx = sourceCtx.Clone()
}
return
}
func exportVar(ctx ExprContext, name string, value any) {
if name[0] == '@' {
name = name[1:]
}
ctx.setVar(name, value)
}
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
if name[0] == '@' {
name = name[1:]
}
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
}
func exportObjects(destCtx, sourceCtx ExprContext) {
exportAll := isEnabled(sourceCtx, control_export_all)
// fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll)
// Export variables
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
// fmt.Printf("\tExporting %q\n", refName)
refValue, _ := sourceCtx.GetVar(refName)
exportVar(destCtx, refName, refValue)
}
// Export functions
for _, refName := range sourceCtx.EnumFuncs(func(name string) bool { return exportAll || name[0] == '@' }) {
if info, _ := sourceCtx.GetFuncInfo(refName); info != nil {
exportFunc(destCtx, refName, info)
}
}
}
+1 -1
View File
@@ -36,7 +36,7 @@ type ExprContext interface {
setVar(varName string, value any) setVar(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) ExprFunc
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)
} }
-154
View File
@@ -1,154 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// data-cursors.go
package expr
import (
"errors"
"io"
)
type dataCursor struct {
ds map[string]Functor
ctx ExprContext
index int
resource any
nextFunc Functor
cleanFunc Functor
resetFunc Functor
currentFunc Functor
}
func newDataCursor(ctx ExprContext, ds map[string]Functor) (dc *dataCursor) {
dc = &dataCursor{
ds: ds,
index: -1,
ctx: ctx.Clone(),
}
return
}
// func mapToString(m map[string]Functor) string {
// var sb strings.Builder
// sb.WriteByte('{')
// for key, _ := range m {
// if sb.Len() > 1 {
// sb.WriteString(fmt.Sprintf(", %q: func(){}", key))
// } else {
// sb.WriteString(fmt.Sprintf("%q: func(){}", key))
// }
// }
// sb.WriteByte('}')
// return sb.String()
// }
func (dc *dataCursor) String() string {
return "$()"
/*
var sb strings.Builder
sb.WriteString(fmt.Sprintf(`$(
index: %d,
ds: %s,
ctx: `, dc.index, mapToString(dc.ds)))
CtxToBuilder(&sb, dc.ctx, 1)
sb.WriteByte(')')
return sb.String()
*/
}
func (dc *dataCursor) HasOperation(name string) (exists bool) {
exists = name == indexName
if !exists {
f, ok := dc.ds[name]
exists = ok && isFunctor(f)
}
return
}
func (dc *dataCursor) CallOperation(name string, args []any) (value any, err error) {
if name == indexName {
value = int64(dc.Index())
} else if functor, ok := dc.ds[name]; ok && isFunctor(functor) {
if functor == dc.cleanFunc {
value, err = dc.Clean()
} else if functor == dc.resetFunc {
value, err = dc.Reset()
} else {
ctx := cloneContext(dc.ctx)
value, err = functor.Invoke(ctx, name, []any{})
exportObjects(dc.ctx, ctx)
}
} else {
err = errNoOperation(name)
}
return
}
func (dc *dataCursor) Reset() (success bool, err error) {
if dc.resetFunc != nil {
if dc.resource != nil {
ctx := cloneContext(dc.ctx)
if _, err = dc.resetFunc.Invoke(ctx, resetName, []any{dc.resource}); err == nil {
dc.index = -1
}
exportObjects(dc.ctx, ctx)
} else {
err = errInvalidDataSource()
}
} else {
err = errNoOperation(resetName)
}
success = err == nil
return
}
func (dc *dataCursor) Clean() (success bool, err error) {
if dc.cleanFunc != nil {
if dc.resource != nil {
ctx := cloneContext(dc.ctx)
_, err = dc.cleanFunc.Invoke(ctx, cleanName, []any{dc.resource})
dc.resource = nil
exportObjects(dc.ctx, ctx)
} else {
err = errInvalidDataSource()
}
} else {
err = errors.New("no 'clean' function defined in the data-source")
}
success = err == nil
return
}
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
ctx := cloneContext(dc.ctx)
if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil {
err = io.EOF
}
exportObjects(dc.ctx, ctx)
return
}
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
if dc.resource != nil {
ctx := cloneContext(dc.ctx)
// fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
if item == nil {
err = io.EOF
} else {
dc.index++
}
}
// fmt.Printf("Exiting Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
exportObjects(dc.ctx, ctx)
// fmt.Printf("Outer-Ctx [%p]: %s\n", dc.ctx, CtxToString(dc.ctx, 0))
} else {
err = errInvalidDataSource()
}
return
}
func (dc *dataCursor) Index() int {
return dc.index
}
-115
View File
@@ -1,115 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// dict_test.go
package expr
import (
"errors"
"strings"
"testing"
)
func TestDictParser(t *testing.T) {
section := "Dict"
type inputType struct {
source string
wantResult any
wantErr error
}
inputs := []inputType{
/* 1 */ {`{}`, map[any]any{}, nil},
/* 2 */ {`{123}`, nil, errors.New(`[1:6] expected ":", got "}"`)},
/* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1):"one", int64(2):"two", int64(3):"three"}, nil},
/* 4 */ {`{1:"one",2:"two",3:"three"}.2`, "three", nil},
// /* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
// /* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
// /* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
// /* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
// /* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
// /* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
// /* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
// /* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
// /* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
// /* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
// /* 14 */ {`[1,2,3].1`, int64(2), nil},
// /* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
// /* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
}
succeeded := 0
failed := 0
// inputs1 := []inputType{
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
// }
for i, input := range inputs {
var expr *ast
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, "Dict", 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)
}
if (gotResult == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotList, okGot := gotResult.([]any); okGot {
if wantList, okWant := input.wantResult.([]any); okWant {
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
} else {
equal := len(gotList) == len(wantList)
if equal {
for i, gotItem := range gotList {
wantItem := wantList[i]
equal = gotItem == wantItem
if !equal {
break
}
}
}
if !equal {
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 {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
+33 -124
View File
@@ -22,92 +22,15 @@ Expressions calculator
toc::[] toc::[]
#TODO: Work in progress (last update on 2024/05/07, 07:15 am)# #TODO: Work in progress#
== 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.
=== `dev-expr` test tool
`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, beyond in additon to the automatic test suite based on the Go test framework, `dev-expr` provides an important aid for quickly testing of new features during their development.
It cat work as a REPL, *R*ead-*E*xecute-*P*rint-*L*oop, or it can process expression acquired from files or standard input.
The program in located in the _tools_ directory.
Here are some examples of execution.
.Run `dev-expr` in REPL mode and ask for help
[source,shell]
----
# Assume the expr source directory. Type 'exit' or Ctrl+D to quit the program.
[user]$ tools/expr -- Expressions calculator v1.7.0,2024/05/08 (celestino.amoroso@portale-stac.it)
Type help to get the list of command.
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
>>> help
--- REPL commands:
base -- Set the integer output base: 2, 8, 10, or 16
exit -- Exit the program
help -- Show command list
ml -- Enable/Disable multi-line output
mods -- List builtin modules
source -- Load a file as input
tty -- Enable/Disable ansi output <1>
--- Command line options:
-b <builtin> Import builtin modules.
<builtin> can be a list of module names or a glob-pattern.
Use the special value 'all' or the pattern '*' to import all modules.
-e <expression> Evaluate <expression> instead of standard-input
-i Force REPL operation when all -e occurences have been processed
-h, --help, help Show this help menu
-m, --modules List all builtin modules
-p Print prefix form
-t Print tree form <2>
>>>
----
<1> Only available for single fraction values
<2> Work in progress
.REPL examples
[source,shell]
----
[user]$ tools/expr -- Expressions calculator v1.6.1,2024/05/06 (celestino.amoroso@portale-stac.it)
Type help to get the list of command.
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
>>> 2+3
5
>>> 2+3*(4-1.5)
9.5
>>> 0xFD + 0b1 + 0o1 <1>
255
>>> 1|2 + 2|3 <2>
7|6
>>> ml <3>
>>> 1|2 + 2|3
7
-
6
>>> 1+2 but 5|2+0.5 <4>
3
>>> 1+2; 5|2+0.5 <5>
3
>>>
----
<1> Number bases: 0x = hexadecimal, 0o = octal, 0b = binary.
<2> Fractions: numerator | denominator.
<3> Activate multi-line output of fractions.
<4> But operator, see <<_but_operator>>.
<5> Multi-expression: the same result of the previous single expression but this it is obtained with two separated calculations.
=== Concepts and terminology === Concepts and terminology
#TODO# #TODO#
image::expression-diagram.png[]
== Data types == Data types
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists. _Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
@@ -246,7 +169,7 @@ x = 1; y = 2*x
== Other operations == Other operations
=== [blue]`;` operator === [blue]`;` operator
The semicolon operator [blue]`;` is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The latter is the final result. The semicolon operator [blue]`;` is an infixed operator. It evaluates the left expression first and then the right expression. The latter is the final result.
IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions. IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions.
@@ -259,12 +182,12 @@ a=1; b=2; c=3; a+b+c // returns 6
---- ----
=== [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 any type of expression. 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 very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`(` and [blue]`)`. [blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` 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 variable in the evaluation context or to change their value (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
@@ -279,22 +202,13 @@ The _selector operator_ is very similar to the _switch/case/default_ statement a
.Syntax .Syntax
[source,bnf] [source,bnf]
---- ----
<selector-operator> ::= <select-expression> "?" <selector-case> { ":" <selector-case> } ["::" <default-multi-expression>] <selector-operator> ::= <expression> "?" <selector-case> { ":" <selector-case> } ["::" <case-value>]
<selector-case> ::= [<match-list>] <case-value> <selector-case> ::= [<list>] <case-value>
<match-list> ::= "["<item>{","<items>}"]" <case-value> ::= "{" <multi-expr> "}"
<item> ::= <expression <multi-expr> ::= <expr> {";" <expr>}
<case-multi-expression> ::= "{" <multi-expression> "}"
<multi-expression> ::= <expression> {";" <expression>}
---- ----
In other words, the selector operator evaluates the expression (`<select-expression>`) on the left-hand side of the `?` symbol; it then compares the result obtained with the values listed in the `<match-list>`'s. If the comparision find a match with a value in a match-list, the associated `<case-multi-expression>` is evaluted, and its result will be the final result of the selection operation. .Example
The match lists are optional. In that case, the position, from left to right, of the `<selector-case>` is used as match-list. Of course, that only works if the select-expression results in an integer.
The `:` symbol (colon) is the separator of the selector-cases. Note that if the value of the select-expression does not match any match-list, an error will be issued. Therefore, it is strongly recommended to provide a default (multi-)expression introduced by the `::` symbol (double-colon). Also note that the default expression has no match-list.
.Examples
[source,go] [source,go]
---- ----
1 ? {"a"} : {"b"} // returns "b" 1 ? {"a"} : {"b"} // returns "b"
@@ -310,38 +224,33 @@ The `:` symbol (colon) is the separator of the selector-cases. Note that if the
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,<5"]
|=== |===
| Priority | Operators | Position | Operation | Operands and results | Priority | Operators | Position | Operation | Operands and results
.1+|*ITEM*| [blue]`.` | _Infix_ | _Item_| _collection_ `"."` _key_ -> _any_ 1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ "!" -> _integer_
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_ 1+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| ("+"\|"-") _number_ -> _number_
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_ .5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ "*" _number_ -> _number_
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_ | [blue]`*` | _Infix_ | _String-repeat_ | _string_ "*" _integer_ -> _string_
.3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`"+"`\|`"-"`) _number_ -> _number_ | [blue]`/` | _Infix_ | _Division_ | _number_ "/" _number_ -> _number_
| [blue]`#` | _Prefix_ | _Lenght-of_ | `"#"` _collection_ -> _integer_ | [blue]`./` | _Infix_ | _Float-division_ | __number__ "./" _number_ -> _float_
| [blue]`#` | _Prefix_ | _Size-of_ | `"#"` _iterator_ -> _integer_ | [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ "%" _integer_ -> _integer_
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `"*"` _number_ -> _number_ .5+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ "+" _number_ -> _number_
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ `"*"` _integer_ -> _string_ | [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) "+" (_string_\|_number_) -> _string_
| [blue]`/` | _Infix_ | _Division_ | _number_ `"/"` _number_ -> _number_ | [blue]`+` | _Infix_ | _List-join_ | _list_ "+" _list_ -> _list_
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `"./"` _number_ -> _float_ | [blue]`-` | _Infix_ | _Subtraction_ | _number_ "-" _number_ -> _number_
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `"%"` _integer_ -> _integer_ | [blue]`-` | _Infix_ | _List-difference_ | _list_ "-" _list_ -> _list_
.5+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_ .6+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ "<" _comparable_ -> _boolean_
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `"+"` (_string_\|_number_) -> _string_ | [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ "\<=" _comparable_ -> _boolean_
| [blue]`+` | _Infix_ | _List-join_ | _list_ `"+"` _list_ -> _list_ | [blue]`>` | _Infix_ | _greater_ | _comparable_ ">" _comparable_ -> _boolean_
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `"-"` _number_ -> _number_ | [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ ">=" _comparable_ -> _boolean_
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `"-"` _list_ -> _list_ | [blue]`==` | _Infix_ | _equal_ | _comparable_ "==" _comparable_ -> _boolean_
.6+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_ | [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ "!=" _comparable_ -> _boolean_
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `"\<="` _comparable_ -> _boolean_ .1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | "not" _boolean_ -> _boolean_
| [blue]`>` | _Infix_ | _greater_ | _comparable_ `">"` _comparable_ -> _boolean_ .2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ "and" _boolean_ -> _boolean_
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ `">="` _comparable_ -> _boolean_ | [blue]`&&` | _Infix_ | _and_ | _boolean_ "&&" _boolean_ -> _boolean_
| [blue]`==` | _Infix_ | _equal_ | _comparable_ `"=="` _comparable_ -> _boolean_ .2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ "or" _boolean_ -> _boolean_
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ `"!="` _comparable_ -> _boolean_ | [blue]`\|\|` | _Infix_ | _or_ | _boolean_ "\|\|" _boolean_ -> _boolean_
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | `"not"` _boolean_ -> _boolean_
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ `"and"` _boolean_ -> _boolean_
| [blue]`&&` | _Infix_ | _and_ | _boolean_ `"&&"` _boolean_ -> _boolean_
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ `"or"` _boolean_ -> _boolean_
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ `"\|\|"` _boolean_ -> _boolean_
.1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_ .1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_ .1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|=== |===
-1400
View File
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

+10 -20
View File
@@ -5,6 +5,7 @@
package expr package expr
import ( import (
"fmt"
"strings" "strings"
"testing" "testing"
) )
@@ -17,29 +18,18 @@ func TestExpr(t *testing.T) {
} }
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`0?{}`, nil, nil}, /* 1 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), 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},
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
/* 10 */ {`
ds={
"init":func(end){@end=end; @current=0 but true},
"current":func(){current},
"next":func(){
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
}
};
it=$(ds,3);
it++;
it++
`, int64(1), nil},
} }
succeeded := 0 succeeded := 0
failed := 0 failed := 0
for i, input := range inputs { inputs1 := []inputType{
{`f=openFile("/tmp/test2.txt"); line=readFile(f); closeFile(f); line`, "ciao", nil},
//{`f = func(op){op()}; f(func(){2})`, int64(2), nil},
}
for i, input := range inputs1 {
var expr Expr var expr Expr
var gotResult any var gotResult any
var gotErr error var gotErr error
@@ -50,7 +40,7 @@ func TestExpr(t *testing.T) {
ImportOsFuncs(ctx) ImportOsFuncs(ctx)
parser := NewParser(ctx) parser := NewParser(ctx)
logTest(t, i+1, "Expr", input.source, input.wantResult, input.wantErr) logTest(t, i+1, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source) r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations()) scanner := NewScanner(r, DefaultTranslations())
@@ -78,5 +68,5 @@ func TestExpr(t *testing.T) {
failed++ failed++
} }
} }
t.Logf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed) t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
} }
-33
View File
@@ -1,33 +0,0 @@
builtin ["os.file", "base"];
readInt=func(fh){
line=readFile(fh);
line ? [nil] {nil} :: {int(line)}
};
ds={
"init":func(filename){
fh=openFile(filename);
fh ? [nil] {nil} :: { @current=readInt(fh); @prev=@current };
fh
},
"current":func(){
prev
},
"next":func(fh){
current ?
[nil] {current}
:: {@prev=current; @current=readInt(fh) but current}
},
"clean":func(fh){
closeFile(fh)
}
}
//;f=$(ds, "int.list")
/*
;f++
;f++
;f++
*/
//;add(f)
-20
View File
@@ -1,20 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// formatter.go
package expr
type FmtOpt uint16
const (
TTY FmtOpt = 1 << iota
MultiLine
Base2
Base8
Base10
Base16
)
type Formatter interface {
ToString(options FmtOpt) string
}
-59
View File
@@ -1,59 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-builtins.go
package expr
import (
"math"
"strconv"
)
func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) == 1 {
result = args[0] == nil
} else {
err = errOneParam(name)
}
return
}
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) == 1 {
switch v := args[0].(type) {
case int64:
result = v
case float64:
result = int64(math.Trunc(v))
case bool:
if v {
result = int64(1)
} else {
result = int64(0)
}
case string:
var i int
if i, err = strconv.Atoi(v); err == nil {
result = int64(i)
}
default:
err = errCantConvert(name, v, "int")
}
} else {
err = errOneParam(name)
}
return
}
func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return
}
func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, -1)
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, -1)
}
func init() {
registerImport("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
}
+4 -8
View File
@@ -20,7 +20,7 @@ func importFunc(ctx ExprContext, name string, args []any) (result any, err error
return importGeneral(ctx, name, args) return importGeneral(ctx, name, args)
} }
func importAllFunc(ctx ExprContext, name string, args []any) (result any, err error) { func includeFunc(ctx ExprContext, name string, args []any) (result any, err error) {
enable(ctx, control_export_all) enable(ctx, control_export_all)
return importGeneral(ctx, name, args) return importGeneral(ctx, name, args)
} }
@@ -30,12 +30,12 @@ func importGeneral(ctx ExprContext, name string, args []any) (result any, err er
dirList = addEnvImportDirs(dirList) dirList = addEnvImportDirs(dirList)
dirList = addPresetImportDirs(ctx, dirList) dirList = addPresetImportDirs(ctx, dirList)
result, err = doImport(ctx, name, dirList, NewArrayIterator(args)) result, err = doImport(ctx, name, dirList, NewFlatArrayIterator(args))
return return
} }
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) { func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(IsString(paramValue) /*|| isList(paramValue)*/) { if !(isString(paramValue) /*|| isList(paramValue)*/) {
err = fmt.Errorf("%s(): param nr %d has wrong type %T, string expected", funcName, paramPos+1, paramValue) err = fmt.Errorf("%s(): param nr %d has wrong type %T, string expected", funcName, paramPos+1, paramValue)
} }
return return
@@ -138,9 +138,5 @@ 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", &simpleFunctor{f: importFunc}, 1, -1)
ctx.RegisterFunc("importAll", &simpleFunctor{f: importAllFunc}, 1, -1) ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1)
}
func init() {
registerImport("import", ImportImportFuncs, "Functions import() and include()")
} }
+26 -88
View File
@@ -9,65 +9,36 @@ import (
"io" "io"
) )
func checkNumberParamExpected(funcName string, paramValue any, paramPos, level, subPos int) (err error) { func checkNumberParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(IsNumber(paramValue) || isFraction(paramValue)) /*|| isList(paramValue)*/ { if !(isNumber(paramValue) || isList(paramValue)) {
err = fmt.Errorf("%s(): param nr %d (%d in %d) has wrong type %T, number expected", err = fmt.Errorf("%s(): param nr %d has wrong type %T, number expected", funcName, paramPos+1, paramValue)
funcName, paramPos+1, subPos+1, level, paramValue)
} }
return return
} }
func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result any, err error) { func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
var sumAsFloat, sumAsFract bool var sumAsFloat = false
var floatSum float64 = 0.0 var floatSum float64 = 0.0
var intSum int64 = 0 var intSum int64 = 0
var fractSum *fraction
var v any var v any
level++
for v, err = it.Next(); err == nil; v, err = it.Next() { for v, err = it.Next(); err == nil; v, err = it.Next() {
if list, ok := v.(*ListType); ok { if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
v = NewListIterator(list, nil)
}
if subIter, ok := v.(Iterator); ok {
if v, err = doAdd(ctx, name, subIter, count, level); err != nil {
break
}
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) {
if _, err = extIter.CallOperation(cleanName, nil); err != nil {
return
}
}
} else if err = checkNumberParamExpected(name, v, count, level, it.Index()); err != nil {
break break
} }
count++
if !sumAsFloat { if array, ok := v.([]any); ok {
if IsFloat(v) { if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
sumAsFloat = true break
if sumAsFract {
floatSum = fractSum.toFloat()
} else {
floatSum = float64(intSum)
}
} else if !sumAsFract && isFraction(v) {
fractSum = newFraction(intSum, 1)
sumAsFract = true
} }
} }
if !sumAsFloat && isFloat(v) {
sumAsFloat = true
floatSum = float64(intSum)
}
if sumAsFloat { if sumAsFloat {
floatSum += numAsFloat(v) floatSum += numAsFloat(v)
} else if sumAsFract {
var item *fraction
var ok bool
if item, ok = v.(*fraction); !ok {
iv, _ := v.(int64)
item = newFraction(iv, 1)
}
fractSum = sumFract(fractSum, item)
} else { } else {
iv, _ := v.(int64) iv, _ := v.(int64)
intSum += iv intSum += iv
@@ -77,8 +48,6 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
err = nil err = nil
if sumAsFloat { if sumAsFloat {
result = floatSum result = floatSum
} else if sumAsFract {
result = fractSum
} else { } else {
result = intSum result = intSum
} }
@@ -87,62 +56,33 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
} }
func addFunc(ctx ExprContext, name string, args []any) (result any, err error) { func addFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result, err = doAdd(ctx, name, NewArrayIterator(args), 0, -1) result, err = doAdd(ctx, name, NewFlatArrayIterator(args))
return return
} }
func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result any, err error) { func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
var mulAsFloat, mulAsFract bool var mulAsFloat = false
var floatProd float64 = 1.0 var floatProd float64 = 1.0
var intProd int64 = 1 var intProd int64 = 1
var fractProd *fraction
var v any var v any
level++
for v, err = it.Next(); err == nil; v, err = it.Next() { for v, err = it.Next(); err == nil; v, err = it.Next() {
if list, ok := v.(*ListType); ok { if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
v = NewListIterator(list, nil) break
} }
if subIter, ok := v.(Iterator); ok {
if v, err = doMul(ctx, name, subIter, count, level); err != nil { if array, ok := v.([]any); ok {
break if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
}
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) {
if _, err = extIter.CallOperation(cleanName, nil); err != nil {
return
}
}
} else {
if err = checkNumberParamExpected(name, v, count, level, it.Index()); err != nil {
break break
} }
} }
count++
if !mulAsFloat { if !mulAsFloat && isFloat(v) {
if IsFloat(v) { mulAsFloat = true
mulAsFloat = true floatProd = float64(intProd)
if mulAsFract {
floatProd = fractProd.toFloat()
} else {
floatProd = float64(intProd)
}
} else if !mulAsFract && isFraction(v) {
fractProd = newFraction(intProd, 1)
mulAsFract = true
}
} }
if mulAsFloat { if mulAsFloat {
floatProd *= numAsFloat(v) floatProd *= numAsFloat(v)
} else if mulAsFract {
var item *fraction
var ok bool
if item, ok = v.(*fraction); !ok {
iv, _ := v.(int64)
item = newFraction(iv, 1)
}
fractProd = mulFract(fractProd, item)
} else { } else {
iv, _ := v.(int64) iv, _ := v.(int64)
intProd *= iv intProd *= iv
@@ -152,8 +92,6 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
err = nil err = nil
if mulAsFloat { if mulAsFloat {
result = floatProd result = floatProd
} else if mulAsFract {
result = fractProd
} else { } else {
result = intProd result = intProd
} }
@@ -162,7 +100,7 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
} }
func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) { func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result, err = doMul(ctx, name, NewArrayIterator(args), 0, -1) result, err = doMul(ctx, name, NewFlatArrayIterator(args))
return return
} }
@@ -172,5 +110,5 @@ func ImportMathFuncs(ctx ExprContext) {
} }
func init() { func init() {
registerImport("math.arith", ImportMathFuncs, "Functions add() and mul()") registerImport("math.arith", ImportMathFuncs)
} }
+2 -9
View File
@@ -126,7 +126,7 @@ func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle var handle osHandle
result = nil
if len(args) > 0 { if len(args) > 0 {
handle, _ = args[0].(osHandle) handle, _ = args[0].(osHandle)
} }
@@ -141,16 +141,13 @@ func readFileFunc(ctx ExprContext, name string, args []any) (result any, err err
limit = s[0] limit = s[0]
} }
} }
if v, err = r.reader.ReadString(limit); err == nil { if v, err = r.reader.ReadString(limit); err == nil || err == io.EOF {
if len(v) > 0 && v[len(v)-1] == limit { if len(v) > 0 && v[len(v)-1] == limit {
result = v[0 : len(v)-1] result = v[0 : len(v)-1]
} else { } else {
result = v result = v
} }
} }
if err == io.EOF {
err = nil
}
} }
} }
} }
@@ -165,7 +162,3 @@ func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2) ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1) ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
} }
func init() {
registerImport("os.file", ImportOsFuncs, "Operating system file functions")
}
-211
View File
@@ -1,211 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-string.go
package expr
import (
"fmt"
"io"
"strings"
)
// --- Start of function definitions
func doJoinStr(funcName string, sep string, it Iterator) (result any, err error) {
var sb strings.Builder
var v any
for v, err = it.Next(); err == nil; v, err = it.Next() {
if it.Index() > 0 {
sb.WriteString(sep)
}
if s, ok := v.(string); ok {
sb.WriteString(s)
} else {
err = errExpectedGot(funcName, typeString, v)
return
}
}
if err == nil || err == io.EOF {
err = nil
result = sb.String()
}
return
}
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) < 1 {
return nil, errMissingRequiredParameter(name, paramSeparator)
}
if sep, ok := args[0].(string); ok {
if len(args) == 1 {
result = ""
} else if len(args) == 2 {
if ls, ok := args[1].(*ListType); ok {
result, err = doJoinStr(name, sep, NewListIterator(ls, nil))
} else if it, ok := args[1].(Iterator); ok {
result, err = doJoinStr(name, sep, it)
} else {
err = errInvalidParameterValue(name, paramParts, args[1])
}
} else {
result, err = doJoinStr(name, sep, NewArrayIterator(args[1:]))
}
} else {
err = errWrongParamType(name, paramSeparator, typeString, args[0])
}
return
}
func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var start = 0
var count = -1
var source string
var ok bool
if len(args) < 1 {
return nil, errMissingRequiredParameter(name, paramSource)
}
if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0])
}
if len(args) > 1 {
if start, err = toInt(args[1], name+"()"); err != nil {
return
}
if len(args) > 2 {
if count, err = toInt(args[2], name+"()"); err != nil {
return
}
}
if start < 0 {
start = len(source) + start
}
}
if count < 0 {
count = len(source) - start
}
end := min(start+count, len(source))
result = source[start:end]
return
}
func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var source string
var ok bool
if len(args) < 1 {
return nil, errMissingRequiredParameter(name, paramSource)
}
if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0])
}
result = strings.TrimSpace(source)
return
}
func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var source string
var ok bool
result = false
if len(args) < 1 {
return result, errMissingRequiredParameter(name, paramSource)
}
if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0])
}
for i, targetSpec := range args[1:] {
if target, ok := targetSpec.(string); ok {
if strings.HasPrefix(source, target) {
result = true
break
}
} else {
err = fmt.Errorf("target item nr %d is %T, expected string", i+1, targetSpec)
break
}
}
return
}
func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var source string
var ok bool
result = false
if len(args) < 1 {
return result, errMissingRequiredParameter(name, paramSource)
}
if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0])
}
for i, targetSpec := range args[1:] {
if target, ok := targetSpec.(string); ok {
if strings.HasSuffix(source, target) {
result = true
break
}
} else {
err = fmt.Errorf("target item nr %d is %T, expected string", i+1, targetSpec)
break
}
}
return
}
func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var source, sep string
var count int = -1
var parts []string
var ok bool
if len(args) < 1 {
return result, errMissingRequiredParameter(name, paramSource)
}
if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0])
}
if len(args) >= 2 {
if sep, ok = args[1].(string); !ok {
return nil, fmt.Errorf("separator param must be string, got %T (%v)", args[1], args[1])
}
if len(args) >= 3 {
if count64, ok := args[2].(int64); ok { // TODO replace type assertion with toInt()
count = int(count64)
} else {
return nil, fmt.Errorf("part count must be integer, got %T (%v)", args[2], args[2])
}
}
}
if count > 0 {
parts = strings.SplitN(source, sep, count)
} else if count < 0 {
parts = strings.Split(source, sep)
} else {
parts = []string{}
}
list := make(ListType, len(parts))
for i, part := range parts {
list[i] = part
}
result = &list
return
}
// --- End of function definitions
// Import above functions in the context
func ImportStringFuncs(ctx ExprContext) {
ctx.RegisterFunc("joinStr", &simpleFunctor{f: joinStrFunc}, 1, -1)
ctx.RegisterFunc("subStr", &simpleFunctor{f: subStrFunc}, 1, -1)
ctx.RegisterFunc("splitStr", &simpleFunctor{f: splitStrFunc}, 2, -1)
ctx.RegisterFunc("trimStr", &simpleFunctor{f: trimStrFunc}, 1, -1)
ctx.RegisterFunc("startsWithStr", &simpleFunctor{f: startsWithStrFunc}, 2, -1)
ctx.RegisterFunc("endsWithStr", &simpleFunctor{f: endsWithStrFunc}, 2, -1)
}
// Register the import function in the import-register.
// That will allow to import all function of this module by the "builtin" operator."
func init() {
registerImport("string", ImportStringFuncs, "string utilities")
}
-66
View File
@@ -1,66 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// funcs_test.go
package expr
import (
"errors"
"testing"
)
func TestFuncs(t *testing.T) {
inputs := []inputType{
/* 1 */ {`isNil(nil)`, true, nil},
/* 2 */ {`v=nil; isNil(v)`, true, nil},
/* 3 */ {`v=5; isNil(v)`, false, nil},
/* 4 */ {`int(true)`, int64(1), nil},
/* 5 */ {`int(false)`, int64(0), nil},
/* 6 */ {`int(3.1)`, int64(3), nil},
/* 7 */ {`int(3.9)`, int64(3), nil},
/* 8 */ {`int("432")`, int64(432), nil},
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)},
/* 10 */ {`int("432", 4)`, nil, errors.New(`int() requires exactly one param`)},
/* 11 */ {`int(nil)`, nil, errors.New(`int() can't convert <nil> to int`)},
/* 12 */ {`two=func(){2}; two()`, int64(2), nil},
/* 13 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
/* 14 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
/* 15 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
/* 16 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
/* 17 */ {`builtin "import"; import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 18 */ {`builtin "import"; import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 19 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
/* 20 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
/* 21 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
/* 22 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
/* 23 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 24 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 25 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
/* 26 */ {`builtin "import"; importAll("./test-funcs.expr"); six()`, int64(6), nil},
/* 27 */ {`builtin "import"; import("./sample-export-all.expr"); six()`, int64(6), nil},
/* 28 */ {`builtin "string"; joinStr("-", "one", "two", "three")`, "one-two-three", nil},
/* 29 */ {`builtin "string"; joinStr("-", ["one", "two", "three"])`, "one-two-three", nil},
/* 30 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr("-", ls)`, "one-two-three", nil},
/* 31 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr(1, ls)`, nil, errors.New(`joinStr() the "separator" parameter must be a string, got a int64 (1)`)},
/* 32 */ {`builtin "string"; ls= ["one", 2, "three"]; joinStr("-", ls)`, nil, errors.New(`joinStr() expected string, got int64 (2)`)},
/* 33 */ {`builtin "string"; "<"+trimStr(" bye bye ")+">"`, "<bye bye>", nil},
/* 34 */ {`builtin "string"; subStr("0123456789", 1,2)`, "12", nil},
/* 35 */ {`builtin "string"; subStr("0123456789", -3,2)`, "78", nil},
/* 36 */ {`builtin "string"; subStr("0123456789", -3)`, "789", nil},
/* 37 */ {`builtin "string"; subStr("0123456789")`, "0123456789", nil},
/* 38 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "012")`, true, nil},
/* 39 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "0125")`, false, nil},
/* 40 */ {`builtin "string"; startsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)},
/* 41 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "789")`, true, nil},
/* 42 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "0125")`, false, nil},
/* 43 */ {`builtin "string"; endsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)},
/* 44 */ {`builtin "string"; splitStr("one-two-three", "-", )`, newListA("one","two","three"), nil},
/* 45 */ {`["a", "b", "c"]`, newListA("a","b","c"), nil},
/* 46 */ {`["a", "b", "c"]`, newList([]any{"a","b","c"}), nil},
}
t.Setenv("EXPR_PATH", ".")
// parserTest(t, "Func", inputs[25:26])
parserTest(t, "Func", inputs)
}
+47
View File
@@ -0,0 +1,47 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// function-register.go
package expr
import (
"path/filepath"
)
var functionRegister map[string]func(ExprContext)
func registerImport(name string, importFunc func(ExprContext)) {
if functionRegister == nil {
functionRegister = make(map[string]func(ExprContext))
}
functionRegister[name] = importFunc
}
func ImportInContext(ctx ExprContext, name string) (exists bool) {
var importFunc func(ExprContext)
if importFunc, exists = functionRegister[name]; exists {
importFunc(ctx)
}
return
}
func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
var matched bool
for name, importFunc := range functionRegister {
if matched, err = filepath.Match(pattern, name); err == nil {
if matched {
count++
importFunc(ctx)
}
} else {
break
}
}
return
}
func init() {
if functionRegister == nil {
functionRegister = make(map[string]func(ExprContext))
}
}
-23
View File
@@ -6,8 +6,6 @@ package expr
import ( import (
"fmt" "fmt"
"io"
"os"
"strings" "strings"
) )
@@ -61,24 +59,3 @@ func EvalStringV(source string, args []Arg) (result any, err error) {
} }
return return
} }
func EvalStream(ctx ExprContext, r io.Reader) (result any, err error) {
var tree *ast
scanner := NewScanner(r, DefaultTranslations())
parser := NewParser(ctx)
if tree, err = parser.Parse(scanner); err == nil {
result, err = tree.Eval(ctx)
}
return
}
func EvalFile(ctx ExprContext, filePath string) (result any, err error) {
var fh *os.File
if fh, err = os.Open(filePath); err != nil {
return nil, err
}
defer fh.Close()
result, err = EvalStream(ctx, fh)
return
}
+3
View File
@@ -0,0 +1,3 @@
First test
next commit
nr 3
-4
View File
@@ -1,4 +0,0 @@
10
20
5
12
-132
View File
@@ -1,132 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iter-list.go
package expr
import (
"fmt"
"io"
)
type ListIterator struct {
a *ListType
count int
index int
start int
stop int
step int
}
func NewListIterator(list *ListType, args []any) (it *ListIterator) {
var argc int = 0
listLen := len(([]any)(*list))
if args != nil {
argc = len(args)
}
it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1}
if argc >= 1 {
if i, err := toInt(args[0], "start index"); err == nil {
if i < 0 {
i = listLen + i
}
it.start = i
}
if argc >= 2 {
if i, err := toInt(args[1], "stop index"); err == nil {
if i < 0 {
i = listLen + i
}
it.stop = i
}
if argc >= 3 {
if i, err := toInt(args[2], "step"); err == nil {
if i < 0 {
i = -i
}
if it.start > it.stop {
it.step = -i
} else {
it.step = i
}
}
}
}
}
it.index = it.start - it.step
return
}
func NewArrayIterator(array []any) (it *ListIterator) {
it = &ListIterator{a: (*ListType)(&array), count: 0, index: -1, start: 0, stop: len(array) - 1, step: 1}
return
}
func NewAnyIterator(value any) (it *ListIterator) {
if value == nil {
it = NewArrayIterator([]any{})
} else if list, ok := value.(*ListType); ok {
it = NewListIterator(list, nil)
} else if array, ok := value.([]any); ok {
it = NewArrayIterator(array)
} else if it1, ok := value.(*ListIterator); ok {
it = it1
} else {
it = NewArrayIterator([]any{value})
}
return
}
func (it *ListIterator) String() string {
var l = 0
if it.a != nil {
l = len(*it.a)
}
return fmt.Sprintf("$(#%d)", l)
}
func (it *ListIterator) HasOperation(name string) bool {
yes := name == resetName || name == indexName || name == countName
return yes
}
func (it *ListIterator) CallOperation(name string, args []any) (v any, err error) {
switch name {
case resetName:
v, err = it.Reset()
case indexName:
v = int64(it.Index())
case countName:
v = it.count
default:
err = errNoOperation(name)
}
return
}
func (it *ListIterator) Current() (item any, err error) {
a := *(it.a)
if it.index >= 0 && it.index <= it.stop {
item = a[it.index]
} else {
err = io.EOF
}
return
}
func (it *ListIterator) Next() (item any, err error) {
it.index += it.step
if item, err = it.Current(); err != io.EOF {
it.count++
}
return
}
func (it *ListIterator) Index() int {
return it.index
}
func (it *ListIterator) Reset() (bool, error) {
it.index = it.start
return true, nil
}
-18
View File
@@ -1,18 +0,0 @@
ds={
"init":func(end){@end=end; @current=0; @prev=@current},
"current":func(){prev},
"next":func(){
(current <= end) ? [true] {@current=current+1; @prev=current} :: {nil}
},
"reset":func(){@current=0; @prev=@current}
}
// Example
//;
//it=$(ds,3);
//it++;
//it."reset"
//it++;
//it++;
//add(it)
+23 -25
View File
@@ -4,39 +4,37 @@
// iterator.go // iterator.go
package expr package expr
import ( import "io"
"errors"
"fmt"
)
// Operator names
const (
initName = "init"
cleanName = "clean"
resetName = "reset"
nextName = "next"
currentName = "current"
indexName = "index"
countName = "count"
)
type Iterator interface { type Iterator interface {
Reset()
Next() (item any, err error) // must return io.EOF after the last item Next() (item any, err error) // must return io.EOF after the last item
Current() (item any, err error)
Index() int Index() int
} }
type ExtIterator interface { type FlatArrayIterator struct {
Iterator a []any
HasOperation(name string) bool index int
CallOperation(name string, args []any) (value any, err error)
} }
func errNoOperation(name string) error { func NewFlatArrayIterator(array []any) *FlatArrayIterator {
return fmt.Errorf("no %q function defined in the data-source", name) return &FlatArrayIterator{a: array, index: 0}
} }
func errInvalidDataSource() error { func (it *FlatArrayIterator) Reset() {
return errors.New("invalid data-source") it.index = 0
}
func (it *FlatArrayIterator) Next() (item any, err error) {
if it.index < len(it.a) {
item = it.a[it.index]
it.index++
} else {
err = io.EOF
}
return
}
func (it *FlatArrayIterator) Index() int {
return it.index - 1
} }
-26
View File
@@ -1,26 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iterator_test.go
package expr
import "testing"
func TestIteratorParser(t *testing.T) {
inputs := []inputType{
/* 1 */ {`include "iterator.expr"; it=$(ds,3); ()it`, int64(0), nil},
/* 2 */ {`include "iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil},
/* 3 */ {`include "iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(2), nil},
/* 4 */ {`include "iterator.expr"; it=$(ds,3); it++; it++; it.reset; ()it`, int64(0), nil},
/* 5 */ {`builtin "math.arith"; include "iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil},
/* 6 */ {`builtin "math.arith"; include "iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil},
/* 7 */ {`builtin "math.arith"; include "file-reader.expr"; it=$(ds,"int.list"); mul(it)`, int64(12000), nil},
/* 8 */ {`include "file-reader.expr"; it=$(ds,"int.list"); it++; it.index`, int64(0), nil},
/* 10 */ {`include "file-reader.expr"; it=$(ds,"int.list"); it.clean`, true, nil},
/* 11 */ {`it=$(1,2,3); it++`, int64(1), nil},
}
// inputs1 := []inputType{
// /* 1 */ {`0?{}`, nil, nil},
// }
parserTest(t, "Iterator", inputs)
}
-115
View File
@@ -1,115 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// list_test.go
package expr
import (
"errors"
"strings"
"testing"
)
func TestListParser(t *testing.T) {
section := "List"
type inputType struct {
source string
wantResult any
wantErr error
}
inputs := []inputType{
/* 1 */ {`[]`, []any{}, nil},
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
/* 7 */ {`add([1,4,3,2])`, int64(10), nil},
/* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
/* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
/* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
/* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 14 */ {`[1,2,3].1`, int64(2), nil},
/* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
/* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
// /* 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},
}
succeeded := 0
failed := 0
// inputs1 := []inputType{
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
// }
for i, input := range inputs {
var expr *ast
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, "List", 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)
}
if (gotResult == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotList, okGot := gotResult.([]any); okGot {
if wantList, okWant := input.wantResult.([]any); okWant {
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
} else {
equal := len(gotList) == len(wantList)
if equal {
for i, gotItem := range gotList {
wantItem := wantList[i]
equal = gotItem == wantItem
if !equal {
break
}
}
}
if !equal {
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 {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
-74
View File
@@ -1,74 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// module-register.go
package expr
import (
"fmt"
"path/filepath"
)
type module struct {
importFunc func(ExprContext)
description string
imported bool
}
func newModule(importFunc func(ExprContext), description string) *module {
return &module{importFunc, description, false}
}
var moduleRegister map[string]*module
func registerImport(name string, importFunc func(ExprContext), description string) {
if moduleRegister == nil {
moduleRegister = make(map[string]*module)
}
if _, exists := moduleRegister[name]; exists {
panic(fmt.Errorf("module %q already registered", name))
}
moduleRegister[name] = newModule(importFunc, description)
}
func ImportInContext(ctx ExprContext, name string) (exists bool) {
var mod *module
if mod, exists = moduleRegister[name]; exists {
mod.importFunc(ctx)
mod.imported = true
}
return
}
func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
var matched bool
for name, mod := range moduleRegister {
if matched, err = filepath.Match(pattern, name); err == nil {
if matched {
count++
mod.importFunc(ctx)
mod.imported = true
}
} else {
break
}
}
return
}
func IterateModules(op func(name, description string, imported bool) bool) {
if op != nil {
for name, mod := range moduleRegister {
if !op(name, mod.description, mod.imported) {
break
}
}
}
}
// ----
func init() {
if moduleRegister == nil {
moduleRegister = make(map[string]*module)
}
}
+48 -7
View File
@@ -4,8 +4,22 @@
// operand-const.go // operand-const.go
package expr package expr
// -------- const term // -------- bool const term
func newConstTerm(tk *Token) *term { func newBoolTerm(tk *Token) *term {
return &term{
tk: *tk,
// class: classConst,
// kind: kindBool,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalConst,
}
}
// -------- integer const term
func newIntegerTerm(tk *Token) *term {
return &term{ return &term{
tk: *tk, tk: *tk,
parent: nil, parent: nil,
@@ -16,6 +30,34 @@ func newConstTerm(tk *Token) *term {
} }
} }
// -------- float const term
func newFloatTerm(tk *Token) *term {
return &term{
tk: *tk,
// class: classConst,
// kind: kindFloat,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalConst,
}
}
// -------- string const term
func newStringTerm(tk *Token) *term {
return &term{
tk: *tk,
// class: classConst,
// kind: kindString,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalConst,
}
}
// -------- eval func // -------- eval func
func evalConst(ctx ExprContext, self *term) (v any, err error) { func evalConst(ctx ExprContext, self *term) (v any, err error) {
v = self.tk.Value v = self.tk.Value
@@ -24,9 +66,8 @@ func evalConst(ctx ExprContext, self *term) (v any, err error) {
// init // init
func init() { func init() {
registerTermConstructor(SymString, newConstTerm) registerTermConstructor(SymString, newStringTerm)
registerTermConstructor(SymInteger, newConstTerm) registerTermConstructor(SymInteger, newIntegerTerm)
registerTermConstructor(SymFloat, newConstTerm) registerTermConstructor(SymFloat, newFloatTerm)
registerTermConstructor(SymBool, newConstTerm) registerTermConstructor(SymBool, newBoolTerm)
registerTermConstructor(SymKwNil, newConstTerm)
} }
-34
View File
@@ -1,34 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-dict.go
package expr
// -------- dict term
func newDictTerm(args map[any]*term) *term {
return &term{
tk: *NewValueToken(0, 0, SymDict, "{}", args),
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalDict,
}
}
// -------- dict func
func evalDict(ctx ExprContext, self *term) (v any, err error) {
dict, _ := self.value().(map[any]*term)
items := make(map[any]any, len(dict))
for key, tree := range dict {
var param any
if param, err = tree.compute(ctx); err != nil {
break
}
items[key] = param
}
if err == nil {
v = items
}
return
}
+5
View File
@@ -27,3 +27,8 @@ func evalExpr(ctx ExprContext, self *term) (v any, err error) {
} }
return return
} }
// init
// func init() {
// registerTermConstructor(SymExpression, newExprTerm)
// }
+38 -27
View File
@@ -6,13 +6,14 @@ package expr
import ( import (
"errors" "errors"
"fmt"
) )
// -------- function call term // -------- function call term
func newFuncCallTerm(tk *Token, args []*term) *term { func newFuncCallTerm(tk *Token, args []*term) *term {
return &term{ return &term{
tk: *tk, tk: *tk,
// class: classVar,
// kind: kindUnknown,
parent: nil, parent: nil,
children: args, children: args,
position: posLeaf, position: posLeaf,
@@ -22,28 +23,9 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
} }
// -------- eval func call // -------- eval func call
func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
if info, exists := ctx.GetFuncInfo(name); exists {
if info.MinArgs() > len(params) {
if info.MaxArgs() < 0 {
err = fmt.Errorf("too few params -- expected %d or more, got %d", info.MinArgs(), len(params))
} else {
err = fmt.Errorf("too few params -- expected %d, got %d", info.MinArgs(), len(params))
}
}
if info.MaxArgs() >= 0 && info.MaxArgs() < len(params) {
err = fmt.Errorf("too much params -- expected %d, got %d", info.MaxArgs(), len(params))
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) { func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
ctx := cloneContext(parentCtx) ctx := parentCtx.Clone()
name, _ := self.tk.Value.(string) name, _ := self.tk.Value.(string)
// fmt.Printf("Call %s(), context: %p\n", name, ctx)
params := make([]any, len(self.children)) params := make([]any, len(self.children))
for i, tree := range self.children { for i, tree := range self.children {
var param any var param any
@@ -53,21 +35,44 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
params[i] = param params[i] = param
} }
if err == nil { if err == nil {
if err = checkFunctionCall(ctx, name, params); err == nil { if v, err = ctx.Call(name, params); err == nil {
if v, err = ctx.Call(name, params); err == nil { exportAll := isEnabled(ctx, control_export_all)
exportObjects(parentCtx, ctx) // Export variables
for _, refName := range ctx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
refValue, _ := ctx.GetVar(refName)
exportVar(parentCtx, refName, refValue)
}
// Export functions
for _, refName := range ctx.EnumFuncs(func(name string) bool { return exportAll || name[0] == '@' }) {
if info := ctx.GetFuncInfo(refName); info != nil {
exportFunc(parentCtx, refName, info)
}
} }
} }
} }
return return
} }
func exportVar(ctx ExprContext, name string, value any) {
if name[0] == '@' {
name = name[1:]
}
ctx.setVar(name, value)
}
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
if name[0] == '@' {
name = name[1:]
}
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
}
// -------- function definition term // -------- function definition term
func newFuncDefTerm(tk *Token, args []*term) *term { func newFuncDefTerm(tk *Token, args []*term) *term {
return &term{ return &term{
tk: *tk, // value is the expression body tk: *tk,
parent: nil, parent: nil,
children: args, // function params children: args, // arg[0]=formal-param-list, arg[1]=*ast
position: posLeaf, position: posLeaf,
priority: priValue, priority: priValue,
evalFunc: evalFuncDef, evalFunc: evalFuncDef,
@@ -104,6 +109,12 @@ func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
paramList := make([]string, 0, len(self.children)) paramList := make([]string, 0, len(self.children))
for _, param := range self.children { for _, param := range self.children {
paramList = append(paramList, param.source()) paramList = append(paramList, param.source())
// if paramName, ok := param.value().(string); ok {
// paramList = append(paramList, paramName)
// } else {
// err = fmt.Errorf("invalid function definition: formal param nr %d must be an identifier", i+1)
// break
// }
} }
v = &funcDefFunctor{ v = &funcDefFunctor{
params: paramList, params: paramList,
-184
View File
@@ -1,184 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-iterator.go
package expr
import (
"fmt"
"slices"
"strings"
)
// -------- iterator term
func newDsIteratorTerm(tk *Token, dsTerm *term, args []*term) *term {
tk.Sym = SymIterator
children := make([]*term, 0, 1+len(args))
children = append(children, dsTerm)
children = append(children, args...)
return &term{
tk: *tk,
parent: nil,
children: children,
position: posLeaf,
priority: priValue,
evalFunc: evalIterator,
}
}
func newIteratorTerm(tk *Token, args []*term) *term {
tk.Sym = SymIterator
return &term{
tk: *tk,
parent: nil,
children: args,
position: posLeaf,
priority: priValue,
evalFunc: evalIterator,
}
}
// -------- eval iterator
func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
values = make([]any, len(a))
for i, t := range a {
var value any
if value, err = t.compute(ctx); err == nil {
values[i] = value
} else {
break
}
}
return
}
func evalFirstChild(ctx ExprContext, self *term) (value any, err error) {
if len(self.children) < 1 || self.children[0] == nil {
err = self.Errorf("missing the data-source parameter")
return
}
value, err = self.children[0].compute(ctx)
return
}
func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map[string]Functor, err error) {
if dictAny, ok := firstChildValue.(map[any]any); ok {
requiredFields := []string{currentName, nextName}
fieldsMask := 0b11
foundFields := 0
ds = make(map[string]Functor)
for keyAny, item := range dictAny {
if key, ok := keyAny.(string); ok {
if functor, ok := item.(*funcDefFunctor); ok {
ds[key] = functor
if index := slices.Index(requiredFields, key); index >= 0 {
foundFields |= 1 << index
}
}
}
}
// check required functions
if foundFields != fieldsMask {
missingFields := make([]string, 0, len(requiredFields))
for index, field := range requiredFields {
if (foundFields & (1 << index)) == 0 {
missingFields = append(missingFields, field)
}
}
err = fmt.Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
}
}
return
}
func evalIterator(ctx ExprContext, self *term) (v any, err error) {
var firstChildValue any
var ds map[string]Functor
if firstChildValue, err = evalFirstChild(ctx, self); err != nil {
return
}
if ds, err = getDataSourceDict(ctx, self, firstChildValue); err != nil {
return
}
if ds != nil {
dc := newDataCursor(ctx, ds)
if initFunc, exists := ds[initName]; exists && initFunc != nil {
var args []any
if len(self.children) > 1 {
if args, err = evalTermArray(ctx, self.children[1:]); err != nil {
return
}
} else {
args = []any{}
}
initCtx := dc.ctx.Clone()
if dc.resource, err = initFunc.Invoke(initCtx, initName, args); err != nil {
return
}
exportObjects(dc.ctx, initCtx)
}
dc.nextFunc, _ = ds[nextName]
dc.currentFunc, _ = ds[currentName]
dc.cleanFunc, _ = ds[cleanName]
dc.resetFunc, _ = ds[resetName]
v = dc
} else if list, ok := firstChildValue.(*ListType); ok {
var args []any
if args, err = evalSibling(ctx, self.children, nil); err == nil {
v = NewListIterator(list, args)
}
} else {
var list []any
if list, err = evalSibling(ctx, self.children, firstChildValue); err == nil {
v = NewArrayIterator(list)
}
}
return
}
// func evalChildren(ctx ExprContext, terms []*term, firstChildValue any) (list *ListType, err error) {
// items := make(ListType, len(terms))
// for i, tree := range terms {
// var param any
// if i == 0 && firstChildValue != nil {
// param = firstChildValue
// } else if param, err = tree.compute(ctx); err != nil {
// break
// }
// items[i] = param
// }
// if err == nil {
// list = &items
// }
// return
// }
func evalSibling(ctx ExprContext, terms []*term, firstChildValue any) (list []any, err error) {
items := make([]any, 0, len(terms))
for i, tree := range terms {
var param any
if i == 0 {
if firstChildValue == nil {
continue
}
param = firstChildValue
} else if param, err = tree.compute(ctx); err != nil {
break
}
items = append(items, param)
}
if err == nil {
list = items
}
return
}
+30 -60
View File
@@ -4,64 +4,6 @@
// operand-list.go // operand-list.go
package expr package expr
import (
"fmt"
"strings"
)
type ListType []any
func (ls *ListType) ToString(opt FmtOpt) string {
var sb strings.Builder
sb.WriteByte('[')
if len(*ls) > 0 {
if opt&MultiLine != 0 {
sb.WriteString("\n ")
}
for i, item := range []any(*ls) {
if i > 0 {
if opt&MultiLine != 0 {
sb.WriteString(",\n ")
} else {
sb.WriteString(", ")
}
}
if s, ok := item.(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else {
sb.WriteString(fmt.Sprintf("%v", item))
}
}
if opt&MultiLine != 0 {
sb.WriteByte('\n')
}
}
sb.WriteByte(']')
return sb.String()
}
func (ls *ListType) String() string {
return ls.ToString(0)
}
func newListA(listAny ...any) (list *ListType) {
return newList(listAny)
}
func newList(listAny []any) (list *ListType) {
if listAny != nil {
ls := make(ListType, len(listAny))
for i, item := range listAny {
ls[i] = item
}
list = &ls
}
return
}
// -------- list term // -------- list term
func newListTermA(args ...*term) *term { func newListTermA(args ...*term) *term {
return newListTerm(args) return newListTerm(args)
@@ -81,7 +23,7 @@ func newListTerm(args []*term) *term {
// -------- list func // -------- list func
func evalList(ctx ExprContext, self *term) (v any, err error) { func evalList(ctx ExprContext, self *term) (v any, err error) {
list, _ := self.value().([]*term) list, _ := self.value().([]*term)
items := make(ListType, len(list)) items := make([]any, len(list))
for i, tree := range list { for i, tree := range list {
var param any var param any
if param, err = tree.compute(ctx); err != nil { if param, err = tree.compute(ctx); err != nil {
@@ -90,7 +32,35 @@ func evalList(ctx ExprContext, self *term) (v any, err error) {
items[i] = param items[i] = param
} }
if err == nil { if err == nil {
v = &items v = items
} }
return return
} }
// // -------- list term
// func newListTerm(args []*term) *term {
// return &term{
// tk: *NewToken(0, 0, SymList, "[]"),
// parent: nil,
// children: args,
// position: posLeaf,
// priority: priValue,
// evalFunc: evalList,
// }
// }
// // -------- list func
// func evalList(ctx ExprContext, self *term) (v any, err error) {
// items := make([]any, len(self.children))
// for i, tree := range self.children {
// var param any
// if param, err = tree.compute(ctx); err != nil {
// break
// }
// items[i] = param
// }
// if err == nil {
// v = items
// }
// return
// }
+2 -7
View File
@@ -23,13 +23,8 @@ func newVarTerm(tk *Token) *term {
// -------- eval func // -------- eval func
func evalVar(ctx ExprContext, self *term) (v any, err error) { func evalVar(ctx ExprContext, self *term) (v any, err error) {
var exists bool var exists bool
name := self.source() if v, exists = ctx.GetVar(self.tk.source); !exists {
if v, exists = ctx.GetVar(name); !exists { err = fmt.Errorf("undefined variable %q", self.tk.source)
if info, exists := ctx.GetFuncInfo(name); exists {
v = info.Functor()
} else {
err = fmt.Errorf("undefined variable or function %q", name)
}
} }
return return
} }
+4 -17
View File
@@ -1,5 +1,5 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserightChilded. // All rights reserved.
// operator-assign.go // operator-assign.go
package expr package expr
@@ -27,24 +27,11 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
return return
} }
rightChild := self.children[1] if v, err = self.children[1].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 ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
funcName := rightChild.source()
if info, exists := ctx.GetFuncInfo(funcName); exists {
minArgs = info.MinArgs()
maxArgs = info.MaxArgs()
} else if funcDef, ok := functor.(*funcDefFunctor); ok {
l := len(funcDef.params)
minArgs = l
maxArgs = l
}
ctx.RegisterFunc(leftTerm.source(), functor, minArgs, maxArgs)
} else { } else {
ctx.setVar(leftTerm.source(), v) ctx.setVar(leftTerm.tk.source, v)
} }
} }
return return
+13 -17
View File
@@ -1,11 +1,9 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// operator-builtin.go // operator-length.go
package expr package expr
import "io"
//-------- builtin term //-------- builtin term
func newBuiltinTerm(tk *Token) (inst *term) { func newBuiltinTerm(tk *Token) (inst *term) {
@@ -19,20 +17,16 @@ func newBuiltinTerm(tk *Token) (inst *term) {
} }
func evalBuiltin(ctx ExprContext, self *term) (v any, err error) { func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
var childValue any var rightValue any
if childValue, err = self.evalPrefix(ctx); err != nil { if rightValue, err = self.evalPrefix(ctx); err != nil {
return return
} }
count := 0 count := 0
if IsString(childValue) { if isList(rightValue) {
module, _ := childValue.(string) list, _ := rightValue.([]any)
count, err = ImportInContextByGlobPattern(ctx, module) for i, moduleSpec := range list {
} else {
var moduleSpec any
it := NewAnyIterator(childValue)
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok { if module, ok := moduleSpec.(string); ok {
if ImportInContext(ctx, module) { if ImportInContext(ctx, module) {
count++ count++
@@ -41,16 +35,18 @@ func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
break break
} }
} else { } else {
err = self.Errorf("expected string at item nr %d, got %T", it.Index()+1, moduleSpec) err = self.Errorf("expected string at item nr %d, got %T", i+1, moduleSpec)
break break
} }
} }
if err == io.EOF { } else if isString(rightValue) {
err = nil module, _ := rightValue.(string)
} count, err = ImportInContextByGlobPattern(ctx, module)
} else {
err = self.errIncompatibleType(rightValue)
} }
if err == nil { if err == nil {
v = int64(count) v = count
} }
return return
} }
+10 -10
View File
@@ -34,11 +34,11 @@ 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 { if _, ok := rightValue.(Functor); ok {
// err = errCoalesceNoFunc(self.children[1]) err = errCoalesceNoFunc(self.children[1])
// } else { } else {
v = rightValue v = rightValue
// } }
} }
return return
} }
@@ -71,8 +71,8 @@ func evalAssignCoalesce(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 functor, ok := rightValue.(Functor); ok { if _, ok := rightValue.(Functor); ok {
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1) err = errCoalesceNoFunc(self.children[1])
} else { } else {
v = rightValue v = rightValue
ctx.setVar(leftTerm.source(), rightValue) ctx.setVar(leftTerm.source(), rightValue)
@@ -82,9 +82,9 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
} }
// utils // utils
// func errCoalesceNoFunc(t *term) error { func errCoalesceNoFunc(t *term) error {
// return t.Errorf("the right operand of a coalescing operation cannot be a function definition") return t.Errorf("the right operand of a coalescing operation cannot be a function definition")
// } }
// init // init
func init() { func init() {
-57
View File
@@ -1,57 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-context-value.go
package expr
//-------- context term
func newContextTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priIncDec,
evalFunc: evalContextValue,
}
}
func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
var childValue any
var sourceCtx ExprContext
if len(self.children) == 0 {
sourceCtx = ctx
} else if childValue, err = self.evalPrefix(ctx); err == nil {
if dc, ok := childValue.(*dataCursor); ok {
sourceCtx = dc.ctx
}
} else {
return
}
if sourceCtx != nil {
if formatter, ok := ctx.(Formatter); ok {
v = formatter.ToString(0)
} else {
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
d := make(map[string]any)
for _, key := range keys {
d[key], _ = sourceCtx.GetVar(key)
}
keys = sourceCtx.EnumFuncs(func(name string) bool { return true })
for _, key := range keys {
d[key], _ = sourceCtx.GetFuncInfo(key)
}
v = d
}
} else {
err = self.errIncompatibleType(childValue)
}
return
}
// init
func init() {
registerTermConstructor(SymDoubleDollar, newContextTerm)
}
+25 -64
View File
@@ -4,8 +4,6 @@
// operator-dot.go // operator-dot.go
package expr package expr
import "fmt"
// -------- dot term // -------- dot term
func newDotTerm(tk *Token) (inst *term) { func newDotTerm(tk *Token) (inst *term) {
return &term{ return &term{
@@ -17,78 +15,41 @@ func newDotTerm(tk *Token) (inst *term) {
} }
} }
func verifyIndex(ctx ExprContext, indexTerm *term, maxValue int) (index int, err error) {
var v int
var indexValue any
if indexValue, err = indexTerm.compute(ctx); err == nil {
if v, err = indexTerm.toInt(indexValue, "index expression value must be integer"); err == nil {
if v >= 0 && v < maxValue {
index = v
} else if index >= -maxValue {
index = maxValue + v
} else {
err = indexTerm.Errorf("index %d out of bounds", v)
}
}
}
return
}
func evalDot(ctx ExprContext, self *term) (v any, err error) { func evalDot(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
if err = self.checkOperands(); err != nil { if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if leftValue, err = self.children[0].compute(ctx); err != nil {
return return
} }
indexTerm := self.children[1] indexTerm := self.children[1]
if !isInteger(rightValue) {
err = indexTerm.Errorf("index expression must be integer, got %T", rightValue)
return
}
switch unboxedValue := leftValue.(type) { index64, _ := rightValue.(int64)
case *ListType: index := int(index64)
var index int
array := ([]any)(*unboxedValue) if isList(leftValue) {
if index, err = verifyIndex(ctx, indexTerm, len(array)); err == nil { list, _ := leftValue.([]any)
v = array[index] if index >= 0 && index < len(list) {
v = list[index]
} else if index >= -len(list) {
v = list[len(list)+index]
} else {
err = indexTerm.Errorf("index %v out of bounds", index)
} }
case string: } else if isString(leftValue) {
var index int s, _ := leftValue.(string)
if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil { if index >= 0 && index < len(s) {
v = unboxedValue[index] v = string(s[index])
} else if index >= -len(s) {
v = string(s[len(s)+index])
} else {
err = indexTerm.Errorf("index %v out of bounds", index)
} }
case map[any]any: } else {
var ok bool
var indexValue any
if indexValue, err = indexTerm.compute(ctx); err == nil {
if v, ok = unboxedValue[indexValue]; !ok {
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
}
}
// case *dataCursor:
// if indexTerm.symbol() == SymIdentifier {
// opName := indexTerm.source()
// if opName == resetName {
// _, err = unboxedValue.Reset()
// } else if opName == cleanName {
// _, err = unboxedValue.Clean()
// } else {
// err = indexTerm.Errorf("iterators do not support command %q", opName)
// }
// v = err == nil
// }
case ExtIterator:
if indexTerm.symbol() == SymIdentifier {
opName := indexTerm.source()
if unboxedValue.HasOperation(opName) {
v, err = unboxedValue.CallOperation(opName, []any{})
} else {
err = indexTerm.Errorf("this iterator do not support the %q command", opName)
v = false
}
}
default:
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
return return
+1 -1
View File
@@ -25,7 +25,7 @@ func evalFact(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if IsInteger(leftValue) { if isInteger(leftValue) {
if i, _ := leftValue.(int64); i >= 0 { if i, _ := leftValue.(int64); i >= 0 {
f := int64(1) f := int64(1)
for k := int64(1); k <= i; k++ { for k := int64(1); k <= i; k++ {
-282
View File
@@ -1,282 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-fraction.go
package expr
import (
"errors"
"fmt"
"strconv"
"strings"
)
type fraction struct {
num, den int64
}
func newFraction(num, den int64) *fraction {
/* if den < 0 {
den = -den
num = -num
}*/
num, den = simplifyIntegers(num, den)
return &fraction{num, den}
}
func (f *fraction) toFloat() float64 {
return float64(f.num) / float64(f.den)
}
func (f *fraction) String() string {
return f.ToString(0)
}
func (f *fraction) ToString(opt FmtOpt) string {
var sb strings.Builder
if opt&MultiLine == 0 {
sb.WriteString(fmt.Sprintf("%d|%d", f.num, f.den))
} else {
var s, num string
if f.num < 0 && opt&TTY == 0 {
num = strconv.FormatInt(-f.num, 10)
s = "-"
} else {
num = strconv.FormatInt(f.num, 10)
}
den := strconv.FormatInt(f.den, 10)
size := max(len(num), len(den))
if opt&TTY != 0 {
sb.WriteString(fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, s+num)))
} else {
if len(s) > 0 {
sb.WriteString(" ")
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
sb.WriteByte('\n')
if len(s) > 0 {
sb.WriteString(s)
sb.WriteByte(' ')
}
sb.WriteString(strings.Repeat("-", size))
sb.WriteByte('\n')
if len(s) > 0 {
sb.WriteString(" ")
}
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den)))
}
return sb.String()
}
// -------- fraction term
func newFractionTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: make([]*term, 0, 2),
position: posInfix,
priority: priFraction,
evalFunc: evalFraction,
}
}
// -------- eval func
func evalFraction(ctx ExprContext, self *term) (v any, err error) {
var numValue, denValue any
var num, den int64
var ok bool
if numValue, denValue, err = self.evalInfix(ctx); err != nil {
return
}
if num, ok = numValue.(int64); !ok {
err = fmt.Errorf("numerator must be integer, got %T (%v)", numValue, numValue)
return
}
if den, ok = denValue.(int64); !ok {
err = fmt.Errorf("denominator must be integer, got %T (%v)", denValue, denValue)
return
}
if den == 0 {
err = errors.New("division by zero")
return
}
if den < 0 {
den = -den
num = -num
}
g := gcd(num, den)
num = num / g
den = den / g
if den == 1 {
v = num
} else {
v = &fraction{num, den}
}
return
}
func gcd(a, b int64) (g int64) {
if a < 0 {
a = -a
}
if b < 0 {
b = -b
}
if a < b {
a, b = b, a
}
r := a % b
for r > 0 {
a, b = b, r
r = a % b
}
g = b
return
}
func lcm(a, b int64) (l int64) {
g := gcd(a, b)
l = a * b / g
return
}
func sumFract(f1, f2 *fraction) (sum *fraction) {
m := lcm(f1.den, f2.den)
sum = newFraction(f1.num*(m/f1.den) + f2.num*(m/f2.den), m)
return
}
func mulFract(f1, f2 *fraction) (prod *fraction) {
prod = newFraction(f1.num * f2.num, f1.den * f2.den)
return
}
func anyToFract(v any) (f *fraction, err error) {
var ok bool
if f, ok = v.(*fraction); !ok {
if n, ok := v.(int64); ok {
f = intToFraction(n)
}
}
if f == nil {
err = errExpectedGot("fract", typeFraction, v)
}
return
}
func anyPairToFract(v1, v2 any) (f1, f2 *fraction, err error) {
if f1, err = anyToFract(v1); err != nil {
return
}
if f2, err = anyToFract(v2); err != nil {
return
}
return
}
func sumAnyFract(af1, af2 any) (sum any, err error) {
var f1, f2 *fraction
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
f := sumFract(f1, f2)
if f.num == 0 {
sum = 0
} else {
sum = simplifyFraction(f)
}
return
}
func subAnyFract(af1, af2 any) (sum any, err error) {
var f1, f2 *fraction
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
f2.num = -f2.num
f := sumFract(f1, f2)
if f.num == 0 {
sum = 0
} else {
sum = simplifyFraction(f)
}
return
}
func mulAnyFract(af1, af2 any) (prod any, err error) {
var f1, f2 *fraction
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
if f1.num == 0 || f2.num == 0 {
prod = 0
} else {
f := &fraction{f1.num * f2.num, f1.den * f2.den}
prod = simplifyFraction(f)
}
return
}
func divAnyFract(af1, af2 any) (quot any, err error) {
var f1, f2 *fraction
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
if f2.num == 0 {
err = errors.New("division by zero")
return
return
}
if f1.num == 0 || f2.den == 0 {
quot = 0
} else {
f := &fraction{f1.num * f2.den, f1.den * f2.num}
quot = simplifyFraction(f)
}
return
}
func simplifyFraction(f *fraction) (v any) {
f.num, f.den = simplifyIntegers(f.num, f.den)
if f.den == 1 {
v = f.num
} else {
v = &fraction{f.num, f.den}
}
return v
}
func simplifyIntegers(num, den int64) (a, b int64) {
if num == 0 {
return 0, 1
}
if den == 0 {
panic("fraction with denominator == 0")
}
if den < 0 {
den = -den
num = -num
}
g := gcd(num, den)
a = num / g
b = den / g
return
}
func intToFraction(n int64) *fraction {
return &fraction{n, 1}
}
func isFraction(v any) (ok bool) {
_, ok = v.(*fraction)
return ok
}
// init
func init() {
registerTermConstructor(SymVertBar, newFractionTerm)
}
-57
View File
@@ -1,57 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-include.go
package expr
//-------- include term
func newIncludeTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalInclude,
}
}
func evalInclude(ctx ExprContext, self *term) (v any, err error) {
var childValue any
if childValue, err = self.evalPrefix(ctx); err != nil {
return
}
count := 0
if IsList(childValue) {
list, _ := childValue.([]any)
for i, filePathSpec := range list {
if filePath, ok := filePathSpec.(string); ok {
if v, err = EvalFile(ctx, filePath); err == nil {
count++
} else {
err = self.Errorf("can't load file %q", filePath)
break
}
} else {
err = self.Errorf("expected string at item nr %d, got %T", i+1, filePathSpec)
break
}
}
} else if IsString(childValue) {
filePath, _ := childValue.(string)
v, err = EvalFile(ctx, filePath)
} else {
err = self.errIncompatibleType(childValue)
}
if err == nil {
v = count
}
return
}
// init
func init() {
registerTermConstructor(SymKwInclude, newIncludeTerm)
}
+6 -8
View File
@@ -33,10 +33,9 @@ func evalInsert(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if IsList(rightValue) { if isList(rightValue) {
list, _ := rightValue.(*ListType) list, _ := rightValue.([]any)
newList := append(ListType{leftValue}, *list...) v = append([]any{leftValue}, list...)
v = &newList
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
@@ -50,10 +49,9 @@ func evalAppend(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if IsList(leftValue) { if isList(leftValue) {
list, _ := leftValue.(*ListType) list, _ := leftValue.([]any)
newList := append(*list, rightValue) v = append(list, rightValue)
v = &newList
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
-37
View File
@@ -1,37 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-iter-value.go
package expr
//-------- iter value term
func newIterValueTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priIterValue,
evalFunc: evalIterValue,
}
}
func evalIterValue(ctx ExprContext, self *term) (v any, err error) {
var childValue any
if childValue, err = self.evalPrefix(ctx); err != nil {
return
}
if it, ok := childValue.(Iterator); ok {
v, err = it.Current()
} else {
err = self.errIncompatibleType(childValue)
}
return
}
// init
func init() {
registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
}
+12 -16
View File
@@ -17,27 +17,23 @@ func newLengthTerm(tk *Token) (inst *term) {
} }
func evalLength(ctx ExprContext, self *term) (v any, err error) { func evalLength(ctx ExprContext, self *term) (v any, err error) {
var childValue any var rightValue any
if childValue, err = self.evalPrefix(ctx); err != nil { if rightValue, err = self.evalPrefix(ctx); err != nil {
return return
} }
if IsList(childValue) { if isList(rightValue) {
list, _ := childValue.([]any) list, _ := rightValue.([]any)
v = int64(len(list)) v = len(list)
} else if IsString(childValue) { } else if isString(rightValue) {
s, _ := childValue.(string) s, _ := rightValue.(string)
v = int64(len(s)) v = len(s)
} else if it, ok := childValue.(Iterator); ok { // } else {
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) { // v = 1
count, _ := extIt.CallOperation(countName, nil) // }
v, _ = toInt(count, "")
} else {
v = int64(it.Index() + 1)
}
} else { } else {
err = self.errIncompatibleType(childValue) err = self.errIncompatibleType(rightValue)
} }
return return
} }
-41
View File
@@ -1,41 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-post-inc.go
package expr
// -------- post increment term
func newPostIncTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: make([]*term, 0, 1),
position: posPostfix,
priority: priIncDec,
evalFunc: evalPostInc,
}
}
func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
var childValue any
if childValue, err = self.evalPrefix(ctx); err != nil {
return
}
if it, ok := childValue.(Iterator); ok {
v, err = it.Next()
} else if IsInteger(childValue) && self.children[0].symbol() == SymIdentifier {
v = childValue
i, _ := childValue.(int64)
ctx.SetVar(self.children[0].source(), i+1)
} else {
err = self.errIncompatibleType(childValue)
}
return
}
// init
func init() {
registerTermConstructor(SymDoublePlus, newPostIncTerm)
}
+7 -11
View File
@@ -30,15 +30,13 @@ func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if IsString(leftValue) && IsInteger(rightValue) { if isString(leftValue) && isInteger(rightValue) {
s, _ := leftValue.(string) s, _ := leftValue.(string)
n, _ := rightValue.(int64) n, _ := rightValue.(int64)
v = strings.Repeat(s, int(n)) v = strings.Repeat(s, int(n))
} else if isNumOrFract(leftValue) && isNumOrFract(rightValue) { } else if isNumber(leftValue) && isNumber(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) { if isFloat(leftValue) || isFloat(rightValue) {
v = numAsFloat(leftValue) * numAsFloat(rightValue) v = numAsFloat(leftValue) * numAsFloat(rightValue)
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = mulAnyFract(leftValue, rightValue)
} else { } else {
leftInt, _ := leftValue.(int64) leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64) rightInt, _ := rightValue.(int64)
@@ -71,16 +69,14 @@ func evalDivide(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isNumOrFract(leftValue) && isNumOrFract(rightValue) { if isNumber(leftValue) && isNumber(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) { if isFloat(leftValue) || isFloat(rightValue) {
d := numAsFloat(rightValue) d := numAsFloat(rightValue)
if d == 0.0 { if d == 0.0 {
err = errors.New("division by zero") err = errors.New("division by zero")
} else { } else {
v = numAsFloat(leftValue) / d v = numAsFloat(leftValue) / d
} }
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = divAnyFract(leftValue, rightValue)
} else { } else {
leftInt, _ := leftValue.(int64) leftInt, _ := leftValue.(int64)
if rightInt, _ := rightValue.(int64); rightInt == 0 { if rightInt, _ := rightValue.(int64); rightInt == 0 {
@@ -114,7 +110,7 @@ func evalDivideAsFloat(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isNumOrFract(leftValue) && isNumOrFract(rightValue) { if isNumber(leftValue) && isNumber(rightValue) {
d := numAsFloat(rightValue) d := numAsFloat(rightValue)
if d == 0.0 { if d == 0.0 {
err = errors.New("division by zero") err = errors.New("division by zero")
@@ -148,7 +144,7 @@ func evalReminder(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if IsInteger(leftValue) && IsInteger(rightValue) { if isInteger(leftValue) && isInteger(rightValue) {
rightInt, _ := rightValue.(int64) rightInt, _ := rightValue.(int64)
if rightInt == 0 { if rightInt == 0 {
err = errors.New("division by zero") err = errors.New("division by zero")
+9 -9
View File
@@ -25,15 +25,15 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if IsNumber(leftValue) && IsNumber(rightValue) { if isNumber(leftValue) && isNumber(rightValue) {
if IsInteger(leftValue) && IsInteger(rightValue) { if isInteger(leftValue) && isInteger(rightValue) {
li, _ := leftValue.(int64) li, _ := leftValue.(int64)
ri, _ := rightValue.(int64) ri, _ := rightValue.(int64)
v = li == ri v = li == ri
} else { } else {
v = numAsFloat(leftValue) == numAsFloat(rightValue) v = numAsFloat(leftValue) == numAsFloat(rightValue)
} }
} else if IsString(leftValue) && IsString(rightValue) { } else if isString(leftValue) && isString(rightValue) {
ls, _ := leftValue.(string) ls, _ := leftValue.(string)
rs, _ := rightValue.(string) rs, _ := rightValue.(string)
v = ls == rs v = ls == rs
@@ -111,15 +111,15 @@ func evalLess(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if IsNumber(leftValue) && IsNumber(rightValue) { if isNumber(leftValue) && isNumber(rightValue) {
if IsInteger(leftValue) && IsInteger(rightValue) { if isInteger(leftValue) && isInteger(rightValue) {
li, _ := leftValue.(int64) li, _ := leftValue.(int64)
ri, _ := rightValue.(int64) ri, _ := rightValue.(int64)
v = li < ri v = li < ri
} else { } else {
v = numAsFloat(leftValue) < numAsFloat(rightValue) v = numAsFloat(leftValue) < numAsFloat(rightValue)
} }
} else if IsString(leftValue) && IsString(rightValue) { } else if isString(leftValue) && isString(rightValue) {
ls, _ := leftValue.(string) ls, _ := leftValue.(string)
rs, _ := rightValue.(string) rs, _ := rightValue.(string)
v = ls < rs v = ls < rs
@@ -148,15 +148,15 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if IsNumber(leftValue) && IsNumber(rightValue) { if isNumber(leftValue) && isNumber(rightValue) {
if IsInteger(leftValue) && IsInteger(rightValue) { if isInteger(leftValue) && isInteger(rightValue) {
li, _ := leftValue.(int64) li, _ := leftValue.(int64)
ri, _ := rightValue.(int64) ri, _ := rightValue.(int64)
v = li <= ri v = li <= ri
} else { } else {
v = numAsFloat(leftValue) <= numAsFloat(rightValue) v = numAsFloat(leftValue) <= numAsFloat(rightValue)
} }
} else if IsString(leftValue) && IsString(rightValue) { } else if isString(leftValue) && isString(rightValue) {
ls, _ := leftValue.(string) ls, _ := leftValue.(string)
rs, _ := rightValue.(string) rs, _ := rightValue.(string)
v = ls <= rs v = ls <= rs
+63 -8
View File
@@ -4,7 +4,65 @@
// operator-selector.go // operator-selector.go
package expr package expr
//-------- selector term // //-------- export all term
// func newSelectorTerm(tk *Token) (inst *term) {
// return &term{
// tk: *tk,
// children: make([]*term, 0, 3),
// position: posMultifix,
// priority: priSelector,
// evalFunc: evalSelector,
// }
// }
// func isSelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (selectedValue any, err error) {
// caseData, _ := caseSel.(*selectorCase)
// if caseData.filterList == nil {
// selectedValue, err = caseData.caseExpr.eval(ctx, false)
// } else {
// filterList := caseData.filterList.children
// if len(filterList) == 0 && exprValue == int64(caseIndex) {
// selectedValue, err = caseData.caseExpr.eval(ctx, false)
// } else {
// var caseValue any
// for _, caseTerm := range filterList {
// if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
// selectedValue, err = caseData.caseExpr.eval(ctx, false)
// break
// }
// }
// }
// }
// return
// }
// func evalSelector(ctx ExprContext, self *term) (v any, err error) {
// var exprValue any
// // var caseList []*term
// if err = self.checkOperands(); err != nil {
// return
// }
// exprTerm := self.children[0]
// if exprValue, err = exprTerm.compute(ctx); err != nil {
// return
// }
// caseList := self.children[1:]
// for i, caseTerm := range caseList {
// caseSel := caseTerm.value()
// if v, err = isSelectorCase(ctx, exprValue, caseSel, i); err != nil || v != nil {
// break
// }
// }
// if err == nil && v == nil {
// err = exprTerm.tk.Errorf("no case catches the value (%v) of the selection expression", exprValue)
// }
// return
// }
//-------- export all term
func newSelectorTerm(tk *Token) (inst *term) { func newSelectorTerm(tk *Token) (inst *term) {
return &term{ return &term{
@@ -16,21 +74,18 @@ func newSelectorTerm(tk *Token) (inst *term) {
} }
} }
func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (match bool, selectedValue any, err error) { func isSelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (selectedValue any, err error) {
caseData, _ := caseSel.(*selectorCase) caseData, _ := caseSel.(*selectorCase)
if caseData.filterList == nil { if caseData.filterList == nil {
selectedValue, err = caseData.caseExpr.eval(ctx, false) selectedValue, err = caseData.caseExpr.eval(ctx, false)
match = true
} else if filterList, ok := caseData.filterList.value().([]*term); ok { } else if filterList, ok := caseData.filterList.value().([]*term); ok {
if len(filterList) == 0 && exprValue == int64(caseIndex) { if len(filterList) == 0 && exprValue == int64(caseIndex) {
selectedValue, err = caseData.caseExpr.eval(ctx, false) selectedValue, err = caseData.caseExpr.eval(ctx, false)
match = true
} else { } else {
var caseValue any var caseValue any
for _, caseTerm := range filterList { for _, caseTerm := range filterList {
if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue { if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
selectedValue, err = caseData.caseExpr.eval(ctx, false) selectedValue, err = caseData.caseExpr.eval(ctx, false)
match = true
break break
} }
} }
@@ -41,7 +96,7 @@ func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (ma
func evalSelector(ctx ExprContext, self *term) (v any, err error) { func evalSelector(ctx ExprContext, self *term) (v any, err error) {
var exprValue any var exprValue any
var match bool // var caseList []*term
if err = self.checkOperands(); err != nil { if err = self.checkOperands(); err != nil {
return return
@@ -55,11 +110,11 @@ func evalSelector(ctx ExprContext, self *term) (v any, err error) {
caseList, _ := caseListTerm.value().([]*term) caseList, _ := caseListTerm.value().([]*term)
for i, caseTerm := range caseList { for i, caseTerm := range caseList {
caseSel := caseTerm.value() caseSel := caseTerm.value()
if match, v, err = trySelectorCase(ctx, exprValue, caseSel, i); err != nil || match { if v, err = isSelectorCase(ctx, exprValue, caseSel, i); err != nil || v != nil {
break break
} }
} }
if err == nil && !match { if err == nil && v == nil {
err = exprTerm.tk.Errorf("no case catches the value (%v) of the selection expression", exprValue) err = exprTerm.tk.Errorf("no case catches the value (%v) of the selection expression", exprValue)
} }
return return
+2 -2
View File
@@ -35,14 +35,14 @@ func evalSign(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if IsFloat(rightValue) { if isFloat(rightValue) {
if self.tk.Sym == SymChangeSign { if self.tk.Sym == SymChangeSign {
f, _ := rightValue.(float64) f, _ := rightValue.(float64)
v = -f v = -f
} else { } else {
v = rightValue v = rightValue
} }
} else if IsInteger(rightValue) { } else if isInteger(rightValue) {
if self.tk.Sym == SymChangeSign { if self.tk.Sym == SymChangeSign {
i, _ := rightValue.(int64) i, _ := rightValue.(int64)
v = -i v = -i
+22 -30
View File
@@ -28,39 +28,33 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if (IsString(leftValue) && isNumberString(rightValue)) || (IsString(rightValue) && isNumberString(leftValue)) { if (isString(leftValue) && isNumberString(rightValue)) || (isString(rightValue) && isNumberString(leftValue)) {
v = fmt.Sprintf("%v%v", leftValue, rightValue) v = fmt.Sprintf("%v%v", leftValue, rightValue)
} else if IsNumber(leftValue) && IsNumber(rightValue) { } else if isNumber(leftValue) && isNumber(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) { if isFloat(leftValue) || isFloat(rightValue) {
v = numAsFloat(leftValue) + numAsFloat(rightValue) v = numAsFloat(leftValue) + numAsFloat(rightValue)
} else { } else {
leftInt, _ := leftValue.(int64) leftInt, _ := leftValue.(int64)
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 leftList, rightList []any
var ok bool var ok bool
if leftList, ok = leftValue.(*ListType); !ok { if leftList, ok = leftValue.([]any); !ok {
leftList = &ListType{leftValue} leftList = []any{leftValue}
} }
if rightList, ok = rightValue.(*ListType); !ok { if rightList, ok = rightValue.([]any); !ok {
rightList = &ListType{rightValue} rightList = []any{rightValue}
} }
sumList := make(ListType, 0, len(*leftList)+len(*rightList)) sumList := make([]any, 0, len(leftList)+len(rightList))
for _, item := range *leftList { for _, item := range leftList {
sumList = append(sumList, item) sumList = append(sumList, item)
} }
for _, item := range *rightList { for _, item := range rightList {
sumList = append(sumList, item) sumList = append(sumList, item)
} }
v = &sumList v = sumList
} else if isFraction(leftValue) || isFraction(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) + numAsFloat(rightValue)
} else {
v, err = sumAnyFract(leftValue, rightValue)
}
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
@@ -86,26 +80,24 @@ func evalMinus(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if isNumOrFract(leftValue) && isNumOrFract(rightValue) { if isNumber(leftValue) && isNumber(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) { if isFloat(leftValue) || isFloat(rightValue) {
v = numAsFloat(leftValue) - numAsFloat(rightValue) v = numAsFloat(leftValue) - numAsFloat(rightValue)
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = subAnyFract(leftValue, rightValue)
} else { } else {
leftInt, _ := leftValue.(int64) leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64) rightInt, _ := rightValue.(int64)
v = leftInt - rightInt v = leftInt - rightInt
} }
} else if IsList(leftValue) && IsList(rightValue) { } else if isList(leftValue) && isList(rightValue) {
leftList, _ := leftValue.(*ListType) leftList, _ := leftValue.([]any)
rightList, _ := rightValue.(*ListType) rightList, _ := rightValue.([]any)
diffList := make(ListType, 0, len(*leftList)-len(*rightList)) diffList := make([]any, 0, len(leftList)-len(rightList))
for _, item := range *leftList { for _, item := range leftList {
if slices.Index(*rightList, item) < 0 { if slices.Index(rightList, item) < 0 {
diffList = append(diffList, item) diffList = append(diffList, item)
} }
} }
v = &diffList v = diffList
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
+14 -189
View File
@@ -58,94 +58,34 @@ func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token)
return return
} }
// func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
// // Example: "add = func(x,y) {x+y}
// var body *ast
// args := make([]*term, 0)
// lastSym := SymUnknown
// itemExpected := false
// tk := scanner.Previous()
// for lastSym != SymClosedRound && lastSym != SymEos {
// var subTree *ast
// if subTree, err = self.parseItem(scanner, true, SymComma, SymClosedRound); err == nil {
// if subTree.root != nil {
// if subTree.root.symbol() == SymIdentifier {
// args = append(args, subTree.root)
// } else {
// err = tk.ErrorExpectedGotString("param-name", subTree.root.String())
// }
// } else if itemExpected {
// prev := scanner.Previous()
// err = prev.ErrorExpectedGot("function-param")
// break
// }
// } else {
// break
// }
// lastSym = scanner.Previous().Sym
// itemExpected = lastSym == SymComma
// }
// if err == nil && lastSym != SymClosedRound {
// err = tk.ErrorExpectedGot(")")
// }
// if err == nil {
// tk = scanner.Next()
// if tk.Sym == SymOpenBrace {
// body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
// } else {
// err = tk.ErrorExpectedGot("{")
// }
// }
// if err == nil {
// // TODO Check arguments
// if scanner.Previous().Sym != SymClosedBrace {
// err = scanner.Previous().ErrorExpectedGot("}")
// } else {
// tk = scanner.makeValueToken(SymExpression, "", body)
// tree = newFuncDefTerm(tk, args)
// }
// }
// return
// }
func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) { func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
// Example: "add = func(x,y) {x+y} // Example: "add = func(x,y) {x+y}
var body *ast var body *ast
args := make([]*term, 0) args := make([]*term, 0)
lastSym := SymUnknown tk := scanner.Next()
itemExpected := false for tk.Sym != SymClosedRound && tk.Sym != SymEos {
tk := scanner.Previous() if tk.Sym == SymIdentifier {
for lastSym != SymClosedRound && lastSym != SymEos { t := newTerm(tk, nil)
tk = scanner.Next() args = append(args, t)
if tk.IsSymbol(SymIdentifier) { } else {
param := newTerm(tk, nil) err = tk.Errorf("invalid param %q, variable identifier expected", tk.source)
args = append(args, param)
tk = scanner.Next()
} else if itemExpected {
prev := scanner.Previous()
err = prev.ErrorExpectedGot("function-param")
break break
} }
lastSym = scanner.Previous().Sym tk = scanner.Next()
itemExpected = lastSym == SymComma
} }
if err == nil && tk.Sym != SymClosedRound {
if err == nil && lastSym != SymClosedRound { err = tk.Errorf("unterminate function params list")
err = tk.ErrorExpectedGot(")")
} }
if err == nil { if err == nil {
tk = scanner.Next() tk = scanner.Next()
if tk.IsSymbol(SymOpenBrace) { if tk.Sym == SymOpenBrace {
body, err = self.parseGeneral(scanner, true, true, SymClosedBrace) body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
} else {
err = tk.ErrorExpectedGot("{")
} }
} }
if err == nil { if err == nil {
// TODO Check arguments // TODO Check arguments
if scanner.Previous().Sym != SymClosedBrace { if scanner.Previous().Sym != SymClosedBrace {
err = scanner.Previous().ErrorExpectedGot("}") err = scanner.Previous().Errorf("not properly terminated function body")
} else { } else {
tk = scanner.makeValueToken(SymExpression, "", body) tk = scanner.makeValueToken(SymExpression, "", body)
tree = newFuncDefTerm(tk, args) tree = newFuncDefTerm(tk, args)
@@ -157,27 +97,21 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) { func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
args := make([]*term, 0) args := make([]*term, 0)
lastSym := SymUnknown lastSym := SymUnknown
itemExpected := false
for lastSym != SymClosedSquare && lastSym != SymEos { for lastSym != SymClosedSquare && lastSym != SymEos {
var subTree *ast var subTree *ast
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil { if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
if subTree.root != nil { if subTree.root != nil {
args = append(args, subTree.root) args = append(args, subTree.root)
} else if itemExpected {
prev := scanner.Previous()
err = prev.ErrorExpectedGot("list-item")
break
} }
} else { } else {
break break
} }
lastSym = scanner.Previous().Sym lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
} }
if err == nil { if err == nil {
// TODO Check arguments // TODO Check arguments
if lastSym != SymClosedSquare { if lastSym != SymClosedSquare {
err = scanner.Previous().ErrorExpectedGot("]") err = scanner.Previous().Errorf("unterminate items list")
} else { } else {
subtree = newListTerm(args) subtree = newListTerm(args)
} }
@@ -185,103 +119,6 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
return return
} }
func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
tk := scanner.Previous()
args := make([]*term, 0)
lastSym := SymUnknown
itemExpected := false
for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
if subTree.root != nil {
args = append(args, subTree.root)
} else if itemExpected {
prev := scanner.Previous()
err = prev.ErrorExpectedGot("iterator-param")
break
}
} else {
break
}
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
}
if err == nil {
// TODO Check arguments
if lastSym != SymClosedRound {
err = scanner.Previous().ErrorExpectedGot(")")
} else {
subtree = newIteratorTerm(tk, args)
}
}
return
}
func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, err error) {
tk := scanner.Next()
if tk.Sym == SymError {
err = tk.Error()
return
}
if tk.Sym == SymClosedBrace || tk.Sym == SymEos {
return
}
if tk.Sym == SymInteger || tk.Sym == SymString {
tkSep := scanner.Next()
if tkSep.Sym != SymColon {
err = tkSep.ErrorExpectedGot(":")
} else {
key = tk.Value
}
} else {
// err = tk.Errorf("expected dictionary key or closed brace, got %q", tk)
err = tk.ErrorExpectedGot("dictionary-key or }")
}
return
}
func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
args := make(map[any]*term, 0)
lastSym := SymUnknown
itemExpected := false
for lastSym != SymClosedBrace && lastSym != SymEos {
var subTree *ast
var key any
if key, err = self.parseDictKey(scanner, allowVarRef); err != nil {
break
} else if key == nil {
tk := scanner.Previous()
lastSym = tk.Sym
if itemExpected {
err = tk.ErrorExpectedGot("dictionary-key")
}
break
}
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedBrace); err == nil {
if subTree.root != nil {
args[key] = subTree.root
} else if key != nil {
prev := scanner.Previous()
err = prev.ErrorExpectedGot("dictionary-value")
break
}
} else {
break
}
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
}
if err == nil {
// TODO Check arguments
if lastSym != SymClosedBrace {
err = scanner.Previous().ErrorExpectedGot("}")
} else {
subtree = newDictTerm(args)
}
}
return
}
func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) { func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) {
var filterList *term var filterList *term
var caseExpr *ast var caseExpr *ast
@@ -308,7 +145,7 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
return return
} }
} else { } else {
err = tk.ErrorExpectedGot("{") err = tk.Errorf("selector-case expected, got %q", tk.source)
} }
if err == nil { if err == nil {
@@ -404,12 +241,6 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
err = tree.addTerm(listTerm) err = tree.addTerm(listTerm)
currentTerm = listTerm currentTerm = listTerm
} }
case SymOpenBrace:
var mapTerm *term
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
err = tree.addTerm(mapTerm)
currentTerm = mapTerm
}
case SymEqual: case SymEqual:
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil { if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
currentTerm, err = tree.addToken2(tk) currentTerm, err = tree.addToken2(tk)
@@ -420,12 +251,6 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
err = tree.addTerm(funcDefTerm) err = tree.addTerm(funcDefTerm)
currentTerm = funcDefTerm currentTerm = funcDefTerm
} }
case SymDollarRound:
var iterDefTerm *term
if iterDefTerm, err = self.parseIterDef(scanner, allowVarRef); err == nil {
err = tree.addTerm(iterDefTerm)
currentTerm = iterDefTerm
}
case SymIdentifier: case SymIdentifier:
if tk.source[0] == '@' && !allowVarRef { if tk.source[0] == '@' && !allowVarRef {
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source) err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)
+226 -128
View File
@@ -6,18 +6,18 @@ package expr
import ( import (
"errors" "errors"
"reflect" "fmt"
"strings" "strings"
"testing" "testing"
) )
type inputType struct { func TestParser(t *testing.T) {
source string type inputType struct {
wantResult any source string
wantErr error wantResult any
} wantErr error
}
func TestGeneralParser(t *testing.T) {
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`1+/*5*/2`, int64(3), nil}, /* 1 */ {`1+/*5*/2`, int64(3), nil},
/* 2 */ {`3 == 4`, false, nil}, /* 2 */ {`3 == 4`, false, nil},
@@ -51,133 +51,134 @@ func TestGeneralParser(t *testing.T) {
/* 30 */ {"-(-2+1)", int64(1), nil}, /* 30 */ {"-(-2+1)", int64(1), nil},
/* 31 */ {"(1+1)*5", int64(10), nil}, /* 31 */ {"(1+1)*5", int64(10), nil},
/* 32 */ {"200 / (1+1) - 1", int64(99), nil}, /* 32 */ {"200 / (1+1) - 1", int64(99), nil},
/* 33 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil}, /* 33 */ {`add(1,2,3)`, int64(6), nil},
/* 34 */ {`(((1)))`, int64(1), nil}, /* 34 */ {`mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
/* 35 */ {`var2="abc"; "uno_" + var2`, `uno_abc`, nil}, /* 35 */ {`add(1+4,3+2,5*(3-2))`, int64(15), nil},
/* 36 */ {`0 || 0.0 && "hello"`, false, nil}, /* 36 */ {`add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
/* 37 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)}, /* 37 */ {`add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
/* 38 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)}, /* 38 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil},
/* 39 */ {`false // very simple expression`, false, nil}, /* 39 */ {`(((1)))`, int64(1), nil},
/* 40 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two non-nil operands, got 1`)}, /* 40 */ {`"uno_" + var2`, `uno_abc`, nil},
/* 41 */ {"", nil, nil}, /* 41 */ {`0 || 0.0 && "hello"`, false, nil},
/* 42 */ {"4!", int64(24), nil}, /* 42 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)},
/* 43 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)}, /* 43 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
/* 44 */ {"-4!", int64(-24), nil}, /* 44 */ {`false // very simple expression`, false, nil},
/* 45 */ {"1.5 < 7", true, nil}, /* 45 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two not nil operands, got 1`)},
/* 46 */ {"1.5 > 7", false, nil}, /* 46 */ {"", nil, errors.New(`empty expression`)},
/* 47 */ {"1.5 <= 7", true, nil}, /* 47 */ {"4!", int64(24), nil},
/* 48 */ {"1.5 >= 7", false, nil}, /* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
/* 49 */ {"1.5 != 7", true, nil}, /* 49 */ {"-4!", int64(-24), nil},
/* 50 */ {"1.5 == 7", false, nil}, /* 50 */ {"1.5 < 7", true, nil},
/* 51 */ {`"1.5" < "7"`, true, nil}, /* 51 */ {"1.5 > 7", false, nil},
/* 52 */ {`"1.5" > "7"`, false, nil}, /* 52 */ {"1.5 <= 7", true, nil},
/* 53 */ {`"1.5" == "7"`, false, nil}, /* 53 */ {"1.5 >= 7", false, nil},
/* 54 */ {`"1.5" != "7"`, true, nil}, /* 54 */ {"1.5 != 7", true, nil},
/* 55 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two non-nil operands, got 1`)}, /* 55 */ {"1.5 == 7", false, nil},
/* 56 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two non-nil operands, got 1`)}, /* 56 */ {`"1.5" < "7"`, true, nil},
/* 57 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two non-nil operands, got 1`)}, /* 57 */ {`"1.5" > "7"`, false, nil},
/* 58 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two non-nil operands, got 1`)}, /* 58 */ {`"1.5" == "7"`, false, nil},
/* 59 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two non-nil operands, got 1`)}, /* 59 */ {`"1.5" != "7"`, true, nil},
/* 60 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two non-nil operands, got 1`)}, /* 60 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two not nil operands, got 1`)},
/* 61 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two non-nil operands, got 1`)}, /* 61 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two not nil operands, got 1`)},
/* 62 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two non-nil operands, got 1`)}, /* 62 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two not nil operands, got 1`)},
/* 63 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two non-nil operands, got 1`)}, /* 63 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two not nil operands, got 1`)},
/* 64 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two non-nil operands, got 1`)}, /* 64 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two not nil operands, got 1`)},
/* 65 */ {"+1.5", float64(1.5), nil}, /* 65 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two not nil operands, got 1`)},
/* 66 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)}, /* 66 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two not nil operands, got 1`)},
/* 67 */ {"4 / 0", nil, errors.New(`division by zero`)}, /* 67 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two not nil operands, got 1`)},
/* 68 */ {"4.0 / 0", nil, errors.New(`division by zero`)}, /* 68 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two not nil operands, got 1`)},
/* 69 */ {"4.0 / \n2", float64(2.0), nil}, /* 69 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two not nil operands, got 1`)},
/* 70 */ {`123`, int64(123), nil}, /* 70 */ {"+1.5", float64(1.5), nil},
/* 71 */ {`1.`, float64(1.0), nil}, /* 71 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
/* 72 */ {`1.E-2`, float64(0.01), nil}, /* 72 */ {"4 / 0", nil, errors.New(`division by zero`)},
/* 73 */ {`1E2`, float64(100), nil}, /* 73 */ {"4.0 / 0", nil, errors.New(`division by zero`)},
/* 74 */ {`1 / 2`, int64(0), nil}, /* 74 */ {"4.0 / \n2", float64(2.0), nil},
/* 75 */ {`1.0 / 2`, float64(0.5), nil}, /* 75 */ {`123`, int64(123), nil},
/* 76 */ {`1 ./ 2`, float64(0.5), nil}, /* 76 */ {`1.`, float64(1.0), nil},
/* 77 */ {`5 % 2`, int64(1), nil}, /* 77 */ {`1.E-2`, float64(0.01), nil},
/* 78 */ {`5 % (-2)`, int64(1), nil}, /* 78 */ {`1E2`, float64(100), nil},
/* 79 */ {`-5 % 2`, int64(-1), nil}, /* 79 */ {`1 / 2`, int64(0), 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 */ {`1.0 / 2`, float64(0.5), nil},
/* 81 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil}, /* 81 */ {`1 ./ 2`, float64(0.5), nil},
/* 82 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil}, /* 82 */ {`5 % 2`, int64(1), nil},
/* 83 */ {`"a" < "b" AND ~ 2 == 1`, true, nil}, /* 83 */ {`5 % (-2)`, int64(1), nil},
/* 84 */ {`~ 2 > 1`, false, nil}, /* 84 */ {`-5 % 2`, int64(-1), nil},
/* 85 */ {`~ true && true`, false, nil}, /* 85 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [int64] and right operand '2' [float64] are not compatible with operator "%"`)},
/* 86 */ {`~ false || true`, true, nil}, /* 86 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
/* 87 */ {`false but true`, true, nil}, /* 87 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
/* 88 */ {`2+3 but 5*2`, int64(10), nil}, /* 88 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
/* 89 */ {`x=2`, int64(2), nil}, /* 89 */ {`~ 2 > 1`, false, nil},
/* 90 */ {`x=2 but x*10`, int64(20), nil}, /* 90 */ {`~ true && true`, false, nil},
/* 91 */ {`false and true`, false, nil}, /* 91 */ {`~ false || true`, true, nil},
/* 92 */ {`false and (x==2)`, false, nil}, /* 92 */ {`false but true`, true, nil},
/* 93 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable or function "x"`)}, /* 93 */ {`2+3 but 5*2`, int64(10), nil},
/* 94 */ {`false or true`, true, nil}, /* 94 */ {`add(1,2) but var2`, "abc", nil},
/* 95 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)}, /* 95 */ {`x=2`, int64(2), nil},
/* 96 */ {`a=5; a`, int64(5), nil}, /* 96 */ {`x=2 but x*10`, int64(20), nil},
/* 97 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)}, /* 97 */ {`false and true`, false, nil},
/* 98 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable`)}, /* 98 */ {`false and (x==2)`, false, nil},
/* 99 */ {`2+(a=5)`, int64(7), nil}, /* 99 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable "x"`)},
/* 100 */ {`x ?? "default"`, "default", nil}, /* 100 */ {`false or true`, true, nil},
/* 101 */ {`x="hello"; x ?? "default"`, "hello", nil}, /* 101 */ {`false or (x==2)`, nil, errors.New(`undefined variable "x"`)},
/* 102 */ {`y=x ?? func(){4}; y()`, int64(4), nil}, /* 102 */ {`a=5; a`, int64(5), nil},
/* 103 */ {`x ?= "default"; x`, "default", nil}, /* 103 */ {`a=5; b=2; add(a, b*3)`, int64(11), nil},
/* 104 */ {`x="hello"; x ?= "default"; x`, "hello", nil}, /* 104 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
/* 105 */ {`1 ? {"a"} : {"b"}`, "b", nil}, /* 105 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable`)},
/* 106 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil}, /* 106 */ {`2+(a=5)`, int64(7), nil},
/* 107 */ {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil}, /* 107 */ {`two=func(){2}; two()`, int64(2), nil},
/* 108 */ {`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`, nil, errors.New(`[1:34] case list in default clause`)}, /* 108 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
/* 109 */ {`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`, "b", nil}, /* 109 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
/* 110 */ {`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`, "b", nil}, /* 110 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
/* 111 */ {`10 ? {"a"} : {"b"}`, nil, errors.New(`[1:3] no case catches the value (10) of the selection expression`)}, /* 111 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
/* 112 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, errors.New(`[1:22] selector-case outside of a selector context`)}, /* 112 */ {`import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 113 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil}, /* 113 */ {`import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 114 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil}, /* 114 */ {`x ?? "default"`, "default", nil},
/* 115 */ {`nil`, nil, nil}, /* 115 */ {`x="hello"; x ?? "default"`, "hello", nil},
/* 116 */ {`null`, nil, errors.New(`undefined variable or function "null"`)}, /* 116 */ {`x ?? func(){}"`, nil, errors.New(`[1:15] the right operand of a coalescing operation cannot be a function definition`)},
/* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)}, /* 117 */ {`x ?= "default"; x`, "default", nil},
/* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)}, /* 118 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
/* 119 */ {`{}`, map[any]any{}, nil}, /* 119 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
/* 120 */ {`1|2`, newFraction(1, 2), nil}, /* 120 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
/* 121 */ {`1|2 + 1`, newFraction(3, 2), nil}, /* 121 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
/* 122 */ {`1|2 - 1`, newFraction(-1, 2), nil}, /* 122 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable "x"`)},
/* 123 */ {`1|2 * 1`, newFraction(1, 2), nil}, /* 123 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 124 */ {`1|2 * 2|3`, newFraction(2, 6), nil}, /* 124 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 125 */ {`1|2 / 2|3`, newFraction(3, 4), nil}, /* 125 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
/* 126 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil}, /* 126 */ {`include("./test-funcs.expr"); six()`, int64(6), nil},
/* 127 */ {`builtin "math.arith"; mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)}, /* 127 */ {`import("./sample-export-all.expr"); six()`, int64(6), nil},
/* 128 */ {`builtin "math.arith"; add(1+4,3+2,5*(3-2))`, int64(15), nil}, /* 128 */ {`1 ? {"a"} : {"b"}`, "b", nil},
/* 129 */ {`builtin "math.arith"; add(add(1+4),3+2,5*(3-2))`, int64(15), nil}, /* 129 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil},
/* 130 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil}, /* 130 */ {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
/* 131 */ {`builtin "math.arith"; a=5; b=2; add(a, b*3)`, int64(11), nil}, /* 131 */ {`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`, nil, errors.New(`[1:34] case list in default clause`)},
/* 132 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil}, /* 132 */ {`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`, "b", nil},
/* 133 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil}, /* 133 */ {`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`, "b", nil},
/* 134 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil}, /* 134 */ {`10 ? {"a"} : {"b"}`, nil, errors.New(`[1:3] no case catches the value (10) of the selection expression`)},
/* 135 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil}, /* 135 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, errors.New(`[1:22] selector-case outside of a selector context`)},
/* 136 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil}, /* 136 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
/* 137 */ {`builtin "os.file"`, int64(1), nil}, /* 137 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
/* 138 */ {`v=10; v++; v`, int64(11), nil},
/* 139 */ {`1+1|2+0.5`, float64(2), nil},
} }
check_env_expr_path := 113
// t.Setenv("EXPR_PATH", ".")
parserTest(t, "General", inputs)
}
func parserTest(t *testing.T, section string, inputs []inputType) {
succeeded := 0 succeeded := 0
failed := 0 failed := 0
// inputs1 := []inputType{
// {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
// }
for i, input := range inputs { for i, input := range inputs {
var expr Expr var expr Expr
var gotResult any var gotResult any
var gotErr error var gotErr error
ctx := NewSimpleFuncStore() ctx := NewSimpleFuncStore()
ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx)
ImportImportFuncs(ctx)
parser := NewParser(ctx) parser := NewParser(ctx)
logTest(t, i+1, "Iterator", input.source, input.wantResult, input.wantErr) logTest(t, i+1, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source) r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations()) scanner := NewScanner(r, DefaultTranslations())
@@ -187,9 +188,7 @@ func parserTest(t *testing.T, section string, inputs []inputType) {
gotResult, gotErr = expr.Eval(ctx) gotResult, gotErr = expr.Eval(ctx)
} }
eq := reflect.DeepEqual(gotResult, input.wantResult) if 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) t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false good = false
} }
@@ -205,15 +204,114 @@ func parserTest(t *testing.T, section string, inputs []inputType) {
succeeded++ succeeded++
} else { } else {
failed++ failed++
if i+1 == check_env_expr_path {
t.Logf(`NOTICE: Test nr %d requires EXPR_PATH="."`, check_env_expr_path)
}
} }
} }
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed) t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
} }
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) { func TestListParser(t *testing.T) {
type inputType struct {
source string
wantResult any
wantErr error
}
// inputs1 := []inputType{
// {`add(1,2,3)`, int64(6), nil},
// }
inputs := []inputType{
/* 1 */ {`[]`, []any{}, nil},
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
/* 7 */ {`add([1,4,3,2])`, int64(10), nil},
/* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
/* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 has wrong type string, number expected`)},
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), 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},
}
succeeded := 0
failed := 0
for i, input := range inputs {
var expr *ast
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, 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)
}
if (gotResult == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotList, okGot := gotResult.([]any); okGot {
if wantList, okWant := input.wantResult.([]any); okWant {
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
} else {
equal := len(gotList) == len(wantList)
if equal {
for i, gotItem := range gotList {
wantItem := wantList[i]
equal = gotItem == wantItem
if !equal {
break
}
}
}
if !equal {
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 {
succeeded++
} else {
failed++
}
}
t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
}
func logTest(t *testing.T, n int, source string, wantResult any, wantErr error) {
if wantErr == nil { if wantErr == nil {
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult) t.Log(fmt.Sprintf("[+]Test nr %3d -- %q --> %v", n, source, wantResult))
} else { } else {
t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr) t.Log(fmt.Sprintf("[-]Test nr %3d -- %q --> %v", n, source, wantErr))
} }
} }
+3 -26
View File
@@ -86,14 +86,13 @@ func (self *scanner) Next() (tk *Token) {
} }
func (self *scanner) fetchNextToken() (tk *Token) { func (self *scanner) fetchNextToken() (tk *Token) {
var ch byte
if err := self.skipBlanks(); err != nil { if err := self.skipBlanks(); err != nil {
return self.makeErrorToken(err) return self.makeErrorToken(err)
} }
escape := false escape := false
for { for {
ch, _ = self.readChar() ch, _ := self.readChar()
switch ch { switch ch {
case '+': case '+':
if next, _ := self.peek(); next == '+' { if next, _ := self.peek(); next == '+' {
@@ -144,8 +143,6 @@ func (self *scanner) fetchNextToken() (tk *Token) {
} }
case ',': case ',':
tk = self.makeToken(SymComma, ch) tk = self.makeToken(SymComma, ch)
case '^':
tk = self.makeToken(SymCaret, ch)
case ':': case ':':
if next, _ := self.peek(); next == ':' { if next, _ := self.peek(); next == ':' {
tk = self.moveOn(SymDoubleColon, ch, next) tk = self.moveOn(SymDoubleColon, ch, next)
@@ -160,12 +157,6 @@ func (self *scanner) fetchNextToken() (tk *Token) {
//} else if next == '/' { //} else if next == '/' {
if next, _ := self.peek(); next == '/' { if next, _ := self.peek(); next == '/' {
tk = self.moveOn(SymDotSlash, ch, next) tk = self.moveOn(SymDotSlash, ch, next)
} else if next == '.' {
if next1, _ := self.peek(); next1 == '.' {
tk = self.moveOn(SymTripleDot, ch, next, next1)
} else {
tk = self.moveOn(SymDoubleDot, ch, next)
}
} else { } else {
tk = self.makeToken(SymDot, ch) tk = self.makeToken(SymDot, ch)
} }
@@ -245,20 +236,9 @@ func (self *scanner) fetchNextToken() (tk *Token) {
tk = self.makeToken(SymGreater, ch) tk = self.makeToken(SymGreater, ch)
} }
case '$': case '$':
if next, _ := self.peek(); next == '(' { tk = self.makeToken(SymDollar, ch)
tk = self.moveOn(SymDollarRound, ch, next)
tk.source += ")"
} else if next == '$' {
tk = self.moveOn(SymDoubleDollar, ch, next)
} else {
tk = self.makeToken(SymDollar, ch)
}
case '(': case '(':
if next, _ := self.peek(); next == ')' { tk = self.makeToken(SymOpenRound, ch)
tk = self.moveOn(SymOpenClosedRound, ch, next)
} else {
tk = self.makeToken(SymOpenRound, ch)
}
case ')': case ')':
tk = self.makeToken(SymClosedRound, ch) tk = self.makeToken(SymClosedRound, ch)
case '[': case '[':
@@ -291,9 +271,6 @@ func (self *scanner) fetchNextToken() (tk *Token) {
break break
} }
} }
if tk == nil {
tk = NewErrorToken(self.row, self.column, fmt.Errorf("unknown symbol '%c'", ch))
}
return return
} }
+2 -2
View File
@@ -36,7 +36,7 @@ func TestScanner(t *testing.T) {
/* 14 */ {`:`, SymColon, nil, nil}, /* 14 */ {`:`, SymColon, nil, nil},
/* 15 */ {`;`, SymSemiColon, nil, nil}, /* 15 */ {`;`, SymSemiColon, nil, nil},
/* 16 */ {`.`, SymDot, nil, nil}, /* 16 */ {`.`, SymDot, nil, nil},
/* 17 */ {`0.5`, SymFloat, float64(0.5), nil}, /* 17 */ {`.5`, SymFloat, float64(0.5), nil},
/* 18 */ {`\\`, SymBackSlash, nil, nil}, /* 18 */ {`\\`, SymBackSlash, nil, nil},
/* 19 */ {"`", SymBackTick, nil, nil}, /* 19 */ {"`", SymBackTick, nil, nil},
/* 20 */ {"?", SymQuestion, nil, nil}, /* 20 */ {"?", SymQuestion, nil, nil},
@@ -74,7 +74,7 @@ func TestScanner(t *testing.T) {
scanner := NewScanner(r, nil) scanner := NewScanner(r, nil)
if tk := scanner.Next(); tk == nil { if tk := scanner.Next(); tk == nil {
t.Errorf("%d: %q -> got = (nil), want %v (value %v [%T])", i+1, input.source, input.wantSym, input.wantValue, input.wantValue) t.Errorf("%d: %q -> got = (nil), want %v (value %v [%T)", i+1, input.source, input.wantSym, input.wantValue, input.wantValue)
} else if tk.Sym != input.wantSym || tk.Value != input.wantValue { } else if tk.Sym != input.wantSym || tk.Value != input.wantValue {
if tk.Sym == SymError && input.wantSym == tk.Sym { if tk.Sym == SymError && input.wantSym == tk.Sym {
if tkErr, tkOk := tk.Value.(error); tkOk { if tkErr, tkOk := tk.Value.(error); tkOk {
+6 -66
View File
@@ -4,10 +4,7 @@
// simple-func-store.go // simple-func-store.go
package expr package expr
import ( import "fmt"
"fmt"
"strings"
)
type SimpleFuncStore struct { type SimpleFuncStore struct {
SimpleVarStore SimpleVarStore
@@ -21,29 +18,6 @@ type funcInfo struct {
functor Functor 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 { func (info *funcInfo) Name() string {
return info.name return info.name
} }
@@ -61,55 +35,21 @@ func (info *funcInfo) Functor() Functor {
} }
func NewSimpleFuncStore() *SimpleFuncStore { func NewSimpleFuncStore() *SimpleFuncStore {
ctx := &SimpleFuncStore{ return &SimpleFuncStore{
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)}, SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
funcStore: make(map[string]*funcInfo), funcStore: make(map[string]*funcInfo),
} }
ImportBuiltinsFuncs(ctx)
return ctx
} }
func (ctx *SimpleFuncStore) Clone() ExprContext { func (ctx *SimpleFuncStore) Clone() ExprContext {
svs := ctx.SimpleVarStore
return &SimpleFuncStore{ return &SimpleFuncStore{
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)}, SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()}, funcStore: CloneMap(ctx.funcStore),
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
} }
} }
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) { func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc) {
sb.WriteString("funcs: {\n") info, _ = ctx.funcStore[name]
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 return
} }
+3 -54
View File
@@ -4,10 +4,7 @@
// simple-var-store.go // simple-var-store.go
package expr package expr
import ( import "fmt"
"fmt"
"strings"
)
type SimpleVarStore struct { type SimpleVarStore struct {
varStore map[string]any varStore map[string]any
@@ -19,14 +16,9 @@ func NewSimpleVarStore() *SimpleVarStore {
} }
} }
func (ctx *SimpleVarStore) cloneVars() (vars map[string]any) {
return CloneFilteredMap(ctx.varStore, func(name string) bool { return name[0] != '@' })
}
func (ctx *SimpleVarStore) Clone() (clone ExprContext) { func (ctx *SimpleVarStore) Clone() (clone ExprContext) {
// fmt.Println("*** Cloning context ***")
clone = &SimpleVarStore{ clone = &SimpleVarStore{
varStore: ctx.cloneVars(), varStore: CloneMap(ctx.varStore),
} }
return clone return clone
} }
@@ -37,12 +29,10 @@ func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
} }
func (ctx *SimpleVarStore) setVar(varName string, value any) { func (ctx *SimpleVarStore) setVar(varName string, value any) {
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
ctx.varStore[varName] = value ctx.varStore[varName] = value
} }
func (ctx *SimpleVarStore) SetVar(varName string, value any) { func (ctx *SimpleVarStore) SetVar(varName string, value any) {
// fmt.Printf("[%p] SetVar(%v, %v)\n", ctx, varName, value)
if allowedValue, ok := fromGenericAny(value); ok { if allowedValue, ok := fromGenericAny(value); ok {
ctx.varStore[varName] = allowedValue ctx.varStore[varName] = allowedValue
} else { } else {
@@ -64,7 +54,7 @@ func (ctx *SimpleVarStore) EnumVars(acceptor func(name string) (accept bool)) (v
return return
} }
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc, exists bool) { func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc) {
return return
} }
@@ -78,44 +68,3 @@ func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, m
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) { func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
return return
} }
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 (ctx *SimpleVarStore) ToString(opt FmtOpt) string {
var sb strings.Builder
varsCtxToBuilder(&sb, ctx, 0)
return sb.String()
}
+1 -13
View File
@@ -33,7 +33,7 @@ const (
SymBackTick // 22: '`' SymBackTick // 22: '`'
SymExclamation // 23: '!' SymExclamation // 23: '!'
SymQuestion // 24: '?' SymQuestion // 24: '?'
SymAmpersand // 25: '&' SymAmpersand // 25: '&&'
SymDoubleAmpersand // 26: '&&' SymDoubleAmpersand // 26: '&&'
SymPercent // 27: '%' SymPercent // 27: '%'
SymAt // 28: '@' SymAt // 28: '@'
@@ -61,12 +61,6 @@ const (
SymDoubleColon // 50: '::' SymDoubleColon // 50: '::'
SymInsert // 51: '>>' SymInsert // 51: '>>'
SymAppend // 52: '<<' SymAppend // 52: '<<'
SymCaret // 53: '^'
SymDollarRound // 54: '$('
SymOpenClosedRound // 55: '()'
SymDoubleDollar // 56: '$$'
SymDoubleDot // 57: '..'
SymTripleDot // 58: '...'
SymChangeSign SymChangeSign
SymUnchangeSign SymUnchangeSign
SymIdentifier SymIdentifier
@@ -74,7 +68,6 @@ const (
SymInteger SymInteger
SymFloat SymFloat
SymString SymString
SymIterator
SymOr SymOr
SymAnd SymAnd
SymNot SymNot
@@ -82,7 +75,6 @@ const (
SymFuncCall SymFuncCall
SymFuncDef SymFuncDef
SymList SymList
SymDict
SymExpression SymExpression
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>] SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}" SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
@@ -98,8 +90,6 @@ const (
SymKwBut SymKwBut
SymKwFunc SymKwFunc
SymKwBuiltin SymKwBuiltin
SymKwInclude
SymKwNil
) )
var keywords map[string]Symbol var keywords map[string]Symbol
@@ -111,9 +101,7 @@ func init() {
"BUILTIN": SymKwBuiltin, "BUILTIN": SymKwBuiltin,
"BUT": SymKwBut, "BUT": SymKwBut,
"FUNC": SymKwFunc, "FUNC": SymKwFunc,
"INCLUDE": SymKwInclude,
"NOT": SymKwNot, "NOT": SymKwNot,
"OR": SymKwOr, "OR": SymKwOr,
"NIL": SymKwNil,
} }
} }
+2 -18
View File
@@ -20,13 +20,10 @@ const (
priRelational priRelational
priSum priSum
priProduct priProduct
priFraction
priSelector priSelector
priSign priSign
priFact priFact
priIterValue
priCoalesce priCoalesce
priIncDec
priDot priDot
priValue priValue
) )
@@ -130,10 +127,6 @@ func (self *term) setParent(parent *term) {
} }
} }
func (self *term) symbol() Symbol {
return self.tk.Sym
}
func (self *term) source() string { func (self *term) source() string {
return self.tk.source return self.tk.source
} }
@@ -151,15 +144,6 @@ func (self *term) compute(ctx ExprContext) (v any, err error) {
return return
} }
func (self *term) toInt(computedValue any, valueDescription string) (i int, err error) {
if index64, ok := computedValue.(int64); ok {
i = int(index64)
} else {
err = self.Errorf("%s, got %T (%v)", valueDescription, computedValue, computedValue)
}
return
}
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error { func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
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 '%v' [%T] and right operand '%v' [%T] are not compatible with operator %q",
@@ -218,9 +202,9 @@ func (self *term) evalInfix(ctx ExprContext) (leftValue, rightValue any, err err
return return
} }
func (self *term) evalPrefix(ctx ExprContext) (childValue any, err error) { func (self *term) evalPrefix(ctx ExprContext) (rightValue any, err error) {
if err = self.checkOperands(); err == nil { if err = self.checkOperands(); err == nil {
childValue, err = self.children[0].compute(ctx) rightValue, err = self.children[0].compute(ctx)
} }
return return
} }
-2
View File
@@ -1,2 +0,0 @@
uno
due
+1 -15
View File
@@ -46,7 +46,7 @@ func NewValueToken(row, col int, sym Symbol, source string, value any) *Token {
func NewErrorToken(row, col int, err error) *Token { func NewErrorToken(row, col int, err error) *Token {
if err == io.EOF { if err == io.EOF {
return NewToken(row, col, SymEos, "<EOF>") return NewToken(row, col, SymEos, "")
} }
return NewValueToken(row, col, SymError, fmt.Sprintf("[%d:%d]", row, col), err) return NewValueToken(row, col, SymError, fmt.Sprintf("[%d:%d]", row, col), err)
} }
@@ -63,10 +63,6 @@ func (tk *Token) IsTerm(termSymbols []Symbol) bool {
return tk.IsEos() || tk.IsError() || (termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0) return tk.IsEos() || tk.IsError() || (termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0)
} }
func (tk *Token) IsSymbol(sym Symbol) bool {
return tk.Sym == sym
}
func (self *Token) Errorf(template string, args ...any) (err error) { func (self *Token) Errorf(template string, args ...any) (err error) {
err = fmt.Errorf(fmt.Sprintf("[%d:%d] ", self.row, self.col)+template, args...) err = fmt.Errorf(fmt.Sprintf("[%d:%d] ", self.row, self.col)+template, args...)
return return
@@ -85,13 +81,3 @@ func (self *Token) Errors(msg string) (err error) {
err = fmt.Errorf("[%d:%d] %v", self.row, self.col, msg) err = fmt.Errorf("[%d:%d] %v", self.row, self.col, msg)
return return
} }
func (self *Token) ErrorExpectedGot(symbol string) (err error) {
err = fmt.Errorf("[%d:%d] expected %q, got %q", self.row, self.col, symbol, self)
return
}
func (self *Token) ErrorExpectedGotString(symbol, got string) (err error) {
err = fmt.Errorf("[%d:%d] expected %q, got %q", self.row, self.col, symbol, got)
return
}
+2 -3
View File
@@ -5,7 +5,6 @@
package expr package expr
import ( import (
"fmt"
"testing" "testing"
) )
@@ -18,8 +17,8 @@ func TestDevString(t *testing.T) {
} }
inputs := []inputType{ inputs := []inputType{
/* 1 */ {"100", SymInteger, 100, fmt.Sprintf(`[%d]"100"{100}`, SymInteger)}, /* 1 */ {"100", SymInteger, 100, `[55]"100"{100}`},
/* 2 */ {"+", SymPlus, nil, fmt.Sprintf(`[%d]"+"{}`, SymPlus)}, /* 2 */ {"+", SymPlus, nil, `[6]"+"{}`},
} }
for i, input := range inputs { for i, input := range inputs {
-71
View File
@@ -1,71 +0,0 @@
#!/usr/bin/env bash
if [ $# -lt 2 ]; then
echo >&2 "Usage: $(basename "${0}") <module-name> <func-name>..."
exit 1
fi
MODULE_NAME=${1,,}
GO_FILE="func-${MODULE_NAME//[-.]/_}.go"
shift
FUNC_LIST=
i=0
for name; do
if [ ${i} -gt 0 ]; then
if [ $((i+1)) -eq $# ]; then
FUNC_LIST+=" and "
else
FUNC_LIST+=", "
fi
fi
FUNC_LIST+="${name}()"
((i++))
done
cat > "${GO_FILE}" <<EOF
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-${MODULE_NAME}.go
package expr
import (
)
// --- Start of function definitions
$(
for name; do
cat <<IEOF
func ${name}Func(ctx ExprContext, name string, args []any) (result any, err error) {
return
}
IEOF
done
)
// --- End of function definitions
// Import above functions in the context
func Import${MODULE_NAME^}Funcs(ctx ExprContext) {
$(
for name; do
cat <<IEOF
ctx.RegisterFunc("${name}", &simpleFunctor{f: ${name}Func}, 1, -1)
IEOF
done
)
}
// Register the import function in the import-register.
// That will allow to import all function of this module by the "builtin" operator."
func init() {
registerImport("${MODULE_NAME}", Import${name}Funcs, "The \"${MODULE_NAME}\" module implements the ${FUNC_LIST} function(s)")
}
EOF
+11 -66
View File
@@ -4,67 +4,41 @@
// utils.go // utils.go
package expr package expr
import ( import "reflect"
"fmt"
"reflect"
)
func IsString(v any) (ok bool) { func isString(v any) (ok bool) {
_, ok = v.(string) _, ok = v.(string)
return ok return ok
} }
func IsInteger(v any) (ok bool) { func isInteger(v any) (ok bool) {
_, ok = v.(int64) _, ok = v.(int64)
return ok return ok
} }
func IsFloat(v any) (ok bool) { func isFloat(v any) (ok bool) {
_, ok = v.(float64) _, ok = v.(float64)
return ok return ok
} }
func IsList(v any) (ok bool) { func isList(v any) (ok bool) {
_, ok = v.(*ListType) _, ok = v.([]any)
return ok return ok
} }
func IsDict(v any) (ok bool) { func isNumber(v any) (ok bool) {
_, ok = v.(map[any]any) return isFloat(v) || isInteger(v)
return ok
}
func IsNumber(v any) (ok bool) {
return IsFloat(v) || IsInteger(v)
}
func isNumOrFract(v any) (ok bool) {
return IsFloat(v) || IsInteger(v) || isFraction(v)
} }
func isNumberString(v any) (ok bool) { func isNumberString(v any) (ok bool) {
return IsString(v) || IsNumber(v) return isString(v) || isNumber(v)
}
func isFunctor(v any) (ok bool) {
_, ok = v.(Functor)
return
}
func isIterator(v any) (ok bool) {
_, ok = v.(Iterator)
return
} }
func numAsFloat(v any) (f float64) { func numAsFloat(v any) (f float64) {
var ok bool var ok bool
if f, ok = v.(float64); !ok { if f, ok = v.(float64); !ok {
if fract, ok := v.(*fraction); ok { i, _ := v.(int64)
f = fract.toFloat() f = float64(i)
} else {
i, _ := v.(int64)
f = float64(i)
}
} }
return return
} }
@@ -157,32 +131,3 @@ func CloneMap[K comparable, V any](source map[K]V) map[K]V {
dest := make(map[K]V, len(source)) dest := make(map[K]V, len(source))
return CopyMap(dest, source) return CopyMap(dest, source)
} }
func CopyFilteredMap[K comparable, V any](dest, source map[K]V, filter func(key K) (accept bool)) map[K]V {
// fmt.Printf("--- Clone with filter %p\n", filter)
if filter == nil {
return CopyMap(dest, source)
} else {
for k, v := range source {
if filter(k) {
// fmt.Printf("\tClone var %q\n", k)
dest[k] = v
}
}
}
return dest
}
func CloneFilteredMap[K comparable, V any](source map[K]V, filter func(key K) (accept bool)) map[K]V {
dest := make(map[K]V, len(source))
return CopyFilteredMap(dest, source, filter)
}
func toInt(value any, description string) (i int, err error) {
if valueInt64, ok := value.(int64); ok {
i = int(valueInt64)
} else {
err = fmt.Errorf("%s expected integer, got %T (%v)", description, value, value)
}
return
}