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 @@