Skip to content

Commit

Permalink
implement func name(...) {....} as short for name=func(...) {...} (
Browse files Browse the repository at this point in the history
…#92)

* follow up on #91

* Fixes #44: allow func name(...) {...} for name=func(...

* use the expected short hand on the web example

* change fib example to be shorter and use the short hand of func name()

* share common part of SetCacheKey and Inspect (with name) for functions

* further share inspect/cachekey code
  • Loading branch information
ldemailly authored Aug 2, 2024
1 parent d8fedc3 commit f5798dc
Show file tree
Hide file tree
Showing 9 changed files with 50 additions and 19 deletions.
9 changes: 7 additions & 2 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 5 additions & 1 deletion eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions eval/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
12 changes: 5 additions & 7 deletions examples/fib.gr
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion examples/sample.gr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 17 additions & 6 deletions object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
8 changes: 7 additions & 1 deletion parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
5 changes: 5 additions & 0 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion wasm/grol_wasm.html
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
<label for="input">Edit the sample/Enter your GROL code here:</label>
<textarea id="input" rows="12" cols="80">
println("Outputting a smiley: 😀")
fact=func(n) { // function example
func fact(n) { // function example, name is optional
log("called fact ", n) // log output
// parenthesis are optional:
if (n<=1) {
Expand Down

0 comments on commit f5798dc

Please sign in to comment.