mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 15:17:19 +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:
@@ -37,9 +37,21 @@ vr clean remove .vobj/.vbin artifacts
|
|||||||
vr up rebuild bin/vr
|
vr up rebuild bin/vr
|
||||||
vr symlink link bin/vr into your PATH
|
vr symlink link bin/vr into your PATH
|
||||||
vr config [set <key> <value>] toolchain config (outdir, verbose)
|
vr config [set <key> <value>] toolchain config (outdir, verbose)
|
||||||
|
vr repl interactive session
|
||||||
|
vr fmt [-w] <file.vr> format source (keeps comments)
|
||||||
|
vr init [name] scaffold a project (vr.mod + main.vr)
|
||||||
|
vr get <owner/repo | git-url | ./path> fetch a package into vendor/
|
||||||
|
vr install install dependencies from vr.mod
|
||||||
|
vr list show the project manifest
|
||||||
vr info | loader | alloc | version | help
|
vr info | loader | alloc | version | help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/vr repl # try expressions and functions interactively
|
||||||
|
./bin/vr fmt -w f.vr # normalize a file's indentation/spacing in place
|
||||||
|
./bin/vr init myproj # start a project; vr get owner/repo fetches packages
|
||||||
|
```
|
||||||
|
|
||||||
Quick start:
|
Quick start:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -61,9 +73,10 @@ Quick start:
|
|||||||
|
|
||||||
## The VuurRaaf language
|
## The VuurRaaf language
|
||||||
|
|
||||||
A small, V-flavored language. Values are 64-bit integers, strings, arrays, or
|
A small, V-flavored language. Values are 64-bit integers, 64-bit floats,
|
||||||
structs (strings concatenate with `+` and compare with `==`/`!=`; arrays and
|
strings, arrays, structs, enums, and closures (strings concatenate with `+`
|
||||||
structs are mutable references that compare by identity).
|
and compare with `==`/`!=`; arrays and structs are mutable references that
|
||||||
|
compare by identity).
|
||||||
|
|
||||||
```
|
```
|
||||||
fn sum(items) {
|
fn sum(items) {
|
||||||
@@ -138,10 +151,29 @@ fn main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- functions: `fn name(a, b) { ... }` with `return expr`
|
- functions: `fn name(a, b) { ... }` with `return expr`; default parameter
|
||||||
|
values `fn f(a, b = 10)`, variadic params `fn f(nums...)`, destructuring
|
||||||
|
`let { a, b } = rec` and `let [x, y] = arr`, and anonymous closures
|
||||||
|
`let f = fn(x) { return x * 2 }` stored in variables and arrays
|
||||||
|
- generics: `fn first[T](arr) { return arr[0] }` with checked call sites
|
||||||
|
`first[int](arr)` — the VM is dynamically typed, so type parameters erase
|
||||||
|
to a single function but arity and duplicates are validated
|
||||||
- variables: `let name = expr`, reassignment `name = expr`
|
- variables: `let name = expr`, reassignment `name = expr`
|
||||||
|
- floats: `3.14`, `0.5`, `1e3` — float literals and `float(x)`; arithmetic
|
||||||
|
promotes to float; `floor` / `ceil` / `round` / `sqrt` / `pow` / `abs` /
|
||||||
|
`min` / `max` / `rand` / `rand_int`
|
||||||
|
- strings are UTF-8: `len(s)` counts characters, `s[i]` and `s[a..b]` index
|
||||||
|
and slice by character (runes), and methods like `s.to_upper()`,
|
||||||
|
`s.contains(x)`, `s.split(d)`, `s.index_of(x)`, `s.to_int()`, `s.len()`
|
||||||
|
work on any string-valued expression
|
||||||
- arrays: `[e1, e2, ...]`, indexing `a[i]` (read and write), `len(a)`,
|
- arrays: `[e1, e2, ...]`, indexing `a[i]` (read and write), `len(a)`,
|
||||||
`push(a, v)`; array literals may nest
|
`push/insert/remove/pop/sort/reverse/clone/index_of/join`; array literals
|
||||||
|
may nest
|
||||||
|
- error handling: `try { ... } catch e { ... }` and `throw "message"` — the
|
||||||
|
runtime unwinds to the nearest catch
|
||||||
|
- bitwise operators: `& | ^ ~ << >>`
|
||||||
|
- host builtins: `read_file` / `write_file`, `args()`, `getenv` / `setenv`,
|
||||||
|
`exit`, `sleep`, `time()`, `type(x)`, `str(x)`, `int(x)`, `split` / `join`
|
||||||
- for loops: `for x in arr { }` and ranges `for i in 0..10 { }` /
|
- for loops: `for x in arr { }` and ranges `for i in 0..10 { }` /
|
||||||
`for i in 0...10 { }`; loop variables are scoped to the loop body
|
`for i in 0...10 { }`; loop variables are scoped to the loop body
|
||||||
- `break` / `continue` inside `while` and `for` loops (in `for` loops
|
- `break` / `continue` inside `while` and `for` loops (in `for` loops
|
||||||
@@ -153,9 +185,14 @@ fn main() {
|
|||||||
field access `a.b` and assignment `a.b = v` (chained: `a[i].b`, `a.b[i]`);
|
field access `a.b` and assignment `a.b = v` (chained: `a[i].b`, `a.b[i]`);
|
||||||
structs are mutable references (identity `==`/`!=`), and setting a missing
|
structs are mutable references (identity `==`/`!=`), and setting a missing
|
||||||
field adds it, so records can be built incrementally
|
field adds it, so records can be built incrementally
|
||||||
- operators: `+ - * / %`, `== != < <= > >=`, `and or not`, unary `-`
|
- enums: `enum Color { red green blue }` with `Color.red`, `e.to_string()`,
|
||||||
|
`e.count()`, and iteration in `for`
|
||||||
|
- constants: `const NAME = 42` (compile-time integer/bool values)
|
||||||
|
- operators: `+ - * / %`, `== != < <= > >=`, `and or not`, `& | ^ ~ << >>`,
|
||||||
|
unary `-`; constant expressions fold at compile time
|
||||||
- statements: `let`, assignment, `if/else`, `match`, `while`, `for`,
|
- statements: `let`, assignment, `if/else`, `match`, `while`, `for`,
|
||||||
`break`, `continue`, `return`, `assert`, calls, `print(...)` / `println(...)`
|
`break`, `continue`, `return`, `assert`, `try/catch`/`throw`, calls,
|
||||||
|
`print(...)` / `println(...)`
|
||||||
- comments: `//`
|
- comments: `//`
|
||||||
|
|
||||||
## Assembly
|
## Assembly
|
||||||
@@ -198,25 +235,32 @@ field name as a string first, exactly like the compiler does).
|
|||||||
|
|
||||||
| module | role |
|
| module | role |
|
||||||
|--------------|-------------------------------------------------------------|
|
|--------------|-------------------------------------------------------------|
|
||||||
| `compiler/` | lexer, recursive-descent parser, bytecode codegen (VROBJ) |
|
| `compiler/` | lexer, parser, type checker, bytecode codegen (VROBJ) |
|
||||||
| `assembler/` | `.vasm` -> VROBJ |
|
| `assembler/` | `.vasm` -> VROBJ |
|
||||||
| `linker/` | resolves relocations, rebases strings, emits VRBIN |
|
| `linker/` | resolves relocations, rebases strings, emits VRBIN |
|
||||||
| `vm/` | stack VM: tagged values, call frames, string/array heaps |
|
| `vm/` | stack VM: tagged values, call frames, string/array heaps |
|
||||||
| `obj/` | VROBJ/VRBIN binary formats |
|
| `obj/` | VROBJ/VRBIN binary formats |
|
||||||
| `bin/` | small standalone tools: `tl_alloc.v`, `tl_loader.v` |
|
| `bin/` | small standalone tools: `tl_alloc.v`, `tl_loader.v` |
|
||||||
|
|
||||||
The VM is a stack machine with 64-bit tagged values using two tag bits:
|
The VM is a stack machine with 64-bit tagged values using three tag bits:
|
||||||
numbers are stored shifted left by two, string handles end in `01`, array
|
ints, string/array/struct/float/closure handles — so no integer ever
|
||||||
handles in `11` — so no integer ever collides with a heap handle. Arrays live
|
collides with a heap handle. A mark-and-sweep garbage collector runs between
|
||||||
in a growable heap (`mkarray`/`aget`/`aset`/`alen`/`apush`). Calls push a
|
opcodes when the heap grows past a threshold, tracing the stack (which holds
|
||||||
frame (return address, base pointer, argc), copy arguments into local slots,
|
every frame's locals) and compacting the pools; string constants baked into
|
||||||
and reserve extra locals with `enter n`. `vr debug` prints every instruction
|
bytecode are never collected. Bytecode carries a line table, so runtime
|
||||||
with the stack contents (arrays rendered as `[1, 2, ...]`).
|
errors report the source line. A conservative compile-time type checker
|
||||||
|
(`compiler/check.v`) rejects provably wrong programs (unknown variables,
|
||||||
|
field access on numbers, arithmetic on strings, wrong arity) while leaving
|
||||||
|
dynamic programs alone. `vr debug` prints every instruction with the stack
|
||||||
|
contents (arrays rendered as `[1, 2, ...]`).
|
||||||
|
|
||||||
## Repository layout
|
## Repository layout
|
||||||
|
|
||||||
```
|
```
|
||||||
main.v CLI entry point (vr <command> ...)
|
main.v CLI entry point (vr <command> ...)
|
||||||
|
repl.v interactive REPL
|
||||||
|
fmt.v source formatter
|
||||||
|
pkg.v package manager (init/get/install/list)
|
||||||
v.mod module definition
|
v.mod module definition
|
||||||
compiler/ assembler/ linker/ vm/ obj/ the toolchain itself
|
compiler/ assembler/ linker/ vm/ obj/ the toolchain itself
|
||||||
bin/ built binary + standalone tools
|
bin/ built binary + standalone tools
|
||||||
|
|||||||
+51
-13
@@ -3,6 +3,7 @@ module compiler
|
|||||||
|
|
||||||
pub enum ExprKind {
|
pub enum ExprKind {
|
||||||
int_lit
|
int_lit
|
||||||
|
float_lit
|
||||||
str_lit
|
str_lit
|
||||||
bool_lit
|
bool_lit
|
||||||
ident
|
ident
|
||||||
@@ -11,7 +12,9 @@ pub enum ExprKind {
|
|||||||
index
|
index
|
||||||
field
|
field
|
||||||
method_call
|
method_call
|
||||||
|
slice
|
||||||
unary
|
unary
|
||||||
|
anon_fn
|
||||||
binary
|
binary
|
||||||
call
|
call
|
||||||
}
|
}
|
||||||
@@ -25,22 +28,31 @@ pub mut:
|
|||||||
|
|
||||||
pub struct Expr {
|
pub struct Expr {
|
||||||
pub mut:
|
pub mut:
|
||||||
kind ExprKind
|
kind ExprKind
|
||||||
int_v i64
|
int_v i64
|
||||||
str_v string
|
float_v f64
|
||||||
|
str_v string
|
||||||
name string // ident/call name, or the field name of a `.field` access
|
name string // ident/call name, or the field name of a `.field` access
|
||||||
op TokKind
|
op TokKind
|
||||||
left &Expr = unsafe { nil }
|
left &Expr = unsafe { nil }
|
||||||
right &Expr = unsafe { nil }
|
right &Expr = unsafe { nil }
|
||||||
|
extra &Expr = unsafe { nil } // slice: end index expression
|
||||||
elems []Expr
|
elems []Expr
|
||||||
fields []StructField // struct_lit: the named fields
|
fields []StructField // struct_lit: the named fields
|
||||||
args []Expr
|
args []Expr
|
||||||
line int
|
type_args []string // call: explicit generic type arguments (first[int](...))
|
||||||
|
fparams []string // anon_fn: parameter names
|
||||||
|
fdefaults []Expr // anon_fn: default values (parallel to fparams)
|
||||||
|
fhas_defs []bool // anon_fn: which params have defaults
|
||||||
|
fvariadic bool // anon_fn: last param is variadic
|
||||||
|
fn_body []Stmt // anon_fn: function body
|
||||||
|
line int
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum StmtKind {
|
pub enum StmtKind {
|
||||||
expr_stmt
|
expr_stmt
|
||||||
let_stmt
|
let_stmt
|
||||||
|
destruct_stmt
|
||||||
assign_stmt
|
assign_stmt
|
||||||
index_assign
|
index_assign
|
||||||
field_assign
|
field_assign
|
||||||
@@ -53,6 +65,8 @@ pub enum StmtKind {
|
|||||||
continue_stmt
|
continue_stmt
|
||||||
ret_stmt
|
ret_stmt
|
||||||
assert_stmt
|
assert_stmt
|
||||||
|
try_stmt
|
||||||
|
throw_stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchArm is a single `value { body }` arm of a match statement.
|
// MatchArm is a single `value { body }` arm of a match statement.
|
||||||
@@ -66,6 +80,9 @@ pub struct Stmt {
|
|||||||
pub mut:
|
pub mut:
|
||||||
kind StmtKind
|
kind StmtKind
|
||||||
target string
|
target string
|
||||||
|
idx_target string // for_in_stmt: index variable name (empty when unused)
|
||||||
|
destruct_targets []string // destruct_stmt: names to bind
|
||||||
|
destruct_field bool // destruct_stmt: struct ({ a, b }) vs array ([a, b])
|
||||||
expr Expr
|
expr Expr
|
||||||
cond Expr
|
cond Expr
|
||||||
base Expr // index_assign: the indexed expression
|
base Expr // index_assign: the indexed expression
|
||||||
@@ -90,10 +107,14 @@ pub mut:
|
|||||||
|
|
||||||
pub struct FnDecl {
|
pub struct FnDecl {
|
||||||
pub mut:
|
pub mut:
|
||||||
name string
|
name string
|
||||||
recv_name string // method receiver local name ('' for plain functions)
|
type_params []string // generic type parameters (fn first[T, U](...) { ... })
|
||||||
recv_type string // method receiver struct type ('' for plain functions)
|
recv_name string // method receiver local name ('' for plain functions)
|
||||||
|
recv_type string // method receiver struct type ('' for plain functions)
|
||||||
params []string
|
params []string
|
||||||
|
defaults []Expr // parallel to params; empty Expr{} when no default
|
||||||
|
has_defs []bool // parallel to params: whether a default exists
|
||||||
|
variadic bool // last param is variadic (nums...)
|
||||||
body []Stmt
|
body []Stmt
|
||||||
line int
|
line int
|
||||||
}
|
}
|
||||||
@@ -120,11 +141,28 @@ pub mut:
|
|||||||
line int
|
line int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InterfaceDecl is an `interface Name { method1(); method2() type }` declaration.
|
||||||
|
// Methods are stored as (name, return_type) pairs. The interface is satisfied
|
||||||
|
// by any struct that implements all listed methods (structural/duck typing).
|
||||||
|
pub struct InterfaceDecl {
|
||||||
|
pub mut:
|
||||||
|
name string
|
||||||
|
methods []InterfaceMethod
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InterfaceMethod {
|
||||||
|
pub mut:
|
||||||
|
name string
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Program {
|
pub struct Program {
|
||||||
pub mut:
|
pub mut:
|
||||||
fns []FnDecl
|
fns []FnDecl
|
||||||
structs []StructDecl
|
structs []StructDecl
|
||||||
enums []EnumDecl
|
enums []EnumDecl
|
||||||
imports []ImportDecl
|
imports []ImportDecl
|
||||||
consts []ConstDecl
|
consts []ConstDecl
|
||||||
|
interfaces []InterfaceDecl
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,599 @@
|
|||||||
|
// check.v — a compile-time type checker for VuurRaaf.
|
||||||
|
//
|
||||||
|
// Runs after parsing and before codegen. The language is dynamically typed at
|
||||||
|
// runtime, so this pass is deliberately conservative: it rejects programs that
|
||||||
|
// are *provably* wrong (unknown variables, field access on numbers, arithmetic
|
||||||
|
// on strings, wrong arity on known functions) while leaving genuinely dynamic
|
||||||
|
// programs (untyped parameters, unknown receiver types, mixed containers, and
|
||||||
|
// calls to functions defined in other objects) alone. Unresolved function
|
||||||
|
// names are deferred to the linker, matching the toolchain's separate
|
||||||
|
// compilation model.
|
||||||
|
module compiler
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
enum CType {
|
||||||
|
unknown
|
||||||
|
int_t
|
||||||
|
float_t
|
||||||
|
string_t
|
||||||
|
bool_t
|
||||||
|
array_t
|
||||||
|
struct_t
|
||||||
|
enum_t
|
||||||
|
closure_t
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TypeInfo {
|
||||||
|
kind CType
|
||||||
|
name string // struct/enum type name when statically known
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FnSig {
|
||||||
|
min_args int
|
||||||
|
has_defs []bool
|
||||||
|
variadic bool
|
||||||
|
n_type_params int // generic type parameters declared on the function
|
||||||
|
returns TypeInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Checker {
|
||||||
|
mut:
|
||||||
|
types map[string]TypeInfo // current scope: local name -> type
|
||||||
|
fns map[string]FnSig
|
||||||
|
structs map[string][]string
|
||||||
|
enums map[string][]string
|
||||||
|
consts map[string]TypeInfo
|
||||||
|
loop_depth int
|
||||||
|
checked map[string]bool // imported files already checked
|
||||||
|
}
|
||||||
|
|
||||||
|
// check validates a parsed program and returns an error on the first problem.
|
||||||
|
fn check(prog Program) ! {
|
||||||
|
mut c := Checker{}
|
||||||
|
// register declarations
|
||||||
|
for sd in prog.structs {
|
||||||
|
if sd.name in c.structs {
|
||||||
|
return error('duplicate struct declaration "${sd.name}" (line ${sd.line})')
|
||||||
|
}
|
||||||
|
c.structs[sd.name] = sd.fields
|
||||||
|
}
|
||||||
|
for ed in prog.enums {
|
||||||
|
if ed.name in c.enums {
|
||||||
|
return error('duplicate enum declaration "${ed.name}" (line ${ed.line})')
|
||||||
|
}
|
||||||
|
c.enums[ed.name] = ed.variants
|
||||||
|
}
|
||||||
|
for cd in prog.consts {
|
||||||
|
c.consts[cd.name] = TypeInfo{ kind: .int_t }
|
||||||
|
}
|
||||||
|
for fd in prog.fns {
|
||||||
|
if fd.name in c.fns {
|
||||||
|
return error('duplicate function "${fd.name}" (line ${fd.line})')
|
||||||
|
}
|
||||||
|
c.fns[fd.name] = FnSig{ min_args: fd.params.len - def_count(fd), has_defs: fd.has_defs, variadic: fd.variadic, n_type_params: fd.type_params.len }
|
||||||
|
}
|
||||||
|
// imported files are checked (and their symbols merged) recursively
|
||||||
|
for imp in prog.imports {
|
||||||
|
c.check_import(imp.path)!
|
||||||
|
}
|
||||||
|
for fd in prog.fns {
|
||||||
|
c.check_fn(fd)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn def_count(fd FnDecl) int {
|
||||||
|
mut n := 0
|
||||||
|
for has in fd.has_defs {
|
||||||
|
if has {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut c Checker) check_import(path string) ! {
|
||||||
|
if path in c.checked {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.checked[path] = true
|
||||||
|
resolved := resolve_import(path) or { return error('cannot read import "${path}"') }
|
||||||
|
src := os.read_file(resolved) or { return error('cannot read import "${path}"') }
|
||||||
|
prog := parse(tokenize(src)!)!
|
||||||
|
// merge declarations from the import
|
||||||
|
for sd in prog.structs {
|
||||||
|
if sd.name !in c.structs {
|
||||||
|
c.structs[sd.name] = sd.fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ed in prog.enums {
|
||||||
|
if ed.name !in c.enums {
|
||||||
|
c.enums[ed.name] = ed.variants
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for cd in prog.consts {
|
||||||
|
if cd.name !in c.consts {
|
||||||
|
c.consts[cd.name] = TypeInfo{ kind: .int_t }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for fd in prog.fns {
|
||||||
|
if fd.name !in c.fns {
|
||||||
|
c.fns[fd.name] = FnSig{ min_args: fd.params.len - def_count(fd), has_defs: fd.has_defs, variadic: fd.variadic, n_type_params: fd.type_params.len }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for imp in prog.imports {
|
||||||
|
c.check_import(imp.path)!
|
||||||
|
}
|
||||||
|
for fd in prog.fns {
|
||||||
|
c.check_fn(fd)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut c Checker) check_fn(fd FnDecl) ! {
|
||||||
|
c.types.clear()
|
||||||
|
c.loop_depth = 0
|
||||||
|
// receiver and parameters are untyped (unknown) — the runtime is dynamic
|
||||||
|
if fd.recv_name.len > 0 {
|
||||||
|
c.types[fd.recv_name] = TypeInfo{ kind: .struct_t }
|
||||||
|
}
|
||||||
|
for p in fd.params {
|
||||||
|
c.types[p] = TypeInfo{ kind: .unknown }
|
||||||
|
}
|
||||||
|
for st in fd.body {
|
||||||
|
c.check_stmt(st)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut c Checker) check_stmt(st Stmt) ! {
|
||||||
|
match st.kind {
|
||||||
|
.expr_stmt {
|
||||||
|
_ = c.check_expr(st.expr)!
|
||||||
|
}
|
||||||
|
.let_stmt {
|
||||||
|
t := c.check_expr(st.expr)!
|
||||||
|
c.types[st.target] = t
|
||||||
|
}
|
||||||
|
.destruct_stmt {
|
||||||
|
base := c.check_expr(st.expr)!
|
||||||
|
for name in st.destruct_targets {
|
||||||
|
c.types[name] = if st.destruct_field { TypeInfo{ kind: .unknown } } else { TypeInfo{ kind: .unknown } }
|
||||||
|
}
|
||||||
|
_ = base
|
||||||
|
}
|
||||||
|
.assign_stmt {
|
||||||
|
if st.target !in c.types {
|
||||||
|
return error('unknown variable "${st.target}" (line ${st.line})')
|
||||||
|
}
|
||||||
|
_ = c.check_expr(st.expr)!
|
||||||
|
}
|
||||||
|
.index_assign {
|
||||||
|
base := c.check_expr(st.base)!
|
||||||
|
_ = c.check_expr(st.idx)!
|
||||||
|
c.expect_container(base, 'index assignment', st.line)!
|
||||||
|
_ = c.check_expr(st.expr)!
|
||||||
|
}
|
||||||
|
.field_assign {
|
||||||
|
base := c.check_expr(st.base)!
|
||||||
|
c.expect_struct_like(base, 'field assignment', st.line)!
|
||||||
|
_ = c.check_expr(st.expr)!
|
||||||
|
}
|
||||||
|
.if_stmt {
|
||||||
|
_ = c.check_expr(st.cond)!
|
||||||
|
for s in st.body {
|
||||||
|
c.check_stmt(s)!
|
||||||
|
}
|
||||||
|
for s in st.els {
|
||||||
|
c.check_stmt(s)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.match_stmt {
|
||||||
|
_ = c.check_expr(st.expr)!
|
||||||
|
for arm in st.arms {
|
||||||
|
_ = c.check_expr(arm.val)!
|
||||||
|
for s in arm.body {
|
||||||
|
c.check_stmt(s)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for s in st.els_body {
|
||||||
|
c.check_stmt(s)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.while_stmt {
|
||||||
|
_ = c.check_expr(st.cond)!
|
||||||
|
c.loop_depth++
|
||||||
|
for s in st.body {
|
||||||
|
c.check_stmt(s)!
|
||||||
|
}
|
||||||
|
c.loop_depth--
|
||||||
|
}
|
||||||
|
.for_range_stmt {
|
||||||
|
start_t := c.check_expr(st.expr)!
|
||||||
|
end_t := c.check_expr(st.cond)!
|
||||||
|
c.expect_numeric(start_t, 'range start', st.line)!
|
||||||
|
c.expect_numeric(end_t, 'range end', st.line)!
|
||||||
|
c.types[st.target] = TypeInfo{ kind: .int_t }
|
||||||
|
c.loop_depth++
|
||||||
|
for s in st.body {
|
||||||
|
c.check_stmt(s)!
|
||||||
|
}
|
||||||
|
c.loop_depth--
|
||||||
|
}
|
||||||
|
.for_in_stmt {
|
||||||
|
seq := c.check_expr(st.expr)!
|
||||||
|
// iterate enums and arrays; unknown is allowed (dynamic)
|
||||||
|
if seq.kind == .int_t || seq.kind == .float_t || seq.kind == .bool_t {
|
||||||
|
return error('cannot iterate a ${type_name(seq.kind)} (line ${st.line})')
|
||||||
|
}
|
||||||
|
c.types[st.target] = TypeInfo{ kind: .unknown }
|
||||||
|
if st.idx_target.len > 0 {
|
||||||
|
c.types[st.idx_target] = TypeInfo{ kind: .int_t }
|
||||||
|
}
|
||||||
|
c.loop_depth++
|
||||||
|
for s in st.body {
|
||||||
|
c.check_stmt(s)!
|
||||||
|
}
|
||||||
|
c.loop_depth--
|
||||||
|
}
|
||||||
|
.break_stmt, .continue_stmt {
|
||||||
|
if c.loop_depth == 0 {
|
||||||
|
what := if st.kind == .break_stmt { 'break' } else { 'continue' }
|
||||||
|
return error('${what} outside of a loop (line ${st.line})')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ret_stmt {
|
||||||
|
if st.has_val {
|
||||||
|
_ = c.check_expr(st.expr)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.assert_stmt {
|
||||||
|
_ = c.check_expr(st.expr)!
|
||||||
|
}
|
||||||
|
.try_stmt {
|
||||||
|
c.loop_depth++ // errors unwind through loops; keep depth permissive
|
||||||
|
c.loop_depth--
|
||||||
|
for s in st.body {
|
||||||
|
c.check_stmt(s)!
|
||||||
|
}
|
||||||
|
c.types[st.target] = TypeInfo{ kind: .string_t }
|
||||||
|
for s in st.els {
|
||||||
|
c.check_stmt(s)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.throw_stmt {
|
||||||
|
_ = c.check_expr(st.expr)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut c Checker) check_expr(e Expr) !TypeInfo {
|
||||||
|
return match e.kind {
|
||||||
|
.int_lit { TypeInfo{ kind: .int_t } }
|
||||||
|
.float_lit { TypeInfo{ kind: .float_t } }
|
||||||
|
.str_lit { TypeInfo{ kind: .string_t } }
|
||||||
|
.bool_lit { TypeInfo{ kind: .bool_t } }
|
||||||
|
.ident {
|
||||||
|
if e.name in c.types {
|
||||||
|
c.types[e.name]
|
||||||
|
} else if e.name in c.consts {
|
||||||
|
c.consts[e.name]
|
||||||
|
} else {
|
||||||
|
return error('unknown variable "${e.name}" (line ${e.line})')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.array_lit {
|
||||||
|
for el in e.elems {
|
||||||
|
_ = c.check_expr(el)!
|
||||||
|
}
|
||||||
|
TypeInfo{ kind: .array_t }
|
||||||
|
}
|
||||||
|
.struct_lit {
|
||||||
|
if e.name.len > 0 && e.name in c.structs {
|
||||||
|
fields := c.structs[e.name]
|
||||||
|
mut seen := map[string]bool{}
|
||||||
|
for f in e.fields {
|
||||||
|
if f.name !in 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
|
||||||
|
_ = c.check_expr(f.val)!
|
||||||
|
}
|
||||||
|
return TypeInfo{ kind: .struct_t, name: e.name }
|
||||||
|
}
|
||||||
|
for f in e.fields {
|
||||||
|
_ = c.check_expr(f.val)!
|
||||||
|
}
|
||||||
|
TypeInfo{ kind: .struct_t }
|
||||||
|
}
|
||||||
|
.index {
|
||||||
|
base := c.check_expr(*e.left)!
|
||||||
|
_ = c.check_expr(*e.right)!
|
||||||
|
c.expect_container(base, 'indexing', e.line)!
|
||||||
|
TypeInfo{ kind: .unknown }
|
||||||
|
}
|
||||||
|
.field {
|
||||||
|
base := c.check_expr(*e.left)!
|
||||||
|
c.expect_struct_like(base, 'field access', e.line)!
|
||||||
|
// enum variant: Color.red → enum_t
|
||||||
|
if base.kind == .enum_t {
|
||||||
|
return TypeInfo{ kind: .enum_t, name: base.name }
|
||||||
|
}
|
||||||
|
TypeInfo{ kind: .unknown }
|
||||||
|
}
|
||||||
|
.method_call {
|
||||||
|
recv := c.check_expr(*e.left)!
|
||||||
|
if recv.kind == .int_t || recv.kind == .float_t || recv.kind == .bool_t {
|
||||||
|
return error('cannot call a method on a ${type_name(recv.kind)} (line ${e.line})')
|
||||||
|
}
|
||||||
|
for a in e.args {
|
||||||
|
_ = c.check_expr(a)!
|
||||||
|
}
|
||||||
|
TypeInfo{ kind: .unknown }
|
||||||
|
}
|
||||||
|
.slice {
|
||||||
|
base := c.check_expr(*e.left)!
|
||||||
|
_ = c.check_expr(*e.right)!
|
||||||
|
_ = c.check_expr(*e.extra)!
|
||||||
|
if base.kind == .int_t || base.kind == .float_t || base.kind == .bool_t {
|
||||||
|
return error('cannot slice a ${type_name(base.kind)} (line ${e.line})')
|
||||||
|
}
|
||||||
|
if base.kind == .string_t {
|
||||||
|
TypeInfo{ kind: .string_t }
|
||||||
|
} else {
|
||||||
|
TypeInfo{ kind: .unknown }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.unary {
|
||||||
|
op := c.check_expr(*e.right)!
|
||||||
|
match e.op {
|
||||||
|
.kw_not { TypeInfo{ kind: .bool_t } }
|
||||||
|
.tilde {
|
||||||
|
c.expect_int(op, 'bitwise not', e.line)!
|
||||||
|
TypeInfo{ kind: .int_t }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
c.expect_numeric(op, 'unary minus', e.line)!
|
||||||
|
op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.binary {
|
||||||
|
c.check_binary(e)!
|
||||||
|
}
|
||||||
|
.call {
|
||||||
|
c.check_call(e)!
|
||||||
|
}
|
||||||
|
.anon_fn {
|
||||||
|
for p in e.fparams {
|
||||||
|
c.types[p] = TypeInfo{ kind: .unknown }
|
||||||
|
}
|
||||||
|
for s in e.fn_body {
|
||||||
|
c.check_stmt(s)!
|
||||||
|
}
|
||||||
|
TypeInfo{ kind: .closure_t }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut c Checker) check_binary(e Expr) !TypeInfo {
|
||||||
|
l := c.check_expr(*e.left)!
|
||||||
|
r := c.check_expr(*e.right)!
|
||||||
|
return match e.op {
|
||||||
|
.plus {
|
||||||
|
if l.kind == .string_t || r.kind == .string_t {
|
||||||
|
return TypeInfo{ kind: .string_t }
|
||||||
|
}
|
||||||
|
if l.kind == .array_t || r.kind == .array_t {
|
||||||
|
return error('cannot add arrays with + (line ${e.line})')
|
||||||
|
}
|
||||||
|
if l.kind == .struct_t || r.kind == .struct_t {
|
||||||
|
return error('cannot add structs with + (line ${e.line})')
|
||||||
|
}
|
||||||
|
if l.kind == .bool_t || r.kind == .bool_t {
|
||||||
|
return error('cannot add a bool with + (line ${e.line})')
|
||||||
|
}
|
||||||
|
if l.kind == .unknown || r.kind == .unknown {
|
||||||
|
return TypeInfo{ kind: .unknown }
|
||||||
|
}
|
||||||
|
if l.kind == .float_t || r.kind == .float_t {
|
||||||
|
return TypeInfo{ kind: .float_t }
|
||||||
|
}
|
||||||
|
return TypeInfo{ kind: .int_t }
|
||||||
|
}
|
||||||
|
.minus, .star, .slash, .percent {
|
||||||
|
c.expect_numeric(l, 'arithmetic', e.line)!
|
||||||
|
c.expect_numeric(r, 'arithmetic', e.line)!
|
||||||
|
if l.kind == .float_t || r.kind == .float_t {
|
||||||
|
TypeInfo{ kind: .float_t }
|
||||||
|
} else {
|
||||||
|
TypeInfo{ kind: .int_t }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.eq_eq, .not_eq {
|
||||||
|
TypeInfo{ kind: .bool_t }
|
||||||
|
}
|
||||||
|
.lt, .le, .gt, .ge {
|
||||||
|
if l.kind == .array_t || r.kind == .array_t {
|
||||||
|
return error('cannot order arrays (line ${e.line})')
|
||||||
|
}
|
||||||
|
if l.kind == .struct_t || r.kind == .struct_t {
|
||||||
|
return error('cannot order structs (line ${e.line})')
|
||||||
|
}
|
||||||
|
if l.kind == .bool_t && r.kind == .bool_t {
|
||||||
|
return error('cannot order booleans (line ${e.line})')
|
||||||
|
}
|
||||||
|
if l.kind != .unknown && r.kind != .unknown && l.kind != r.kind && !(is_num_kind(l.kind) && is_num_kind(r.kind)) {
|
||||||
|
return error('cannot compare a ${type_name(l.kind)} and a ${type_name(r.kind)} (line ${e.line})')
|
||||||
|
}
|
||||||
|
TypeInfo{ kind: .bool_t }
|
||||||
|
}
|
||||||
|
.kw_and, .kw_or {
|
||||||
|
TypeInfo{ kind: .bool_t }
|
||||||
|
}
|
||||||
|
.amp, .pipe, .caret, .lt_lt, .gt_gt {
|
||||||
|
c.expect_int(l, 'bitwise operator', e.line)!
|
||||||
|
c.expect_int(r, 'bitwise operator', e.line)!
|
||||||
|
TypeInfo{ kind: .int_t }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return error('unsupported operator at line ${e.line}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut c Checker) check_call(e Expr) !TypeInfo {
|
||||||
|
// builtin calls
|
||||||
|
if e.name == 'len' {
|
||||||
|
if e.args.len != 1 {
|
||||||
|
return error('len() takes exactly one argument (line ${e.line})')
|
||||||
|
}
|
||||||
|
t := c.check_expr(e.args[0])!
|
||||||
|
if t.kind == .int_t || t.kind == .float_t || t.kind == .bool_t || t.kind == .closure_t {
|
||||||
|
return error('len() on a ${type_name(t.kind)} (line ${e.line})')
|
||||||
|
}
|
||||||
|
return TypeInfo{ kind: .int_t }
|
||||||
|
}
|
||||||
|
if e.name == 'push' || e.name == 'insert' || e.name == 'remove' {
|
||||||
|
if e.args.len == 0 {
|
||||||
|
return error('${e.name}() expects arguments (line ${e.line})')
|
||||||
|
}
|
||||||
|
seq := c.check_expr(e.args[0])!
|
||||||
|
c.expect_container(seq, '${e.name}()', e.line)!
|
||||||
|
for a in e.args[1..] {
|
||||||
|
_ = c.check_expr(a)!
|
||||||
|
}
|
||||||
|
return TypeInfo{ kind: .unknown }
|
||||||
|
}
|
||||||
|
if e.name == 'has' || e.name == 'delete' {
|
||||||
|
if e.args.len != 2 {
|
||||||
|
return error('${e.name}() takes exactly two arguments (line ${e.line})')
|
||||||
|
}
|
||||||
|
seq := c.check_expr(e.args[0])!
|
||||||
|
c.expect_struct_like(seq, '${e.name}()', e.line)!
|
||||||
|
_ = c.check_expr(e.args[1])!
|
||||||
|
return TypeInfo{ kind: .unknown }
|
||||||
|
}
|
||||||
|
if e.name == 'keys' {
|
||||||
|
if e.args.len != 1 {
|
||||||
|
return error('keys() takes exactly one argument (line ${e.line})')
|
||||||
|
}
|
||||||
|
seq := c.check_expr(e.args[0])!
|
||||||
|
c.expect_struct_like(seq, 'keys()', e.line)!
|
||||||
|
return TypeInfo{ kind: .array_t }
|
||||||
|
}
|
||||||
|
if e.name == 'print' || e.name == 'println' {
|
||||||
|
if e.args.len != 1 {
|
||||||
|
return error('${e.name}() takes exactly one argument (line ${e.line})')
|
||||||
|
}
|
||||||
|
_ = c.check_expr(e.args[0])!
|
||||||
|
return TypeInfo{ kind: .unknown }
|
||||||
|
}
|
||||||
|
// host builtins (native) — validate arity from the spec table
|
||||||
|
bid, bargc := builtin_spec(e.name)
|
||||||
|
if bid >= 0 {
|
||||||
|
if e.args.len != bargc {
|
||||||
|
return error('${e.name}() takes exactly ${bargc} argument(s) (line ${e.line})')
|
||||||
|
}
|
||||||
|
for a in e.args {
|
||||||
|
_ = c.check_expr(a)!
|
||||||
|
}
|
||||||
|
return builtin_result_type(e.name)
|
||||||
|
}
|
||||||
|
// closure call: a local holding a function value
|
||||||
|
if e.name in c.types && c.types[e.name].kind == .closure_t {
|
||||||
|
for a in e.args {
|
||||||
|
_ = c.check_expr(a)!
|
||||||
|
}
|
||||||
|
return TypeInfo{ kind: .unknown }
|
||||||
|
}
|
||||||
|
// user function — arity is checked only when the signature is known.
|
||||||
|
// Unknown names are allowed: this toolchain supports separate compilation,
|
||||||
|
// so a call may resolve to a function in another object at link time.
|
||||||
|
// Truly missing functions are reported by the linker, not the checker.
|
||||||
|
if e.name in c.fns {
|
||||||
|
sig := c.fns[e.name]
|
||||||
|
// generic type arguments must match the declared type parameters
|
||||||
|
if e.type_args.len > 0 && e.type_args.len != sig.n_type_params {
|
||||||
|
return error('${e.name}() takes ${sig.n_type_params} type argument(s), got ${e.type_args.len} (line ${e.line})')
|
||||||
|
}
|
||||||
|
if e.type_args.len == 0 && sig.n_type_params > 0 {
|
||||||
|
// calling a generic function without explicit type args is fine —
|
||||||
|
// the VM infers from the values at runtime
|
||||||
|
}
|
||||||
|
if !sig.variadic {
|
||||||
|
if e.args.len < sig.min_args {
|
||||||
|
return error('${e.name}() expects at least ${sig.min_args} argument(s), got ${e.args.len} (line ${e.line})')
|
||||||
|
}
|
||||||
|
if e.args.len > sig.has_defs.len {
|
||||||
|
return error('${e.name}() expects at most ${sig.has_defs.len} argument(s), got ${e.args.len} (line ${e.line})')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for a in e.args {
|
||||||
|
_ = c.check_expr(a)!
|
||||||
|
}
|
||||||
|
return TypeInfo{ kind: .unknown }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn builtin_result_type(name string) TypeInfo {
|
||||||
|
return match name {
|
||||||
|
'abs', 'min', 'max', 'floor', 'ceil', 'round', 'rand_int' { TypeInfo{ kind: .int_t } }
|
||||||
|
'pow', 'sqrt', 'rand', 'float', 'time' { TypeInfo{ kind: .float_t } }
|
||||||
|
'int' { TypeInfo{ kind: .int_t } }
|
||||||
|
'str', 'type', 'lower', 'upper', 'trim', 'getenv', 'read_file' { TypeInfo{ kind: .string_t } }
|
||||||
|
'contains', 'starts_with', 'ends_with' { TypeInfo{ kind: .bool_t } }
|
||||||
|
'split' { TypeInfo{ kind: .array_t } }
|
||||||
|
'join' { TypeInfo{ kind: .string_t } }
|
||||||
|
'sort', 'reverse' { TypeInfo{ kind: .array_t } }
|
||||||
|
'pop' { TypeInfo{ kind: .unknown } }
|
||||||
|
'clone', 'index_of' { TypeInfo{ kind: .unknown } }
|
||||||
|
'args', 'keys' { TypeInfo{ kind: .array_t } }
|
||||||
|
'len' { TypeInfo{ kind: .int_t } }
|
||||||
|
'write_file', 'setenv', 'exit', 'sleep', 'eprint' { TypeInfo{ kind: .unknown } }
|
||||||
|
else { TypeInfo{ kind: .unknown } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_num_kind(k CType) bool {
|
||||||
|
return k == .int_t || k == .float_t
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_name(k CType) string {
|
||||||
|
return match k {
|
||||||
|
.int_t { 'int' }
|
||||||
|
.float_t { 'float' }
|
||||||
|
.string_t { 'string' }
|
||||||
|
.bool_t { 'bool' }
|
||||||
|
.array_t { 'array' }
|
||||||
|
.struct_t { 'struct' }
|
||||||
|
.enum_t { 'enum' }
|
||||||
|
.closure_t { 'function' }
|
||||||
|
else { 'value' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut c Checker) expect_numeric(t TypeInfo, what string, line int) ! {
|
||||||
|
if t.kind == .string_t || t.kind == .array_t || t.kind == .struct_t || t.kind == .bool_t || t.kind == .closure_t {
|
||||||
|
return error('${what} on a ${type_name(t.kind)} (line ${line})')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut c Checker) expect_int(t TypeInfo, what string, line int) ! {
|
||||||
|
if t.kind == .string_t || t.kind == .array_t || t.kind == .struct_t || t.kind == .bool_t || t.kind == .closure_t || t.kind == .float_t {
|
||||||
|
return error('${what} requires an int, got a ${type_name(t.kind)} (line ${line})')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut c Checker) expect_container(t TypeInfo, what string, line int) ! {
|
||||||
|
if t.kind == .int_t || t.kind == .float_t || t.kind == .bool_t || t.kind == .closure_t {
|
||||||
|
return error('${what} on a ${type_name(t.kind)} (line ${line})')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut c Checker) expect_struct_like(t TypeInfo, what string, line int) ! {
|
||||||
|
if t.kind == .int_t || t.kind == .float_t || t.kind == .bool_t || t.kind == .string_t || t.kind == .closure_t {
|
||||||
|
return error('${what} on a ${type_name(t.kind)} (line ${line})')
|
||||||
|
}
|
||||||
|
}
|
||||||
+459
-17
@@ -31,8 +31,10 @@ mut:
|
|||||||
types map[string]string // local name -> declared struct type ('' = unknown)
|
types map[string]string // local name -> declared struct type ('' = unknown)
|
||||||
structs map[string][]string // declared struct name -> field list
|
structs map[string][]string // declared struct name -> field list
|
||||||
enums map[string][]string // enum name -> variant list
|
enums map[string][]string // enum name -> variant list
|
||||||
|
lam_counter int // anonymous function counter
|
||||||
enum_vals map[string]int // 'Enum.variant' -> integer value
|
enum_vals map[string]int // 'Enum.variant' -> integer value
|
||||||
consts map[string]i64 // constant name -> integer value
|
consts map[string]i64 // constant name -> integer value
|
||||||
|
lines []obj.LineInfo // code offset -> source line (debug info)
|
||||||
local_cnt int
|
local_cnt int
|
||||||
argc int
|
argc int
|
||||||
cur_fn string
|
cur_fn string
|
||||||
@@ -78,7 +80,7 @@ fn gen(prog Program) !obj.Obj {
|
|||||||
}
|
}
|
||||||
// compile imported files and merge their objects
|
// compile imported files and merge their objects
|
||||||
for imp in prog.imports {
|
for imp in prog.imports {
|
||||||
imported := compile_file(imp.path)!
|
imported := compile_file(resolve_import(imp.path)!)!
|
||||||
// merge symbols from the imported object
|
// merge symbols from the imported object
|
||||||
for s in imported.symbols {
|
for s in imported.symbols {
|
||||||
g.symbols << s
|
g.symbols << s
|
||||||
@@ -93,6 +95,10 @@ fn gen(prog Program) !obj.Obj {
|
|||||||
for r in imported.relocs {
|
for r in imported.relocs {
|
||||||
g.relocs << obj.Reloc{ offset: u32(code_off) + r.offset, name: r.name, kind: r.kind }
|
g.relocs << obj.Reloc{ offset: u32(code_off) + r.offset, name: r.name, kind: r.kind }
|
||||||
}
|
}
|
||||||
|
// merge debug info, rebasing offsets into this object's code space
|
||||||
|
for l in imported.lines {
|
||||||
|
g.lines << obj.LineInfo{ off: u32(code_off) + l.off, line: l.line }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for fd in prog.fns {
|
for fd in prog.fns {
|
||||||
g.gen_fn(fd)!
|
g.gen_fn(fd)!
|
||||||
@@ -102,6 +108,7 @@ fn gen(prog Program) !obj.Obj {
|
|||||||
strings: g.strings
|
strings: g.strings
|
||||||
code: g.code
|
code: g.code
|
||||||
relocs: g.relocs
|
relocs: g.relocs
|
||||||
|
lines: g.lines
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +118,7 @@ fn (mut g Gen) gen_fn(fd FnDecl) ! {
|
|||||||
sym := if fd.recv_type.len > 0 { '${fd.recv_type}.${fd.name}' } else { fd.name }
|
sym := if fd.recv_type.len > 0 { '${fd.recv_type}.${fd.name}' } else { fd.name }
|
||||||
g.cur_fn = sym
|
g.cur_fn = sym
|
||||||
g.symbols << obj.Symbol{ name: sym, entry: g.code.len }
|
g.symbols << obj.Symbol{ name: sym, entry: g.code.len }
|
||||||
|
g.lines << obj.LineInfo{ off: u32(g.code.len), line: fd.line }
|
||||||
g.locals.clear()
|
g.locals.clear()
|
||||||
g.types.clear()
|
g.types.clear()
|
||||||
g.local_cnt = 0
|
g.local_cnt = 0
|
||||||
@@ -121,20 +129,67 @@ fn (mut g Gen) gen_fn(fd FnDecl) ! {
|
|||||||
g.types[fd.recv_name] = fd.recv_type
|
g.types[fd.recv_name] = fd.recv_type
|
||||||
next = 1
|
next = 1
|
||||||
}
|
}
|
||||||
|
// a variadic parameter does not occupy an argument slot; it gets a fresh
|
||||||
|
// local that the prologue fills with the collected vararg array
|
||||||
|
if fd.variadic {
|
||||||
|
g.argc--
|
||||||
|
}
|
||||||
for i, p in fd.params {
|
for i, p in fd.params {
|
||||||
|
if fd.variadic && i == fd.params.len - 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
g.locals[p] = i + next
|
g.locals[p] = i + next
|
||||||
}
|
}
|
||||||
g.local_cnt = g.argc
|
g.local_cnt = g.argc
|
||||||
|
if fd.variadic {
|
||||||
|
vidx := g.local_cnt
|
||||||
|
g.local_cnt++
|
||||||
|
g.locals[fd.params[fd.params.len - 1]] = vidx
|
||||||
|
}
|
||||||
// `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
|
||||||
// has been scanned.
|
// has been scanned.
|
||||||
g.code << op_enter
|
g.code << op_enter
|
||||||
g.enter_off = u32(g.code.len)
|
g.enter_off = u32(g.code.len)
|
||||||
g.code << obj.encode_i64(0)
|
g.code << obj.encode_i64(0)
|
||||||
|
// default parameter values: if the caller passed fewer args than this
|
||||||
|
// param's slot, evaluate the default and store it
|
||||||
|
for i, p in fd.params {
|
||||||
|
if fd.variadic && i == fd.params.len - 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i >= fd.has_defs.len || !fd.has_defs[i] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slot := i + next
|
||||||
|
skip_l := g.new_label()
|
||||||
|
g.code << op_argc
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(i64(slot))
|
||||||
|
g.code << op_le
|
||||||
|
g.code << op_jz
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
g.fixups << Fixup{ name: skip_l, off: u32(g.code.len) - 8 }
|
||||||
|
g.gen_expr(fd.defaults[i])!
|
||||||
|
g.emit_store(slot)
|
||||||
|
g.emit_label(skip_l)
|
||||||
|
}
|
||||||
|
// variadic collection: build an array from args[argc..actual-1]
|
||||||
|
if fd.variadic {
|
||||||
|
vidx := g.locals[fd.params[fd.params.len - 1]] or {
|
||||||
|
return error('internal: variadic param missing')
|
||||||
|
}
|
||||||
|
g.code << op_varargs
|
||||||
|
g.code << obj.encode_i64(i64(g.argc))
|
||||||
|
g.code << obj.encode_i64(i64(vidx))
|
||||||
|
}
|
||||||
for st in fd.body {
|
for st in fd.body {
|
||||||
g.gen_stmt(st)!
|
g.gen_stmt(st)!
|
||||||
}
|
}
|
||||||
g.code << op_ret // trailing return for fall-through
|
g.code << op_ret // trailing return for fall-through
|
||||||
obj.patch_i64(mut g.code, g.enter_off, i64(g.local_cnt - g.argc))
|
// reserve all local slots: the callee may be called with fewer arguments
|
||||||
|
// than declared (default parameters) or more (variadic), so the frame must
|
||||||
|
// always cover slots 0..local_cnt-1
|
||||||
|
obj.patch_i64(mut g.code, g.enter_off, i64(g.local_cnt))
|
||||||
// resolve intra-function jump targets
|
// resolve intra-function jump targets
|
||||||
for f in g.fixups {
|
for f in g.fixups {
|
||||||
target := g.labels[f.name] or {
|
target := g.labels[f.name] or {
|
||||||
@@ -148,6 +203,7 @@ fn (mut g Gen) gen_fn(fd FnDecl) ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn (mut g Gen) gen_stmt(st Stmt) ! {
|
fn (mut g Gen) gen_stmt(st Stmt) ! {
|
||||||
|
g.lines << obj.LineInfo{ off: u32(g.code.len), line: st.line }
|
||||||
match st.kind {
|
match st.kind {
|
||||||
.expr_stmt {
|
.expr_stmt {
|
||||||
g.gen_expr(st.expr)!
|
g.gen_expr(st.expr)!
|
||||||
@@ -168,6 +224,28 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
|
|||||||
g.code << op_store
|
g.code << op_store
|
||||||
g.code << obj.encode_i64(i64(idx))
|
g.code << obj.encode_i64(i64(idx))
|
||||||
}
|
}
|
||||||
|
.destruct_stmt {
|
||||||
|
// let { a, b } = e → tmp := e; a := tmp.a; b := tmp.b
|
||||||
|
// let [a, b] = e → tmp := e; a := tmp[0]; b := tmp[1]
|
||||||
|
tmp_idx := g.new_local()
|
||||||
|
g.gen_expr(st.expr)!
|
||||||
|
g.emit_store(tmp_idx)
|
||||||
|
for i, name in st.destruct_targets {
|
||||||
|
g.emit_load(tmp_idx)
|
||||||
|
if st.destruct_field {
|
||||||
|
g.emit_field_name(name)
|
||||||
|
g.code << op_sget
|
||||||
|
} else {
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(i64(i))
|
||||||
|
g.code << op_aget
|
||||||
|
}
|
||||||
|
idx := g.new_local()
|
||||||
|
g.locals[name] = idx
|
||||||
|
g.types.delete(name)
|
||||||
|
g.emit_store(idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
.assign_stmt {
|
.assign_stmt {
|
||||||
idx := g.locals[st.target] or {
|
idx := g.locals[st.target] or {
|
||||||
return error('unknown variable "${st.target}" at line ${st.line}')
|
return error('unknown variable "${st.target}" at line ${st.line}')
|
||||||
@@ -350,9 +428,26 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
|
|||||||
prev_t := g.types[st.target] or { '' }
|
prev_t := g.types[st.target] or { '' }
|
||||||
g.locals[st.target] = elem_idx
|
g.locals[st.target] = elem_idx
|
||||||
g.types.delete(st.target)
|
g.types.delete(st.target)
|
||||||
|
// bind the index variable if present (for i, v in arr)
|
||||||
|
prev_idx := if st.idx_target.len > 0 { g.locals[st.idx_target] or { -1 } } else { -1 }
|
||||||
|
prev_idx_t := if st.idx_target.len > 0 { g.types[st.idx_target] or { '' } } else { '' }
|
||||||
|
if st.idx_target.len > 0 {
|
||||||
|
g.locals[st.idx_target] = idx_idx
|
||||||
|
g.types.delete(st.idx_target)
|
||||||
|
}
|
||||||
for s in st.body {
|
for s in st.body {
|
||||||
g.gen_stmt(s)!
|
g.gen_stmt(s)!
|
||||||
}
|
}
|
||||||
|
if st.idx_target.len > 0 {
|
||||||
|
if prev_idx >= 0 {
|
||||||
|
g.locals[st.idx_target] = prev_idx
|
||||||
|
} else {
|
||||||
|
g.locals.delete(st.idx_target)
|
||||||
|
}
|
||||||
|
if prev_idx_t.len > 0 {
|
||||||
|
g.types[st.idx_target] = prev_idx_t
|
||||||
|
}
|
||||||
|
}
|
||||||
if prev >= 0 {
|
if prev >= 0 {
|
||||||
g.locals[st.target] = prev
|
g.locals[st.target] = prev
|
||||||
} else {
|
} else {
|
||||||
@@ -403,6 +498,44 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
|
|||||||
g.code << obj.encode_i64(0)
|
g.code << obj.encode_i64(0)
|
||||||
g.fixups << Fixup{ name: ctx.continue_l, off: u32(g.code.len) - 8 }
|
g.fixups << Fixup{ name: ctx.continue_l, off: u32(g.code.len) - 8 }
|
||||||
}
|
}
|
||||||
|
.throw_stmt {
|
||||||
|
g.gen_expr(st.expr)!
|
||||||
|
g.code << op_throw
|
||||||
|
}
|
||||||
|
.try_stmt {
|
||||||
|
catch_l := g.new_label()
|
||||||
|
end_l := g.new_label()
|
||||||
|
err_idx := g.new_local()
|
||||||
|
g.code << op_try
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
g.fixups << Fixup{ name: catch_l, off: u32(g.code.len) - 8 }
|
||||||
|
for s in st.body {
|
||||||
|
g.gen_stmt(s)!
|
||||||
|
}
|
||||||
|
g.code << op_catch_done
|
||||||
|
g.code << op_jmp
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
g.fixups << Fixup{ name: end_l, off: u32(g.code.len) - 8 }
|
||||||
|
g.emit_label(catch_l)
|
||||||
|
g.code << op_store
|
||||||
|
g.code << obj.encode_i64(i64(err_idx))
|
||||||
|
prev := g.locals[st.target] or { -1 }
|
||||||
|
prev_t := g.types[st.target] or { '' }
|
||||||
|
g.locals[st.target] = err_idx
|
||||||
|
g.types.delete(st.target)
|
||||||
|
for s in st.els {
|
||||||
|
g.gen_stmt(s)!
|
||||||
|
}
|
||||||
|
if prev >= 0 {
|
||||||
|
g.locals[st.target] = prev
|
||||||
|
} else {
|
||||||
|
g.locals.delete(st.target)
|
||||||
|
}
|
||||||
|
if prev_t.len > 0 {
|
||||||
|
g.types[st.target] = prev_t
|
||||||
|
}
|
||||||
|
g.emit_label(end_l)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,6 +545,10 @@ fn (mut g Gen) gen_expr(e Expr) ! {
|
|||||||
g.code << op_push_i
|
g.code << op_push_i
|
||||||
g.code << obj.encode_i64(e.int_v)
|
g.code << obj.encode_i64(e.int_v)
|
||||||
}
|
}
|
||||||
|
.float_lit {
|
||||||
|
g.code << op_push_f
|
||||||
|
g.code << obj.encode_f64(e.float_v)
|
||||||
|
}
|
||||||
.str_lit {
|
.str_lit {
|
||||||
// the index is a placeholder; the linker rebases it via a string
|
// the index is a placeholder; the linker rebases it via a string
|
||||||
// relocation so multi-file links keep working
|
// relocation so multi-file links keep working
|
||||||
@@ -468,7 +605,21 @@ fn (mut g Gen) gen_expr(e Expr) ! {
|
|||||||
}
|
}
|
||||||
.method_call {
|
.method_call {
|
||||||
// p.dist(x) → call <Type>.dist p, x
|
// p.dist(x) → call <Type>.dist p, x
|
||||||
recv_t := g.method_receiver_type(e)!
|
recv_t := g.method_receiver_type(e)
|
||||||
|
// string methods: s.len(), s.to_upper(), s.contains(x), ... —
|
||||||
|
// the receiver type is known when it is a literal or a local that
|
||||||
|
// was assigned a string literal
|
||||||
|
if recv_t == 'string' || e.left.kind == .str_lit {
|
||||||
|
g.gen_expr(*e.left)!
|
||||||
|
for a in e.args {
|
||||||
|
g.gen_expr(a)!
|
||||||
|
}
|
||||||
|
g.code << op_str_method
|
||||||
|
g.code << obj.encode_i64(0) // name placeholder — rebased by the linker
|
||||||
|
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: e.name, kind: 1 }
|
||||||
|
g.code << obj.encode_i64(i64(e.args.len))
|
||||||
|
return
|
||||||
|
}
|
||||||
// built-in: enum.to_string() generates a match on the integer value
|
// built-in: enum.to_string() generates a match on the integer value
|
||||||
if e.name == 'to_string' && recv_t in g.enums && e.args.len == 0 {
|
if e.name == 'to_string' && recv_t in g.enums && e.args.len == 0 {
|
||||||
g.gen_enum_to_string(recv_t, *e.left, e.line)!
|
g.gen_enum_to_string(recv_t, *e.left, e.line)!
|
||||||
@@ -487,11 +638,24 @@ fn (mut g Gen) gen_expr(e Expr) ! {
|
|||||||
for a in e.args {
|
for a in e.args {
|
||||||
g.gen_expr(a)!
|
g.gen_expr(a)!
|
||||||
}
|
}
|
||||||
g.code << op_call
|
// if receiver type is known, emit a static method call
|
||||||
g.code << obj.encode_i64(0) // placeholder — patched by the linker
|
if recv_t.len > 0 {
|
||||||
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: '${recv_t}.${e.name}', kind: 0 }
|
g.code << op_call
|
||||||
g.code << obj.encode_i64(i64(e.args.len + 1)) // receiver + args
|
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
|
||||||
|
} else {
|
||||||
|
// unknown type: treat as closure call on a struct field
|
||||||
|
g.emit_field_name(e.name)
|
||||||
|
g.code << op_sget
|
||||||
|
for a in e.args {
|
||||||
|
g.gen_expr(a)!
|
||||||
|
}
|
||||||
|
g.code << op_call_closure
|
||||||
|
g.code << obj.encode_i64(i64(e.args.len))
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
.index {
|
.index {
|
||||||
// if the index is a string literal, use struct field access (map style)
|
// if the index is a string literal, use struct field access (map style)
|
||||||
if e.right.kind == .str_lit {
|
if e.right.kind == .str_lit {
|
||||||
@@ -504,6 +668,57 @@ fn (mut g Gen) gen_expr(e Expr) ! {
|
|||||||
g.code << op_aget
|
g.code << op_aget
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.slice {
|
||||||
|
// arr[start..end] → push value, start, end; slice
|
||||||
|
g.gen_expr(*e.left)!
|
||||||
|
g.gen_expr(*e.right)!
|
||||||
|
g.gen_expr(*e.extra)!
|
||||||
|
g.code << op_slice
|
||||||
|
}
|
||||||
|
.anon_fn {
|
||||||
|
g.lam_counter++
|
||||||
|
name := '__lam_${g.lam_counter}'
|
||||||
|
// jump over the lambda body so callers don't fall through
|
||||||
|
g.code << op_jmp
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
skip_fix_off := u32(g.code.len) - 8
|
||||||
|
fd := FnDecl{
|
||||||
|
name: name
|
||||||
|
params: e.fparams
|
||||||
|
defaults: e.fdefaults
|
||||||
|
has_defs: e.fhas_defs
|
||||||
|
variadic: e.fvariadic
|
||||||
|
body: e.fn_body
|
||||||
|
line: e.line
|
||||||
|
}
|
||||||
|
// Save enclosing fixup/label/locals/type state; gen_fn clears them.
|
||||||
|
// enter_off and argc are also per-function, so they must be restored
|
||||||
|
// or the enclosing function's `enter n` patch is lost (locals would
|
||||||
|
// then collide with the stack top).
|
||||||
|
saved_fixups := g.fixups.clone()
|
||||||
|
saved_labels := g.labels.clone()
|
||||||
|
saved_locals := g.locals.clone()
|
||||||
|
saved_types := g.types.clone()
|
||||||
|
saved_local_cnt := g.local_cnt
|
||||||
|
saved_enter_off := g.enter_off
|
||||||
|
saved_argc := g.argc
|
||||||
|
g.labels.clear()
|
||||||
|
g.fixups = []Fixup{}
|
||||||
|
g.gen_fn(fd)!
|
||||||
|
// Restore the enclosing state.
|
||||||
|
g.fixups = saved_fixups
|
||||||
|
g.labels = saved_labels.clone()
|
||||||
|
g.locals = saved_locals.clone()
|
||||||
|
g.types = saved_types.clone()
|
||||||
|
g.local_cnt = saved_local_cnt
|
||||||
|
g.enter_off = saved_enter_off
|
||||||
|
g.argc = saved_argc
|
||||||
|
// Patch the skip jump to land at the closure opcode we emit next.
|
||||||
|
obj.patch_i64(mut g.code, skip_fix_off, i64(g.code.len))
|
||||||
|
g.code << op_closure
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: name, kind: 0 }
|
||||||
|
}
|
||||||
.bool_lit {
|
.bool_lit {
|
||||||
g.code << op_push_i
|
g.code << op_push_i
|
||||||
g.code << obj.encode_i64(e.int_v)
|
g.code << obj.encode_i64(e.int_v)
|
||||||
@@ -526,11 +741,29 @@ fn (mut g Gen) gen_expr(e Expr) ! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.unary {
|
.unary {
|
||||||
|
// constant-fold unary ops on literals: -5, -2.5, not true, ~7
|
||||||
|
if e.right.kind == .int_lit && (e.op == .minus || e.op == .tilde) {
|
||||||
|
v := e.right.int_v
|
||||||
|
res := if e.op == .minus { -v } else { ~v }
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if e.right.kind == .float_lit && e.op == .minus {
|
||||||
|
g.code << op_push_f
|
||||||
|
g.code << obj.encode_f64(-e.right.float_v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if e.right.kind == .bool_lit && e.op == .kw_not {
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(if e.right.int_v == 0 { 1 } else { 0 })
|
||||||
|
return
|
||||||
|
}
|
||||||
g.gen_expr(*e.right)!
|
g.gen_expr(*e.right)!
|
||||||
if e.op == .kw_not {
|
match e.op {
|
||||||
g.code << op_not
|
.kw_not { g.code << op_not }
|
||||||
} else {
|
.tilde { g.code << op_not_b }
|
||||||
g.code << op_neg
|
else { g.code << op_neg }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.binary {
|
.binary {
|
||||||
@@ -594,6 +827,31 @@ fn (mut g Gen) gen_call(e Expr) ! {
|
|||||||
g.code << op_skeys
|
g.code << op_skeys
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// closure call: ident(args) where ident is a local holding a closure
|
||||||
|
if e.name in g.locals {
|
||||||
|
g.gen_expr(Expr{ kind: .ident, name: e.name, line: e.line })!
|
||||||
|
g.code << op_dup // separate the closure copy from the local slot
|
||||||
|
for a in e.args {
|
||||||
|
g.gen_expr(a)!
|
||||||
|
}
|
||||||
|
g.code << op_call_closure
|
||||||
|
g.code << obj.encode_i64(i64(e.args.len))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// host builtins (file I/O, OS, math, collections) go through op_native
|
||||||
|
bid, bargc := builtin_spec(e.name)
|
||||||
|
if bid >= 0 {
|
||||||
|
if e.args.len != bargc {
|
||||||
|
return error('${e.name}() takes exactly ${bargc} argument(s) (line ${e.line})')
|
||||||
|
}
|
||||||
|
for a in e.args {
|
||||||
|
g.gen_expr(a)!
|
||||||
|
}
|
||||||
|
g.code << op_native
|
||||||
|
g.code << obj.encode_i64(i64(bid))
|
||||||
|
g.code << obj.encode_i64(i64(bargc))
|
||||||
|
return
|
||||||
|
}
|
||||||
for a in e.args {
|
for a in e.args {
|
||||||
g.gen_expr(a)!
|
g.gen_expr(a)!
|
||||||
}
|
}
|
||||||
@@ -603,7 +861,169 @@ fn (mut g Gen) gen_call(e Expr) ! {
|
|||||||
g.code << obj.encode_i64(i64(e.args.len)) // argc
|
g.code << obj.encode_i64(i64(e.args.len)) // argc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// builtin_spec maps a builtin function name to its (native id, arg count).
|
||||||
|
// A negative id means the name is not a builtin (it is a user function).
|
||||||
|
fn builtin_spec(name string) (int, int) {
|
||||||
|
return match name {
|
||||||
|
'abs' { native_abs, 1 }
|
||||||
|
'min' { native_min, 2 }
|
||||||
|
'max' { native_max, 2 }
|
||||||
|
'pow' { native_pow, 2 }
|
||||||
|
'sqrt' { native_sqrt, 1 }
|
||||||
|
'floor' { native_floor, 1 }
|
||||||
|
'ceil' { native_ceil, 1 }
|
||||||
|
'round' { native_round, 1 }
|
||||||
|
'rand' { native_rand, 0 }
|
||||||
|
'rand_int' { native_rand_int, 1 }
|
||||||
|
'int' { native_int, 1 }
|
||||||
|
'str' { native_str, 1 }
|
||||||
|
'float' { native_float, 1 }
|
||||||
|
'type' { native_type, 1 }
|
||||||
|
'split' { native_split, 2 }
|
||||||
|
'join' { native_join, 2 }
|
||||||
|
'contains' { native_contains, 2 }
|
||||||
|
'starts_with' { native_starts_with, 2 }
|
||||||
|
'ends_with' { native_ends_with, 2 }
|
||||||
|
'trim' { native_trim, 1 }
|
||||||
|
'lower' { native_lower, 1 }
|
||||||
|
'upper' { native_upper, 1 }
|
||||||
|
'pop' { native_pop, 1 }
|
||||||
|
'insert' { native_insert, 3 }
|
||||||
|
'remove' { native_remove, 2 }
|
||||||
|
'sort' { native_sort, 1 }
|
||||||
|
'clone' { native_clone, 1 }
|
||||||
|
'reverse' { native_reverse, 1 }
|
||||||
|
'index_of' { native_index_of, 2 }
|
||||||
|
'args' { native_args, 0 }
|
||||||
|
'getenv' { native_getenv, 1 }
|
||||||
|
'setenv' { native_setenv, 2 }
|
||||||
|
'exit' { native_exit, 1 }
|
||||||
|
'time' { native_time, 0 }
|
||||||
|
'sleep' { native_sleep, 1 }
|
||||||
|
'read_file' { native_read_file, 1 }
|
||||||
|
'write_file' { native_write_file, 2 }
|
||||||
|
'eprint' { native_eprint, 1 }
|
||||||
|
else { -1, 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fold_binary constant-folds binary expressions whose operands are both
|
||||||
|
// literals, emitting the precomputed constant. Returns false when the
|
||||||
|
// expression cannot be folded (leaving it to the runtime). Division/modulo by
|
||||||
|
// zero and out-of-range shifts are deliberately not folded so the runtime
|
||||||
|
// still reports them.
|
||||||
|
fn (mut g Gen) fold_binary(e Expr) bool {
|
||||||
|
// integer folding
|
||||||
|
if e.left.kind == .int_lit && e.right.kind == .int_lit {
|
||||||
|
l := e.left.int_v
|
||||||
|
r := e.right.int_v
|
||||||
|
mut res := i64(0)
|
||||||
|
match e.op {
|
||||||
|
.plus { res = l + r }
|
||||||
|
.minus { res = l - r }
|
||||||
|
.star { res = l * r }
|
||||||
|
.slash {
|
||||||
|
if r == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
res = l / r
|
||||||
|
}
|
||||||
|
.percent {
|
||||||
|
if r == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
res = l % r
|
||||||
|
}
|
||||||
|
.amp { res = l & r }
|
||||||
|
.pipe { res = l | r }
|
||||||
|
.caret { res = l ^ r }
|
||||||
|
.lt_lt {
|
||||||
|
if r < 0 || r > 63 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
res = l << u32(r)
|
||||||
|
}
|
||||||
|
.gt_gt {
|
||||||
|
if r < 0 || r > 63 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
res = l >> u32(r)
|
||||||
|
}
|
||||||
|
.eq_eq { res = if l == r { 1 } else { 0 } }
|
||||||
|
.not_eq { res = if l != r { 1 } else { 0 } }
|
||||||
|
.lt { res = if l < r { 1 } else { 0 } }
|
||||||
|
.le { res = if l <= r { 1 } else { 0 } }
|
||||||
|
.gt { res = if l > r { 1 } else { 0 } }
|
||||||
|
.ge { res = if l >= r { 1 } else { 0 } }
|
||||||
|
else { return false }
|
||||||
|
}
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(res)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// float folding
|
||||||
|
if e.left.kind == .float_lit && e.right.kind == .float_lit {
|
||||||
|
l := e.left.float_v
|
||||||
|
r := e.right.float_v
|
||||||
|
mut res := 0.0
|
||||||
|
mut is_bool := false
|
||||||
|
mut bres := false
|
||||||
|
match e.op {
|
||||||
|
.plus { res = l + r }
|
||||||
|
.minus { res = l - r }
|
||||||
|
.star { res = l * r }
|
||||||
|
.slash {
|
||||||
|
if r == 0.0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
res = l / r
|
||||||
|
}
|
||||||
|
.eq_eq { is_bool = true; bres = l == r }
|
||||||
|
.not_eq { is_bool = true; bres = l != r }
|
||||||
|
.lt { is_bool = true; bres = l < r }
|
||||||
|
.le { is_bool = true; bres = l <= r }
|
||||||
|
.gt { is_bool = true; bres = l > r }
|
||||||
|
.ge { is_bool = true; bres = l >= r }
|
||||||
|
else { return false }
|
||||||
|
}
|
||||||
|
if is_bool {
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(if bres { 1 } else { 0 })
|
||||||
|
} else {
|
||||||
|
g.code << op_push_f
|
||||||
|
g.code << obj.encode_f64(res)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// string concatenation folding: "a" + "b" → one interned constant.
|
||||||
|
// The string is emitted as a relocation so the linker interns it in the
|
||||||
|
// final table, exactly like a plain string literal.
|
||||||
|
if e.left.kind == .str_lit && e.right.kind == .str_lit && e.op == .plus {
|
||||||
|
g.code << op_push_s
|
||||||
|
g.code << obj.encode_i64(0) // placeholder — rebased by the linker
|
||||||
|
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: e.left.str_v + e.right.str_v, kind: 1 }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// boolean short-circuit folding: only when both sides are bool literals
|
||||||
|
if e.left.kind == .bool_lit && e.right.kind == .bool_lit {
|
||||||
|
if e.op == .kw_and {
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(if e.left.int_v != 0 && e.right.int_v != 0 { 1 } else { 0 })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if e.op == .kw_or {
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(if e.left.int_v != 0 || e.right.int_v != 0 { 1 } else { 0 })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut g Gen) gen_binary(e Expr) ! {
|
fn (mut g Gen) gen_binary(e Expr) ! {
|
||||||
|
if g.fold_binary(e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
match e.op {
|
match e.op {
|
||||||
.kw_and {
|
.kw_and {
|
||||||
// a and b → short-circuit: if !a or !b then 0 else 1
|
// a and b → short-circuit: if !a or !b then 0 else 1
|
||||||
@@ -664,6 +1084,11 @@ fn (mut g Gen) gen_binary(e Expr) ! {
|
|||||||
.le { op_le }
|
.le { op_le }
|
||||||
.gt { op_gt }
|
.gt { op_gt }
|
||||||
.ge { op_ge }
|
.ge { op_ge }
|
||||||
|
.amp { op_and_b }
|
||||||
|
.pipe { op_or_b }
|
||||||
|
.caret { op_xor }
|
||||||
|
.lt_lt { op_shl }
|
||||||
|
.gt_gt { op_shr }
|
||||||
else {
|
else {
|
||||||
return error('unsupported binary operator at line ${e.line}')
|
return error('unsupported binary operator at line ${e.line}')
|
||||||
}
|
}
|
||||||
@@ -678,6 +1103,9 @@ fn (mut g Gen) gen_binary(e Expr) ! {
|
|||||||
// variable, or an enum variant `Enum.variant`. Everything else has no
|
// variable, or an enum variant `Enum.variant`. Everything else has no
|
||||||
// known type ('').
|
// known type ('').
|
||||||
fn (mut g Gen) expr_type(e Expr) string {
|
fn (mut g Gen) expr_type(e Expr) string {
|
||||||
|
if e.kind == .str_lit {
|
||||||
|
return 'string'
|
||||||
|
}
|
||||||
if e.kind == .struct_lit {
|
if e.kind == .struct_lit {
|
||||||
return e.name
|
return e.name
|
||||||
}
|
}
|
||||||
@@ -691,14 +1119,28 @@ fn (mut g Gen) expr_type(e Expr) string {
|
|||||||
return e.left.name
|
return e.left.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// slicing or indexing a known string yields a string
|
||||||
|
if (e.kind == .slice || e.kind == .index) && g.expr_type(*e.left) == 'string' {
|
||||||
|
return 'string'
|
||||||
|
}
|
||||||
|
// string concatenation: "a" + "b" (or anything + a string literal)
|
||||||
|
if e.kind == .binary && e.op == .plus && (e.left.kind == .str_lit || e.right.kind == .str_lit) {
|
||||||
|
return 'string'
|
||||||
|
}
|
||||||
|
// string-producing builtins typed as strings so method chains keep working
|
||||||
|
if e.kind == .call {
|
||||||
|
return match e.name {
|
||||||
|
'upper', 'lower', 'trim', 'str', 'getenv', 'read_file', 'join' { 'string' }
|
||||||
|
else { '' }
|
||||||
|
}
|
||||||
|
}
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// method_receiver_type resolves the struct type a method call is made on.
|
// 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
|
// Returns '' when the type is statically unknown (at which point the
|
||||||
// (from a typed literal, an assignment, or a method receiver binding)
|
// call becomes a dynamic closure invocation via field access).
|
||||||
// or an enum variant expression (e.g. Color.red).
|
fn (mut g Gen) method_receiver_type(e Expr) string {
|
||||||
fn (mut g Gen) method_receiver_type(e Expr) !string {
|
|
||||||
recv := e.left
|
recv := e.left
|
||||||
if recv.kind == .ident {
|
if recv.kind == .ident {
|
||||||
t := g.types[recv.name] or { '' }
|
t := g.types[recv.name] or { '' }
|
||||||
@@ -706,14 +1148,14 @@ fn (mut g Gen) method_receiver_type(e Expr) !string {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// enum variant: Color.red → type is "Color"
|
// enum variant: Color.red -> type is "Color"
|
||||||
if recv.kind == .field && recv.left.kind == .ident {
|
if recv.kind == .field && recv.left.kind == .ident {
|
||||||
key := '${recv.left.name}.${recv.name}'
|
key := '${recv.left.name}.${recv.name}'
|
||||||
if key in g.enum_vals {
|
if key in g.enum_vals {
|
||||||
return recv.left.name
|
return recv.left.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return error('cannot resolve method "${e.name}": receiver type unknown (line ${e.line})')
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// gen_enum_to_string generates bytecode for `e.to_string()` on an enum value.
|
// gen_enum_to_string generates bytecode for `e.to_string()` on an enum value.
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import obj
|
|||||||
pub fn compile(src string) !obj.Obj {
|
pub fn compile(src string) !obj.Obj {
|
||||||
toks := tokenize(src)!
|
toks := tokenize(src)!
|
||||||
prog := parse(toks)!
|
prog := parse(toks)!
|
||||||
|
// conservative compile-time type check; catches provable errors early
|
||||||
|
check(prog)!
|
||||||
return gen(prog)
|
return gen(prog)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,3 +17,18 @@ pub fn compile_file(path string) !obj.Obj {
|
|||||||
src := os.read_file(path)!
|
src := os.read_file(path)!
|
||||||
return compile(src)!
|
return compile(src)!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolve_import turns an import path into a readable source file. It tries
|
||||||
|
// the path as given first, then falls back to the package-manager layout so
|
||||||
|
// `import "pkg/file.vr"` finds vendor/pkg/file.vr.
|
||||||
|
pub fn resolve_import(path string) !string {
|
||||||
|
if os.exists(path) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
for cand in ['vendor/${path}', 'vendor/${path}.vr', 'vendor/${path}/main.vr', 'vendor/${path}/src/main.vr'] {
|
||||||
|
if os.exists(cand) {
|
||||||
|
return cand
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return error('cannot resolve import "${path}" (tried vendor/)"')
|
||||||
|
}
|
||||||
|
|||||||
+115
-45
@@ -2,7 +2,7 @@
|
|||||||
module compiler
|
module compiler
|
||||||
|
|
||||||
pub fn tokenize(src string) ![]Tok {
|
pub fn tokenize(src string) ![]Tok {
|
||||||
mut l := Lexer{ src: src }
|
mut l := Lexer{ src: src, line: 1 }
|
||||||
mut toks := []Tok{}
|
mut toks := []Tok{}
|
||||||
for {
|
for {
|
||||||
t := l.next()!
|
t := l.next()!
|
||||||
@@ -16,9 +16,10 @@ pub fn tokenize(src string) ![]Tok {
|
|||||||
|
|
||||||
struct Lexer {
|
struct Lexer {
|
||||||
mut:
|
mut:
|
||||||
src string
|
src string
|
||||||
pos int
|
pos int
|
||||||
line int
|
line int
|
||||||
|
line_start int // byte offset where the current line begins
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut l Lexer) peek() u8 {
|
fn (mut l Lexer) peek() u8 {
|
||||||
@@ -40,6 +41,7 @@ fn (mut l Lexer) advance() u8 {
|
|||||||
l.pos++
|
l.pos++
|
||||||
if c == `\n` {
|
if c == `\n` {
|
||||||
l.line++
|
l.line++
|
||||||
|
l.line_start = l.pos
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
@@ -61,34 +63,35 @@ fn (mut l Lexer) next() !Tok {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
line := l.line
|
line := l.line
|
||||||
|
col := l.pos - l.line_start + 1 // 1-based column
|
||||||
if l.pos >= l.src.len {
|
if l.pos >= l.src.len {
|
||||||
return Tok{ kind: .eof, lit: '', line: line }
|
return Tok{ kind: .eof, lit: '', line: line, col: col }
|
||||||
}
|
}
|
||||||
c := l.peek()
|
c := l.peek()
|
||||||
match c {
|
match c {
|
||||||
`(` {
|
`(` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .lparen, lit: '(', line: line }
|
return Tok{ kind: .lparen, lit: '(', line: line, col: col }
|
||||||
}
|
}
|
||||||
`)` {
|
`)` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .rparen, lit: ')', line: line }
|
return Tok{ kind: .rparen, lit: ')', line: line, col: col }
|
||||||
}
|
}
|
||||||
`{` {
|
`{` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .lbrace, lit: '{', line: line }
|
return Tok{ kind: .lbrace, lit: '{', line: line, col: col }
|
||||||
}
|
}
|
||||||
`}` {
|
`}` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .rbrace, lit: '}', line: line }
|
return Tok{ kind: .rbrace, lit: '}', line: line, col: col }
|
||||||
}
|
}
|
||||||
`[` {
|
`[` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .lbracket, lit: '[', line: line }
|
return Tok{ kind: .lbracket, lit: '[', line: line, col: col }
|
||||||
}
|
}
|
||||||
`]` {
|
`]` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .rbracket, lit: ']', line: line }
|
return Tok{ kind: .rbracket, lit: ']', line: line, col: col }
|
||||||
}
|
}
|
||||||
`.` {
|
`.` {
|
||||||
l.advance()
|
l.advance()
|
||||||
@@ -96,112 +99,165 @@ fn (mut l Lexer) next() !Tok {
|
|||||||
l.advance()
|
l.advance()
|
||||||
if l.peek() == `.` {
|
if l.peek() == `.` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .dotdotdot, lit: '...', line: line }
|
return Tok{ kind: .dotdotdot, lit: '...', line: line, col: col }
|
||||||
}
|
}
|
||||||
return Tok{ kind: .dotdot, lit: '..', line: line }
|
return Tok{ kind: .dotdot, lit: '..', line: line, col: col }
|
||||||
}
|
}
|
||||||
return Tok{ kind: .dot, lit: '.', line: line }
|
return Tok{ kind: .dot, lit: '.', line: line, col: col }
|
||||||
}
|
}
|
||||||
`,` {
|
`,` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .comma, lit: ',', line: line }
|
return Tok{ kind: .comma, lit: ',', line: line, col: col }
|
||||||
}
|
}
|
||||||
`:` {
|
`:` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .colon, lit: ':', line: line }
|
return Tok{ kind: .colon, lit: ':', line: line, col: col }
|
||||||
}
|
}
|
||||||
`+` {
|
`+` {
|
||||||
l.advance()
|
l.advance()
|
||||||
if l.peek() == `=` {
|
if l.peek() == `=` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .plus_eq, lit: '+=', line: line }
|
return Tok{ kind: .plus_eq, lit: '+=', line: line, col: col }
|
||||||
}
|
}
|
||||||
return Tok{ kind: .plus, lit: '+', line: line }
|
return Tok{ kind: .plus, lit: '+', line: line, col: col }
|
||||||
}
|
}
|
||||||
`-` {
|
`-` {
|
||||||
l.advance()
|
l.advance()
|
||||||
if l.peek() == `=` {
|
if l.peek() == `=` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .minus_eq, lit: '-=', line: line }
|
return Tok{ kind: .minus_eq, lit: '-=', line: line, col: col }
|
||||||
}
|
}
|
||||||
return Tok{ kind: .minus, lit: '-', line: line }
|
return Tok{ kind: .minus, lit: '-', line: line, col: col }
|
||||||
}
|
}
|
||||||
`*` {
|
`*` {
|
||||||
l.advance()
|
l.advance()
|
||||||
if l.peek() == `=` {
|
if l.peek() == `=` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .star_eq, lit: '*=', line: line }
|
return Tok{ kind: .star_eq, lit: '*=', line: line, col: col }
|
||||||
}
|
}
|
||||||
return Tok{ kind: .star, lit: '*', line: line }
|
return Tok{ kind: .star, lit: '*', line: line, col: col }
|
||||||
}
|
}
|
||||||
`/` {
|
`/` {
|
||||||
l.advance()
|
l.advance()
|
||||||
if l.peek() == `=` {
|
if l.peek() == `=` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .slash_eq, lit: '/=', line: line }
|
return Tok{ kind: .slash_eq, lit: '/=', line: line, col: col }
|
||||||
}
|
}
|
||||||
return Tok{ kind: .slash, lit: '/', line: line }
|
return Tok{ kind: .slash, lit: '/', line: line, col: col }
|
||||||
}
|
}
|
||||||
`%` {
|
`%` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .percent, lit: '%', line: line }
|
return Tok{ kind: .percent, lit: '%', line: line, col: col }
|
||||||
|
}
|
||||||
|
`&` {
|
||||||
|
l.advance()
|
||||||
|
return Tok{ kind: .amp, lit: '&', line: line, col: col }
|
||||||
|
}
|
||||||
|
`|` {
|
||||||
|
l.advance()
|
||||||
|
return Tok{ kind: .pipe, lit: '|', line: line, col: col }
|
||||||
|
}
|
||||||
|
`^` {
|
||||||
|
l.advance()
|
||||||
|
return Tok{ kind: .caret, lit: '^', line: line, col: col }
|
||||||
|
}
|
||||||
|
`~` {
|
||||||
|
l.advance()
|
||||||
|
return Tok{ kind: .tilde, lit: '~', line: line, col: col }
|
||||||
}
|
}
|
||||||
`=` {
|
`=` {
|
||||||
l.advance()
|
l.advance()
|
||||||
if l.peek() == `=` {
|
if l.peek() == `=` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .eq_eq, lit: '==', line: line }
|
return Tok{ kind: .eq_eq, lit: '==', line: line, col: col }
|
||||||
}
|
}
|
||||||
return Tok{ kind: .assign, lit: '=', line: line }
|
return Tok{ kind: .assign, lit: '=', line: line, col: col }
|
||||||
}
|
}
|
||||||
`!` {
|
`!` {
|
||||||
l.advance()
|
l.advance()
|
||||||
if l.peek() == `=` {
|
if l.peek() == `=` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .not_eq, lit: '!=', line: line }
|
return Tok{ kind: .not_eq, lit: '!=', line: line, col: col }
|
||||||
}
|
}
|
||||||
return error('unexpected character "!" at line ${line} (did you mean "not"?)')
|
return error('unexpected character "!" at line ${line}, col ${col} (did you mean "not"?)')
|
||||||
}
|
}
|
||||||
`<` {
|
`<` {
|
||||||
l.advance()
|
l.advance()
|
||||||
if l.peek() == `=` {
|
if l.peek() == `=` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .le, lit: '<=', line: line }
|
return Tok{ kind: .le, lit: '<=', line: line, col: col }
|
||||||
}
|
}
|
||||||
return Tok{ kind: .lt, lit: '<', line: line }
|
if l.peek() == `<` {
|
||||||
|
l.advance()
|
||||||
|
return Tok{ kind: .lt_lt, lit: '<<', line: line, col: col }
|
||||||
|
}
|
||||||
|
return Tok{ kind: .lt, lit: '<', line: line, col: col }
|
||||||
}
|
}
|
||||||
`>` {
|
`>` {
|
||||||
l.advance()
|
l.advance()
|
||||||
if l.peek() == `=` {
|
if l.peek() == `=` {
|
||||||
l.advance()
|
l.advance()
|
||||||
return Tok{ kind: .ge, lit: '>=', line: line }
|
return Tok{ kind: .ge, lit: '>=', line: line, col: col }
|
||||||
}
|
}
|
||||||
return Tok{ kind: .gt, lit: '>', line: line }
|
if l.peek() == `>` {
|
||||||
|
l.advance()
|
||||||
|
return Tok{ kind: .gt_gt, lit: '>>', line: line, col: col }
|
||||||
|
}
|
||||||
|
return Tok{ kind: .gt, lit: '>', line: line, col: col }
|
||||||
}
|
}
|
||||||
`\"` {
|
`\"` {
|
||||||
return l.lex_string(line)!
|
return l.lex_string(line, col)!
|
||||||
}
|
}
|
||||||
`0`...`9` {
|
`0`...`9` {
|
||||||
return l.lex_number(line)
|
return l.lex_number(line, col)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) || c == `_` {
|
if (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) || c == `_` {
|
||||||
return l.lex_ident(line)
|
return l.lex_ident(line, col)
|
||||||
}
|
}
|
||||||
return error('unexpected character "${c.ascii_str()}" at line ${line}')
|
return error('unexpected character "${c.ascii_str()}" at line ${line}, col ${col}')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut l Lexer) lex_number(line int) Tok {
|
fn (mut l Lexer) lex_number(line int, col int) Tok {
|
||||||
start := l.pos
|
start := l.pos
|
||||||
for l.pos < l.src.len && l.peek() >= `0` && l.peek() <= `9` {
|
for l.pos < l.src.len && l.peek() >= `0` && l.peek() <= `9` {
|
||||||
l.advance()
|
l.advance()
|
||||||
}
|
}
|
||||||
return Tok{ kind: .int_lit, lit: l.src[start..l.pos], line: line }
|
mut is_float := false
|
||||||
|
// fractional part: `.` followed by a digit (so `1..3` and `1...3` stay ints)
|
||||||
|
if l.peek() == `.` && l.pos + 1 < l.src.len && l.peek2() >= `0` && l.peek2() <= `9` {
|
||||||
|
is_float = true
|
||||||
|
l.advance() // consume `.`
|
||||||
|
for l.pos < l.src.len && l.peek() >= `0` && l.peek() <= `9` {
|
||||||
|
l.advance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// exponent part: e / E followed by optional sign and digits
|
||||||
|
if l.peek() == `e` || l.peek() == `E` {
|
||||||
|
save := l.pos
|
||||||
|
l.advance()
|
||||||
|
if l.peek() == `+` || l.peek() == `-` {
|
||||||
|
l.advance()
|
||||||
|
}
|
||||||
|
if l.peek() >= `0` && l.peek() <= `9` {
|
||||||
|
is_float = true
|
||||||
|
for l.pos < l.src.len && l.peek() >= `0` && l.peek() <= `9` {
|
||||||
|
l.advance()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
l.pos = save // not an exponent after all
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lit := l.src[start..l.pos]
|
||||||
|
if is_float {
|
||||||
|
return Tok{ kind: .float_lit, lit: lit, line: line, col: col }
|
||||||
|
}
|
||||||
|
return Tok{ kind: .int_lit, lit: lit, line: line, col: col }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut l Lexer) lex_ident(line int) Tok {
|
fn (mut l Lexer) lex_ident(line int, col int) Tok {
|
||||||
start := l.pos
|
start := l.pos
|
||||||
for l.pos < l.src.len {
|
for l.pos < l.src.len {
|
||||||
c := l.peek()
|
c := l.peek()
|
||||||
@@ -236,18 +292,26 @@ fn (mut l Lexer) lex_ident(line int) Tok {
|
|||||||
'import' { TokKind.kw_import }
|
'import' { TokKind.kw_import }
|
||||||
'enum' { TokKind.kw_enum }
|
'enum' { TokKind.kw_enum }
|
||||||
'const' { TokKind.kw_const }
|
'const' { TokKind.kw_const }
|
||||||
|
'interface' { TokKind.kw_interface }
|
||||||
|
'try' { TokKind.kw_try }
|
||||||
|
'catch' { TokKind.kw_catch }
|
||||||
|
'throw' { TokKind.kw_throw }
|
||||||
else { TokKind.ident }
|
else { TokKind.ident }
|
||||||
}
|
}
|
||||||
return Tok{ kind: kind, lit: lit, line: line }
|
return Tok{ kind: kind, lit: lit, line: line, col: col }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut l Lexer) lex_string(line int) !Tok {
|
fn (mut l Lexer) lex_string(line int, col int) !Tok {
|
||||||
l.advance() // opening quote
|
l.advance() // opening quote
|
||||||
mut s := ''
|
mut s := ''
|
||||||
|
mut has_interp := false
|
||||||
for l.pos < l.src.len {
|
for l.pos < l.src.len {
|
||||||
c := l.advance()
|
c := l.advance()
|
||||||
if c == `\"` {
|
if c == `\"` {
|
||||||
return Tok{ kind: .str_lit, lit: s, line: line }
|
if has_interp {
|
||||||
|
return Tok{ kind: .str_interp, lit: s, line: line, col: col }
|
||||||
|
}
|
||||||
|
return Tok{ kind: .str_lit, lit: s, line: line, col: col }
|
||||||
}
|
}
|
||||||
if c == `\\` {
|
if c == `\\` {
|
||||||
if l.pos >= l.src.len {
|
if l.pos >= l.src.len {
|
||||||
@@ -268,12 +332,18 @@ fn (mut l Lexer) lex_string(line int) !Tok {
|
|||||||
s += '\\'
|
s += '\\'
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return error('invalid escape \\${e.ascii_str()} at line ${line}')
|
return error('invalid escape \\${e.ascii_str()} at line ${line}, col ${col}')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if c == `$` && l.peek() == `{` {
|
||||||
|
has_interp = true
|
||||||
|
s += '\${'
|
||||||
|
l.advance() // skip the '{'
|
||||||
|
continue
|
||||||
|
}
|
||||||
s += c.ascii_str()
|
s += c.ascii_str()
|
||||||
}
|
}
|
||||||
return error('unterminated string at line ${line}')
|
return error('unterminated string at line ${line}, col ${col}')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,3 +47,61 @@ const op_shas = u8(40)
|
|||||||
const op_sdel = u8(41)
|
const op_sdel = u8(41)
|
||||||
const op_slen = u8(42)
|
const op_slen = u8(42)
|
||||||
const op_skeys = u8(43)
|
const op_skeys = u8(43)
|
||||||
|
const op_slice = u8(44)
|
||||||
|
const op_push_f = u8(45)
|
||||||
|
const op_native = u8(46)
|
||||||
|
const op_and_b = u8(47)
|
||||||
|
const op_or_b = u8(48)
|
||||||
|
const op_xor = u8(49)
|
||||||
|
const op_shl = u8(50)
|
||||||
|
const op_shr = u8(51)
|
||||||
|
const op_not_b = u8(52)
|
||||||
|
const op_try = u8(53)
|
||||||
|
const op_throw = u8(54)
|
||||||
|
const op_catch_done = u8(55)
|
||||||
|
const op_closure = u8(56)
|
||||||
|
const op_call_closure = u8(57)
|
||||||
|
const op_argc = u8(58) // push the current frame's arg count
|
||||||
|
const op_load_dyn = u8(59) // pop idx, push stack[bp + idx]
|
||||||
|
const op_varargs = u8(60) // <named:i64> <dst:i64> — collect args[named..argc-1] into an array at local dst
|
||||||
|
const op_str_method = u8(61) // <name:str> <argc:i64> — call a string method (s.len(), s.contains(x), ...)
|
||||||
|
|
||||||
|
// native builtin ids (keep in sync with vm/opcodes.v)
|
||||||
|
const native_abs = 100
|
||||||
|
const native_min = 101
|
||||||
|
const native_max = 102
|
||||||
|
const native_pow = 103
|
||||||
|
const native_sqrt = 104
|
||||||
|
const native_floor = 105
|
||||||
|
const native_ceil = 106
|
||||||
|
const native_round = 107
|
||||||
|
const native_rand = 108
|
||||||
|
const native_rand_int = 109
|
||||||
|
const native_int = 110
|
||||||
|
const native_str = 111
|
||||||
|
const native_float = 112
|
||||||
|
const native_type = 113
|
||||||
|
const native_split = 114
|
||||||
|
const native_join = 115
|
||||||
|
const native_contains = 116
|
||||||
|
const native_starts_with = 117
|
||||||
|
const native_ends_with = 118
|
||||||
|
const native_trim = 119
|
||||||
|
const native_lower = 120
|
||||||
|
const native_upper = 121
|
||||||
|
const native_pop = 122
|
||||||
|
const native_insert = 123
|
||||||
|
const native_remove = 124
|
||||||
|
const native_sort = 125
|
||||||
|
const native_clone = 126
|
||||||
|
const native_reverse = 127
|
||||||
|
const native_index_of = 128
|
||||||
|
const native_args = 129
|
||||||
|
const native_getenv = 130
|
||||||
|
const native_setenv = 131
|
||||||
|
const native_exit = 132
|
||||||
|
const native_time = 133
|
||||||
|
const native_sleep = 134
|
||||||
|
const native_read_file = 135
|
||||||
|
const native_write_file = 136
|
||||||
|
const native_eprint = 137
|
||||||
|
|||||||
+394
-23
@@ -64,7 +64,7 @@ fn (mut p Parser) advance() Tok {
|
|||||||
fn (mut p Parser) expect(k TokKind, what string) !Tok {
|
fn (mut p Parser) expect(k TokKind, what string) !Tok {
|
||||||
t := p.cur()
|
t := p.cur()
|
||||||
if t.kind != k {
|
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()
|
return p.advance()
|
||||||
}
|
}
|
||||||
@@ -92,8 +92,9 @@ fn (mut p Parser) parse_program() !Program {
|
|||||||
.kw_struct { prog.structs << p.parse_struct_decl()! }
|
.kw_struct { prog.structs << p.parse_struct_decl()! }
|
||||||
.kw_enum { prog.enums << p.parse_enum_decl()! }
|
.kw_enum { prog.enums << p.parse_enum_decl()! }
|
||||||
.kw_const { prog.consts << p.parse_const_decl()! }
|
.kw_const { prog.consts << p.parse_const_decl()! }
|
||||||
|
.kw_interface { prog.interfaces << p.parse_interface_decl()! }
|
||||||
.kw_fn { prog.fns << p.parse_fn()! }
|
.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 {
|
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 }
|
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) { }`.
|
// 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'")!
|
||||||
@@ -176,18 +213,76 @@ fn (mut p Parser) parse_fn() !FnDecl {
|
|||||||
p.expect(.rparen, "')'")!
|
p.expect(.rparen, "')'")!
|
||||||
}
|
}
|
||||||
name := p.expect(.ident, 'function name')!
|
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()!
|
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.
|
// parse_params parses `(a, b, c = expr, rest...)` — the parameter list of a
|
||||||
fn (mut p Parser) parse_params() ![]string {
|
// 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, "'('")!
|
p.expect(.lparen, "'('")!
|
||||||
mut params := []string{}
|
mut params := []string{}
|
||||||
|
mut defaults := []Expr{}
|
||||||
|
mut has_defs := []bool{}
|
||||||
|
mut variadic := false
|
||||||
if p.cur().kind != .rparen {
|
if p.cur().kind != .rparen {
|
||||||
for {
|
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 {
|
if p.cur().kind == .comma {
|
||||||
p.advance()
|
p.advance()
|
||||||
continue
|
continue
|
||||||
@@ -196,7 +291,7 @@ fn (mut p Parser) parse_params() ![]string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.expect(.rparen, "')'")!
|
p.expect(.rparen, "')'")!
|
||||||
return params
|
return params, defaults, has_defs, variadic
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut p Parser) parse_block() ![]Stmt {
|
fn (mut p Parser) parse_block() ![]Stmt {
|
||||||
@@ -217,6 +312,30 @@ fn (mut p Parser) parse_stmt() !Stmt {
|
|||||||
match t.kind {
|
match t.kind {
|
||||||
.kw_let {
|
.kw_let {
|
||||||
p.advance()
|
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')!
|
name := p.expect(.ident, 'variable name')!
|
||||||
p.expect(.assign, "'='")!
|
p.expect(.assign, "'='")!
|
||||||
e := p.parse_expr()!
|
e := p.parse_expr()!
|
||||||
@@ -257,6 +376,14 @@ fn (mut p Parser) parse_stmt() !Stmt {
|
|||||||
.kw_for {
|
.kw_for {
|
||||||
p.advance()
|
p.advance()
|
||||||
name := p.expect(.ident, 'loop variable')!
|
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'")!
|
p.expect(.kw_in, "'in'")!
|
||||||
first := p.parse_expr()!
|
first := p.parse_expr()!
|
||||||
if p.cur().kind == .dotdot || p.cur().kind == .dotdotdot {
|
if p.cur().kind == .dotdot || p.cur().kind == .dotdotdot {
|
||||||
@@ -264,10 +391,10 @@ fn (mut p Parser) parse_stmt() !Stmt {
|
|||||||
p.advance()
|
p.advance()
|
||||||
end := p.parse_expr()!
|
end := p.parse_expr()!
|
||||||
body := p.parse_block()!
|
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()!
|
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 {
|
.kw_return {
|
||||||
p.advance()
|
p.advance()
|
||||||
@@ -297,13 +424,29 @@ fn (mut p Parser) parse_stmt() !Stmt {
|
|||||||
}
|
}
|
||||||
return Stmt{ kind: .assert_stmt, expr: e, line: t.line }
|
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 {
|
.ident {
|
||||||
p.advance()
|
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{}
|
mut e := Expr{}
|
||||||
if p.cur().kind == .lparen {
|
if p.cur().kind == .lparen {
|
||||||
// a call statement: foo(args), optionally chained foo().x
|
// 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)!
|
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 {
|
} else {
|
||||||
e = p.parse_postfix_tail(Expr{ kind: .ident, name: t.lit, line: t.line })!
|
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 {
|
.kw_print, .kw_println {
|
||||||
p.advance()
|
p.advance()
|
||||||
e := p.parse_call(t)!
|
e := p.parse_call(t, [])!
|
||||||
return Stmt{ kind: .expr_stmt, expr: e, line: t.line }
|
return Stmt{ kind: .expr_stmt, expr: e, line: t.line }
|
||||||
}
|
}
|
||||||
else {
|
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 }
|
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
|
// 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
|
// so that `&l`/`&r` target fresh heap objects (taking the address of a local
|
||||||
// that is later reassigned would create a self-referential node).
|
// 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 }
|
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`.
|
// field_node builds `base.name`.
|
||||||
fn field_node(base Expr, name string, line int) Expr {
|
fn field_node(base Expr, name string, line int) Expr {
|
||||||
mut b := base
|
mut b := base
|
||||||
@@ -436,8 +603,41 @@ fn (mut p Parser) parse_or() !Expr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn (mut p Parser) parse_and() !Expr {
|
fn (mut p Parser) parse_and() !Expr {
|
||||||
mut e := p.parse_eq()!
|
mut e := p.parse_bitor()!
|
||||||
for p.cur().kind == .kw_and {
|
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()
|
op := p.advance()
|
||||||
rhs := p.parse_eq()!
|
rhs := p.parse_eq()!
|
||||||
e = bin_node(op.kind, e, rhs, op.line)
|
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 {
|
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 {
|
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()
|
op := p.advance()
|
||||||
rhs := p.parse_add()!
|
rhs := p.parse_add()!
|
||||||
e = bin_node(op.kind, e, rhs, op.line)
|
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 {
|
fn (mut p Parser) parse_unary() !Expr {
|
||||||
t := p.cur()
|
t := p.cur()
|
||||||
if t.kind == .kw_not || t.kind == .minus {
|
if t.kind == .kw_not || t.kind == .minus || t.kind == .tilde {
|
||||||
p.advance()
|
p.advance()
|
||||||
e := p.parse_unary()!
|
e := p.parse_unary()!
|
||||||
return unary_node(t.kind, e, t.line)
|
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 {
|
if p.cur().kind == .lbracket {
|
||||||
p.advance()
|
p.advance()
|
||||||
idx := p.parse_expr()!
|
idx := p.parse_expr()!
|
||||||
p.expect(.rbracket, "']'")!
|
if p.cur().kind == .dotdot || p.cur().kind == .dotdotdot {
|
||||||
cur = index_node(cur, idx, cur.line)
|
// 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
|
continue
|
||||||
}
|
}
|
||||||
if p.cur().kind == .dot {
|
if p.cur().kind == .dot {
|
||||||
@@ -542,10 +767,18 @@ fn (mut p Parser) parse_primary() !Expr {
|
|||||||
p.advance()
|
p.advance()
|
||||||
return Expr{ kind: .int_lit, int_v: t.lit.i64(), line: t.line }
|
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 {
|
.str_lit {
|
||||||
p.advance()
|
p.advance()
|
||||||
return Expr{ kind: .str_lit, str_v: t.lit, line: t.line }
|
return Expr{ kind: .str_lit, str_v: t.lit, line: t.line }
|
||||||
}
|
}
|
||||||
|
.str_interp {
|
||||||
|
p.advance()
|
||||||
|
return p.parse_str_interp(t)!
|
||||||
|
}
|
||||||
.kw_true {
|
.kw_true {
|
||||||
p.advance()
|
p.advance()
|
||||||
return Expr{ kind: .bool_lit, int_v: 1, line: t.line }
|
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 {
|
.kw_print, .kw_println {
|
||||||
p.advance()
|
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 {
|
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 {
|
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 {
|
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 }
|
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()!
|
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.
|
// 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" :`
|
// 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
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ pub enum TokKind {
|
|||||||
eof
|
eof
|
||||||
ident
|
ident
|
||||||
int_lit
|
int_lit
|
||||||
|
float_lit
|
||||||
str_lit
|
str_lit
|
||||||
|
str_interp
|
||||||
lparen
|
lparen
|
||||||
rparen
|
rparen
|
||||||
lbrace
|
lbrace
|
||||||
@@ -20,6 +22,12 @@ pub enum TokKind {
|
|||||||
star
|
star
|
||||||
slash
|
slash
|
||||||
percent
|
percent
|
||||||
|
amp
|
||||||
|
pipe
|
||||||
|
caret
|
||||||
|
tilde
|
||||||
|
lt_lt
|
||||||
|
gt_gt
|
||||||
eq_eq
|
eq_eq
|
||||||
not_eq
|
not_eq
|
||||||
lt
|
lt
|
||||||
@@ -56,6 +64,10 @@ pub enum TokKind {
|
|||||||
kw_import
|
kw_import
|
||||||
kw_enum
|
kw_enum
|
||||||
kw_const
|
kw_const
|
||||||
|
kw_interface
|
||||||
|
kw_try
|
||||||
|
kw_catch
|
||||||
|
kw_throw
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Tok {
|
pub struct Tok {
|
||||||
@@ -63,4 +75,5 @@ pub:
|
|||||||
kind TokKind
|
kind TokKind
|
||||||
lit string
|
lit string
|
||||||
line int
|
line int
|
||||||
|
col int // 1-based column within the line
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// for i, v in arr — iterate with both index and value
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let fruits = ["apple", "banana", "cherry"]
|
||||||
|
|
||||||
|
// basic indexed iteration
|
||||||
|
for i, v in fruits {
|
||||||
|
println("${i}: ${v}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// use the index for computation
|
||||||
|
let nums = [10, 20, 30, 40, 50]
|
||||||
|
let sum = 0
|
||||||
|
for i, v in nums {
|
||||||
|
if i % 2 == 0 {
|
||||||
|
sum += v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("sum of even-indexed elements: ${sum}")
|
||||||
|
|
||||||
|
// build a new array using index
|
||||||
|
let letters = ["a", "b", "c", "d"]
|
||||||
|
let tagged = []
|
||||||
|
for i, v in letters {
|
||||||
|
push(tagged, "${v}${i}")
|
||||||
|
}
|
||||||
|
println(tagged)
|
||||||
|
|
||||||
|
// plain for-in still works (no index)
|
||||||
|
for v in fruits {
|
||||||
|
print(v)
|
||||||
|
print(" ")
|
||||||
|
}
|
||||||
|
println("")
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// generic function syntax (V-style type parameters)
|
||||||
|
|
||||||
|
// generic functions: type params are parsed but the VM handles types dynamically
|
||||||
|
fn first[T](arr) {
|
||||||
|
return arr[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last[T](arr) {
|
||||||
|
let n = len(arr)
|
||||||
|
return arr[n - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// works with integers
|
||||||
|
let nums = [10, 20, 30]
|
||||||
|
println("first num: ${first[int](nums)}")
|
||||||
|
println("last num: ${last[int](nums)}")
|
||||||
|
|
||||||
|
// works with strings
|
||||||
|
let words = ["hello", "world", "foo"]
|
||||||
|
println("first word: ${first[string](words)}")
|
||||||
|
println("last word: ${last[string](words)}")
|
||||||
|
|
||||||
|
// type args are optional (VM is dynamically typed)
|
||||||
|
println("first (no type arg): ${first(nums)}")
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// string interpolation: embed expressions inside "..." with ${}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let name = "alice"
|
||||||
|
let age = 30
|
||||||
|
|
||||||
|
// simple variable interpolation
|
||||||
|
println("hello ${name}!")
|
||||||
|
|
||||||
|
// multiple interpolations
|
||||||
|
println("${name} is ${age} years old")
|
||||||
|
|
||||||
|
// expression inside interpolation
|
||||||
|
println("${name} will be ${age + 1} next year")
|
||||||
|
|
||||||
|
// interpolation with function calls
|
||||||
|
let nums = [10, 20, 30]
|
||||||
|
println("first element: ${nums[0]}")
|
||||||
|
|
||||||
|
// string concatenation with interpolation
|
||||||
|
let greeting = "hi"
|
||||||
|
println("${greeting}, ${name}! age=${age}")
|
||||||
|
|
||||||
|
// no interpolation (regular string)
|
||||||
|
println("no interpolation here")
|
||||||
|
|
||||||
|
// empty interpolation adjacent to text
|
||||||
|
let x = 42
|
||||||
|
println("value is ${x}")
|
||||||
|
|
||||||
|
// arithmetic in interpolation
|
||||||
|
println("double age: ${age * 2}")
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// array and string slicing: arr[1..3], arr[1:], str[0..5]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// --- array slicing ---
|
||||||
|
let nums = [10, 20, 30, 40, 50]
|
||||||
|
|
||||||
|
// basic slice: arr[start..end]
|
||||||
|
let mid = nums[1..3]
|
||||||
|
println(mid) // [20, 30]
|
||||||
|
|
||||||
|
// slice from start
|
||||||
|
let first_two = nums[0..2]
|
||||||
|
println(first_two) // [10, 20]
|
||||||
|
|
||||||
|
// slice to end (open-ended)
|
||||||
|
let tail = nums[2..]
|
||||||
|
println(tail) // [30, 40, 50]
|
||||||
|
|
||||||
|
// single element slice
|
||||||
|
let single = nums[3..4]
|
||||||
|
println(single) // [40]
|
||||||
|
|
||||||
|
// --- string slicing ---
|
||||||
|
let greeting = "hello, world!"
|
||||||
|
|
||||||
|
// basic substring
|
||||||
|
let hello = greeting[0..5]
|
||||||
|
println(hello) // "hello"
|
||||||
|
|
||||||
|
// slice from index
|
||||||
|
let world = greeting[7..]
|
||||||
|
println(world) // "world!"
|
||||||
|
|
||||||
|
// slice to index
|
||||||
|
let hello_comma = greeting[0..6]
|
||||||
|
println(hello_comma) // "hello,"
|
||||||
|
|
||||||
|
// --- slicing in expressions ---
|
||||||
|
let data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
let first = data[0..3]
|
||||||
|
let last = data[7..]
|
||||||
|
println(first)
|
||||||
|
println(last)
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
// fmt.v — `vr fmt`: a source formatter for VuurRaaf.
|
||||||
|
//
|
||||||
|
// Works line-by-line: it fixes indentation from brace depth, trims stray
|
||||||
|
// whitespace, collapses space runs outside string literals, and preserves
|
||||||
|
// every comment (whole-line and inline). It never reorders or rewrites code.
|
||||||
|
//
|
||||||
|
// vr fmt file.vr print the formatted source to stdout
|
||||||
|
// vr fmt -w file.vr rewrite the file in place
|
||||||
|
module main
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
fn toolchain_fmt(args []string) ! {
|
||||||
|
if args.len == 0 {
|
||||||
|
return error('usage: vr fmt [-w] <file.vr>')
|
||||||
|
}
|
||||||
|
mut write := false
|
||||||
|
mut files := []string{}
|
||||||
|
for a in args {
|
||||||
|
if a == '-w' || a == '--write' {
|
||||||
|
write = true
|
||||||
|
} else {
|
||||||
|
files << a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if files.len == 0 {
|
||||||
|
return error('usage: vr fmt [-w] <file.vr>')
|
||||||
|
}
|
||||||
|
for f in files {
|
||||||
|
src := os.read_file(f) or { return error('cannot read "${f}": ${err.msg()}') }
|
||||||
|
formatted := fmt_source(src)
|
||||||
|
if write {
|
||||||
|
os.write_file(f, formatted) or { return error('cannot write "${f}": ${err.msg()}') }
|
||||||
|
println('formatted ${f}')
|
||||||
|
} else {
|
||||||
|
print(formatted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt_source normalizes a whole source file. Comment lines are indented with
|
||||||
|
// the surrounding depth; closing braces drop one level like the code does.
|
||||||
|
fn fmt_source(src string) string {
|
||||||
|
mut out := ''
|
||||||
|
mut depth := 0
|
||||||
|
for raw in src.split_into_lines() {
|
||||||
|
line := raw.trim_space()
|
||||||
|
if line == '' {
|
||||||
|
out += '\n'
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d := brace_delta(line)
|
||||||
|
mut indent := depth
|
||||||
|
if line.starts_with('}') && depth > 0 {
|
||||||
|
indent = depth - 1
|
||||||
|
}
|
||||||
|
out += '\t'.repeat(indent) + collapse_spaces(line) + '\n'
|
||||||
|
depth += d
|
||||||
|
if depth < 0 {
|
||||||
|
depth = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// collapse_spaces replaces runs of spaces outside of string literals with a
|
||||||
|
// single space, so indentation and padding become canonical without touching
|
||||||
|
// string contents.
|
||||||
|
fn collapse_spaces(line string) string {
|
||||||
|
mut out := ''
|
||||||
|
mut in_str := false
|
||||||
|
mut i := 0
|
||||||
|
for i < line.len {
|
||||||
|
c := line[i]
|
||||||
|
if c == `"` {
|
||||||
|
in_str = !in_str
|
||||||
|
out += c.ascii_str()
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if in_str && c == `\\` {
|
||||||
|
out += c.ascii_str()
|
||||||
|
i++
|
||||||
|
if i < line.len {
|
||||||
|
out += line[i].ascii_str()
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !in_str && c == ` ` {
|
||||||
|
for i < line.len && line[i] == ` ` {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
out += ' '
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out += c.ascii_str()
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// brace_delta is shared with the REPL (repl.v): it tokenizes the line so
|
||||||
|
// braces inside string literals do not affect depth.
|
||||||
+5
-1
@@ -15,6 +15,7 @@ pub fn link(paths []string, out string) ! {
|
|||||||
mut strings := []string{}
|
mut strings := []string{}
|
||||||
mut symbols := map[string]int{}
|
mut symbols := map[string]int{}
|
||||||
mut relocs := []obj.Reloc{}
|
mut relocs := []obj.Reloc{}
|
||||||
|
mut lines := []obj.LineInfo{}
|
||||||
for p in paths {
|
for p in paths {
|
||||||
o := obj.read(p)!
|
o := obj.read(p)!
|
||||||
base := code.len
|
base := code.len
|
||||||
@@ -28,6 +29,9 @@ pub fn link(paths []string, out string) ! {
|
|||||||
for r in o.relocs {
|
for r in o.relocs {
|
||||||
relocs << obj.Reloc{ offset: r.offset + u32(base), name: r.name, kind: r.kind }
|
relocs << obj.Reloc{ offset: r.offset + u32(base), name: r.name, kind: r.kind }
|
||||||
}
|
}
|
||||||
|
for l in o.lines {
|
||||||
|
lines << obj.LineInfo{ off: l.off + u32(base), line: l.line }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// resolve relocations
|
// resolve relocations
|
||||||
for r in relocs {
|
for r in relocs {
|
||||||
@@ -47,7 +51,7 @@ pub fn link(paths []string, out string) ! {
|
|||||||
for name, entry in symbols {
|
for name, entry in symbols {
|
||||||
fns << obj.BinFn{ name: name, entry: entry }
|
fns << obj.BinFn{ name: name, entry: entry }
|
||||||
}
|
}
|
||||||
obj.write_bin(out, obj.Bin{ fns: fns, strings: strings, code: code })!
|
obj.write_bin(out, obj.Bin{ fns: fns, strings: strings, code: code, lines: lines })!
|
||||||
}
|
}
|
||||||
|
|
||||||
fn intern_str(mut table []string, s string) int {
|
fn intern_str(mut table []string, s string) int {
|
||||||
|
|||||||
@@ -70,6 +70,24 @@ fn main() {
|
|||||||
'bench', 'b' {
|
'bench', 'b' {
|
||||||
toolchain_bench(rest) or { die('bench', err) }
|
toolchain_bench(rest) or { die('bench', err) }
|
||||||
}
|
}
|
||||||
|
'repl', 'i' {
|
||||||
|
toolchain_repl() or { die('repl', err) }
|
||||||
|
}
|
||||||
|
'fmt' {
|
||||||
|
toolchain_fmt(rest) or { die('fmt', err) }
|
||||||
|
}
|
||||||
|
'init' {
|
||||||
|
toolchain_init(rest) or { die('init', err) }
|
||||||
|
}
|
||||||
|
'get' {
|
||||||
|
toolchain_get(rest) or { die('get', err) }
|
||||||
|
}
|
||||||
|
'install' {
|
||||||
|
toolchain_install() or { die('install', err) }
|
||||||
|
}
|
||||||
|
'list' {
|
||||||
|
toolchain_list() or { die('list', err) }
|
||||||
|
}
|
||||||
'clean' {
|
'clean' {
|
||||||
toolchain_clean()
|
toolchain_clean()
|
||||||
}
|
}
|
||||||
@@ -153,6 +171,12 @@ fn toolchain_help() {
|
|||||||
println(' debug <file.vr|file.vbin> run with an instruction trace')
|
println(' debug <file.vr|file.vbin> run with an instruction trace')
|
||||||
println(' test <file.vr> run every test_* function')
|
println(' test <file.vr> run every test_* function')
|
||||||
println(' bench <file.vr> [iterations] benchmark main()')
|
println(' bench <file.vr> [iterations] benchmark main()')
|
||||||
|
println(' repl interactive session')
|
||||||
|
println(' fmt [-w] <file.vr> format source')
|
||||||
|
println(' init [name] scaffold a project')
|
||||||
|
println(' get <owner/repo | url | ./path> fetch a package into vendor/')
|
||||||
|
println(' install install deps from vr.mod')
|
||||||
|
println(' list show the project manifest')
|
||||||
println(' clean remove build artifacts')
|
println(' clean remove build artifacts')
|
||||||
println(' up rebuild the vr binary into bin/')
|
println(' up rebuild the vr binary into bin/')
|
||||||
println(' symlink link bin/vr into your PATH')
|
println(' symlink link bin/vr into your PATH')
|
||||||
@@ -311,16 +335,17 @@ fn toolchain_link(args []string) ! {
|
|||||||
|
|
||||||
fn toolchain_run(args []string) ! {
|
fn toolchain_run(args []string) ! {
|
||||||
if args.len == 0 {
|
if args.len == 0 {
|
||||||
return error('usage: vr run <file.vr|file.vbin>')
|
return error('usage: vr run <file.vr|file.vbin> [program-args...]')
|
||||||
}
|
}
|
||||||
f := args[0]
|
f := args[0]
|
||||||
|
prog_args := args[1..]
|
||||||
if f.ends_with('.vbin') {
|
if f.ends_with('.vbin') {
|
||||||
bin := obj.read_bin(f)!
|
bin := obj.read_bin(f)!
|
||||||
vm.run(bin, 'main', false)!
|
vm.run_with_args(bin, 'main', false, prog_args)!
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if f.ends_with('.vr') {
|
if f.ends_with('.vr') {
|
||||||
run_src(f, 'main', false)!
|
run_src_with_args(f, 'main', false, prog_args)!
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return error('unsupported file type: ${f} (expected .vr or .vbin)')
|
return error('unsupported file type: ${f} (expected .vr or .vbin)')
|
||||||
@@ -423,6 +448,10 @@ fn toolchain_bench(args []string) ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_src(src string, entry string, trace bool) ! {
|
fn run_src(src string, entry string, trace bool) ! {
|
||||||
|
run_src_with_args(src, entry, trace, []string{})!
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_src_with_args(src string, entry string, trace bool, args []string) ! {
|
||||||
tmp_obj := os.join_path(os.temp_dir(), 'vr_${os.getpid()}.vobj')
|
tmp_obj := os.join_path(os.temp_dir(), 'vr_${os.getpid()}.vobj')
|
||||||
tmp_bin := os.join_path(os.temp_dir(), 'vr_${os.getpid()}.vbin')
|
tmp_bin := os.join_path(os.temp_dir(), 'vr_${os.getpid()}.vbin')
|
||||||
defer {
|
defer {
|
||||||
@@ -433,7 +462,7 @@ fn run_src(src string, entry string, trace bool) ! {
|
|||||||
obj.write(tmp_obj, o)!
|
obj.write(tmp_obj, o)!
|
||||||
linker.link([tmp_obj], tmp_bin)!
|
linker.link([tmp_obj], tmp_bin)!
|
||||||
bin := obj.read_bin(tmp_bin)!
|
bin := obj.read_bin(tmp_bin)!
|
||||||
vm.run(bin, entry, trace)!
|
vm.run_with_args(bin, entry, trace, args)!
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
module obj
|
module obj
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import math
|
||||||
|
|
||||||
pub const magic = 'VROBJ'
|
pub const magic = 'VROBJ'
|
||||||
pub const bin_magic = 'VRBIN'
|
pub const bin_magic = 'VRBIN'
|
||||||
@@ -36,6 +37,7 @@ pub mut:
|
|||||||
strings []string
|
strings []string
|
||||||
code []u8
|
code []u8
|
||||||
relocs []Reloc
|
relocs []Reloc
|
||||||
|
lines []LineInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BinFn {
|
pub struct BinFn {
|
||||||
@@ -44,11 +46,20 @@ pub mut:
|
|||||||
entry int
|
entry int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LineInfo maps a code offset to the source line it was generated from,
|
||||||
|
// enabling source-level locations in runtime errors.
|
||||||
|
pub struct LineInfo {
|
||||||
|
pub mut:
|
||||||
|
off u32
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Bin {
|
pub struct Bin {
|
||||||
pub mut:
|
pub mut:
|
||||||
fns []BinFn
|
fns []BinFn
|
||||||
strings []string
|
strings []string
|
||||||
code []u8
|
code []u8
|
||||||
|
lines []LineInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -66,6 +77,16 @@ pub fn encode_i64(v i64) []u8 {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// encode_f64 writes a little-endian f64 (its IEEE-754 bit pattern).
|
||||||
|
pub fn encode_f64(v f64) []u8 {
|
||||||
|
bits := math.f64_bits(v)
|
||||||
|
mut b := []u8{}
|
||||||
|
for i in 0..8 {
|
||||||
|
b << u8((bits >> (8 * i)) & 0xff)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
// patch_i64 writes a little-endian i64 over `code[off..off+8]`.
|
// patch_i64 writes a little-endian i64 over `code[off..off+8]`.
|
||||||
pub fn patch_i64(mut code []u8, off u32, v i64) {
|
pub fn patch_i64(mut code []u8, off u32, v i64) {
|
||||||
for i in 0..8 {
|
for i in 0..8 {
|
||||||
@@ -146,6 +167,11 @@ pub fn write(path string, o Obj) ! {
|
|||||||
b << r.name.bytes()
|
b << r.name.bytes()
|
||||||
b << r.kind
|
b << r.kind
|
||||||
}
|
}
|
||||||
|
b << encode_u32(u32(o.lines.len))
|
||||||
|
for l in o.lines {
|
||||||
|
b << encode_u32(l.off)
|
||||||
|
b << encode_i64(i64(l.line))
|
||||||
|
}
|
||||||
os.write_bytes(path, b)!
|
os.write_bytes(path, b)!
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,6 +206,12 @@ pub fn read(path string) !Obj {
|
|||||||
kind := r.u8_()!
|
kind := r.u8_()!
|
||||||
o.relocs << Reloc{ offset: off, name: name, kind: kind }
|
o.relocs << Reloc{ offset: off, name: name, kind: kind }
|
||||||
}
|
}
|
||||||
|
nlines := int(r.u32_()!)
|
||||||
|
for _ in 0..nlines {
|
||||||
|
off := r.u32_()!
|
||||||
|
line := int(r.i64_()!)
|
||||||
|
o.lines << LineInfo{ off: off, line: line }
|
||||||
|
}
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +235,11 @@ pub fn write_bin(path string, bin Bin) ! {
|
|||||||
}
|
}
|
||||||
b << encode_u32(u32(bin.code.len))
|
b << encode_u32(u32(bin.code.len))
|
||||||
b << bin.code
|
b << bin.code
|
||||||
|
b << encode_u32(u32(bin.lines.len))
|
||||||
|
for l in bin.lines {
|
||||||
|
b << encode_u32(l.off)
|
||||||
|
b << encode_i64(i64(l.line))
|
||||||
|
}
|
||||||
os.write_bytes(path, b)!
|
os.write_bytes(path, b)!
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,5 +266,12 @@ pub fn read_bin(path string) !Bin {
|
|||||||
return error('executable file truncated')
|
return error('executable file truncated')
|
||||||
}
|
}
|
||||||
bin.code = b[r.pos..r.pos + ncode]
|
bin.code = b[r.pos..r.pos + ncode]
|
||||||
|
r.pos += ncode
|
||||||
|
nlines := int(r.u32_()!)
|
||||||
|
for _ in 0..nlines {
|
||||||
|
off := r.u32_()!
|
||||||
|
line := int(r.i64_()!)
|
||||||
|
bin.lines << LineInfo{ off: off, line: line }
|
||||||
|
}
|
||||||
return bin
|
return bin
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
// pkg.v — `vr init` / `vr get` / `vr install`: a minimal package manager.
|
||||||
|
//
|
||||||
|
// Packages are fetched into ./vendor/<name>/ and recorded in vr.mod as
|
||||||
|
// `dep "<spec>"`. The compiler resolves `import "pkg/file"` against vendor/
|
||||||
|
// automatically (see compiler/compiler.v). Supported package specs:
|
||||||
|
//
|
||||||
|
// vr get owner/repo clone from GitHub (https://github.com/owner/repo)
|
||||||
|
// vr get https://host/repo clone any git repository
|
||||||
|
// vr get ./local/path copy a local directory
|
||||||
|
module main
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
const manifest_file = 'vr.mod'
|
||||||
|
const vendor_dir = 'vendor'
|
||||||
|
|
||||||
|
fn toolchain_init(args []string) ! {
|
||||||
|
pkg := if args.len > 0 { args[0] } else { os.base(os.getwd()) }
|
||||||
|
if os.exists(manifest_file) {
|
||||||
|
return error('${manifest_file} already exists here')
|
||||||
|
}
|
||||||
|
os.write_file(manifest_file, 'module ${pkg}\nversion 0.1.0\n\n# deps:\n# dep "owner/repo"\n')!
|
||||||
|
os.mkdir('vendor') or {}
|
||||||
|
if !os.exists('main.vr') {
|
||||||
|
os.write_file('main.vr', 'fn main() {\n\tprintln("hello from ${pkg}")\n}\n')!
|
||||||
|
}
|
||||||
|
println('initialized project "${pkg}" (${manifest_file}, main.vr)')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toolchain_get(args []string) ! {
|
||||||
|
if args.len == 0 {
|
||||||
|
return error('usage: vr get <owner/repo | git-url | ./path>')
|
||||||
|
}
|
||||||
|
spec := args[0]
|
||||||
|
// determine the package name and how to fetch it
|
||||||
|
mut pkg := ''
|
||||||
|
if spec.starts_with('./') || spec.starts_with('/') {
|
||||||
|
pkg = os.base(spec)
|
||||||
|
} else if spec.contains('://') {
|
||||||
|
pkg = os.base(spec)
|
||||||
|
} else if spec.contains('/') {
|
||||||
|
pkg = spec.all_after('/')
|
||||||
|
} else {
|
||||||
|
pkg = spec
|
||||||
|
}
|
||||||
|
if pkg.ends_with('.git') {
|
||||||
|
pkg = pkg[..pkg.len - 4]
|
||||||
|
}
|
||||||
|
if pkg == '' || pkg == '.' {
|
||||||
|
return error('cannot derive a package name from "${spec}"')
|
||||||
|
}
|
||||||
|
os.mkdir(vendor_dir) or {}
|
||||||
|
dst := os.join_path(vendor_dir, pkg)
|
||||||
|
if os.exists(dst) {
|
||||||
|
return error('package "${pkg}" already exists at ${dst}')
|
||||||
|
}
|
||||||
|
if spec.starts_with('./') || spec.starts_with('/') {
|
||||||
|
copy_dir(spec, dst) or { return error('cannot copy ${spec}: ${err.msg()}') }
|
||||||
|
} else {
|
||||||
|
url := if spec.contains('://') { spec } else { 'https://github.com/${spec}.git' }
|
||||||
|
println('cloning ${url} -> ${dst} ...')
|
||||||
|
r := os.exec(['git', 'clone', '--depth', '1', url, dst])
|
||||||
|
if r.exit_code != 0 {
|
||||||
|
return error('git clone failed: ${r.output}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// record the dependency in the manifest (idempotent)
|
||||||
|
if os.exists(manifest_file) {
|
||||||
|
manifest := os.read_file(manifest_file) or { '' }
|
||||||
|
if !manifest.contains('dep "${spec}"') {
|
||||||
|
os.write_file(manifest_file, manifest.trim_space() + '\ndep "${spec}"\n') or {
|
||||||
|
return error('cannot update ${manifest_file}: ${err.msg()}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println('added package "${pkg}" (${spec})')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toolchain_install() ! {
|
||||||
|
if !os.exists(manifest_file) {
|
||||||
|
return error('no ${manifest_file} found — run `vr init` first')
|
||||||
|
}
|
||||||
|
manifest := os.read_file(manifest_file)!
|
||||||
|
mut n := 0
|
||||||
|
for line in manifest.split_into_lines() {
|
||||||
|
t := line.trim_space()
|
||||||
|
if !t.starts_with('dep "') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mut spec := t[5..]
|
||||||
|
if spec.ends_with('"') {
|
||||||
|
spec = spec[..spec.len - 1]
|
||||||
|
}
|
||||||
|
if spec == '' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
toolchain_get([spec]) or { eprintln(' install: ${err.msg()}') }
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
println('no dependencies in ${manifest_file}')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
println('installed ${n} package(s) into ${vendor_dir}/')
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy_dir recursively copies a local directory tree.
|
||||||
|
fn copy_dir(src string, dst string) ! {
|
||||||
|
if !os.is_dir(src) {
|
||||||
|
return error('${src} is not a directory')
|
||||||
|
}
|
||||||
|
os.mkdir_all(dst) or { return error('cannot create ${dst}: ${err.msg()}') }
|
||||||
|
for entry in os.ls(src) or { return error('cannot read ${src}: ${err.msg()}') } {
|
||||||
|
sp := os.join_path(src, entry)
|
||||||
|
dp := os.join_path(dst, entry)
|
||||||
|
if os.is_dir(sp) {
|
||||||
|
copy_dir(sp, dp)!
|
||||||
|
} else if os.is_file(sp) {
|
||||||
|
os.cp(sp, dp, os.CopyParams{}) or { return error('cannot copy ${sp}: ${err.msg()}') }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toolchain_list() ! {
|
||||||
|
if !os.exists(manifest_file) {
|
||||||
|
return error('no ${manifest_file} found here')
|
||||||
|
}
|
||||||
|
manifest := os.read_file(manifest_file)!
|
||||||
|
mod_name := manifest.all_before('\n').replace('module ', '').trim_space()
|
||||||
|
println('project: ${mod_name}')
|
||||||
|
mut found := false
|
||||||
|
for line in manifest.split_into_lines() {
|
||||||
|
t := line.trim_space()
|
||||||
|
if t.starts_with('dep "') {
|
||||||
|
found = true
|
||||||
|
mut spec := t[5..]
|
||||||
|
if spec.ends_with('"') {
|
||||||
|
spec = spec[..spec.len - 1]
|
||||||
|
}
|
||||||
|
mut pkg := os.base(spec)
|
||||||
|
if pkg.ends_with('.git') {
|
||||||
|
pkg = pkg[..pkg.len - 4]
|
||||||
|
}
|
||||||
|
status := if os.exists(os.join_path(vendor_dir, pkg)) { 'installed' } else { 'missing' }
|
||||||
|
println(' ${pkg} (${status}) <- ${spec}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
println(' (no dependencies)')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
// repl.v — an interactive read-eval-print loop for VuurRaaf.
|
||||||
|
//
|
||||||
|
// The REPL keeps two growing buffers: top-level declarations (fn / struct /
|
||||||
|
// enum / const / import / interface) and persistent statements (let, assert,
|
||||||
|
// assignment). Every other line — expressions, println, if/while/for blocks,
|
||||||
|
// match, try — is transient: it is evaluated once against the accumulated
|
||||||
|
// state and its output is not repeated on later lines. Expressions are
|
||||||
|
// echoed like Python's REPL (1 + 1 prints 2); statement keywords run for
|
||||||
|
// their side effects.
|
||||||
|
//
|
||||||
|
// > let x = 5
|
||||||
|
// > x * 2
|
||||||
|
// 10
|
||||||
|
// > fn square(n) { return n * n }
|
||||||
|
// > square(9)
|
||||||
|
// 81
|
||||||
|
//
|
||||||
|
// Multi-line input is gathered while braces are unbalanced:
|
||||||
|
//
|
||||||
|
// > fn fib(n) {
|
||||||
|
// ... if n < 2 { return n }
|
||||||
|
// ... return fib(n - 1) + fib(n - 2)
|
||||||
|
// ... }
|
||||||
|
module main
|
||||||
|
|
||||||
|
import os
|
||||||
|
import obj
|
||||||
|
import compiler
|
||||||
|
import linker
|
||||||
|
import vm
|
||||||
|
|
||||||
|
fn toolchain_repl() ! {
|
||||||
|
println('VuurRaaf ${version} — type :help for commands, :quit to exit')
|
||||||
|
mut top := '' // fn / struct / enum / const / import declarations
|
||||||
|
mut body := '' // persistent statements (let / assert / assignment)
|
||||||
|
mut pending := '' // accumulated multi-line input
|
||||||
|
mut depth := 0 // brace balance of pending input
|
||||||
|
for {
|
||||||
|
prompt := if depth > 0 { '... ' } else { '> ' }
|
||||||
|
line := os.input(prompt)
|
||||||
|
if line == '' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
trimmed := line.trim_space()
|
||||||
|
if trimmed.starts_with(':') && depth == 0 {
|
||||||
|
act := repl_meta(trimmed)
|
||||||
|
if act == 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if act == 2 {
|
||||||
|
top = ''
|
||||||
|
body = ''
|
||||||
|
println('session reset')
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// gather multi-line input until braces balance
|
||||||
|
pending += line + '\n'
|
||||||
|
depth += brace_delta(line)
|
||||||
|
if depth > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
block := pending
|
||||||
|
pending = ''
|
||||||
|
depth = 0
|
||||||
|
match repl_classify(block) {
|
||||||
|
'top' {
|
||||||
|
// fn / struct / enum / const / import — compile-time only
|
||||||
|
candidate := top + '\n' + block
|
||||||
|
if repl_compile(candidate, body, '') {
|
||||||
|
top = candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'body' {
|
||||||
|
candidate := body + '\n' + block
|
||||||
|
if repl_compile(top, candidate, '') {
|
||||||
|
body = candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// transient: run once against the accumulated state
|
||||||
|
tail := if repl_is_expr(block) { 'println(${block.trim_space()})' } else { block }
|
||||||
|
repl_compile(top, body, tail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// repl_meta handles :quit / :reset / :help. Returns 1 to quit, 2 to reset,
|
||||||
|
// and 0 otherwise.
|
||||||
|
fn repl_meta(cmd string) int {
|
||||||
|
match cmd {
|
||||||
|
':q', ':quit', ':exit' {
|
||||||
|
println('bye')
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
':reset' {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
':help', ':h' {
|
||||||
|
println(' :help this help')
|
||||||
|
println(' :quit :exit leave the REPL')
|
||||||
|
println(' :reset clear all definitions')
|
||||||
|
println(' expressions are echoed; let/assign/fn persist')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
eprintln('repl: unknown command "${cmd}" (:help for a list)')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// repl_compile compiles top declarations plus a main body (with an optional
|
||||||
|
// trailing tail statement), links, runs, and reports errors without exiting.
|
||||||
|
// Returns true when the program compiled and ran cleanly.
|
||||||
|
fn repl_compile(top string, body string, tail string) bool {
|
||||||
|
src := top + '\nfn main() {\n' + body + '\n' + tail + '\n}'
|
||||||
|
o := compiler.compile(src) or {
|
||||||
|
eprintln('repl: ${err.msg()}')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
tmp_obj := os.join_path(os.temp_dir(), 'vr_repl_${os.getpid()}.vobj')
|
||||||
|
tmp_bin := os.join_path(os.temp_dir(), 'vr_repl_${os.getpid()}.vbin')
|
||||||
|
defer {
|
||||||
|
os.rm(tmp_obj) or {}
|
||||||
|
os.rm(tmp_bin) or {}
|
||||||
|
}
|
||||||
|
obj.write(tmp_obj, o) or {
|
||||||
|
eprintln('repl: ${err.msg()}')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
linker.link([tmp_obj], tmp_bin) or {
|
||||||
|
eprintln('repl: ${err.msg()}')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
bin := obj.read_bin(tmp_bin) or {
|
||||||
|
eprintln('repl: ${err.msg()}')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
vm.run(bin, 'main', false) or {
|
||||||
|
eprintln('repl: ${err.msg()}')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// repl_classify buckets a (possibly multi-line) input block.
|
||||||
|
fn repl_classify(block string) string {
|
||||||
|
toks := compiler.tokenize(block) or { return 'transient' }
|
||||||
|
if toks.len == 0 {
|
||||||
|
return 'transient'
|
||||||
|
}
|
||||||
|
match toks[0].kind {
|
||||||
|
.kw_fn, .kw_struct, .kw_enum, .kw_const, .kw_import, .kw_interface {
|
||||||
|
return 'top'
|
||||||
|
}
|
||||||
|
.kw_let, .kw_assert {
|
||||||
|
return 'body'
|
||||||
|
}
|
||||||
|
.ident {
|
||||||
|
// assignment persists; anything else is an expression
|
||||||
|
if toks.len >= 2 && toks[1].kind in [.assign, .plus_eq, .minus_eq, .star_eq, .slash_eq] {
|
||||||
|
return 'body'
|
||||||
|
}
|
||||||
|
return 'transient'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'transient'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// repl_is_expr reports whether a transient block is a bare expression that
|
||||||
|
// should be echoed (as opposed to a statement keyword like println or if).
|
||||||
|
fn repl_is_expr(block string) bool {
|
||||||
|
toks := compiler.tokenize(block) or { return false }
|
||||||
|
if toks.len == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
match toks[0].kind {
|
||||||
|
.kw_print, .kw_println, .kw_if, .kw_while, .kw_for, .kw_match, .kw_break, .kw_continue, .kw_return, .kw_try, .kw_throw {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// brace_delta counts the brace balance of a line using the tokenizer, so
|
||||||
|
// braces inside string literals do not confuse continuation detection.
|
||||||
|
fn brace_delta(line string) int {
|
||||||
|
toks := compiler.tokenize(line) or { return 0 }
|
||||||
|
mut d := 0
|
||||||
|
for t in toks {
|
||||||
|
if t.kind == .lbrace {
|
||||||
|
d++
|
||||||
|
} else if t.kind == .rbrace {
|
||||||
|
d--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
// gc.v — mark-and-sweep garbage collector for the VuurRaaf VM.
|
||||||
|
//
|
||||||
|
// The collector runs between opcodes (never mid-instruction, so no live value
|
||||||
|
// is ever hidden in a temporary). Roots are the value stack, which also holds
|
||||||
|
// every frame's locals (they live at bp+idx). Arrays and structs are traced
|
||||||
|
// transitively. The sweep compacts each pool and remaps surviving handles.
|
||||||
|
//
|
||||||
|
// String constants baked into the bytecode (op_push_s operands) live in
|
||||||
|
// strings[0..const_strs] and are never collected; only runtime-allocated
|
||||||
|
// strings participate in the cycle.
|
||||||
|
module vm
|
||||||
|
|
||||||
|
const gc_alloc_trigger = 4096
|
||||||
|
|
||||||
|
// collect marks all heap values reachable from the stack, then sweeps and
|
||||||
|
// compacts the pools, remapping handles on the stack and inside live
|
||||||
|
// containers.
|
||||||
|
fn (mut v Vm) collect() {
|
||||||
|
// ---- mark ----
|
||||||
|
mut str_mark := []bool{len: v.strings.len}
|
||||||
|
mut arr_mark := []bool{len: v.arrays.len}
|
||||||
|
mut struct_mark := []bool{len: v.structs.len}
|
||||||
|
mut float_mark := []bool{len: v.floats.len}
|
||||||
|
mut closure_mark := []bool{len: v.closures.len}
|
||||||
|
for i in 0..v.sp {
|
||||||
|
v.mark_value(v.stack[i], mut str_mark, mut arr_mark, mut struct_mark, mut float_mark, mut closure_mark)
|
||||||
|
}
|
||||||
|
// ---- remap tables: old index -> new index (-1 = collected) ----
|
||||||
|
mut str_new := []int{len: v.strings.len, init: -1}
|
||||||
|
mut arr_new := []int{len: v.arrays.len, init: -1}
|
||||||
|
mut struct_new := []int{len: v.structs.len, init: -1}
|
||||||
|
mut float_new := []int{len: v.floats.len, init: -1}
|
||||||
|
mut closure_new := []int{len: v.closures.len, init: -1}
|
||||||
|
mut nstr := v.const_strs
|
||||||
|
for i in v.const_strs..v.strings.len {
|
||||||
|
if str_mark[i] {
|
||||||
|
str_new[i] = nstr
|
||||||
|
nstr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mut narr := 0
|
||||||
|
for i in 0..v.arrays.len {
|
||||||
|
if arr_mark[i] {
|
||||||
|
arr_new[i] = narr
|
||||||
|
narr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mut nstruct := 0
|
||||||
|
for i in 0..v.structs.len {
|
||||||
|
if struct_mark[i] {
|
||||||
|
struct_new[i] = nstruct
|
||||||
|
nstruct++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mut nfloat := 0
|
||||||
|
for i in 0..v.floats.len {
|
||||||
|
if float_mark[i] {
|
||||||
|
float_new[i] = nfloat
|
||||||
|
nfloat++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mut nclosure := 0
|
||||||
|
for i in 0..v.closures.len {
|
||||||
|
if closure_mark[i] {
|
||||||
|
closure_new[i] = nclosure
|
||||||
|
nclosure++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ---- rewrite live references ----
|
||||||
|
for i in 0..v.sp {
|
||||||
|
v.stack[i] = v.remap(v.stack[i], str_new, arr_new, struct_new, float_new, closure_new)
|
||||||
|
}
|
||||||
|
for h in 0..v.arrays.len {
|
||||||
|
if arr_mark[h] {
|
||||||
|
for j in 0..v.arrays[h].len {
|
||||||
|
v.arrays[h][j] = v.remap(v.arrays[h][j], str_new, arr_new, struct_new, float_new, closure_new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for h in 0..v.structs.len {
|
||||||
|
if struct_mark[h] {
|
||||||
|
for j in 0..v.structs[h].fields.len {
|
||||||
|
v.structs[h].fields[j].val = v.remap(v.structs[h].fields[j].val, str_new, arr_new, struct_new, float_new, closure_new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ---- compact pools ----
|
||||||
|
mut strings := v.strings[..v.const_strs]
|
||||||
|
for i in v.const_strs..v.strings.len {
|
||||||
|
if str_mark[i] {
|
||||||
|
strings << v.strings[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.strings = strings
|
||||||
|
mut arrays := [][]i64{}
|
||||||
|
for i in 0..v.arrays.len {
|
||||||
|
if arr_mark[i] {
|
||||||
|
arrays << v.arrays[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.arrays = arrays
|
||||||
|
mut structs := []StructVal{}
|
||||||
|
for i in 0..v.structs.len {
|
||||||
|
if struct_mark[i] {
|
||||||
|
structs << v.structs[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.structs = structs
|
||||||
|
mut floats := []f64{}
|
||||||
|
for i in 0..v.floats.len {
|
||||||
|
if float_mark[i] {
|
||||||
|
floats << v.floats[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.floats = floats
|
||||||
|
mut closures := []Closure{}
|
||||||
|
for i in 0..v.closures.len {
|
||||||
|
if closure_mark[i] {
|
||||||
|
closures << v.closures[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.closures = closures
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark_value traces a value and everything it references using an explicit
|
||||||
|
// worklist (arrays of arrays can nest deeply; recursion could overflow).
|
||||||
|
fn (mut v Vm) mark_value(x i64, mut str_mark []bool, mut arr_mark []bool, mut struct_mark []bool, mut float_mark []bool, mut closure_mark []bool) {
|
||||||
|
mut work := []i64{}
|
||||||
|
work << x
|
||||||
|
for work.len > 0 {
|
||||||
|
val := work.pop()
|
||||||
|
match v.tag(val) {
|
||||||
|
tag_str {
|
||||||
|
h := v.hand(val)
|
||||||
|
if h >= v.const_strs && h < str_mark.len && !str_mark[h] {
|
||||||
|
str_mark[h] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag_arr {
|
||||||
|
h := v.hand(val)
|
||||||
|
if h >= 0 && h < arr_mark.len && !arr_mark[h] {
|
||||||
|
arr_mark[h] = true
|
||||||
|
for el in v.arrays[h] {
|
||||||
|
work << el
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag_struct {
|
||||||
|
h := v.hand(val)
|
||||||
|
if h >= 0 && h < struct_mark.len && !struct_mark[h] {
|
||||||
|
struct_mark[h] = true
|
||||||
|
for f in v.structs[h].fields {
|
||||||
|
work << f.val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag_float {
|
||||||
|
h := v.hand(val)
|
||||||
|
if h >= 0 && h < float_mark.len && !float_mark[h] {
|
||||||
|
float_mark[h] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag_closure {
|
||||||
|
h := v.hand(val)
|
||||||
|
if h >= 0 && h < closure_mark.len && !closure_mark[h] {
|
||||||
|
closure_mark[h] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remap translates a handle to its post-compaction index, leaving integers
|
||||||
|
// and uncollected values untouched.
|
||||||
|
fn (mut v Vm) remap(x i64, str_new []int, arr_new []int, struct_new []int, float_new []int, closure_new []int) i64 {
|
||||||
|
match v.tag(x) {
|
||||||
|
tag_str {
|
||||||
|
h := v.hand(x)
|
||||||
|
if h >= v.const_strs && h < str_new.len && str_new[h] >= 0 {
|
||||||
|
return v.mkstr(str_new[h])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag_arr {
|
||||||
|
h := v.hand(x)
|
||||||
|
if h >= 0 && h < arr_new.len && arr_new[h] >= 0 {
|
||||||
|
return v.mkarr(arr_new[h])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag_struct {
|
||||||
|
h := v.hand(x)
|
||||||
|
if h >= 0 && h < struct_new.len && struct_new[h] >= 0 {
|
||||||
|
return v.mkstruct_handle(struct_new[h])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag_float {
|
||||||
|
h := v.hand(x)
|
||||||
|
if h >= 0 && h < float_new.len && float_new[h] >= 0 {
|
||||||
|
return v.mkfloat(float_new[h])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag_closure {
|
||||||
|
h := v.hand(x)
|
||||||
|
if h >= 0 && h < closure_new.len && closure_new[h] >= 0 {
|
||||||
|
return v.mkclosure(closure_new[h])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
+420
@@ -0,0 +1,420 @@
|
|||||||
|
// native.v — host builtins for the VuurRaaf VM.
|
||||||
|
//
|
||||||
|
// The op_native instruction carries a builtin id and an argument count. Each
|
||||||
|
// builtin pops its arguments off the stack (left-to-right push order means the
|
||||||
|
// last argument is on top) and pushes a single result (except exit(), which
|
||||||
|
// halts the machine). This is where the language touches the host: file I/O,
|
||||||
|
// environment, time, randomness, and the math/collection helpers.
|
||||||
|
module vm
|
||||||
|
|
||||||
|
import os
|
||||||
|
import math
|
||||||
|
import rand
|
||||||
|
import time
|
||||||
|
|
||||||
|
fn (mut v Vm) native(id int, _argc int) ! {
|
||||||
|
match id {
|
||||||
|
native_abs {
|
||||||
|
x := v.pop()!
|
||||||
|
if v.is_float(x) {
|
||||||
|
v.push(v.push_float(math.abs(v.fval(x))))!
|
||||||
|
} else {
|
||||||
|
val := v.dec_int(x)
|
||||||
|
v.push(v.enc_int(if val < 0 { -val } else { val }))!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
native_min {
|
||||||
|
b := v.pop()!
|
||||||
|
a := v.pop()!
|
||||||
|
if v.is_float(a) || v.is_float(b) {
|
||||||
|
v.push(v.push_float(math.min(v.to_f64(a), v.to_f64(b))))!
|
||||||
|
} else {
|
||||||
|
x := v.dec_int(a)
|
||||||
|
y := v.dec_int(b)
|
||||||
|
v.push(v.enc_int(if x < y { x } else { y }))!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
native_max {
|
||||||
|
b := v.pop()!
|
||||||
|
a := v.pop()!
|
||||||
|
if v.is_float(a) || v.is_float(b) {
|
||||||
|
v.push(v.push_float(math.max(v.to_f64(a), v.to_f64(b))))!
|
||||||
|
} else {
|
||||||
|
x := v.dec_int(a)
|
||||||
|
y := v.dec_int(b)
|
||||||
|
v.push(v.enc_int(if x > y { x } else { y }))!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
native_pow {
|
||||||
|
b := v.pop()!
|
||||||
|
a := v.pop()!
|
||||||
|
v.push(v.push_float(math.pow(v.to_f64(a), v.to_f64(b))))!
|
||||||
|
}
|
||||||
|
native_sqrt {
|
||||||
|
x := v.pop()!
|
||||||
|
v.push(v.push_float(math.sqrt(v.to_f64(x))))!
|
||||||
|
}
|
||||||
|
native_floor {
|
||||||
|
x := v.pop()!
|
||||||
|
v.push(v.enc_int(i64(math.floor(v.to_f64(x)))))!
|
||||||
|
}
|
||||||
|
native_ceil {
|
||||||
|
x := v.pop()!
|
||||||
|
v.push(v.enc_int(i64(math.ceil(v.to_f64(x)))))!
|
||||||
|
}
|
||||||
|
native_round {
|
||||||
|
x := v.pop()!
|
||||||
|
v.push(v.enc_int(i64(math.round(v.to_f64(x)))))!
|
||||||
|
}
|
||||||
|
native_rand {
|
||||||
|
v.push(v.push_float(rand.f64()))!
|
||||||
|
}
|
||||||
|
native_rand_int {
|
||||||
|
n := int(v.dec_int(v.pop()!))
|
||||||
|
if n <= 0 {
|
||||||
|
return error('rand_int() expects a positive bound')
|
||||||
|
}
|
||||||
|
v.push(v.enc_int(i64(rand.intn(n) or { return error('rand_int() failed') })))!
|
||||||
|
}
|
||||||
|
native_int {
|
||||||
|
x := v.pop()!
|
||||||
|
if v.is_str(x) && v.valid_handle(x) {
|
||||||
|
v.push(v.enc_int(i64(v.strings[v.hand(x)].i64())))!
|
||||||
|
} else if v.is_float(x) {
|
||||||
|
v.push(v.enc_int(i64(v.fval(x))))!
|
||||||
|
} else if v.is_arr(x) || v.is_struct(x) {
|
||||||
|
return error('cannot convert a ${v.type_name(x)} to int')
|
||||||
|
} else {
|
||||||
|
v.push(x)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
native_str {
|
||||||
|
x := v.pop()!
|
||||||
|
v.push(v.alloc_str(v.val_str(x, 0)))!
|
||||||
|
}
|
||||||
|
native_float {
|
||||||
|
x := v.pop()!
|
||||||
|
if v.is_str(x) && v.valid_handle(x) {
|
||||||
|
v.push(v.push_float(v.strings[v.hand(x)].f64()))!
|
||||||
|
} else if v.is_float(x) {
|
||||||
|
v.push(x)!
|
||||||
|
} else if v.is_arr(x) || v.is_struct(x) {
|
||||||
|
return error('cannot convert a ${v.type_name(x)} to float')
|
||||||
|
} else {
|
||||||
|
v.push(v.push_float(f64(v.dec_int(x))))!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
native_type {
|
||||||
|
x := v.pop()!
|
||||||
|
v.push(v.alloc_str(v.type_name(x)))!
|
||||||
|
}
|
||||||
|
native_split {
|
||||||
|
delim := v.pop_str()!
|
||||||
|
s := v.pop_str()!
|
||||||
|
parts := s.split(delim)
|
||||||
|
mut arr := []i64{}
|
||||||
|
for p in parts {
|
||||||
|
v.strings << p
|
||||||
|
arr << v.mkstr(v.strings.len - 1)
|
||||||
|
}
|
||||||
|
v.arrays << arr
|
||||||
|
v.push(v.mkarr(v.arrays.len - 1))!
|
||||||
|
}
|
||||||
|
native_join {
|
||||||
|
delim := v.pop_str()!
|
||||||
|
h := v.pop()!
|
||||||
|
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
||||||
|
return error('join() expects an array as its first argument')
|
||||||
|
}
|
||||||
|
a := v.arrays[v.hand(h)]
|
||||||
|
mut parts := []string{}
|
||||||
|
for x in a {
|
||||||
|
if v.is_str(x) && v.valid_handle(x) {
|
||||||
|
parts << v.strings[v.hand(x)]
|
||||||
|
} else {
|
||||||
|
parts << v.val_str(x, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.push(v.alloc_str(parts.join(delim)))!
|
||||||
|
}
|
||||||
|
native_contains {
|
||||||
|
sub := v.pop_str()!
|
||||||
|
s := v.pop_str()!
|
||||||
|
v.push(v.enc_int(bool_i64(s.contains(sub))))!
|
||||||
|
}
|
||||||
|
native_starts_with {
|
||||||
|
sub := v.pop_str()!
|
||||||
|
s := v.pop_str()!
|
||||||
|
v.push(v.enc_int(bool_i64(s.starts_with(sub))))!
|
||||||
|
}
|
||||||
|
native_ends_with {
|
||||||
|
sub := v.pop_str()!
|
||||||
|
s := v.pop_str()!
|
||||||
|
v.push(v.enc_int(bool_i64(s.ends_with(sub))))!
|
||||||
|
}
|
||||||
|
native_trim {
|
||||||
|
s := v.pop_str()!
|
||||||
|
v.push(v.alloc_str(s.trim_space()))!
|
||||||
|
}
|
||||||
|
native_lower {
|
||||||
|
s := v.pop_str()!
|
||||||
|
v.push(v.alloc_str(s.to_lower()))!
|
||||||
|
}
|
||||||
|
native_upper {
|
||||||
|
s := v.pop_str()!
|
||||||
|
v.push(v.alloc_str(s.to_upper()))!
|
||||||
|
}
|
||||||
|
native_pop {
|
||||||
|
h := v.pop()!
|
||||||
|
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
||||||
|
return error('pop() expects an array')
|
||||||
|
}
|
||||||
|
mut a := v.arrays[v.hand(h)]
|
||||||
|
if a.len == 0 {
|
||||||
|
return error('pop() on an empty array')
|
||||||
|
}
|
||||||
|
val := a[a.len - 1]
|
||||||
|
v.arrays[v.hand(h)] = a[..a.len - 1]
|
||||||
|
v.push(val)!
|
||||||
|
}
|
||||||
|
native_insert {
|
||||||
|
val := v.pop()!
|
||||||
|
idx := int(v.dec_int(v.pop()!))
|
||||||
|
h := v.pop()!
|
||||||
|
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
||||||
|
return error('insert() expects an array as its first argument')
|
||||||
|
}
|
||||||
|
a := v.arrays[v.hand(h)]
|
||||||
|
if idx < 0 || idx > a.len {
|
||||||
|
return error('insert index ${idx} out of bounds (len ${a.len})')
|
||||||
|
}
|
||||||
|
mut na := []i64{}
|
||||||
|
for i, x in a {
|
||||||
|
if i == idx {
|
||||||
|
na << val
|
||||||
|
}
|
||||||
|
na << x
|
||||||
|
}
|
||||||
|
if idx == a.len {
|
||||||
|
na << val
|
||||||
|
}
|
||||||
|
v.arrays[v.hand(h)] = na
|
||||||
|
v.push(h)!
|
||||||
|
}
|
||||||
|
native_remove {
|
||||||
|
idx := int(v.dec_int(v.pop()!))
|
||||||
|
h := v.pop()!
|
||||||
|
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
||||||
|
return error('remove() expects an array as its first argument')
|
||||||
|
}
|
||||||
|
a := v.arrays[v.hand(h)]
|
||||||
|
if idx < 0 || idx >= a.len {
|
||||||
|
return error('remove index ${idx} out of bounds (len ${a.len})')
|
||||||
|
}
|
||||||
|
mut na := []i64{}
|
||||||
|
for i, x in a {
|
||||||
|
if i != idx {
|
||||||
|
na << x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.arrays[v.hand(h)] = na
|
||||||
|
v.push(h)!
|
||||||
|
}
|
||||||
|
native_sort {
|
||||||
|
h := v.pop()!
|
||||||
|
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
||||||
|
return error('sort() expects an array')
|
||||||
|
}
|
||||||
|
mut a := v.arrays[v.hand(h)]
|
||||||
|
// insertion sort by numeric value
|
||||||
|
for i in 1..a.len {
|
||||||
|
key := a[i]
|
||||||
|
mut j := i - 1
|
||||||
|
for j >= 0 && v.num_gt(a[j], key) {
|
||||||
|
a[j + 1] = a[j]
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
a[j + 1] = key
|
||||||
|
}
|
||||||
|
v.push(h)!
|
||||||
|
}
|
||||||
|
native_clone {
|
||||||
|
x := v.pop()!
|
||||||
|
if v.is_arr(x) && v.valid_arr_handle(x) {
|
||||||
|
v.arrays << v.arrays[v.hand(x)].clone()
|
||||||
|
v.push(v.mkarr(v.arrays.len - 1))!
|
||||||
|
} else if v.is_struct(x) && v.valid_struct_handle(x) {
|
||||||
|
s := v.structs[v.hand(x)]
|
||||||
|
v.structs << StructVal{ fields: s.fields.clone() }
|
||||||
|
v.push(v.mkstruct_handle(v.structs.len - 1))!
|
||||||
|
} else if v.is_str(x) && v.valid_handle(x) {
|
||||||
|
v.push(v.alloc_str(v.strings[v.hand(x)]))!
|
||||||
|
} else if v.is_float(x) {
|
||||||
|
v.push(v.push_float(v.fval(x)))!
|
||||||
|
} else {
|
||||||
|
v.push(x)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
native_reverse {
|
||||||
|
h := v.pop()!
|
||||||
|
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
||||||
|
return error('reverse() expects an array')
|
||||||
|
}
|
||||||
|
mut a := v.arrays[v.hand(h)]
|
||||||
|
for i in 0..a.len / 2 {
|
||||||
|
a[i], a[a.len - 1 - i] = a[a.len - 1 - i], a[i]
|
||||||
|
}
|
||||||
|
v.push(h)!
|
||||||
|
}
|
||||||
|
native_index_of {
|
||||||
|
val := v.pop()!
|
||||||
|
h := v.pop()!
|
||||||
|
if v.is_arr(h) && v.valid_arr_handle(h) {
|
||||||
|
a := v.arrays[v.hand(h)]
|
||||||
|
for i, x in a {
|
||||||
|
if v.cmp(x, val, '==')! == 1 {
|
||||||
|
v.push(v.enc_int(i64(i)))!
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.push(v.enc_int(-1))!
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v.is_str(h) && v.valid_handle(h) {
|
||||||
|
if !v.is_str(val) || !v.valid_handle(val) {
|
||||||
|
return error('index_of() on a string expects a string needle')
|
||||||
|
}
|
||||||
|
s := v.strings[v.hand(h)]
|
||||||
|
needle := v.strings[v.hand(val)]
|
||||||
|
byte_idx := s.index(needle) or { -1 }
|
||||||
|
if byte_idx < 0 {
|
||||||
|
v.push(v.enc_int(-1))!
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// convert byte offset to a rune index so UTF-8 strings count characters
|
||||||
|
rune_idx := s[..byte_idx].runes().len
|
||||||
|
v.push(v.enc_int(i64(rune_idx)))!
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return error('index_of() expects an array or string as its first argument')
|
||||||
|
}
|
||||||
|
native_args {
|
||||||
|
mut arr := []i64{}
|
||||||
|
for s in v.prog_args {
|
||||||
|
v.strings << s
|
||||||
|
arr << v.mkstr(v.strings.len - 1)
|
||||||
|
}
|
||||||
|
v.arrays << arr
|
||||||
|
v.push(v.mkarr(v.arrays.len - 1))!
|
||||||
|
}
|
||||||
|
native_getenv {
|
||||||
|
name := v.pop_str()!
|
||||||
|
v.push(v.alloc_str(os.getenv(name)))!
|
||||||
|
}
|
||||||
|
native_setenv {
|
||||||
|
val := v.pop_str()!
|
||||||
|
name := v.pop_str()!
|
||||||
|
os.setenv(name, val, true)
|
||||||
|
v.push(v.enc_int(0))!
|
||||||
|
}
|
||||||
|
native_exit {
|
||||||
|
code := int(v.dec_int(v.pop()!))
|
||||||
|
v.exit_code = i64(code)
|
||||||
|
v.did_exit = true
|
||||||
|
v.halted = true
|
||||||
|
}
|
||||||
|
native_time {
|
||||||
|
v.push(v.push_float(f64(time.now().unix_milli()) / 1000.0))!
|
||||||
|
}
|
||||||
|
native_sleep {
|
||||||
|
ms := int(v.dec_int(v.pop()!))
|
||||||
|
time.sleep(time.Duration(ms) * time.millisecond)
|
||||||
|
v.push(v.enc_int(0))!
|
||||||
|
}
|
||||||
|
native_read_file {
|
||||||
|
path := v.pop_str()!
|
||||||
|
content := os.read_file(path) or { return error('cannot read file "${path}": ${err}') }
|
||||||
|
v.push(v.alloc_str(content))!
|
||||||
|
}
|
||||||
|
native_write_file {
|
||||||
|
content := v.pop_str()!
|
||||||
|
path := v.pop_str()!
|
||||||
|
os.write_file(path, content) or { return error('cannot write file "${path}": ${err}') }
|
||||||
|
v.push(v.enc_int(0))!
|
||||||
|
}
|
||||||
|
native_eprint {
|
||||||
|
x := v.pop()!
|
||||||
|
eprintln(v.val_str(x, 0))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return error('unknown native builtin ${id}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pop_str pops the top value and requires it to be a string.
|
||||||
|
fn (mut v Vm) pop_str() !string {
|
||||||
|
x := v.pop()!
|
||||||
|
if !v.is_str(x) || !v.valid_handle(x) {
|
||||||
|
return error('expected a string argument')
|
||||||
|
}
|
||||||
|
return v.strings[v.hand(x)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// type_name returns the type label of a tagged value.
|
||||||
|
fn (mut v Vm) type_name(x i64) string {
|
||||||
|
if v.is_str(x) {
|
||||||
|
return 'string'
|
||||||
|
}
|
||||||
|
if v.is_float(x) {
|
||||||
|
return 'float'
|
||||||
|
}
|
||||||
|
if v.is_arr(x) {
|
||||||
|
return 'array'
|
||||||
|
}
|
||||||
|
if v.is_struct(x) {
|
||||||
|
return 'struct'
|
||||||
|
}
|
||||||
|
return 'int'
|
||||||
|
}
|
||||||
|
|
||||||
|
// str_method dispatches a string method call. The receiver was pushed before
|
||||||
|
// the arguments, so it is the first argument from the native builtin's point
|
||||||
|
// of view; delegating keeps the behavior identical to the free-function forms.
|
||||||
|
fn (mut v Vm) str_method(name string, argc int) ! {
|
||||||
|
bid := match name {
|
||||||
|
'to_upper' { native_upper }
|
||||||
|
'to_lower' { native_lower }
|
||||||
|
'trim' { native_trim }
|
||||||
|
'contains' { native_contains }
|
||||||
|
'starts_with' { native_starts_with }
|
||||||
|
'ends_with' { native_ends_with }
|
||||||
|
'split' { native_split }
|
||||||
|
'index_of' { native_index_of }
|
||||||
|
'to_int' { native_int }
|
||||||
|
'to_float' { native_float }
|
||||||
|
else { -1 }
|
||||||
|
}
|
||||||
|
if bid >= 0 {
|
||||||
|
// native builtins pop the first argument (the receiver) last
|
||||||
|
v.native(bid, argc + 1)!
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if name == 'len' {
|
||||||
|
h := v.pop()!
|
||||||
|
if !v.is_str(h) || !v.valid_handle(h) {
|
||||||
|
return error('len() on a non-string value')
|
||||||
|
}
|
||||||
|
v.push(v.enc_int(i64(v.strings[v.hand(h)].runes().len)))!
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return error('unknown string method "${name}"')
|
||||||
|
}
|
||||||
|
|
||||||
|
// num_gt compares two values by their numeric value (int or float).
|
||||||
|
fn (mut v Vm) num_gt(x i64, y i64) bool {
|
||||||
|
if v.is_float(x) || v.is_float(y) {
|
||||||
|
return v.to_f64(x) > v.to_f64(y)
|
||||||
|
}
|
||||||
|
return v.dec_int(x) > v.dec_int(y)
|
||||||
|
}
|
||||||
@@ -47,3 +47,61 @@ const op_shas = u8(40) // has(map, "key") -> 1 if key exists, 0 otherwise
|
|||||||
const op_sdel = u8(41) // delete(map, "key") -> removes the key
|
const op_sdel = u8(41) // delete(map, "key") -> removes the key
|
||||||
const op_slen = u8(42) // slen(struct) -> number of fields
|
const op_slen = u8(42) // slen(struct) -> number of fields
|
||||||
const op_skeys = u8(43) // skeys(struct) -> array of field name strings
|
const op_skeys = u8(43) // skeys(struct) -> array of field name strings
|
||||||
|
const op_slice = u8(44) // slice(arr/str, start, end) -> sliced arr/str
|
||||||
|
const op_push_f = u8(45) // push a float literal (8-byte little-endian f64)
|
||||||
|
const op_native = u8(46) // call a host builtin: <id:i64> <argc:i64>
|
||||||
|
const op_and_b = u8(47) // bitwise AND
|
||||||
|
const op_or_b = u8(48) // bitwise OR
|
||||||
|
const op_xor = u8(49) // bitwise XOR
|
||||||
|
const op_shl = u8(50) // shift left
|
||||||
|
const op_shr = u8(51) // shift right
|
||||||
|
const op_not_b = u8(52) // bitwise NOT
|
||||||
|
const op_try = u8(53) // push handler: <catch_ip:i64>
|
||||||
|
const op_throw = u8(54) // throw: pop error, unwind to nearest handler, push error
|
||||||
|
const op_catch_done = u8(55) // pop handler (normal completion)
|
||||||
|
const op_closure = u8(56) // push closure: <entry:i64>
|
||||||
|
const op_call_closure = u8(57) // call closure: <argc:i64>
|
||||||
|
const op_argc = u8(58) // push the current frame's arg count
|
||||||
|
const op_load_dyn = u8(59) // pop idx, push stack[bp + idx]
|
||||||
|
const op_varargs = u8(60) // <named:i64> <dst:i64> — collect args[named..argc-1] into array at local dst
|
||||||
|
const op_str_method = u8(61) // <name:str> <argc:i64> — call a string method (s.len(), s.contains(x), ...)
|
||||||
|
|
||||||
|
// native builtin ids (the operand to op_native)
|
||||||
|
const native_abs = 100
|
||||||
|
const native_min = 101
|
||||||
|
const native_max = 102
|
||||||
|
const native_pow = 103
|
||||||
|
const native_sqrt = 104
|
||||||
|
const native_floor = 105
|
||||||
|
const native_ceil = 106
|
||||||
|
const native_round = 107
|
||||||
|
const native_rand = 108
|
||||||
|
const native_rand_int = 109
|
||||||
|
const native_int = 110
|
||||||
|
const native_str = 111
|
||||||
|
const native_float = 112
|
||||||
|
const native_type = 113
|
||||||
|
const native_split = 114
|
||||||
|
const native_join = 115
|
||||||
|
const native_contains = 116
|
||||||
|
const native_starts_with = 117
|
||||||
|
const native_ends_with = 118
|
||||||
|
const native_trim = 119
|
||||||
|
const native_lower = 120
|
||||||
|
const native_upper = 121
|
||||||
|
const native_pop = 122
|
||||||
|
const native_insert = 123
|
||||||
|
const native_remove = 124
|
||||||
|
const native_sort = 125
|
||||||
|
const native_clone = 126
|
||||||
|
const native_reverse = 127
|
||||||
|
const native_index_of = 128
|
||||||
|
const native_args = 129
|
||||||
|
const native_getenv = 130
|
||||||
|
const native_setenv = 131
|
||||||
|
const native_exit = 132
|
||||||
|
const native_time = 133
|
||||||
|
const native_sleep = 134
|
||||||
|
const native_read_file = 135
|
||||||
|
const native_write_file = 136
|
||||||
|
const native_eprint = 137
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// ops.v — arithmetic, comparison, and string operations for the VuurRaaf VM.
|
// ops.v — arithmetic, comparison, and string operations for the VuurRaaf VM.
|
||||||
module vm
|
module vm
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
fn (mut v Vm) add(a i64, b i64) !i64 {
|
fn (mut v Vm) add(a i64, b i64) !i64 {
|
||||||
if v.is_arr(a) || v.is_arr(b) {
|
if v.is_arr(a) || v.is_arr(b) {
|
||||||
return error('cannot add arrays with +')
|
return error('cannot add arrays with +')
|
||||||
@@ -17,18 +19,61 @@ fn (mut v Vm) add(a i64, b i64) !i64 {
|
|||||||
if v.is_str(b) {
|
if v.is_str(b) {
|
||||||
return v.alloc_str(v.num_str(a) + v.strings[v.hand(b)])
|
return v.alloc_str(v.num_str(a) + v.strings[v.hand(b)])
|
||||||
}
|
}
|
||||||
|
if v.is_float(a) || v.is_float(b) {
|
||||||
|
return v.push_float(v.to_f64(a) + v.to_f64(b))
|
||||||
|
}
|
||||||
return v.enc_int(v.dec_int(a) + v.dec_int(b))
|
return v.enc_int(v.dec_int(a) + v.dec_int(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// to_f64 promotes an integer or float tagged value to f64.
|
||||||
|
fn (mut v Vm) to_f64(x i64) f64 {
|
||||||
|
if v.is_float(x) {
|
||||||
|
return v.fval(x)
|
||||||
|
}
|
||||||
|
return f64(v.dec_int(x))
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut v Vm) alloc_str(s string) i64 {
|
fn (mut v Vm) alloc_str(s string) i64 {
|
||||||
v.strings << s
|
v.strings << s
|
||||||
return v.mkstr(v.strings.len - 1)
|
return v.mkstr(v.strings.len - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut v Vm) num_str(x i64) string {
|
fn (mut v Vm) num_str(x i64) string {
|
||||||
|
if v.is_float(x) {
|
||||||
|
return fmt_float(v.fval(x))
|
||||||
|
}
|
||||||
return v.dec_int(x).str()
|
return v.dec_int(x).str()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fmt_float renders an f64 nicely: integral values lose the trailing ".0",
|
||||||
|
// -0 collapses to 0, and most floats avoid V's default scientific notation.
|
||||||
|
fn fmt_float(f f64) string {
|
||||||
|
if f == 0.0 {
|
||||||
|
return '0'
|
||||||
|
}
|
||||||
|
if math.is_nan(f) {
|
||||||
|
return 'NaN'
|
||||||
|
}
|
||||||
|
if math.is_inf(f, 1) {
|
||||||
|
return 'Inf'
|
||||||
|
}
|
||||||
|
if math.is_inf(f, -1) {
|
||||||
|
return '-Inf'
|
||||||
|
}
|
||||||
|
if f == math.floor(f) && math.abs(f) < 1e18 {
|
||||||
|
return i64(f).str()
|
||||||
|
}
|
||||||
|
if math.abs(f) < 1e18 {
|
||||||
|
mut s := '${f:.14f}'
|
||||||
|
s = s.trim_right('0').trim_right('.')
|
||||||
|
if s == '' {
|
||||||
|
return '0'
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return f.str()
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut v Vm) arith(a i64, b i64, op string) !i64 {
|
fn (mut v Vm) arith(a i64, b i64, op string) !i64 {
|
||||||
if v.is_str(a) || v.is_str(b) {
|
if v.is_str(a) || v.is_str(b) {
|
||||||
return error('cannot use strings with "${op}"')
|
return error('cannot use strings with "${op}"')
|
||||||
@@ -39,6 +84,33 @@ fn (mut v Vm) arith(a i64, b i64, op string) !i64 {
|
|||||||
if v.is_struct(a) || v.is_struct(b) {
|
if v.is_struct(a) || v.is_struct(b) {
|
||||||
return error('cannot use structs with "${op}"')
|
return error('cannot use structs with "${op}"')
|
||||||
}
|
}
|
||||||
|
if v.is_float(a) || v.is_float(b) {
|
||||||
|
x := v.to_f64(a)
|
||||||
|
y := v.to_f64(b)
|
||||||
|
match op {
|
||||||
|
'-' {
|
||||||
|
return v.push_float(x - y)
|
||||||
|
}
|
||||||
|
'*' {
|
||||||
|
return v.push_float(x * y)
|
||||||
|
}
|
||||||
|
'/' {
|
||||||
|
if y == 0.0 {
|
||||||
|
return error('division by zero')
|
||||||
|
}
|
||||||
|
return v.push_float(x / y)
|
||||||
|
}
|
||||||
|
'%' {
|
||||||
|
if y == 0.0 {
|
||||||
|
return error('division by zero')
|
||||||
|
}
|
||||||
|
return v.push_float(math.fmod(x, y))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return error('internal: bad arith op "${op}"')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
x := v.dec_int(a)
|
x := v.dec_int(a)
|
||||||
y := v.dec_int(b)
|
y := v.dec_int(b)
|
||||||
match op {
|
match op {
|
||||||
@@ -97,6 +169,19 @@ fn (mut v Vm) cmp(a i64, b i64, op string) !i64 {
|
|||||||
if v.is_str(a) || v.is_str(b) {
|
if v.is_str(a) || v.is_str(b) {
|
||||||
return error('cannot compare a string and a number')
|
return error('cannot compare a string and a number')
|
||||||
}
|
}
|
||||||
|
if v.is_float(a) || v.is_float(b) {
|
||||||
|
x := v.to_f64(a)
|
||||||
|
y := v.to_f64(b)
|
||||||
|
return bool_i64(match op {
|
||||||
|
'==' { x == y }
|
||||||
|
'!=' { x != y }
|
||||||
|
'<' { x < y }
|
||||||
|
'<=' { x <= y }
|
||||||
|
'>' { x > y }
|
||||||
|
'>=' { x >= y }
|
||||||
|
else { return error('internal: bad cmp op "${op}"') }
|
||||||
|
})
|
||||||
|
}
|
||||||
x := v.dec_int(a)
|
x := v.dec_int(a)
|
||||||
y := v.dec_int(b)
|
y := v.dec_int(b)
|
||||||
return bool_i64(match op {
|
return bool_i64(match op {
|
||||||
|
|||||||
+30
-3
@@ -44,6 +44,12 @@ fn (mut v Vm) val_str(x i64, depth int) string {
|
|||||||
}
|
}
|
||||||
return out + '}'
|
return out + '}'
|
||||||
}
|
}
|
||||||
|
if v.is_float(x) && v.valid_float_handle(x) {
|
||||||
|
return fmt_float(v.fval(x))
|
||||||
|
}
|
||||||
|
if v.is_closure(x) && v.valid_closure_handle(x) {
|
||||||
|
return '<fn@${v.closures[v.hand(x)].entry}>'
|
||||||
|
}
|
||||||
return v.dec_int(x).str()
|
return v.dec_int(x).str()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +58,7 @@ fn (mut v Vm) trace_op(op u8) {
|
|||||||
op_halt { 'halt' }
|
op_halt { 'halt' }
|
||||||
op_push_i { 'push_int' }
|
op_push_i { 'push_int' }
|
||||||
op_push_s { 'push_str' }
|
op_push_s { 'push_str' }
|
||||||
|
op_push_f { 'push_float' }
|
||||||
op_load { 'load' }
|
op_load { 'load' }
|
||||||
op_store { 'store' }
|
op_store { 'store' }
|
||||||
op_pop { 'pop' }
|
op_pop { 'pop' }
|
||||||
@@ -93,6 +100,23 @@ fn (mut v Vm) trace_op(op u8) {
|
|||||||
op_sdel { 'sdel' }
|
op_sdel { 'sdel' }
|
||||||
op_slen { 'slen' }
|
op_slen { 'slen' }
|
||||||
op_skeys { 'skeys' }
|
op_skeys { 'skeys' }
|
||||||
|
op_slice { 'slice' }
|
||||||
|
op_native { 'native' }
|
||||||
|
op_and_b { 'and_b' }
|
||||||
|
op_or_b { 'or_b' }
|
||||||
|
op_xor { 'xor' }
|
||||||
|
op_shl { 'shl' }
|
||||||
|
op_shr { 'shr' }
|
||||||
|
op_not_b { 'not_b' }
|
||||||
|
op_try { 'try' }
|
||||||
|
op_throw { 'throw' }
|
||||||
|
op_catch_done { 'catch_done' }
|
||||||
|
op_closure { 'closure' }
|
||||||
|
op_call_closure { 'call_closure' }
|
||||||
|
op_argc { 'argc' }
|
||||||
|
op_load_dyn { 'load_dyn' }
|
||||||
|
op_varargs { 'varargs' }
|
||||||
|
op_str_method { 'str_method' }
|
||||||
else { '??' }
|
else { '??' }
|
||||||
}
|
}
|
||||||
mut s := ''
|
mut s := ''
|
||||||
@@ -100,10 +124,13 @@ fn (mut v Vm) trace_op(op u8) {
|
|||||||
if i > 0 {
|
if i > 0 {
|
||||||
s += ' '
|
s += ' '
|
||||||
}
|
}
|
||||||
if v.is_str(v.stack[i]) && v.valid_handle(v.stack[i]) {
|
x := v.stack[i]
|
||||||
s += '"${v.strings[v.hand(v.stack[i])]}"'
|
if v.is_str(x) && v.valid_handle(x) {
|
||||||
|
s += '"${v.strings[v.hand(x)]}"'
|
||||||
|
} else if v.is_closure(x) {
|
||||||
|
s += '<fn>'
|
||||||
} else {
|
} else {
|
||||||
s += v.val_str(v.stack[i], 0)
|
s += v.val_str(x, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println(' [ip=${v.ip:4}] ${name:-9} stack: [${s}]')
|
println(' [ip=${v.ip:4}] ${name:-9} stack: [${s}]')
|
||||||
|
|||||||
+32
-10
@@ -1,6 +1,8 @@
|
|||||||
// types.v — core types and constants for the VuurRaaf VM.
|
// types.v — core types and constants for the VuurRaaf VM.
|
||||||
module vm
|
module vm
|
||||||
|
|
||||||
|
import obj
|
||||||
|
|
||||||
const stack_cap = 65536
|
const stack_cap = 65536
|
||||||
|
|
||||||
// Field is one `name: value` entry of a struct value.
|
// Field is one `name: value` entry of a struct value.
|
||||||
@@ -15,18 +17,38 @@ mut:
|
|||||||
fields []Field
|
fields []Field
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handler records a try/catch handler pushed at runtime.
|
||||||
|
struct Closure {
|
||||||
|
entry int // code IP of the function body
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Handler {
|
||||||
|
ip int // catch_ip
|
||||||
|
bp int // frame base at try point
|
||||||
|
sp int // stack pointer right after the handler record
|
||||||
|
}
|
||||||
|
|
||||||
struct Vm {
|
struct Vm {
|
||||||
mut:
|
mut:
|
||||||
code []u8
|
code []u8
|
||||||
strings []string
|
strings []string
|
||||||
arrays [][]i64
|
arrays [][]i64
|
||||||
structs []StructVal
|
structs []StructVal
|
||||||
stack []i64
|
floats []f64
|
||||||
sp int
|
closures []Closure
|
||||||
bp int
|
stack []i64
|
||||||
ip int
|
sp int
|
||||||
trace bool
|
bp int
|
||||||
halted bool
|
ip int
|
||||||
|
trace bool
|
||||||
|
halted bool
|
||||||
|
prog_args []string
|
||||||
|
exit_code i64
|
||||||
|
did_exit bool
|
||||||
|
handlers []Handler
|
||||||
|
lines []obj.LineInfo // debug info: code offset -> source line
|
||||||
|
const_strs int // strings[0..const_strs] are bytecode constants, never collected
|
||||||
|
last_heap int // heap size at the last GC check (allocation trigger)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bool_i64(b bool) i64 {
|
fn bool_i64(b bool) i64 {
|
||||||
|
|||||||
+90
-16
@@ -1,49 +1,101 @@
|
|||||||
// value.v — tagged value encoding and helpers for the VuurRaaf VM.
|
// value.v — tagged value encoding and helpers for the VuurRaaf VM.
|
||||||
//
|
//
|
||||||
// Stack values are 64-bit tagged integers with two tag bits:
|
// Stack values are 64-bit tagged integers with three tag bits:
|
||||||
// low bits 00 -> encoded number (value = raw << 2)
|
// low bits 000 -> encoded integer (value = raw << 3)
|
||||||
// low bits 01 -> string handle (handle = value >> 2, into v.strings)
|
// low bits 001 -> string handle (handle = value >> 3, into v.strings)
|
||||||
// low bits 10 -> struct handle (handle = value >> 2, into v.structs)
|
// low bits 010 -> struct handle (handle = value >> 3, into v.structs)
|
||||||
// low bits 11 -> array handle (handle = value >> 2, into v.arrays)
|
// low bits 011 -> array handle (handle = value >> 3, into v.arrays)
|
||||||
|
// low bits 100 -> float handle (handle = value >> 3, into v.floats)
|
||||||
module vm
|
module vm
|
||||||
|
|
||||||
fn (mut v Vm) is_str(x i64) bool {
|
const tag_mask = u64(7)
|
||||||
return x & 3 == 1
|
const tag_int = u64(0)
|
||||||
|
const tag_str = u64(1)
|
||||||
|
const tag_struct = u64(2)
|
||||||
|
const tag_arr = u64(3)
|
||||||
|
const tag_float = u64(4)
|
||||||
|
const tag_closure = u64(5)
|
||||||
|
|
||||||
|
fn (mut v Vm) tag(x i64) u64 {
|
||||||
|
return u64(x) & tag_mask
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut v Vm) is_arr(x i64) bool {
|
fn (mut v Vm) is_int(x i64) bool {
|
||||||
return x & 3 == 3
|
return u64(x) & tag_mask == tag_int
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) is_str(x i64) bool {
|
||||||
|
return u64(x) & tag_mask == tag_str
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut v Vm) is_struct(x i64) bool {
|
fn (mut v Vm) is_struct(x i64) bool {
|
||||||
return x & 3 == 2
|
return u64(x) & tag_mask == tag_struct
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) is_arr(x i64) bool {
|
||||||
|
return u64(x) & tag_mask == tag_arr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) is_float(x i64) bool {
|
||||||
|
return u64(x) & tag_mask == tag_float
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) is_closure(x i64) bool {
|
||||||
|
return u64(x) & tag_mask == tag_closure
|
||||||
|
}
|
||||||
|
|
||||||
|
// is_num reports whether x is an integer (tag 0). Floats are a distinct type.
|
||||||
|
fn (mut v Vm) is_num(x i64) bool {
|
||||||
|
return u64(x) & tag_mask == tag_int
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut v Vm) enc_int(x i64) i64 {
|
fn (mut v Vm) enc_int(x i64) i64 {
|
||||||
return u64(x) << 2
|
return i64(u64(x) << 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut v Vm) dec_int(x i64) i64 {
|
fn (mut v Vm) dec_int(x i64) i64 {
|
||||||
return x >> 2
|
return x >> 3
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut v Vm) hand(x i64) int {
|
fn (mut v Vm) hand(x i64) int {
|
||||||
return int(x >> 2)
|
return int(x >> 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut v Vm) mkstr(idx int) i64 {
|
fn (mut v Vm) mkstr(idx int) i64 {
|
||||||
return (u64(idx) << 2) | 1
|
return i64((u64(idx) << 3) | tag_str)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut v Vm) mkarr(idx int) i64 {
|
fn (mut v Vm) mkarr(idx int) i64 {
|
||||||
return (u64(idx) << 2) | 3
|
return i64((u64(idx) << 3) | tag_arr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut v Vm) mkstruct_handle(idx int) i64 {
|
fn (mut v Vm) mkstruct_handle(idx int) i64 {
|
||||||
return (u64(idx) << 2) | 2
|
return i64((u64(idx) << 3) | tag_struct)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) mkfloat(idx int) i64 {
|
||||||
|
return i64((u64(idx) << 3) | tag_float)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) mkclosure(idx int) i64 {
|
||||||
|
return i64((u64(idx) << 3) | tag_closure)
|
||||||
|
}
|
||||||
|
|
||||||
|
// push_float interns a float into the pool and returns its tagged handle.
|
||||||
|
fn (mut v Vm) push_float(f f64) i64 {
|
||||||
|
v.floats << f
|
||||||
|
return v.mkfloat(v.floats.len - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fval returns the f64 value of a float handle.
|
||||||
|
fn (mut v Vm) fval(x i64) f64 {
|
||||||
|
return v.floats[v.hand(x)]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut v Vm) truthy(x i64) bool {
|
fn (mut v Vm) truthy(x i64) bool {
|
||||||
|
if v.is_float(x) {
|
||||||
|
return v.fval(x) != 0.0
|
||||||
|
}
|
||||||
return x != 0
|
return x != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,3 +113,25 @@ fn (mut v Vm) valid_struct_handle(x i64) bool {
|
|||||||
h := v.hand(x)
|
h := v.hand(x)
|
||||||
return h >= 0 && h < v.structs.len
|
return h >= 0 && h < v.structs.len
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) valid_float_handle(x i64) bool {
|
||||||
|
h := v.hand(x)
|
||||||
|
return h >= 0 && h < v.floats.len
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) valid_closure_handle(x i64) bool {
|
||||||
|
h := v.hand(x)
|
||||||
|
return h >= 0 && h < v.closures.len
|
||||||
|
}
|
||||||
|
|
||||||
|
// valid_handle_for bounds-checks a handle against the pool matching its tag.
|
||||||
|
fn (mut v Vm) valid_handle_for(x i64) bool {
|
||||||
|
return match v.tag(x) {
|
||||||
|
tag_str { v.valid_handle(x) }
|
||||||
|
tag_struct { v.valid_struct_handle(x) }
|
||||||
|
tag_arr { v.valid_arr_handle(x) }
|
||||||
|
tag_float { v.valid_float_handle(x) }
|
||||||
|
tag_closure { v.valid_closure_handle(x) }
|
||||||
|
else { true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,15 +6,25 @@
|
|||||||
module vm
|
module vm
|
||||||
|
|
||||||
import obj
|
import obj
|
||||||
|
import math
|
||||||
|
|
||||||
// run executes the function named `entry` from the executable `bin` and
|
// run executes the function named `entry` from the executable `bin` and
|
||||||
// returns its return value (0 if it never returns one).
|
// returns its return value (0 if it never returns one).
|
||||||
pub fn run(bin obj.Bin, entry string, trace bool) !i64 {
|
pub fn run(bin obj.Bin, entry string, trace bool) !i64 {
|
||||||
|
return run_with_args(bin, entry, trace, []string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// run_with_args is run() with command-line arguments exposed to the program
|
||||||
|
// via the `args()` builtin.
|
||||||
|
pub fn run_with_args(bin obj.Bin, entry string, trace bool, args []string) !i64 {
|
||||||
mut v := Vm{
|
mut v := Vm{
|
||||||
code: bin.code
|
code: bin.code
|
||||||
strings: bin.strings.clone()
|
strings: bin.strings.clone()
|
||||||
stack: []i64{len: stack_cap}
|
stack: []i64{len: stack_cap}
|
||||||
trace: trace
|
trace: trace
|
||||||
|
prog_args: args
|
||||||
|
lines: bin.lines
|
||||||
|
const_strs: bin.strings.len
|
||||||
}
|
}
|
||||||
mut entry_ip := -1
|
mut entry_ip := -1
|
||||||
for f in bin.fns {
|
for f in bin.fns {
|
||||||
@@ -38,15 +48,43 @@ pub fn run(bin obj.Bin, entry string, trace bool) !i64 {
|
|||||||
v.sp++
|
v.sp++
|
||||||
v.bp = v.sp
|
v.bp = v.sp
|
||||||
v.ip = entry_ip
|
v.ip = entry_ip
|
||||||
v.exec()!
|
v.exec() or {
|
||||||
|
return error('${err.msg()} at ${v.where()}')
|
||||||
|
}
|
||||||
|
if v.did_exit {
|
||||||
|
return v.exit_code
|
||||||
|
}
|
||||||
if v.sp > 0 {
|
if v.sp > 0 {
|
||||||
return v.dec_int(v.stack[0])
|
return v.dec_int(v.stack[0])
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// where returns a source-level location for the current instruction pointer:
|
||||||
|
// `line 12 (ip 345)` when debug info is available, otherwise just `(ip 345)`.
|
||||||
|
fn (v Vm) where() string {
|
||||||
|
// line table entries are recorded in code order, so walk backwards from
|
||||||
|
// the most recent entry to find the last one at or before v.ip
|
||||||
|
for i := v.lines.len - 1; i >= 0; i-- {
|
||||||
|
if v.ip >= int(v.lines[i].off) {
|
||||||
|
return 'line ${v.lines[i].line} (ip ${v.ip})'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '(ip ${v.ip})'
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut v Vm) exec() ! {
|
fn (mut v Vm) exec() ! {
|
||||||
for !v.halted {
|
for !v.halted {
|
||||||
|
// garbage collection: runs between opcodes when the heap has grown by
|
||||||
|
// gc_alloc_trigger entries since the last collection, so no live value
|
||||||
|
// is ever mid-flight in an instruction handler
|
||||||
|
heap := v.strings.len + v.arrays.len + v.structs.len + v.floats.len + v.closures.len
|
||||||
|
if heap > v.last_heap + gc_alloc_trigger {
|
||||||
|
v.collect()
|
||||||
|
v.last_heap = v.strings.len + v.arrays.len + v.structs.len + v.floats.len + v.closures.len
|
||||||
|
} else {
|
||||||
|
v.last_heap = heap
|
||||||
|
}
|
||||||
op := v.code[v.ip]
|
op := v.code[v.ip]
|
||||||
if v.trace {
|
if v.trace {
|
||||||
v.trace_op(op)
|
v.trace_op(op)
|
||||||
@@ -64,6 +102,11 @@ fn (mut v Vm) exec() ! {
|
|||||||
idx := int(v.read_i64())
|
idx := int(v.read_i64())
|
||||||
v.push(v.mkstr(idx))!
|
v.push(v.mkstr(idx))!
|
||||||
}
|
}
|
||||||
|
op_push_f {
|
||||||
|
v.ip++
|
||||||
|
f := v.read_f64()
|
||||||
|
v.push(v.push_float(f))!
|
||||||
|
}
|
||||||
op_load {
|
op_load {
|
||||||
v.ip++
|
v.ip++
|
||||||
idx := int(v.read_i64())
|
idx := int(v.read_i64())
|
||||||
@@ -117,16 +160,17 @@ fn (mut v Vm) exec() ! {
|
|||||||
op_neg {
|
op_neg {
|
||||||
v.ip++
|
v.ip++
|
||||||
a := v.pop()!
|
a := v.pop()!
|
||||||
if v.is_str(a) {
|
if v.is_float(a) {
|
||||||
|
v.push(v.push_float(-v.fval(a)))!
|
||||||
|
} else if v.is_str(a) {
|
||||||
return error('cannot negate a string')
|
return error('cannot negate a string')
|
||||||
}
|
} else if v.is_arr(a) {
|
||||||
if v.is_arr(a) {
|
|
||||||
return error('cannot negate an array')
|
return error('cannot negate an array')
|
||||||
}
|
} else if v.is_struct(a) {
|
||||||
if v.is_struct(a) {
|
|
||||||
return error('cannot negate a struct')
|
return error('cannot negate a struct')
|
||||||
|
} else {
|
||||||
|
v.push(v.enc_int(-v.dec_int(a)))!
|
||||||
}
|
}
|
||||||
v.push(v.enc_int(-v.dec_int(a)))!
|
|
||||||
}
|
}
|
||||||
op_eq {
|
op_eq {
|
||||||
v.ip++
|
v.ip++
|
||||||
@@ -247,14 +291,22 @@ fn (mut v Vm) exec() ! {
|
|||||||
v.ip++
|
v.ip++
|
||||||
idx := int(v.dec_int(v.pop()!))
|
idx := int(v.dec_int(v.pop()!))
|
||||||
h := v.pop()!
|
h := v.pop()!
|
||||||
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
if v.is_arr(h) && v.valid_arr_handle(h) {
|
||||||
return error('indexing a non-array value')
|
a := v.arrays[v.hand(h)]
|
||||||
|
if idx < 0 || idx >= a.len {
|
||||||
|
return error('array index ${idx} out of bounds (len ${a.len})')
|
||||||
|
}
|
||||||
|
v.push(a[idx])!
|
||||||
|
} else if v.is_str(h) && v.valid_handle(h) {
|
||||||
|
// rune-based string indexing: s[i] is the i-th character
|
||||||
|
runes := v.strings[v.hand(h)].runes()
|
||||||
|
if idx < 0 || idx >= runes.len {
|
||||||
|
return error('string index ${idx} out of bounds (len ${runes.len})')
|
||||||
|
}
|
||||||
|
v.push(v.alloc_str(runes[idx].str()))!
|
||||||
|
} else {
|
||||||
|
return error('indexing a non-array, non-string value')
|
||||||
}
|
}
|
||||||
a := v.arrays[v.hand(h)]
|
|
||||||
if idx < 0 || idx >= a.len {
|
|
||||||
return error('array index ${idx} out of bounds (len ${a.len})')
|
|
||||||
}
|
|
||||||
v.push(a[idx])!
|
|
||||||
}
|
}
|
||||||
op_aset {
|
op_aset {
|
||||||
v.ip++
|
v.ip++
|
||||||
@@ -276,8 +328,10 @@ fn (mut v Vm) exec() ! {
|
|||||||
v.push(v.enc_int(i64(v.arrays[v.hand(h)].len)))!
|
v.push(v.enc_int(i64(v.arrays[v.hand(h)].len)))!
|
||||||
} else if v.is_struct(h) && v.valid_struct_handle(h) {
|
} else if v.is_struct(h) && v.valid_struct_handle(h) {
|
||||||
v.push(v.enc_int(i64(v.structs[v.hand(h)].fields.len)))!
|
v.push(v.enc_int(i64(v.structs[v.hand(h)].fields.len)))!
|
||||||
|
} else if v.is_str(h) && v.valid_handle(h) {
|
||||||
|
v.push(v.enc_int(i64(v.strings[v.hand(h)].runes().len)))!
|
||||||
} else {
|
} else {
|
||||||
return error('len() on a non-array, non-struct value')
|
return error('len() on a non-array, non-struct, non-string value')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
op_apush {
|
op_apush {
|
||||||
@@ -370,6 +424,139 @@ fn (mut v Vm) exec() ! {
|
|||||||
op_skeys {
|
op_skeys {
|
||||||
v.op_skeys()!
|
v.op_skeys()!
|
||||||
}
|
}
|
||||||
|
op_slice {
|
||||||
|
v.op_slice()!
|
||||||
|
}
|
||||||
|
op_native {
|
||||||
|
v.ip++
|
||||||
|
id := int(v.read_i64())
|
||||||
|
argc := int(v.read_i64())
|
||||||
|
v.native(id, argc)!
|
||||||
|
}
|
||||||
|
op_and_b {
|
||||||
|
v.ip++
|
||||||
|
b := v.pop()!
|
||||||
|
a := v.pop()!
|
||||||
|
v.push(v.enc_int(v.dec_int(a) & v.dec_int(b)))!
|
||||||
|
}
|
||||||
|
op_or_b {
|
||||||
|
v.ip++
|
||||||
|
b := v.pop()!
|
||||||
|
a := v.pop()!
|
||||||
|
v.push(v.enc_int(v.dec_int(a) | v.dec_int(b)))!
|
||||||
|
}
|
||||||
|
op_xor {
|
||||||
|
v.ip++
|
||||||
|
b := v.pop()!
|
||||||
|
a := v.pop()!
|
||||||
|
v.push(v.enc_int(v.dec_int(a) ^ v.dec_int(b)))!
|
||||||
|
}
|
||||||
|
op_shl {
|
||||||
|
v.ip++
|
||||||
|
b := v.pop()!
|
||||||
|
a := v.pop()!
|
||||||
|
x := v.dec_int(a)
|
||||||
|
y := u32(v.dec_int(b))
|
||||||
|
v.push(v.enc_int(x << y))!
|
||||||
|
}
|
||||||
|
op_shr {
|
||||||
|
v.ip++
|
||||||
|
b := v.pop()!
|
||||||
|
a := v.pop()!
|
||||||
|
x := v.dec_int(a)
|
||||||
|
y := u32(v.dec_int(b))
|
||||||
|
v.push(v.enc_int(x >> y))!
|
||||||
|
}
|
||||||
|
op_not_b {
|
||||||
|
v.ip++
|
||||||
|
a := v.pop()!
|
||||||
|
v.push(v.enc_int(~v.dec_int(a)))!
|
||||||
|
}
|
||||||
|
op_try {
|
||||||
|
v.ip++
|
||||||
|
catch_ip := int(v.read_i64())
|
||||||
|
v.handlers << Handler{ ip: catch_ip, bp: v.bp, sp: v.sp }
|
||||||
|
}
|
||||||
|
op_throw {
|
||||||
|
v.ip++
|
||||||
|
err_val := v.pop()!
|
||||||
|
if v.handlers.len == 0 {
|
||||||
|
return error('unhandled throw: ${v.val_str(err_val, 0)}')
|
||||||
|
}
|
||||||
|
h := v.handlers[v.handlers.len - 1]
|
||||||
|
v.handlers.delete_last()
|
||||||
|
v.bp = h.bp
|
||||||
|
v.sp = h.sp
|
||||||
|
v.push(err_val)!
|
||||||
|
v.ip = h.ip
|
||||||
|
}
|
||||||
|
op_catch_done {
|
||||||
|
v.ip++
|
||||||
|
if v.handlers.len > 0 {
|
||||||
|
v.handlers.delete_last()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
op_closure {
|
||||||
|
v.ip++
|
||||||
|
entry := int(v.read_i64())
|
||||||
|
v.closures << Closure{ entry: entry }
|
||||||
|
v.push(v.mkclosure(v.closures.len - 1))!
|
||||||
|
}
|
||||||
|
op_call_closure {
|
||||||
|
v.ip++
|
||||||
|
argc := int(v.read_i64())
|
||||||
|
// stack: [...closure, arg_0, ..., arg_{argc-1}]
|
||||||
|
h := v.stack[v.sp - argc - 1]
|
||||||
|
if !v.is_closure(h) || !v.valid_closure_handle(h) {
|
||||||
|
return error('cannot call a non-function value')
|
||||||
|
}
|
||||||
|
entry := v.closures[v.hand(h)].entry
|
||||||
|
// Shift args left to overwrite the closure slot, then
|
||||||
|
// insert a return-value placeholder so the callee's
|
||||||
|
// retv never overwrites the caller's local that held
|
||||||
|
// the closure handle.
|
||||||
|
for i := 0; i < argc; i++ {
|
||||||
|
v.stack[v.sp - argc - 1 + i] = v.stack[v.sp - argc + i]
|
||||||
|
}
|
||||||
|
v.stack[v.sp - 1] = v.enc_int(0) // return-value placeholder
|
||||||
|
v.sp-- // closure was removed; unwind one slot
|
||||||
|
v.call(entry, argc)
|
||||||
|
}
|
||||||
|
op_argc {
|
||||||
|
v.ip++
|
||||||
|
argc := v.dec_int(v.stack[v.bp - 1])
|
||||||
|
v.push(v.enc_int(argc))!
|
||||||
|
}
|
||||||
|
op_load_dyn {
|
||||||
|
v.ip++
|
||||||
|
idx := int(v.dec_int(v.pop()!))
|
||||||
|
if v.bp + idx < 0 || v.bp + idx >= v.sp {
|
||||||
|
return error('dynamic load index ${idx} out of range')
|
||||||
|
}
|
||||||
|
v.push(v.stack[v.bp + idx])!
|
||||||
|
}
|
||||||
|
op_varargs {
|
||||||
|
v.ip++
|
||||||
|
named := int(v.read_i64())
|
||||||
|
dst := int(v.read_i64())
|
||||||
|
argc := int(v.dec_int(v.stack[v.bp - 1]))
|
||||||
|
mut n := argc - named
|
||||||
|
if n < 0 {
|
||||||
|
n = 0
|
||||||
|
}
|
||||||
|
mut arr := []i64{len: n}
|
||||||
|
for i in 0..n {
|
||||||
|
arr[i] = v.stack[v.bp + named + i]
|
||||||
|
}
|
||||||
|
v.arrays << arr
|
||||||
|
v.stack[v.bp + dst] = v.mkarr(v.arrays.len - 1)
|
||||||
|
}
|
||||||
|
op_str_method {
|
||||||
|
v.ip++
|
||||||
|
sidx := int(v.read_i64())
|
||||||
|
argc := int(v.read_i64())
|
||||||
|
v.str_method(v.strings[sidx], argc)!
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
return error('unknown opcode ${op} at ip ${v.ip}')
|
return error('unknown opcode ${op} at ip ${v.ip}')
|
||||||
}
|
}
|
||||||
@@ -453,6 +640,60 @@ fn (mut v Vm) op_skeys() ! {
|
|||||||
v.push(v.mkarr(v.arrays.len - 1))!
|
v.push(v.mkarr(v.arrays.len - 1))!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// op_slice slices an array or string: stack = [value, start, end] → sliced value.
|
||||||
|
// end == -1 means "open-ended" (slice to the end).
|
||||||
|
fn (mut v Vm) op_slice() ! {
|
||||||
|
v.ip++
|
||||||
|
end_val := v.dec_int(v.pop()!)
|
||||||
|
start_val := v.dec_int(v.pop()!)
|
||||||
|
h := v.pop()!
|
||||||
|
// --- array slicing ---
|
||||||
|
if v.is_arr(h) && v.valid_arr_handle(h) {
|
||||||
|
arr := v.arrays[v.hand(h)]
|
||||||
|
mut s := if start_val < 0 { 0 } else { int(start_val) }
|
||||||
|
mut e := if end_val < 0 { arr.len } else { int(end_val) }
|
||||||
|
if s > arr.len {
|
||||||
|
s = arr.len
|
||||||
|
}
|
||||||
|
if e > arr.len {
|
||||||
|
e = arr.len
|
||||||
|
}
|
||||||
|
if s > e {
|
||||||
|
e = s
|
||||||
|
}
|
||||||
|
mut sliced := []i64{}
|
||||||
|
for i in s..e {
|
||||||
|
sliced << arr[i]
|
||||||
|
}
|
||||||
|
v.arrays << sliced
|
||||||
|
v.push(v.mkarr(v.arrays.len - 1))!
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// --- string slicing ---
|
||||||
|
if v.is_str(h) && v.valid_handle(h) {
|
||||||
|
src := v.strings[v.hand(h)]
|
||||||
|
runes := src.runes()
|
||||||
|
mut s := if start_val < 0 { 0 } else { int(start_val) }
|
||||||
|
mut e := if end_val < 0 { runes.len } else { int(end_val) }
|
||||||
|
if s > runes.len {
|
||||||
|
s = runes.len
|
||||||
|
}
|
||||||
|
if e > runes.len {
|
||||||
|
e = runes.len
|
||||||
|
}
|
||||||
|
if s > e {
|
||||||
|
e = s
|
||||||
|
}
|
||||||
|
mut sliced := ''
|
||||||
|
for i in s..e {
|
||||||
|
sliced += runes[i].str()
|
||||||
|
}
|
||||||
|
v.push(v.alloc_str(sliced))!
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return error('slice() on a non-array, non-string value')
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut v Vm) read_i64() i64 {
|
fn (mut v Vm) read_i64() i64 {
|
||||||
mut val := u64(0)
|
mut val := u64(0)
|
||||||
for i in 0..8 {
|
for i in 0..8 {
|
||||||
@@ -462,6 +703,15 @@ fn (mut v Vm) read_i64() i64 {
|
|||||||
return i64(val)
|
return i64(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) read_f64() f64 {
|
||||||
|
mut val := u64(0)
|
||||||
|
for i in 0..8 {
|
||||||
|
val |= u64(v.code[v.ip + i]) << u32(8 * i)
|
||||||
|
}
|
||||||
|
v.ip += 8
|
||||||
|
return math.f64_from_bits(val)
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut v Vm) push(x i64) ! {
|
fn (mut v Vm) push(x i64) ! {
|
||||||
if v.sp >= v.stack.len {
|
if v.sp >= v.stack.len {
|
||||||
return error('stack overflow')
|
return error('stack overflow')
|
||||||
|
|||||||
Reference in New Issue
Block a user