mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 14:57:18 +00:00
671 lines
18 KiB
V
671 lines
18 KiB
V
// parser.v — recursive-descent parser for the VuurRaaf language.
|
|
//
|
|
// Grammar (informal):
|
|
// program := import* (struct | enum | const | fn)*
|
|
// import := 'import' STRING
|
|
// struct := 'struct' IDENT '{' [IDENT (',' IDENT)*] '}'
|
|
// enum := 'enum' IDENT '{' [IDENT (',' IDENT)*] '}'
|
|
// const := 'const' IDENT '=' expr
|
|
// fn := 'fn' [ '(' IDENT IDENT ')' ] IDENT '(' [IDENT (',' IDENT)*] ')' block
|
|
// block := '{' stmt* '}'
|
|
// stmt := 'let' IDENT '=' expr
|
|
// | IDENT '=' expr
|
|
// | postfix '=' expr (a[i] = v, a.b = v)
|
|
// | 'if' cond block ['else' ('if' ... | block)]
|
|
// | 'match' expr '{' (expr block | 'else' block)* '}'
|
|
// | 'while' cond block
|
|
// | 'for' IDENT 'in' range block (range := expr '..' expr | expr '...' expr)
|
|
// | 'for' IDENT 'in' expr block (iterate an array)
|
|
// | 'break' | 'continue'
|
|
// | 'return' [expr]
|
|
// | 'assert' expr
|
|
// | expr
|
|
// cond := ['('] expr [')'] // parens optional
|
|
// expr := or ('or' or)*
|
|
// or := and ('and' and)*
|
|
// and := eq (('=='|'!=') eq)*
|
|
// eq := rel (('<'|'<='|'>'|'>=') rel)*
|
|
// rel := add (('+'|'-') add)*
|
|
// add := mul (('*'|'/'|'%') mul)*
|
|
// mul := ('not'|'-') mul | primary
|
|
// primary := INT | STR | 'true' | 'false' | IDENT ['(' args ')'] | '(' expr ')'
|
|
// | '{' [IDENT ':' expr (',' IDENT ':' expr)*] '}' (anonymous struct literal)
|
|
// | IDENT '{' IDENT ':' expr ... '}' (typed struct literal)
|
|
// postfix := primary ('.' IDENT ['(' args ')'] | '[' expr ']')*
|
|
// ('.' IDENT '(' ... ')' is a method call; everything else field access)
|
|
module compiler
|
|
|
|
pub fn parse(toks []Tok) !Program {
|
|
mut p := Parser{ toks: toks }
|
|
return p.parse_program()
|
|
}
|
|
|
|
struct Parser {
|
|
mut:
|
|
toks []Tok
|
|
pos int
|
|
}
|
|
|
|
fn (mut p Parser) cur() Tok {
|
|
if p.pos < p.toks.len {
|
|
return p.toks[p.pos]
|
|
}
|
|
return p.toks[p.toks.len - 1]
|
|
}
|
|
|
|
fn (mut p Parser) advance() Tok {
|
|
t := p.cur()
|
|
if p.pos < p.toks.len - 1 {
|
|
p.pos++
|
|
}
|
|
return t
|
|
}
|
|
|
|
fn (mut p Parser) expect(k TokKind, what string) !Tok {
|
|
t := p.cur()
|
|
if t.kind != k {
|
|
return error('expected ${what}, got "${t.lit}" at line ${t.line}')
|
|
}
|
|
return p.advance()
|
|
}
|
|
|
|
// parse_cond parses a condition, accepting either `if cond {` or `if (cond) {`.
|
|
fn (mut p Parser) parse_cond() !Expr {
|
|
if p.cur().kind == .lparen {
|
|
p.advance()
|
|
e := p.parse_expr()!
|
|
p.expect(.rparen, "')'")!
|
|
return e
|
|
}
|
|
return p.parse_expr()!
|
|
}
|
|
|
|
fn (mut p Parser) parse_program() !Program {
|
|
mut prog := Program{}
|
|
// imports come first
|
|
for p.cur().kind == .kw_import {
|
|
prog.imports << p.parse_import()!
|
|
}
|
|
// then top-level declarations
|
|
for p.cur().kind != .eof {
|
|
match p.cur().kind {
|
|
.kw_struct { prog.structs << p.parse_struct_decl()! }
|
|
.kw_enum { prog.enums << p.parse_enum_decl()! }
|
|
.kw_const { prog.consts << p.parse_const_decl()! }
|
|
.kw_fn { prog.fns << p.parse_fn()! }
|
|
else { return error('unexpected token "${p.cur().lit}" at line ${p.cur().line}') }
|
|
}
|
|
}
|
|
if prog.fns.len == 0 {
|
|
return error('no functions found in source')
|
|
}
|
|
return prog
|
|
}
|
|
|
|
// parse_struct_decl parses `struct Name { a, b, c }`.
|
|
fn (mut p Parser) parse_struct_decl() !StructDecl {
|
|
t := p.expect(.kw_struct, "'struct'")!
|
|
name := p.expect(.ident, 'struct name')!
|
|
p.expect(.lbrace, "'{'")!
|
|
mut fields := []string{}
|
|
if p.cur().kind != .rbrace {
|
|
for {
|
|
fields << p.expect(.ident, 'field name')!.lit
|
|
if p.cur().kind == .comma {
|
|
p.advance()
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
}
|
|
p.expect(.rbrace, "'}'")!
|
|
return StructDecl{ name: name.lit, fields: fields, line: t.line }
|
|
}
|
|
|
|
// parse_import parses `import "path/to/file.vr"`.
|
|
fn (mut p Parser) parse_import() !ImportDecl {
|
|
t := p.expect(.kw_import, "'import'")!
|
|
path := p.expect(.str_lit, 'import path')!
|
|
return ImportDecl{ path: path.lit, line: t.line }
|
|
}
|
|
|
|
// parse_enum_decl parses `enum Name { variant1 variant2 ... }`.
|
|
// Variants are separated by commas or newlines.
|
|
fn (mut p Parser) parse_enum_decl() !EnumDecl {
|
|
t := p.expect(.kw_enum, "'enum'")!
|
|
name := p.expect(.ident, 'enum name')!
|
|
p.expect(.lbrace, "'{'")!
|
|
mut variants := []string{}
|
|
if p.cur().kind != .rbrace {
|
|
for {
|
|
variants << p.expect(.ident, 'variant name')!.lit
|
|
if p.cur().kind == .comma {
|
|
p.advance()
|
|
continue
|
|
}
|
|
if p.cur().kind != .rbrace {
|
|
// expect another variant (newline-separated)
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
}
|
|
p.expect(.rbrace, "'}'")!
|
|
return EnumDecl{ name: name.lit, variants: variants, line: t.line }
|
|
}
|
|
|
|
// parse_const_decl parses `const NAME = expr`.
|
|
fn (mut p Parser) parse_const_decl() !ConstDecl {
|
|
t := p.expect(.kw_const, "'const'")!
|
|
name := p.expect(.ident, 'constant name')!
|
|
p.expect(.assign, "'='")!
|
|
value := p.parse_expr()!
|
|
return ConstDecl{ name: name.lit, value: value, line: t.line }
|
|
}
|
|
|
|
// parse_fn parses `fn name(params) { }` or a method `fn (p Type) name(params) { }`.
|
|
fn (mut p Parser) parse_fn() !FnDecl {
|
|
fn_tok := p.expect(.kw_fn, "'fn'")!
|
|
mut recv_name := ''
|
|
mut recv_type := ''
|
|
if p.cur().kind == .lparen {
|
|
// method: fn (p Type) name(...)
|
|
p.advance()
|
|
recv_name = p.expect(.ident, 'receiver name')!.lit
|
|
recv_type = p.expect(.ident, 'receiver type')!.lit
|
|
p.expect(.rparen, "')'")!
|
|
}
|
|
name := p.expect(.ident, 'function name')!
|
|
params := p.parse_params()!
|
|
body := p.parse_block()!
|
|
return FnDecl{ name: name.lit, recv_name: recv_name, recv_type: recv_type, params: params, body: body, line: fn_tok.line }
|
|
}
|
|
|
|
// parse_params parses `(a, b, c)` — the parameter list of a function.
|
|
fn (mut p Parser) parse_params() ![]string {
|
|
p.expect(.lparen, "'('")!
|
|
mut params := []string{}
|
|
if p.cur().kind != .rparen {
|
|
for {
|
|
params << p.expect(.ident, 'parameter name')!.lit
|
|
if p.cur().kind == .comma {
|
|
p.advance()
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
}
|
|
p.expect(.rparen, "')'")!
|
|
return params
|
|
}
|
|
|
|
fn (mut p Parser) parse_block() ![]Stmt {
|
|
p.expect(.lbrace, "'{'")!
|
|
mut stmts := []Stmt{}
|
|
for p.cur().kind != .rbrace {
|
|
if p.cur().kind == .eof {
|
|
return error('unexpected end of file inside block (missing "}")')
|
|
}
|
|
stmts << p.parse_stmt()!
|
|
}
|
|
p.expect(.rbrace, "'}'")!
|
|
return stmts
|
|
}
|
|
|
|
fn (mut p Parser) parse_stmt() !Stmt {
|
|
t := p.cur()
|
|
match t.kind {
|
|
.kw_let {
|
|
p.advance()
|
|
name := p.expect(.ident, 'variable name')!
|
|
p.expect(.assign, "'='")!
|
|
e := p.parse_expr()!
|
|
return Stmt{ kind: .let_stmt, target: name.lit, expr: e, line: t.line }
|
|
} .kw_if {
|
|
return p.parse_if(t)!
|
|
}
|
|
.kw_match {
|
|
p.advance()
|
|
subject := p.parse_expr()!
|
|
p.expect(.lbrace, "'{'")!
|
|
mut arms := []MatchArm{}
|
|
mut has_else := false
|
|
mut els_body := []Stmt{}
|
|
for p.cur().kind != .rbrace {
|
|
if p.cur().kind == .eof {
|
|
return error('unexpected end of file inside match (missing "}")')
|
|
}
|
|
if p.cur().kind == .kw_else {
|
|
p.advance()
|
|
els_body = p.parse_block()!
|
|
has_else = true
|
|
continue
|
|
}
|
|
val := p.parse_expr()!
|
|
body := p.parse_block()!
|
|
arms << MatchArm{ val: val, body: body }
|
|
}
|
|
p.expect(.rbrace, "'}'")!
|
|
return Stmt{ kind: .match_stmt, expr: subject, arms: arms, has_else: has_else, els_body: els_body, line: t.line }
|
|
}
|
|
.kw_while {
|
|
p.advance()
|
|
cond := p.parse_cond()!
|
|
body := p.parse_block()!
|
|
return Stmt{ kind: .while_stmt, cond: cond, body: body, line: t.line }
|
|
}
|
|
.kw_for {
|
|
p.advance()
|
|
name := p.expect(.ident, 'loop variable')!
|
|
p.expect(.kw_in, "'in'")!
|
|
first := p.parse_expr()!
|
|
if p.cur().kind == .dotdot || p.cur().kind == .dotdotdot {
|
|
inclusive := p.cur().kind == .dotdotdot
|
|
p.advance()
|
|
end := p.parse_expr()!
|
|
body := p.parse_block()!
|
|
return Stmt{ kind: .for_range_stmt, target: name.lit, expr: first, cond: end, inclusive: inclusive, body: body, line: t.line }
|
|
}
|
|
body := p.parse_block()!
|
|
return Stmt{ kind: .for_in_stmt, target: name.lit, expr: first, body: body, line: t.line }
|
|
}
|
|
.kw_return {
|
|
p.advance()
|
|
mut e := Expr{}
|
|
has_val := p.cur().kind != .rbrace
|
|
if has_val {
|
|
e = p.parse_expr()!
|
|
}
|
|
return Stmt{ kind: .ret_stmt, expr: e, has_val: has_val, line: t.line }
|
|
}
|
|
.kw_break {
|
|
p.advance()
|
|
return Stmt{ kind: .break_stmt, line: t.line }
|
|
}
|
|
.kw_continue {
|
|
p.advance()
|
|
return Stmt{ kind: .continue_stmt, line: t.line }
|
|
} .kw_assert {
|
|
p.advance()
|
|
mut e := Expr{}
|
|
if p.cur().kind == .lparen {
|
|
p.advance()
|
|
e = p.parse_expr()!
|
|
p.expect(.rparen, "')'")!
|
|
} else {
|
|
e = p.parse_expr()!
|
|
}
|
|
return Stmt{ kind: .assert_stmt, expr: e, line: t.line }
|
|
}
|
|
.ident {
|
|
p.advance()
|
|
mut e := Expr{}
|
|
if p.cur().kind == .lparen {
|
|
// a call statement: foo(args), optionally chained foo().x
|
|
e = p.parse_call(t)!
|
|
e = p.parse_postfix_tail(e)!
|
|
} else {
|
|
e = p.parse_postfix_tail(Expr{ kind: .ident, name: t.lit, line: t.line })!
|
|
}
|
|
if p.cur().kind == .assign {
|
|
// assignment to an ident, an index, or a field
|
|
p.advance()
|
|
rhs := p.parse_expr()!
|
|
match e.kind {
|
|
.ident {
|
|
return Stmt{ kind: .assign_stmt, target: e.name, expr: rhs, line: t.line }
|
|
}
|
|
.index {
|
|
return Stmt{ kind: .index_assign, base: *e.left, idx: *e.right, expr: rhs, line: t.line }
|
|
}
|
|
.field {
|
|
return Stmt{ kind: .field_assign, base: *e.left, target: e.name, expr: rhs, line: t.line }
|
|
}
|
|
else {
|
|
return error('cannot assign to this expression (line ${t.line})')
|
|
}
|
|
}
|
|
}
|
|
// compound assignment: x += expr, a[i] += expr, a.b += expr
|
|
if p.cur().kind == .plus_eq || p.cur().kind == .minus_eq || p.cur().kind == .star_eq || p.cur().kind == .slash_eq {
|
|
op_tok := p.advance()
|
|
rhs := p.parse_expr()!
|
|
bin_op := match op_tok.kind {
|
|
.plus_eq { TokKind.plus }
|
|
.minus_eq { TokKind.minus }
|
|
.star_eq { TokKind.star }
|
|
.slash_eq { TokKind.slash }
|
|
else { return error('unexpected compound operator (line ${t.line})') }
|
|
}
|
|
// desugar: LHS op= RHS → LHS = LHS op RHS
|
|
match e.kind {
|
|
.ident {
|
|
full_rhs := bin_node(bin_op, e, rhs, op_tok.line)
|
|
return Stmt{ kind: .assign_stmt, target: e.name, expr: full_rhs, line: t.line }
|
|
}
|
|
.index {
|
|
full_rhs := bin_node(bin_op, e, rhs, op_tok.line)
|
|
return Stmt{ kind: .index_assign, base: *e.left, idx: *e.right, expr: full_rhs, line: t.line }
|
|
}
|
|
.field {
|
|
full_rhs := bin_node(bin_op, e, rhs, op_tok.line)
|
|
return Stmt{ kind: .field_assign, base: *e.left, target: e.name, expr: full_rhs, line: t.line }
|
|
}
|
|
else {
|
|
return error('cannot use compound assignment on this expression (line ${t.line})')
|
|
}
|
|
}
|
|
}
|
|
return Stmt{ kind: .expr_stmt, expr: e, line: t.line }
|
|
}
|
|
.kw_print, .kw_println {
|
|
p.advance()
|
|
e := p.parse_call(t)!
|
|
return Stmt{ kind: .expr_stmt, expr: e, line: t.line }
|
|
}
|
|
else {
|
|
return error('unexpected token "${t.lit}" at line ${t.line}')
|
|
}
|
|
}
|
|
}
|
|
|
|
// parse_if parses `if cond block ['else' ('if' ... | block)]`. An `else if`
|
|
// chain is represented by putting the nested if-statement in the else list,
|
|
// so codegen needs no special casing.
|
|
fn (mut p Parser) parse_if(t Tok) !Stmt {
|
|
p.advance()
|
|
cond := p.parse_cond()!
|
|
body := p.parse_block()!
|
|
mut els := []Stmt{}
|
|
if p.cur().kind == .kw_else {
|
|
p.advance()
|
|
if p.cur().kind == .kw_if {
|
|
els << p.parse_if(p.cur())!
|
|
} else {
|
|
els = p.parse_block()!
|
|
}
|
|
}
|
|
return Stmt{ kind: .if_stmt, cond: cond, body: body, els: els, line: t.line }
|
|
}
|
|
|
|
// bin_node allocates a binary-operator node. It takes copies of the operands
|
|
// so that `&l`/`&r` target fresh heap objects (taking the address of a local
|
|
// that is later reassigned would create a self-referential node).
|
|
fn bin_node(op TokKind, left Expr, right Expr, line int) Expr {
|
|
mut l := left
|
|
mut r := right
|
|
return Expr{ kind: .binary, op: op, left: &l, right: &r, line: line }
|
|
}
|
|
|
|
fn unary_node(op TokKind, operand Expr, line int) Expr {
|
|
mut o := operand
|
|
return Expr{ kind: .unary, op: op, right: &o, line: line }
|
|
}
|
|
|
|
// index_node builds `base[idx]`.
|
|
fn index_node(base Expr, idx Expr, line int) Expr {
|
|
mut b := base
|
|
mut i := idx
|
|
return Expr{ kind: .index, left: &b, right: &i, line: line }
|
|
}
|
|
|
|
// field_node builds `base.name`.
|
|
fn field_node(base Expr, name string, line int) Expr {
|
|
mut b := base
|
|
return Expr{ kind: .field, left: &b, name: name, line: line }
|
|
}
|
|
|
|
// method_node builds `base.name(args)`.
|
|
fn method_node(recv Expr, name string, args []Expr, line int) Expr {
|
|
mut r := recv
|
|
return Expr{ kind: .method_call, left: &r, name: name, args: args, line: line }
|
|
}
|
|
|
|
fn (mut p Parser) parse_expr() !Expr {
|
|
return p.parse_or()!
|
|
}
|
|
|
|
fn (mut p Parser) parse_or() !Expr {
|
|
mut e := p.parse_and()!
|
|
for p.cur().kind == .kw_or {
|
|
op := p.advance()
|
|
rhs := p.parse_and()!
|
|
e = bin_node(op.kind, e, rhs, op.line)
|
|
}
|
|
return e
|
|
}
|
|
|
|
fn (mut p Parser) parse_and() !Expr {
|
|
mut e := p.parse_eq()!
|
|
for p.cur().kind == .kw_and {
|
|
op := p.advance()
|
|
rhs := p.parse_eq()!
|
|
e = bin_node(op.kind, e, rhs, op.line)
|
|
}
|
|
return e
|
|
}
|
|
|
|
fn (mut p Parser) parse_eq() !Expr {
|
|
mut e := p.parse_rel()!
|
|
for p.cur().kind == .eq_eq || p.cur().kind == .not_eq {
|
|
op := p.advance()
|
|
rhs := p.parse_rel()!
|
|
e = bin_node(op.kind, e, rhs, op.line)
|
|
}
|
|
return e
|
|
}
|
|
|
|
fn (mut p Parser) parse_rel() !Expr {
|
|
mut e := p.parse_add()!
|
|
for p.cur().kind == .lt || p.cur().kind == .le || p.cur().kind == .gt || p.cur().kind == .ge {
|
|
op := p.advance()
|
|
rhs := p.parse_add()!
|
|
e = bin_node(op.kind, e, rhs, op.line)
|
|
}
|
|
return e
|
|
}
|
|
|
|
fn (mut p Parser) parse_add() !Expr {
|
|
mut e := p.parse_mul()!
|
|
for p.cur().kind == .plus || p.cur().kind == .minus {
|
|
op := p.advance()
|
|
rhs := p.parse_mul()!
|
|
e = bin_node(op.kind, e, rhs, op.line)
|
|
}
|
|
return e
|
|
}
|
|
|
|
fn (mut p Parser) parse_mul() !Expr {
|
|
mut e := p.parse_unary()!
|
|
for p.cur().kind == .star || p.cur().kind == .slash || p.cur().kind == .percent {
|
|
op := p.advance()
|
|
rhs := p.parse_unary()!
|
|
e = bin_node(op.kind, e, rhs, op.line)
|
|
}
|
|
return e
|
|
}
|
|
|
|
fn (mut p Parser) parse_unary() !Expr {
|
|
t := p.cur()
|
|
if t.kind == .kw_not || t.kind == .minus {
|
|
p.advance()
|
|
e := p.parse_unary()!
|
|
return unary_node(t.kind, e, t.line)
|
|
}
|
|
return p.parse_postfix()!
|
|
}
|
|
|
|
// parse_postfix handles postfix operators after a primary: indexing
|
|
// `a[i]` (chainable `a[i][j]`) and field access `a.b` (chainable `a.b.c`),
|
|
// in any mix: `a[i].b`, `a.b[i]`, ...
|
|
fn (mut p Parser) parse_postfix() !Expr {
|
|
mut e := p.parse_primary()!
|
|
return p.parse_postfix_tail(e)!
|
|
}
|
|
|
|
// parse_postfix_tail continues a postfix chain from an already-parsed base.
|
|
// It takes the base by value (Expr only holds pointers to heap-allocated
|
|
// child nodes) and returns the extended chain. A `.name(` is a method call
|
|
// (the receiver is the expression the dot was applied to); `.name` without
|
|
// parens is plain field access.
|
|
fn (mut p Parser) parse_postfix_tail(e Expr) !Expr {
|
|
mut cur := e
|
|
for {
|
|
if p.cur().kind == .lbracket {
|
|
p.advance()
|
|
idx := p.parse_expr()!
|
|
p.expect(.rbracket, "']'")!
|
|
cur = index_node(cur, idx, cur.line)
|
|
continue
|
|
}
|
|
if p.cur().kind == .dot {
|
|
p.advance()
|
|
name := p.expect(.ident, 'field name')!
|
|
f := field_node(cur, name.lit, cur.line)
|
|
if p.cur().kind == .lparen {
|
|
args := p.parse_args()!
|
|
cur = method_node(*f.left, f.name, args, f.line)
|
|
} else {
|
|
cur = f
|
|
}
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
return cur
|
|
}
|
|
|
|
fn (mut p Parser) parse_primary() !Expr {
|
|
t := p.cur()
|
|
match t.kind {
|
|
.int_lit {
|
|
p.advance()
|
|
return Expr{ kind: .int_lit, int_v: t.lit.i64(), line: t.line }
|
|
}
|
|
.str_lit {
|
|
p.advance()
|
|
return Expr{ kind: .str_lit, str_v: t.lit, line: t.line }
|
|
}
|
|
.kw_true {
|
|
p.advance()
|
|
return Expr{ kind: .bool_lit, int_v: 1, line: t.line }
|
|
}
|
|
.kw_false {
|
|
p.advance()
|
|
return Expr{ kind: .bool_lit, int_v: 0, line: t.line }
|
|
}
|
|
.lparen {
|
|
p.advance()
|
|
e := p.parse_expr()!
|
|
p.expect(.rparen, "')'")!
|
|
return e
|
|
}
|
|
.lbracket {
|
|
p.advance()
|
|
mut elems := []Expr{}
|
|
if p.cur().kind != .rbracket {
|
|
for {
|
|
elems << p.parse_expr()!
|
|
if p.cur().kind == .comma {
|
|
p.advance()
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
}
|
|
p.expect(.rbracket, "']'")!
|
|
return Expr{ kind: .array_lit, elems: elems, line: t.line }
|
|
}
|
|
.lbrace {
|
|
// anonymous struct literal: { name: expr, ... }
|
|
fields := p.parse_struct_fields()!
|
|
return Expr{ kind: .struct_lit, name: '', fields: fields, line: t.line }
|
|
}
|
|
.ident {
|
|
p.advance()
|
|
if p.cur().kind == .lbrace && p.looks_like_struct_lit() {
|
|
// typed struct literal: Name{ name: expr, ... } — only when the
|
|
// brace clearly opens a field list (`{ ident :`), so `if x {` and
|
|
// match arms like `x { ... }` still parse as blocks
|
|
fields := p.parse_struct_fields()!
|
|
return Expr{ kind: .struct_lit, name: t.lit, fields: fields, line: t.line }
|
|
}
|
|
return p.parse_call_or_ident(t)!
|
|
}
|
|
.kw_print, .kw_println {
|
|
p.advance()
|
|
return p.parse_call(t)!
|
|
}
|
|
else {
|
|
return error('unexpected token "${t.lit}" at line ${t.line}')
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (mut p Parser) parse_call_or_ident(t Tok) !Expr {
|
|
if p.cur().kind == .lparen {
|
|
return p.parse_call(t)!
|
|
}
|
|
return Expr{ kind: .ident, name: t.lit, line: t.line }
|
|
}
|
|
|
|
fn (mut p Parser) parse_call(name Tok) !Expr {
|
|
args := p.parse_args()!
|
|
return Expr{ kind: .call, name: name.lit, args: args, line: name.line }
|
|
}
|
|
|
|
// parse_args parses `(e1, e2, ...)` and returns the argument expressions.
|
|
fn (mut p Parser) parse_args() ![]Expr {
|
|
p.expect(.lparen, "'('")!
|
|
mut args := []Expr{}
|
|
if p.cur().kind != .rparen {
|
|
for {
|
|
args << p.parse_expr()!
|
|
if p.cur().kind == .comma {
|
|
p.advance()
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
}
|
|
p.expect(.rparen, "')'")!
|
|
return args
|
|
}
|
|
|
|
// parse_struct_fields parses `{ name: expr, ... }` or `{ "key": expr, ... }` and returns the fields.
|
|
fn (mut p Parser) parse_struct_fields() ![]StructField {
|
|
p.expect(.lbrace, "'{'")!
|
|
mut fields := []StructField{}
|
|
if p.cur().kind != .rbrace {
|
|
for {
|
|
// field name can be an identifier or a string literal (for maps)
|
|
mut fname := ''
|
|
if p.cur().kind == .str_lit {
|
|
fname = p.advance().lit
|
|
} else {
|
|
fname = p.expect(.ident, 'field name')!.lit
|
|
}
|
|
p.expect(.colon, "':'")!
|
|
val := p.parse_expr()!
|
|
fields << StructField{ name: fname, val: val }
|
|
if p.cur().kind == .comma {
|
|
p.advance()
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
}
|
|
p.expect(.rbrace, "'}'")!
|
|
return fields
|
|
}
|
|
|
|
// looks_like_struct_lit reports whether the current token (`{`) opens a typed
|
|
// struct literal: the tokens after the brace must be `ident :`.
|
|
fn (mut p Parser) looks_like_struct_lit() bool {
|
|
if p.pos + 2 >= p.toks.len {
|
|
return false
|
|
}
|
|
// typed struct literal: `{ ident :` or map literal: `{ "key" :`
|
|
return (p.toks[p.pos + 1].kind == .ident || p.toks[p.pos + 1].kind == .str_lit) && p.toks[p.pos + 2].kind == .colon
|
|
}
|