Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement func name(...) {....} as short for name=func(...) {...} #92

Merged
merged 6 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit out of this PR, but maybe you could use godoc link

Suggested change
func (f *Function) finishFuncOutput(out *strings.Builder) string {
// Common part of [Inspect] and [SetCacheKey]. Prints the rest of the function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure about the godoc link in such case

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

godoc doesn't generate doc for private function does it? or maybe there is an option to do so I suppose

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point

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
Comment on lines +491 to +493
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit difficult to read

Maybe this

Suggested change
name := &ast.Identifier{}
name.Token = p.curToken
lit.Name = name
lit.Name = &ast.Identifier{Token: p.curToken}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the rest of the code might be aligned with current code.

Copy link
Member Author

@ldemailly ldemailly Aug 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's like that because the Tokens are in Base... so you'd need to do the super annoying {Base: Base{Token: ...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh indeed

}
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