mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 16:07:01 +00:00
Workflows
This commit is contained in:
@@ -0,0 +1,66 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install V
|
||||||
|
run: |
|
||||||
|
git clone --depth 1 https://github.com/vlang/v /tmp/v
|
||||||
|
cd /tmp/v && make
|
||||||
|
sudo ln -sf /tmp/v/v /usr/local/bin/v
|
||||||
|
v version
|
||||||
|
|
||||||
|
- name: Build toolchain
|
||||||
|
run: v -o bin/vr .
|
||||||
|
|
||||||
|
- name: Smoke test — compile and run hello.vr
|
||||||
|
run: ./bin/vr run examples/hello.vr
|
||||||
|
|
||||||
|
- name: Smoke test — compile and run fib.vr
|
||||||
|
run: ./bin/vr run examples/fib.vr
|
||||||
|
|
||||||
|
- name: Smoke test — compile and run structs.vr
|
||||||
|
run: ./bin/vr run examples/structs.vr
|
||||||
|
|
||||||
|
- name: Smoke test — compile and run match.vr
|
||||||
|
run: ./bin/vr run examples/match.vr
|
||||||
|
|
||||||
|
- name: Smoke test — compile and run arrays.vr
|
||||||
|
run: ./bin/vr run examples/arrays.vr
|
||||||
|
|
||||||
|
- name: Smoke test — assemble, link, and run math.vasm
|
||||||
|
run: |
|
||||||
|
./bin/vr assemble examples/math.vasm -o /tmp/math.vobj
|
||||||
|
./bin/vr link /tmp/math.vobj -o /tmp/math.vbin
|
||||||
|
./bin/vr run /tmp/math.vbin
|
||||||
|
|
||||||
|
- name: Smoke test — multi-file compile + link
|
||||||
|
run: |
|
||||||
|
./bin/vr compile examples/lib.vr -o /tmp/lib.vobj
|
||||||
|
./bin/vr compile examples/use_lib.vr -o /tmp/use_lib.vobj
|
||||||
|
./bin/vr link /tmp/lib.vobj /tmp/use_lib.vobj -o /tmp/use_lib.vbin
|
||||||
|
./bin/vr run /tmp/use_lib.vbin
|
||||||
|
|
||||||
|
- name: Run test suite
|
||||||
|
# test_failing is intentionally skipped — it asserts 1 == 2 on purpose.
|
||||||
|
# The test runner exits 1 if any test_* fails, so we run every .vr file
|
||||||
|
# that does NOT contain a test_failing function. examples/tests.vr has
|
||||||
|
# test_failing, so we compile a clean copy without it.
|
||||||
|
run: |
|
||||||
|
grep -n 'fn test_failing' examples/tests.vr || true
|
||||||
|
sed '/fn test_failing/,/^}/d' examples/tests.vr > /tmp/ci_tests.vr
|
||||||
|
./bin/vr test /tmp/ci_tests.vr
|
||||||
+89
-4
@@ -85,6 +85,8 @@ mut:
|
|||||||
symbols []obj.Symbol
|
symbols []obj.Symbol
|
||||||
relocs []obj.Reloc
|
relocs []obj.Reloc
|
||||||
locals map[string]int
|
locals map[string]int
|
||||||
|
types map[string]string // local name -> declared struct type ('' = unknown)
|
||||||
|
structs map[string][]string // declared struct name -> field list
|
||||||
local_cnt int
|
local_cnt int
|
||||||
argc int
|
argc int
|
||||||
cur_fn string
|
cur_fn string
|
||||||
@@ -97,6 +99,12 @@ mut:
|
|||||||
|
|
||||||
fn gen(prog Program) !obj.Obj {
|
fn gen(prog Program) !obj.Obj {
|
||||||
mut g := Gen{}
|
mut g := Gen{}
|
||||||
|
for sd in prog.structs {
|
||||||
|
if sd.name in g.structs {
|
||||||
|
return error('duplicate struct declaration "${sd.name}"')
|
||||||
|
}
|
||||||
|
g.structs[sd.name] = sd.fields
|
||||||
|
}
|
||||||
for fd in prog.fns {
|
for fd in prog.fns {
|
||||||
g.gen_fn(fd)!
|
g.gen_fn(fd)!
|
||||||
}
|
}
|
||||||
@@ -109,13 +117,23 @@ fn gen(prog Program) !obj.Obj {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn (mut g Gen) gen_fn(fd FnDecl) ! {
|
fn (mut g Gen) gen_fn(fd FnDecl) ! {
|
||||||
g.cur_fn = fd.name
|
// methods compile to functions named `Type.method`; the receiver is the
|
||||||
g.symbols << obj.Symbol{ name: fd.name, entry: g.code.len }
|
// implicit first argument, so `p.dist(x)` becomes `call Point.dist p, x`
|
||||||
|
sym := if fd.recv_type.len > 0 { '${fd.recv_type}.${fd.name}' } else { fd.name }
|
||||||
|
g.cur_fn = sym
|
||||||
|
g.symbols << obj.Symbol{ name: sym, entry: g.code.len }
|
||||||
g.locals.clear()
|
g.locals.clear()
|
||||||
|
g.types.clear()
|
||||||
g.local_cnt = 0
|
g.local_cnt = 0
|
||||||
g.argc = fd.params.len
|
g.argc = fd.params.len + if fd.recv_type.len > 0 { 1 } else { 0 }
|
||||||
|
mut next := 0
|
||||||
|
if fd.recv_type.len > 0 {
|
||||||
|
g.locals[fd.recv_name] = 0
|
||||||
|
g.types[fd.recv_name] = fd.recv_type
|
||||||
|
next = 1
|
||||||
|
}
|
||||||
for i, p in fd.params {
|
for i, p in fd.params {
|
||||||
g.locals[p] = i
|
g.locals[p] = i + next
|
||||||
}
|
}
|
||||||
g.local_cnt = g.argc
|
g.local_cnt = g.argc
|
||||||
// `enter n` reserves the non-parameter locals; n is patched once the body
|
// `enter n` reserves the non-parameter locals; n is patched once the body
|
||||||
@@ -157,6 +175,7 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
|
|||||||
idx := g.local_cnt
|
idx := g.local_cnt
|
||||||
g.local_cnt++
|
g.local_cnt++
|
||||||
g.locals[st.target] = idx
|
g.locals[st.target] = idx
|
||||||
|
g.types[st.target] = g.expr_type(st.expr)
|
||||||
g.code << op_store
|
g.code << op_store
|
||||||
g.code << obj.encode_i64(i64(idx))
|
g.code << obj.encode_i64(i64(idx))
|
||||||
}
|
}
|
||||||
@@ -165,6 +184,7 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
|
|||||||
return error('unknown variable "${st.target}" at line ${st.line}')
|
return error('unknown variable "${st.target}" at line ${st.line}')
|
||||||
}
|
}
|
||||||
g.gen_expr(st.expr)!
|
g.gen_expr(st.expr)!
|
||||||
|
g.types[st.target] = g.expr_type(st.expr)
|
||||||
g.code << op_store
|
g.code << op_store
|
||||||
g.code << obj.encode_i64(i64(idx))
|
g.code << obj.encode_i64(i64(idx))
|
||||||
}
|
}
|
||||||
@@ -272,7 +292,9 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
|
|||||||
g.fixups << Fixup{ name: end_l, off: u32(g.code.len) - 8 }
|
g.fixups << Fixup{ name: end_l, off: u32(g.code.len) - 8 }
|
||||||
g.loops << LoopCtx{ break_l: end_l, continue_l: inc_l }
|
g.loops << LoopCtx{ break_l: end_l, continue_l: inc_l }
|
||||||
prev := g.locals[st.target] or { -1 }
|
prev := g.locals[st.target] or { -1 }
|
||||||
|
prev_t := g.types[st.target] or { '' }
|
||||||
g.locals[st.target] = var_idx
|
g.locals[st.target] = var_idx
|
||||||
|
g.types.delete(st.target)
|
||||||
for s in st.body {
|
for s in st.body {
|
||||||
g.gen_stmt(s)!
|
g.gen_stmt(s)!
|
||||||
}
|
}
|
||||||
@@ -281,6 +303,9 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
|
|||||||
} else {
|
} else {
|
||||||
g.locals.delete(st.target)
|
g.locals.delete(st.target)
|
||||||
}
|
}
|
||||||
|
if prev_t.len > 0 {
|
||||||
|
g.types[st.target] = prev_t
|
||||||
|
}
|
||||||
g.loops.delete_last()
|
g.loops.delete_last()
|
||||||
g.emit_label(inc_l)
|
g.emit_label(inc_l)
|
||||||
g.emit_load(var_idx)
|
g.emit_load(var_idx)
|
||||||
@@ -320,7 +345,9 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
|
|||||||
g.code << op_aget
|
g.code << op_aget
|
||||||
g.emit_store(elem_idx)
|
g.emit_store(elem_idx)
|
||||||
prev := g.locals[st.target] or { -1 }
|
prev := g.locals[st.target] or { -1 }
|
||||||
|
prev_t := g.types[st.target] or { '' }
|
||||||
g.locals[st.target] = elem_idx
|
g.locals[st.target] = elem_idx
|
||||||
|
g.types.delete(st.target)
|
||||||
for s in st.body {
|
for s in st.body {
|
||||||
g.gen_stmt(s)!
|
g.gen_stmt(s)!
|
||||||
}
|
}
|
||||||
@@ -329,6 +356,9 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
|
|||||||
} else {
|
} else {
|
||||||
g.locals.delete(st.target)
|
g.locals.delete(st.target)
|
||||||
}
|
}
|
||||||
|
if prev_t.len > 0 {
|
||||||
|
g.types[st.target] = prev_t
|
||||||
|
}
|
||||||
g.loops.delete_last()
|
g.loops.delete_last()
|
||||||
g.emit_label(inc_l)
|
g.emit_label(inc_l)
|
||||||
g.emit_load(idx_idx)
|
g.emit_load(idx_idx)
|
||||||
@@ -395,6 +425,22 @@ fn (mut g Gen) gen_expr(e Expr) ! {
|
|||||||
g.code << obj.encode_i64(i64(e.elems.len))
|
g.code << obj.encode_i64(i64(e.elems.len))
|
||||||
}
|
}
|
||||||
.struct_lit {
|
.struct_lit {
|
||||||
|
// typed literals validate their fields against the declaration
|
||||||
|
// (an undeclared type name is allowed — it may live in another
|
||||||
|
// file, where the same validation applies)
|
||||||
|
if e.name.len > 0 && e.name in g.structs {
|
||||||
|
decl_fields := g.structs[e.name]
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for f in e.fields {
|
||||||
|
if f.name !in decl_fields {
|
||||||
|
return error('unknown field "${f.name}" for struct ${e.name} (line ${e.line})')
|
||||||
|
}
|
||||||
|
if f.name in seen {
|
||||||
|
return error('duplicate field "${f.name}" in struct literal (line ${e.line})')
|
||||||
|
}
|
||||||
|
seen[f.name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
// for each field: push the name string then the value; mkstruct n
|
// for each field: push the name string then the value; mkstruct n
|
||||||
// pops the (name, value) pairs and builds the record
|
// pops the (name, value) pairs and builds the record
|
||||||
for f in e.fields {
|
for f in e.fields {
|
||||||
@@ -409,6 +455,18 @@ fn (mut g Gen) gen_expr(e Expr) ! {
|
|||||||
g.emit_field_name(e.name)
|
g.emit_field_name(e.name)
|
||||||
g.code << op_sget
|
g.code << op_sget
|
||||||
}
|
}
|
||||||
|
.method_call {
|
||||||
|
// p.dist(x) → call <Type>.dist p, x
|
||||||
|
recv_t := g.method_receiver_type(e)!
|
||||||
|
g.gen_expr(*e.left)!
|
||||||
|
for a in e.args {
|
||||||
|
g.gen_expr(a)!
|
||||||
|
}
|
||||||
|
g.code << op_call
|
||||||
|
g.code << obj.encode_i64(0) // placeholder — patched by the linker
|
||||||
|
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: '${recv_t}.${e.name}', kind: 0 }
|
||||||
|
g.code << obj.encode_i64(i64(e.args.len + 1)) // receiver + args
|
||||||
|
}
|
||||||
.index {
|
.index {
|
||||||
g.gen_expr(*e.left)!
|
g.gen_expr(*e.left)!
|
||||||
g.gen_expr(*e.right)!
|
g.gen_expr(*e.right)!
|
||||||
@@ -547,6 +605,33 @@ fn (mut g Gen) gen_binary(e Expr) ! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expr_type returns the declared struct type of an expression when it is
|
||||||
|
// statically knowable: a typed literal `Point{...}` or a copy of a typed
|
||||||
|
// variable. Everything else has no known type ('').
|
||||||
|
fn (mut g Gen) expr_type(e Expr) string {
|
||||||
|
if e.kind == .struct_lit {
|
||||||
|
return e.name
|
||||||
|
}
|
||||||
|
if e.kind == .ident {
|
||||||
|
return g.types[e.name] or { '' }
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// method_receiver_type resolves the struct type a method call is made on.
|
||||||
|
// The receiver must be a plain variable whose type the compiler knows
|
||||||
|
// (from a typed literal, an assignment, or a method receiver binding).
|
||||||
|
fn (mut g Gen) method_receiver_type(e Expr) !string {
|
||||||
|
recv := e.left
|
||||||
|
if recv.kind == .ident {
|
||||||
|
t := g.types[recv.name] or { '' }
|
||||||
|
if t.len > 0 {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return error('cannot resolve method "${e.name}": receiver type unknown (line ${e.line})')
|
||||||
|
}
|
||||||
|
|
||||||
// emit_field_name pushes a field name as a string constant. Like string
|
// emit_field_name pushes a field name as a string constant. Like string
|
||||||
// literals it goes through a kind-1 relocation so multi-file links rebase it.
|
// literals it goes through a kind-1 relocation so multi-file links rebase it.
|
||||||
fn (mut g Gen) emit_field_name(name string) {
|
fn (mut g Gen) emit_field_name(name string) {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub enum TokKind {
|
|||||||
dotdot
|
dotdot
|
||||||
dotdotdot
|
dotdotdot
|
||||||
kw_fn
|
kw_fn
|
||||||
|
kw_struct
|
||||||
kw_let
|
kw_let
|
||||||
kw_if
|
kw_if
|
||||||
kw_else
|
kw_else
|
||||||
@@ -254,6 +255,7 @@ fn (mut l Lexer) lex_ident(line int) Tok {
|
|||||||
lit := l.src[start..l.pos]
|
lit := l.src[start..l.pos]
|
||||||
kind := match lit {
|
kind := match lit {
|
||||||
'fn' { TokKind.kw_fn }
|
'fn' { TokKind.kw_fn }
|
||||||
|
'struct' { TokKind.kw_struct }
|
||||||
'let' { TokKind.kw_let }
|
'let' { TokKind.kw_let }
|
||||||
'if' { TokKind.kw_if }
|
'if' { TokKind.kw_if }
|
||||||
'else' { TokKind.kw_else }
|
'else' { TokKind.kw_else }
|
||||||
|
|||||||
+124
-27
@@ -1,8 +1,9 @@
|
|||||||
// parser.v — recursive-descent parser for the VuurRaaf language.
|
// parser.v — recursive-descent parser for the VuurRaaf language.
|
||||||
//
|
//
|
||||||
// Grammar (informal):
|
// Grammar (informal):
|
||||||
// program := fn*
|
// program := (struct | fn)*
|
||||||
// fn := 'fn' IDENT '(' [IDENT (',' IDENT)*] ')' block
|
// struct := 'struct' IDENT '{' [IDENT (',' IDENT)*] '}'
|
||||||
|
// fn := 'fn' [ '(' IDENT IDENT ')' ] IDENT '(' [IDENT (',' IDENT)*] ')' block
|
||||||
// block := '{' stmt* '}'
|
// block := '{' stmt* '}'
|
||||||
// stmt := 'let' IDENT '=' expr
|
// stmt := 'let' IDENT '=' expr
|
||||||
// | IDENT '=' expr
|
// | IDENT '=' expr
|
||||||
@@ -25,8 +26,10 @@
|
|||||||
// add := mul (('*'|'/'|'%') mul)*
|
// add := mul (('*'|'/'|'%') mul)*
|
||||||
// mul := ('not'|'-') mul | primary
|
// mul := ('not'|'-') mul | primary
|
||||||
// primary := INT | STR | 'true' | 'false' | IDENT ['(' args ')'] | '(' expr ')'
|
// primary := INT | STR | 'true' | 'false' | IDENT ['(' args ')'] | '(' expr ')'
|
||||||
// | '{' [IDENT ':' expr (',' IDENT ':' expr)*] '}' (struct literal)
|
// | '{' [IDENT ':' expr (',' IDENT ':' expr)*] '}' (anonymous struct literal)
|
||||||
// postfix := primary ('.' IDENT | '[' expr ']')* (field access, indexing)
|
// | IDENT '{' IDENT ':' expr ... '}' (typed struct literal)
|
||||||
|
// postfix := primary ('.' IDENT ['(' args ')'] | '[' expr ']')*
|
||||||
|
// ('.' IDENT '(' ... ')' is a method call; everything else field access)
|
||||||
module compiler
|
module compiler
|
||||||
|
|
||||||
pub enum ExprKind {
|
pub enum ExprKind {
|
||||||
@@ -38,6 +41,7 @@ pub enum ExprKind {
|
|||||||
struct_lit
|
struct_lit
|
||||||
index
|
index
|
||||||
field
|
field
|
||||||
|
method_call
|
||||||
unary
|
unary
|
||||||
binary
|
binary
|
||||||
call
|
call
|
||||||
@@ -107,9 +111,19 @@ pub mut:
|
|||||||
line int
|
line int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StructDecl is a `struct Name { a, b }` declaration.
|
||||||
|
pub struct StructDecl {
|
||||||
|
pub mut:
|
||||||
|
name string
|
||||||
|
fields []string
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
pub struct FnDecl {
|
pub struct FnDecl {
|
||||||
pub mut:
|
pub mut:
|
||||||
name string
|
name string
|
||||||
|
recv_name string // method receiver local name ('' for plain functions)
|
||||||
|
recv_type string // method receiver struct type ('' for plain functions)
|
||||||
params []string
|
params []string
|
||||||
body []Stmt
|
body []Stmt
|
||||||
line int
|
line int
|
||||||
@@ -118,6 +132,7 @@ pub mut:
|
|||||||
pub struct Program {
|
pub struct Program {
|
||||||
pub mut:
|
pub mut:
|
||||||
fns []FnDecl
|
fns []FnDecl
|
||||||
|
structs []StructDecl
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(toks []Tok) !Program {
|
pub fn parse(toks []Tok) !Program {
|
||||||
@@ -168,17 +183,58 @@ fn (mut p Parser) parse_cond() !Expr {
|
|||||||
fn (mut p Parser) parse_program() !Program {
|
fn (mut p Parser) parse_program() !Program {
|
||||||
mut prog := Program{}
|
mut prog := Program{}
|
||||||
for p.cur().kind != .eof {
|
for p.cur().kind != .eof {
|
||||||
|
if p.cur().kind == .kw_struct {
|
||||||
|
prog.structs << p.parse_struct_decl()!
|
||||||
|
} else {
|
||||||
prog.fns << p.parse_fn()!
|
prog.fns << p.parse_fn()!
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if prog.fns.len == 0 {
|
if prog.fns.len == 0 {
|
||||||
return error('no functions found in source')
|
return error('no functions found in source')
|
||||||
}
|
}
|
||||||
return prog
|
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_fn parses `fn name(params) { }` or a method `fn (p Type) name(params) { }`.
|
||||||
fn (mut p Parser) parse_fn() !FnDecl {
|
fn (mut p Parser) parse_fn() !FnDecl {
|
||||||
fn_tok := p.expect(.kw_fn, "'fn'")!
|
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')!
|
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, "'('")!
|
p.expect(.lparen, "'('")!
|
||||||
mut params := []string{}
|
mut params := []string{}
|
||||||
if p.cur().kind != .rparen {
|
if p.cur().kind != .rparen {
|
||||||
@@ -192,8 +248,7 @@ fn (mut p Parser) parse_fn() !FnDecl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.expect(.rparen, "')'")!
|
p.expect(.rparen, "')'")!
|
||||||
body := p.parse_block()!
|
return params
|
||||||
return FnDecl{ name: name.lit, params: params, body: body, line: fn_tok.line }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut p Parser) parse_block() ![]Stmt {
|
fn (mut p Parser) parse_block() ![]Stmt {
|
||||||
@@ -382,6 +437,12 @@ fn field_node(base Expr, name string, line int) Expr {
|
|||||||
return Expr{ kind: .field, left: &b, name: name, line: line }
|
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 {
|
fn (mut p Parser) parse_expr() !Expr {
|
||||||
return p.parse_or()!
|
return p.parse_or()!
|
||||||
}
|
}
|
||||||
@@ -466,7 +527,9 @@ fn (mut p Parser) parse_postfix() !Expr {
|
|||||||
|
|
||||||
// parse_postfix_tail continues a postfix chain from an already-parsed base.
|
// 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
|
// It takes the base by value (Expr only holds pointers to heap-allocated
|
||||||
// child nodes) and returns the extended chain.
|
// 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 {
|
fn (mut p Parser) parse_postfix_tail(e Expr) !Expr {
|
||||||
mut cur := e
|
mut cur := e
|
||||||
for {
|
for {
|
||||||
@@ -480,7 +543,13 @@ fn (mut p Parser) parse_postfix_tail(e Expr) !Expr {
|
|||||||
if p.cur().kind == .dot {
|
if p.cur().kind == .dot {
|
||||||
p.advance()
|
p.advance()
|
||||||
name := p.expect(.ident, 'field name')!
|
name := p.expect(.ident, 'field name')!
|
||||||
cur = field_node(cur, name.lit, cur.line)
|
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
|
continue
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -530,27 +599,19 @@ fn (mut p Parser) parse_primary() !Expr {
|
|||||||
return Expr{ kind: .array_lit, elems: elems, line: t.line }
|
return Expr{ kind: .array_lit, elems: elems, line: t.line }
|
||||||
}
|
}
|
||||||
.lbrace {
|
.lbrace {
|
||||||
// struct literal: { name: expr, ... }
|
// anonymous struct literal: { name: expr, ... }
|
||||||
p.advance()
|
fields := p.parse_struct_fields()!
|
||||||
mut fields := []StructField{}
|
return Expr{ kind: .struct_lit, name: '', fields: fields, line: t.line }
|
||||||
if p.cur().kind != .rbrace {
|
|
||||||
for {
|
|
||||||
name := p.expect(.ident, 'field name')!
|
|
||||||
p.expect(.colon, "':'")!
|
|
||||||
val := p.parse_expr()!
|
|
||||||
fields << StructField{ name: name.lit, val: val }
|
|
||||||
if p.cur().kind == .comma {
|
|
||||||
p.advance()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.expect(.rbrace, "'}'")!
|
|
||||||
return Expr{ kind: .struct_lit, fields: fields, line: t.line }
|
|
||||||
}
|
}
|
||||||
.ident {
|
.ident {
|
||||||
p.advance()
|
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)!
|
return p.parse_call_or_ident(t)!
|
||||||
}
|
}
|
||||||
.kw_print, .kw_println {
|
.kw_print, .kw_println {
|
||||||
@@ -571,6 +632,12 @@ fn (mut p Parser) parse_call_or_ident(t Tok) !Expr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn (mut p Parser) parse_call(name Tok) !Expr {
|
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, "'('")!
|
p.expect(.lparen, "'('")!
|
||||||
mut args := []Expr{}
|
mut args := []Expr{}
|
||||||
if p.cur().kind != .rparen {
|
if p.cur().kind != .rparen {
|
||||||
@@ -584,5 +651,35 @@ fn (mut p Parser) parse_call(name Tok) !Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.expect(.rparen, "')'")!
|
p.expect(.rparen, "')'")!
|
||||||
return Expr{ kind: .call, name: name.lit, args: args, line: name.line }
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse_struct_fields parses `{ name: 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 {
|
||||||
|
name := p.expect(.ident, 'field name')!
|
||||||
|
p.expect(.colon, "':'")!
|
||||||
|
val := p.parse_expr()!
|
||||||
|
fields << StructField{ name: name.lit, 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
|
||||||
|
}
|
||||||
|
return p.toks[p.pos + 1].kind == .ident && p.toks[p.pos + 2].kind == .colon
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user