mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 15:17:19 +00:00
Added default json, etc.
This commit is contained in:
@@ -260,6 +260,16 @@ fn main() {
|
||||
- bitwise operators: `& | ^ ~ << >>`
|
||||
- host builtins: `read_file` / `write_file`, `args()`, `getenv` / `setenv`,
|
||||
`exit`, `sleep`, `time()`, `type(x)`, `str(x)`, `int(x)`, `split` / `join`
|
||||
- JSON: `json_encode(x)` / `json_decode(s)` — objects become structs, arrays
|
||||
become arrays, integral numbers decode as ints, `null` decodes to `none`
|
||||
- `none`: a literal for "no value" (JSON null); `x == none` compares,
|
||||
and it renders as `none` / encodes as `null`
|
||||
- string formatting: `format(x, "%.2f")` (printf-style: `%d %i %f %s %x %X`,
|
||||
width, `-`/`0` flags, precision), `replace`, `split_lines`, `pad` /
|
||||
`pad_left`, `repeat` — all also available as string methods (`s.replace()`,
|
||||
`s.pad(4)`, ...)
|
||||
- native builtin errors (failed `read_file`, `json_decode`, ...) are caught by
|
||||
`try { } catch e { }` like explicit `throw`s
|
||||
- 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
|
||||
- `break` / `continue` inside `while` and `for` loops (in `for` loops
|
||||
@@ -351,5 +361,5 @@ vm/native.v host builtins incl. the build_* (.vrmm) builtins
|
||||
v.mod module definition
|
||||
compiler/ assembler/ linker/ vm/ obj/ the toolchain itself
|
||||
bin/ built binary + standalone tools
|
||||
examples/ runnable examples (hello, lib, asm, tests, fib)
|
||||
examples/ runnable examples (hello, lib, asm, tests, fib, json)
|
||||
```
|
||||
|
||||
@@ -6,6 +6,7 @@ pub enum ExprKind {
|
||||
float_lit
|
||||
str_lit
|
||||
bool_lit
|
||||
none_lit
|
||||
ident
|
||||
array_lit
|
||||
struct_lit
|
||||
|
||||
+17
-8
@@ -18,6 +18,7 @@ enum CType {
|
||||
float_t
|
||||
string_t
|
||||
bool_t
|
||||
none_t
|
||||
array_t
|
||||
struct_t
|
||||
enum_t
|
||||
@@ -221,7 +222,7 @@ fn (mut c Checker) check_stmt(st Stmt) ! {
|
||||
.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 {
|
||||
if seq.kind == .int_t || seq.kind == .float_t || seq.kind == .bool_t || seq.kind == .none_t {
|
||||
return error('cannot iterate a ${type_name(seq.kind)} (line ${st.line})')
|
||||
}
|
||||
c.types[st.target] = TypeInfo{ kind: .unknown }
|
||||
@@ -271,6 +272,7 @@ fn (mut c Checker) check_expr(e Expr) !TypeInfo {
|
||||
.float_lit { TypeInfo{ kind: .float_t } }
|
||||
.str_lit { TypeInfo{ kind: .string_t } }
|
||||
.bool_lit { TypeInfo{ kind: .bool_t } }
|
||||
.none_lit { TypeInfo{ kind: .none_t } }
|
||||
.ident {
|
||||
if e.name in c.types {
|
||||
c.types[e.name]
|
||||
@@ -324,7 +326,7 @@ fn (mut c Checker) check_expr(e Expr) !TypeInfo {
|
||||
}
|
||||
.method_call {
|
||||
recv := c.check_expr(*e.left)!
|
||||
if recv.kind == .int_t || recv.kind == .float_t || recv.kind == .bool_t {
|
||||
if recv.kind == .int_t || recv.kind == .float_t || recv.kind == .bool_t || recv.kind == .none_t {
|
||||
return error('cannot call a method on a ${type_name(recv.kind)} (line ${e.line})')
|
||||
}
|
||||
for a in e.args {
|
||||
@@ -336,7 +338,7 @@ fn (mut c Checker) check_expr(e Expr) !TypeInfo {
|
||||
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 {
|
||||
if base.kind == .int_t || base.kind == .float_t || base.kind == .bool_t || base.kind == .none_t {
|
||||
return error('cannot slice a ${type_name(base.kind)} (line ${e.line})')
|
||||
}
|
||||
if base.kind == .string_t {
|
||||
@@ -424,6 +426,9 @@ fn (mut c Checker) check_binary(e Expr) !TypeInfo {
|
||||
if l.kind == .bool_t && r.kind == .bool_t {
|
||||
return error('cannot order booleans (line ${e.line})')
|
||||
}
|
||||
if l.kind == .none_t || r.kind == .none_t {
|
||||
return error('cannot order a none (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})')
|
||||
}
|
||||
@@ -450,7 +455,7 @@ fn (mut c Checker) check_call(e Expr) !TypeInfo {
|
||||
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 {
|
||||
if t.kind == .int_t || t.kind == .float_t || t.kind == .bool_t || t.kind == .none_t || t.kind == .closure_t {
|
||||
return error('len() on a ${type_name(t.kind)} (line ${e.line})')
|
||||
}
|
||||
return TypeInfo{ kind: .int_t }
|
||||
@@ -552,6 +557,9 @@ fn builtin_result_type(name string) TypeInfo {
|
||||
'args', 'keys' { TypeInfo{ kind: .array_t } }
|
||||
'len' { TypeInfo{ kind: .int_t } }
|
||||
'write_file', 'setenv', 'exit', 'sleep', 'eprint' { TypeInfo{ kind: .unknown } }
|
||||
// stdlib: JSON + string formatting
|
||||
'json_encode', 'format', 'replace', 'pad', 'pad_left', 'repeat' { TypeInfo{ kind: .string_t } }
|
||||
'json_decode', 'split_lines' { TypeInfo{ kind: .unknown } }
|
||||
// build-module builtins (.vrmm)
|
||||
'build_compile', 'build_assemble', 'build_link', 'build_exec', 'build_base',
|
||||
'build_dir', 'build_join', 'build_root' { TypeInfo{ kind: .string_t } }
|
||||
@@ -576,30 +584,31 @@ fn type_name(k CType) string {
|
||||
.struct_t { 'struct' }
|
||||
.enum_t { 'enum' }
|
||||
.closure_t { 'function' }
|
||||
.none_t { 'none' }
|
||||
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 {
|
||||
if t.kind == .string_t || t.kind == .array_t || t.kind == .struct_t || t.kind == .bool_t || t.kind == .none_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 {
|
||||
if t.kind == .string_t || t.kind == .array_t || t.kind == .struct_t || t.kind == .bool_t || t.kind == .none_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 {
|
||||
if t.kind == .int_t || t.kind == .float_t || t.kind == .bool_t || t.kind == .none_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 {
|
||||
if t.kind == .int_t || t.kind == .float_t || t.kind == .bool_t || t.kind == .string_t || t.kind == .none_t || t.kind == .closure_t {
|
||||
return error('${what} on a ${type_name(t.kind)} (line ${line})')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,6 +723,9 @@ fn (mut g Gen) gen_expr(e Expr) ! {
|
||||
g.code << op_push_i
|
||||
g.code << obj.encode_i64(e.int_v)
|
||||
}
|
||||
.none_lit {
|
||||
g.code << op_push_none
|
||||
}
|
||||
.ident {
|
||||
// check if it's a constant
|
||||
if e.name in g.consts {
|
||||
@@ -923,6 +926,15 @@ fn builtin_spec(name string) (int, int) {
|
||||
'build_dir' { native_build_dir, 1 }
|
||||
'build_join' { native_build_join, 2 }
|
||||
'build_root' { native_build_root, 0 }
|
||||
// stdlib: JSON + string formatting
|
||||
'json_encode' { native_json_encode, 1 }
|
||||
'json_decode' { native_json_decode, 1 }
|
||||
'format' { native_format, 2 }
|
||||
'replace' { native_replace, 3 }
|
||||
'split_lines' { native_split_lines, 1 }
|
||||
'pad' { native_pad, 2 }
|
||||
'pad_left' { native_pad_left, 2 }
|
||||
'repeat' { native_repeat, 2 }
|
||||
else { -1, 0 }
|
||||
}
|
||||
}
|
||||
@@ -1153,6 +1165,7 @@ fn (mut g Gen) expr_type(e Expr) string {
|
||||
'upper', 'lower', 'trim', 'str', 'getenv', 'read_file', 'join' { 'string' }
|
||||
'build_compile', 'build_assemble', 'build_link', 'build_exec', 'build_base',
|
||||
'build_dir', 'build_join', 'build_root' { 'string' }
|
||||
'json_encode', 'format', 'replace', 'pad', 'pad_left', 'repeat' { 'string' }
|
||||
else { '' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +291,7 @@ fn (mut l Lexer) lex_ident(line int, col int) Tok {
|
||||
'return' { TokKind.kw_return }
|
||||
'true' { TokKind.kw_true }
|
||||
'false' { TokKind.kw_false }
|
||||
'none' { TokKind.kw_none }
|
||||
'and' { TokKind.kw_and }
|
||||
'or' { TokKind.kw_or }
|
||||
'not' { TokKind.kw_not }
|
||||
|
||||
@@ -65,6 +65,7 @@ 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), ...)
|
||||
const op_push_none = u8(62) // push the `none` sentinel
|
||||
|
||||
// native builtin ids (keep in sync with vm/opcodes.v)
|
||||
const native_abs = 100
|
||||
@@ -127,3 +128,13 @@ const native_build_base = 153
|
||||
const native_build_dir = 154
|
||||
const native_build_join = 155
|
||||
const native_build_root = 156
|
||||
|
||||
// stdlib builtins (JSON + string formatting)
|
||||
const native_json_encode = 157
|
||||
const native_json_decode = 158
|
||||
const native_format = 159
|
||||
const native_replace = 160
|
||||
const native_split_lines = 161
|
||||
const native_pad = 162
|
||||
const native_pad_left = 163
|
||||
const native_repeat = 164
|
||||
|
||||
@@ -787,6 +787,10 @@ fn (mut p Parser) parse_primary() !Expr {
|
||||
p.advance()
|
||||
return Expr{ kind: .bool_lit, int_v: 0, line: t.line }
|
||||
}
|
||||
.kw_none {
|
||||
p.advance()
|
||||
return Expr{ kind: .none_lit, line: t.line }
|
||||
}
|
||||
.lparen {
|
||||
p.advance()
|
||||
e := p.parse_expr()!
|
||||
|
||||
@@ -55,6 +55,7 @@ pub enum TokKind {
|
||||
kw_return
|
||||
kw_true
|
||||
kw_false
|
||||
kw_none
|
||||
kw_and
|
||||
kw_or
|
||||
kw_not
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// json.vr — the stdlib JSON + formatting builtins.
|
||||
// vr run examples/json.vr
|
||||
|
||||
fn main() {
|
||||
// --- encode: structs -> objects, arrays -> arrays, none -> null ---
|
||||
let user = {
|
||||
name: "amy",
|
||||
age: 30,
|
||||
active: 1,
|
||||
score: 87.5,
|
||||
tags: ["admin", "beta"],
|
||||
meta: { last_seen: none }
|
||||
}
|
||||
println(json_encode(user))
|
||||
// {"name":"amy","age":30,"active":1,"score":87.5,"tags":["admin","beta"],"meta":{"last_seen":null}}
|
||||
|
||||
// --- decode: objects become structs, numbers become ints when integral ---
|
||||
let parsed = json_decode("{\"name\": \"bob\", \"age\": 42, \"scores\": [1, 2.5, 3]}")
|
||||
println(parsed.name)
|
||||
println(parsed.age)
|
||||
println(parsed.scores[1])
|
||||
println(json_encode(parsed))
|
||||
|
||||
// null decodes to `none` — test with ==
|
||||
let maybe = json_decode("{\"value\": null}")
|
||||
if maybe.value == none {
|
||||
println("field is none")
|
||||
}
|
||||
|
||||
// --- format: printf-style ---
|
||||
println(format(3.14159, "%.2f")) // 3.14
|
||||
println(format(42, "%05d")) // 00042
|
||||
println(format(255, "%X")) // FF
|
||||
println(format("right", "%10s")) // right
|
||||
println(format("left", "%-10s") + "|") // left |
|
||||
|
||||
// --- string tools ---
|
||||
println("hello world".replace("l", "L"))
|
||||
println(len(split_lines("a\nb\nc")))
|
||||
println(pad_left("42", 5) + "|")
|
||||
println("ab".repeat(3))
|
||||
|
||||
// --- json + formatting together ---
|
||||
let total = 1234.5
|
||||
println("total: " + format(total, "%.2f"))
|
||||
}
|
||||
@@ -226,6 +226,87 @@ fn test_structs() {
|
||||
assert a != c
|
||||
}
|
||||
|
||||
fn test_json() {
|
||||
// encoding
|
||||
assert json_encode(42) == "42"
|
||||
assert json_encode(3.14) == "3.14"
|
||||
assert json_encode(1.0) == "1"
|
||||
assert json_encode("hi") == "\"hi\""
|
||||
assert json_encode([1, "two", 3.5]) == "[1,\"two\",3.5]"
|
||||
let rec = { name: "amy", age: 30 }
|
||||
assert json_encode(rec) == "{\"name\":\"amy\",\"age\":30}"
|
||||
assert json_encode(none) == "null"
|
||||
|
||||
// decoding
|
||||
assert json_decode("42") == 42
|
||||
assert json_decode("3.14") == 3.14
|
||||
assert json_decode("\"hi\"") == "hi"
|
||||
assert json_decode("true") == 1
|
||||
assert json_decode("null") == none
|
||||
let obj = json_decode("{\"x\": 1, \"y\": [1, 2], \"z\": {\"deep\": true}}")
|
||||
assert obj.x == 1
|
||||
assert obj.y[1] == 2
|
||||
assert obj.z.deep == 1
|
||||
|
||||
// round-trip
|
||||
assert json_encode(json_decode("{\"a\": 1, \"b\": [2, 3]}")) == "{\"a\":1,\"b\":[2,3]}"
|
||||
|
||||
// unicode escapes
|
||||
assert json_decode("\"\\u0041\"") == "A"
|
||||
assert json_decode("\"\\ud83d\\ude00\"") == "😀"
|
||||
}
|
||||
|
||||
fn test_format() {
|
||||
assert format(42, "%d") == "42"
|
||||
assert format(42, "%05d") == "00042"
|
||||
assert format(-42, "%05d") == "-0042"
|
||||
assert format(42, "%-5d") == "42 "
|
||||
assert format(3.14159, "%.2f") == "3.14"
|
||||
assert format(3.0, "%.2f") == "3.00"
|
||||
assert format(255, "%X") == "FF"
|
||||
assert format("hi", "%s") == "hi"
|
||||
assert format(7, "%5d") == " 7"
|
||||
}
|
||||
|
||||
fn test_string_tools() {
|
||||
assert replace("hello world", "l", "L") == "heLLo worLd"
|
||||
assert "hello world".replace("o", "0") == "hell0 w0rld"
|
||||
assert len(split_lines("a\nb\nc")) == 3
|
||||
assert "a\nb".split_lines()[1] == "b"
|
||||
assert pad("x", 4) == "x "
|
||||
assert "x".pad(4) == "x "
|
||||
assert pad_left("x", 4) == " x"
|
||||
assert "x".pad_left(4) == " x"
|
||||
assert repeat("ab", 3) == "ababab"
|
||||
assert "ab".repeat(2) == "abab"
|
||||
assert pad("toolong", 2) == "toolong"
|
||||
}
|
||||
|
||||
fn test_none() {
|
||||
assert none == none
|
||||
assert none == 0 == false
|
||||
assert none != 5
|
||||
if none {
|
||||
assert false
|
||||
}
|
||||
assert not (none == 1)
|
||||
}
|
||||
|
||||
fn test_native_errors_are_catchable() {
|
||||
try {
|
||||
read_file("/no/such/file.vr")
|
||||
assert false
|
||||
} catch e {
|
||||
assert len(e) > 0
|
||||
}
|
||||
try {
|
||||
json_decode("not json")
|
||||
assert false
|
||||
} catch e {
|
||||
assert len(e) > 0
|
||||
}
|
||||
}
|
||||
|
||||
fn test_failing() {
|
||||
// this one is meant to fail — shows up in `vr test` output
|
||||
assert 1 == 2
|
||||
|
||||
@@ -0,0 +1,450 @@
|
||||
// json.v — JSON encoding and decoding for the VuurRaaf VM.
|
||||
//
|
||||
// The decoder is self-contained (no json2) because json2.decode[Any] can loop
|
||||
// forever on truncated input; this parser is strictly bounded — every loop
|
||||
// consumes input, so malformed documents always fail with a position.
|
||||
//
|
||||
// Values map 1:1 to the language: objects -> structs (keys in input order,
|
||||
// last duplicate wins), arrays -> arrays, strings -> strings, numbers -> int
|
||||
// when integral (42, 42.0) else float, true/false -> 1/0, null -> `none`.
|
||||
module vm
|
||||
|
||||
import math
|
||||
|
||||
// JsonParser walks a JSON document one byte at a time.
|
||||
struct JsonParser {
|
||||
mut:
|
||||
src string
|
||||
pos int
|
||||
}
|
||||
|
||||
// json_parse decodes a whole JSON document into a VM value.
|
||||
fn (mut v Vm) json_parse(s string) !i64 {
|
||||
mut p := JsonParser{ src: s }
|
||||
p.skip_ws()
|
||||
if p.pos >= p.src.len {
|
||||
return p.error('empty input')
|
||||
}
|
||||
val := v.json_parse_value(mut p)!
|
||||
p.skip_ws()
|
||||
if p.pos < p.src.len {
|
||||
return p.error('trailing data after the value')
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
fn (mut p JsonParser) peek() u8 {
|
||||
if p.pos >= p.src.len {
|
||||
return 0
|
||||
}
|
||||
return p.src[p.pos]
|
||||
}
|
||||
|
||||
fn (mut p JsonParser) skip_ws() {
|
||||
for p.pos < p.src.len {
|
||||
c := p.src[p.pos]
|
||||
if c == ` ` || c == `\t` || c == `\n` || c == `\r` {
|
||||
p.pos++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// error builds a parse error with the current line and column.
|
||||
fn (mut p JsonParser) error(msg string) IError {
|
||||
mut line := 1
|
||||
mut col := 1
|
||||
for i in 0..p.pos {
|
||||
if p.src[i] == `\n` {
|
||||
line++
|
||||
col = 1
|
||||
} else {
|
||||
col++
|
||||
}
|
||||
}
|
||||
return error('${msg} at line ${line}, col ${col}')
|
||||
}
|
||||
|
||||
fn (mut p JsonParser) expect_word(w string) ! {
|
||||
if p.pos + w.len > p.src.len || p.src[p.pos..p.pos + w.len] != w {
|
||||
return p.error('invalid token')
|
||||
}
|
||||
p.pos += w.len
|
||||
}
|
||||
|
||||
fn (mut v Vm) json_parse_value(mut p JsonParser) !i64 {
|
||||
p.skip_ws()
|
||||
match p.peek() {
|
||||
`{` {
|
||||
return v.json_parse_object(mut p)!
|
||||
}
|
||||
`[` {
|
||||
return v.json_parse_array(mut p)!
|
||||
}
|
||||
`"` {
|
||||
return v.alloc_str(p.parse_string()!)
|
||||
}
|
||||
`t` {
|
||||
p.expect_word('true')!
|
||||
return v.enc_int(1)
|
||||
}
|
||||
`f` {
|
||||
p.expect_word('false')!
|
||||
return v.enc_int(0)
|
||||
}
|
||||
`n` {
|
||||
p.expect_word('null')!
|
||||
return none_val
|
||||
}
|
||||
else {
|
||||
c := p.peek()
|
||||
if (c >= `0` && c <= `9`) || c == `-` {
|
||||
return v.json_parse_number(mut p)!
|
||||
}
|
||||
return p.error('unexpected character "${c.ascii_str()}"')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut v Vm) json_parse_object(mut p JsonParser) !i64 {
|
||||
p.pos++ // consume '{'
|
||||
mut fields := []Field{}
|
||||
p.skip_ws()
|
||||
if p.peek() == `}` {
|
||||
p.pos++
|
||||
v.structs << StructVal{ fields: fields }
|
||||
return v.mkstruct_handle(v.structs.len - 1)
|
||||
}
|
||||
for {
|
||||
p.skip_ws()
|
||||
if p.peek() != `"` {
|
||||
return p.error('expected a string key in object')
|
||||
}
|
||||
key := p.parse_string()!
|
||||
p.skip_ws()
|
||||
if p.peek() != `:` {
|
||||
return p.error('expected ":" after object key')
|
||||
}
|
||||
p.pos++
|
||||
val := v.json_parse_value(mut p)!
|
||||
// last duplicate key wins (like most JSON parsers)
|
||||
mut replaced := false
|
||||
for i in 0..fields.len {
|
||||
if fields[i].name == key {
|
||||
fields[i].val = val
|
||||
replaced = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !replaced {
|
||||
fields << Field{ name: key, val: val }
|
||||
}
|
||||
p.skip_ws()
|
||||
c := p.peek()
|
||||
if c == `,` {
|
||||
p.pos++
|
||||
continue
|
||||
}
|
||||
if c == `}` {
|
||||
p.pos++
|
||||
break
|
||||
}
|
||||
return p.error('expected "," or "}" in object')
|
||||
}
|
||||
v.structs << StructVal{ fields: fields }
|
||||
return v.mkstruct_handle(v.structs.len - 1)
|
||||
}
|
||||
|
||||
fn (mut v Vm) json_parse_array(mut p JsonParser) !i64 {
|
||||
p.pos++ // consume '['
|
||||
mut arr := []i64{}
|
||||
p.skip_ws()
|
||||
if p.peek() == `]` {
|
||||
p.pos++
|
||||
v.arrays << arr
|
||||
return v.mkarr(v.arrays.len - 1)
|
||||
}
|
||||
for {
|
||||
arr << v.json_parse_value(mut p)!
|
||||
p.skip_ws()
|
||||
c := p.peek()
|
||||
if c == `,` {
|
||||
p.pos++
|
||||
continue
|
||||
}
|
||||
if c == `]` {
|
||||
p.pos++
|
||||
break
|
||||
}
|
||||
return p.error('expected "," or "]" in array')
|
||||
}
|
||||
v.arrays << arr
|
||||
return v.mkarr(v.arrays.len - 1)
|
||||
}
|
||||
|
||||
fn (mut v Vm) json_parse_number(mut p JsonParser) !i64 {
|
||||
start := p.pos
|
||||
if p.peek() == `-` {
|
||||
p.pos++
|
||||
}
|
||||
mut digits := 0
|
||||
for p.pos < p.src.len && p.src[p.pos] >= `0` && p.src[p.pos] <= `9` {
|
||||
p.pos++
|
||||
digits++
|
||||
}
|
||||
if digits == 0 {
|
||||
return p.error('invalid number')
|
||||
}
|
||||
mut is_float := false
|
||||
if p.pos < p.src.len && p.src[p.pos] == `.` {
|
||||
is_float = true
|
||||
p.pos++
|
||||
mut fd := 0
|
||||
for p.pos < p.src.len && p.src[p.pos] >= `0` && p.src[p.pos] <= `9` {
|
||||
p.pos++
|
||||
fd++
|
||||
}
|
||||
if fd == 0 {
|
||||
return p.error('invalid number (missing digits after ".")')
|
||||
}
|
||||
}
|
||||
if p.pos < p.src.len && (p.src[p.pos] == `e` || p.src[p.pos] == `E`) {
|
||||
is_float = true
|
||||
p.pos++
|
||||
if p.pos < p.src.len && (p.src[p.pos] == `+` || p.src[p.pos] == `-`) {
|
||||
p.pos++
|
||||
}
|
||||
mut ed := 0
|
||||
for p.pos < p.src.len && p.src[p.pos] >= `0` && p.src[p.pos] <= `9` {
|
||||
p.pos++
|
||||
ed++
|
||||
}
|
||||
if ed == 0 {
|
||||
return p.error('invalid number (missing exponent digits)')
|
||||
}
|
||||
}
|
||||
raw := p.src[start..p.pos]
|
||||
if is_float {
|
||||
f := raw.f64()
|
||||
// integral floats become ints so `42` and `42.0` round-trip cleanly
|
||||
if f == math.floor(f) && math.abs(f) < 1e18 {
|
||||
return v.enc_int(i64(f))
|
||||
}
|
||||
return v.push_float(f)
|
||||
}
|
||||
return v.enc_int(raw.i64())
|
||||
}
|
||||
|
||||
fn (mut p JsonParser) parse_string() !string {
|
||||
p.pos++ // opening quote
|
||||
mut out := ''
|
||||
for {
|
||||
if p.pos >= p.src.len {
|
||||
return p.error('unterminated string')
|
||||
}
|
||||
c := p.src[p.pos]
|
||||
if c == `"` {
|
||||
p.pos++
|
||||
return out
|
||||
}
|
||||
if c == `\\` {
|
||||
p.pos++
|
||||
if p.pos >= p.src.len {
|
||||
return p.error('unterminated escape sequence')
|
||||
}
|
||||
e := p.src[p.pos]
|
||||
p.pos++
|
||||
match e {
|
||||
`"` {
|
||||
out += '"'
|
||||
}
|
||||
`\\` {
|
||||
out += '\\'
|
||||
}
|
||||
`/` {
|
||||
out += '/'
|
||||
}
|
||||
`b` {
|
||||
out += '\b'
|
||||
}
|
||||
`f` {
|
||||
out += '\f'
|
||||
}
|
||||
`n` {
|
||||
out += '\n'
|
||||
}
|
||||
`r` {
|
||||
out += '\r'
|
||||
}
|
||||
`t` {
|
||||
out += '\t'
|
||||
}
|
||||
`u` {
|
||||
out += p.parse_unicode_escape()!
|
||||
}
|
||||
else {
|
||||
return p.error('invalid escape "\\${e.ascii_str()}"')
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
// pass a UTF-8 code point through as raw bytes
|
||||
width := utf8_width(c)
|
||||
if width == 0 {
|
||||
return p.error('invalid UTF-8 byte')
|
||||
}
|
||||
out += p.src[p.pos..p.pos + width]
|
||||
p.pos += width
|
||||
}
|
||||
return p.error('unterminated string')
|
||||
}
|
||||
|
||||
// parse_unicode_escape handles \uXXXX, combining surrogate pairs so emoji and
|
||||
// astral characters decode correctly.
|
||||
fn (mut p JsonParser) parse_unicode_escape() !string {
|
||||
if p.pos + 4 > p.src.len {
|
||||
return p.error('invalid \\u escape')
|
||||
}
|
||||
hi := hex4(p.src[p.pos..p.pos + 4]) or { return p.error('invalid \\u escape') }
|
||||
p.pos += 4
|
||||
mut code := hi
|
||||
if hi >= 0xD800 && hi <= 0xDBFF {
|
||||
// high surrogate: combine with an immediately following low surrogate
|
||||
if p.pos + 6 <= p.src.len && p.src[p.pos] == `\\` && p.src[p.pos + 1] == `u` {
|
||||
lo := hex4(p.src[p.pos + 2..p.pos + 6]) or { return p.error('invalid \\u escape') }
|
||||
if lo >= 0xDC00 && lo <= 0xDFFF {
|
||||
code = 0x10000 + (hi - 0xD800) * 0x400 + (lo - 0xDC00)
|
||||
p.pos += 6
|
||||
}
|
||||
}
|
||||
} else if hi >= 0xDC00 && hi <= 0xDFFF {
|
||||
return p.error('unpaired low surrogate')
|
||||
}
|
||||
return utf8_encode(code)
|
||||
}
|
||||
|
||||
fn hex4(s string) !u32 {
|
||||
mut n := u32(0)
|
||||
for i in 0..4 {
|
||||
c := s[i]
|
||||
d := match c {
|
||||
`0`...`9` { u32(c - `0`) }
|
||||
`a`...`f` { u32(c - `a` + 10) }
|
||||
`A`...`F` { u32(c - `A` + 10) }
|
||||
else { return error('bad hex digit "${c.ascii_str()}"') }
|
||||
}
|
||||
n = n * 16 + d
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
fn utf8_width(b u8) int {
|
||||
if b < 0x80 {
|
||||
return 1
|
||||
}
|
||||
if b >= 0xC0 && b <= 0xDF {
|
||||
return 2
|
||||
}
|
||||
if b >= 0xE0 && b <= 0xEF {
|
||||
return 3
|
||||
}
|
||||
if b >= 0xF0 && b <= 0xF7 {
|
||||
return 4
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// utf8_encode renders a Unicode code point as UTF-8 bytes.
|
||||
fn utf8_encode(code u32) string {
|
||||
if code < 0x80 {
|
||||
return u8(code).ascii_str()
|
||||
}
|
||||
mut bytes := []u8{}
|
||||
if code < 0x800 {
|
||||
bytes << u8(0xC0 | (code >> 6))
|
||||
bytes << u8(0x80 | (code & 0x3F))
|
||||
} else if code < 0x10000 {
|
||||
bytes << u8(0xE0 | (code >> 12))
|
||||
bytes << u8(0x80 | ((code >> 6) & 0x3F))
|
||||
bytes << u8(0x80 | (code & 0x3F))
|
||||
} else {
|
||||
bytes << u8(0xF0 | (code >> 18))
|
||||
bytes << u8(0x80 | ((code >> 12) & 0x3F))
|
||||
bytes << u8(0x80 | ((code >> 6) & 0x3F))
|
||||
bytes << u8(0x80 | (code & 0x3F))
|
||||
}
|
||||
return bytes.bytestr()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// encoding
|
||||
|
||||
// json_encode_value renders a VuurRaaf value as a JSON string. Ints (and
|
||||
// bools, which are 0/1) become numbers, floats use fmt_float so integral
|
||||
// floats stay clean, structs become objects, arrays become arrays, and the
|
||||
// `none` value becomes null. Functions cannot be encoded.
|
||||
fn (mut v Vm) json_encode_value(x i64, depth int) !string {
|
||||
if depth > 64 {
|
||||
return error('value is nested too deeply (possible cycle)')
|
||||
}
|
||||
if v.is_none(x) {
|
||||
return 'null'
|
||||
}
|
||||
if v.is_str(x) && v.valid_handle(x) {
|
||||
return json_quote(v.strings[v.hand(x)])
|
||||
}
|
||||
if v.is_arr(x) && v.valid_arr_handle(x) {
|
||||
mut parts := []string{}
|
||||
for el in v.arrays[v.hand(x)] {
|
||||
parts << v.json_encode_value(el, depth + 1)!
|
||||
}
|
||||
return '[' + parts.join(',') + ']'
|
||||
}
|
||||
if v.is_struct(x) && v.valid_struct_handle(x) {
|
||||
mut parts := []string{}
|
||||
for f in v.structs[v.hand(x)].fields {
|
||||
parts << json_quote(f.name) + ':' + v.json_encode_value(f.val, depth + 1)!
|
||||
}
|
||||
return '{' + parts.join(',') + '}'
|
||||
}
|
||||
if v.is_float(x) && v.valid_float_handle(x) {
|
||||
f := v.fval(x)
|
||||
if math.is_nan(f) || math.is_inf(f, 1) || math.is_inf(f, -1) {
|
||||
return error('cannot encode NaN or Infinity as JSON')
|
||||
}
|
||||
return fmt_float(f)
|
||||
}
|
||||
if v.is_closure(x) && v.valid_closure_handle(x) {
|
||||
return error('cannot encode a function value as JSON')
|
||||
}
|
||||
return v.dec_int(x).str()
|
||||
}
|
||||
|
||||
// json_quote escapes a string into a JSON string literal. UTF-8 bytes pass
|
||||
// through untouched; control characters become \\u00XX escapes.
|
||||
fn json_quote(s string) string {
|
||||
hex := '0123456789ABCDEF'
|
||||
mut out := '"'
|
||||
for b in s.bytes() {
|
||||
match b {
|
||||
`"` { out += '\\"' }
|
||||
`\\` { out += '\\\\' }
|
||||
`\n` { out += '\\n' }
|
||||
`\r` { out += '\\r' }
|
||||
`\t` { out += '\\t' }
|
||||
`\b` { out += '\\b' }
|
||||
`\f` { out += '\\f' }
|
||||
else {
|
||||
if b < 0x20 {
|
||||
out += '\\u00'
|
||||
out += hex[int(b >> 4)].ascii_str()
|
||||
out += hex[int(b & 0xF)].ascii_str()
|
||||
} else {
|
||||
out += b.ascii_str()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out + '"'
|
||||
}
|
||||
+180
@@ -581,12 +581,187 @@ fn (mut v Vm) native(id int, _argc int) ! {
|
||||
native_build_root {
|
||||
v.push(v.alloc_str(v.build_root))!
|
||||
}
|
||||
// -------------------------------------------------------------------
|
||||
// stdlib: JSON + string formatting
|
||||
native_json_encode {
|
||||
x := v.pop()!
|
||||
s := v.json_encode_value(x, 0) or { return error('json_encode: ${err.msg()}') }
|
||||
v.push(v.alloc_str(s))!
|
||||
}
|
||||
native_json_decode {
|
||||
s := v.pop_str()!
|
||||
val := v.json_parse(s) or { return error('json_decode: ${err.msg()}') }
|
||||
v.push(val)!
|
||||
}
|
||||
native_format {
|
||||
spec := v.pop_str()!
|
||||
x := v.pop()!
|
||||
s := v.format_value(x, spec) or { return error('format: ${err.msg()}') }
|
||||
v.push(v.alloc_str(s))!
|
||||
}
|
||||
native_replace {
|
||||
to := v.pop_str()!
|
||||
from := v.pop_str()!
|
||||
s := v.pop_str()!
|
||||
v.push(v.alloc_str(s.replace(from, to)))!
|
||||
}
|
||||
native_split_lines {
|
||||
s := v.pop_str()!
|
||||
mut arr := []i64{}
|
||||
for ln in s.split_into_lines() {
|
||||
v.strings << ln
|
||||
arr << v.mkstr(v.strings.len - 1)
|
||||
}
|
||||
v.arrays << arr
|
||||
v.push(v.mkarr(v.arrays.len - 1))!
|
||||
}
|
||||
native_pad {
|
||||
width := int(v.dec_int(v.pop()!))
|
||||
s := v.pop_str()!
|
||||
n := s.runes().len
|
||||
v.push(v.alloc_str(if n < width { s + ' '.repeat(width - n) } else { s }))!
|
||||
}
|
||||
native_pad_left {
|
||||
width := int(v.dec_int(v.pop()!))
|
||||
s := v.pop_str()!
|
||||
n := s.runes().len
|
||||
v.push(v.alloc_str(if n < width { ' '.repeat(width - n) + s } else { s }))!
|
||||
}
|
||||
native_repeat {
|
||||
n := int(v.dec_int(v.pop()!))
|
||||
s := v.pop_str()!
|
||||
if n < 0 {
|
||||
return error('repeat() expects a non-negative count')
|
||||
}
|
||||
v.push(v.alloc_str(s.repeat(n)))!
|
||||
}
|
||||
else {
|
||||
return error('unknown native builtin ${id}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// format() — a small printf-style formatter: %d %i %f %s %x %X %% with
|
||||
// optional width, left-align (-), zero-padding (0), and precision (.N).
|
||||
fn (mut v Vm) format_value(x i64, spec string) !string {
|
||||
if spec.len < 2 || spec[0] != `%` {
|
||||
return error('expected a printf-style spec like "%d" or "%.2f", got "${spec}"')
|
||||
}
|
||||
mut i := 1
|
||||
mut left := false
|
||||
mut zero := false
|
||||
if i < spec.len && spec[i] == `-` {
|
||||
left = true
|
||||
i++
|
||||
}
|
||||
if i < spec.len && spec[i] == `0` {
|
||||
zero = true
|
||||
i++
|
||||
}
|
||||
mut width := 0
|
||||
for i < spec.len && spec[i] >= `0` && spec[i] <= `9` {
|
||||
width = width * 10 + int(spec[i] - `0`)
|
||||
i++
|
||||
}
|
||||
mut prec := -1
|
||||
if i < spec.len && spec[i] == `.` {
|
||||
i++
|
||||
prec = 0
|
||||
for i < spec.len && spec[i] >= `0` && spec[i] <= `9` {
|
||||
prec = prec * 10 + int(spec[i] - `0`)
|
||||
i++
|
||||
}
|
||||
}
|
||||
if i >= spec.len {
|
||||
return error('incomplete format spec "${spec}"')
|
||||
}
|
||||
conv := spec[i]
|
||||
mut core := ''
|
||||
match conv {
|
||||
`d`, `i` {
|
||||
num := if v.is_float(x) { i64(v.fval(x)) } else { v.dec_int(x) }
|
||||
core = num.str()
|
||||
}
|
||||
`f` {
|
||||
f := if v.is_float(x) { v.fval(x) } else { f64(v.dec_int(x)) }
|
||||
core = v.format_fixed(f, if prec < 0 { 6 } else { prec })
|
||||
}
|
||||
`s` {
|
||||
core = v.val_str(x, 0)
|
||||
if prec >= 0 && core.len > prec {
|
||||
core = core[..prec]
|
||||
}
|
||||
}
|
||||
`x` {
|
||||
core = u64(v.dec_int(x)).hex()
|
||||
}
|
||||
`X` {
|
||||
core = u64(v.dec_int(x)).hex().to_upper()
|
||||
}
|
||||
`%` {
|
||||
return '%'
|
||||
}
|
||||
else {
|
||||
return error('unsupported conversion "%${conv.ascii_str()}" (supported: %d %i %f %s %x %X %%)')
|
||||
}
|
||||
}
|
||||
if core.len < width {
|
||||
n := width - core.len
|
||||
if left {
|
||||
core += ' '.repeat(n)
|
||||
} else if zero && core.starts_with('-') {
|
||||
core = '-' + '0'.repeat(n) + core[1..]
|
||||
} else if zero {
|
||||
core = '0'.repeat(n) + core
|
||||
} else {
|
||||
core = ' '.repeat(n) + core
|
||||
}
|
||||
}
|
||||
return core
|
||||
}
|
||||
|
||||
// format_fixed renders an f64 in fixed-point notation with `prec` decimals
|
||||
// (rounding), the way printf's %f does.
|
||||
fn (mut v Vm) format_fixed(f f64, prec int) string {
|
||||
mut p := prec
|
||||
if p < 0 {
|
||||
p = 0
|
||||
}
|
||||
if p > 20 {
|
||||
p = 20
|
||||
}
|
||||
if math.is_nan(f) {
|
||||
return 'NaN'
|
||||
}
|
||||
if math.is_inf(f, 1) {
|
||||
return 'Inf'
|
||||
}
|
||||
if math.is_inf(f, -1) {
|
||||
return '-Inf'
|
||||
}
|
||||
neg := f < 0.0
|
||||
af := math.abs(f)
|
||||
// beyond ~15 digits a float no longer carries exact decimal places, so
|
||||
// fall back to the friendliest available rendering
|
||||
if af >= 1e15 {
|
||||
return fmt_float(f)
|
||||
}
|
||||
scale := math.pow(10.0, f64(p))
|
||||
r := math.round(af * scale)
|
||||
i := i64(r / scale)
|
||||
core := i.str()
|
||||
if p == 0 {
|
||||
return if neg { '-' + core } else { core }
|
||||
}
|
||||
d := i64(math.round(math.fmod(r, scale)))
|
||||
mut ds := d.str()
|
||||
if ds.len < p {
|
||||
ds = '0'.repeat(p - ds.len) + ds
|
||||
}
|
||||
return (if neg { '-' } else { '' }) + core + '.' + ds
|
||||
}
|
||||
|
||||
// copy_tree recursively copies a directory tree (used by build_copy).
|
||||
fn (mut v Vm) copy_tree(src string, dst string) ! {
|
||||
if !os.is_dir(src) {
|
||||
@@ -645,6 +820,11 @@ fn (mut v Vm) str_method(name string, argc int) ! {
|
||||
'index_of' { native_index_of }
|
||||
'to_int' { native_int }
|
||||
'to_float' { native_float }
|
||||
'replace' { native_replace }
|
||||
'split_lines' { native_split_lines }
|
||||
'pad' { native_pad }
|
||||
'pad_left' { native_pad_left }
|
||||
'repeat' { native_repeat }
|
||||
else { -1 }
|
||||
}
|
||||
if bid >= 0 {
|
||||
|
||||
@@ -65,6 +65,7 @@ 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), ...)
|
||||
const op_push_none = u8(62) // push the `none` sentinel
|
||||
|
||||
// native builtin ids (the operand to op_native)
|
||||
const native_abs = 100
|
||||
@@ -126,3 +127,13 @@ const native_build_base = 153
|
||||
const native_build_dir = 154
|
||||
const native_build_join = 155
|
||||
const native_build_root = 156
|
||||
|
||||
// stdlib builtins (JSON + string formatting)
|
||||
const native_json_encode = 157
|
||||
const native_json_decode = 158
|
||||
const native_format = 159
|
||||
const native_replace = 160
|
||||
const native_split_lines = 161
|
||||
const native_pad = 162
|
||||
const native_pad_left = 163
|
||||
const native_repeat = 164
|
||||
|
||||
@@ -4,6 +4,9 @@ module vm
|
||||
import math
|
||||
|
||||
fn (mut v Vm) add(a i64, b i64) !i64 {
|
||||
if v.is_none(a) || v.is_none(b) {
|
||||
return error('cannot use none with +')
|
||||
}
|
||||
if v.is_arr(a) || v.is_arr(b) {
|
||||
return error('cannot add arrays with +')
|
||||
}
|
||||
@@ -75,6 +78,9 @@ fn fmt_float(f f64) string {
|
||||
}
|
||||
|
||||
fn (mut v Vm) arith(a i64, b i64, op string) !i64 {
|
||||
if v.is_none(a) || v.is_none(b) {
|
||||
return error('cannot use none with "${op}"')
|
||||
}
|
||||
if v.is_str(a) || v.is_str(b) {
|
||||
return error('cannot use strings with "${op}"')
|
||||
}
|
||||
@@ -139,6 +145,16 @@ fn (mut v Vm) arith(a i64, b i64, op string) !i64 {
|
||||
}
|
||||
|
||||
fn (mut v Vm) cmp(a i64, b i64, op string) !i64 {
|
||||
if v.is_none(a) || v.is_none(b) {
|
||||
// none equals only none; ordering a none is an error
|
||||
if op == '==' {
|
||||
return bool_i64(v.is_none(a) && v.is_none(b))
|
||||
}
|
||||
if op == '!=' {
|
||||
return bool_i64(!(v.is_none(a) && v.is_none(b)))
|
||||
}
|
||||
return error('cannot order none')
|
||||
}
|
||||
if v.is_arr(a) || v.is_arr(b) {
|
||||
// arrays compare by identity (handle equality) with ==/!=
|
||||
if op == '==' || op == '!=' {
|
||||
|
||||
@@ -11,6 +11,9 @@ fn (mut v Vm) val_str(x i64, depth int) string {
|
||||
if depth > 16 {
|
||||
return '...'
|
||||
}
|
||||
if v.is_none(x) {
|
||||
return 'none'
|
||||
}
|
||||
if v.is_str(x) && v.valid_handle(x) {
|
||||
return v.strings[v.hand(x)]
|
||||
}
|
||||
@@ -117,6 +120,7 @@ fn (mut v Vm) trace_op(op u8) {
|
||||
op_load_dyn { 'load_dyn' }
|
||||
op_varargs { 'varargs' }
|
||||
op_str_method { 'str_method' }
|
||||
op_push_none { 'push_none' }
|
||||
else { '??' }
|
||||
}
|
||||
mut s := ''
|
||||
|
||||
+13
@@ -16,6 +16,12 @@ const tag_arr = u64(3)
|
||||
const tag_float = u64(4)
|
||||
const tag_closure = u64(5)
|
||||
|
||||
// none_val is the sentinel for the `none` literal (and JSON null). Tag 110
|
||||
// is not a valid encoded integer (those are multiples of 8) nor any handle,
|
||||
// so it can never collide with a real value. The GC ignores it (its tag
|
||||
// matches no heap pool).
|
||||
const none_val = i64(6)
|
||||
|
||||
fn (mut v Vm) tag(x i64) u64 {
|
||||
return u64(x) & tag_mask
|
||||
}
|
||||
@@ -44,6 +50,10 @@ fn (mut v Vm) is_closure(x i64) bool {
|
||||
return u64(x) & tag_mask == tag_closure
|
||||
}
|
||||
|
||||
fn (mut v Vm) is_none(x i64) bool {
|
||||
return x == none_val
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -93,6 +103,9 @@ fn (mut v Vm) fval(x i64) f64 {
|
||||
}
|
||||
|
||||
fn (mut v Vm) truthy(x i64) bool {
|
||||
if v.is_none(x) {
|
||||
return false
|
||||
}
|
||||
if v.is_float(x) {
|
||||
return v.fval(x) != 0.0
|
||||
}
|
||||
|
||||
@@ -443,7 +443,20 @@ fn (mut v Vm) exec() ! {
|
||||
v.ip++
|
||||
id := int(v.read_i64())
|
||||
argc := int(v.read_i64())
|
||||
v.native(id, argc)!
|
||||
v.native(id, argc) or {
|
||||
// a failed builtin becomes a VM-level throw, so try/catch can
|
||||
// intercept it exactly like an explicit `throw`; with no
|
||||
// handler it keeps propagating to the caller
|
||||
if v.handlers.len == 0 {
|
||||
return err
|
||||
}
|
||||
h := v.handlers[v.handlers.len - 1]
|
||||
v.handlers.delete_last()
|
||||
v.bp = h.bp
|
||||
v.sp = h.sp
|
||||
v.push(v.alloc_str(err.msg()))!
|
||||
v.ip = h.ip
|
||||
}
|
||||
}
|
||||
op_and_b {
|
||||
v.ip++
|
||||
@@ -569,6 +582,10 @@ fn (mut v Vm) exec() ! {
|
||||
argc := int(v.read_i64())
|
||||
v.str_method(v.strings[sidx], argc)!
|
||||
}
|
||||
op_push_none {
|
||||
v.ip++
|
||||
v.push(none_val)!
|
||||
}
|
||||
else {
|
||||
return error('unknown opcode ${op} at ip ${v.ip}')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user