// parser.v — recursive-descent parser for the VuurRaaf language. // // Grammar (informal): // program := fn* // fn := 'fn' IDENT '(' [IDENT (',' IDENT)*] ')' block // block := '{' stmt* '}' // stmt := 'let' IDENT '=' expr // | IDENT '=' expr // | postfix '=' expr (a[i] = v) // | 'if' cond block ['else' block] // | 'while' cond block // | 'for' IDENT 'in' range block (range := expr '..' expr | expr '...' expr) // | 'for' IDENT 'in' expr block (iterate an array) // | '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 ')' module compiler pub enum ExprKind { int_lit str_lit bool_lit ident array_lit index unary binary call } pub struct Expr { pub mut: kind ExprKind int_v i64 str_v string name string op TokKind left &Expr = unsafe { nil } right &Expr = unsafe { nil } elems []Expr args []Expr line int } pub enum StmtKind { expr_stmt let_stmt assign_stmt index_assign if_stmt while_stmt for_range_stmt for_in_stmt ret_stmt assert_stmt } pub struct Stmt { pub mut: kind StmtKind target string expr Expr cond Expr base Expr // index_assign: the indexed expression idx Expr // index_assign: the index expression body []Stmt els []Stmt has_val bool inclusive bool // for_range_stmt: `..` (false) vs `...` (true) line int } pub struct FnDecl { pub mut: name string params []string body []Stmt line int } pub struct Program { pub mut: fns []FnDecl } 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{} for p.cur().kind != .eof { prog.fns << p.parse_fn()! } if prog.fns.len == 0 { return error('no functions found in source') } return prog } fn (mut p Parser) parse_fn() !FnDecl { fn_tok := p.expect(.kw_fn, "'fn'")! name := p.expect(.ident, 'function name')! 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, "')'")! body := p.parse_block()! return FnDecl{ name: name.lit, params: params, body: body, line: fn_tok.line } } 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 { p.advance() cond := p.parse_cond()! body := p.parse_block()! mut els := []Stmt{} if p.cur().kind == .kw_else { p.advance() els = p.parse_block()! } return Stmt{ kind: .if_stmt, cond: cond, body: body, els: els, 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_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() if p.cur().kind == .assign { p.advance() e := p.parse_expr()! return Stmt{ kind: .assign_stmt, target: t.lit, expr: e, line: t.line } } if p.cur().kind == .lbracket { // a[i] = v or a[i] (expression statement) e := p.parse_index_chain(t)! if p.cur().kind == .assign { p.advance() rhs := p.parse_expr()! return Stmt{ kind: .index_assign, base: *e.left, idx: *e.right, expr: rhs, line: t.line } } return Stmt{ kind: .expr_stmt, expr: e, line: t.line } } e := p.parse_call_or_ident(t)! 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}') } } } // 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 } } 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 indexing: `base[expr]`, possibly chained `a[i][j]`. fn (mut p Parser) parse_postfix() !Expr { mut e := p.parse_primary()! for p.cur().kind == .lbracket { p.advance() idx := p.parse_expr()! p.expect(.rbracket, "']'")! e = index_node(e, idx, e.line) } return e } // parse_index_chain is like parse_postfix but starts from an already-consumed // identifier token (used for statements like `a[i] = v`). fn (mut p Parser) parse_index_chain(t Tok) !Expr { mut e := Expr{ kind: .ident, name: t.lit, line: t.line } for p.cur().kind == .lbracket { p.advance() idx := p.parse_expr()! p.expect(.rbracket, "']'")! e = index_node(e, idx, t.line) } return e } 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 } } .ident { p.advance() 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 { 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 Expr{ kind: .call, name: name.lit, args: args, line: name.line } }