Skip to content

Commit bf30361

Browse files
committed
parser: Add assignment parsing
Add assignment parsing and assignment AST node. Extract parseAssignable from parseTerm to reuse here. This will be extended by indexed and field selector expressions.
1 parent 996dd8c commit bf30361

File tree

3 files changed

+115
-13
lines changed

3 files changed

+115
-13
lines changed

pkg/parser/ast.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ type Declaration struct {
3636
Value Node // literal, expression, assignable, ...
3737
}
3838

39+
type Assignment struct {
40+
Token *lexer.Token
41+
Target Node // Variable, index or field expression
42+
Value Node // literal, expression, assignable, ...
43+
}
44+
3945
type Return struct {
4046
Token *lexer.Token
4147
Value Node // literal, expression, assignable, ...
@@ -141,6 +147,13 @@ func (r *Return) Type() *Type {
141147
return r.T
142148
}
143149

150+
func (a *Assignment) String() string {
151+
return a.Target.String() + " = " + a.Value.String()
152+
}
153+
func (a *Assignment) Type() *Type {
154+
return a.Target.Type()
155+
}
156+
144157
func (f *FuncDecl) String() string {
145158
s := make([]string, len(f.Params))
146159
for i, param := range f.Params {

pkg/parser/parser.go

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func (p *Parser) parseStatement(scope *scope) Node {
168168
case lexer.IDENT:
169169
switch p.peek.Type {
170170
case lexer.ASSIGN, lexer.LBRACKET, lexer.DOT:
171-
return p.parseAssignStatement(scope) // TODO
171+
return p.parseAssignmentStatement(scope)
172172
case lexer.COLON:
173173
return p.parseTypedDeclStatement(scope)
174174
case lexer.DECLARE:
@@ -196,9 +196,44 @@ func (p *Parser) parseStatement(scope *scope) Node {
196196
return nil
197197
}
198198

199-
func (p *Parser) parseAssignStatement(scope *scope) Node {
199+
func (p *Parser) parseAssignmentStatement(scope *scope) Node {
200+
if p.isFuncCall(p.cur) {
201+
p.appendError("cannot assign to '" + p.cur.Literal + "' as it is a function not a variable")
202+
p.advancePastNL()
203+
return nil
204+
}
205+
206+
target := p.parseAssignable(scope)
207+
tok := p.cur
208+
if target == nil {
209+
p.advancePastNL()
210+
return nil
211+
}
212+
p.assertToken(lexer.ASSIGN)
213+
p.advance()
214+
value := p.parseTopLevelExpression(scope)
215+
if value == nil {
216+
p.advancePastNL()
217+
return nil
218+
}
219+
if !target.Type().Accepts(value.Type()) {
220+
msg := "'" + target.String() + "' accepts values of type " + target.Type().Format() + ", found " + value.Type().Format()
221+
p.appendErrorForToken(msg, tok)
222+
}
223+
p.assertEOL()
200224
p.advancePastNL()
201-
return nil
225+
return &Assignment{Token: tok, Target: target, Value: value}
226+
}
227+
228+
func (p *Parser) parseAssignable(scope *scope) Node {
229+
name := p.cur.Literal
230+
p.advance()
231+
v, ok := scope.get(name)
232+
if !ok {
233+
p.appendError("unknown variable name '" + name + "'")
234+
return nil
235+
}
236+
return v
202237
}
203238

204239
func (p *Parser) parseFuncDeclSignature() *FuncDecl {
@@ -329,18 +364,12 @@ func (p *Parser) parseTerm(scope *scope) Node {
329364
//TODO: UNARY_OP Term; composite literals; assignable; slice; type_assertion; "(" toplevel_expr ")"
330365
tt := p.cur.TokenType()
331366
if tt == lexer.IDENT {
332-
varName := p.cur.Literal
333-
p.advance()
334-
v, ok := scope.get(varName)
335-
if !ok {
336-
if _, ok := p.funcs[varName]; ok {
337-
p.appendError("function call must be parenthesized: (" + varName + " ...)")
338-
} else {
339-
p.appendError("unknown variable name '" + varName + "'")
340-
}
367+
if p.isFuncCall(p.cur) {
368+
p.appendError("function call must be parenthesized: (" + p.cur.Literal + " ...)")
369+
p.advance()
341370
return nil
342371
}
343-
return v
372+
return p.parseAssignable(scope)
344373
}
345374
if p.isLiteral() {
346375
lit := p.parseLiteral(scope)

pkg/parser/parser_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ func TestFunctionCallError(t *testing.T) {
129129
`a := f0`: "line 1 column 8: invalid declaration, function 'f0' has no return value",
130130
`f0 "arg"`: "line 1 column 9: 'f0' takes 0 arguments, found 1",
131131
`f2`: "line 1 column 3: 'f2' takes 1 argument, found 0",
132+
`f2 f1`: "line 1 column 4: function call must be parenthesized: (f1 ...)",
132133
`f1 "arg"`: "line 1 column 9: 'f1' takes variadic arguments of type 'num', found 'string'",
133134
`f3 1 2`: "line 1 column 7: 'f3' takes 2nd argument of type 'string', found 'num'",
134135
`f3 "1" "2"`: "line 1 column 11: 'f3' takes 1st argument of type 'num', found 'string'",
@@ -209,6 +210,65 @@ end
209210
assert.Equal(t, "return n1", returnStmt.String())
210211
}
211212

213+
func TestFuncAssignment(t *testing.T) {
214+
inputs := []string{`
215+
a := 1
216+
b:num
217+
b = a
218+
`, `
219+
a:num
220+
b:num
221+
b = a
222+
`, `
223+
a:num
224+
b:any
225+
b = a
226+
`,
227+
}
228+
for _, input := range inputs {
229+
parser := New(input, testBuiltins())
230+
_ = parser.Parse()
231+
assertNoParseError(t, parser, input)
232+
}
233+
}
234+
235+
func TestFuncAssignmentErr(t *testing.T) {
236+
inputs := map[string]string{`
237+
b:num
238+
b = true
239+
`: "line 3 column 3: 'b' accepts values of type num, found bool",
240+
`
241+
a:= 1
242+
a = b
243+
`: "line 3 column 6: unknown variable name 'b'",
244+
`
245+
a:= 1
246+
b = a
247+
`: "line 3 column 3: unknown variable name 'b'",
248+
`
249+
a:= 1
250+
a = num[]
251+
`: "line 3 column 3: 'a' accepts values of type num, found num[]",
252+
`
253+
a:num
254+
b:any
255+
a = b
256+
`: "line 4 column 3: 'a' accepts values of type num, found any",
257+
`
258+
func fn
259+
return true
260+
end
261+
fn = 3
262+
`: "line 5 column 1: cannot assign to 'fn' as it is a function not a variable",
263+
}
264+
for input, wantErr := range inputs {
265+
parser := New(input, testBuiltins())
266+
_ = parser.Parse()
267+
assertParseError(t, parser, input)
268+
assert.Equal(t, wantErr, parser.MaxErrorsString(1))
269+
}
270+
}
271+
212272
func TestScope(t *testing.T) {
213273
inputs := []string{`
214274
x := 1

0 commit comments

Comments
 (0)