diff --git a/ast/ast.go b/ast/ast.go index 920a7c02..88a00dd8 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -303,7 +303,7 @@ type IfExpression struct { Alternative *Statements } -func (ie IfExpression) handlElse(out *PrintState) { +func (ie IfExpression) printElse(out *PrintState) { if out.Compact { out.Print("else") } else { @@ -328,7 +328,7 @@ func (ie IfExpression) PrettyPrint(out *PrintState) *PrintState { } ie.Consequence.PrettyPrint(out) if ie.Alternative != nil { - ie.handlElse(out) + ie.printElse(out) } return out } @@ -358,12 +358,17 @@ func (b Builtin) PrettyPrint(out *PrintState) *PrintState { type FunctionLiteral struct { Base // The 'func' token + Name *Identifier Parameters []Node Body *Statements } func (fl FunctionLiteral) PrettyPrint(out *PrintState) *PrintState { out.Print(fl.Literal()) + if fl.Name != nil { + out.Print(" ") + out.Print(fl.Name.Literal()) + } out.Print("(") out.ComaList(fl.Parameters) if out.Compact { diff --git a/eval/eval.go b/eval/eval.go index c9388c34..a437408a 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -157,8 +157,12 @@ func (s *State) evalInternal(node any) object.Object { case *ast.FunctionLiteral: params := node.Parameters body := node.Body - fn := object.Function{Parameters: params, Env: s.env, Body: body} + name := node.Name + fn := object.Function{Parameters: params, Name: name, Env: s.env, Body: body} fn.SetCacheKey() // sets cache key + if name != nil { + s.env.Set(name.Literal(), fn) + } return fn case *ast.CallExpression: f := s.evalInternal(node.Function) diff --git a/eval/eval_test.go b/eval/eval_test.go index c0dfdd3a..c3281fc9 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -16,6 +16,8 @@ func TestEvalIntegerExpression(t *testing.T) { expected int64 }{ {`f=func(x) {len(x)}; f([1,2,3])`, 3}, + // shorthand syntax for function declaration: + {`func f(x) {len(x)}; f([1,2,3])`, 3}, {"(3)\n(4)", 4}, // expression on new line should be... new. {"5 // is 5", 5}, {"10", 10}, diff --git a/examples/fib.gr b/examples/fib.gr index 957d1ac6..ea62172b 100644 --- a/examples/fib.gr +++ b/examples/fib.gr @@ -1,11 +1,9 @@ -fib = func(x) { - if x <= 0 { - return 0 +func fib(x) { + if x <= 1 { + x + } else { + fib(x - 1) + fib(x - 2) } - if x == 1 { - return 1 - } - fib(x - 1) + fib(x - 2) } r = fib(35) log("fib(35) =", r) diff --git a/examples/sample.gr b/examples/sample.gr index 2914b285..9f1cea2b 100644 --- a/examples/sample.gr +++ b/examples/sample.gr @@ -13,7 +13,7 @@ unless = macro(cond, iffalse, iftrue) { unless(10 > 5, print("BUG: not greater\n"), print("macro test: greater\n")) -fact=func(n) { // function +fact=func(n) { // first class function objects, can also be written as `func fact(n) {` as shorthand log("called fact ", n) // log (timestamped stderr output) if (n<=1) { return 1 diff --git a/object/object.go b/object/object.go index 0e95c054..d3e0d78d 100644 --- a/object/object.go +++ b/object/object.go @@ -179,6 +179,7 @@ func (rv ReturnValue) Inspect() string { return rv.Value.Inspect() } type Function struct { Parameters []ast.Node + Name *ast.Identifier CacheKey string Body *ast.Statements Env *Environment @@ -198,24 +199,34 @@ func WriteStrings(out *strings.Builder, list []Object, before, sep, after string 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, +// so it's not part of the cache key. func (f *Function) SetCacheKey() string { out := strings.Builder{} out.WriteString("func") + f.CacheKey = f.finishFuncOutput(&out) + return f.CacheKey +} + +// Common part of Inspect and SetCacheKey. Outputs the rest of the function. +func (f *Function) finishFuncOutput(out *strings.Builder) string { out.WriteString("(") - ps := &ast.PrintState{Out: &out, Compact: true} + ps := &ast.PrintState{Out: out, Compact: true} ps.ComaList(f.Parameters) out.WriteString("){") f.Body.PrettyPrint(ps) out.WriteString("}") - f.CacheKey = out.String() - return f.CacheKey + return out.String() } func (f Function) Inspect() string { - if f.CacheKey == "" { - panic("CacheKey not set") + if f.Name == nil { + return f.CacheKey } - return f.CacheKey + out := strings.Builder{} + out.WriteString("func ") + out.WriteString(f.Name.Literal()) + return f.finishFuncOutput(&out) } type Array struct { diff --git a/parser/parser.go b/parser/parser.go index 210de790..9a06db41 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -485,7 +485,13 @@ func (p *Parser) parseBlockStatement() *ast.Statements { func (p *Parser) parseFunctionLiteral() ast.Node { lit := &ast.FunctionLiteral{} lit.Token = p.curToken - + // Optional name/identifier + if p.peekTokenIs(token.IDENT) { + p.nextToken() + name := &ast.Identifier{} + name.Token = p.curToken + lit.Name = name + } if !p.expectPeek(token.LPAREN) { return nil } diff --git a/parser/parser_test.go b/parser/parser_test.go index 6e0b6033..a690df43 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -289,6 +289,11 @@ e = "f"`, }`, `if i>3{10}else if i>2{20}else{30}`, }, + { + `func fact(n) {if (n<=1) {return 1} n*fact(n-1)}`, + "func fact(n) {\n\tif n <= 1 {\n\t\treturn 1\n\t}\n\tn * fact(n - 1)\n}", + "func fact(n){if n<=1{return 1}n*fact(n-1)}", + }, } for i, tt := range tests { l := lexer.New(tt.input) diff --git a/wasm/grol_wasm.html b/wasm/grol_wasm.html index 8b1ecde3..fdb606d2 100644 --- a/wasm/grol_wasm.html +++ b/wasm/grol_wasm.html @@ -105,7 +105,7 @@