mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 14:57:18 +00:00
Added default json, etc.
This commit is contained in:
@@ -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