Compare commits
22 Commits
c977e82d9e
...
v0.6.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ee0bb5701 | |||
| b2b0bb04c5 | |||
| f3abf5e77c | |||
| 34b7799177 | |||
| 8c3c54913a | |||
| 5910345c08 | |||
| 8b4dad1381 | |||
| 6ef468408c | |||
| 3a30d890c6 | |||
| 71ab417a56 | |||
| 7cfb89c25c | |||
| 569dbfda9d | |||
| f342dfe9f3 | |||
| 5378952394 | |||
| a9d6a82011 | |||
| 43b74131fb | |||
| cb0eac54b2 | |||
| b219b55878 | |||
| 56c86f917e | |||
| 539a4b44e9 | |||
| 74df927179 | |||
| 510966c497 |
+115
@@ -0,0 +1,115 @@
|
||||
// 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)
|
||||
}
|
||||
+79
-2
@@ -22,15 +22,92 @@ Expressions calculator
|
||||
|
||||
toc::[]
|
||||
|
||||
#TODO: Work in progress#
|
||||
#TODO: Work in progress (last update on 2024/05/07, 07:15 am)#
|
||||
|
||||
== Expr
|
||||
_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
|
||||
#TODO#
|
||||
|
||||
image::expression-diagram.png[]
|
||||
|
||||
== Data types
|
||||
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
|
||||
|
||||
@@ -182,7 +259,7 @@ a=1; b=2; c=3; a+b+c // returns 6
|
||||
----
|
||||
|
||||
=== [blue]`but` operator
|
||||
[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 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 very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`(` and [blue]`)`.
|
||||
|
||||
|
||||
+1400
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
+6
-8
@@ -21,13 +21,8 @@ func TestExpr(t *testing.T) {
|
||||
/* 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},
|
||||
}
|
||||
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
|
||||
inputs1 := []inputType{
|
||||
/* 1 */ {`
|
||||
/* 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},
|
||||
@@ -41,7 +36,10 @@ func TestExpr(t *testing.T) {
|
||||
`, int64(1), nil},
|
||||
}
|
||||
|
||||
for i, input := range inputs1 {
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
|
||||
for i, input := range inputs {
|
||||
var expr Expr
|
||||
var gotResult any
|
||||
var gotErr error
|
||||
|
||||
@@ -9,6 +9,10 @@ type FmtOpt uint16
|
||||
const (
|
||||
TTY FmtOpt = 1 << iota
|
||||
MultiLine
|
||||
Base2
|
||||
Base8
|
||||
Base10
|
||||
Base16
|
||||
)
|
||||
|
||||
type Formatter interface {
|
||||
|
||||
+3
-3
@@ -20,7 +20,7 @@ func importFunc(ctx ExprContext, name string, args []any) (result any, err error
|
||||
return importGeneral(ctx, name, args)
|
||||
}
|
||||
|
||||
func includeFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
func importAllFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
enable(ctx, control_export_all)
|
||||
return importGeneral(ctx, name, args)
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func importGeneral(ctx ExprContext, name string, args []any) (result any, err er
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return
|
||||
@@ -138,7 +138,7 @@ func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (resu
|
||||
|
||||
func ImportImportFuncs(ctx ExprContext) {
|
||||
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
|
||||
ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1)
|
||||
ctx.RegisterFunc("importAll", &simpleFunctor{f: importAllFunc}, 1, -1)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
+3
-3
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func checkNumberParamExpected(funcName string, paramValue any, paramPos, level, subPos int) (err error) {
|
||||
if !(isNumber(paramValue) || isFraction(paramValue)) /*|| isList(paramValue)*/ {
|
||||
if !(IsNumber(paramValue) || isFraction(paramValue)) /*|| isList(paramValue)*/ {
|
||||
err = fmt.Errorf("%s(): param nr %d (%d in %d) has wrong type %T, number expected",
|
||||
funcName, paramPos+1, subPos+1, level, paramValue)
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
|
||||
count++
|
||||
|
||||
if !sumAsFloat {
|
||||
if isFloat(v) {
|
||||
if IsFloat(v) {
|
||||
sumAsFloat = true
|
||||
if sumAsFract {
|
||||
floatSum = fractSum.toFloat()
|
||||
@@ -120,7 +120,7 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
|
||||
count++
|
||||
|
||||
if !mulAsFloat {
|
||||
if isFloat(v) {
|
||||
if IsFloat(v) {
|
||||
mulAsFloat = true
|
||||
if mulAsFract {
|
||||
floatProd = fractProd.toFloat()
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
@@ -102,13 +103,105 @@ func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err erro
|
||||
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.
|
||||
|
||||
+38
-53
@@ -6,17 +6,10 @@ package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncs(t *testing.T) {
|
||||
type inputType struct {
|
||||
source string
|
||||
wantResult any
|
||||
wantErr error
|
||||
}
|
||||
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`isNil(nil)`, true, nil},
|
||||
/* 2 */ {`v=nil; isNil(v)`, true, nil},
|
||||
@@ -29,53 +22,45 @@ func TestFuncs(t *testing.T) {
|
||||
/* 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},
|
||||
}
|
||||
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// inputs1 := []inputType{
|
||||
// /* 1 */ {`0?{}`, nil, nil},
|
||||
// }
|
||||
|
||||
for i, input := range inputs {
|
||||
var expr Expr
|
||||
var gotResult any
|
||||
var gotErr error
|
||||
|
||||
ctx := NewSimpleFuncStore()
|
||||
// ImportMathFuncs(ctx)
|
||||
// ImportImportFunc(ctx)
|
||||
// ImportOsFuncs(ctx)
|
||||
parser := NewParser(ctx)
|
||||
|
||||
logTest(t, i+1, "Funcs", 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 != input.wantResult {
|
||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||
good = false
|
||||
}
|
||||
|
||||
if gotErr != input.wantErr {
|
||||
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
||||
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
|
||||
good = false
|
||||
}
|
||||
}
|
||||
|
||||
if good {
|
||||
succeeded++
|
||||
} else {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
t.Logf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed)
|
||||
// parserTest(t, "Func", inputs[25:26])
|
||||
parserTest(t, "Func", inputs)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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
@@ -0,0 +1,115 @@
|
||||
// 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)
|
||||
}
|
||||
+5
-1
@@ -25,7 +25,11 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
|
||||
func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
|
||||
if info, exists := ctx.GetFuncInfo(name); exists {
|
||||
if info.MinArgs() > len(params) {
|
||||
err = fmt.Errorf("too few params -- expected %d, got %d", 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))
|
||||
|
||||
@@ -46,6 +46,22 @@ 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
|
||||
func newListTermA(args ...*term) *term {
|
||||
return newListTerm(args)
|
||||
|
||||
+11
-5
@@ -1,5 +1,5 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
// All rights reserightChilded.
|
||||
|
||||
// operator-assign.go
|
||||
package expr
|
||||
@@ -27,11 +27,17 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if v, err = self.children[1].compute(ctx); err == nil {
|
||||
if functor, ok := v.(Functor); ok {
|
||||
var minArgs, maxArgs int = 0, 0
|
||||
rightChild := self.children[1]
|
||||
|
||||
if funcDef, ok := functor.(*funcDefFunctor); ok {
|
||||
if v, err = rightChild.compute(ctx); err == nil {
|
||||
if functor, ok := v.(Functor); ok {
|
||||
var minArgs, maxArgs int = 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
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
|
||||
count := 0
|
||||
if isString(childValue) {
|
||||
if IsString(childValue) {
|
||||
module, _ := childValue.(string)
|
||||
count, err = ImportInContextByGlobPattern(ctx, module)
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ func evalFact(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if isInteger(leftValue) {
|
||||
if IsInteger(leftValue) {
|
||||
if i, _ := leftValue.(int64); i >= 0 {
|
||||
f := int64(1)
|
||||
for k := int64(1); k <= i; k++ {
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ func evalInclude(ctx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
|
||||
count := 0
|
||||
if isList(childValue) {
|
||||
if IsList(childValue) {
|
||||
list, _ := childValue.([]any)
|
||||
for i, filePathSpec := range list {
|
||||
if filePath, ok := filePathSpec.(string); ok {
|
||||
@@ -39,7 +39,7 @@ func evalInclude(ctx ExprContext, self *term) (v any, err error) {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if isString(childValue) {
|
||||
} else if IsString(childValue) {
|
||||
filePath, _ := childValue.(string)
|
||||
v, err = EvalFile(ctx, filePath)
|
||||
} else {
|
||||
|
||||
+8
-6
@@ -33,9 +33,10 @@ func evalInsert(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if isList(rightValue) {
|
||||
list, _ := rightValue.([]any)
|
||||
v = append([]any{leftValue}, list...)
|
||||
if IsList(rightValue) {
|
||||
list, _ := rightValue.(*ListType)
|
||||
newList := append(ListType{leftValue}, *list...)
|
||||
v = &newList
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
@@ -49,9 +50,10 @@ func evalAppend(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if isList(leftValue) {
|
||||
list, _ := leftValue.([]any)
|
||||
v = append(list, rightValue)
|
||||
if IsList(leftValue) {
|
||||
list, _ := leftValue.(*ListType)
|
||||
newList := append(*list, rightValue)
|
||||
v = &newList
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
|
||||
+2
-2
@@ -23,10 +23,10 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if isList(childValue) {
|
||||
if IsList(childValue) {
|
||||
list, _ := childValue.([]any)
|
||||
v = int64(len(list))
|
||||
} else if isString(childValue) {
|
||||
} else if IsString(childValue) {
|
||||
s, _ := childValue.(string)
|
||||
v = int64(len(s))
|
||||
} else if it, ok := childValue.(Iterator); ok {
|
||||
|
||||
@@ -25,7 +25,7 @@ func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
|
||||
|
||||
if it, ok := childValue.(Iterator); ok {
|
||||
v, err = it.Next()
|
||||
} else if isInteger(childValue) && self.children[0].symbol() == SymIdentifier {
|
||||
} else if IsInteger(childValue) && self.children[0].symbol() == SymIdentifier {
|
||||
v = childValue
|
||||
i, _ := childValue.(int64)
|
||||
ctx.SetVar(self.children[0].source(), i+1)
|
||||
|
||||
+11
-11
@@ -30,20 +30,20 @@ func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if isString(leftValue) && isInteger(rightValue) {
|
||||
if IsString(leftValue) && IsInteger(rightValue) {
|
||||
s, _ := leftValue.(string)
|
||||
n, _ := rightValue.(int64)
|
||||
v = strings.Repeat(s, int(n))
|
||||
} else if isNumber(leftValue) && isNumber(rightValue) {
|
||||
if isFloat(leftValue) || isFloat(rightValue) {
|
||||
} else if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
|
||||
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||
v = numAsFloat(leftValue) * numAsFloat(rightValue)
|
||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||
v, err = mulAnyFract(leftValue, rightValue)
|
||||
} else {
|
||||
leftInt, _ := leftValue.(int64)
|
||||
rightInt, _ := rightValue.(int64)
|
||||
v = leftInt * rightInt
|
||||
}
|
||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||
v, err = mulAnyFract(leftValue, rightValue)
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
@@ -71,14 +71,16 @@ func evalDivide(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if isNumber(leftValue) && isNumber(rightValue) {
|
||||
if isFloat(leftValue) || isFloat(rightValue) {
|
||||
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
|
||||
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||
d := numAsFloat(rightValue)
|
||||
if d == 0.0 {
|
||||
err = errors.New("division by zero")
|
||||
} else {
|
||||
v = numAsFloat(leftValue) / d
|
||||
}
|
||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||
v, err = divAnyFract(leftValue, rightValue)
|
||||
} else {
|
||||
leftInt, _ := leftValue.(int64)
|
||||
if rightInt, _ := rightValue.(int64); rightInt == 0 {
|
||||
@@ -87,8 +89,6 @@ func evalDivide(ctx ExprContext, self *term) (v any, err error) {
|
||||
v = leftInt / rightInt
|
||||
}
|
||||
}
|
||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||
v, err = divAnyFract(leftValue, rightValue)
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
@@ -114,7 +114,7 @@ func evalDivideAsFloat(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if isNumber(leftValue) && isNumber(rightValue) {
|
||||
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
|
||||
d := numAsFloat(rightValue)
|
||||
if d == 0.0 {
|
||||
err = errors.New("division by zero")
|
||||
@@ -148,7 +148,7 @@ func evalReminder(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if isInteger(leftValue) && isInteger(rightValue) {
|
||||
if IsInteger(leftValue) && IsInteger(rightValue) {
|
||||
rightInt, _ := rightValue.(int64)
|
||||
if rightInt == 0 {
|
||||
err = errors.New("division by zero")
|
||||
|
||||
+9
-9
@@ -25,15 +25,15 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if isNumber(leftValue) && isNumber(rightValue) {
|
||||
if isInteger(leftValue) && isInteger(rightValue) {
|
||||
if IsNumber(leftValue) && IsNumber(rightValue) {
|
||||
if IsInteger(leftValue) && IsInteger(rightValue) {
|
||||
li, _ := leftValue.(int64)
|
||||
ri, _ := rightValue.(int64)
|
||||
v = li == ri
|
||||
} else {
|
||||
v = numAsFloat(leftValue) == numAsFloat(rightValue)
|
||||
}
|
||||
} else if isString(leftValue) && isString(rightValue) {
|
||||
} else if IsString(leftValue) && IsString(rightValue) {
|
||||
ls, _ := leftValue.(string)
|
||||
rs, _ := rightValue.(string)
|
||||
v = ls == rs
|
||||
@@ -111,15 +111,15 @@ func evalLess(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if isNumber(leftValue) && isNumber(rightValue) {
|
||||
if isInteger(leftValue) && isInteger(rightValue) {
|
||||
if IsNumber(leftValue) && IsNumber(rightValue) {
|
||||
if IsInteger(leftValue) && IsInteger(rightValue) {
|
||||
li, _ := leftValue.(int64)
|
||||
ri, _ := rightValue.(int64)
|
||||
v = li < ri
|
||||
} else {
|
||||
v = numAsFloat(leftValue) < numAsFloat(rightValue)
|
||||
}
|
||||
} else if isString(leftValue) && isString(rightValue) {
|
||||
} else if IsString(leftValue) && IsString(rightValue) {
|
||||
ls, _ := leftValue.(string)
|
||||
rs, _ := rightValue.(string)
|
||||
v = ls < rs
|
||||
@@ -148,15 +148,15 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if isNumber(leftValue) && isNumber(rightValue) {
|
||||
if isInteger(leftValue) && isInteger(rightValue) {
|
||||
if IsNumber(leftValue) && IsNumber(rightValue) {
|
||||
if IsInteger(leftValue) && IsInteger(rightValue) {
|
||||
li, _ := leftValue.(int64)
|
||||
ri, _ := rightValue.(int64)
|
||||
v = li <= ri
|
||||
} else {
|
||||
v = numAsFloat(leftValue) <= numAsFloat(rightValue)
|
||||
}
|
||||
} else if isString(leftValue) && isString(rightValue) {
|
||||
} else if IsString(leftValue) && IsString(rightValue) {
|
||||
ls, _ := leftValue.(string)
|
||||
rs, _ := rightValue.(string)
|
||||
v = ls <= rs
|
||||
|
||||
+2
-2
@@ -35,14 +35,14 @@ func evalSign(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if isFloat(rightValue) {
|
||||
if IsFloat(rightValue) {
|
||||
if self.tk.Sym == SymChangeSign {
|
||||
f, _ := rightValue.(float64)
|
||||
v = -f
|
||||
} else {
|
||||
v = rightValue
|
||||
}
|
||||
} else if isInteger(rightValue) {
|
||||
} else if IsInteger(rightValue) {
|
||||
if self.tk.Sym == SymChangeSign {
|
||||
i, _ := rightValue.(int64)
|
||||
v = -i
|
||||
|
||||
+14
-10
@@ -28,17 +28,17 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
||||
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)
|
||||
} else if isNumber(leftValue) && isNumber(rightValue) {
|
||||
if isFloat(leftValue) || isFloat(rightValue) {
|
||||
} else if IsNumber(leftValue) && IsNumber(rightValue) {
|
||||
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
||||
} else {
|
||||
leftInt, _ := leftValue.(int64)
|
||||
rightInt, _ := rightValue.(int64)
|
||||
v = leftInt + rightInt
|
||||
}
|
||||
} else if isList(leftValue) || isList(rightValue) {
|
||||
} else if IsList(leftValue) || IsList(rightValue) {
|
||||
var leftList, rightList *ListType
|
||||
var ok bool
|
||||
if leftList, ok = leftValue.(*ListType); !ok {
|
||||
@@ -56,7 +56,11 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
v = &sumList
|
||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||
v, err = sumAnyFract(leftValue, rightValue)
|
||||
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
||||
} else {
|
||||
v, err = sumAnyFract(leftValue, rightValue)
|
||||
}
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
@@ -82,15 +86,17 @@ func evalMinus(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if isNumber(leftValue) && isNumber(rightValue) {
|
||||
if isFloat(leftValue) || isFloat(rightValue) {
|
||||
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
|
||||
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||
v = numAsFloat(leftValue) - numAsFloat(rightValue)
|
||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||
v, err = subAnyFract(leftValue, rightValue)
|
||||
} else {
|
||||
leftInt, _ := leftValue.(int64)
|
||||
rightInt, _ := rightValue.(int64)
|
||||
v = leftInt - rightInt
|
||||
}
|
||||
} else if isList(leftValue) && isList(rightValue) {
|
||||
} else if IsList(leftValue) && IsList(rightValue) {
|
||||
leftList, _ := leftValue.(*ListType)
|
||||
rightList, _ := rightValue.(*ListType)
|
||||
diffList := make(ListType, 0, len(*leftList)-len(*rightList))
|
||||
@@ -100,8 +106,6 @@ func evalMinus(ctx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
}
|
||||
v = &diffList
|
||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||
v, err = subAnyFract(leftValue, rightValue)
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
|
||||
+121
-248
@@ -11,13 +11,13 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
type inputType struct {
|
||||
source string
|
||||
wantResult any
|
||||
wantErr error
|
||||
}
|
||||
type inputType struct {
|
||||
source string
|
||||
wantResult any
|
||||
wantErr error
|
||||
}
|
||||
|
||||
func TestGeneralParser(t *testing.T) {
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`1+/*5*/2`, int64(3), nil},
|
||||
/* 2 */ {`3 == 4`, false, nil},
|
||||
@@ -51,160 +51,133 @@ func TestParser(t *testing.T) {
|
||||
/* 30 */ {"-(-2+1)", int64(1), nil},
|
||||
/* 31 */ {"(1+1)*5", int64(10), nil},
|
||||
/* 32 */ {"200 / (1+1) - 1", int64(99), nil},
|
||||
/* 33 */ {`add(1,2,3)`, int64(6), nil},
|
||||
/* 34 */ {`mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
|
||||
/* 35 */ {`add(1+4,3+2,5*(3-2))`, int64(15), nil},
|
||||
/* 36 */ {`add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
|
||||
/* 37 */ {`add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
|
||||
/* 38 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil},
|
||||
/* 39 */ {`(((1)))`, int64(1), nil},
|
||||
/* 40 */ {`"uno_" + var2`, `uno_abc`, nil},
|
||||
/* 41 */ {`0 || 0.0 && "hello"`, false, nil},
|
||||
/* 42 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)},
|
||||
/* 43 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
|
||||
/* 44 */ {`false // very simple expression`, false, nil},
|
||||
/* 45 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two non-nil operands, got 1`)},
|
||||
/* 46 */ {"", nil, nil},
|
||||
/* 47 */ {"4!", int64(24), nil},
|
||||
/* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
|
||||
/* 49 */ {"-4!", int64(-24), nil},
|
||||
/* 50 */ {"1.5 < 7", true, nil},
|
||||
/* 51 */ {"1.5 > 7", false, nil},
|
||||
/* 52 */ {"1.5 <= 7", true, nil},
|
||||
/* 53 */ {"1.5 >= 7", false, nil},
|
||||
/* 54 */ {"1.5 != 7", true, nil},
|
||||
/* 55 */ {"1.5 == 7", false, nil},
|
||||
/* 56 */ {`"1.5" < "7"`, true, nil},
|
||||
/* 57 */ {`"1.5" > "7"`, false, nil},
|
||||
/* 58 */ {`"1.5" == "7"`, false, nil},
|
||||
/* 59 */ {`"1.5" != "7"`, true, nil},
|
||||
/* 60 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two non-nil operands, got 1`)},
|
||||
/* 61 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two non-nil operands, got 1`)},
|
||||
/* 62 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two non-nil operands, got 1`)},
|
||||
/* 63 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two non-nil operands, got 1`)},
|
||||
/* 64 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two non-nil operands, got 1`)},
|
||||
/* 65 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two non-nil operands, got 1`)},
|
||||
/* 66 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two non-nil operands, got 1`)},
|
||||
/* 67 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two non-nil operands, got 1`)},
|
||||
/* 68 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two non-nil operands, got 1`)},
|
||||
/* 69 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two non-nil operands, got 1`)},
|
||||
/* 70 */ {"+1.5", float64(1.5), nil},
|
||||
/* 71 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
|
||||
/* 72 */ {"4 / 0", nil, errors.New(`division by zero`)},
|
||||
/* 73 */ {"4.0 / 0", nil, errors.New(`division by zero`)},
|
||||
/* 74 */ {"4.0 / \n2", float64(2.0), nil},
|
||||
/* 75 */ {`123`, int64(123), nil},
|
||||
/* 76 */ {`1.`, float64(1.0), nil},
|
||||
/* 77 */ {`1.E-2`, float64(0.01), nil},
|
||||
/* 78 */ {`1E2`, float64(100), nil},
|
||||
/* 79 */ {`1 / 2`, int64(0), nil},
|
||||
/* 80 */ {`1.0 / 2`, float64(0.5), nil},
|
||||
/* 81 */ {`1 ./ 2`, float64(0.5), nil},
|
||||
/* 82 */ {`5 % 2`, int64(1), nil},
|
||||
/* 83 */ {`5 % (-2)`, int64(1), nil},
|
||||
/* 84 */ {`-5 % 2`, int64(-1), 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 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
|
||||
/* 87 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
|
||||
/* 88 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
|
||||
/* 89 */ {`~ 2 > 1`, false, nil},
|
||||
/* 90 */ {`~ true && true`, false, nil},
|
||||
/* 91 */ {`~ false || true`, true, nil},
|
||||
/* 92 */ {`false but true`, true, nil},
|
||||
/* 93 */ {`2+3 but 5*2`, int64(10), nil},
|
||||
/* 94 */ {`add(1,2) but var2`, "abc", nil},
|
||||
/* 95 */ {`x=2`, int64(2), nil},
|
||||
/* 96 */ {`x=2 but x*10`, int64(20), nil},
|
||||
/* 97 */ {`false and true`, false, nil},
|
||||
/* 98 */ {`false and (x==2)`, false, nil},
|
||||
/* 99 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable or function "x"`)},
|
||||
/* 100 */ {`false or true`, true, nil},
|
||||
/* 101 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)},
|
||||
/* 102 */ {`a=5; a`, int64(5), nil},
|
||||
/* 103 */ {`a=5; b=2; add(a, b*3)`, int64(11), nil},
|
||||
/* 104 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
|
||||
/* 105 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable`)},
|
||||
/* 106 */ {`2+(a=5)`, int64(7), nil},
|
||||
/* 107 */ {`two=func(){2}; two()`, int64(2), nil},
|
||||
/* 108 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
|
||||
/* 109 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
|
||||
/* 110 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
|
||||
/* 111 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
|
||||
/* 112 */ {`import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||
// /* 113 */ {`import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||
/* 114 */ {`x ?? "default"`, "default", nil},
|
||||
/* 115 */ {`x="hello"; x ?? "default"`, "hello", nil},
|
||||
/* 116 */ {`y=x ?? func(){4}; y()`, int64(4), nil},
|
||||
/* 117 */ {`x ?= "default"; x`, "default", nil},
|
||||
/* 118 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
|
||||
/* 119 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
|
||||
/* 120 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
|
||||
/* 121 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
|
||||
/* 122 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
|
||||
/* 123 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
||||
/* 124 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
||||
/* 125 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
|
||||
// /* 126 */ {`include("./test-funcs.expr"); six()`, int64(6), nil},
|
||||
/* 127 */ {`import("./sample-export-all.expr"); six()`, int64(6), nil},
|
||||
/* 128 */ {`1 ? {"a"} : {"b"}`, "b", nil},
|
||||
/* 129 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil},
|
||||
/* 130 */ {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
|
||||
/* 131 */ {`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`, nil, errors.New(`[1:34] case list in default clause`)},
|
||||
/* 132 */ {`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`, "b", nil},
|
||||
/* 133 */ {`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`, "b", nil},
|
||||
/* 134 */ {`10 ? {"a"} : {"b"}`, nil, errors.New(`[1:3] no case catches the value (10) of the selection expression`)},
|
||||
/* 135 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, errors.New(`[1:22] selector-case outside of a selector context`)},
|
||||
/* 136 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
|
||||
/* 137 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
|
||||
/* 138 */ {`nil`, nil, nil},
|
||||
/* 139 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
|
||||
/* 140 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
|
||||
/* 141 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)},
|
||||
/* 142 */ {`{}`, map[any]any{}, nil},
|
||||
/* 143 */ {`1|2`, newFraction(1, 2), nil},
|
||||
/* 144 */ {`1|2 + 1`, newFraction(3, 2), nil},
|
||||
/* 145 */ {`1|2 - 1`, newFraction(-1, 2), nil},
|
||||
/* 146 */ {`1|2 * 1`, newFraction(1, 2), nil},
|
||||
/* 147 */ {`1|2 * 2|3`, newFraction(2, 6), nil},
|
||||
/* 148 */ {`1|2 / 2|3`, newFraction(3, 4), nil},
|
||||
/* 149 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil},
|
||||
/* 150 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil},
|
||||
/* 151 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
|
||||
/* 152 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
|
||||
/* 153 */ {`include "iterator.expr"; it=$(ds,3); ()it`, int64(0), nil},
|
||||
/* 154 */ {`include "iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil},
|
||||
/* 155 */ {`include "iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(2), nil},
|
||||
/* 156 */ {`include "iterator.expr"; it=$(ds,3); it++; it++; it.reset; ()it`, int64(0), nil},
|
||||
/* 157 */ {`builtin "math.arith"; include "iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil},
|
||||
/* 158 */ {`builtin "math.arith"; include "iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil},
|
||||
/* 159 */ {`include "file-reader.expr"; it=$(ds,"int.list"); mul(it)`, int64(12000), nil},
|
||||
/* 160 */ {`include "file-reader.expr"; it=$(ds,"int.list"); it++; it.index`, int64(0), nil},
|
||||
/* 161 */ {`include "file-reader.expr"; it=$(ds,"int.list"); it.clean`, true, nil},
|
||||
/* 162 */ {`builtin "os.file"`, int64(1), nil},
|
||||
/* 163 */ {`it=$(1,2,3); it++`, int64(1), nil},
|
||||
/* 33 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil},
|
||||
/* 34 */ {`(((1)))`, int64(1), nil},
|
||||
/* 35 */ {`var2="abc"; "uno_" + var2`, `uno_abc`, nil},
|
||||
/* 36 */ {`0 || 0.0 && "hello"`, false, nil},
|
||||
/* 37 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)},
|
||||
/* 38 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
|
||||
/* 39 */ {`false // very simple expression`, false, nil},
|
||||
/* 40 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two non-nil operands, got 1`)},
|
||||
/* 41 */ {"", nil, nil},
|
||||
/* 42 */ {"4!", int64(24), nil},
|
||||
/* 43 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
|
||||
/* 44 */ {"-4!", int64(-24), nil},
|
||||
/* 45 */ {"1.5 < 7", true, nil},
|
||||
/* 46 */ {"1.5 > 7", false, nil},
|
||||
/* 47 */ {"1.5 <= 7", true, nil},
|
||||
/* 48 */ {"1.5 >= 7", false, nil},
|
||||
/* 49 */ {"1.5 != 7", true, nil},
|
||||
/* 50 */ {"1.5 == 7", false, nil},
|
||||
/* 51 */ {`"1.5" < "7"`, true, nil},
|
||||
/* 52 */ {`"1.5" > "7"`, false, nil},
|
||||
/* 53 */ {`"1.5" == "7"`, false, nil},
|
||||
/* 54 */ {`"1.5" != "7"`, true, nil},
|
||||
/* 55 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two non-nil operands, got 1`)},
|
||||
/* 56 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two non-nil operands, got 1`)},
|
||||
/* 57 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two non-nil operands, got 1`)},
|
||||
/* 58 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two non-nil operands, got 1`)},
|
||||
/* 59 */ {"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 non-nil operands, got 1`)},
|
||||
/* 61 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two non-nil operands, got 1`)},
|
||||
/* 62 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two non-nil operands, got 1`)},
|
||||
/* 63 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two non-nil operands, got 1`)},
|
||||
/* 64 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two non-nil operands, got 1`)},
|
||||
/* 65 */ {"+1.5", float64(1.5), nil},
|
||||
/* 66 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
|
||||
/* 67 */ {"4 / 0", nil, errors.New(`division by zero`)},
|
||||
/* 68 */ {"4.0 / 0", nil, errors.New(`division by zero`)},
|
||||
/* 69 */ {"4.0 / \n2", float64(2.0), nil},
|
||||
/* 70 */ {`123`, int64(123), nil},
|
||||
/* 71 */ {`1.`, float64(1.0), nil},
|
||||
/* 72 */ {`1.E-2`, float64(0.01), nil},
|
||||
/* 73 */ {`1E2`, float64(100), nil},
|
||||
/* 74 */ {`1 / 2`, int64(0), nil},
|
||||
/* 75 */ {`1.0 / 2`, float64(0.5), nil},
|
||||
/* 76 */ {`1 ./ 2`, float64(0.5), nil},
|
||||
/* 77 */ {`5 % 2`, int64(1), nil},
|
||||
/* 78 */ {`5 % (-2)`, int64(1), nil},
|
||||
/* 79 */ {`-5 % 2`, int64(-1), nil},
|
||||
/* 80 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [int64] and right operand '2' [float64] are not compatible with operator "%"`)},
|
||||
/* 81 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
|
||||
/* 82 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
|
||||
/* 83 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
|
||||
/* 84 */ {`~ 2 > 1`, false, nil},
|
||||
/* 85 */ {`~ true && true`, false, nil},
|
||||
/* 86 */ {`~ false || true`, true, nil},
|
||||
/* 87 */ {`false but true`, true, nil},
|
||||
/* 88 */ {`2+3 but 5*2`, int64(10), nil},
|
||||
/* 89 */ {`x=2`, int64(2), nil},
|
||||
/* 90 */ {`x=2 but x*10`, int64(20), nil},
|
||||
/* 91 */ {`false and true`, false, nil},
|
||||
/* 92 */ {`false and (x==2)`, false, nil},
|
||||
/* 93 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable or function "x"`)},
|
||||
/* 94 */ {`false or true`, true, nil},
|
||||
/* 95 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)},
|
||||
/* 96 */ {`a=5; a`, int64(5), nil},
|
||||
/* 97 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
|
||||
/* 98 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable`)},
|
||||
/* 99 */ {`2+(a=5)`, int64(7), nil},
|
||||
/* 100 */ {`x ?? "default"`, "default", nil},
|
||||
/* 101 */ {`x="hello"; x ?? "default"`, "hello", nil},
|
||||
/* 102 */ {`y=x ?? func(){4}; y()`, int64(4), nil},
|
||||
/* 103 */ {`x ?= "default"; x`, "default", nil},
|
||||
/* 104 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
|
||||
/* 105 */ {`1 ? {"a"} : {"b"}`, "b", nil},
|
||||
/* 106 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil},
|
||||
/* 107 */ {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
|
||||
/* 108 */ {`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`, nil, errors.New(`[1:34] case list in default clause`)},
|
||||
/* 109 */ {`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`, "b", nil},
|
||||
/* 110 */ {`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`, "b", nil},
|
||||
/* 111 */ {`10 ? {"a"} : {"b"}`, nil, errors.New(`[1:3] no case catches the value (10) of the selection expression`)},
|
||||
/* 112 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, errors.New(`[1:22] selector-case outside of a selector context`)},
|
||||
/* 113 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
|
||||
/* 114 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
|
||||
/* 115 */ {`nil`, nil, nil},
|
||||
/* 116 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
|
||||
/* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
|
||||
/* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)},
|
||||
/* 119 */ {`{}`, map[any]any{}, nil},
|
||||
/* 120 */ {`1|2`, newFraction(1, 2), nil},
|
||||
/* 121 */ {`1|2 + 1`, newFraction(3, 2), nil},
|
||||
/* 122 */ {`1|2 - 1`, newFraction(-1, 2), nil},
|
||||
/* 123 */ {`1|2 * 1`, newFraction(1, 2), nil},
|
||||
/* 124 */ {`1|2 * 2|3`, newFraction(2, 6), nil},
|
||||
/* 125 */ {`1|2 / 2|3`, newFraction(3, 4), nil},
|
||||
/* 126 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil},
|
||||
/* 127 */ {`builtin "math.arith"; mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
|
||||
/* 128 */ {`builtin "math.arith"; add(1+4,3+2,5*(3-2))`, int64(15), nil},
|
||||
/* 129 */ {`builtin "math.arith"; add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
|
||||
/* 130 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
|
||||
/* 131 */ {`builtin "math.arith"; a=5; b=2; add(a, b*3)`, int64(11), nil},
|
||||
/* 132 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil},
|
||||
/* 133 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil},
|
||||
/* 134 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil},
|
||||
/* 135 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
|
||||
/* 136 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
|
||||
/* 137 */ {`builtin "os.file"`, int64(1), 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
|
||||
failed := 0
|
||||
|
||||
// inputs1 := []inputType{
|
||||
// /* 159 */ {`include "file-reader.expr"; it=$(ds,"int.list"); mul(it)`, int64(12000), nil},
|
||||
// }
|
||||
|
||||
for i, input := range inputs {
|
||||
var expr Expr
|
||||
var gotResult any
|
||||
var gotErr error
|
||||
|
||||
ctx := NewSimpleFuncStore()
|
||||
ctx.SetVar("var1", int64(123))
|
||||
ctx.SetVar("var2", "abc")
|
||||
ImportMathFuncs(ctx)
|
||||
ImportImportFuncs(ctx)
|
||||
parser := NewParser(ctx)
|
||||
|
||||
logTest(t, i+1, "General", input.source, input.wantResult, input.wantErr)
|
||||
logTest(t, i+1, "Iterator", input.source, input.wantResult, input.wantErr)
|
||||
|
||||
r := strings.NewReader(input.source)
|
||||
scanner := NewScanner(r, DefaultTranslations())
|
||||
@@ -232,109 +205,9 @@ func TestParser(t *testing.T) {
|
||||
succeeded++
|
||||
} else {
|
||||
failed++
|
||||
if i+1 == check_env_expr_path {
|
||||
t.Logf(`NOTICE: Test nr %d requires EXPR_PATH="."`, check_env_expr_path)
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Logf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed)
|
||||
}
|
||||
|
||||
func TestListParser(t *testing.T) {
|
||||
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},
|
||||
|
||||
// /* 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("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed)
|
||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||
}
|
||||
|
||||
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
|
||||
|
||||
@@ -9,37 +9,41 @@ import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func isString(v any) (ok bool) {
|
||||
func IsString(v any) (ok bool) {
|
||||
_, ok = v.(string)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isInteger(v any) (ok bool) {
|
||||
func IsInteger(v any) (ok bool) {
|
||||
_, ok = v.(int64)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isFloat(v any) (ok bool) {
|
||||
func IsFloat(v any) (ok bool) {
|
||||
_, ok = v.(float64)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isList(v any) (ok bool) {
|
||||
func IsList(v any) (ok bool) {
|
||||
_, ok = v.(*ListType)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isDict(v any) (ok bool) {
|
||||
func IsDict(v any) (ok bool) {
|
||||
_, ok = v.(map[any]any)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isNumber(v any) (ok bool) {
|
||||
return isFloat(v) || isInteger(v)
|
||||
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) {
|
||||
return isString(v) || isNumber(v)
|
||||
return IsString(v) || IsNumber(v)
|
||||
}
|
||||
|
||||
func isFunctor(v any) (ok bool) {
|
||||
|
||||
Reference in New Issue
Block a user