From 4683a08da23c4bfc5ff29f4789765654553b9d2b Mon Sep 17 00:00:00 2001 From: Celestino Amoroso Date: Tue, 2 Apr 2024 06:49:16 +0200 Subject: [PATCH] import() function added --- func-import.go | 120 ++++++++++++++++++++++++++++++++++++++++++++++++ func-math.go | 32 ------------- parser_test.go | 23 ++++++---- preset.go | 15 ++++++ test-funcs.expr | 12 +++++ 5 files changed, 162 insertions(+), 40 deletions(-) create mode 100644 func-import.go create mode 100644 test-funcs.expr diff --git a/func-import.go b/func-import.go new file mode 100644 index 0000000..e1cab80 --- /dev/null +++ b/func-import.go @@ -0,0 +1,120 @@ +// func-import.go +package expr + +import ( + "errors" + "fmt" + "io" + "os" + "path" + "path/filepath" + "strings" +) + +const ENV_EXPR_PATH = "EXPR_PATH" + +func importFunc(ctx exprContext, name string, args []any) (result any, err error) { + var dirList []string + + dirList = addEnvImportDirs(dirList) + dirList = addPresetImportDirs(ctx, dirList) + result, err = doImport(ctx, name, dirList, NewFlatArrayIterator(args)) + return +} + +func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) { + if !(isString(paramValue) /*|| isList(paramValue)*/) { + err = fmt.Errorf("%s(): param nr %d has wrong type %T, string expected", funcName, paramPos+1, paramValue) + } + return +} + +func addEnvImportDirs(dirList []string) []string { + if dirSpec, exists := os.LookupEnv(ENV_EXPR_PATH); exists { + dirs := strings.Split(dirSpec, ":") + if dirList == nil { + dirList = dirs + } else { + dirList = append(dirList, dirs...) + } + } + return dirList +} + +func addPresetImportDirs(ctx exprContext, dirList []string) []string { + if dirSpec, exists := getPresetString(ctx, preset_import_path); exists { + dirs := strings.Split(dirSpec, ":") + if dirList == nil { + dirList = dirs + } else { + dirList = append(dirList, dirs...) + } + } + return dirList +} + +func isFile(filePath string) bool { + info, err := os.Stat(filePath) + return (err == nil || errors.Is(err, os.ErrExist)) && info.Mode().IsRegular() +} + +func searchAmongPath(filename string, dirList []string) (filePath string) { + for _, dir := range dirList { + if fullPath := path.Join(dir, filename); isFile(fullPath) { + filePath = fullPath + break + } + } + return +} + +func isPathRelative(filePath string) bool { + unixPath := filepath.ToSlash(filePath) + return strings.HasPrefix(unixPath, "./") || strings.HasPrefix(unixPath, "../") +} + +func makeFilepath(filename string, dirList []string) (filePath string, err error) { + if path.IsAbs(filename) || isPathRelative(filename) { + if isFile(filename) { + filePath = filename + } + } else { + filePath = searchAmongPath(filename, dirList) + } + if len(filePath) == 0 { + err = fmt.Errorf("source file %q not found", filename) + } + return +} + +func doImport(ctx exprContext, name string, dirList []string, it Iterator) (result any, err error) { + var v any + var sourceFilepath string + + for v, err = it.Next(); err == nil; v, err = it.Next() { + if err = checkStringParamExpected(name, v, it.Index()); err != nil { + break + } + if sourceFilepath, err = makeFilepath(v.(string), dirList); err != nil { + break + } + var file *os.File + if file, err = os.Open(sourceFilepath); err == nil { + defer file.Close() + var expr *ast + scanner := NewScanner(file, DefaultTranslations()) + parser := NewParser(ctx) + if expr, err = parser.parse(scanner); err == nil { + _, err = expr.Eval(ctx, false) + } + } + } + if err != nil && err == io.EOF { + err = nil + } + return +} + +func importImportFunc(ctx exprContext) { + ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1) +} diff --git a/func-math.go b/func-math.go index 121dd51..9d2333a 100644 --- a/func-math.go +++ b/func-math.go @@ -6,38 +6,6 @@ import ( "io" ) -// func addFunc(ctx exprContext, name string, args []any) (result any, err error) { -// var sumAsFloat = false -// var floatSum float64 = 0.0 -// var intSum int64 = 0 - -// for i, v := range args { -// if !isNumber(v) { -// err = fmt.Errorf("add(): param nr %d has wrong type %T, number expected", i+1, v) -// break -// } - -// if !sumAsFloat && isFloat(v) { -// sumAsFloat = true -// floatSum = float64(intSum) -// } -// if sumAsFloat { -// floatSum += numAsFloat(v) -// } else { -// iv, _ := v.(int64) -// intSum += iv -// } -// } -// if err == nil { -// if sumAsFloat { -// result = floatSum -// } else { -// result = intSum -// } -// } -// return -// } - func checkNumberParamExpected(funcName string, paramValue any, paramPos int) (err error) { if !(isNumber(paramValue) || isList(paramValue)) { err = fmt.Errorf("%s(): param nr %d has wrong type %T, number expected", funcName, paramPos+1, paramValue) diff --git a/parser_test.go b/parser_test.go index 31f8649..985e6a7 100644 --- a/parser_test.go +++ b/parser_test.go @@ -130,11 +130,16 @@ func TestParser(t *testing.T) { /* 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}, } + check_env_expr_path := 113 + succeeded := 0 failed := 0 // inputs1 := []inputType{ + // {`import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil}, // {`add(1,2,3)`, int64(6), nil}, // {`a=5; a`, int64(5), nil}, // {`a=5; b=2; add(a, b*3)`, int64(11), nil}, @@ -150,6 +155,7 @@ func TestParser(t *testing.T) { ctx.SetValue("var1", int64(123)) ctx.SetValue("var2", "abc") importMathFuncs(ctx) + importImportFunc(ctx) parser := NewParser(ctx) logTest(t, i+1, input.source, input.wantResult, input.wantErr) @@ -178,6 +184,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 environment variable with value "."`, check_env_expr_path) + } } } t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed)) @@ -190,13 +199,6 @@ func TestListParser(t *testing.T) { wantErr error } - // ctx := newTestContext() - ctx := NewSimpleFuncStore() - ctx.SetValue("var1", int64(123)) - ctx.SetValue("var2", "abc") - // ctx.addFunc("add", addFunc) - importMathFuncs(ctx) - // inputs1 := []inputType{ // {`add(1,2,3)`, int64(6), nil}, // } @@ -220,12 +222,17 @@ func TestListParser(t *testing.T) { succeeded := 0 failed := 0 - parser := NewParser(ctx) for i, input := range inputs { var expr *ast var gotResult any var gotErr error + ctx := NewSimpleFuncStore() + ctx.SetValue("var1", int64(123)) + ctx.SetValue("var2", "abc") + importMathFuncs(ctx) + parser := NewParser(ctx) + logTest(t, i+1, input.source, input.wantResult, input.wantErr) r := strings.NewReader(input.source) diff --git a/preset.go b/preset.go index d64ec88..e4804db 100644 --- a/preset.go +++ b/preset.go @@ -7,10 +7,17 @@ import "strings" const ( preset_last_result = "_last" preset_bool_shortcut = "_bool_shortcut" + preset_import_path = "_import_path" +) + +// Initial values +const ( + init_import_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr" ) func initDefaultVars(ctx exprContext) { ctx.SetValue(preset_bool_shortcut, true) + ctx.SetValue(preset_import_path, init_import_path) } func enable(ctx exprContext, name string) { @@ -37,3 +44,11 @@ func isEnabled(ctx exprContext, name string) (status bool) { } return } + +func getPresetString(ctx exprContext, name string) (s string, exists bool) { + var v any + if v, exists = ctx.GetValue(name); exists { + s, exists = v.(string) + } + return +} diff --git a/test-funcs.expr b/test-funcs.expr new file mode 100644 index 0000000..e482e91 --- /dev/null +++ b/test-funcs.expr @@ -0,0 +1,12 @@ +/* + test-funcs.expr: example source file +*/ + +// double(x): returns 2*x +double=func(x){2*x}; + +// Define variable 'a' wth value 5 +a=5; + +// two(): returns 2 +two=func() {2};