Skip to content

Commit 65d4127

Browse files
committed
parser: Implement for niladic function calls without parens
Implement niladic function calls without parens, so that the following Evy code is now valid: if rand1 > 0.5 print "Heads" end print read print rand1 rand1+10 which reads better than `if (rand1) > 0.5` etc.
1 parent a134663 commit 65d4127

File tree

5 files changed

+102
-7
lines changed

5 files changed

+102
-7
lines changed

pkg/parser/ast.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ func (f *FuncDefStmt) Type() *Type {
142142
return f.ReturnType
143143
}
144144

145+
func (f *FuncDefStmt) isNiladic() bool {
146+
return len(f.Params) == 0 && f.VariadicParam == nil
147+
}
148+
145149
// EventHandlerStmt is an AST node that represents an event handler
146150
// definition. It includes the handler body, such as:
147151
//

pkg/parser/expression.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,28 +49,30 @@ var precedences = map[lexer.TokenType]precedence{
4949

5050
func (p *parser) parseTopLevelExpr() Node {
5151
tok := p.cur
52-
if tok.Type == lexer.IDENT && p.funcs[tok.Literal] != nil {
53-
return p.parseFuncCall()
52+
if tok.Type == lexer.IDENT && p.funcs[tok.Literal] != nil && !p.funcs[tok.Literal].isNiladic() {
53+
return p.parseFuncCall(true)
5454
}
5555
return p.parseExpr(lowestPrec)
5656
}
5757

58-
func (p *parser) parseFuncCall() Node {
58+
func (p *parser) parseFuncCall(isTopLevel bool) Node {
5959
fc := &FuncCall{token: p.cur, Name: p.cur.Literal}
6060
p.advance() // advance past function name IDENT
6161
funcDef := p.funcs[fc.Name]
6262
funcDef.isCalled = true
6363
fc.FuncDef = funcDef
64-
fc.Arguments = p.parseExprList()
65-
p.assertArgTypes(fc.FuncDef, fc.Arguments)
64+
if isTopLevel || !funcDef.isNiladic() {
65+
fc.Arguments = p.parseExprList()
66+
p.assertArgTypes(fc.FuncDef, fc.Arguments)
67+
}
6668
return fc
6769
}
6870

6971
func (p *parser) parseExpr(prec precedence) Node {
7072
var left Node
7173
switch p.cur.Type {
7274
case lexer.IDENT:
73-
left = p.lookupVar()
75+
left = p.parseIdentExpr()
7476
case lexer.STRING_LIT, lexer.NUM_LIT, lexer.TRUE, lexer.FALSE, lexer.LBRACKET, lexer.LCURLY:
7577
left = p.parseLiteral()
7678
case lexer.BANG, lexer.MINUS:
@@ -122,6 +124,14 @@ func (p *parser) isAtExprEnd() bool {
122124
return p.isAtEOL()
123125
}
124126

127+
func (p *parser) parseIdentExpr() Node {
128+
funcDef := p.funcs[p.cur.Literal]
129+
if funcDef != nil && funcDef.isNiladic() {
130+
return p.parseFuncCall(false) // not a top-level func call
131+
}
132+
return p.lookupVar()
133+
}
134+
125135
func (p *parser) parseUnaryExpr() Node {
126136
tok := p.cur
127137
unaryExp := &UnaryExpression{token: tok, Op: op(tok)}

pkg/parser/expression_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ func TestParseTopLevelExpression(t *testing.T) {
7373
`print s[1]`: "print(any((s[1])))",
7474
"print map2[s]": "print(any((map2[s])))",
7575

76+
// niladic
77+
`print rand1`: "print(any(rand1()))",
78+
`print rand1 rand1`: "print(any(rand1()), any(rand1()))",
79+
7680
// // Index expression
7781
"arr[1]": "(arr[1])",
7882
"arr2[1][2]": "((arr2[1])[2])",
@@ -300,3 +304,75 @@ func TestParseTopLevelExpressionErr(t *testing.T) {
300304
assert.Equal(t, wantErr, got.Error(), "input: %s\nerrors:\n%s", input, parser.errors)
301305
}
302306
}
307+
308+
func TestParseNiladic(t *testing.T) {
309+
inputs := map[string]string{
310+
"rand1": "print rand1",
311+
"rand1-twice": "print rand1 rand1",
312+
"rand1-expr": "print rand1+10",
313+
"rand1-toplevelexpr": `
314+
n := rand1+0.5
315+
print n
316+
`,
317+
"rand1-toplevelexpr-space": `
318+
if rand1 > 0.5
319+
print "big"
320+
end
321+
`,
322+
"custom1": `
323+
func answer:string
324+
return "42"
325+
end
326+
327+
print answer`,
328+
}
329+
330+
for name, input := range inputs {
331+
t.Run(name, func(t *testing.T) {
332+
parser := newParser(input, testBuiltins())
333+
_ = parser.parse()
334+
assertNoParseError(t, parser, input)
335+
})
336+
}
337+
}
338+
339+
func TestParseNiladicErr(t *testing.T) {
340+
type inputWithError struct {
341+
input string
342+
wantErrMsg string
343+
}
344+
inputs := map[string]inputWithError{
345+
"rand1-group": {
346+
input: "print (rand1 100)",
347+
wantErrMsg: `line 1 column 14: expected ")", got ""`,
348+
},
349+
"custom1": {
350+
input: `
351+
func answer
352+
print "42"
353+
end
354+
355+
len answer`,
356+
wantErrMsg: `line 6 column 5: "len" takes 1st argument of type any, found none`,
357+
},
358+
"custom1-variadic": {
359+
input: `
360+
func answer
361+
print "42"
362+
end
363+
364+
print answer`,
365+
wantErrMsg: `line 6 column 7: "print" takes variadic arguments of type any, found none`,
366+
},
367+
}
368+
369+
for name, tc := range inputs {
370+
t.Run(name, func(t *testing.T) {
371+
parser := newParser(tc.input, testBuiltins())
372+
_ = parser.parse()
373+
assertParseError(t, parser, tc.input)
374+
gotErr := parser.errors.Truncate(1)
375+
assert.Equal(t, tc.wantErrMsg, gotErr.Error())
376+
})
377+
}
378+
}

pkg/parser/parser.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,7 @@ func (p *parser) isFuncCall(tok *lexer.Token) bool {
564564
}
565565

566566
func (p *parser) parseFunCallStatement() Node {
567-
fc := p.parseFuncCall().(*FuncCall)
567+
fc := p.parseFuncCall(true).(*FuncCall) // top-level funcCall
568568
p.assertEOL()
569569
fcs := &FuncCallStmt{token: fc.token, FuncCall: fc}
570570
p.recordComment(fcs)

pkg/parser/parser_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,6 +1785,11 @@ func testBuiltins() Builtins {
17851785
},
17861786
ReturnType: STRING_TYPE,
17871787
},
1788+
"rand1": {
1789+
Name: "rand1",
1790+
Params: []*Var{},
1791+
ReturnType: NUM_TYPE,
1792+
},
17881793
}
17891794
eventHandlers := map[string]*EventHandlerStmt{
17901795
"down": {

0 commit comments

Comments
 (0)