mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 15:37:18 +00:00
Production pass: floats, GC, type checking, and tooling
Adds floats, bitwise ops, UTF-8 strings with methods, try/catch, closures, generics validation, a compile-time type checker, a mark-and-sweep GC, source-level debug info, constant folding, and the repl/fmt/package-manager commands. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
+394
-23
@@ -64,7 +64,7 @@ fn (mut p Parser) advance() Tok {
|
||||
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 error('expected ${what}, got "${t.lit}" at line ${t.line}, col ${t.col}')
|
||||
}
|
||||
return p.advance()
|
||||
}
|
||||
@@ -92,8 +92,9 @@ fn (mut p Parser) parse_program() !Program {
|
||||
.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_interface { prog.interfaces << p.parse_interface_decl()! }
|
||||
.kw_fn { prog.fns << p.parse_fn()! }
|
||||
else { return error('unexpected token "${p.cur().lit}" at line ${p.cur().line}') }
|
||||
else { return error('unexpected token "${p.cur().lit}" at line ${p.cur().line}, col ${p.cur().col}') }
|
||||
}
|
||||
}
|
||||
if prog.fns.len == 0 {
|
||||
@@ -163,6 +164,42 @@ fn (mut p Parser) parse_const_decl() !ConstDecl {
|
||||
return ConstDecl{ name: name.lit, value: value, line: t.line }
|
||||
}
|
||||
|
||||
// parse_interface_decl parses `interface Name { method1() method2() ... }`.
|
||||
// Methods may optionally be followed by `()` (any parameter list is ignored)
|
||||
// and are separated by commas or newlines.
|
||||
fn (mut p Parser) parse_interface_decl() !InterfaceDecl {
|
||||
t := p.expect(.kw_interface, "'interface'")!
|
||||
name := p.expect(.ident, 'interface name')!
|
||||
p.expect(.lbrace, "'{'")!
|
||||
mut methods := []InterfaceMethod{}
|
||||
if p.cur().kind != .rbrace {
|
||||
for {
|
||||
mname := p.expect(.ident, 'method name')!
|
||||
if p.cur().kind == .lparen {
|
||||
p.advance()
|
||||
for p.cur().kind != .rparen {
|
||||
if p.cur().kind == .eof {
|
||||
return error('unexpected end of file in interface method (line ${mname.line})')
|
||||
}
|
||||
p.advance()
|
||||
}
|
||||
p.expect(.rparen, "')'")!
|
||||
}
|
||||
methods << InterfaceMethod{ name: mname.lit, line: mname.line }
|
||||
if p.cur().kind == .comma {
|
||||
p.advance()
|
||||
continue
|
||||
}
|
||||
if p.cur().kind != .rbrace {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
p.expect(.rbrace, "'}'")!
|
||||
return InterfaceDecl{ name: name.lit, methods: methods, 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'")!
|
||||
@@ -176,18 +213,76 @@ fn (mut p Parser) parse_fn() !FnDecl {
|
||||
p.expect(.rparen, "')'")!
|
||||
}
|
||||
name := p.expect(.ident, 'function name')!
|
||||
params := p.parse_params()!
|
||||
// generic type parameters: fn first[T, U](arr) { ... } — captured so the
|
||||
// type checker can validate call sites; the VM is dynamically typed, so
|
||||
// they erase to a single function at runtime
|
||||
mut type_params := []string{}
|
||||
if p.cur().kind == .lbracket {
|
||||
p.advance()
|
||||
for p.cur().kind != .rbracket {
|
||||
tp := p.expect(.ident, 'type parameter')!.lit
|
||||
if tp in type_params {
|
||||
return error('duplicate type parameter "${tp}" in function ${name.lit} (line ${name.line}, col ${name.col})')
|
||||
}
|
||||
type_params << tp
|
||||
if p.cur().kind == .comma {
|
||||
p.advance()
|
||||
}
|
||||
}
|
||||
p.expect(.rbracket, "']'")!
|
||||
}
|
||||
params, defaults, has_defs, variadic := 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 }
|
||||
return FnDecl{
|
||||
name: name.lit
|
||||
type_params: type_params
|
||||
recv_name: recv_name
|
||||
recv_type: recv_type
|
||||
params: params
|
||||
defaults: defaults
|
||||
has_defs: has_defs
|
||||
variadic: variadic
|
||||
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 {
|
||||
// parse_params parses `(a, b, c = expr, rest...)` — the parameter list of a
|
||||
// function. Returns the names, the default-value expressions (parallel array,
|
||||
// empty Expr{} when no default), whether each has a default, and whether the
|
||||
// last parameter is variadic.
|
||||
fn (mut p Parser) parse_params() !([]string, []Expr, []bool, bool) {
|
||||
p.expect(.lparen, "'('")!
|
||||
mut params := []string{}
|
||||
mut defaults := []Expr{}
|
||||
mut has_defs := []bool{}
|
||||
mut variadic := false
|
||||
if p.cur().kind != .rparen {
|
||||
for {
|
||||
params << p.expect(.ident, 'parameter name')!.lit
|
||||
name_tok := p.expect(.ident, 'parameter name')!
|
||||
name := name_tok.lit
|
||||
if p.cur().kind == .dotdotdot {
|
||||
// variadic parameter: `rest...` (must be last)
|
||||
p.advance()
|
||||
params << name
|
||||
defaults << Expr{}
|
||||
has_defs << false
|
||||
variadic = true
|
||||
if p.cur().kind == .comma {
|
||||
return error('a variadic parameter must be last (line ${name_tok.line})')
|
||||
}
|
||||
break
|
||||
}
|
||||
mut def := Expr{}
|
||||
mut has_def := false
|
||||
if p.cur().kind == .assign {
|
||||
p.advance()
|
||||
def = p.parse_expr()!
|
||||
has_def = true
|
||||
}
|
||||
params << name
|
||||
defaults << def
|
||||
has_defs << has_def
|
||||
if p.cur().kind == .comma {
|
||||
p.advance()
|
||||
continue
|
||||
@@ -196,7 +291,7 @@ fn (mut p Parser) parse_params() ![]string {
|
||||
}
|
||||
}
|
||||
p.expect(.rparen, "')'")!
|
||||
return params
|
||||
return params, defaults, has_defs, variadic
|
||||
}
|
||||
|
||||
fn (mut p Parser) parse_block() ![]Stmt {
|
||||
@@ -217,6 +312,30 @@ fn (mut p Parser) parse_stmt() !Stmt {
|
||||
match t.kind {
|
||||
.kw_let {
|
||||
p.advance()
|
||||
// destructuring let: `let { a, b } = expr` or `let [a, b] = expr`
|
||||
if p.cur().kind == .lbrace || p.cur().kind == .lbracket {
|
||||
is_field := p.cur().kind == .lbrace
|
||||
p.advance()
|
||||
mut names := []string{}
|
||||
mut closing := TokKind.rbracket
|
||||
if is_field {
|
||||
closing = .rbrace
|
||||
}
|
||||
if p.cur().kind != closing {
|
||||
for {
|
||||
names << p.expect(.ident, 'binding name')!.lit
|
||||
if p.cur().kind == .comma {
|
||||
p.advance()
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
p.expect(closing, "']' or '}'")!
|
||||
p.expect(.assign, "'='")!
|
||||
e := p.parse_expr()!
|
||||
return Stmt{ kind: .destruct_stmt, expr: e, destruct_targets: names, destruct_field: is_field, line: t.line }
|
||||
}
|
||||
name := p.expect(.ident, 'variable name')!
|
||||
p.expect(.assign, "'='")!
|
||||
e := p.parse_expr()!
|
||||
@@ -257,6 +376,14 @@ fn (mut p Parser) parse_stmt() !Stmt {
|
||||
.kw_for {
|
||||
p.advance()
|
||||
name := p.expect(.ident, 'loop variable')!
|
||||
mut idx_name := ''
|
||||
mut val_name := name.lit
|
||||
if p.cur().kind == .comma {
|
||||
// for i, v in arr { ... }
|
||||
p.advance()
|
||||
idx_name = name.lit
|
||||
val_name = p.expect(.ident, 'loop value variable')!.lit
|
||||
}
|
||||
p.expect(.kw_in, "'in'")!
|
||||
first := p.parse_expr()!
|
||||
if p.cur().kind == .dotdot || p.cur().kind == .dotdotdot {
|
||||
@@ -264,10 +391,10 @@ fn (mut p Parser) parse_stmt() !Stmt {
|
||||
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 }
|
||||
return Stmt{ kind: .for_range_stmt, target: val_name, 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 }
|
||||
return Stmt{ kind: .for_in_stmt, target: val_name, idx_target: idx_name, expr: first, body: body, line: t.line }
|
||||
}
|
||||
.kw_return {
|
||||
p.advance()
|
||||
@@ -297,13 +424,29 @@ fn (mut p Parser) parse_stmt() !Stmt {
|
||||
}
|
||||
return Stmt{ kind: .assert_stmt, expr: e, line: t.line }
|
||||
}
|
||||
.kw_try {
|
||||
p.advance()
|
||||
return p.parse_try_stmt(t)!
|
||||
}
|
||||
.kw_throw {
|
||||
p.advance()
|
||||
e := p.parse_expr()!
|
||||
return Stmt{ kind: .throw_stmt, expr: e, line: t.line }
|
||||
}
|
||||
.ident {
|
||||
p.advance()
|
||||
// generic type args in statement position: first[int](...)
|
||||
mut type_args := []string{}
|
||||
if p.cur().kind == .lbracket && p.looks_like_generic_args() {
|
||||
type_args = p.parse_type_args()!
|
||||
}
|
||||
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_call(t, type_args)!
|
||||
e = p.parse_postfix_tail(e)!
|
||||
} else if type_args.len > 0 {
|
||||
return error('generic type arguments on a non-call "${t.lit}" (line ${t.line}, col ${t.col})')
|
||||
} else {
|
||||
e = p.parse_postfix_tail(Expr{ kind: .ident, name: t.lit, line: t.line })!
|
||||
}
|
||||
@@ -360,11 +503,11 @@ fn (mut p Parser) parse_stmt() !Stmt {
|
||||
}
|
||||
.kw_print, .kw_println {
|
||||
p.advance()
|
||||
e := p.parse_call(t)!
|
||||
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}')
|
||||
return error('unexpected token "${t.lit}" at line ${t.line}, col ${t.col}')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -388,6 +531,21 @@ fn (mut p Parser) parse_if(t Tok) !Stmt {
|
||||
return Stmt{ kind: .if_stmt, cond: cond, body: body, els: els, line: t.line }
|
||||
}
|
||||
|
||||
// parse_try_stmt parses `try { body } catch ident { body }`.
|
||||
fn (mut p Parser) parse_try_stmt(t Tok) !Stmt {
|
||||
body := p.parse_block()!
|
||||
p.expect(.kw_catch, "'catch'")!
|
||||
catch_var := p.expect(.ident, 'error variable')!.lit
|
||||
catch_body := p.parse_block()!
|
||||
return Stmt{
|
||||
kind: .try_stmt
|
||||
body: body
|
||||
target: catch_var
|
||||
els: catch_body
|
||||
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).
|
||||
@@ -409,6 +567,15 @@ fn index_node(base Expr, idx Expr, line int) Expr {
|
||||
return Expr{ kind: .index, left: &b, right: &i, line: line }
|
||||
}
|
||||
|
||||
// slice_node builds `base[start..end]`.
|
||||
// end uses -1 as sentinel for "open-ended" (slice to end).
|
||||
fn slice_node(base Expr, start Expr, end Expr, _inclusive bool, line int) Expr {
|
||||
mut b := base
|
||||
mut s := start
|
||||
mut e := end
|
||||
return Expr{ kind: .slice, left: &b, right: &s, extra: &e, line: line }
|
||||
}
|
||||
|
||||
// field_node builds `base.name`.
|
||||
fn field_node(base Expr, name string, line int) Expr {
|
||||
mut b := base
|
||||
@@ -436,8 +603,41 @@ fn (mut p Parser) parse_or() !Expr {
|
||||
}
|
||||
|
||||
fn (mut p Parser) parse_and() !Expr {
|
||||
mut e := p.parse_eq()!
|
||||
mut e := p.parse_bitor()!
|
||||
for p.cur().kind == .kw_and {
|
||||
op := p.advance()
|
||||
rhs := p.parse_bitor()!
|
||||
e = bin_node(op.kind, e, rhs, op.line)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// parse_bitor handles `|` (bitwise OR).
|
||||
fn (mut p Parser) parse_bitor() !Expr {
|
||||
mut e := p.parse_bitxor()!
|
||||
for p.cur().kind == .pipe {
|
||||
op := p.advance()
|
||||
rhs := p.parse_bitxor()!
|
||||
e = bin_node(op.kind, e, rhs, op.line)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// parse_bitxor handles `^` (bitwise XOR).
|
||||
fn (mut p Parser) parse_bitxor() !Expr {
|
||||
mut e := p.parse_bitand()!
|
||||
for p.cur().kind == .caret {
|
||||
op := p.advance()
|
||||
rhs := p.parse_bitand()!
|
||||
e = bin_node(op.kind, e, rhs, op.line)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// parse_bitand handles `&` (bitwise AND).
|
||||
fn (mut p Parser) parse_bitand() !Expr {
|
||||
mut e := p.parse_eq()!
|
||||
for p.cur().kind == .amp {
|
||||
op := p.advance()
|
||||
rhs := p.parse_eq()!
|
||||
e = bin_node(op.kind, e, rhs, op.line)
|
||||
@@ -456,8 +656,19 @@ fn (mut p Parser) parse_eq() !Expr {
|
||||
}
|
||||
|
||||
fn (mut p Parser) parse_rel() !Expr {
|
||||
mut e := p.parse_add()!
|
||||
mut e := p.parse_shift()!
|
||||
for p.cur().kind == .lt || p.cur().kind == .le || p.cur().kind == .gt || p.cur().kind == .ge {
|
||||
op := p.advance()
|
||||
rhs := p.parse_shift()!
|
||||
e = bin_node(op.kind, e, rhs, op.line)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// parse_shift handles `<<` and `>>` (bitwise shift).
|
||||
fn (mut p Parser) parse_shift() !Expr {
|
||||
mut e := p.parse_add()!
|
||||
for p.cur().kind == .lt_lt || p.cur().kind == .gt_gt {
|
||||
op := p.advance()
|
||||
rhs := p.parse_add()!
|
||||
e = bin_node(op.kind, e, rhs, op.line)
|
||||
@@ -487,7 +698,7 @@ fn (mut p Parser) parse_mul() !Expr {
|
||||
|
||||
fn (mut p Parser) parse_unary() !Expr {
|
||||
t := p.cur()
|
||||
if t.kind == .kw_not || t.kind == .minus {
|
||||
if t.kind == .kw_not || t.kind == .minus || t.kind == .tilde {
|
||||
p.advance()
|
||||
e := p.parse_unary()!
|
||||
return unary_node(t.kind, e, t.line)
|
||||
@@ -514,8 +725,22 @@ fn (mut p Parser) parse_postfix_tail(e Expr) !Expr {
|
||||
if p.cur().kind == .lbracket {
|
||||
p.advance()
|
||||
idx := p.parse_expr()!
|
||||
p.expect(.rbracket, "']'")!
|
||||
cur = index_node(cur, idx, cur.line)
|
||||
if p.cur().kind == .dotdot || p.cur().kind == .dotdotdot {
|
||||
// arr[start..end] or arr[start..] slicing
|
||||
p.advance()
|
||||
if p.cur().kind == .rbracket {
|
||||
// arr[start..] — slice to end
|
||||
p.advance()
|
||||
cur = slice_node(cur, idx, Expr{ kind: .int_lit, int_v: -1, line: cur.line }, false, cur.line)
|
||||
} else {
|
||||
end := p.parse_expr()!
|
||||
p.expect(.rbracket, "']'")!
|
||||
cur = slice_node(cur, idx, end, false, cur.line)
|
||||
}
|
||||
} else {
|
||||
p.expect(.rbracket, "']'")!
|
||||
cur = index_node(cur, idx, cur.line)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if p.cur().kind == .dot {
|
||||
@@ -542,10 +767,18 @@ fn (mut p Parser) parse_primary() !Expr {
|
||||
p.advance()
|
||||
return Expr{ kind: .int_lit, int_v: t.lit.i64(), line: t.line }
|
||||
}
|
||||
.float_lit {
|
||||
p.advance()
|
||||
return Expr{ kind: .float_lit, float_v: t.lit.f64(), line: t.line }
|
||||
}
|
||||
.str_lit {
|
||||
p.advance()
|
||||
return Expr{ kind: .str_lit, str_v: t.lit, line: t.line }
|
||||
}
|
||||
.str_interp {
|
||||
p.advance()
|
||||
return p.parse_str_interp(t)!
|
||||
}
|
||||
.kw_true {
|
||||
p.advance()
|
||||
return Expr{ kind: .bool_lit, int_v: 1, line: t.line }
|
||||
@@ -594,24 +827,52 @@ fn (mut p Parser) parse_primary() !Expr {
|
||||
}
|
||||
.kw_print, .kw_println {
|
||||
p.advance()
|
||||
return p.parse_call(t)!
|
||||
return p.parse_call(t, [])!
|
||||
}
|
||||
.kw_fn {
|
||||
// anonymous function expression: fn(params) { body }
|
||||
p.advance()
|
||||
return p.parse_anon_fn(t)!
|
||||
}
|
||||
else {
|
||||
return error('unexpected token "${t.lit}" at line ${t.line}')
|
||||
return error('unexpected token "${t.lit}" at line ${t.line}, col ${t.col}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut p Parser) parse_call_or_ident(t Tok) !Expr {
|
||||
// generic type args: first[int](...) — captured for the type checker;
|
||||
// only treated as type args when [ is followed by idents then ] then (
|
||||
mut type_args := []string{}
|
||||
if p.cur().kind == .lbracket && p.looks_like_generic_args() {
|
||||
type_args = p.parse_type_args()!
|
||||
}
|
||||
if p.cur().kind == .lparen {
|
||||
return p.parse_call(t)!
|
||||
return p.parse_call(t, type_args)!
|
||||
}
|
||||
if type_args.len > 0 {
|
||||
return error('generic type arguments on a non-call "${t.lit}" (line ${t.line}, col ${t.col})')
|
||||
}
|
||||
return Expr{ kind: .ident, name: t.lit, line: t.line }
|
||||
}
|
||||
|
||||
fn (mut p Parser) parse_call(name Tok) !Expr {
|
||||
// parse_type_args parses `[int, string]` into a list of type names.
|
||||
fn (mut p Parser) parse_type_args() ![]string {
|
||||
p.advance() // consume '['
|
||||
mut args := []string{}
|
||||
for p.cur().kind != .rbracket {
|
||||
args << p.expect(.ident, 'type argument')!.lit
|
||||
if p.cur().kind == .comma {
|
||||
p.advance()
|
||||
}
|
||||
}
|
||||
p.expect(.rbracket, "']'")!
|
||||
return args
|
||||
}
|
||||
|
||||
fn (mut p Parser) parse_call(name Tok, type_args []string) !Expr {
|
||||
args := p.parse_args()!
|
||||
return Expr{ kind: .call, name: name.lit, args: args, line: name.line }
|
||||
return Expr{ kind: .call, name: name.lit, type_args: type_args, args: args, line: name.line }
|
||||
}
|
||||
|
||||
// parse_args parses `(e1, e2, ...)` and returns the argument expressions.
|
||||
@@ -668,3 +929,113 @@ fn (mut p Parser) looks_like_struct_lit() bool {
|
||||
// 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
|
||||
}
|
||||
|
||||
// looks_like_generic_args checks if `[` starts generic type args like `[T]` or `[T, U]`
|
||||
// rather than array indexing. It peeks ahead to see `ident ... ] (`.
|
||||
fn (mut p Parser) looks_like_generic_args() bool {
|
||||
// current token must be lbracket (the caller already checked this)
|
||||
if p.pos + 2 >= p.toks.len {
|
||||
return false
|
||||
}
|
||||
// must start with an ident after the [
|
||||
if p.toks[p.pos + 1].kind != .ident {
|
||||
return false
|
||||
}
|
||||
// scan forward: ident, comma, ident, ..., rbracket, then lparen
|
||||
mut i := p.pos + 2
|
||||
for i < p.toks.len && p.toks[i].kind != .rbracket {
|
||||
if p.toks[i].kind != .ident && p.toks[i].kind != .comma {
|
||||
return false
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i >= p.toks.len {
|
||||
return false
|
||||
}
|
||||
// p.toks[i] should be rbracket
|
||||
if i + 1 >= p.toks.len {
|
||||
return false
|
||||
}
|
||||
return p.toks[i + 1].kind == .lparen
|
||||
}
|
||||
|
||||
// parse_str_interp handles string interpolation: "hello ${name} ${age}".
|
||||
// It splits the raw string into alternating text/expression parts and builds
|
||||
// a chain of + concatenations so the compiler needs no special handling.
|
||||
fn (mut p Parser) parse_str_interp(t Tok) !Expr {
|
||||
parts := split_str_interp(t.lit)
|
||||
// parts alternates: text, expr, text, expr, ..., text
|
||||
if parts.len == 1 {
|
||||
return Expr{ kind: .str_lit, str_v: parts[0], line: t.line }
|
||||
}
|
||||
// build the first string part
|
||||
mut result := Expr{ kind: .str_lit, str_v: parts[0], line: t.line }
|
||||
mut i := 1
|
||||
for i < parts.len {
|
||||
// parts[i] is an expression — tokenise and parse it
|
||||
expr_src := parts[i]
|
||||
expr_toks := tokenize(expr_src)!
|
||||
mut ep := Parser{ toks: expr_toks }
|
||||
expr := ep.parse_expr()!
|
||||
result = bin_node(.plus, result, expr, t.line)
|
||||
i++
|
||||
// parts[i] is the next text fragment
|
||||
if i < parts.len {
|
||||
if parts[i].len > 0 {
|
||||
str_e := Expr{ kind: .str_lit, str_v: parts[i], line: t.line }
|
||||
result = bin_node(.plus, result, str_e, t.line)
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// parse_anon_fn parses an anonymous function expression: `fn(params) { body }`.
|
||||
fn (mut p Parser) parse_anon_fn(t Tok) !Expr {
|
||||
params, defaults, has_defs, variadic := p.parse_params()!
|
||||
body := p.parse_block()!
|
||||
return Expr{
|
||||
kind: .anon_fn
|
||||
fparams: params
|
||||
fdefaults: defaults
|
||||
fhas_defs: has_defs
|
||||
fvariadic: variadic
|
||||
fn_body: body
|
||||
line: t.line
|
||||
}
|
||||
}
|
||||
|
||||
// split_str_interp splits an interpolated string at ${...} boundaries.
|
||||
// Returns alternating [text, expr, text, expr, ..., text] fragments.
|
||||
fn split_str_interp(s string) []string {
|
||||
mut parts := []string{}
|
||||
mut i := 0
|
||||
mut current := ''
|
||||
for i < s.len {
|
||||
if s[i] == `$` && i + 1 < s.len && s[i + 1] == `{` {
|
||||
parts << current
|
||||
current = ''
|
||||
i += 2
|
||||
mut depth := 1
|
||||
mut expr := ''
|
||||
for i < s.len && depth > 0 {
|
||||
if s[i] == `{` {
|
||||
depth++
|
||||
} else if s[i] == `}` {
|
||||
depth--
|
||||
}
|
||||
if depth > 0 {
|
||||
expr += s[i].ascii_str()
|
||||
}
|
||||
i++
|
||||
}
|
||||
parts << expr
|
||||
} else {
|
||||
current += s[i].ascii_str()
|
||||
i++
|
||||
}
|
||||
}
|
||||
parts << current
|
||||
return parts
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user