From f66cd1fdb17b7d591e017c0174f72ab186dbdf37 Mon Sep 17 00:00:00 2001 From: Celestino Amoroso Date: Sat, 1 Jun 2024 16:31:50 +0200 Subject: [PATCH] New test file for specific code section or data type --- ast.go | 5 +- common-errors.go | 24 ++++--- context.go | 1 + func-base.go | 2 +- func-os.go | 101 ++++++++++++++++++++-------- function.go | 13 +++- module-register.go | 25 ------- operand-func.go | 30 +-------- t_fractions_test.go | 66 ++++++++++++++++++ t_func-base_test.go | 67 +++++++++++++++++++ t_func-import_test.go | 24 +++++++ t_func-math-arith_test.go | 29 ++++++++ t_func-os_test.go | 29 ++++++++ t_func-string_test.go | 69 +++++++++++++++++++ t_funcs_test.go | 137 +++++++++++--------------------------- t_list_test.go | 6 ++ t_module-register_test.go | 31 +++++++++ t_parser_test.go | 26 ++------ t_utils_test.go | 17 +++++ 19 files changed, 488 insertions(+), 214 deletions(-) create mode 100644 t_fractions_test.go create mode 100644 t_func-base_test.go create mode 100644 t_func-import_test.go create mode 100644 t_func-math-arith_test.go create mode 100644 t_func-os_test.go create mode 100644 t_func-string_test.go create mode 100644 t_module-register_test.go diff --git a/ast.go b/ast.go index f8299a9..6319187 100644 --- a/ast.go +++ b/ast.go @@ -127,8 +127,9 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) { } } if err == nil { - result, err = self.root.compute(ctx) - ctx.UnsafeSetVar(ControlLastResult, result) + if result, err = self.root.compute(ctx); err == nil { + ctx.UnsafeSetVar(ControlLastResult, result) + } } // } else { // err = errors.New("empty expression") diff --git a/common-errors.go b/common-errors.go index b1bb2e7..0a9cc11 100644 --- a/common-errors.go +++ b/common-errors.go @@ -8,32 +8,40 @@ import ( "fmt" ) -func errTooFewParams(minArgs, maxArgs, argCount int) (err error) { +func errTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) { if maxArgs < 0 { - err = fmt.Errorf("too few params -- expected %d or more, got %d", minArgs, argCount) + err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount) } else { - err = fmt.Errorf("too few params -- expected %d, got %d", minArgs, argCount) + err = fmt.Errorf("%s(): too few params -- expected %d, got %d", funcName, minArgs, argCount) } return } -func errTooMuchParams(maxArgs, argCount int) (err error) { - err = fmt.Errorf("too much params -- expected %d, got %d", maxArgs, argCount) +func errTooMuchParams(funcName string, maxArgs, argCount int) (err error) { + err = fmt.Errorf("%s(): too much params -- expected %d, got %d", funcName, maxArgs, argCount) return } // --- General errors func errCantConvert(funcName string, value any, kind string) error { - return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind) + if typer, ok := value.(Typer); ok { + return fmt.Errorf("%s(): can't convert %s to %s", funcName, typer.TypeName(), kind) + } else { + 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) } -func errDivisionByZero(funcName string) error { - return fmt.Errorf("%s() division by zero", funcName) +func errFuncDivisionByZero(funcName string) error { + return fmt.Errorf("%s(): division by zero", funcName) +} + +func errDivisionByZero() error { + return fmt.Errorf("division by zero") } // --- Parameter errors diff --git a/context.go b/context.go index 6c2df5f..a77f9c7 100644 --- a/context.go +++ b/context.go @@ -9,6 +9,7 @@ type Functor interface { Invoke(ctx ExprContext, name string, args []any) (result any, err error) SetFunc(info ExprFunc) GetFunc() ExprFunc + GetParams() []ExprFuncParam } // ---- Function Param Info diff --git a/func-base.go b/func-base.go index e1568ab..1cb32c6 100644 --- a/func-base.go +++ b/func-base.go @@ -129,7 +129,7 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error) if den, ok = args[1].(int64); !ok { err = errExpectedGot(name, "integer", args[1]) } else if den == 0 { - err = errDivisionByZero(name) + err = errFuncDivisionByZero(name) } } if err == nil { diff --git a/func-os.go b/func-os.go index 38fe280..7a42eca 100644 --- a/func-os.go +++ b/func-os.go @@ -20,6 +20,14 @@ type osWriter struct { writer *bufio.Writer } +func (h *osWriter) TypeName() string { + return "osWriter" +} + +func (h *osWriter) String() string { + return "writer" +} + func (h *osWriter) getFile() *os.File { return h.fh } @@ -29,6 +37,14 @@ type osReader struct { reader *bufio.Reader } +func (h *osReader) TypeName() string { + return "osReader" +} + +func (h *osReader) String() string { + return "reader" +} + func (h *osReader) getFile() *os.File { return h.fh } @@ -84,11 +100,23 @@ func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err e return } +func errInvalidFileHandle(funcName string, v any) error { + if v != nil { + return fmt.Errorf("%s(): invalid file handle %v [%T]", funcName, v, v) + } else { + return fmt.Errorf("%s(): invalid file handle", funcName) + } +} + func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { var handle osHandle + var invalidFileHandle any if len(args) > 0 { - handle, _ = args[0].(osHandle) + var ok bool + if handle, ok = args[0].(osHandle); !ok { + invalidFileHandle = args[0] + } } if handle != nil { @@ -96,12 +124,14 @@ func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err er if w, ok := handle.(*osWriter); ok { err = w.writer.Flush() } + if err == nil { err = fh.Close() } } - } else { - err = fmt.Errorf("%s(): invalid file handle", name) + } + if err == nil && (handle == nil || invalidFileHandle != nil) { + err = errInvalidFileHandle("closeFileFunc", handle) } result = err == nil return @@ -109,51 +139,68 @@ func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err er func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { var handle osHandle + var invalidFileHandle any if len(args) > 0 { - handle, _ = args[0].(osHandle) + var ok bool + if handle, ok = args[0].(osHandle); !ok { + invalidFileHandle = args[0] + } } if handle != nil { - if fh := handle.getFile(); fh != nil { - if w, ok := handle.(*osWriter); ok { - result, err = fmt.Fprint(w.writer, args[1:]...) - } + if w, ok := handle.(*osWriter); ok { + result, err = fmt.Fprint(w.writer, args[1:]...) + } else { + invalidFileHandle = handle } } + + if err == nil && (handle == nil || invalidFileHandle != nil) { + err = errInvalidFileHandle("writeFileFunc", invalidFileHandle) + } return } func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { var handle osHandle + var invalidFileHandle any + result = nil if len(args) > 0 { - handle, _ = args[0].(osHandle) + var ok bool + if handle, ok = args[0].(osHandle); !ok || args[0] == nil { + invalidFileHandle = args[0] + } } if handle != nil { - if fh := handle.getFile(); fh != nil { - if r, ok := handle.(*osReader); ok { - var limit byte = '\n' - var v string - if len(args) > 1 { - if s, ok := args[1].(string); ok && len(s) > 0 { - limit = s[0] - } - } - if v, err = r.reader.ReadString(limit); err == nil { - if len(v) > 0 && v[len(v)-1] == limit { - result = v[0 : len(v)-1] - } else { - result = v - } - } - if err == io.EOF { - err = nil + if r, ok := handle.(*osReader); ok { + var limit byte = '\n' + var v string + if len(args) > 1 { + if s, ok := args[1].(string); ok && len(s) > 0 { + limit = s[0] } } + if v, err = r.reader.ReadString(limit); err == nil { + if len(v) > 0 && v[len(v)-1] == limit { + result = v[0 : len(v)-1] + } else { + result = v + } + } + if err == io.EOF { + err = nil + } + } else { + invalidFileHandle = handle } } + + if err == nil && (handle == nil || invalidFileHandle != nil) { + err = errInvalidFileHandle("readFileFunc", invalidFileHandle) + } return } diff --git a/function.go b/function.go index 4605798..38c97d3 100644 --- a/function.go +++ b/function.go @@ -26,6 +26,14 @@ func (functor *baseFunctor) ToString(opt FmtOpt) (s string) { return s } +func (functor *baseFunctor) GetParams() (params []ExprFuncParam) { + if functor.info != nil { + return functor.info.Params() + } else { + return []ExprFuncParam{} + } +} + func (functor *baseFunctor) SetFunc(info ExprFunc) { functor.info = info } @@ -70,9 +78,8 @@ func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (re arg := args[i] if funcArg, ok := arg.(Functor); ok { // ctx.RegisterFunc(p, functor, 0, -1) - ctx.RegisterFunc(p, funcArg, typeAny, []ExprFuncParam{ - newFuncParam(paramValue), - }) + paramSpecs := funcArg.GetParams() + ctx.RegisterFunc(p, funcArg, typeAny, paramSpecs) } else { ctx.UnsafeSetVar(p, arg) } diff --git a/module-register.go b/module-register.go index 1499cfe..d583882 100644 --- a/module-register.go +++ b/module-register.go @@ -30,31 +30,6 @@ func registerImport(name string, importFunc func(ExprContext), description strin 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 { diff --git a/operand-func.go b/operand-func.go index f2b9b0a..cb3b274 100644 --- a/operand-func.go +++ b/operand-func.go @@ -25,10 +25,10 @@ func newFuncCallTerm(tk *Token, args []*term) *term { func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) { if info, exists, owner := GetFuncInfo(ctx, name); exists { if info.MinArgs() > len(params) { - err = errTooFewParams(info.MinArgs(), info.MaxArgs(), len(params)) + err = errTooFewParams(name, info.MinArgs(), info.MaxArgs(), len(params)) } if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(params) { - err = errTooMuchParams(info.MaxArgs(), len(params)) + err = errTooMuchParams(name, info.MaxArgs(), len(params)) } if err == nil && owner != ctx { // ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs()) @@ -75,32 +75,6 @@ func newFuncDefTerm(tk *Token, args []*term) *term { } // -------- eval func def -// TODO -// type funcDefFunctor struct { -// params []string -// expr Expr -// } - -// func (funcDef *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) { -// for i, p := range funcDef.params { -// if i < len(args) { -// arg := args[i] -// if functor, ok := arg.(Functor); ok { -// // ctx.RegisterFunc(p, functor, 0, -1) -// ctx.RegisterFunc2(p, functor, typeAny, []ExprFuncParam{ -// newFuncParam(paramValue), -// }) -// } else { -// ctx.UnsafeSetVar(p, arg) -// } -// } else { -// ctx.UnsafeSetVar(p, nil) -// } -// } -// result, err = funcDef.expr.eval(ctx, false) -// return -// } - func evalFuncDef(ctx ExprContext, self *term) (v any, err error) { bodySpec := self.value() if expr, ok := bodySpec.(*ast); ok { diff --git a/t_fractions_test.go b/t_fractions_test.go new file mode 100644 index 0000000..d2fbd34 --- /dev/null +++ b/t_fractions_test.go @@ -0,0 +1,66 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// t_fractions_test.go +package expr + +import ( + "errors" + "testing" +) + +func TestFractionsParser(t *testing.T) { + section := "Fraction" + inputs := []inputType{ + /* 1 */ {`1|2`, newFraction(1, 2), nil}, + /* 2 */ {`1|2 + 1`, newFraction(3, 2), nil}, + /* 3 */ {`1|2 - 1`, newFraction(-1, 2), nil}, + /* 4 */ {`1|2 * 1`, newFraction(1, 2), nil}, + /* 5 */ {`1|2 * 2|3`, newFraction(2, 6), nil}, + /* 6 */ {`1|2 / 2|3`, newFraction(3, 4), nil}, + /* 7 */ {`1|"5"`, nil, errors.New(`denominator must be integer, got string (5)`)}, + /* 8 */ {`"1"|5`, nil, errors.New(`numerator must be integer, got string (1)`)}, + /* 9 */ {`1|+5`, nil, errors.New(`[1:3] infix operator "|" requires two non-nil operands, got 1`)}, + /* 10 */ {`1|(-2)`, newFraction(-1, 2), nil}, + /* 11 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil}, + /* 12 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil}, + /* 13 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil}, + /* 14 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil}, + /* 15 */ {`1|0`, nil, errors.New(`division by zero`)}, + /* 16 */ {`fract(-0.5)`, newFraction(-1, 2), nil}, + /* 17 */ {`fract("")`, (*FractionType)(nil), errors.New(`bad syntax`)}, + /* 18 */ {`fract("-1")`, newFraction(-1, 1), nil}, + /* 19 */ {`fract("+1")`, newFraction(1, 1), nil}, + /* 20 */ {`fract("1a")`, (*FractionType)(nil), errors.New(`strconv.ParseInt: parsing "1a": invalid syntax`)}, + /* 21 */ {`fract(1,0)`, nil, errors.New(`fract(): division by zero`)}, + } + parserTest(t, section, inputs) +} + +func TestFractionToStringSimple(t *testing.T) { + source := newFraction(1, 2) + want := "1|2" + got := source.ToString(0) + if got != want { + t.Errorf(`(1,2) -> result = %v [%T], want = %v [%T]`, got, got, want, want) + } +} + +func TestFractionToStringMultiline(t *testing.T) { + source := newFraction(1, 2) + want := "1\n-\n2" + got := source.ToString(MultiLine) + if got != want { + t.Errorf(`(1,2) -> result = %v [%T], want = %v [%T]`, got, got, want, want) + } +} + +// TODO Check this test: the output string ends with a space +func _TestToStringMultilineTty(t *testing.T) { + source := newFraction(-1, 2) + want := "\x1b[4m-1\x1b[0m\n2" + got := source.ToString(MultiLine | TTY) + if got != want { + t.Errorf(`(1,2) -> result = %#v [%T], want = %#v [%T]`, got, got, want, want) + } +} diff --git a/t_func-base_test.go b/t_func-base_test.go new file mode 100644 index 0000000..20a40e6 --- /dev/null +++ b/t_func-base_test.go @@ -0,0 +1,67 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// t_func-base_test.go +package expr + +import ( + "errors" + "testing" +) + +func TestFuncBase(t *testing.T) { + section := "Func-Base" + + 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(): too much params -- expected 1, got 2`)}, + /* 11 */ {`int(nil)`, nil, errors.New(`int(): can't convert to int`)}, + /* 12 */ {`isInt(2+1)`, true, nil}, + /* 13 */ {`isInt(3.1)`, false, nil}, + /* 14 */ {`isFloat(3.1)`, true, nil}, + /* 15 */ {`isString("3.1")`, true, nil}, + /* 16 */ {`isString("3" + 1)`, true, nil}, + /* 17 */ {`isList(["3", 1])`, true, nil}, + /* 18 */ {`isDict({"a":"3", "b":1})`, true, nil}, + /* 19 */ {`isFract(1|3)`, true, nil}, + /* 20 */ {`isFract(3|1)`, false, nil}, + /* 21 */ {`isRational(3|1)`, true, nil}, + /* 22 */ {`fract("2.2(3)")`, newFraction(67, 30), nil}, + /* 23 */ {`fract("1.21(3)")`, newFraction(91, 75), nil}, + /* 24 */ {`fract(1.21(3))`, newFraction(91, 75), nil}, + /* 25 */ {`fract(1.21)`, newFraction(121, 100), nil}, + /* 26 */ {`dec(2)`, float64(2), nil}, + /* 27 */ {`dec(2.0)`, float64(2), nil}, + /* 28 */ {`dec("2.0")`, float64(2), nil}, + /* 29 */ {`dec(true)`, float64(1), nil}, + /* 30 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")}, + /* 31 */ {`dec()`, nil, errors.New(`dec(): too few params -- expected 1, got 0`)}, + /* 32 */ {`dec(1,2,3)`, nil, errors.New(`dec(): too much params -- expected 1, got 3`)}, + /* 33 */ {`isBool(false)`, true, nil}, + /* 34 */ {`fract(1|2)`, newFraction(1, 2), nil}, + /* 35 */ {`fract(12,2)`, newFraction(6, 1), nil}, + /* 36 */ {`bool(2)`, true, nil}, + /* 37 */ {`bool(1|2)`, true, nil}, + /* 38 */ {`bool(1.0)`, true, nil}, + /* 39 */ {`bool("1")`, true, nil}, + /* 40 */ {`bool(false)`, false, nil}, + /* 41 */ {`bool([1])`, nil, errors.New(`bool(): can't convert list to bool`)}, + /* 42 */ {`dec(false)`, float64(0), nil}, + /* 43 */ {`dec(1|2)`, float64(0.5), nil}, + /* 44 */ {`dec([1])`, nil, errors.New(`dec(): can't convert list to float`)}, + // /* 45 */ {`string([1])`, nil, errors.New(`string(): can't convert list to string`)}, + } + + t.Setenv("EXPR_PATH", ".") + + // parserTestSpec(t, section, inputs, 2) + parserTest(t, section, inputs) +} diff --git a/t_func-import_test.go b/t_func-import_test.go new file mode 100644 index 0000000..6f8574b --- /dev/null +++ b/t_func-import_test.go @@ -0,0 +1,24 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// t_func-import_test.go +package expr + +import ( + "testing" +) + +func TestFuncImport(t *testing.T) { + section := "Func-Import" + inputs := []inputType{ + /* 1 */ {`builtin "import"; import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil}, + /* 2 */ {`builtin "import"; import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil}, + /* 3 */ {`builtin "import"; importAll("./test-funcs.expr"); six()`, int64(6), nil}, + /* 4 */ {`builtin "import"; import("./sample-export-all.expr"); six()`, int64(6), nil}, + } + + t.Setenv("EXPR_PATH", ".") + + // parserTestSpec(t, section, inputs, 69) + parserTest(t, section, inputs) +} diff --git a/t_func-math-arith_test.go b/t_func-math-arith_test.go new file mode 100644 index 0000000..12bd920 --- /dev/null +++ b/t_func-math-arith_test.go @@ -0,0 +1,29 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// t_func-math-arith_test.go +package expr + +import ( + "errors" + "testing" +) + +func TestFuncMathArith(t *testing.T) { + section := "Func-Math-Arith" + inputs := []inputType{ + /* 1 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil}, + /* 2 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil}, + /* 3 */ {`builtin "math.arith"; mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)}, + /* 4 */ {`builtin "math.arith"; add(1+4,3+2,5*(3-2))`, int64(15), nil}, + /* 5 */ {`builtin "math.arith"; add(add(1+4),3+2,5*(3-2))`, int64(15), nil}, + /* 6 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil}, + /* 7 */ {`builtin "math.arith"; a=5; b=2; add(a, b*3)`, int64(11), nil}, + /* 8 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil}, + } + + // t.Setenv("EXPR_PATH", ".") + + // parserTestSpec(t, section, inputs, 69) + parserTest(t, section, inputs) +} diff --git a/t_func-os_test.go b/t_func-os_test.go new file mode 100644 index 0000000..5867468 --- /dev/null +++ b/t_func-os_test.go @@ -0,0 +1,29 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// t_func-os_test.go +package expr + +import ( + "errors" + "testing" +) + +func TestFuncOs(t *testing.T) { + section := "Func-OS" + inputs := []inputType{ + /* 1 */ {`builtin "os.file"`, int64(1), nil}, + /* 2 */ {`builtin "os.file"; handle=openFile("/etc/hosts"); closeFile(handle)`, true, nil}, + /* 3 */ {`builtin "os.file"; handle=openFile("/etc/hostsX")`, nil, errors.New(`open /etc/hostsX: no such file or directory`)}, + /* 4 */ {`builtin "os.file"; handle=createFile("/tmp/dummy"); closeFile(handle)`, true, nil}, + /* 5 */ {`builtin "os.file"; handle=appendFile("/tmp/dummy"); writeFile(handle, "bye-bye"); closeFile(handle)`, true, nil}, + /* 6 */ {`builtin "os.file"; handle=openFile("/tmp/dummy"); word=readFile(handle, "-"); closeFile(handle);word`, "bye", nil}, + /* 7 */ {`builtin "os.file"; word=readFile(nil, "-")`, nil, errors.New(`readFileFunc(): invalid file handle`)}, + /* 7 */ {`builtin "os.file"; writeFile(nil, "bye")`, nil, errors.New(`writeFileFunc(): invalid file handle`)}, + } + + // t.Setenv("EXPR_PATH", ".") + + // parserTestSpec(t, section, inputs, 69) + parserTest(t, section, inputs) +} diff --git a/t_func-string_test.go b/t_func-string_test.go new file mode 100644 index 0000000..e2bfba5 --- /dev/null +++ b/t_func-string_test.go @@ -0,0 +1,69 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// t_func-string_test.go +package expr + +import ( + "errors" + "testing" +) + +func TestFuncString(t *testing.T) { + section := "Func-String" + + inputs := []inputType{ + /* 1 */ {`builtin "string"; joinStr("-", "one", "two", "three")`, "one-two-three", nil}, + /* 2 */ {`builtin "string"; joinStr("-", ["one", "two", "three"])`, "one-two-three", nil}, + /* 3 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr("-", ls)`, "one-two-three", nil}, + /* 4 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr(1, ls)`, nil, errors.New(`joinStr() the "separator" parameter must be a string, got a int64 (1)`)}, + /* 5 */ {`builtin "string"; ls= ["one", 2, "three"]; joinStr("-", ls)`, nil, errors.New(`joinStr() expected string, got int64 (2)`)}, + /* 6 */ {`builtin "string"; "<"+trimStr(" bye bye ")+">"`, "", nil}, + /* 7 */ {`builtin "string"; subStr("0123456789", 1,2)`, "12", nil}, + /* 8 */ {`builtin "string"; subStr("0123456789", -3,2)`, "78", nil}, + /* 9 */ {`builtin "string"; subStr("0123456789", -3)`, "789", nil}, + /* 10 */ {`builtin "string"; subStr("0123456789")`, "0123456789", nil}, + /* 11 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "012")`, true, nil}, + /* 12 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "0125")`, false, nil}, + /* 13 */ {`builtin "string"; startsWithStr("0123456789")`, nil, errors.New(`startsWithStr(): too few params -- expected 2 or more, got 1`)}, + /* 14 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "789")`, true, nil}, + /* 15 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "0125")`, false, nil}, + /* 16 */ {`builtin "string"; endsWithStr("0123456789")`, nil, errors.New(`endsWithStr(): too few params -- expected 2 or more, got 1`)}, + /* 17 */ {`builtin "string"; splitStr("one-two-three", "-", )`, newListA("one", "two", "three"), nil}, + /* 18 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got int64 (1)`)}, + /* 19 */ {`builtin "string"; joinStr()`, nil, errors.New(`joinStr(): too few params -- expected 1 or more, got 0`)}, + + /* 69 */ /*{`builtin "string"; $$global`, `vars: { + } + funcs: { + add(any=0 ...) -> number, + dec(any) -> decimal, + endsWithStr(source, suffix) -> boolean, + fract(any, denominator=1) -> fraction, + import( ...) -> any, + importAll( ...) -> any, + int(any) -> integer, + isBool(any) -> boolean, + isDec(any) -> boolean, + isDict(any) -> boolean, + isFloat(any) -> boolean, + isFract(any) -> boolean, + isInt(any) -> boolean, + isList(any) -> boolean, + isNil(any) -> boolean, + isString(any) -> boolean, + joinStr(separator, item="" ...) -> string, + mul(any=1 ...) -> number, + splitStr(source, separator="", count=-1) -> list of string, + startsWithStr(source, prefix) -> boolean, + subStr(source, start=0, count=-1) -> string, + trimStr(source) -> string + } + `, nil},*/ + } + + //t.Setenv("EXPR_PATH", ".") + + // parserTestSpec(t, "Func", inputs, 69) + parserTest(t, section, inputs) +} diff --git a/t_funcs_test.go b/t_funcs_test.go index 099d45d..3346da0 100644 --- a/t_funcs_test.go +++ b/t_funcs_test.go @@ -10,107 +10,48 @@ import ( ) func TestFuncs(t *testing.T) { + section := "Funcs" 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(`too much params -- expected 1, got 2`)}, - /* 11 */ {`int(nil)`, nil, errors.New(`int() can't convert 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 ")+">"`, "", 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 */ {`isInt(2+1)`, true, nil}, - /* 46 */ {`isInt(3.1)`, false, nil}, - /* 47 */ {`isFloat(3.1)`, true, nil}, - /* 48 */ {`isString("3.1")`, true, nil}, - /* 49 */ {`isString("3" + 1)`, true, nil}, - /* 50 */ {`isList(["3", 1])`, true, nil}, - /* 51 */ {`isDict({"a":"3", "b":1})`, true, nil}, - /* 52 */ {`isFract(1|3)`, true, nil}, - /* 53 */ {`isFract(3|1)`, false, nil}, - /* 54 */ {`isRational(3|1)`, true, nil}, - /* 55 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil}, - /* 56 */ {`fract("2.2(3)")`, newFraction(67, 30), nil}, - /* 57 */ {`fract("1.21(3)")`, newFraction(91, 75), nil}, - /* 58 */ {`fract(1.21(3))`, newFraction(91, 75), nil}, - /* 59 */ {`fract(1.21)`, newFraction(121, 100), nil}, - /* 60 */ {`dec(2)`, float64(2), nil}, - /* 61 */ {`dec(2.0)`, float64(2), nil}, - /* 62 */ {`dec("2.0")`, float64(2), nil}, - /* 63 */ {`dec(true)`, float64(1), nil}, - /* 64 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")}, - /* 65 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got int64 (1)`)}, - /* 66 */ {`dec()`, nil, errors.New(`too few params -- expected 1, got 0`)}, - /* 67 */ {`dec(1,2,3)`, nil, errors.New(`too much params -- expected 1, got 3`)}, - /* 68 */ {`builtin "string"; joinStr()`, nil, errors.New(`too few params -- expected 1 or more, got 0`)}, - /* 69 */ /*{`builtin "string"; $$global`, `vars: { - - } - funcs: { - add(any=0 ...) -> number, - dec(any) -> decimal, - endsWithStr(source, suffix) -> boolean, - fract(any, denominator=1) -> fraction, - import( ...) -> any, - importAll( ...) -> any, - int(any) -> integer, - isBool(any) -> boolean, - isDec(any) -> boolean, - isDict(any) -> boolean, - isFloat(any) -> boolean, - isFract(any) -> boolean, - isInt(any) -> boolean, - isList(any) -> boolean, - isNil(any) -> boolean, - isString(any) -> boolean, - joinStr(separator, item="" ...) -> string, - mul(any=1 ...) -> number, - splitStr(source, separator="", count=-1) -> list of string, - startsWithStr(source, prefix) -> boolean, - subStr(source, start=0, count=-1) -> string, - trimStr(source) -> string - } - `, nil},*/ + /* 1 */ {`two=func(){2}; two()`, int64(2), nil}, + /* 2 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil}, + /* 3 */ {`double=func(x){2*x}; double(3)`, int64(6), nil}, + /* 4 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil}, + /* 5 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil}, + /* 6 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)}, + /* 7 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil}, + /* 8 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil}, + /* 9 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)}, + /* 10 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil}, + /* 11 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil}, + /* 12 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil}, + /* 13 */ {`two=func(){2}; four=func(f){f()+f()}; four(two)`, int64(4), nil}, + /* 14 */ {`two=func(){2}; two(123)`, nil, errors.New(`two(): too much params -- expected 0, got 1`)}, } t.Setenv("EXPR_PATH", ".") - // parserTestSpec(t, "Func", inputs, 69) - parserTest(t, "Func", inputs) + // parserTestSpec(t, section, inputs, 69) + parserTest(t, section, inputs) +} + +func dummy(ctx ExprContext, name string, args []any) (result any, err error) { + return +} + +func TestFunctionToStringSimple(t *testing.T) { + source := newGolangFunctor(dummy) + want := "func() {}" + got := source.ToString(0) + if got != want { + t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want) + } +} + +func TestFunctionGetFunc(t *testing.T) { + source := newGolangFunctor(dummy) + want := ExprFunc(nil) + got := source.GetFunc() + if got != want { + t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want) + } } diff --git a/t_list_test.go b/t_list_test.go index 26fc48b..69b3bf2 100644 --- a/t_list_test.go +++ b/t_list_test.go @@ -46,6 +46,12 @@ func TestListParser(t *testing.T) { /* 24 */ {`["a","b","c","d"][1]`, "b", nil}, /* 25 */ {`["a","b","c","d"][1,1]`, nil, errors.New(`[1:19] one index only is allowed`)}, /* 26 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil}, + /* 27 */ {`["a", "b", "c"] << ;`, nil, errors.New(`[1:18] infix operator "<<" requires two non-nil operands, got 1`)}, + /* 28 */ {`2 << 3;`, nil, errors.New(`[1:4] left operand '2' [integer] and right operand '3' [integer] are not compatible with operator "<<"`)}, + /* 29 */ {`but >> ["a", "b", "c"]`, nil, errors.New(`[1:6] infix operator ">>" requires two non-nil operands, got 0`)}, + /* 30 */ {`2 >> 3;`, nil, errors.New(`[1:4] left operand '2' [integer] and right operand '3' [integer] are not compatible with operator ">>"`)}, + /* 31 */ {`a=[1,2]; a<<3`, []any{1, 2, 3}, nil}, + /* 33 */ {`a=[1,2]; 5>>a`, []any{5, 1, 2}, 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}, diff --git a/t_module-register_test.go b/t_module-register_test.go new file mode 100644 index 0000000..3e4176c --- /dev/null +++ b/t_module-register_test.go @@ -0,0 +1,31 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// t_module-register_test.go +package expr + +import ( + "testing" +) + +func TestIterateModules(t *testing.T) { + section := "Module-Register" + + mods := make([]string, 0, 100) + IterateModules(func(name, description string, imported bool) bool { + mods = append(mods, name) + return true + }) + + t.Logf("%s -- IterateModules(): %v", section, mods) + if len(mods) == 0 { + t.Errorf("Module-List: got-length zero, expected-length greater than zero") + } + + // IterateModules(func(name, description string, imported bool) bool { + // return false + // }) + // if len(mods) > 0 { + // t.Errorf("Module-List: got-length greater than zero, expected-length zero") + // } +} diff --git a/t_parser_test.go b/t_parser_test.go index c2ab827..e8dd3b7 100644 --- a/t_parser_test.go +++ b/t_parser_test.go @@ -138,28 +138,10 @@ func TestGeneralParser(t *testing.T) { /* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)}, /* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)}, /* 119 */ {`{}`, &DictType{}, 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}, - /* 140 */ {`1.2()`, newFraction(6, 5), nil}, - /* 141 */ {`1|(2-2)`, nil, errors.New(`division by zero`)}, + /* 120 */ {`v=10; v++; v`, int64(11), nil}, + /* 121 */ {`1+1|2+0.5`, float64(2), nil}, + /* 122 */ {`1.2()`, newFraction(6, 5), nil}, + /* 123 */ {`1|(2-2)`, nil, errors.New(`division by zero`)}, } // t.Setenv("EXPR_PATH", ".") diff --git a/t_utils_test.go b/t_utils_test.go index 383d2e5..88bade9 100644 --- a/t_utils_test.go +++ b/t_utils_test.go @@ -54,6 +54,14 @@ func TestIsString(t *testing.T) { succeeded++ } + count++ + if isIterator("fake") { + t.Errorf(`%d: isIterator("fake") -> result = true, want false`, count) + failed++ + } else { + succeeded++ + } + count++ b, ok := toBool(true) if !(ok && b) { @@ -72,6 +80,15 @@ func TestIsString(t *testing.T) { succeeded++ } + count++ + b, ok = toBool([]int{}) + if ok { + t.Errorf("%d: toBool([]) b=%v, ok=%v -> result = true, want false", count, b, ok) + failed++ + } else { + succeeded++ + } + t.Logf("test count: %d, succeeded count: %d, failed count: %d", count, succeeded, failed) }