diff --git a/eval/eval.go b/eval/eval.go index a437408a..0c4a7192 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -15,15 +15,22 @@ import ( ) type State struct { - env *object.Environment - Out io.Writer - LogOut io.Writer - NoLog bool // turn log() into println() (for EvalString) - cache Cache + env *object.Environment + Out io.Writer + LogOut io.Writer + NoLog bool // turn log() into println() (for EvalString) + cache Cache + extensions map[string]object.Extension } func NewState() *State { - return &State{env: object.NewEnvironment(), Out: os.Stdout, LogOut: os.Stdout, cache: NewCache()} + return &State{ + env: object.NewRootEnvironment(), + Out: os.Stdout, + LogOut: os.Stdout, + cache: NewCache(), + extensions: object.ExtraFunctions(), + } } func (s *State) ResetCache() { @@ -52,7 +59,7 @@ func (s *State) evalAssignment(right object.Object, node *ast.InfixExpression) o // let free assignments. id, ok := node.Left.(*ast.Identifier) if !ok { - return object.Error{Value: ""} + return object.Error{Value: "assignment to non identifier: " + node.Left.Value().DebugString()} } if rt := right.Type(); rt == object.ERROR { log.Warnf("can't assign %q: %v", right.Inspect(), right) @@ -81,7 +88,7 @@ func (s *State) evalPostfixExpression(node *ast.PostfixExpression) object.Object id := node.Prev.Literal() val, ok := s.env.Get(id) if !ok { - return object.Error{Value: ""} + return object.Error{Value: "identifier not found: " + id} } var toAdd int64 switch node.Type() { //nolint:exhaustive // we have default. @@ -104,7 +111,7 @@ func (s *State) evalPostfixExpression(node *ast.PostfixExpression) object.Object } // Doesn't unwrap return - return bubbles up. -func (s *State) evalInternal(node any) object.Object { +func (s *State) evalInternal(node any) object.Object { //nolint:funlen // we have a lot of cases. switch node := node.(type) { // Statements case *ast.Statements: @@ -166,7 +173,6 @@ func (s *State) evalInternal(node any) object.Object { return fn case *ast.CallExpression: f := s.evalInternal(node.Function) - name := node.Function.Value().Literal() if f.Type() == object.ERROR { return f } @@ -174,6 +180,10 @@ func (s *State) evalInternal(node any) object.Object { if oerr != nil { return *oerr } + if f.Type() == object.EXTENSION { + return s.applyExtension(f.(object.Extension), args) + } + name := node.Function.Value().Literal() return s.applyFunction(name, f, args) case *ast.ArrayLiteral: elements, oerr := s.evalExpressions(node.Elements) @@ -352,10 +362,37 @@ func evalArrayIndexExpression(array, index object.Object) object.Object { return arrayObject.Elements[idx] } +func (s *State) applyExtension(fn object.Extension, args []object.Object) object.Object { + l := len(args) + if l < fn.MinArgs { + return object.Error{Value: fmt.Sprintf("wrong number of arguments got=%d, want %s", + l, fn.Inspect())} // shows usage + } + if fn.MaxArgs != -1 && l > fn.MaxArgs { + return object.Error{Value: fmt.Sprintf("wrong number of arguments got=%d, want %s", + l, fn.Inspect())} // shows usage + } + for i, arg := range args { + if i >= len(fn.ArgTypes) { + break + } + // Auto promote integer to float if needed. + if fn.ArgTypes[i] == object.FLOAT && arg.Type() == object.INTEGER { + args[i] = object.Float{Value: float64(arg.(object.Integer).Value)} + continue + } + if fn.ArgTypes[i] != arg.Type() { + return object.Error{Value: fmt.Sprintf("wrong type of argument got=%s, want %s", + arg.Type(), fn.Inspect())} + } + } + return fn.Callback(args) +} + func (s *State) applyFunction(name string, fn object.Object, args []object.Object) object.Object { function, ok := fn.(object.Function) if !ok { - return object.Error{Value: ""} + return object.Error{Value: "not a function: " + fn.Type().String() + ":" + fn.Inspect()} } if v, output, ok := s.cache.Get(function.CacheKey, args); ok { log.Debugf("Cache hit for %s %v", function.CacheKey, args) @@ -386,7 +423,7 @@ func extendFunctionEnv(name string, fn object.Function, args []object.Object) (* env := object.NewEnclosedEnvironment(fn.Env) n := len(fn.Parameters) if len(args) != n { - return nil, &object.Error{Value: fmt.Sprintf("", + return nil, &object.Error{Value: fmt.Sprintf("wrong number of arguments for %s. got=%d, want=%d", name, len(args), n)} } for paramIdx, param := range fn.Parameters { @@ -412,9 +449,14 @@ func (s *State) evalExpressions(exps []ast.Node) ([]object.Object, *object.Error } func (s *State) evalIdentifier(node *ast.Identifier) object.Object { + // local var can shadow extensions. val, ok := s.env.Get(node.Literal()) + if ok { + return val + } + val, ok = s.extensions[node.Literal()] if !ok { - return object.Error{Value: ""} + return object.Error{Value: "identifier not found: " + node.Literal()} } return val } @@ -429,7 +471,7 @@ func (s *State) evalIfExpression(ie *ast.IfExpression) object.Object { log.LogVf("if %s is object.FALSE, picking else branch", ie.Condition.Value().DebugString()) return s.evalInternal(ie.Alternative) default: - return object.Error{Value: ""} + return object.Error{Value: "condition is not a boolean: " + condition.Inspect()} } } @@ -476,15 +518,15 @@ func (s *State) evalBangOperatorExpression(right object.Object) object.Object { case object.FALSE: return object.TRUE case object.NULL: - return object.Error{Value: ""} + return object.Error{Value: "not of object.NULL"} default: - return object.Error{Value: ""} + return object.Error{Value: "not of " + right.Inspect()} } } func (s *State) evalMinusPrefixOperatorExpression(right object.Object) object.Object { if right.Type() != object.INTEGER { - return object.Error{Value: ""} + return object.Error{Value: "minus of " + right.Inspect()} } value := right.(object.Integer).Value @@ -511,7 +553,7 @@ func (s *State) evalInfixExpression(operator token.Type, left, right object.Obje case operator == token.NOTEQ: return object.NativeBoolToBooleanObject(left != right) default: - return object.Error{Value: ""} + return object.Error{Value: "operation on non integers left=" + left.Inspect() + " right=" + right.Inspect()} } } @@ -526,7 +568,7 @@ func evalStringInfixExpression(operator token.Type, left, right object.Object) o case token.PLUS: return object.String{Value: leftVal + rightVal} default: - return object.Error{Value: fmt.Sprintf("", + return object.Error{Value: fmt.Sprintf("unknown operator: %s %s %s", left.Type(), operator, right.Type())} } } @@ -555,7 +597,7 @@ func evalArrayInfixExpression(operator token.Type, left, right object.Object) ob } return object.Array{Elements: append(leftVal, right.(object.Array).Elements...)} default: - return object.Error{Value: fmt.Sprintf("", + return object.Error{Value: fmt.Sprintf("unknown operator: %s %s %s", left.Type(), operator, right.Type())} } } @@ -581,7 +623,7 @@ func evalMapInfixExpression(operator token.Type, left, right object.Object) obje } return res default: - return object.Error{Value: fmt.Sprintf("", + return object.Error{Value: fmt.Sprintf("unknown operator: %s %s %s", left.Type(), operator, right.Type())} } } diff --git a/eval/eval_test.go b/eval/eval_test.go index c3281fc9..f25c775c 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -5,6 +5,7 @@ import ( "grol.io/grol/ast" "grol.io/grol/eval" + "grol.io/grol/extensions" "grol.io/grol/lexer" "grol.io/grol/object" "grol.io/grol/parser" @@ -232,31 +233,31 @@ func TestErrorHandling(t *testing.T) { }{ { "myfunc=func(x,y) {x+y}; myfunc(1)", - "", + "wrong number of arguments for myfunc. got=1, want=2", }, { "5 + true;", - "", + "operation on non integers left=5 right=true", }, { "5 + true; 5;", - "", + "operation on non integers left=5 right=true", }, { "-true", - "", + "minus of true", }, { "true + false;", - "", + "operation on non integers left=true right=false", }, { "5; true + false; 5", - "", + "operation on non integers left=true right=false", }, { "if (10 > 1) { true + false; }", - "", + "operation on non integers left=true right=false", }, { ` @@ -268,15 +269,15 @@ if (10 > 1) { return 1; } `, - "", + "operation on non integers left=true right=false", }, { "foobar", - "", + "identifier not found: foobar", }, { `"Hello" - "World"`, - "", + "unknown operator: STRING MINUS STRING", }, { `{"name": "Monkey"}[func(x) { x }];`, @@ -751,3 +752,35 @@ func testFloatObject(t *testing.T, obj object.Object, expected float64) bool { return true } + +func TestExtension(t *testing.T) { + err := extensions.Init() + if err != nil { + t.Fatalf("extensions.Init() failed: %v", err) + } + input := `pow` + evaluated := testEval(t, input) + expected := "pow(float, float)" + if evaluated.Inspect() != expected { + t.Errorf("object has wrong value. got=%s, want=%s", evaluated.Inspect(), expected) + } + input = `pow(2,10)` + evaluated = testEval(t, input) + testFloatObject(t, evaluated, 1024) + input = `round(2.7)` + evaluated = testEval(t, input) + testFloatObject(t, evaluated, 3) + input = `cos(PI)` // somehow getting 'exact' -1 for cos() but some 1e-16 for sin(). + evaluated = testEval(t, input) + testFloatObject(t, evaluated, -1) + input = `sprintf("%d %s %g", 42, "ab\ncd", pow(2, 43))` + evaluated = testEval(t, input) + expected = "42 ab\ncd 8.796093022208e+12" // might be brittle the %g output of float64. + actual, ok := evaluated.(object.String) + if !ok { + t.Errorf("object is not string. got=%T (%+v)", evaluated, evaluated) + } + if actual.Value != expected { + t.Errorf("object has wrong value. got=%q, want=%q", actual, expected) + } +} diff --git a/examples/sample.gr b/examples/sample.gr index 9f1cea2b..c54e4cdc 100644 --- a/examples/sample.gr +++ b/examples/sample.gr @@ -21,7 +21,7 @@ fact=func(n) { // first class function objects, can also be written as `func fac /* recursion: */ n*self(n-1) // also last evaluated expression is returned (ie return at the end is optional) } -a=[fact(5), "abc", 76-3] // array can contain different types +a=[fact(5), "abc", 76-3, sqrt(2)] // array can contain different types, grol also has math functions. m={"key": a, 73: 29} // so do maps diff --git a/extensions/extension.go b/extensions/extension.go new file mode 100644 index 00000000..ddd50df8 --- /dev/null +++ b/extensions/extension.go @@ -0,0 +1,101 @@ +// Package mappings some go built in functions to grol functions. +// Same mechanism can be used to map other go functions to grol functions and further extend the language. +package extensions + +import ( + "fmt" + "math" + + "grol.io/grol/object" +) + +var ( + initDone = false + errInInit error +) + +func Init() error { + if initDone { + return errInInit + } + errInInit = initInternal() + initDone = true + return errInInit +} + +type OneFloatInOutFunc func(float64) float64 + +func initInternal() error { + cmd := object.Extension{ + Name: "pow", + MinArgs: 2, + MaxArgs: 2, + ArgTypes: []object.Type{object.FLOAT, object.FLOAT}, + Callback: pow, + } + err := object.CreateFunction(cmd) + if err != nil { + return err + } + err = object.CreateFunction(object.Extension{ + Name: "sprintf", + MinArgs: 1, + MaxArgs: -1, + ArgTypes: []object.Type{object.STRING}, + Callback: sprintf, + }) + if err != nil { + return err + } + oneFloat := object.Extension{ + MinArgs: 1, + MaxArgs: 1, + ArgTypes: []object.Type{object.FLOAT}, + } + for _, function := range []struct { + fn OneFloatInOutFunc + name string + }{ + {math.Sin, "sin"}, + {math.Cos, "cos"}, + {math.Tan, "tan"}, + {math.Log, "ln"}, // proper name for natural logarithm and also doesn't conflict with logger builtin. + {math.Sqrt, "sqrt"}, + {math.Exp, "exp"}, + {math.Asin, "asin"}, + {math.Acos, "acos"}, + {math.Atan, "atan"}, + {math.Round, "round"}, + {math.Trunc, "trunc"}, + {math.Floor, "floor"}, + {math.Ceil, "ceil"}, + } { + oneFloat.Callback = func(args []object.Object) object.Object { + // Arg len check already done through MinArgs=MaxArgs=1 and + // type through ArgTypes: []object.Type{object.FLOAT}. + return object.Float{Value: function.fn(args[0].(object.Float).Value)} + } + oneFloat.Name = function.name + err = object.CreateFunction(oneFloat) + if err != nil { + return err + } + } + object.AddIdentifier("PI", object.Float{Value: math.Pi}) + object.AddIdentifier("E", object.Float{Value: math.E}) // using uppercase so "e" isn't taken/shadowed. + return nil +} + +func pow(args []object.Object) object.Object { + // Arg len check already done through MinArgs and MaxArgs + // and so is type check through ArgTypes. + base := args[0].(object.Float).Value + exp := args[1].(object.Float).Value + result := math.Pow(base, exp) + return object.Float{Value: result} +} + +func sprintf(args []object.Object) object.Object { + res := fmt.Sprintf(args[0].(object.String).Value, object.Unwrap(args[1:])...) + return object.String{Value: res} +} diff --git a/main.go b/main.go index d707d9eb..3d9c2739 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "fortio.org/cli" "fortio.org/log" "grol.io/grol/eval" + "grol.io/grol/extensions" // register extensions "grol.io/grol/repl" ) @@ -33,7 +34,10 @@ func Main() int { FormatOnly: *format, Compact: *compact, } - nArgs := len(flag.Args()) + err := extensions.Init() + if err != nil { + log.Fatalf("Error initializing extensions: %v", err) + } if *commandFlag != "" { res, errs, _ := repl.EvalString(*commandFlag) if len(errs) > 0 { @@ -42,7 +46,7 @@ func Main() int { fmt.Print(res) return len(errs) } - if nArgs == 0 { + if len(flag.Args()) == 0 { repl.Interactive(os.Stdin, os.Stdout, options) return 0 } diff --git a/main_test.txtar b/main_test.txtar index c3938cc4..46ff8152 100644 --- a/main_test.txtar +++ b/main_test.txtar @@ -26,7 +26,7 @@ stderr welcome !grol -c 'foo' stderr 'Errors' stderr 'identifier not found: foo' -stdout '>' +stdout '' # sample_test.gr grol sample_test.gr diff --git a/object/interp.go b/object/interp.go new file mode 100644 index 00000000..1fadcffa --- /dev/null +++ b/object/interp.go @@ -0,0 +1,71 @@ +package object + +import ( + "errors" + "maps" +) + +var ( + extraFunctions map[string]Extension + extraIdentifiers map[string]Object + initDone bool +) + +// Init resets the table of extended functions to empty. +// Optional, will be called on demand the first time through CreateFunction. +func Init() { + extraFunctions = make(map[string]Extension) + extraIdentifiers = make(map[string]Object) + initDone = true +} + +// CreateFunction adds a new function to the table of extended functions. +func CreateFunction(cmd Extension) error { + if !initDone { + Init() + } + if cmd.Name == "" { + return errors.New("empty command name") + } + if cmd.MaxArgs != -1 && cmd.MinArgs > cmd.MaxArgs { + return errors.New(cmd.Name + ": min args > max args") + } + if len(cmd.ArgTypes) < cmd.MinArgs { + return errors.New(cmd.Name + ": arg types < min args") + } + if _, ok := extraFunctions[cmd.Name]; ok { + return errors.New(cmd.Name + ": already defined") + } + extraFunctions[cmd.Name] = cmd + return nil +} + +func ExtraFunctions() map[string]Extension { + return extraFunctions +} + +// Add values to top level environment, e.g "pi" -> 3.14159... +// or "printf(){print(sprintf(%s, args...))}". +func AddIdentifier(name string, value Object) { + if !initDone { + Init() + } + extraIdentifiers[name] = value +} + +// This makes a copy of the extraIdentifiers map to serve as initial Environment without mutating the original. +// use to setup the root environment for the interpreter state. +func initialIdentifiersCopy() map[string]Object { + if !initDone { + Init() + } + return maps.Clone(extraIdentifiers) +} + +func Unwrap(objs []Object) []any { + res := make([]any, len(objs)) + for i, o := range objs { + res[i] = o.Unwrap() + } + return res +} diff --git a/object/object.go b/object/object.go index d3e0d78d..78364695 100644 --- a/object/object.go +++ b/object/object.go @@ -1,6 +1,7 @@ package object import ( + "fmt" "sort" "strconv" "strings" @@ -14,6 +15,7 @@ type Type uint8 type Object interface { Type() Type Inspect() string + Unwrap() any } const ( @@ -30,6 +32,7 @@ const ( MAP QUOTE MACRO + EXTENSION LAST ) @@ -118,6 +121,10 @@ func (i Integer) Inspect() string { return strconv.FormatInt(i.Value, 10) } +func (i Integer) Unwrap() any { + return i.Value +} + func (i Integer) Type() Type { return INTEGER } @@ -126,6 +133,10 @@ type Float struct { Value float64 } +func (f Float) Unwrap() any { + return f.Value +} + func (f Float) Type() Type { return FLOAT } @@ -138,6 +149,10 @@ type Boolean struct { Value bool } +func (b Boolean) Unwrap() any { + return b.Value +} + func (b Boolean) Type() Type { return BOOLEAN } @@ -150,6 +165,10 @@ type String struct { Value string } +func (s String) Unwrap() any { + return s.Value +} + func (s String) Type() Type { return STRING } @@ -160,6 +179,7 @@ func (s String) Inspect() string { type Null struct{} +func (n Null) Unwrap() any { return nil } func (n Null) Type() Type { return NIL } func (n Null) Inspect() string { return "nil" } @@ -167,6 +187,8 @@ type Error struct { Value string // message } +func (e Error) Unwrap() any { return e } +func (e Error) Error() string { return e.Value } func (e Error) Type() Type { return ERROR } func (e Error) Inspect() string { return "" } @@ -174,6 +196,7 @@ type ReturnValue struct { Value Object } +func (rv ReturnValue) Unwrap() any { return rv.Value } func (rv ReturnValue) Type() Type { return RETURN } func (rv ReturnValue) Inspect() string { return rv.Value.Inspect() } @@ -196,7 +219,8 @@ func WriteStrings(out *strings.Builder, list []Object, before, sep, after string out.WriteString(after) } -func (f Function) Type() Type { return FUNC } +func (f Function) Unwrap() any { return f } +func (f Function) Type() Type { return FUNC } // Must be called after the function is fully initialized. // Whether a function result should be cached doesn't depend on the Name, @@ -233,7 +257,8 @@ type Array struct { Elements []Object } -func (ao Array) Type() Type { return ARRAY } +func (ao Array) Unwrap() any { return Unwrap(ao.Elements) } +func (ao Array) Type() Type { return ARRAY } func (ao Array) Inspect() string { out := strings.Builder{} WriteStrings(&out, ao.Elements, "[", ",", "]") @@ -288,6 +313,14 @@ func (mk MapKeys) Swap(i, j int) { mk[i], mk[j] = mk[j], mk[i] } +func (m Map) Unwrap() any { + res := make(map[any]any, len(m)) + for k, v := range m { + res[k.Unwrap()] = v.Unwrap() + } + return res +} + func (m Map) Type() Type { return MAP } func (m Map) Inspect() string { @@ -316,7 +349,8 @@ type Quote struct { Node ast.Node } -func (q Quote) Type() Type { return QUOTE } +func (q Quote) Unwrap() any { return q.Node } +func (q Quote) Type() Type { return QUOTE } func (q Quote) Inspect() string { out := strings.Builder{} out.WriteString("quote(") @@ -331,7 +365,8 @@ type Macro struct { Env *Environment } -func (m Macro) Type() Type { return MACRO } +func (m Macro) Unwrap() any { return m } +func (m Macro) Type() Type { return MACRO } func (m Macro) Inspect() string { out := strings.Builder{} out.WriteString("macro(") @@ -342,3 +377,43 @@ func (m Macro) Inspect() string { out.WriteString("}") return out.String() } + +// Extensions are functions implemented in go and exposed to grol. +type Extension struct { + Name string // Name to make the function available as in grol. + MinArgs int // Minimum number of arguments required. + MaxArgs int // Maximum number of arguments allowed. -1 for unlimited. + ArgTypes []Type // Type of each argument, provided at least up to MinArgs. + Callback ExtFunction // The go function or lambda to call when the grol by Name(...) is invoked. +} + +// ExtFunction is the signature of what grol will call when the extension is invoked. +// Incoming arguments are validated for type and number of arguments based on [Extension]. +type ExtFunction func(args []Object) Object + +func (e *Extension) Usage(out *strings.Builder) { + for i := 1; i <= e.MinArgs; i++ { + if i > 1 { + out.WriteString(", ") + } + t := strings.ToLower(e.ArgTypes[i-1].String()) + out.WriteString(t) + } + switch { + case e.MaxArgs < 0: + out.WriteString(", ...") + case e.MaxArgs > e.MinArgs: + out.WriteString(fmt.Sprintf(", arg%d...arg%d", e.MinArgs+1, e.MaxArgs)) + } +} + +func (e Extension) Unwrap() any { return e } +func (e Extension) Type() Type { return EXTENSION } +func (e Extension) Inspect() string { + out := strings.Builder{} + out.WriteString(e.Name) + out.WriteString("(") + e.Usage(&out) + out.WriteString(")") + return out.String() +} diff --git a/object/object_test.go b/object/object_test.go index e8f08ed5..6dd3e869 100644 --- a/object/object_test.go +++ b/object/object_test.go @@ -29,3 +29,27 @@ func TestStringMapKey(t *testing.T) { t.Errorf("values aren't equal, unexpected") } } + +func TestExtensionUsage(t *testing.T) { + cmd := object.Extension{ + Name: "cmdname", + ArgTypes: []object.Type{ + object.INTEGER, + object.FLOAT, + object.STRING, + }, + MinArgs: 3, + MaxArgs: 6, + } + actual := cmd.Inspect() + expected := "cmdname(integer, float, string, arg4...arg6)" + if actual != expected { + t.Errorf("cmd.Inspect() test 3-6 args got %q, expected %q", actual, expected) + } + cmd.MaxArgs = -1 + actual = cmd.Inspect() + expected = "cmdname(integer, float, string, ...)" + if actual != expected { + t.Errorf("cmd.Inspect() test unlimited args got %q, expected %q", actual, expected) + } +} diff --git a/object/state.go b/object/state.go index bb354186..ee92fa56 100644 --- a/object/state.go +++ b/object/state.go @@ -7,8 +7,8 @@ type Environment struct { outer *Environment } -func NewEnvironment() *Environment { - s := make(map[string]Object) +func NewRootEnvironment() *Environment { + s := initialIdentifiersCopy() return &Environment{store: s} } @@ -34,7 +34,6 @@ func (e *Environment) Set(name string, val Object) Object { } func NewEnclosedEnvironment(outer *Environment) *Environment { - env := NewEnvironment() - env.outer = outer + env := &Environment{store: make(map[string]Object), outer: outer} return env } diff --git a/object/type_string.go b/object/type_string.go index 670860b5..880297ca 100644 --- a/object/type_string.go +++ b/object/type_string.go @@ -21,12 +21,13 @@ func _() { _ = x[MAP-10] _ = x[QUOTE-11] _ = x[MACRO-12] - _ = x[LAST-13] + _ = x[EXTENSION-13] + _ = x[LAST-14] } -const _Type_name = "UNKNOWNINTEGERFLOATBOOLEANNILERRORRETURNFUNCSTRINGARRAYMAPQUOTEMACROLAST" +const _Type_name = "UNKNOWNINTEGERFLOATBOOLEANNILERRORRETURNFUNCSTRINGARRAYMAPQUOTEMACROEXTENSIONLAST" -var _Type_index = [...]uint8{0, 7, 14, 19, 26, 29, 34, 40, 44, 50, 55, 58, 63, 68, 72} +var _Type_index = [...]uint8{0, 7, 14, 19, 26, 29, 34, 40, 44, 50, 55, 58, 63, 68, 77, 81} func (i Type) String() string { if i >= Type(len(_Type_index)-1) { diff --git a/repl/repl_test.go b/repl/repl_test.go index dc83aba2..815d8d88 100644 --- a/repl/repl_test.go +++ b/repl/repl_test.go @@ -116,7 +116,7 @@ func TestEvalStringEvalError(t *testing.T) { ` - expected := ">" + expected := "" res, errs, formatted := repl.EvalString(s) if len(errs) == 0 { t.Fatalf("EvalString() got no errors (res %q), expected some", res) diff --git a/wasm/grol_wasm.html b/wasm/grol_wasm.html index 11a6bf3a..00cab343 100644 --- a/wasm/grol_wasm.html +++ b/wasm/grol_wasm.html @@ -113,7 +113,7 @@ } n*fact(n-1) // recursion } -a=[fact(5), "abc", 76-3] // heterogeneous array +a=[fact(5), "abc", sqrt(2)] // heterogeneous array, math functions /* maps also can have any key,value types: */ m={"str key": a, 42: "str val"} diff --git a/wasm/wasm_main.go b/wasm/wasm_main.go index 18b450be..9771945d 100644 --- a/wasm/wasm_main.go +++ b/wasm/wasm_main.go @@ -15,6 +15,7 @@ import ( "fortio.org/cli" "fortio.org/log" "fortio.org/version" + "grol.io/grol/extensions" "grol.io/grol/repl" ) @@ -54,5 +55,9 @@ func main() { global := js.Global() global.Set("grol", js.FuncOf(jsEval)) global.Set("grolVersion", js.ValueOf(grolVersion)) + err := extensions.Init() + if err != nil { + log.Critf("Error initializing extensions: %v", err) + } <-done }