diff --git a/context.go b/context.go index e004775..5f9c178 100644 --- a/context.go +++ b/context.go @@ -25,6 +25,7 @@ type exprFunc interface { Name() string MinArgs() int MaxArgs() int + Functor() Functor } // ----Expression Context @@ -33,6 +34,7 @@ type exprContext interface { GetVar(varName string) (value any, exists bool) SetVar(varName string, value any) EnumVars(func(name string) (accept bool)) (varNames []string) + EnumFuncs(func(name string) (accept bool)) (funcNames []string) GetFuncInfo(name string) exprFunc Call(name string, args []any) (result any, err error) RegisterFunc(name string, f Functor, minArgs, maxArgs int) diff --git a/func-import.go b/func-import.go index e1cab80..01d75e2 100644 --- a/func-import.go +++ b/func-import.go @@ -104,9 +104,14 @@ func doImport(ctx exprContext, name string, dirList []string, it Iterator) (resu var expr *ast scanner := NewScanner(file, DefaultTranslations()) parser := NewParser(ctx) - if expr, err = parser.parse(scanner); err == nil { + if expr, err = parser.parseGeneral(scanner, true, true); err == nil { _, err = expr.Eval(ctx, false) } + if err != nil { + break + } + } else { + break } } if err != nil && err == io.EOF { diff --git a/operand-func.go b/operand-func.go index 970642e..614957d 100644 --- a/operand-func.go +++ b/operand-func.go @@ -36,10 +36,17 @@ func evalFuncCall(parentCtx exprContext, self *term) (v any, err error) { } if err == nil { if v, err = ctx.Call(name, params); err == nil { + // Export variables for _, refName := range ctx.EnumVars(func(name string) bool { return name[0] == '@' }) { refValue, _ := ctx.GetVar(refName) parentCtx.SetVar(refName[1:], refValue) } + // Export functions + for _, refName := range ctx.EnumFuncs(func(name string) bool { return name[0] == '@' }) { + if info := ctx.GetFuncInfo(refName); info != nil { + parentCtx.RegisterFunc(refName[1:], info.Functor(), info.MinArgs(), info.MaxArgs()) + } + } } } return diff --git a/parser_test.go b/parser_test.go index 25103b2..303f26f 100644 --- a/parser_test.go +++ b/parser_test.go @@ -139,11 +139,11 @@ func TestParser(t *testing.T) { /* 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}, - /* 120 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil}, - /* 121 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable "x"`)}, - /* 122 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil}, - /* 123 */ {`f=func(@y){g=func(){@x=5}; g(); @z=@x; @y=@y+@z}; f(2); y+z`, int64(12), nil}, - // TODO Fix this: /* 124 */ {`f=func(@y){g=func(){@x=5}; g(); @z=@x; @y=@y+@z}; f(2); y+x`, nil, errors.New(`undefined variable "x"`)}, + /* 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 "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}, } check_env_expr_path := 113 @@ -151,6 +151,7 @@ func TestParser(t *testing.T) { failed := 0 // inputs1 := []inputType{ + // {`f=func(@y){g=func(){@x=5}; g(); @z=x}; f(2)`, int64(5), nil}, // {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil}, // {`import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil}, // {`add(1,2,3)`, int64(6), nil}, @@ -188,7 +189,7 @@ func TestParser(t *testing.T) { 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) + t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr) good = false } } @@ -205,7 +206,7 @@ func TestParser(t *testing.T) { t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed)) } -func NoTestListParser(t *testing.T) { +func TestListParser(t *testing.T) { type inputType struct { source string wantResult any @@ -261,7 +262,6 @@ func NoTestListParser(t *testing.T) { good = false } - // if good {} if gotList, okGot := gotResult.([]any); okGot { if wantList, okWant := input.wantResult.([]any); okWant { if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) { diff --git a/simple-func-store.go b/simple-func-store.go index 298ffa6..b55a10d 100644 --- a/simple-func-store.go +++ b/simple-func-store.go @@ -5,13 +5,14 @@ import "fmt" type SimpleFuncStore struct { varStore map[string]any - funcStore map[string]Functor + funcStore map[string]*funcInfo } type funcInfo struct { name string - // minArgs int - // maxArgs int + minArgs int + maxArgs int + functor Functor } func (info *funcInfo) Name() string { @@ -19,17 +20,21 @@ func (info *funcInfo) Name() string { } func (info *funcInfo) MinArgs() int { - return 0 + return info.minArgs } func (info *funcInfo) MaxArgs() int { - return -1 + return info.maxArgs +} + +func (info *funcInfo) Functor() Functor { + return info.functor } func NewSimpleFuncStore() *SimpleFuncStore { return &SimpleFuncStore{ varStore: make(map[string]any), - funcStore: make(map[string]Functor), + funcStore: make(map[string]*funcInfo), } } @@ -63,20 +68,32 @@ func (ctx *SimpleFuncStore) EnumVars(acceptor func(name string) (accept bool)) ( return } -func (ctx *SimpleFuncStore) GetFuncInfo(name string) (f exprFunc) { - var exists bool - if _, exists = ctx.funcStore[name]; exists { - f = &funcInfo{name: name} - } +func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info exprFunc) { + info, _ = ctx.funcStore[name] return } func (ctx *SimpleFuncStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) { - ctx.funcStore[name] = functor + ctx.funcStore[name] = &funcInfo{name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor} +} + +func (ctx *SimpleFuncStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) { + funcNames = make([]string, 0) + for name := range ctx.funcStore { + if acceptor != nil { + if acceptor(name) { + funcNames = append(funcNames, name) + } + } else { + funcNames = append(funcNames, name) + } + } + return } func (ctx *SimpleFuncStore) Call(name string, args []any) (result any, err error) { - if functor, exists := ctx.funcStore[name]; exists { + if info, exists := ctx.funcStore[name]; exists { + functor := info.functor result, err = functor.Invoke(ctx, name, args) } else { err = fmt.Errorf("unknown function %s()", name) diff --git a/simple-var-store.go b/simple-var-store.go index d1dad03..f04ce09 100644 --- a/simple-var-store.go +++ b/simple-var-store.go @@ -51,3 +51,7 @@ func (ctx *SimpleVarStore) Call(name string, args []any) (result any, err error) func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) { } + +func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) { + return +} diff --git a/test-funcs.expr b/test-funcs.expr index e482e91..99c42c6 100644 --- a/test-funcs.expr +++ b/test-funcs.expr @@ -3,10 +3,10 @@ */ // double(x): returns 2*x -double=func(x){2*x}; +@double=func(x){2*x}; // Define variable 'a' wth value 5 -a=5; +@a=5; // two(): returns 2 -two=func() {2}; +@two=func() {2};