From ab2ebd33484ed7df7c6835d22153864cd8968421 Mon Sep 17 00:00:00 2001 From: Laurent Demailly Date: Fri, 20 Sep 2024 15:27:58 -0700 Subject: [PATCH] Added Register (for int64) type. (#240) * Added Register (for int64) type. Rename object.Value() ObjValue() so an object can be both ast.Node and ast.Object * linter * Verify in log verbose that the substitution of registers work, improve slightly perf * make register a pointer to save a ton of alloc and return them unchanged * setup registers for (integer) function arguments too - boxed_text.gr is erroring out so this is a checkpoint * wip fixing the scripts... but ... maybe it's not good to have to make all these changes that registers break * fix array with registers * split CopyRegister value from Value() so object arrays passed to type() retain the reference types * Fix #224 * make str() a built in with correct [] wrapping needed. adding new test file forgotten earlier. * more details on NoErr regexp mismatch, reduced the failure * further reduce * Change modify to allow abort/ok/not ok results; allows to drop ok field from register and stop early when needed * linters * update scripts for int to float issues * Faster examples for regular tests * Adding another prime sieve example --- ast/modify.go | 134 +++++++++++++--- ast/modify_test.go | 6 +- eval/eval.go | 226 +++++++++++++++++++-------- eval/eval_api.go | 12 +- eval/eval_test.go | 31 ++-- eval/macro_expension.go | 2 +- eval/quote_unquote.go | 2 +- examples/boxed_text.gr | 13 +- examples/circle.gr | 48 +++--- examples/image.gr | 4 +- examples/loop.gr | 10 ++ examples/mandelbrot.gr | 4 +- examples/prime_sieve_iter_compact.gr | 31 ++++ extensions/extension.go | 46 ++++-- main_test.txtar | 8 +- object/object.go | 76 +++++++-- object/state.go | 56 +++++-- object/type_string.go | 7 +- tests/_lib.gr | 13 +- tests/registers.gr | 46 ++++++ token/token.go | 1 + token/type_string.go | 133 ++++++++-------- 22 files changed, 654 insertions(+), 255 deletions(-) create mode 100644 examples/loop.gr create mode 100644 examples/prime_sieve_iter_compact.gr create mode 100644 tests/registers.gr diff --git a/ast/modify.go b/ast/modify.go index f7320423..a208bf85 100644 --- a/ast/modify.go +++ b/ast/modify.go @@ -1,62 +1,124 @@ package ast -import "fmt" +import ( + "fmt" + + "fortio.org/log" +) + +func ModifyNoOk(node Node, f func(Node) Node) Node { + newNode, _ := Modify(node, func(n Node) (Node, bool) { + return f(n), true + }) + return newNode +} // Note, this is somewhat similar to eval.go's eval... both are "apply"ing. -func Modify(node Node, f func(Node) Node) Node { //nolint:funlen,gocyclo,gocognit // yeah lots of types. - // TODO: add err checks for _s. +func Modify(node Node, f func(Node) (Node, bool)) (Node, bool) { //nolint:funlen,gocyclo,gocognit,maintidx // yeah lots of types. + // It's quite ugly all these continuation/ok checks. + var cont bool switch node := node.(type) { case *Statements: newNode := &Statements{Base: node.Base, Statements: make([]Node, len(node.Statements))} for i, statement := range node.Statements { - newNode.Statements[i] = Modify(statement, f) + newNode.Statements[i], cont = Modify(statement, f) + if !cont { + return nil, false + } } return f(newNode) case *InfixExpression: newNode := &InfixExpression{Base: node.Base} - newNode.Left = Modify(node.Left, f) - newNode.Right = Modify(node.Right, f) + newNode.Left, cont = Modify(node.Left, f) + if !cont { + return nil, false + } + newNode.Right, cont = Modify(node.Right, f) + if !cont { + return nil, false + } return f(newNode) case *PrefixExpression: newNode := &PrefixExpression{Base: node.Base} - newNode.Right = Modify(node.Right, f) + newNode.Right, cont = Modify(node.Right, f) + if !cont { + return nil, false + } return f(newNode) case *IndexExpression: newNode := &IndexExpression{Base: node.Base} - newNode.Left = Modify(node.Left, f) - newNode.Index = Modify(node.Index, f) + newNode.Left, cont = Modify(node.Left, f) + if !cont { + return nil, false + } + newNode.Index, cont = Modify(node.Index, f) + if !cont { + return nil, false + } return f(newNode) case *IfExpression: newNode := &IfExpression{Base: node.Base} - newNode.Condition = Modify(node.Condition, f) - newNode.Consequence = Modify(node.Consequence, f).(*Statements) + newNode.Condition, cont = Modify(node.Condition, f) + if !cont { + return nil, false + } + nc, ok := Modify(node.Consequence, f) + if !ok { + return nil, false + } + newNode.Consequence = nc.(*Statements) if node.Alternative != nil { - newNode.Alternative = Modify(node.Alternative, f).(*Statements) + nc, ok = Modify(node.Alternative, f) + if !ok { + return nil, false + } + newNode.Alternative = nc.(*Statements) } return f(newNode) case *ForExpression: newNode := &ForExpression{Base: node.Base} - newNode.Condition = Modify(node.Condition, f) - newNode.Body = Modify(node.Body, f).(*Statements) + newNode.Condition, cont = Modify(node.Condition, f) + if !cont { + return nil, false + } + nb, ok := Modify(node.Body, f) + if !ok { + return nil, false + } + newNode.Body = nb.(*Statements) return f(newNode) case *ReturnStatement: newNode := &ReturnStatement{Base: node.Base} if node.ReturnValue != nil { - newNode.ReturnValue = Modify(node.ReturnValue, f) + newNode.ReturnValue, cont = Modify(node.ReturnValue, f) + if !cont { + return nil, false + } } return f(newNode) case *FunctionLiteral: newNode := *node newNode.Parameters = make([]Node, len(node.Parameters)) for i := range node.Parameters { - newNode.Parameters[i] = Modify(node.Parameters[i], f).(*Identifier) + id, ok := Modify(node.Parameters[i], f) + if !ok { + return nil, false + } + newNode.Parameters[i] = id.(*Identifier) } - newNode.Body = Modify(node.Body, f).(*Statements) + nb, ok := Modify(node.Body, f) + if !ok { + return nil, false + } + newNode.Body = nb.(*Statements) return f(&newNode) case *ArrayLiteral: newNode := &ArrayLiteral{Base: node.Base, Elements: make([]Node, len(node.Elements))} for i := range node.Elements { - newNode.Elements[i] = Modify(node.Elements[i], f) + newNode.Elements[i], cont = Modify(node.Elements[i], f) + if !cont { + return nil, false + } } return f(newNode) case *MapLiteral: @@ -66,9 +128,15 @@ func Modify(node Node, f func(Node) Node) Node { //nolint:funlen,gocyclo,gocogni if !ok { panic(fmt.Sprintf("key %v not in pairs for map %v", key, node)) } - newKey := Modify(key, f) + newKey, ok := Modify(key, f) + if !ok { + return nil, false + } newNode.Order = append(newNode.Order, newKey) - newNode.Pairs[newKey] = Modify(val, f) + newNode.Pairs[newKey], cont = Modify(val, f) + if !cont { + return nil, false + } } return f(newNode) case *Identifier: @@ -98,14 +166,20 @@ func Modify(node Node, f func(Node) Node) Node { //nolint:funlen,gocyclo,gocogni case *Builtin: newNode := &Builtin{Base: node.Base, Parameters: make([]Node, len(node.Parameters))} for i := range node.Parameters { - newNode.Parameters[i] = Modify(node.Parameters[i], f) + newNode.Parameters[i], cont = Modify(node.Parameters[i], f) + if !cont { + return nil, false + } } return f(newNode) case *CallExpression: newNode := *node newNode.Arguments = make([]Node, len(node.Arguments)) for i := range node.Arguments { - newNode.Arguments[i] = Modify(node.Arguments[i], f) + newNode.Arguments[i], cont = Modify(node.Arguments[i], f) + if !cont { + return nil, false + } } return f(&newNode) case *MacroLiteral: @@ -113,11 +187,21 @@ func Modify(node Node, f func(Node) Node) Node { //nolint:funlen,gocyclo,gocogni newNode := *node newNode.Parameters = make([]Node, len(node.Parameters)) for i := range node.Parameters { - newNode.Parameters[i] = Modify(node.Parameters[i], f).(*Identifier) + id, ok := Modify(node.Parameters[i], f) + if !ok { + return nil, false + } + newNode.Parameters[i] = id.(*Identifier) + } + nb, ok := Modify(node.Body, f) + if !ok { + return nil, false } - newNode.Body = Modify(node.Body, f).(*Statements) + newNode.Body = nb.(*Statements) return f(&newNode) default: - panic(fmt.Sprintf("Modify not implemented for node type %T", node)) + log.Debugf("Modify not implemented for node type %T", node) + return f(node) + // panic(fmt.Sprintf("Modify not implemented for node type %T", node)) } } diff --git a/ast/modify_test.go b/ast/modify_test.go index 1f9c4a23..35107b16 100644 --- a/ast/modify_test.go +++ b/ast/modify_test.go @@ -26,7 +26,7 @@ var ( } ) -func TestModify(t *testing.T) { +func TestModifyNoOk(t *testing.T) { tests := []struct { input Node expected Node @@ -107,7 +107,7 @@ func TestModify(t *testing.T) { }, } for _, tt := range tests { - modified := Modify(tt.input, turnOneIntoTwo) + modified := ModifyNoOk(tt.input, turnOneIntoTwo) if !reflect.DeepEqual(modified, tt.expected) { t.Errorf("not equal.\n%#v\n-vs-\n%#v", modified, tt.expected) } @@ -117,7 +117,7 @@ func TestModify(t *testing.T) { func TestModifyMap(t *testing.T) { // need to test map separately because deepequal can't compare a map // with keys of the same underlying value (2:2, 2:2) - modified := Modify( + modified := ModifyNoOk( &MapLiteral{ Pairs: map[Node]Node{ aone: one(), diff --git a/eval/eval.go b/eval/eval.go index 6be1a498..361b8cca 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -31,12 +31,21 @@ func (s *State) evalAssignment(right object.Object, node *ast.InfixExpression) o index := s.Eval(idxE.Index) return s.evalIndexAssigment(idxE.Left, index, right) case token.IDENT: - id, _ := node.Left.(*ast.Identifier) + id := node.Left.(*ast.Identifier) name := id.Literal() log.LogVf("eval assign %#v to %s", right, name) // Propagate possible error (constant, extension names setting). // Distinguish between define and assign, define (:=) forces a new variable. return s.env.CreateOrSet(name, right, node.Type() == token.DEFINE) + case token.REGISTER: + reg := node.Left.(*object.Register) + log.LogVf("eval assign %#v to register %d", right, reg.Idx) + intVal, ok := Int64Value(right) + if !ok { + return s.NewError("register assignment of non integer: " + right.Inspect()) + } + *reg.Ptr() = intVal + return right default: return s.NewError("assignment to non identifier: " + node.Left.Value().DebugString()) } @@ -54,10 +63,10 @@ func (s *State) evalIndexAssigment(which ast.Node, index, value object.Object) o val = object.Value(val) // deref. switch val.Type() { case object.ARRAY: - if index.Type() != object.INTEGER { + idx, ok := Int64Value(index) + if !ok { return s.NewError("index assignment to array with non integer index: " + index.Inspect()) } - idx := index.(object.Integer).Value if idx < 0 { idx = int64(object.Len(val)) + idx } @@ -170,6 +179,9 @@ func (s *State) evalInternal(node any) object.Object { //nolint:funlen,gocognit, return s.Error(s.Context.Err()) } switch node := node.(type) { + case *object.Register: + // somehow returning unwrapped node as is for Eval to unwrap is more expensive (escape analysis issue?) + return node // Statements case *ast.Statements: if node == nil { // TODO: only here? this comes from empty else branches. @@ -234,10 +246,8 @@ func (s *State) evalInternal(node any) object.Object { //nolint:funlen,gocognit, return object.Integer{Value: node.Val} case *ast.FloatLiteral: return object.Float{Value: node.Val} - case *ast.Boolean: return object.NativeBoolToBooleanObject(node.Val) - case *ast.StringLiteral: return object.String{Value: node.Literal()} @@ -466,11 +476,11 @@ func (s *State) evalIndexRangeExpression(left object.Object, leftIdx, rightIdx a log.Debugf("eval index %s[%s:%s]", left.Inspect(), leftIndex.Inspect(), rightIndex.Inspect()) } } - if leftIndex.Type() != object.INTEGER || (!nilRight && rightIndex.Type() != object.INTEGER) { + if !object.IsIntType(leftIndex.Type()) || (!nilRight && !object.IsIntType(rightIndex.Type())) { return s.NewError("range index not integer") } num := object.Len(left) - l := leftIndex.(object.Integer).Value + l, _ := Int64Value(leftIndex) if l < 0 { // negative is relative to the end. l = int64(num) + l } @@ -478,7 +488,7 @@ func (s *State) evalIndexRangeExpression(left object.Object, leftIdx, rightIdx a if nilRight { r = int64(num) } else { - r = rightIndex.(object.Integer).Value + r, _ = Int64Value(rightIndex) if r < 0 { r = int64(num) + r } @@ -504,13 +514,16 @@ func (s *State) evalIndexRangeExpression(left object.Object, leftIdx, rightIdx a } func (s *State) evalIndexExpressionIdx(left, index object.Object) object.Object { - idxOrZero := index - if idxOrZero.Type() == object.NIL { - idxOrZero = object.Integer{Value: 0} + var idx int64 + var isInt bool + if index.Type() == object.NIL { + idx = 0 + isInt = true + } else { + idx, isInt = Int64Value(index) } switch { - case left.Type() == object.STRING && idxOrZero.Type() == object.INTEGER: - idx := idxOrZero.(object.Integer).Value + case left.Type() == object.STRING && isInt: str := left.(object.String).Value num := len(str) if idx < 0 { // negative is relative to the end. @@ -520,8 +533,8 @@ func (s *State) evalIndexExpressionIdx(left, index object.Object) object.Object return object.NULL } return object.Integer{Value: int64(str[idx])} - case left.Type() == object.ARRAY && idxOrZero.Type() == object.INTEGER: - return evalArrayIndexExpression(left, idxOrZero) + case left.Type() == object.ARRAY && isInt: + return evalArrayIndexExpression(left, idx) case left.Type() == object.MAP: return evalMapIndexExpression(left, index) case left.Type() == object.NIL: @@ -540,8 +553,7 @@ func evalMapIndexExpression(assoc, key object.Object) object.Object { return v // already unwrapped (index has been Eval'ed) } -func evalArrayIndexExpression(array, index object.Object) object.Object { - idx := index.(object.Integer).Value +func evalArrayIndexExpression(array object.Object, idx int64) object.Object { maxV := int64(object.Len(array) - 1) if idx < 0 { // negative is relative to the end. idx = maxV + 1 + idx // elsewhere we use len() but here maxV is len-1 @@ -614,7 +626,7 @@ func (s *State) applyFunction(name string, fn object.Object, args []object.Objec return s.NewError("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) + log.Debugf("Cache hit for %s %v -> %#v", function.CacheKey, args, v) if len(output) > 0 { _, err := s.Out.Write(output) if err != nil { @@ -623,7 +635,7 @@ func (s *State) applyFunction(name string, fn object.Object, args []object.Objec } return v } - nenv, oerr := s.extendFunctionEnv(s.env, name, function, args) + nenv, newBody, oerr := s.extendFunctionEnv(s.env, name, function, args) if oerr != nil { return *oerr } @@ -635,7 +647,7 @@ func (s *State) applyFunction(name string, fn object.Object, args []object.Objec // This is 0 as the env is new, but... we just want to make sure there is // no get() up stack to confirm the function might be cacheable. before := s.env.GetMisses() - res := s.Eval(function.Body) // Need to have the return value unwrapped. Fixes bug #46, also need to count recursion. + res := s.Eval(newBody) // Need to have the return value unwrapped. Fixes bug #46, also need to count recursion. after := s.env.GetMisses() // restore the previous env/state. s.env = curState @@ -668,14 +680,12 @@ func (s *State) extendFunctionEnv( currrentEnv *object.Environment, name string, fn object.Function, args []object.Object, -) (*object.Environment, *object.Error) { +) (*object.Environment, ast.Node, *object.Error) { // https://github.com/grol-io/grol/issues/47 // fn.Env is "captured state", but for recursion we now parent from current state; eg // func test(n) {if (n==2) {x=1}; if (n==1) {return x}; test(n-1)}; test(3) // return 1 (state set by recursion with n==2) - // Make sure `self` is used to recurse, or named function, otherwise the function will - // need to be found way up the now much deeper stack. - env, sameFunction := object.NewFunctionEnvironment(fn, currrentEnv) + env, _ := object.NewFunctionEnvironment(fn, currrentEnv) params := fn.Parameters atLeast := "" var extra []object.Object @@ -696,30 +706,44 @@ func (s *State) extendFunctionEnv( if len(args) != n { oerr := s.Errorf("wrong number of arguments for %s. got=%d, want%s=%d", name, len(args), atLeast, n) - return nil, &oerr + return nil, nil, &oerr } + var newBody ast.Node + newBody = fn.Body for paramIdx, param := range params { // By definition function parameters are local copies, deref argument values: - oerr := env.CreateOrSet(param.Value().Literal(), object.Value(args[paramIdx]), true) - if log.LogVerbose() { - log.LogVf("set %s to %s - %s", param.Value().Literal(), args[paramIdx].Inspect(), oerr.Inspect()) + pval := object.Value(args[paramIdx]) + needVariable := true + if pval.Type() == object.INTEGER { + // We will release all these registers just by returning/dropping the env. + _, nbody, ok := setupRegister(env, param.Value().Literal(), pval.(object.Integer).Value, newBody) + if ok { + newBody = nbody + needVariable = false + } } - if oerr.Type() == object.ERROR { - oe, _ := oerr.(object.Error) - return nil, &oe + if needVariable { + oerr := env.CreateOrSet(param.Value().Literal(), pval, true) + if log.LogVerbose() { + log.LogVf("set %s to %s - %s", param.Value().Literal(), args[paramIdx].Inspect(), oerr.Inspect()) + } + if oerr.Type() == object.ERROR { + oe, _ := oerr.(object.Error) + return nil, nil, &oe + } } } if fn.Variadic { env.SetNoChecks("..", object.NewArray(extra), true) } - // for recursion in anonymous functions. - // TODO: consider not having to keep setting this in the function's env and treating as a keyword or magic var like info. - env.SetNoChecks("self", fn, true) + // Recursion is handle specially in Get (defining "self" and the function name in the env) // For recursion in named functions, set it here so we don't need to go up a stack of 50k envs to find it - if sameFunction && name != "" { - env.SetNoChecks(name, fn, true) - } - return env, nil + /* + if sameFunction && name != "" { + env.SetNoChecks(name, fn, true) + } + */ + return env, newBody, nil } func (s *State) evalExpressions(exps []ast.Node) ([]object.Object, *object.Error) { @@ -730,7 +754,7 @@ func (s *State) evalExpressions(exps []ast.Node) ([]object.Object, *object.Error oerr := evaluated.(object.Error) return nil, &oerr } - result = append(result, evaluated) + result = append(result, object.CopyRegister(evaluated)) } return result, nil } @@ -770,23 +794,71 @@ func (s *State) evalIfExpression(ie *ast.IfExpression) object.Object { } } -func (s *State) evalForInteger(fe *ast.ForExpression, start *object.Integer, end object.Integer, name string) object.Object { +func ModifyRegister(register *object.Register, in ast.Node) (ast.Node, bool) { + switch in := in.(type) { + case *ast.Identifier: + if in.Literal() == register.Literal() { + register.Count++ + return register, true + } + case *ast.PostfixExpression: + if in.Prev.Literal() == register.Literal() { + // not handled currently (x--) + return nil, false + } + case *ast.FunctionLiteral: + // skip lambda/functions in functions. + return nil, false + } + return in, true +} + +func setupRegister(env *object.Environment, name string, value int64, body ast.Node) (object.Register, ast.Node, bool) { + register := env.MakeRegister(name, value) + newBody, ok := ast.Modify(body, func(in ast.Node) (ast.Node, bool) { + return ModifyRegister(®ister, in) + }) + if log.LogVerbose() { + out := strings.Builder{} + ps := &ast.PrintState{Out: &out, Compact: true} + newBody.PrettyPrint(ps) + log.LogVf("replaced %d registers - ok = %t: %s", register.Count, ok, out.String()) + } + if !ok || register.Count == 0 { + return register, body, ok // original body unchanged. + } + return register, newBody, ok +} + +func (s *State) evalForInteger(fe *ast.ForExpression, start *int64, end int64, name string) object.Object { var lastEval object.Object lastEval = object.NULL startValue := 0 if start != nil { - startValue = int(start.Value) + startValue = int(*start) } - endValue := int(end.Value) + endValue := int(end) num := endValue - startValue if num < 0 { return s.Errorf("for loop with negative count [%d,%d[", startValue, endValue) } + var ptr *int64 + var newBody ast.Node + var register object.Register + newBody = fe.Body + if name != "" { + var ok bool + register, newBody, ok = setupRegister(s.env, name, int64(startValue), fe.Body) + if !ok { + return s.Errorf("for loop register %s shouldn't be modified inside the loop", name) + } + ptr = register.Ptr() + } for i := startValue; i < endValue; i++ { - if name != "" { - s.env.Set(name, object.Integer{Value: int64(i)}) + if ptr != nil { + *ptr = int64(i) } - nextEval := s.evalInternal(fe.Body) + nextEval := s.evalInternal(newBody) switch nextEval.Type() { case object.ERROR: return nextEval @@ -806,6 +878,9 @@ func (s *State) evalForInteger(fe *ast.ForExpression, start *object.Integer, end lastEval = nextEval } } + if ptr != nil { + s.env.ReleaseRegister(register) + } return lastEval } @@ -823,21 +898,24 @@ func (s *State) evalForSpecialForms(fe *ast.ForExpression) (object.Object, bool) name := ie.Left.Value().Literal() if ie.Right.Value().Type() == token.COLON { start := s.evalInternal(ie.Right.(*ast.InfixExpression).Left) - if start.Type() != object.INTEGER { + startInt, ok := Int64Value(start) + if !ok { return s.NewError("for var = n:m n not an integer: " + start.Inspect()), true } end := s.evalInternal(ie.Right.(*ast.InfixExpression).Right) - if end.Type() != object.INTEGER { + endInt, ok := Int64Value(end) + if !ok { return s.NewError("for var = n:m m not an integer: " + end.Inspect()), true } - startInt := start.(object.Integer) - return s.evalForInteger(fe, &startInt, end.(object.Integer), name), true + return s.evalForInteger(fe, &startInt, endInt, name), true } // Evaluate: v := s.evalInternal(ie.Right) switch v.Type() { + case object.REGISTER: + return s.evalForInteger(fe, nil, v.(*object.Register).Int64(), name), true case object.INTEGER: - return s.evalForInteger(fe, nil, v.(object.Integer), name), true + return s.evalForInteger(fe, nil, v.(object.Integer).Value, name), true case object.ERROR: return v, true case object.ARRAY, object.MAP, object.STRING: @@ -909,8 +987,10 @@ func (s *State) evalForExpression(fe *ast.ForExpression) object.Object { switch condition.Type() { case object.ERROR: return condition + case object.REGISTER: + return s.evalForInteger(fe, nil, condition.(*object.Register).Int64(), "") case object.INTEGER: - return s.evalForInteger(fe, nil, condition.(object.Integer), "") + return s.evalForInteger(fe, nil, condition.(object.Integer).Value, "") default: return s.NewError("for condition is not a boolean nor integer nor assignment: " + condition.Inspect()) } @@ -952,8 +1032,9 @@ func (s *State) evalPrefixExpression(operator token.Type, right object.Object) o case token.MINUS: return s.evalMinusPrefixOperatorExpression(right) case token.BITNOT, token.BITXOR: - if right.Type() == object.INTEGER { - return object.Integer{Value: ^right.(object.Integer).Value} + rightVal, ok := Int64Value(right) + if ok { + return object.Integer{Value: ^rightVal} } return s.NewError("bitwise not of " + right.Inspect()) case token.PLUS: @@ -982,6 +1063,9 @@ func (s *State) evalMinusPrefixOperatorExpression(right object.Object) object.Ob case object.INTEGER: value := right.(object.Integer).Value return object.Integer{Value: -value} + case object.REGISTER: + value := right.(*object.Register).Int64() + return object.Integer{Value: -value} case object.FLOAT: value := right.(object.Float).Value return object.Float{Value: -value} @@ -991,6 +1075,8 @@ func (s *State) evalMinusPrefixOperatorExpression(right object.Object) object.Ob } func (s *State) evalInfixExpression(operator token.Type, left, right object.Object) object.Object { + rightVal, rightIsInt := Int64Value(right) + leftVal, leftIsInt := Int64Value(left) switch { case operator == token.EQ: return object.NativeBoolToBooleanObject(object.Equals(left, right)) @@ -1009,8 +1095,8 @@ func (s *State) evalInfixExpression(operator token.Type, left, right object.Obje case operator == token.OR: return object.NativeBoolToBooleanObject(left == object.TRUE || right == object.TRUE) // can't use generics :/ see other comment. - case left.Type() == object.INTEGER && right.Type() == object.INTEGER: - return s.evalIntegerInfixExpression(operator, left, right) + case rightIsInt && leftIsInt: + return s.evalIntegerInfixExpression(operator, leftVal, rightVal) case left.Type() == object.FLOAT || right.Type() == object.FLOAT: return s.evalFloatInfixExpression(operator, left, right) case left.Type() == object.STRING: @@ -1026,15 +1112,15 @@ func (s *State) evalInfixExpression(operator token.Type, left, right object.Obje func (s *State) evalStringInfixExpression(operator token.Type, left, right object.Object) object.Object { leftVal := left.(object.String).Value + rightVal, rightIsInt := Int64Value(right) switch { case operator == token.PLUS && right.Type() == object.STRING: rightVal := right.(object.String).Value return object.String{Value: leftVal + rightVal} - case operator == token.ASTERISK && right.Type() == object.INTEGER: - rightVal := right.(object.Integer).Value + case operator == token.ASTERISK && rightIsInt: n := len(leftVal) * int(rightVal) if rightVal < 0 { - return s.NewError("right operand of * on strings must be a positive integer") + return s.Errorf("right operand of * on strings must be a positive integer, got %d", rightVal) } object.MustBeOk(n / object.ObjectSize) return object.String{Value: strings.Repeat(leftVal, int(rightVal))} @@ -1048,11 +1134,11 @@ func (s *State) evalArrayInfixExpression(operator token.Type, left, right object leftVal := object.Elements(left) switch operator { case token.ASTERISK: // repeat - if right.Type() != object.INTEGER { + rightVal, ok := Int64Value(right) + if !ok { return s.NewError("right operand of * on arrays must be an integer") } // TODO: go1.23 use slices.Repeat - rightVal := right.(object.Integer).Value if rightVal < 0 { return s.NewError("right operand of * on arrays must be a positive integer") } @@ -1063,7 +1149,7 @@ func (s *State) evalArrayInfixExpression(operator token.Type, left, right object return object.NewArray(result) case token.PLUS: // concat / append if right.Type() != object.ARRAY { - return object.NewArray(append(leftVal, right)) + return object.NewArray(append(leftVal, object.Value(right))) } rightArr := object.Elements(right) object.MustBeOk(len(leftVal) + len(rightArr)) @@ -1086,14 +1172,22 @@ func (s *State) evalMapInfixExpression(operator token.Type, left, right object.O } } +func Int64Value(o object.Object) (int64, bool) { + switch o.Type() { + case object.INTEGER: + return o.(object.Integer).Value, true + case object.REGISTER: + return o.(*object.Register).Int64(), true + default: + return -1, false // use -1 to get OOB when ok isn't checked/bug. + } +} + // You would think this is an ideal case for generics... yet... // can't use fields directly in generic code, // https://github.com/golang/go/issues/48522 // would need getters/setters which is not very go idiomatic. -func (s *State) evalIntegerInfixExpression(operator token.Type, left, right object.Object) object.Object { - leftVal := left.(object.Integer).Value - rightVal := right.(object.Integer).Value - +func (s *State) evalIntegerInfixExpression(operator token.Type, leftVal, rightVal int64) object.Object { switch operator { case token.PLUS: return object.Integer{Value: leftVal + rightVal} @@ -1132,6 +1226,8 @@ func (s *State) evalIntegerInfixExpression(operator token.Type, left, right obje func GetFloatValue(o object.Object) (float64, *object.Error) { switch o.Type() { + case object.REGISTER: + return float64(o.(*object.Register).Int64()), nil case object.INTEGER: return float64(o.(object.Integer).Value), nil case object.FLOAT: diff --git a/eval/eval_api.go b/eval/eval_api.go index c998d592..b64cfca3 100644 --- a/eval/eval_api.go +++ b/eval/eval_api.go @@ -132,6 +132,11 @@ func (s *State) SetDefaultContext() context.CancelFunc { return s.SetContext(context.Background(), DefaultMaxDuration) } +// Final unwrapped result of an evaluation (for instance unwraps the registers which Eval() does not). +func (s *State) EvalToplevel(node any) object.Object { + return object.Value(s.Eval(node)) +} + // Does unwrap (so stop bubbling up) return values. func (s *State) Eval(node any) object.Object { if s.depth > s.MaxDepth { @@ -150,8 +155,13 @@ func (s *State) Eval(node any) object.Object { result = returnValue.Value } if refValue, ok := result.(object.Reference); ok { - return object.Value(refValue) + return refValue.ObjValue() + } + /* Doing this at each eval breaks some optimization so we do it only in the caller/repl/last level. + if registerValue, ok := result.(*object.Register); ok { + return registerValue.ObjValue() } + */ return result } diff --git a/eval/eval_test.go b/eval/eval_test.go index 28e48bfb..52edef51 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -102,7 +102,7 @@ func testEval(t *testing.T, input string) object.Object { t.Fatalf("parser has %d error(s) for %q: %v", len(p.Errors()), input, p.Errors()) } s := eval.NewState() // each test starts anew. - return s.Eval(program) + return s.EvalToplevel(program) } func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { @@ -261,9 +261,9 @@ func TestErrorHandling(t *testing.T) { `C1=1;C1=2`, "attempt to change constant C1 from 1 to 2", }, - { - `func x(FOO) {log("x",FOO,PI);if FOO<=1 {return FOO} x(FOO-1)};x(2)`, - "attempt to change constant FOO from 2 to 1", + { // with integer registers you can mutate FOO if it's an int. + `func x(FOO) {log("x",FOO,PI);if len(FOO)<=1 {return FOO} x(FOO[0:-1])};x("AB")`, + `attempt to change constant FOO from "AB" to "A"`, }, { `func FOO(x){x}; func FOO(x){x+1}`, @@ -335,19 +335,19 @@ if (10 > 1) { }, } - for _, tt := range tests { + for i, tt := range tests { evaluated := testEval(t, tt.input) errObj, ok := evaluated.(object.Error) if !ok { - t.Errorf("no error object returned. got=%T(%+v)", - evaluated, evaluated) + t.Errorf("test %d: no error object returned. for %s got=%T(%+v)", + i+1, tt.input, evaluated, evaluated) continue } if errObj.Value != tt.expectedMessage { - t.Errorf("wrong error message. expected=%q, got=%q", - tt.expectedMessage, errObj.Value) + t.Errorf("test %d: wrong error message. for %s expected=%q, got=%q", + i+1, tt.input, tt.expectedMessage, errObj.Value) } } } @@ -1038,3 +1038,16 @@ func TestIncrMatrix(t *testing.T) { t.Errorf("wrong error, got %q instead of %q", actual, expected) } } + +func TestDecrRegister(t *testing.T) { + inp := `x=>{x-- return x}(3)` + s := eval.NewState() + res, err := eval.EvalString(s, inp, false) + if err != nil { + t.Errorf("should not have errored: %v", err) + } + expected := "2" + if res.Inspect() != expected { + t.Errorf("wrong result, got %q", res.Inspect()) + } +} diff --git a/eval/macro_expension.go b/eval/macro_expension.go index 248b71b4..15b21cd6 100644 --- a/eval/macro_expension.go +++ b/eval/macro_expension.go @@ -83,7 +83,7 @@ func (s *State) MacroErrorf(fmtmsg string, args ...any) ast.Node { } func (s *State) ExpandMacros(program ast.Node) ast.Node { - return ast.Modify(program, func(node ast.Node) ast.Node { + return ast.ModifyNoOk(program, func(node ast.Node) ast.Node { callExpression, ok := node.(*ast.CallExpression) if !ok { return node diff --git a/eval/quote_unquote.go b/eval/quote_unquote.go index 814d39dc..2d41c9ad 100644 --- a/eval/quote_unquote.go +++ b/eval/quote_unquote.go @@ -15,7 +15,7 @@ func (s *State) quote(node ast.Node) object.Quote { } func (s *State) evalUnquoteCalls(quoted ast.Node) ast.Node { - return ast.Modify(quoted, func(node ast.Node) ast.Node { + return ast.ModifyNoOk(quoted, func(node ast.Node) ast.Node { if !isUnquoteCall(node) { return node } diff --git a/examples/boxed_text.gr b/examples/boxed_text.gr index e673555a..607bce77 100644 --- a/examples/boxed_text.gr +++ b/examples/boxed_text.gr @@ -59,18 +59,15 @@ boxTextTerm(testStr) // matrix -func str(x) { - sprintf("%v", x) -} - func matrixWidths(matrix) { apply(row => apply(item => width(str(item)), row), matrix) } func forRecursive(n, f, start) { + nn:=n (i,f,x) => { // internal lambda with the index param and intermediate result r = f(i,x) - if (i>=n) { + if (i>=nn) { return r } self(i+1, f,r) @@ -78,8 +75,8 @@ func forRecursive(n, f, start) { } func maxWidths(widths) { - cols = len(widths[0]) - colIndices = 0:cols // Generates [0, 1, 2] for 3 columns + cols := len(widths[0]) + colIndices := 0:cols // Generates [0, 1, 2] for 3 columns apply(colIndex => max(apply( row => row[colIndex], widths)), colIndices) } @@ -151,7 +148,7 @@ matrix = [ ["xyz","hello","world"] ] - println("For", matrix) +println("For", matrix) boxMatrix(matrix) // Example usage for tic-tac-toe board diff --git a/examples/circle.gr b/examples/circle.gr index bb34de99..5a73c5f7 100644 --- a/examples/circle.gr +++ b/examples/circle.gr @@ -22,37 +22,37 @@ func circleQuadrant(img, x, y, rx, ry, color, thickness) { func quadrant(img, x, y, rx, ry, thickness) { sign = sign - x = round(x)+.5 // semi magic adjustment that makes the bezier circle and trueCircle match - y = round(y)+.5 + xx := round(x)+.5 // semi magic adjustment that makes the bezier circle and trueCircle match + yy := round(y)+.5 a := 1.00005519 b := 0.55342686 c := 0.99873585 - thickness = thickness / 2. - ythickness := thickness + thickness2 = thickness / 2. + ythickness := thickness2 if sign(rx) != sign(ry) { - ythickness = -thickness + ythickness = -thickness2 } - p0x := x + rx*a - thickness - p0y := y + p0x := xx + rx*a - thickness2 + p0y := yy image.move_to(img, p0x, p0y) - image.line_to(img, p0x+2*thickness, p0y) - rx = rx + thickness - ry = ry + ythickness - p2x := x + rx*b - p2y := y + ry*c - p1x := x + rx*c - p1y := y + ry*b - p3x := x - p3y := y + ry*a + image.line_to(img, p0x+thickness, p0y) + rxx := rx + thickness2 + ryy := ry + ythickness + p2x := xx + rxx*b + p2y := yy + ryy*c + p1x := xx + rxx*c + p1y := yy + ryy*b + p3x := xx + p3y := yy + ryy*a image.cube_to(img, p1x, p1y, p2x, p2y, p3x, p3y) - rx = rx - 2*thickness - ry = ry - 2*ythickness - p3y := y + ry*a - image.line_to(img, x, p3y) - p2x := x + rx*b - p2y := y + ry*c - p1x := x + rx*c - p1y := y + ry*b + rxx = rxx - thickness + ryy = ryy - 2*ythickness + p3y := yy + ryy*a + image.line_to(img, xx, p3y) + p2x := xx + rxx*b + p2y := yy + ryy*c + p1x := xx + rxx*c + p1y := yy + ryy*b image.cube_to(img, p2x, p2y, p1x, p1y, p0x, p0y) // image.line_to(img, x, y) // covered by close_path in draw } diff --git a/examples/image.gr b/examples/image.gr index ef54cbe6..0a09057c 100644 --- a/examples/image.gr +++ b/examples/image.gr @@ -8,9 +8,9 @@ // With angle as an int modulo 360 input, this gets memoized. func ycbcr(angle) { - angle = PI * angle / 180. + a = PI * angle / 180. // Y Cb Cr - [190, 128 + 120*sin(angle), 128 + 120*cos(angle)] + [190, 128 + 120*sin(a), 128 + 120*cos(a)] } // saturation = 1 diff --git a/examples/loop.gr b/examples/loop.gr new file mode 100644 index 00000000..da44c53f --- /dev/null +++ b/examples/loop.gr @@ -0,0 +1,10 @@ +// increase when benchmarking +n2 := 10_000 +n1 := 5_000 +sum := 0 +for i := n1 { + for j := n2 { + sum = sum + i + j + } +} +sum diff --git a/examples/mandelbrot.gr b/examples/mandelbrot.gr index 635f2485..c7fe0fe7 100644 --- a/examples/mandelbrot.gr +++ b/examples/mandelbrot.gr @@ -1,5 +1,5 @@ -maxIter = 32 +maxIter = 32 // increase when benchmarking func mandelbrot(x, y) { m := maxIter // bind to global, prevents caching, save allocs/cache writes. @@ -17,7 +17,7 @@ func mandelbrot(x, y) { m } -size:=256 +size:=512 img:="img" image.new(img, size, size) diff --git a/examples/prime_sieve_iter_compact.gr b/examples/prime_sieve_iter_compact.gr new file mode 100644 index 00000000..8358242a --- /dev/null +++ b/examples/prime_sieve_iter_compact.gr @@ -0,0 +1,31 @@ +/* Demonstrate how to have lambdas as iterators */ +func primesGen(mx) { + sieve = [true] * mx + cp = 2 + nextPrime = () => { + for cp < mx { + if sieve[cp] { + prime = cp + cp++ + mult = prime * prime + for max(mx - mult, 0) { + if mult % prime == 0 { + sieve[mult] = false + } + mult++ + } + return prime + } else { + cp++ + } + } + nil + } +} + +maxS = 10_000 +primeIter = primesGen(maxS) +for (p = primeIter()) != nil { + print(" ", p) +} +println() diff --git a/extensions/extension.go b/extensions/extension.go index e9761027..7f89a399 100644 --- a/extensions/extension.go +++ b/extensions/extension.go @@ -93,6 +93,12 @@ func initInternal(c *Config) error { if err != nil { return err } + // [x] because for varargs we transform arrays as last arg to varargs. so we wrap the single argument into an array + // so arrays can be printed properly. + err = eval.AddEvalResult("str", `func str(x){sprintf("%v", [x])}`) + if err != nil { + return err + } object.AddIdentifier("nil", object.NULL) object.AddIdentifier("null", object.NULL) object.AddIdentifier("NaN", object.Float{Value: math.NaN()}) @@ -121,7 +127,19 @@ func initInternal(c *Config) error { ArgTypes: []object.Type{object.STRING}, Callback: object.ShortCallback(sprintf), }) + createMathFunctions() + createJSONAndEvalFunctions(c) + createStrFunctions() + createMisc() + createTimeFunctions() + createImageFunctions() + if c.UnrestrictedIOs { + createShellFunctions() + } + return nil +} +func createMathFunctions() { oneFloat := object.Extension{ MinArgs: 1, MaxArgs: 1, @@ -196,15 +214,6 @@ func initInternal(c *Config) error { }, DontCache: true, }) - createJSONAndEvalFunctions(c) - createStrFunctions() - createMisc() - createTimeFunctions() - createImageFunctions() - if c.UnrestrictedIOs { - createShellFunctions() - } - return nil } func createJSONAndEvalFunctions(c *Config) { @@ -227,10 +236,16 @@ func createJSONAndEvalFunctions(c *Config) { jsonFn.Name = "type" jsonFn.Callback = object.ShortCallback(func(args []object.Object) object.Object { obj := args[0] - if r, ok := obj.(object.Reference); ok { - return object.String{Value: "&" + r.Name + ".(" + r.Value().Type().String() + ")"} + switch obj.Type() { + case object.REFERENCE: + r := obj.(object.Reference) + return object.String{Value: "&" + r.Name + ".(" + r.ObjValue().Type().String() + ")"} + case object.REGISTER: + r := obj.(*object.Register) + return object.String{Value: r.DebugString()} + default: + return object.String{Value: obj.Type().String()} } - return object.String{Value: obj.Type().String()} }) MustCreate(jsonFn) jsonFn.Name = "eval" @@ -387,6 +402,7 @@ func createStrFunctions() { //nolint:funlen // we do have quite a few, yes. strFn.Name = "regexp" strFn.Help = "returns true if regular expression matches the string (2nd arg)" strFn.ArgTypes = []object.Type{object.STRING, object.STRING, object.BOOLEAN} + strFn.MinArgs = 2 strFn.MaxArgs = 3 strFn.Callback = func(env any, _ string, args []object.Object) object.Object { s := env.(*eval.State) @@ -512,10 +528,10 @@ func createMisc() { switch o.Type() { case object.REFERENCE: ref := o.(object.Reference) - if ref.Value().Type() != object.STRING { - return s.Errorf("cannot convert ref to %s to base64", ref.Value().Type()) + if ref.ObjValue().Type() != object.STRING { + return s.Errorf("cannot convert ref to %s to base64", ref.ObjValue().Type()) } - data = []byte(ref.Value().(object.String).Value) + data = []byte(ref.ObjValue().(object.String).Value) case object.STRING: data = []byte(o.(object.String).Value) default: diff --git a/main_test.txtar b/main_test.txtar index eca43dce..0834990c 100644 --- a/main_test.txtar +++ b/main_test.txtar @@ -80,7 +80,7 @@ stdout '^1\n3$' stderr '' # quiet mode and fast fibbonaci (log won't show) -grol -quiet -c 'fib=func(x){log("fib",x);if x<=1 {x} else {fib(x-1)+fib(x-2)}}; fib(92)' +grol -quiet -c 'func fib(x){log("fib",x);if x<=1 {x} else {fib(x-1)+fib(x-2)}}; fib(92)' stdout '^7540113804746346429\n$' !stdout '\n\n' !stderr . @@ -121,8 +121,8 @@ stdout '^3$' !stderr . # eval() runs in the same context as when called -grol -quiet -c 'func foo(x) {eval("info")};foo(42)["all_ids"][1]' -stdout '^\["self","x"\]$' +grol -quiet -c 'func foo(x) {eval("info")};foo("A")["all_ids"][1]' +stdout '^\["x"\]$' !stderr . # json of (nested) arrays @@ -404,7 +404,7 @@ fib = func(x) { if (x == 1) { return 1 } - fib(x - 1) + fib(x - 2) + self(x - 1) + self(x - 2) } fib(50) -- sample_test_stdout -- diff --git a/object/object.go b/object/object.go index 1d9341ce..744cf61a 100644 --- a/object/object.go +++ b/object/object.go @@ -41,6 +41,7 @@ const ( MACRO EXTENSION REFERENCE + REGISTER ANY // A marker, for extensions, not a real type. ) @@ -62,7 +63,8 @@ type Number interface { // Hashable in tem of Go map for cache key. func Hashable(o Object) bool { switch o.Type() { //nolint:exhaustive // We have all the types that are hashable + default for the others. - case INTEGER, FLOAT, BOOLEAN, NIL, STRING: + // register because it's a pointer though dubious whether it's hashable for cache key. + case INTEGER, FLOAT, BOOLEAN, NIL, STRING, REGISTER: return true case ARRAY: if sa, ok := o.(SmallArray); ok { @@ -103,20 +105,38 @@ func NativeBoolToBooleanObject(input bool) Boolean { return FALSE } +// registers are equivalent to integers. +func IsIntType(t Type) bool { + return t == INTEGER || t == REGISTER +} + +// registers are considered integer for the purpose of comparison. +func TypeEqual(a, b Type) bool { + return a == b || (IsIntType(a) && IsIntType(b)) +} + func Equals(left, right Object) bool { - // TODO: dereference or not? - if left.Type() != right.Type() { + // TODO: references are usually derefs before coming here, unlike registers. + if !TypeEqual(left.Type(), right.Type()) { return false // int and float aren't the same even though they can Cmp to the same value. } return Cmp(left, right) == 0 } -// Deal with references and return the actual value. +func CopyRegister(o Object) Object { + if r, ok := o.(*Register); ok { + return r.ObjValue() + } + return o +} + +// Deal with references and registers and return the actual value. func Value(o Object) Object { + o = CopyRegister(o) count := 0 for { if r, ok := o.(Reference); ok { - o = r.Value() + o = r.ObjValue() count++ if count > 100 { panic("Too many references") @@ -153,8 +173,8 @@ func Cmp(ei, ej Object) int { } // same types at this point. switch ti { - case REFERENCE: - panic("Unexpected type in Cmp: REFERENCE") + case REFERENCE, REGISTER: + panic("Unexpected type in Cmp: " + ti.String()) case EXTENSION: return cmp.Compare(ei.(Extension).Name, ej.(Extension).Name) case FUNC: @@ -1083,13 +1103,47 @@ func (m Macro) JSON(w io.Writer) error { return err } +// Registers are fast local integer variables skipping the environment map lookup. +type Register struct { + ast.Base + RefEnv *Environment + Idx int + Count int +} + +func (r *Register) Int64() int64 { + return r.RefEnv.registers[r.Idx] +} + +func (r *Register) ObjValue() Object { + return Integer{r.RefEnv.registers[r.Idx]} +} + +func (r *Register) Ptr() *int64 { + return &r.RefEnv.registers[r.Idx] +} + +func (r *Register) Unwrap(str bool) any { return r.ObjValue().Unwrap(str) } +func (r *Register) Type() Type { return REGISTER } +func (r *Register) Inspect() string { return r.ObjValue().Inspect() } +func (r *Register) JSON(w io.Writer) error { return r.ObjValue().JSON(w) } + +func (r *Register) DebugString() string { + return "R[" + strconv.Itoa(r.Idx) + "," + r.Literal() + "]" +} + +func (r *Register) PrettyPrint(out *ast.PrintState) *ast.PrintState { + out.Print(r.DebugString()) + return out +} + // References are pointer to original object up the stack. type Reference struct { Name string RefEnv *Environment } -func (r Reference) Value() Object { +func (r Reference) ObjValue() Object { if log.LogDebug() { log.Debugf("Reference Value() %s -> %s", r.Name, r.RefEnv.store[r.Name].Inspect()) } @@ -1100,10 +1154,10 @@ func (r Reference) Value() Object { return v } -func (r Reference) Unwrap(str bool) any { return r.Value().Unwrap(str) } +func (r Reference) Unwrap(str bool) any { return r.ObjValue().Unwrap(str) } func (r Reference) Type() Type { return REFERENCE } -func (r Reference) Inspect() string { return r.Value().Inspect() } -func (r Reference) JSON(w io.Writer) error { return r.Value().JSON(w) } +func (r Reference) Inspect() string { return r.ObjValue().Inspect() } +func (r Reference) JSON(w io.Writer) error { return r.ObjValue().JSON(w) } // Extensions are functions implemented in go and exposed to grol. type Extension struct { diff --git a/object/state.go b/object/state.go index e2312b78..a42899d9 100644 --- a/object/state.go +++ b/object/state.go @@ -9,20 +9,25 @@ import ( "fortio.org/cli" "fortio.org/log" "fortio.org/sets" + "grol.io/grol/ast" "grol.io/grol/token" "grol.io/grol/trie" ) +const NumRegisters = 8 + type Environment struct { - store map[string]Object - outer *Environment - stack *Environment // Different from outer when we attach to top level lambdas. see logic in NewFunctionEnvironment. - depth int - cacheKey string - ids *trie.Trie - numSet int64 - getMiss int64 - function *Function + store map[string]Object + outer *Environment + stack *Environment // Different from outer when we attach to top level lambdas. see logic in NewFunctionEnvironment. + depth int + cacheKey string + ids *trie.Trie + numSet int64 + getMiss int64 + function *Function + registers [NumRegisters]int64 + numReg int } // Truly empty store suitable for macros storage. @@ -175,6 +180,28 @@ func (e *Environment) SaveGlobals(to io.Writer, maxValueLen int) (int, error) { return n, nil } +func (e *Environment) HasRegisters() bool { + return e.numReg < NumRegisters +} + +func (e *Environment) MakeRegister(originalName string, v int64) Register { + if !e.HasRegisters() { + panic(fmt.Sprintf("No more registers available for %s (%d) have %v", originalName, v, e.registers)) + } + e.registers[e.numReg] = v + tok := token.Intern(token.REGISTER, originalName) + r := Register{RefEnv: e, Idx: e.numReg, Base: ast.Base{Token: tok}} + e.numReg++ + return r +} + +func (e *Environment) ReleaseRegister(register Register) { + if register.Idx != e.numReg-1 { + panic(fmt.Sprintf("Releasing non last register %s %d != %d", register.Literal(), register.Idx, e.numReg-1)) + } + e.numReg-- +} + func (e *Environment) makeRef(name string) (*Reference, bool) { orig := e for e.outer != nil { @@ -202,10 +229,19 @@ func (e *Environment) Get(name string) (Object, bool) { if name == "info" { return e.Info(), true } + if name == "self" { + if e.function != nil { + return *e.function, true + } + return nil, false + } + if e.function != nil && e.function.Name != nil && name == e.function.Name.Literal() { + return *e.function, true + } obj, ok := e.store[name] if ok { // using references to non constant (extensions are constants) implies uncacheable. - if r, ok := obj.(Reference); ok && !Constant(r.Name) && r.Value().Type() != FUNC { + if r, ok := obj.(Reference); ok && !Constant(r.Name) && r.ObjValue().Type() != FUNC { e.getMiss++ log.Debugf("get(%s) GETMISS %d", name, e.getMiss) } diff --git a/object/type_string.go b/object/type_string.go index 0ef68331..81de114b 100644 --- a/object/type_string.go +++ b/object/type_string.go @@ -23,12 +23,13 @@ func _() { _ = x[MACRO-12] _ = x[EXTENSION-13] _ = x[REFERENCE-14] - _ = x[ANY-15] + _ = x[REGISTER-15] + _ = x[ANY-16] } -const _Type_name = "UNKNOWNINTEGERFLOATBOOLEANNILERRORRETURNFUNCSTRINGARRAYMAPQUOTEMACROEXTENSIONREFERENCEANY" +const _Type_name = "UNKNOWNINTEGERFLOATBOOLEANNILERRORRETURNFUNCSTRINGARRAYMAPQUOTEMACROEXTENSIONREFERENCEREGISTERANY" -var _Type_index = [...]uint8{0, 7, 14, 19, 26, 29, 34, 40, 44, 50, 55, 58, 63, 68, 77, 86, 89} +var _Type_index = [...]uint8{0, 7, 14, 19, 26, 29, 34, 40, 44, 50, 55, 58, 63, 68, 77, 86, 94, 97} func (i Type) String() string { if i >= Type(len(_Type_index)-1) { diff --git a/tests/_lib.gr b/tests/_lib.gr index 9137c69a..c7a88739 100644 --- a/tests/_lib.gr +++ b/tests/_lib.gr @@ -4,20 +4,23 @@ Library of macros for testing */ -// Convert any grol object to a string. -func str(x) { - sprintf("%v", x) +// When testing regressions using older grol versions (without builtin str): +/* +func str(s) { + sprintf("%v",[s]) } +*/ // Test for absence of error and that the expression's result matches a regexp. NoErr = macro(msg, expr, expectedRegexp) { quote(if (r = catch(unquote(expr))).err { error("FAIL unexpected error:", r.value, "for", unquote(msg)) } else { - if (regexp(unquote(expectedRegexp), str(r.value))) { + sv = str(r.value) + if regexp(unquote(expectedRegexp), sv) { println("OK", unquote(msg), "is:", r.value) } else { - error("FAIL", unquote(msg), "didn't match expected:", r.value) + error(sprintf("FAIL %s didn't match expected: %v (str %q, regexp %q)", unquote(msg), r.value, sv, unquote(expectedRegexp))) } }) } diff --git a/tests/registers.gr b/tests/registers.gr new file mode 100644 index 00000000..b981a503 --- /dev/null +++ b/tests/registers.gr @@ -0,0 +1,46 @@ + +NoErr("loop not using register", for n := 1 {println("ok")}, "") + +// Should work, x-- aborts register op (for now, makes state ! ok) +func foo(x){ + x-- + x +} +NoErr("register -- should work", foo(3), "2") +func foo(x){ + x = x - 1 +} +NoErr("register assignment should work", foo(3), "2") + +arr := [] +for i:=5 {arr = arr + i} + +if arr != [0,1,2,3,4] { + error("for loop plus elem append not working, got", arr) +} + +arr := [] +for i:=5 {arr = arr + [i]} + +if arr != [0,1,2,3,4] { + error("for loop plus array append not working, got", arr) +} + + +// -- repro the boxed text issue -- + +func apply(f, a) { + if (len(a)==0) { + [] + } else { + [f(first(a))]+apply(f,rest(a)) + } +} + +func maxWidths(widths) { + apply(colIndex => apply(row => row[colIndex], widths), [0,1]) +} + +mw := [[9999,7777]] + +NoErr("matrix max width", maxWidths(mw), `\[\[9999\] \[7777\]\]`) diff --git a/token/token.go b/token/token.go index a5783319..a884217a 100644 --- a/token/token.go +++ b/token/token.go @@ -64,6 +64,7 @@ const ( STRING // "foo bar" or `foo bar` LINECOMMENT BLOCKCOMMENT + REGISTER // not used for parsing, only to tag object.Register as ast node of unique type. endValueTokens diff --git a/token/type_string.go b/token/type_string.go index 58c1148c..4d12ea12 100644 --- a/token/type_string.go +++ b/token/type_string.go @@ -17,75 +17,76 @@ func _() { _ = x[STRING-6] _ = x[LINECOMMENT-7] _ = x[BLOCKCOMMENT-8] - _ = x[endValueTokens-9] - _ = x[startSingleCharTokens-10] - _ = x[ASSIGN-11] - _ = x[PLUS-12] - _ = x[MINUS-13] - _ = x[BANG-14] - _ = x[ASTERISK-15] - _ = x[SLASH-16] - _ = x[PERCENT-17] - _ = x[LT-18] - _ = x[GT-19] - _ = x[BITAND-20] - _ = x[BITOR-21] - _ = x[BITXOR-22] - _ = x[BITNOT-23] - _ = x[COMMA-24] - _ = x[SEMICOLON-25] - _ = x[LPAREN-26] - _ = x[RPAREN-27] - _ = x[LBRACE-28] - _ = x[RBRACE-29] - _ = x[LBRACKET-30] - _ = x[RBRACKET-31] - _ = x[COLON-32] - _ = x[DOT-33] - _ = x[endSingleCharTokens-34] - _ = x[startMultiCharTokens-35] - _ = x[LTEQ-36] - _ = x[GTEQ-37] - _ = x[EQ-38] - _ = x[NOTEQ-39] - _ = x[INCR-40] - _ = x[DECR-41] - _ = x[DOTDOT-42] - _ = x[OR-43] - _ = x[AND-44] - _ = x[LEFTSHIFT-45] - _ = x[RIGHTSHIFT-46] - _ = x[LAMBDA-47] - _ = x[DEFINE-48] - _ = x[endMultiCharTokens-49] - _ = x[startIdentityTokens-50] - _ = x[FUNC-51] - _ = x[TRUE-52] - _ = x[FALSE-53] - _ = x[IF-54] - _ = x[ELSE-55] - _ = x[RETURN-56] - _ = x[FOR-57] - _ = x[BREAK-58] - _ = x[CONTINUE-59] - _ = x[MACRO-60] - _ = x[QUOTE-61] - _ = x[UNQUOTE-62] - _ = x[LEN-63] - _ = x[FIRST-64] - _ = x[REST-65] - _ = x[PRINT-66] - _ = x[PRINTLN-67] - _ = x[LOG-68] - _ = x[ERROR-69] - _ = x[CATCH-70] - _ = x[endIdentityTokens-71] - _ = x[EOF-72] + _ = x[REGISTER-9] + _ = x[endValueTokens-10] + _ = x[startSingleCharTokens-11] + _ = x[ASSIGN-12] + _ = x[PLUS-13] + _ = x[MINUS-14] + _ = x[BANG-15] + _ = x[ASTERISK-16] + _ = x[SLASH-17] + _ = x[PERCENT-18] + _ = x[LT-19] + _ = x[GT-20] + _ = x[BITAND-21] + _ = x[BITOR-22] + _ = x[BITXOR-23] + _ = x[BITNOT-24] + _ = x[COMMA-25] + _ = x[SEMICOLON-26] + _ = x[LPAREN-27] + _ = x[RPAREN-28] + _ = x[LBRACE-29] + _ = x[RBRACE-30] + _ = x[LBRACKET-31] + _ = x[RBRACKET-32] + _ = x[COLON-33] + _ = x[DOT-34] + _ = x[endSingleCharTokens-35] + _ = x[startMultiCharTokens-36] + _ = x[LTEQ-37] + _ = x[GTEQ-38] + _ = x[EQ-39] + _ = x[NOTEQ-40] + _ = x[INCR-41] + _ = x[DECR-42] + _ = x[DOTDOT-43] + _ = x[OR-44] + _ = x[AND-45] + _ = x[LEFTSHIFT-46] + _ = x[RIGHTSHIFT-47] + _ = x[LAMBDA-48] + _ = x[DEFINE-49] + _ = x[endMultiCharTokens-50] + _ = x[startIdentityTokens-51] + _ = x[FUNC-52] + _ = x[TRUE-53] + _ = x[FALSE-54] + _ = x[IF-55] + _ = x[ELSE-56] + _ = x[RETURN-57] + _ = x[FOR-58] + _ = x[BREAK-59] + _ = x[CONTINUE-60] + _ = x[MACRO-61] + _ = x[QUOTE-62] + _ = x[UNQUOTE-63] + _ = x[LEN-64] + _ = x[FIRST-65] + _ = x[REST-66] + _ = x[PRINT-67] + _ = x[PRINTLN-68] + _ = x[LOG-69] + _ = x[ERROR-70] + _ = x[CATCH-71] + _ = x[endIdentityTokens-72] + _ = x[EOF-73] } -const _Type_name = "ILLEGALEOLstartValueTokensIDENTINTFLOATSTRINGLINECOMMENTBLOCKCOMMENTendValueTokensstartSingleCharTokensASSIGNPLUSMINUSBANGASTERISKSLASHPERCENTLTGTBITANDBITORBITXORBITNOTCOMMASEMICOLONLPARENRPARENLBRACERBRACELBRACKETRBRACKETCOLONDOTendSingleCharTokensstartMultiCharTokensLTEQGTEQEQNOTEQINCRDECRDOTDOTORANDLEFTSHIFTRIGHTSHIFTLAMBDADEFINEendMultiCharTokensstartIdentityTokensFUNCTRUEFALSEIFELSERETURNFORBREAKCONTINUEMACROQUOTEUNQUOTELENFIRSTRESTPRINTPRINTLNLOGERRORCATCHendIdentityTokensEOF" +const _Type_name = "ILLEGALEOLstartValueTokensIDENTINTFLOATSTRINGLINECOMMENTBLOCKCOMMENTREGISTERendValueTokensstartSingleCharTokensASSIGNPLUSMINUSBANGASTERISKSLASHPERCENTLTGTBITANDBITORBITXORBITNOTCOMMASEMICOLONLPARENRPARENLBRACERBRACELBRACKETRBRACKETCOLONDOTendSingleCharTokensstartMultiCharTokensLTEQGTEQEQNOTEQINCRDECRDOTDOTORANDLEFTSHIFTRIGHTSHIFTLAMBDADEFINEendMultiCharTokensstartIdentityTokensFUNCTRUEFALSEIFELSERETURNFORBREAKCONTINUEMACROQUOTEUNQUOTELENFIRSTRESTPRINTPRINTLNLOGERRORCATCHendIdentityTokensEOF" -var _Type_index = [...]uint16{0, 7, 10, 26, 31, 34, 39, 45, 56, 68, 82, 103, 109, 113, 118, 122, 130, 135, 142, 144, 146, 152, 157, 163, 169, 174, 183, 189, 195, 201, 207, 215, 223, 228, 231, 250, 270, 274, 278, 280, 285, 289, 293, 299, 301, 304, 313, 323, 329, 335, 353, 372, 376, 380, 385, 387, 391, 397, 400, 405, 413, 418, 423, 430, 433, 438, 442, 447, 454, 457, 462, 467, 484, 487} +var _Type_index = [...]uint16{0, 7, 10, 26, 31, 34, 39, 45, 56, 68, 76, 90, 111, 117, 121, 126, 130, 138, 143, 150, 152, 154, 160, 165, 171, 177, 182, 191, 197, 203, 209, 215, 223, 231, 236, 239, 258, 278, 282, 286, 288, 293, 297, 301, 307, 309, 312, 321, 331, 337, 343, 361, 380, 384, 388, 393, 395, 399, 405, 408, 413, 421, 426, 431, 438, 441, 446, 450, 455, 462, 465, 470, 475, 492, 495} func (i Type) String() string { if i >= Type(len(_Type_index)-1) {