From 3aee1976234f9cdc707a6c5be1ef71f62b06e7c5 Mon Sep 17 00:00:00 2001 From: xrstf Date: Sun, 24 Dec 2023 15:53:32 +0100 Subject: [PATCH] refactor eval package into runtime struct to reduce static dependency on the eval package (closes #5) --- aliases.go | 13 +- cmd/rudi/cmd/console/command.go | 2 +- cmd/rudi/util/context.go | 4 +- pkg/builtin/coalesce/functions.go | 4 +- pkg/builtin/compare/functions.go | 4 +- pkg/builtin/compare/functions_test.go | 13 +- pkg/builtin/core/functions.go | 40 +-- pkg/builtin/core/functions_test.go | 2 +- pkg/builtin/datetime/functions.go | 4 +- pkg/builtin/encoding/functions.go | 4 +- pkg/builtin/hashing/functions.go | 4 +- pkg/builtin/lists/functions.go | 25 +- pkg/builtin/logic/functions.go | 9 +- pkg/builtin/math/functions.go | 4 +- pkg/builtin/meta.go | 2 +- pkg/builtin/rudifunc/functions.go | 9 +- pkg/builtin/strings/functions.go | 4 +- pkg/builtin/types/functions.go | 4 +- pkg/docs/module.go | 2 +- pkg/eval/eval.go | 13 - pkg/eval/eval_expression.go | 38 --- pkg/eval/eval_statement.go | 13 - pkg/eval/types/context.go | 288 ------------------ .../functions/args_consumer.go | 11 +- .../functions/args_consumer_test.go | 28 +- .../functions/args_matcher.go | 2 +- .../functions/args_matcher_test.go | 8 +- pkg/{eval => runtime}/functions/builder.go | 2 +- pkg/{eval => runtime}/functions/expression.go | 5 +- pkg/{eval => runtime}/functions/form.go | 2 +- pkg/{eval => runtime}/functions/form_test.go | 8 +- pkg/{eval => runtime}/functions/function.go | 7 +- .../interpreter}/eval_bool.go | 6 +- pkg/runtime/interpreter/eval_expression.go | 38 +++ .../interpreter}/eval_identifier.go | 6 +- .../interpreter}/eval_null.go | 6 +- .../interpreter}/eval_number.go | 6 +- .../interpreter}/eval_object.go | 23 +- .../interpreter}/eval_program.go | 9 +- .../interpreter}/eval_shim.go | 6 +- pkg/runtime/interpreter/eval_statement.go | 13 + .../interpreter}/eval_string.go | 6 +- .../interpreter}/eval_symbol.go | 24 +- .../interpreter}/eval_tuple.go | 39 +-- .../interpreter}/eval_vector.go | 25 +- pkg/runtime/interpreter/interpreter.go | 16 + .../interpreter}/test/bool_test.go | 0 .../interpreter}/test/expression_test.go | 0 .../interpreter}/test/funcs.go | 7 +- .../interpreter}/test/identifier_test.go | 0 .../interpreter}/test/null_test.go | 0 .../interpreter}/test/number_test.go | 0 .../interpreter}/test/object_test.go | 0 .../interpreter}/test/program_test.go | 0 .../interpreter}/test/statement_test.go | 0 .../interpreter}/test/string_test.go | 0 .../interpreter}/test/symbol_test.go | 2 +- .../interpreter}/test/tuple_test.go | 2 +- .../interpreter}/test/vector_test.go | 0 .../pathexpr/eval.go} | 44 +-- pkg/runtime/types/context.go | 140 +++++++++ pkg/runtime/types/document.go | 22 ++ pkg/runtime/types/functions.go | 104 +++++++ pkg/runtime/types/runtime.go | 25 ++ pkg/runtime/types/variables.go | 49 +++ pkg/testutil/testcase.go | 22 +- program.go | 9 +- 67 files changed, 650 insertions(+), 577 deletions(-) delete mode 100644 pkg/eval/eval.go delete mode 100644 pkg/eval/eval_expression.go delete mode 100644 pkg/eval/eval_statement.go delete mode 100644 pkg/eval/types/context.go rename pkg/{eval => runtime}/functions/args_consumer.go (96%) rename pkg/{eval => runtime}/functions/args_consumer_test.go (83%) rename pkg/{eval => runtime}/functions/args_matcher.go (99%) rename pkg/{eval => runtime}/functions/args_matcher_test.go (97%) rename pkg/{eval => runtime}/functions/builder.go (96%) rename pkg/{eval => runtime}/functions/expression.go (89%) rename pkg/{eval => runtime}/functions/form.go (97%) rename pkg/{eval => runtime}/functions/form_test.go (93%) rename pkg/{eval => runtime}/functions/function.go (91%) rename pkg/{eval => runtime/interpreter}/eval_bool.go (51%) create mode 100644 pkg/runtime/interpreter/eval_expression.go rename pkg/{eval => runtime/interpreter}/eval_identifier.go (55%) rename pkg/{eval => runtime/interpreter}/eval_null.go (50%) rename pkg/{eval => runtime/interpreter}/eval_number.go (50%) rename pkg/{eval => runtime/interpreter}/eval_object.go (71%) rename pkg/{eval => runtime/interpreter}/eval_program.go (67%) rename pkg/{eval => runtime/interpreter}/eval_shim.go (51%) create mode 100644 pkg/runtime/interpreter/eval_statement.go rename pkg/{eval => runtime/interpreter}/eval_string.go (50%) rename pkg/{eval => runtime/interpreter}/eval_symbol.go (54%) rename pkg/{eval => runtime/interpreter}/eval_tuple.go (71%) rename pkg/{eval => runtime/interpreter}/eval_vector.go (54%) create mode 100644 pkg/runtime/interpreter/interpreter.go rename pkg/{eval => runtime/interpreter}/test/bool_test.go (100%) rename pkg/{eval => runtime/interpreter}/test/expression_test.go (100%) rename pkg/{eval => runtime/interpreter}/test/funcs.go (84%) rename pkg/{eval => runtime/interpreter}/test/identifier_test.go (100%) rename pkg/{eval => runtime/interpreter}/test/null_test.go (100%) rename pkg/{eval => runtime/interpreter}/test/number_test.go (100%) rename pkg/{eval => runtime/interpreter}/test/object_test.go (100%) rename pkg/{eval => runtime/interpreter}/test/program_test.go (100%) rename pkg/{eval => runtime/interpreter}/test/statement_test.go (100%) rename pkg/{eval => runtime/interpreter}/test/string_test.go (100%) rename pkg/{eval => runtime/interpreter}/test/symbol_test.go (98%) rename pkg/{eval => runtime/interpreter}/test/tuple_test.go (99%) rename pkg/{eval => runtime/interpreter}/test/vector_test.go (100%) rename pkg/{eval/eval_path_expression.go => runtime/pathexpr/eval.go} (72%) create mode 100644 pkg/runtime/types/context.go create mode 100644 pkg/runtime/types/document.go create mode 100644 pkg/runtime/types/functions.go create mode 100644 pkg/runtime/types/runtime.go create mode 100644 pkg/runtime/types/variables.go diff --git a/aliases.go b/aliases.go index 781512a..b31b385 100644 --- a/aliases.go +++ b/aliases.go @@ -8,8 +8,9 @@ import ( "go.xrstf.de/rudi/pkg/builtin" "go.xrstf.de/rudi/pkg/coalescing" - "go.xrstf.de/rudi/pkg/eval/functions" - "go.xrstf.de/rudi/pkg/eval/types" + "go.xrstf.de/rudi/pkg/runtime/functions" + "go.xrstf.de/rudi/pkg/runtime/interpreter" + "go.xrstf.de/rudi/pkg/runtime/types" ) // Context is the evaluation context for a Rudi program, consisting of @@ -39,8 +40,12 @@ type Document = types.Document type Coalescer = coalescing.Coalescer // NewContext wraps the document, variables and functions into a Context. -func NewContext(ctx context.Context, doc Document, variables Variables, funcs Functions, coalescer Coalescer) Context { - return types.NewContext(ctx, doc, variables, funcs, coalescer) +func NewContext(runtime types.Runtime, ctx context.Context, doc Document, variables Variables, funcs Functions, coalescer Coalescer) (Context, error) { + if runtime == nil { + runtime = interpreter.New() + } + + return types.NewContext(runtime, ctx, doc, variables, funcs, coalescer) } // NewFunctions returns an empty set of runtime functions. diff --git a/cmd/rudi/cmd/console/command.go b/cmd/rudi/cmd/console/command.go index 29d6bc2..c358b78 100644 --- a/cmd/rudi/cmd/console/command.go +++ b/cmd/rudi/cmd/console/command.go @@ -13,7 +13,7 @@ import ( "go.xrstf.de/rudi/cmd/rudi/docs" "go.xrstf.de/rudi/cmd/rudi/options" "go.xrstf.de/rudi/cmd/rudi/util" - "go.xrstf.de/rudi/pkg/eval/types" + "go.xrstf.de/rudi/pkg/runtime/types" colorjson "github.com/TylerBrock/colorjson" "github.com/chzyer/readline" diff --git a/cmd/rudi/util/context.go b/cmd/rudi/util/context.go index 945b2e3..9595bdf 100644 --- a/cmd/rudi/util/context.go +++ b/cmd/rudi/util/context.go @@ -11,6 +11,7 @@ import ( "go.xrstf.de/rudi/cmd/rudi/options" "go.xrstf.de/rudi/cmd/rudi/types" "go.xrstf.de/rudi/pkg/coalescing" + "go.xrstf.de/rudi/pkg/runtime/interpreter" ) func SetupRudiContext(opts *options.Options, fileNames []string, fileContents []any) (rudi.Context, error) { @@ -68,6 +69,5 @@ func SetupRudiContext(opts *options.Options, fileNames []string, fileContents [] // No context set here, caller is expected to provide their own (the Rudi context is re-used // in the console, but the Go context should not be, hence the separation). - //nolint:staticcheck - return rudi.NewContext(nil, document, vars, funcs, coalescer), nil + return rudi.NewContext(interpreter.New(), nil, document, vars, funcs, coalescer) } diff --git a/pkg/builtin/coalesce/functions.go b/pkg/builtin/coalesce/functions.go index 0a36f6c..2ccd474 100644 --- a/pkg/builtin/coalesce/functions.go +++ b/pkg/builtin/coalesce/functions.go @@ -6,8 +6,8 @@ package coalesce import ( "go.xrstf.de/rudi/pkg/builtin/core" "go.xrstf.de/rudi/pkg/coalescing" - "go.xrstf.de/rudi/pkg/eval/functions" - "go.xrstf.de/rudi/pkg/eval/types" + "go.xrstf.de/rudi/pkg/runtime/functions" + "go.xrstf.de/rudi/pkg/runtime/types" ) var ( diff --git a/pkg/builtin/compare/functions.go b/pkg/builtin/compare/functions.go index 4f86ad3..7470582 100644 --- a/pkg/builtin/compare/functions.go +++ b/pkg/builtin/compare/functions.go @@ -8,8 +8,8 @@ import ( "go.xrstf.de/rudi/pkg/coalescing" "go.xrstf.de/rudi/pkg/equality" - "go.xrstf.de/rudi/pkg/eval/functions" - "go.xrstf.de/rudi/pkg/eval/types" + "go.xrstf.de/rudi/pkg/runtime/functions" + "go.xrstf.de/rudi/pkg/runtime/types" ) var ( diff --git a/pkg/builtin/compare/functions_test.go b/pkg/builtin/compare/functions_test.go index 63bcbcc..4bf4874 100644 --- a/pkg/builtin/compare/functions_test.go +++ b/pkg/builtin/compare/functions_test.go @@ -8,7 +8,8 @@ import ( "fmt" "testing" - "go.xrstf.de/rudi/pkg/eval/types" + "go.xrstf.de/rudi/pkg/runtime/interpreter" + "go.xrstf.de/rudi/pkg/runtime/types" "go.xrstf.de/rudi/pkg/testutil" ) @@ -391,7 +392,10 @@ func TestInvalidComparisonFunctions(t *testing.T) { gteFunction, } - ctx := types.NewContext(context.Background(), types.Document{}, nil, nil, nil) + ctx, err := types.NewContext(interpreter.New(), context.Background(), types.Document{}, nil, nil, nil) + if err != nil { + t.Fatalf("Failed to create context: %v", err) + } for _, tc := range testcases { for _, f := range funcs { @@ -463,7 +467,10 @@ func TestComparisonFunctions(t *testing.T) { }, } - ctx := types.NewContext(context.Background(), types.Document{}, nil, nil, nil) + ctx, err := types.NewContext(interpreter.New(), context.Background(), types.Document{}, nil, nil, nil) + if err != nil { + t.Fatalf("Failed to create context: %v", err) + } for _, tc := range testcases { t.Run("", func(t *testing.T) { diff --git a/pkg/builtin/core/functions.go b/pkg/builtin/core/functions.go index 172049e..af9da60 100644 --- a/pkg/builtin/core/functions.go +++ b/pkg/builtin/core/functions.go @@ -10,11 +10,11 @@ import ( "go.xrstf.de/rudi/pkg/coalescing" "go.xrstf.de/rudi/pkg/deepcopy" - "go.xrstf.de/rudi/pkg/eval" - "go.xrstf.de/rudi/pkg/eval/functions" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" - "go.xrstf.de/rudi/pkg/pathexpr" + genericpathexpr "go.xrstf.de/rudi/pkg/pathexpr" + "go.xrstf.de/rudi/pkg/runtime/functions" + "go.xrstf.de/rudi/pkg/runtime/pathexpr" + "go.xrstf.de/rudi/pkg/runtime/types" ) var ( @@ -45,7 +45,7 @@ func keepContextCanceled(err error) error { func ifFunction(ctx types.Context, test bool, yes ast.Expression) (any, error) { if test { - _, result, err := eval.EvalExpression(ctx, yes) + _, result, err := ctx.Runtime().EvalExpression(ctx, yes) return result, err } @@ -54,11 +54,11 @@ func ifFunction(ctx types.Context, test bool, yes ast.Expression) (any, error) { func ifElseFunction(ctx types.Context, test bool, yes, no ast.Expression) (any, error) { if test { - _, result, err := eval.EvalExpression(ctx, yes) + _, result, err := ctx.Runtime().EvalExpression(ctx, yes) return result, err } - _, result, err := eval.EvalExpression(ctx, no) + _, result, err := ctx.Runtime().EvalExpression(ctx, no) return result, err } @@ -73,7 +73,7 @@ func DoFunction(ctx types.Context, args ...ast.Expression) (any, error) { ) for _, arg := range args { - tupleCtx, result, err = eval.EvalExpression(tupleCtx, arg) + tupleCtx, result, err = ctx.Runtime().EvalExpression(tupleCtx, arg) if err != nil { return nil, err } @@ -130,18 +130,18 @@ func hasFunction(ctx types.Context, arg ast.Expression) (any, error) { } // pre-evaluate the path - evaluatedPath, err := eval.EvalPathExpression(ctx, pathExpr) + evaluatedPath, err := pathexpr.Eval(ctx, pathExpr) if err != nil { return nil, fmt.Errorf("invalid path expression: %w", err) } // evaluate the base value - _, value, err := eval.EvalExpression(ctx, expr) + _, value, err := ctx.Runtime().EvalExpression(ctx, expr) if err != nil { return nil, err } - _, err = eval.TraverseEvaluatedPathExpression(value, *evaluatedPath) + _, err = pathexpr.Traverse(value, *evaluatedPath) if err != nil { return false, keepContextCanceled(err) } @@ -161,7 +161,7 @@ func defaultFunction(ctx types.Context, value any, fallback ast.Expression) (any return value, nil } - _, value, err = eval.EvalExpression(ctx, fallback) + _, value, err = ctx.Runtime().EvalExpression(ctx, fallback) if err != nil { return nil, fmt.Errorf("argument #1: %w", err) } @@ -170,7 +170,7 @@ func defaultFunction(ctx types.Context, value any, fallback ast.Expression) (any } func tryFunction(ctx types.Context, test ast.Expression) (any, error) { - _, result, err := eval.EvalExpression(ctx, test) + _, result, err := ctx.Runtime().EvalExpression(ctx, test) if err != nil { return nil, keepContextCanceled(err) } @@ -179,9 +179,9 @@ func tryFunction(ctx types.Context, test ast.Expression) (any, error) { } func tryWithFallbackFunction(ctx types.Context, test ast.Expression, fallback ast.Expression) (any, error) { - _, result, err := eval.EvalExpression(ctx, test) + _, result, err := ctx.Runtime().EvalExpression(ctx, test) if err != nil { - _, result, err = eval.EvalExpression(ctx, fallback) + _, result, err = ctx.Runtime().EvalExpression(ctx, fallback) if err != nil { return nil, fmt.Errorf("argument #1: %w", err) } @@ -206,7 +206,7 @@ func setFunction(ctx types.Context, target, value ast.Expression) (any, error) { } // discard any context changes within the newValue expression - _, newValue, err := eval.EvalExpression(ctx, value) + _, newValue, err := ctx.Runtime().EvalExpression(ctx, value) if err != nil { return nil, fmt.Errorf("argument #1: %w", err) } @@ -236,7 +236,7 @@ func deleteFunction(ctx types.Context, expr ast.Expression) (any, error) { } // pre-evaluate the path - pathExpr, err := eval.EvalPathExpression(ctx, symbol.PathExpression) + pathExpr, err := pathexpr.Eval(ctx, symbol.PathExpression) if err != nil { return nil, fmt.Errorf("argument #0: invalid path expression: %w", err) } @@ -261,7 +261,7 @@ func deleteFunction(ctx types.Context, expr ast.Expression) (any, error) { } // delete the desired path in the value - updatedValue, err := pathexpr.Delete(currentValue, pathexpr.FromEvaluatedPath(*pathExpr)) + updatedValue, err := genericpathexpr.Delete(currentValue, genericpathexpr.FromEvaluatedPath(*pathExpr)) if err != nil { return nil, fmt.Errorf("cannot delete %s in %T: %w", pathExpr, currentValue, err) } @@ -285,7 +285,7 @@ func deleteBangHandler(ctx types.Context, originalArgs []ast.Expression, value a // if the symbol has a path to traverse, do so if sym.PathExpression != nil { // pre-evaluate the path expression - pathExpr, err := eval.EvalPathExpression(ctx, sym.PathExpression) + pathExpr, err := pathexpr.Eval(ctx, sym.PathExpression) if err != nil { return ctx, nil, fmt.Errorf("argument #0: invalid path expression: %w", err) } @@ -303,7 +303,7 @@ func deleteBangHandler(ctx types.Context, originalArgs []ast.Expression, value a } // apply the path expression - updatedValue, err = pathexpr.Delete(currentValue, pathexpr.FromEvaluatedPath(*pathExpr)) + updatedValue, err = genericpathexpr.Delete(currentValue, genericpathexpr.FromEvaluatedPath(*pathExpr)) if err != nil { return ctx, nil, fmt.Errorf("cannot set value in %T at %s: %w", currentValue, pathExpr, err) } diff --git a/pkg/builtin/core/functions_test.go b/pkg/builtin/core/functions_test.go index 54a408c..a8a7a70 100644 --- a/pkg/builtin/core/functions_test.go +++ b/pkg/builtin/core/functions_test.go @@ -6,7 +6,7 @@ package core import ( "testing" - "go.xrstf.de/rudi/pkg/eval/types" + "go.xrstf.de/rudi/pkg/runtime/types" "go.xrstf.de/rudi/pkg/testutil" ) diff --git a/pkg/builtin/datetime/functions.go b/pkg/builtin/datetime/functions.go index b3b3049..61163ec 100644 --- a/pkg/builtin/datetime/functions.go +++ b/pkg/builtin/datetime/functions.go @@ -6,8 +6,8 @@ package datetime import ( "time" - "go.xrstf.de/rudi/pkg/eval/functions" - "go.xrstf.de/rudi/pkg/eval/types" + "go.xrstf.de/rudi/pkg/runtime/functions" + "go.xrstf.de/rudi/pkg/runtime/types" ) var ( diff --git a/pkg/builtin/encoding/functions.go b/pkg/builtin/encoding/functions.go index 9c3806b..73d5884 100644 --- a/pkg/builtin/encoding/functions.go +++ b/pkg/builtin/encoding/functions.go @@ -8,8 +8,8 @@ import ( "encoding/json" "fmt" - "go.xrstf.de/rudi/pkg/eval/functions" - "go.xrstf.de/rudi/pkg/eval/types" + "go.xrstf.de/rudi/pkg/runtime/functions" + "go.xrstf.de/rudi/pkg/runtime/types" ) var ( diff --git a/pkg/builtin/hashing/functions.go b/pkg/builtin/hashing/functions.go index 01cff80..0ca1918 100644 --- a/pkg/builtin/hashing/functions.go +++ b/pkg/builtin/hashing/functions.go @@ -12,8 +12,8 @@ import ( "hash" "io" - "go.xrstf.de/rudi/pkg/eval/functions" - "go.xrstf.de/rudi/pkg/eval/types" + "go.xrstf.de/rudi/pkg/runtime/functions" + "go.xrstf.de/rudi/pkg/runtime/types" ) var ( diff --git a/pkg/builtin/lists/functions.go b/pkg/builtin/lists/functions.go index b8eedd6..e9227e3 100644 --- a/pkg/builtin/lists/functions.go +++ b/pkg/builtin/lists/functions.go @@ -6,10 +6,9 @@ package lists import ( "fmt" - "go.xrstf.de/rudi/pkg/eval" - "go.xrstf.de/rudi/pkg/eval/functions" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/functions" + "go.xrstf.de/rudi/pkg/runtime/types" ) var ( @@ -79,7 +78,7 @@ func rangeVectorFunction(ctx types.Context, data []any, namingVec ast.Expression // do not use separate contexts for each loop iteration, as the loop might build up a counter loopCtx = loopCtx.WithVariables(vars) - loopCtx, result, err = eval.EvalExpression(loopCtx, expr) + loopCtx, result, err = ctx.Runtime().EvalExpression(loopCtx, expr) if err != nil { return nil, err } @@ -114,7 +113,7 @@ func rangeObjectFunction(ctx types.Context, data map[string]any, namingVec ast.E // do not use separate contexts for each loop iteration, as the loop might build up a counter loopCtx = loopCtx.WithVariables(vars) - loopCtx, result, err = eval.EvalExpression(loopCtx, expr) + loopCtx, result, err = ctx.Runtime().EvalExpression(loopCtx, expr) if err != nil { return nil, err } @@ -135,7 +134,7 @@ func mapVectorAnonymousFunction(ctx types.Context, data []any, ident ast.Express } mapHandler := func(ctx types.Context, _ any, value any) (types.Context, any, error) { - return eval.EvalFunctionCall(ctx, identifier, []ast.Expression{types.MakeShim(value)}) + return ctx.Runtime().CallFunction(ctx, identifier, []ast.Expression{types.MakeShim(value)}) } return mapVector(ctx, data, mapHandler) @@ -160,7 +159,7 @@ func mapVectorExpressionFunction(ctx types.Context, data []any, namingVec ast.Ex ctx = ctx.WithVariables(vars) - return eval.EvalExpression(ctx, expr) + return ctx.Runtime().EvalExpression(ctx, expr) } return mapVector(ctx, data, mapHandler) @@ -197,7 +196,7 @@ func mapObjectAnonymousFunction(ctx types.Context, data map[string]any, ident as } mapHandler := func(ctx types.Context, _ any, value any) (types.Context, any, error) { - return eval.EvalFunctionCall(ctx, identifier, []ast.Expression{types.MakeShim(value)}) + return ctx.Runtime().CallFunction(ctx, identifier, []ast.Expression{types.MakeShim(value)}) } return mapObject(ctx, data, mapHandler) @@ -222,7 +221,7 @@ func mapObjectExpressionFunction(ctx types.Context, data map[string]any, namingV ctx = ctx.WithVariables(vars) - return eval.EvalExpression(ctx, expr) + return ctx.Runtime().EvalExpression(ctx, expr) } return mapObject(ctx, data, mapHandler) @@ -259,7 +258,7 @@ func filterVectorAnonymousFunction(ctx types.Context, data []any, ident ast.Expr } mapHandler := func(ctx types.Context, _ any, value any) (types.Context, any, error) { - return eval.EvalFunctionCall(ctx, identifier, []ast.Expression{types.MakeShim(value)}) + return ctx.Runtime().CallFunction(ctx, identifier, []ast.Expression{types.MakeShim(value)}) } return filterVector(ctx, data, mapHandler) @@ -284,7 +283,7 @@ func filterVectorExpressionFunction(ctx types.Context, data []any, namingVec ast ctx = ctx.WithVariables(vars) - return eval.EvalExpression(ctx, expr) + return ctx.Runtime().EvalExpression(ctx, expr) } return filterVector(ctx, data, mapHandler) @@ -327,7 +326,7 @@ func filterObjectAnonymousFunction(ctx types.Context, data map[string]any, ident } mapHandler := func(ctx types.Context, _ any, value any) (types.Context, any, error) { - return eval.EvalFunctionCall(ctx, identifier, []ast.Expression{types.MakeShim(value)}) + return ctx.Runtime().CallFunction(ctx, identifier, []ast.Expression{types.MakeShim(value)}) } return filterObject(ctx, data, mapHandler) @@ -352,7 +351,7 @@ func filterObjectExpressionFunction(ctx types.Context, data map[string]any, nami ctx = ctx.WithVariables(vars) - return eval.EvalExpression(ctx, expr) + return ctx.Runtime().EvalExpression(ctx, expr) } return filterObject(ctx, data, mapHandler) diff --git a/pkg/builtin/logic/functions.go b/pkg/builtin/logic/functions.go index 4f0735b..0a7bc06 100644 --- a/pkg/builtin/logic/functions.go +++ b/pkg/builtin/logic/functions.go @@ -6,10 +6,9 @@ package logic import ( "fmt" - "go.xrstf.de/rudi/pkg/eval" - "go.xrstf.de/rudi/pkg/eval/functions" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/functions" + "go.xrstf.de/rudi/pkg/runtime/types" ) var ( @@ -22,7 +21,7 @@ var ( func andFunction(ctx types.Context, args ...ast.Expression) (any, error) { for i, arg := range args { - _, evaluated, err := eval.EvalExpression(ctx, arg) + _, evaluated, err := ctx.Runtime().EvalExpression(ctx, arg) if err != nil { return nil, fmt.Errorf("argument #%d: %w", i, err) } @@ -42,7 +41,7 @@ func andFunction(ctx types.Context, args ...ast.Expression) (any, error) { func orFunction(ctx types.Context, args ...ast.Expression) (any, error) { for i, arg := range args { - _, evaluated, err := eval.EvalExpression(ctx, arg) + _, evaluated, err := ctx.Runtime().EvalExpression(ctx, arg) if err != nil { return nil, fmt.Errorf("argument #%d: %w", i, err) } diff --git a/pkg/builtin/math/functions.go b/pkg/builtin/math/functions.go index 57b8633..360cf19 100644 --- a/pkg/builtin/math/functions.go +++ b/pkg/builtin/math/functions.go @@ -6,9 +6,9 @@ package math import ( "errors" - "go.xrstf.de/rudi/pkg/eval/functions" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/functions" + "go.xrstf.de/rudi/pkg/runtime/types" ) var ( diff --git a/pkg/builtin/meta.go b/pkg/builtin/meta.go index 42d5e7d..a4d07cb 100644 --- a/pkg/builtin/meta.go +++ b/pkg/builtin/meta.go @@ -16,7 +16,7 @@ import ( "go.xrstf.de/rudi/pkg/builtin/rudifunc" "go.xrstf.de/rudi/pkg/builtin/strings" "go.xrstf.de/rudi/pkg/builtin/types" - evaltypes "go.xrstf.de/rudi/pkg/eval/types" + evaltypes "go.xrstf.de/rudi/pkg/runtime/types" ) var ( diff --git a/pkg/builtin/rudifunc/functions.go b/pkg/builtin/rudifunc/functions.go index 8618492..df98273 100644 --- a/pkg/builtin/rudifunc/functions.go +++ b/pkg/builtin/rudifunc/functions.go @@ -6,10 +6,9 @@ package rudifunc import ( "fmt" - "go.xrstf.de/rudi/pkg/eval" - "go.xrstf.de/rudi/pkg/eval/functions" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/functions" + "go.xrstf.de/rudi/pkg/runtime/types" ) var ( @@ -77,7 +76,7 @@ func (f rudispaceFunc) Evaluate(ctx types.Context, args []ast.Expression) (any, funcArgs := map[string]any{} for i, paramName := range f.params { - _, arg, err := eval.EvalExpression(ctx, args[i]) + _, arg, err := ctx.Runtime().EvalExpression(ctx, args[i]) if err != nil { return nil, err } @@ -85,7 +84,7 @@ func (f rudispaceFunc) Evaluate(ctx types.Context, args []ast.Expression) (any, funcArgs[paramName] = arg } - _, result, err := eval.EvalExpression(ctx.WithVariables(funcArgs), f.body) + _, result, err := ctx.Runtime().EvalExpression(ctx.WithVariables(funcArgs), f.body) return result, err } diff --git a/pkg/builtin/strings/functions.go b/pkg/builtin/strings/functions.go index 624c61e..013e6fd 100644 --- a/pkg/builtin/strings/functions.go +++ b/pkg/builtin/strings/functions.go @@ -8,8 +8,8 @@ import ( stdstrings "strings" "go.xrstf.de/rudi/pkg/equality" - "go.xrstf.de/rudi/pkg/eval/functions" - "go.xrstf.de/rudi/pkg/eval/types" + "go.xrstf.de/rudi/pkg/runtime/functions" + "go.xrstf.de/rudi/pkg/runtime/types" ) var ( diff --git a/pkg/builtin/types/functions.go b/pkg/builtin/types/functions.go index 2a926b9..2658d72 100644 --- a/pkg/builtin/types/functions.go +++ b/pkg/builtin/types/functions.go @@ -7,8 +7,8 @@ import ( "fmt" "go.xrstf.de/rudi/pkg/coalescing" - "go.xrstf.de/rudi/pkg/eval/functions" - "go.xrstf.de/rudi/pkg/eval/types" + "go.xrstf.de/rudi/pkg/runtime/functions" + "go.xrstf.de/rudi/pkg/runtime/types" ) var ( diff --git a/pkg/docs/module.go b/pkg/docs/module.go index 87309db..3a2de86 100644 --- a/pkg/docs/module.go +++ b/pkg/docs/module.go @@ -3,7 +3,7 @@ package docs -import "go.xrstf.de/rudi/pkg/eval/types" +import "go.xrstf.de/rudi/pkg/runtime/types" // A module combines functions with their documentation. This type and concept only exist in // the Rudi interpreter (cmd/rudi); the actual codebase separates the function code from their diff --git a/pkg/eval/eval.go b/pkg/eval/eval.go deleted file mode 100644 index 6e11efc..0000000 --- a/pkg/eval/eval.go +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Christoph Mewes -// SPDX-License-Identifier: MIT - -package eval - -import ( - "go.xrstf.de/rudi/pkg/eval/types" - "go.xrstf.de/rudi/pkg/lang/ast" -) - -func Run(ctx types.Context, p *ast.Program) (types.Context, any, error) { - return EvalProgram(ctx, p) -} diff --git a/pkg/eval/eval_expression.go b/pkg/eval/eval_expression.go deleted file mode 100644 index f344f6b..0000000 --- a/pkg/eval/eval_expression.go +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Christoph Mewes -// SPDX-License-Identifier: MIT - -package eval - -import ( - "fmt" - - "go.xrstf.de/rudi/pkg/eval/types" - "go.xrstf.de/rudi/pkg/lang/ast" -) - -func EvalExpression(ctx types.Context, expr ast.Expression) (types.Context, any, error) { - switch asserted := expr.(type) { - case ast.Null: - return EvalNull(ctx, asserted) - case ast.Bool: - return EvalBool(ctx, asserted) - case ast.String: - return EvalString(ctx, asserted) - case ast.Number: - return EvalNumber(ctx, asserted) - case ast.ObjectNode: - return EvalObjectNode(ctx, asserted) - case ast.VectorNode: - return EvalVectorNode(ctx, asserted) - case ast.Symbol: - return EvalSymbol(ctx, asserted) - case ast.Tuple: - return EvalTuple(ctx, asserted) - case ast.Identifier: - return EvalIdentifier(ctx, asserted) - case ast.Shim: - return EvalShim(ctx, asserted) - } - - return ctx, nil, fmt.Errorf("unknown expression %s (%T)", expr.String(), expr) -} diff --git a/pkg/eval/eval_statement.go b/pkg/eval/eval_statement.go deleted file mode 100644 index ad85c09..0000000 --- a/pkg/eval/eval_statement.go +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Christoph Mewes -// SPDX-License-Identifier: MIT - -package eval - -import ( - "go.xrstf.de/rudi/pkg/eval/types" - "go.xrstf.de/rudi/pkg/lang/ast" -) - -func EvalStatement(ctx types.Context, stmt ast.Statement) (types.Context, any, error) { - return EvalExpression(ctx, stmt.Expression) -} diff --git a/pkg/eval/types/context.go b/pkg/eval/types/context.go deleted file mode 100644 index 2ceeb44..0000000 --- a/pkg/eval/types/context.go +++ /dev/null @@ -1,288 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Christoph Mewes -// SPDX-License-Identifier: MIT - -package types - -import ( - "context" - - "go.xrstf.de/rudi/pkg/coalescing" - "go.xrstf.de/rudi/pkg/lang/ast" -) - -type Document struct { - data any -} - -func NewDocument(data any) (Document, error) { - return Document{ - data: data, - }, nil -} - -func (d *Document) Data() any { - return d.data -} - -func (d *Document) Set(wrappedData any) { - d.data = wrappedData -} - -type Context struct { - ctx context.Context - document *Document - fixedFuncs Functions - userFuncs Functions - variables Variables - coalescer coalescing.Coalescer -} - -func NewContext(ctx context.Context, doc Document, variables Variables, funcs Functions, coalescer coalescing.Coalescer) Context { - if ctx == nil { - ctx = context.Background() - } - - if variables == nil { - variables = NewVariables() - } - - if funcs == nil { - funcs = NewFunctions() - } - - if coalescer == nil { - coalescer = coalescing.NewStrict() - } - - return Context{ - ctx: ctx, - document: &doc, - fixedFuncs: funcs, - userFuncs: NewFunctions(), - variables: variables, - coalescer: coalescer, - } -} - -// Coalesce is named this way to make the frequent calls read fluently -// (for example "ctx.Coalesce().ToBool(...)"). -func (c Context) Coalesce() coalescing.Coalescer { - return c.coalescer -} - -func (c Context) GoContext() context.Context { - return c.ctx -} - -func (c Context) GetDocument() *Document { - return c.document -} - -func (c Context) GetVariable(name string) (any, bool) { - return c.variables.Get(name) -} - -func (c Context) GetFunction(name string) (Function, bool) { - f, ok := c.fixedFuncs.Get(name) - if ok { - return f, true - } - - return c.userFuncs.Get(name) -} - -func (c Context) WithGoContext(ctx context.Context) Context { - return Context{ - ctx: ctx, - document: c.document, - fixedFuncs: c.fixedFuncs, - userFuncs: c.userFuncs, - variables: c.variables, - coalescer: c.coalescer, - } -} - -func (c Context) WithVariable(name string, val any) Context { - return Context{ - ctx: c.ctx, - document: c.document, - fixedFuncs: c.fixedFuncs, - userFuncs: c.userFuncs, - variables: c.variables.With(name, val), - coalescer: c.coalescer, - } -} - -func (c Context) WithVariables(vars map[string]any) Context { - if len(vars) == 0 { - return c - } - - return Context{ - ctx: c.ctx, - document: c.document, - fixedFuncs: c.fixedFuncs, - userFuncs: c.userFuncs, - variables: c.variables.WithMany(vars), - coalescer: c.coalescer, - } -} - -func (c Context) WithCoalescer(coalescer coalescing.Coalescer) Context { - return Context{ - ctx: c.ctx, - document: c.document, - fixedFuncs: c.fixedFuncs, - userFuncs: c.userFuncs, - variables: c.variables, - coalescer: coalescer, - } -} - -func (c Context) WithRudispaceFunction(funcName string, fun Function) Context { - return Context{ - ctx: c.ctx, - document: c.document, - fixedFuncs: c.fixedFuncs, - userFuncs: c.userFuncs.DeepCopy().Set(funcName, fun), - variables: c.variables, - coalescer: c.coalescer, - } -} - -type Function interface { - Evaluate(ctx Context, args []ast.Expression) (any, error) - - // Description returns a short, one-line description of the function; markdown - // can be used to highlight other function names, like "behaves similar - // to `foo`, but …". - Description() string -} - -type TupleFunction func(ctx Context, args []ast.Expression) (any, error) - -type basicFunc struct { - f TupleFunction - desc string -} - -// NewFunction creates the lowest of low level functions in Rudi and should rarely be used by -// integrators/library developers. Use the helpers in the root package to define functions -// using reflection and pattern matching instead. -func NewFunction(f TupleFunction, description string) Function { - return basicFunc{ - f: f, - desc: description, - } -} - -var _ Function = basicFunc{} - -func (f basicFunc) Evaluate(ctx Context, args []ast.Expression) (any, error) { - return f.f(ctx, args) -} - -func (f basicFunc) Description() string { - return f.desc -} - -type Functions map[string]Function - -func NewFunctions() Functions { - return Functions{} -} - -func (f Functions) Get(name string) (Function, bool) { - variable, exists := f[name] - return variable, exists -} - -// Set sets/replaces the function in the current set (in-place). -// The function returns the same Functions to allow fluent access. -func (f Functions) Set(name string, fun Function) Functions { - f[name] = fun - return f -} - -// Set removes a function from the set. -// The function returns the same Functions to allow fluent access. -func (f Functions) Delete(name string) Functions { - delete(f, name) - return f -} - -// Add adds all functions from other to the current set. -// The function returns the same Functions to allow fluent access. -func (f Functions) Add(other Functions) Functions { - for name, fun := range other { - f[name] = fun - } - return f -} - -// Remove removes all functions from this set that are part of the other set, -// to enable constructs like AllFunctions.Remove(MathFunctions) -// The function returns the same Functions to allow fluent access. -func (f Functions) Remove(other Functions) Functions { - for name := range other { - f.Delete(name) - } - return f -} - -func (f Functions) DeepCopy() Functions { - result := NewFunctions() - for key, val := range f { - result[key] = val - } - return result -} - -type Variables map[string]any - -func NewVariables() Variables { - return Variables{} -} - -func (v Variables) Get(name string) (any, bool) { - variable, exists := v[name] - return variable, exists -} - -// Set sets/replaces the variable value in the current set (in-place). -// The function returns the same variables to allow fluent access. -func (v Variables) Set(name string, val any) Variables { - v[name] = val - return v -} - -// With returns a copy of the variables, with the new variable being added to it. -func (v Variables) With(name string, val any) Variables { - return v.DeepCopy().Set(name, val) -} - -// WithMany is like With(), but for adding multiple new variables at once. This -// should be preferred to With() to prevent unnecessary DeepCopies. -func (v Variables) WithMany(vars map[string]any) Variables { - if len(vars) == 0 { - return v - } - - out := v.DeepCopy() - for k, v := range vars { - out.Set(k, v) - } - return out -} - -func (v Variables) DeepCopy() Variables { - result := NewVariables() - for key, val := range v { - result[key] = val - } - return result -} - -func MakeShim(val any) ast.Shim { - return ast.Shim{Value: val} -} diff --git a/pkg/eval/functions/args_consumer.go b/pkg/runtime/functions/args_consumer.go similarity index 96% rename from pkg/eval/functions/args_consumer.go rename to pkg/runtime/functions/args_consumer.go index 8a8b672..d9562c2 100644 --- a/pkg/eval/functions/args_consumer.go +++ b/pkg/runtime/functions/args_consumer.go @@ -6,15 +6,16 @@ package functions import ( "reflect" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/types" ) var ( - dummy ast.Expression - expressionType = reflect.TypeOf(&dummy).Elem() - contextType = reflect.TypeOf(&types.Context{}).Elem() - numberType = reflect.TypeOf(&ast.Number{}).Elem() + dummyExpression ast.Expression + + expressionType = reflect.TypeOf(&dummyExpression).Elem() + contextType = reflect.TypeOf(types.Context{}) + numberType = reflect.TypeOf(ast.Number{}) ) type argsConsumer func(ctx types.Context, args []cachedExpression) (asserted []any, remaining []cachedExpression, err error) diff --git a/pkg/eval/functions/args_consumer_test.go b/pkg/runtime/functions/args_consumer_test.go similarity index 83% rename from pkg/eval/functions/args_consumer_test.go rename to pkg/runtime/functions/args_consumer_test.go index 7706669..7b6b43f 100644 --- a/pkg/eval/functions/args_consumer_test.go +++ b/pkg/runtime/functions/args_consumer_test.go @@ -8,8 +8,9 @@ import ( "testing" "go.xrstf.de/rudi/pkg/coalescing" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/interpreter" + "go.xrstf.de/rudi/pkg/runtime/types" "github.com/google/go-cmp/cmp" ) @@ -84,7 +85,10 @@ func TestBoolConsumer(t *testing.T) { } coalescer := coalescing.NewHumane() - ctx := types.NewContext(context.Background(), types.Document{}, nil, nil, coalescer) + ctx, err := types.NewContext(interpreter.New(), context.Background(), types.Document{}, nil, nil, coalescer) + if err != nil { + t.Fatalf("Failed to create context: %v", err) + } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { @@ -132,7 +136,10 @@ func TestStringConsumer(t *testing.T) { } coalescer := coalescing.NewHumane() - ctx := types.NewContext(context.Background(), types.Document{}, nil, nil, coalescer) + ctx, err := types.NewContext(interpreter.New(), context.Background(), types.Document{}, nil, nil, coalescer) + if err != nil { + t.Fatalf("Failed to create context: %v", err) + } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { @@ -182,7 +189,10 @@ func TestAnyConsumer(t *testing.T) { } coalescer := coalescing.NewHumane() - ctx := types.NewContext(context.Background(), types.Document{}, nil, nil, coalescer) + ctx, err := types.NewContext(interpreter.New(), context.Background(), types.Document{}, nil, nil, coalescer) + if err != nil { + t.Fatalf("Failed to create context: %v", err) + } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { @@ -214,7 +224,10 @@ func TestExpressionConsumer(t *testing.T) { } coalescer := coalescing.NewHumane() - ctx := types.NewContext(context.Background(), types.Document{}, nil, nil, coalescer) + ctx, err := types.NewContext(interpreter.New(), context.Background(), types.Document{}, nil, nil, coalescer) + if err != nil { + t.Fatalf("Failed to create context: %v", err) + } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { @@ -244,7 +257,10 @@ func TestVariadicConsumer(t *testing.T) { } coalescer := coalescing.NewHumane() - ctx := types.NewContext(context.Background(), types.Document{}, nil, nil, coalescer) + ctx, err := types.NewContext(interpreter.New(), context.Background(), types.Document{}, nil, nil, coalescer) + if err != nil { + t.Fatalf("Failed to create context: %v", err) + } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { diff --git a/pkg/eval/functions/args_matcher.go b/pkg/runtime/functions/args_matcher.go similarity index 99% rename from pkg/eval/functions/args_matcher.go rename to pkg/runtime/functions/args_matcher.go index b602c04..ead8950 100644 --- a/pkg/eval/functions/args_matcher.go +++ b/pkg/runtime/functions/args_matcher.go @@ -8,7 +8,7 @@ import ( "fmt" "reflect" - "go.xrstf.de/rudi/pkg/eval/types" + "go.xrstf.de/rudi/pkg/runtime/types" ) const noLimit = -1 diff --git a/pkg/eval/functions/args_matcher_test.go b/pkg/runtime/functions/args_matcher_test.go similarity index 97% rename from pkg/eval/functions/args_matcher_test.go rename to pkg/runtime/functions/args_matcher_test.go index 093c71c..208484e 100644 --- a/pkg/eval/functions/args_matcher_test.go +++ b/pkg/runtime/functions/args_matcher_test.go @@ -8,8 +8,9 @@ import ( "testing" "go.xrstf.de/rudi/pkg/coalescing" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/interpreter" + "go.xrstf.de/rudi/pkg/runtime/types" "github.com/google/go-cmp/cmp" ) @@ -491,7 +492,10 @@ func TestArgsMatcher(t *testing.T) { } coalescer := coalescing.NewHumane() - ctx := types.NewContext(context.Background(), types.Document{}, nil, nil, coalescer) + ctx, err := types.NewContext(interpreter.New(), context.Background(), types.Document{}, nil, nil, coalescer) + if err != nil { + t.Fatalf("Failed to create context: %v", err) + } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { diff --git a/pkg/eval/functions/builder.go b/pkg/runtime/functions/builder.go similarity index 96% rename from pkg/eval/functions/builder.go rename to pkg/runtime/functions/builder.go index 1abee0d..08620e1 100644 --- a/pkg/eval/functions/builder.go +++ b/pkg/runtime/functions/builder.go @@ -7,7 +7,7 @@ import ( "fmt" "go.xrstf.de/rudi/pkg/coalescing" - "go.xrstf.de/rudi/pkg/eval/types" + "go.xrstf.de/rudi/pkg/runtime/types" ) type Builder struct { diff --git a/pkg/eval/functions/expression.go b/pkg/runtime/functions/expression.go similarity index 89% rename from pkg/eval/functions/expression.go rename to pkg/runtime/functions/expression.go index 29cd18d..af82149 100644 --- a/pkg/eval/functions/expression.go +++ b/pkg/runtime/functions/expression.go @@ -4,9 +4,8 @@ package functions import ( - "go.xrstf.de/rudi/pkg/eval" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/types" ) // cachedExpression are used to avoid having to re-compute the same expressions when attempting @@ -29,7 +28,7 @@ func convertArgs(args []ast.Expression) []cachedExpression { func (e *cachedExpression) Eval(ctx types.Context) (any, error) { if !e.evaluated { - _, result, err := eval.EvalExpression(ctx, e.expr) + _, result, err := ctx.Runtime().EvalExpression(ctx, e.expr) if err != nil { return nil, err } diff --git a/pkg/eval/functions/form.go b/pkg/runtime/functions/form.go similarity index 97% rename from pkg/eval/functions/form.go rename to pkg/runtime/functions/form.go index cfa2d08..4f12fb1 100644 --- a/pkg/eval/functions/form.go +++ b/pkg/runtime/functions/form.go @@ -7,7 +7,7 @@ import ( "fmt" "reflect" - "go.xrstf.de/rudi/pkg/eval/types" + "go.xrstf.de/rudi/pkg/runtime/types" ) type form struct { diff --git a/pkg/eval/functions/form_test.go b/pkg/runtime/functions/form_test.go similarity index 93% rename from pkg/eval/functions/form_test.go rename to pkg/runtime/functions/form_test.go index ff2ed32..2cb66e4 100644 --- a/pkg/eval/functions/form_test.go +++ b/pkg/runtime/functions/form_test.go @@ -10,8 +10,9 @@ import ( "testing" "go.xrstf.de/rudi/pkg/coalescing" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/interpreter" + "go.xrstf.de/rudi/pkg/runtime/types" ) func TestFormCalling(t *testing.T) { @@ -170,7 +171,10 @@ func TestFormCalling(t *testing.T) { } coalescer := coalescing.NewHumane() - ctx := types.NewContext(context.Background(), types.Document{}, nil, nil, coalescer) + ctx, err := types.NewContext(interpreter.New(), context.Background(), types.Document{}, nil, nil, coalescer) + if err != nil { + t.Fatalf("Failed to create context: %v", err) + } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { diff --git a/pkg/eval/functions/function.go b/pkg/runtime/functions/function.go similarity index 91% rename from pkg/eval/functions/function.go rename to pkg/runtime/functions/function.go index 7263b55..76082eb 100644 --- a/pkg/eval/functions/function.go +++ b/pkg/runtime/functions/function.go @@ -8,9 +8,8 @@ import ( "fmt" "go.xrstf.de/rudi/pkg/coalescing" - "go.xrstf.de/rudi/pkg/eval" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/types" ) type regularFunction struct { @@ -63,8 +62,8 @@ type extendedFunction struct { } var ( - _ types.Function = &extendedFunction{} - _ eval.BangHandler = &extendedFunction{} + _ types.Function = &extendedFunction{} + _ types.BangHandler = &extendedFunction{} ) func (f *extendedFunction) BangHandler(ctx types.Context, originalArgs []ast.Expression, value any) (types.Context, any, error) { diff --git a/pkg/eval/eval_bool.go b/pkg/runtime/interpreter/eval_bool.go similarity index 51% rename from pkg/eval/eval_bool.go rename to pkg/runtime/interpreter/eval_bool.go index 62ca49f..2bf4ef6 100644 --- a/pkg/eval/eval_bool.go +++ b/pkg/runtime/interpreter/eval_bool.go @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: 2023 Christoph Mewes // SPDX-License-Identifier: MIT -package eval +package interpreter import ( - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/types" ) -func EvalBool(ctx types.Context, b ast.Bool) (types.Context, any, error) { +func (*interpreter) EvalBool(ctx types.Context, b ast.Bool) (types.Context, any, error) { return ctx, bool(b), nil } diff --git a/pkg/runtime/interpreter/eval_expression.go b/pkg/runtime/interpreter/eval_expression.go new file mode 100644 index 0000000..ce89de6 --- /dev/null +++ b/pkg/runtime/interpreter/eval_expression.go @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2023 Christoph Mewes +// SPDX-License-Identifier: MIT + +package interpreter + +import ( + "fmt" + + "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/types" +) + +func (i *interpreter) EvalExpression(ctx types.Context, expr ast.Expression) (types.Context, any, error) { + switch asserted := expr.(type) { + case ast.Null: + return i.EvalNull(ctx, asserted) + case ast.Bool: + return i.EvalBool(ctx, asserted) + case ast.String: + return i.EvalString(ctx, asserted) + case ast.Number: + return i.EvalNumber(ctx, asserted) + case ast.ObjectNode: + return i.EvalObjectNode(ctx, asserted) + case ast.VectorNode: + return i.EvalVectorNode(ctx, asserted) + case ast.Symbol: + return i.EvalSymbol(ctx, asserted) + case ast.Tuple: + return i.EvalTuple(ctx, asserted) + case ast.Identifier: + return i.EvalIdentifier(ctx, asserted) + case ast.Shim: + return i.EvalShim(ctx, asserted) + } + + return ctx, nil, fmt.Errorf("unknown expression %s (%T)", expr.String(), expr) +} diff --git a/pkg/eval/eval_identifier.go b/pkg/runtime/interpreter/eval_identifier.go similarity index 55% rename from pkg/eval/eval_identifier.go rename to pkg/runtime/interpreter/eval_identifier.go index 8624774..8c1109a 100644 --- a/pkg/eval/eval_identifier.go +++ b/pkg/runtime/interpreter/eval_identifier.go @@ -1,15 +1,15 @@ // SPDX-FileCopyrightText: 2023 Christoph Mewes // SPDX-License-Identifier: MIT -package eval +package interpreter import ( "fmt" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/types" ) -func EvalIdentifier(ctx types.Context, ident ast.Identifier) (types.Context, any, error) { +func (*interpreter) EvalIdentifier(ctx types.Context, ident ast.Identifier) (types.Context, any, error) { return ctx, nil, fmt.Errorf("unexpected identifier: %v", ident) } diff --git a/pkg/eval/eval_null.go b/pkg/runtime/interpreter/eval_null.go similarity index 50% rename from pkg/eval/eval_null.go rename to pkg/runtime/interpreter/eval_null.go index 7a8ac22..0c2fe6c 100644 --- a/pkg/eval/eval_null.go +++ b/pkg/runtime/interpreter/eval_null.go @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: 2023 Christoph Mewes // SPDX-License-Identifier: MIT -package eval +package interpreter import ( - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/types" ) -func EvalNull(ctx types.Context, n ast.Null) (types.Context, any, error) { +func (*interpreter) EvalNull(ctx types.Context, n ast.Null) (types.Context, any, error) { return ctx, nil, nil } diff --git a/pkg/eval/eval_number.go b/pkg/runtime/interpreter/eval_number.go similarity index 50% rename from pkg/eval/eval_number.go rename to pkg/runtime/interpreter/eval_number.go index 6760d92..15d79d4 100644 --- a/pkg/eval/eval_number.go +++ b/pkg/runtime/interpreter/eval_number.go @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: 2023 Christoph Mewes // SPDX-License-Identifier: MIT -package eval +package interpreter import ( - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/types" ) -func EvalNumber(ctx types.Context, n ast.Number) (types.Context, any, error) { +func (*interpreter) EvalNumber(ctx types.Context, n ast.Number) (types.Context, any, error) { return ctx, n.Value, nil } diff --git a/pkg/eval/eval_object.go b/pkg/runtime/interpreter/eval_object.go similarity index 71% rename from pkg/eval/eval_object.go rename to pkg/runtime/interpreter/eval_object.go index 9739d53..40eb61b 100644 --- a/pkg/eval/eval_object.go +++ b/pkg/runtime/interpreter/eval_object.go @@ -1,17 +1,18 @@ // SPDX-FileCopyrightText: 2023 Christoph Mewes // SPDX-License-Identifier: MIT -package eval +package interpreter import ( "errors" "fmt" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/pathexpr" + "go.xrstf.de/rudi/pkg/runtime/types" ) -func EvalObjectNode(ctx types.Context, obj ast.ObjectNode) (types.Context, any, error) { +func (i *interpreter) EvalObjectNode(ctx types.Context, obj ast.ObjectNode) (types.Context, any, error) { innerCtx := ctx result := map[string]any{} @@ -33,7 +34,7 @@ func EvalObjectNode(ctx types.Context, obj ast.ObjectNode) (types.Context, any, default: // Just like with arrays, use a growing context during the object evaluation, // in case someone wants to define a variable here... for some reason. - innerCtx, key, err = EvalExpression(innerCtx, pair.Key) + innerCtx, key, err = i.EvalExpression(innerCtx, pair.Key) if err != nil { return ctx, nil, fmt.Errorf("failed to evaluate object key %s: %w", pair.Key.String(), err) } @@ -44,7 +45,7 @@ func EvalObjectNode(ctx types.Context, obj ast.ObjectNode) (types.Context, any, return ctx, nil, fmt.Errorf("object key: %w", err) } - innerCtx, value, err = EvalExpression(innerCtx, pair.Value) + innerCtx, value, err = i.EvalExpression(innerCtx, pair.Value) if err != nil { return ctx, nil, fmt.Errorf("failed to evaluate object value %s: %w", pair.Value.String(), err) } @@ -52,14 +53,10 @@ func EvalObjectNode(ctx types.Context, obj ast.ObjectNode) (types.Context, any, result[keyString] = value } - if obj.PathExpression != nil { - deeper, err := TraversePathExpression(ctx, result, obj.PathExpression) - if err != nil { - return ctx, nil, err - } - - return ctx, deeper, nil + deeper, err := pathexpr.Apply(ctx, result, obj.PathExpression) + if err != nil { + return ctx, nil, err } - return ctx, result, nil + return ctx, deeper, nil } diff --git a/pkg/eval/eval_program.go b/pkg/runtime/interpreter/eval_program.go similarity index 67% rename from pkg/eval/eval_program.go rename to pkg/runtime/interpreter/eval_program.go index f7fae6f..ccb1f81 100644 --- a/pkg/eval/eval_program.go +++ b/pkg/runtime/interpreter/eval_program.go @@ -1,17 +1,18 @@ // SPDX-FileCopyrightText: 2023 Christoph Mewes // SPDX-License-Identifier: MIT -package eval +package interpreter import ( "errors" "fmt" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/types" ) -func EvalProgram(ctx types.Context, p *ast.Program) (types.Context, any, error) { +// Run implements types.Runtime. +func (i *interpreter) EvalProgram(ctx types.Context, p *ast.Program) (types.Context, any, error) { if p == nil { return ctx, nil, errors.New("program is nil") } @@ -28,7 +29,7 @@ func EvalProgram(ctx types.Context, p *ast.Program) (types.Context, any, error) ) for _, stmt := range p.Statements { - innerCtx, result, err = EvalStatement(innerCtx, stmt) + innerCtx, result, err = i.EvalStatement(innerCtx, stmt) if err != nil { return ctx, nil, fmt.Errorf("failed to eval statement %s: %w", stmt.String(), err) } diff --git a/pkg/eval/eval_shim.go b/pkg/runtime/interpreter/eval_shim.go similarity index 51% rename from pkg/eval/eval_shim.go rename to pkg/runtime/interpreter/eval_shim.go index 4014595..7241fba 100644 --- a/pkg/eval/eval_shim.go +++ b/pkg/runtime/interpreter/eval_shim.go @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: 2023 Christoph Mewes // SPDX-License-Identifier: MIT -package eval +package interpreter import ( - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/types" ) -func EvalShim(ctx types.Context, s ast.Shim) (types.Context, any, error) { +func (*interpreter) EvalShim(ctx types.Context, s ast.Shim) (types.Context, any, error) { return ctx, s.Value, nil } diff --git a/pkg/runtime/interpreter/eval_statement.go b/pkg/runtime/interpreter/eval_statement.go new file mode 100644 index 0000000..098bd63 --- /dev/null +++ b/pkg/runtime/interpreter/eval_statement.go @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2023 Christoph Mewes +// SPDX-License-Identifier: MIT + +package interpreter + +import ( + "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/types" +) + +func (i *interpreter) EvalStatement(ctx types.Context, stmt ast.Statement) (types.Context, any, error) { + return i.EvalExpression(ctx, stmt.Expression) +} diff --git a/pkg/eval/eval_string.go b/pkg/runtime/interpreter/eval_string.go similarity index 50% rename from pkg/eval/eval_string.go rename to pkg/runtime/interpreter/eval_string.go index c108ce7..227adcc 100644 --- a/pkg/eval/eval_string.go +++ b/pkg/runtime/interpreter/eval_string.go @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: 2023 Christoph Mewes // SPDX-License-Identifier: MIT -package eval +package interpreter import ( - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/types" ) -func EvalString(ctx types.Context, str ast.String) (types.Context, any, error) { +func (*interpreter) EvalString(ctx types.Context, str ast.String) (types.Context, any, error) { return ctx, string(str), nil } diff --git a/pkg/eval/eval_symbol.go b/pkg/runtime/interpreter/eval_symbol.go similarity index 54% rename from pkg/eval/eval_symbol.go rename to pkg/runtime/interpreter/eval_symbol.go index 9a12ee0..4beeaef 100644 --- a/pkg/eval/eval_symbol.go +++ b/pkg/runtime/interpreter/eval_symbol.go @@ -1,32 +1,18 @@ // SPDX-FileCopyrightText: 2023 Christoph Mewes // SPDX-License-Identifier: MIT -package eval +package interpreter import ( "errors" "fmt" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/pathexpr" + "go.xrstf.de/rudi/pkg/runtime/types" ) -func EvalSymbol(ctx types.Context, sym ast.Symbol) (types.Context, any, error) { - // pre-evaluate the path expression - pathExpr := ast.EvaluatedPathExpression{} - - if sym.PathExpression != nil { - evaluated, err := EvalPathExpression(ctx, sym.PathExpression) - if err != nil { - return ctx, nil, fmt.Errorf("invalid path expression: %w", err) - } - pathExpr = *evaluated - } - - return EvalSymbolWithEvaluatedPath(ctx, sym, pathExpr) -} - -func EvalSymbolWithEvaluatedPath(ctx types.Context, sym ast.Symbol, path ast.EvaluatedPathExpression) (types.Context, any, error) { +func (*interpreter) EvalSymbol(ctx types.Context, sym ast.Symbol) (types.Context, any, error) { rootValue := ctx.GetDocument().Data() // sanity check @@ -51,7 +37,7 @@ func EvalSymbolWithEvaluatedPath(ctx types.Context, sym ast.Symbol, path ast.Eva } } - deeper, err := TraverseEvaluatedPathExpression(rootValue, path) + deeper, err := pathexpr.Apply(ctx, rootValue, sym.PathExpression) if err != nil { return ctx, nil, fmt.Errorf("cannot evaluate %s: %w", sym.String(), err) } diff --git a/pkg/eval/eval_tuple.go b/pkg/runtime/interpreter/eval_tuple.go similarity index 71% rename from pkg/eval/eval_tuple.go rename to pkg/runtime/interpreter/eval_tuple.go index fe72a98..bc2b575 100644 --- a/pkg/eval/eval_tuple.go +++ b/pkg/runtime/interpreter/eval_tuple.go @@ -1,19 +1,20 @@ // SPDX-FileCopyrightText: 2023 Christoph Mewes // SPDX-License-Identifier: MIT -package eval +package interpreter import ( "errors" "fmt" "go.xrstf.de/rudi/pkg/deepcopy" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" - "go.xrstf.de/rudi/pkg/pathexpr" + genericpathexpr "go.xrstf.de/rudi/pkg/pathexpr" + "go.xrstf.de/rudi/pkg/runtime/pathexpr" + "go.xrstf.de/rudi/pkg/runtime/types" ) -func EvalTuple(ctx types.Context, tup ast.Tuple) (types.Context, any, error) { +func (i *interpreter) EvalTuple(ctx types.Context, tup ast.Tuple) (types.Context, any, error) { // Function calls are the only place where we check if the Go context has been cancelled. // This error should not be caught and swallowed by any other function, like `try` or `default`. if err := ctx.GoContext().Err(); err != nil { @@ -29,32 +30,20 @@ func EvalTuple(ctx types.Context, tup ast.Tuple) (types.Context, any, error) { return ctx, nil, errors.New("invalid tuple: first expression must be an identifier") } - resultCtx, result, err := EvalFunctionCall(ctx, identifier, tup.Expressions[1:]) + resultCtx, result, err := i.CallFunction(ctx, identifier, tup.Expressions[1:]) if err != nil { return ctx, nil, err } - if tup.PathExpression != nil { - deeper, err := TraversePathExpression(ctx, result, tup.PathExpression) - if err != nil { - return ctx, nil, err - } - - return resultCtx, deeper, nil + deeper, err := pathexpr.Apply(ctx, result, tup.PathExpression) + if err != nil { + return ctx, nil, err } - return resultCtx, result, nil -} - -type BangHandler interface { - // All functions work fine with the default bang handler ("set!", "append!", ...), except - // for "delete!", which requires special handling to make it work as expected. Custom bang - // handlers are useful to introducing side effects explicitly (so it becomes very clear if a - // function in Rudi has side effects or not). - BangHandler(ctx types.Context, args []ast.Expression, value any) (types.Context, any, error) + return resultCtx, deeper, nil } -func EvalFunctionCall(ctx types.Context, fun ast.Identifier, args []ast.Expression) (types.Context, any, error) { +func (*interpreter) CallFunction(ctx types.Context, fun ast.Identifier, args []ast.Expression) (types.Context, any, error) { funcName := fun.Name function, ok := ctx.GetFunction(funcName) if !ok { @@ -72,7 +61,7 @@ func EvalFunctionCall(ctx types.Context, fun ast.Identifier, args []ast.Expressi // if desired, update the context and introduce side effects if fun.Bang { // "delete!" has a special behaviour for the bang modifier, so do possibly some other functions. - if custom, ok := function.(BangHandler); ok { + if custom, ok := function.(types.BangHandler); ok { return custom.BangHandler(ctx, args, result) } @@ -100,7 +89,7 @@ func EvalFunctionCall(ctx types.Context, fun ast.Identifier, args []ast.Expressi // if the symbol has a path to traverse, do so if updateSymbol.PathExpression != nil { // pre-evaluate the path expression - pathExpr, err := EvalPathExpression(ctx, updateSymbol.PathExpression) + pathExpr, err := pathexpr.Eval(ctx, updateSymbol.PathExpression) if err != nil { return ctx, nil, fmt.Errorf("argument #0: invalid path expression: %w", err) } @@ -123,7 +112,7 @@ func EvalFunctionCall(ctx types.Context, fun ast.Identifier, args []ast.Expressi } // apply the path expression - updatedValue, err = pathexpr.Set(currentValue, pathexpr.FromEvaluatedPath(*pathExpr), updatedValue) + updatedValue, err = genericpathexpr.Set(currentValue, genericpathexpr.FromEvaluatedPath(*pathExpr), updatedValue) if err != nil { return ctx, nil, fmt.Errorf("cannot set value in %T at %s: %w", currentValue, pathExpr, err) } diff --git a/pkg/eval/eval_vector.go b/pkg/runtime/interpreter/eval_vector.go similarity index 54% rename from pkg/eval/eval_vector.go rename to pkg/runtime/interpreter/eval_vector.go index 7286ef5..b88b6b9 100644 --- a/pkg/eval/eval_vector.go +++ b/pkg/runtime/interpreter/eval_vector.go @@ -1,16 +1,17 @@ // SPDX-FileCopyrightText: 2023 Christoph Mewes // SPDX-License-Identifier: MIT -package eval +package interpreter import ( "fmt" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/pathexpr" + "go.xrstf.de/rudi/pkg/runtime/types" ) -func EvalVectorNode(ctx types.Context, vec ast.VectorNode) (types.Context, any, error) { +func (i *interpreter) EvalVectorNode(ctx types.Context, vec ast.VectorNode) (types.Context, any, error) { innerCtx := ctx result := make([]any, len(vec.Expressions)) @@ -19,26 +20,22 @@ func EvalVectorNode(ctx types.Context, vec ast.VectorNode) (types.Context, any, err error ) - for i, expr := range vec.Expressions { + for ii, expr := range vec.Expressions { // Keep overwriting the current context, so that e.g. variables // defined in one vector element can be used in all following // elements (no idea why you would define vars in vectors tho). - innerCtx, data, err = EvalExpression(innerCtx, expr) + innerCtx, data, err = i.EvalExpression(innerCtx, expr) if err != nil { return ctx, nil, fmt.Errorf("failed to eval expression %s: %w", expr.String(), err) } - result[i] = data + result[ii] = data } - if vec.PathExpression != nil { - deeper, err := TraversePathExpression(ctx, result, vec.PathExpression) - if err != nil { - return ctx, nil, err - } - - return ctx, deeper, nil + deeper, err := pathexpr.Apply(ctx, result, vec.PathExpression) + if err != nil { + return ctx, nil, err } - return ctx, result, nil + return ctx, deeper, nil } diff --git a/pkg/runtime/interpreter/interpreter.go b/pkg/runtime/interpreter/interpreter.go new file mode 100644 index 0000000..f505289 --- /dev/null +++ b/pkg/runtime/interpreter/interpreter.go @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2023 Christoph Mewes +// SPDX-License-Identifier: MIT + +package interpreter + +import ( + "go.xrstf.de/rudi/pkg/runtime/types" +) + +type interpreter struct{} + +var _ types.Runtime = &interpreter{} + +func New() types.Runtime { + return &interpreter{} +} diff --git a/pkg/eval/test/bool_test.go b/pkg/runtime/interpreter/test/bool_test.go similarity index 100% rename from pkg/eval/test/bool_test.go rename to pkg/runtime/interpreter/test/bool_test.go diff --git a/pkg/eval/test/expression_test.go b/pkg/runtime/interpreter/test/expression_test.go similarity index 100% rename from pkg/eval/test/expression_test.go rename to pkg/runtime/interpreter/test/expression_test.go diff --git a/pkg/eval/test/funcs.go b/pkg/runtime/interpreter/test/funcs.go similarity index 84% rename from pkg/eval/test/funcs.go rename to pkg/runtime/interpreter/test/funcs.go index 5758beb..c82770b 100644 --- a/pkg/eval/test/funcs.go +++ b/pkg/runtime/interpreter/test/funcs.go @@ -6,9 +6,8 @@ package test import ( "fmt" - "go.xrstf.de/rudi/pkg/eval" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/types" ) var ( @@ -18,7 +17,7 @@ var ( return nil, fmt.Errorf("expected 1 argument, got %d", len(args)) } - _, value, err := eval.EvalExpression(ctx, args[0]) + _, value, err := ctx.Runtime().EvalExpression(ctx, args[0]) return value, err }, "evaluates the given expression and returns its value"), @@ -31,7 +30,7 @@ var ( return nil, fmt.Errorf("expected 2 arguments, got %d", len(args)) } - _, value, err := eval.EvalExpression(ctx, args[1]) + _, value, err := ctx.Runtime().EvalExpression(ctx, args[1]) return value, err }, "sets a variable or accesses the global document, most often used with the bang modifier"), diff --git a/pkg/eval/test/identifier_test.go b/pkg/runtime/interpreter/test/identifier_test.go similarity index 100% rename from pkg/eval/test/identifier_test.go rename to pkg/runtime/interpreter/test/identifier_test.go diff --git a/pkg/eval/test/null_test.go b/pkg/runtime/interpreter/test/null_test.go similarity index 100% rename from pkg/eval/test/null_test.go rename to pkg/runtime/interpreter/test/null_test.go diff --git a/pkg/eval/test/number_test.go b/pkg/runtime/interpreter/test/number_test.go similarity index 100% rename from pkg/eval/test/number_test.go rename to pkg/runtime/interpreter/test/number_test.go diff --git a/pkg/eval/test/object_test.go b/pkg/runtime/interpreter/test/object_test.go similarity index 100% rename from pkg/eval/test/object_test.go rename to pkg/runtime/interpreter/test/object_test.go diff --git a/pkg/eval/test/program_test.go b/pkg/runtime/interpreter/test/program_test.go similarity index 100% rename from pkg/eval/test/program_test.go rename to pkg/runtime/interpreter/test/program_test.go diff --git a/pkg/eval/test/statement_test.go b/pkg/runtime/interpreter/test/statement_test.go similarity index 100% rename from pkg/eval/test/statement_test.go rename to pkg/runtime/interpreter/test/statement_test.go diff --git a/pkg/eval/test/string_test.go b/pkg/runtime/interpreter/test/string_test.go similarity index 100% rename from pkg/eval/test/string_test.go rename to pkg/runtime/interpreter/test/string_test.go diff --git a/pkg/eval/test/symbol_test.go b/pkg/runtime/interpreter/test/symbol_test.go similarity index 98% rename from pkg/eval/test/symbol_test.go rename to pkg/runtime/interpreter/test/symbol_test.go index da2cf40..6a861ae 100644 --- a/pkg/eval/test/symbol_test.go +++ b/pkg/runtime/interpreter/test/symbol_test.go @@ -7,9 +7,9 @@ import ( "fmt" "testing" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" "go.xrstf.de/rudi/pkg/pathexpr" + "go.xrstf.de/rudi/pkg/runtime/types" "go.xrstf.de/rudi/pkg/testutil" ) diff --git a/pkg/eval/test/tuple_test.go b/pkg/runtime/interpreter/test/tuple_test.go similarity index 99% rename from pkg/eval/test/tuple_test.go rename to pkg/runtime/interpreter/test/tuple_test.go index b79b657..61da2a2 100644 --- a/pkg/eval/test/tuple_test.go +++ b/pkg/runtime/interpreter/test/tuple_test.go @@ -7,8 +7,8 @@ import ( "context" "testing" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" + "go.xrstf.de/rudi/pkg/runtime/types" "go.xrstf.de/rudi/pkg/testutil" ) diff --git a/pkg/eval/test/vector_test.go b/pkg/runtime/interpreter/test/vector_test.go similarity index 100% rename from pkg/eval/test/vector_test.go rename to pkg/runtime/interpreter/test/vector_test.go diff --git a/pkg/eval/eval_path_expression.go b/pkg/runtime/pathexpr/eval.go similarity index 72% rename from pkg/eval/eval_path_expression.go rename to pkg/runtime/pathexpr/eval.go index b4d8997..232311f 100644 --- a/pkg/eval/eval_path_expression.go +++ b/pkg/runtime/pathexpr/eval.go @@ -1,18 +1,34 @@ // SPDX-FileCopyrightText: 2023 Christoph Mewes // SPDX-License-Identifier: MIT -package eval +package pathexpr import ( "fmt" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" - "go.xrstf.de/rudi/pkg/pathexpr" + genericpathexpr "go.xrstf.de/rudi/pkg/pathexpr" + "go.xrstf.de/rudi/pkg/runtime/types" ) -func EvalPathExpression(ctx types.Context, path *ast.PathExpression) (*ast.EvaluatedPathExpression, error) { - innerCtx := ctx +func Apply(ctx types.Context, value any, path *ast.PathExpression) (any, error) { + if path == nil { + return value, nil + } + + evaluated, err := Eval(ctx, path) + if err != nil { + return nil, fmt.Errorf("invalid path expression: %w", err) + } + + return Traverse(value, *evaluated) +} + +func Traverse(value any, path ast.EvaluatedPathExpression) (any, error) { + return genericpathexpr.Get(value, genericpathexpr.FromEvaluatedPath(path)) +} + +func Eval(ctx types.Context, path *ast.PathExpression) (*ast.EvaluatedPathExpression, error) { result := &ast.EvaluatedPathExpression{ Steps: []ast.EvaluatedPathStep{}, } @@ -24,6 +40,9 @@ func EvalPathExpression(ctx types.Context, path *ast.PathExpression) (*ast.Evalu return result, nil } + innerCtx := ctx + runtime := ctx.Runtime() + for _, step := range path.Steps { var ( evaluated any @@ -36,7 +55,7 @@ func EvalPathExpression(ctx types.Context, path *ast.PathExpression) (*ast.Evalu case ast.Identifier: evaluated = asserted.Name default: - innerCtx, evaluated, err = EvalExpression(innerCtx, step) + innerCtx, evaluated, err = runtime.EvalExpression(innerCtx, step) if err != nil { return nil, fmt.Errorf("invalid accessor: %w", err) } @@ -71,16 +90,3 @@ func convertToAccessor(evaluated any) (*ast.EvaluatedPathStep, error) { return nil, fmt.Errorf("cannot use %T in path expression", asserted) } } - -func TraversePathExpression(ctx types.Context, value any, path *ast.PathExpression) (any, error) { - evaluated, err := EvalPathExpression(ctx, path) - if err != nil { - return nil, fmt.Errorf("invalid path expression: %w", err) - } - - return TraverseEvaluatedPathExpression(value, *evaluated) -} - -func TraverseEvaluatedPathExpression(value any, path ast.EvaluatedPathExpression) (any, error) { - return pathexpr.Get(value, pathexpr.FromEvaluatedPath(path)) -} diff --git a/pkg/runtime/types/context.go b/pkg/runtime/types/context.go new file mode 100644 index 0000000..c7e2ee2 --- /dev/null +++ b/pkg/runtime/types/context.go @@ -0,0 +1,140 @@ +// SPDX-FileCopyrightText: 2023 Christoph Mewes +// SPDX-License-Identifier: MIT + +package types + +import ( + "context" + "errors" + + "go.xrstf.de/rudi/pkg/coalescing" + "go.xrstf.de/rudi/pkg/lang/ast" +) + +type Context struct { + ctx context.Context + document *Document + fixedFuncs Functions + userFuncs Functions + variables Variables + coalescer coalescing.Coalescer + runtime Runtime +} + +func NewContext(runtime Runtime, ctx context.Context, doc Document, variables Variables, funcs Functions, coalescer coalescing.Coalescer) (Context, error) { + if runtime == nil { + return Context{}, errors.New("no runtime provided") + } + + if ctx == nil { + ctx = context.Background() + } + + if variables == nil { + variables = NewVariables() + } + + if funcs == nil { + funcs = NewFunctions() + } + + if coalescer == nil { + coalescer = coalescing.NewStrict() + } + + return Context{ + ctx: ctx, + document: &doc, + fixedFuncs: funcs, + userFuncs: NewFunctions(), + variables: variables, + coalescer: coalescer, + runtime: runtime, + }, nil +} + +// Coalesce is named this way to make the frequent calls read fluently +// (for example "ctx.Coalesce().ToBool(...)"). +func (c Context) Coalesce() coalescing.Coalescer { + return c.coalescer +} + +func (c Context) GoContext() context.Context { + return c.ctx +} + +func (c Context) Runtime() Runtime { + return c.runtime +} + +func (c Context) GetDocument() *Document { + return c.document +} + +func (c Context) GetVariable(name string) (any, bool) { + return c.variables.Get(name) +} + +func (c Context) GetFunction(name string) (Function, bool) { + f, ok := c.fixedFuncs.Get(name) + if ok { + return f, true + } + + return c.userFuncs.Get(name) +} + +func (c Context) WithGoContext(ctx context.Context) Context { + clone := c.shallowCopy() + clone.ctx = ctx + + return clone +} + +func (c Context) WithVariable(name string, val any) Context { + clone := c.shallowCopy() + clone.variables = c.variables.With(name, val) + + return clone +} + +func (c Context) WithVariables(vars map[string]any) Context { + if len(vars) == 0 { + return c + } + + clone := c.shallowCopy() + clone.variables = c.variables.WithMany(vars) + + return clone +} + +func (c Context) WithCoalescer(coalescer coalescing.Coalescer) Context { + clone := c.shallowCopy() + clone.coalescer = coalescer + + return clone +} + +func (c Context) WithRudispaceFunction(funcName string, fun Function) Context { + clone := c.shallowCopy() + clone.userFuncs = c.userFuncs.DeepCopy().Set(funcName, fun) + + return clone +} + +func (c Context) shallowCopy() Context { + return Context{ + ctx: c.ctx, + document: c.document, + fixedFuncs: c.fixedFuncs, + userFuncs: c.userFuncs, + variables: c.variables, + coalescer: c.coalescer, + runtime: c.runtime, + } +} + +func MakeShim(val any) ast.Shim { + return ast.Shim{Value: val} +} diff --git a/pkg/runtime/types/document.go b/pkg/runtime/types/document.go new file mode 100644 index 0000000..8fd5da3 --- /dev/null +++ b/pkg/runtime/types/document.go @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Christoph Mewes +// SPDX-License-Identifier: MIT + +package types + +type Document struct { + data any +} + +func NewDocument(data any) (Document, error) { + return Document{ + data: data, + }, nil +} + +func (d *Document) Data() any { + return d.data +} + +func (d *Document) Set(wrappedData any) { + d.data = wrappedData +} diff --git a/pkg/runtime/types/functions.go b/pkg/runtime/types/functions.go new file mode 100644 index 0000000..7e47038 --- /dev/null +++ b/pkg/runtime/types/functions.go @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2023 Christoph Mewes +// SPDX-License-Identifier: MIT + +package types + +import ( + "go.xrstf.de/rudi/pkg/lang/ast" +) + +type Function interface { + Evaluate(ctx Context, args []ast.Expression) (any, error) + + // Description returns a short, one-line description of the function; markdown + // can be used to highlight other function names, like "behaves similar + // to `foo`, but …". + Description() string +} + +type BangHandler interface { + // All functions work fine with the default bang handler ("set!", "append!", ...), except + // for "delete!", which requires special handling to make it work as expected. Custom bang + // handlers are useful to introducing side effects explicitly (so it becomes very clear if a + // function in Rudi has side effects or not). + BangHandler(ctx Context, args []ast.Expression, value any) (Context, any, error) +} + +type TupleFunction func(ctx Context, args []ast.Expression) (any, error) + +type basicFunc struct { + f TupleFunction + desc string +} + +// NewFunction creates the lowest of low level functions in Rudi and should rarely be used by +// integrators/library developers. Use the helpers in the root package to define functions +// using reflection and pattern matching instead. +func NewFunction(f TupleFunction, description string) Function { + return basicFunc{ + f: f, + desc: description, + } +} + +var _ Function = basicFunc{} + +func (f basicFunc) Evaluate(ctx Context, args []ast.Expression) (any, error) { + return f.f(ctx, args) +} + +func (f basicFunc) Description() string { + return f.desc +} + +type Functions map[string]Function + +func NewFunctions() Functions { + return Functions{} +} + +func (f Functions) Get(name string) (Function, bool) { + variable, exists := f[name] + return variable, exists +} + +// Set sets/replaces the function in the current set (in-place). +// The function returns the same Functions to allow fluent access. +func (f Functions) Set(name string, fun Function) Functions { + f[name] = fun + return f +} + +// Set removes a function from the set. +// The function returns the same Functions to allow fluent access. +func (f Functions) Delete(name string) Functions { + delete(f, name) + return f +} + +// Add adds all functions from other to the current set. +// The function returns the same Functions to allow fluent access. +func (f Functions) Add(other Functions) Functions { + for name, fun := range other { + f[name] = fun + } + return f +} + +// Remove removes all functions from this set that are part of the other set, +// to enable constructs like AllFunctions.Remove(MathFunctions) +// The function returns the same Functions to allow fluent access. +func (f Functions) Remove(other Functions) Functions { + for name := range other { + f.Delete(name) + } + return f +} + +func (f Functions) DeepCopy() Functions { + result := NewFunctions() + for key, val := range f { + result[key] = val + } + return result +} diff --git a/pkg/runtime/types/runtime.go b/pkg/runtime/types/runtime.go new file mode 100644 index 0000000..4175876 --- /dev/null +++ b/pkg/runtime/types/runtime.go @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2023 Christoph Mewes +// SPDX-License-Identifier: MIT + +package types + +import ( + "go.xrstf.de/rudi/pkg/lang/ast" +) + +type Runtime interface { + EvalNull(ctx Context, n ast.Null) (Context, any, error) + EvalBool(ctx Context, b ast.Bool) (Context, any, error) + EvalNumber(ctx Context, n ast.Number) (Context, any, error) + EvalString(ctx Context, str ast.String) (Context, any, error) + EvalSymbol(ctx Context, sym ast.Symbol) (Context, any, error) + EvalVectorNode(ctx Context, vec ast.VectorNode) (Context, any, error) + EvalObjectNode(ctx Context, obj ast.ObjectNode) (Context, any, error) + EvalIdentifier(ctx Context, ident ast.Identifier) (Context, any, error) + EvalExpression(ctx Context, expr ast.Expression) (Context, any, error) + EvalTuple(ctx Context, tup ast.Tuple) (Context, any, error) + EvalStatement(ctx Context, stmt ast.Statement) (Context, any, error) + EvalProgram(ctx Context, p *ast.Program) (Context, any, error) + + CallFunction(ctx Context, fun ast.Identifier, args []ast.Expression) (Context, any, error) +} diff --git a/pkg/runtime/types/variables.go b/pkg/runtime/types/variables.go new file mode 100644 index 0000000..f1eeb18 --- /dev/null +++ b/pkg/runtime/types/variables.go @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2023 Christoph Mewes +// SPDX-License-Identifier: MIT + +package types + +type Variables map[string]any + +func NewVariables() Variables { + return Variables{} +} + +func (v Variables) Get(name string) (any, bool) { + variable, exists := v[name] + return variable, exists +} + +// Set sets/replaces the variable value in the current set (in-place). +// The function returns the same variables to allow fluent access. +func (v Variables) Set(name string, val any) Variables { + v[name] = val + return v +} + +// With returns a copy of the variables, with the new variable being added to it. +func (v Variables) With(name string, val any) Variables { + return v.DeepCopy().Set(name, val) +} + +// WithMany is like With(), but for adding multiple new variables at once. This +// should be preferred to With() to prevent unnecessary DeepCopies. +func (v Variables) WithMany(vars map[string]any) Variables { + if len(vars) == 0 { + return v + } + + out := v.DeepCopy() + for k, v := range vars { + out.Set(k, v) + } + return out +} + +func (v Variables) DeepCopy() Variables { + result := NewVariables() + for key, val := range v { + result[key] = val + } + return result +} diff --git a/pkg/testutil/testcase.go b/pkg/testutil/testcase.go index a93a581..7170de3 100644 --- a/pkg/testutil/testcase.go +++ b/pkg/testutil/testcase.go @@ -11,10 +11,10 @@ import ( "testing" "go.xrstf.de/rudi/pkg/coalescing" - "go.xrstf.de/rudi/pkg/eval" - "go.xrstf.de/rudi/pkg/eval/types" "go.xrstf.de/rudi/pkg/lang/ast" "go.xrstf.de/rudi/pkg/lang/parser" + "go.xrstf.de/rudi/pkg/runtime/interpreter" + "go.xrstf.de/rudi/pkg/runtime/types" "github.com/google/go-cmp/cmp" ) @@ -29,6 +29,7 @@ type Testcase struct { Variables types.Variables Functions types.Functions Coalescer coalescing.Coalescer + Runtime types.Runtime Expected any ExpectedDocument any @@ -80,7 +81,14 @@ func (tc *Testcase) eval(t *testing.T) (types.Context, any, error) { log.Fatalf("Failed to create parser document: %v", err) } - progContext := types.NewContext(tc.Context, doc, tc.Variables, tc.Functions, tc.Coalescer) + if tc.Runtime == nil { + tc.Runtime = interpreter.New() + } + + progContext, err := types.NewContext(tc.Runtime, tc.Context, doc, tc.Variables, tc.Functions, tc.Coalescer) + if err != nil { + t.Fatalf("Failed to create context: %v", err) + } if tc.Expression != "" { prog := strings.NewReader(tc.Expression) @@ -95,7 +103,7 @@ func (tc *Testcase) eval(t *testing.T) (types.Context, any, error) { t.Fatalf("Parsed result is not a ast.Program, but %T", got) } - return eval.EvalProgram(progContext, &program) + return tc.Runtime.EvalProgram(progContext, &program) } // To enable tests for programs and statements, we handle them explicitly @@ -104,11 +112,11 @@ func (tc *Testcase) eval(t *testing.T) (types.Context, any, error) { switch asserted := tc.AST.(type) { case ast.Program: - return eval.EvalProgram(progContext, &asserted) + return tc.Runtime.EvalProgram(progContext, &asserted) case ast.Statement: - return eval.EvalStatement(progContext, asserted) + return tc.Runtime.EvalStatement(progContext, asserted) default: - return eval.EvalExpression(progContext, tc.AST) + return tc.Runtime.EvalExpression(progContext, tc.AST) } } diff --git a/program.go b/program.go index 6571d2a..b2735e3 100644 --- a/program.go +++ b/program.go @@ -8,10 +8,10 @@ import ( "fmt" "io" - "go.xrstf.de/rudi/pkg/eval" "go.xrstf.de/rudi/pkg/lang/ast" "go.xrstf.de/rudi/pkg/lang/parser" "go.xrstf.de/rudi/pkg/printer" + "go.xrstf.de/rudi/pkg/runtime/interpreter" ) // Program is a parsed Rudi program, ready to be run (executed). Programs are @@ -82,7 +82,10 @@ func (p *rudiProgram) Run(ctx context.Context, data any, variables Variables, fu return nil, nil, fmt.Errorf("cannot process %T: %w", data, err) } - rudiCtx := NewContext(ctx, doc, variables, funcs, coalescer) + rudiCtx, err := NewContext(interpreter.New(), ctx, doc, variables, funcs, coalescer) + if err != nil { + return nil, nil, fmt.Errorf("failed to create context: %w", err) + } finalCtx, result, err := p.RunContext(rudiCtx) if err != nil { @@ -99,7 +102,7 @@ func (p *rudiProgram) Run(ctx context.Context, data any, variables Variables, fu // bare final context instead of its document's value. The result is still // the result of the final expression in the program. func (p *rudiProgram) RunContext(ctx Context) (finalCtx Context, result any, err error) { - finalCtx, result, err = eval.Run(ctx, p.prog) + finalCtx, result, err = ctx.Runtime().EvalProgram(ctx, p.prog) if err != nil { return ctx, nil, err }