Compare commits

...

10 Commits

8 changed files with 268 additions and 77 deletions

115
dict_test.go Normal file
View File

@ -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)
}

View File

@ -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

View File

@ -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.

View File

@ -38,8 +38,29 @@ func TestFuncs(t *testing.T) {
/* 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)
}

View File

@ -33,6 +33,9 @@ func TestListParser(t *testing.T) {
/* 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},

View File

@ -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))

View File

@ -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)

View File

@ -11,6 +11,12 @@ import (
"testing"
)
type inputType struct {
source string
wantResult any
wantErr error
}
func TestGeneralParser(t *testing.T) {
inputs := []inputType{
/* 1 */ {`1+/*5*/2`, int64(3), nil},
@ -150,68 +156,11 @@ func TestGeneralParser(t *testing.T) {
/* 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},
}
check_env_expr_path := 113
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)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good := true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
eq := reflect.DeepEqual(gotResult, input.wantResult)
if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
good = false
}
}
if good {
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)
}
type inputType struct {
source string
wantResult any
wantErr error
// t.Setenv("EXPR_PATH", ".")
parserTest(t, "General", inputs)
}
func parserTest(t *testing.T, section string, inputs []inputType) {
@ -219,20 +168,12 @@ 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, "Iterator", input.source, input.wantResult, input.wantErr)