mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 16:07:01 +00:00
stdlin
This commit is contained in:
+2
-1
@@ -122,7 +122,8 @@ pub mut:
|
|||||||
|
|
||||||
pub struct ImportDecl {
|
pub struct ImportDecl {
|
||||||
pub mut:
|
pub mut:
|
||||||
path string
|
path string // the file to load (module name for bare `import os`)
|
||||||
|
name string // module name ('' for quoted file imports: flat merge)
|
||||||
line int
|
line int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+44
-11
@@ -46,7 +46,8 @@ mut:
|
|||||||
enums map[string][]string
|
enums map[string][]string
|
||||||
consts map[string]TypeInfo
|
consts map[string]TypeInfo
|
||||||
loop_depth int
|
loop_depth int
|
||||||
checked map[string]bool // imported files already checked
|
checked map[string]bool // imported files already checked
|
||||||
|
modules map[string]bool // imported module names (bare `import os`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check validates a parsed program and returns an error on the first problem.
|
// check validates a parsed program and returns an error on the first problem.
|
||||||
@@ -76,7 +77,10 @@ fn check(prog Program) ! {
|
|||||||
}
|
}
|
||||||
// imported files are checked (and their symbols merged) recursively
|
// imported files are checked (and their symbols merged) recursively
|
||||||
for imp in prog.imports {
|
for imp in prog.imports {
|
||||||
c.check_import(imp.path)!
|
if imp.name.len > 0 {
|
||||||
|
c.modules[imp.name] = true
|
||||||
|
}
|
||||||
|
c.check_import(imp.path, imp.name)!
|
||||||
}
|
}
|
||||||
for fd in prog.fns {
|
for fd in prog.fns {
|
||||||
c.check_fn(fd)!
|
c.check_fn(fd)!
|
||||||
@@ -93,7 +97,7 @@ fn def_count(fd FnDecl) int {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut c Checker) check_import(path string) ! {
|
fn (mut c Checker) check_import(path string, mod_name string) ! {
|
||||||
if path in c.checked {
|
if path in c.checked {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -101,7 +105,9 @@ fn (mut c Checker) check_import(path string) ! {
|
|||||||
resolved := resolve_import(path) or { return error('cannot read import "${path}"') }
|
resolved := resolve_import(path) or { return error('cannot read import "${path}"') }
|
||||||
src := os.read_file(resolved) or { return error('cannot read import "${path}"') }
|
src := os.read_file(resolved) or { return error('cannot read import "${path}"') }
|
||||||
prog := parse(tokenize(src)!)!
|
prog := parse(tokenize(src)!)!
|
||||||
// merge declarations from the import
|
// merge declarations from the import (bare modules register their
|
||||||
|
// functions under both the bare name and the "mod.fn" name, so internal
|
||||||
|
// calls check against the former and program calls against the latter)
|
||||||
for sd in prog.structs {
|
for sd in prog.structs {
|
||||||
if sd.name !in c.structs {
|
if sd.name !in c.structs {
|
||||||
c.structs[sd.name] = sd.fields
|
c.structs[sd.name] = sd.fields
|
||||||
@@ -121,9 +127,15 @@ fn (mut c Checker) check_import(path string) ! {
|
|||||||
if fd.name !in c.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 }
|
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 }
|
||||||
}
|
}
|
||||||
|
if mod_name.len > 0 {
|
||||||
|
key := '${mod_name}.${fd.name}'
|
||||||
|
if key !in c.fns {
|
||||||
|
c.fns[key] = 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 {
|
for imp in prog.imports {
|
||||||
c.check_import(imp.path)!
|
c.check_import(imp.path, imp.name)!
|
||||||
}
|
}
|
||||||
for fd in prog.fns {
|
for fd in prog.fns {
|
||||||
c.check_fn(fd)!
|
c.check_fn(fd)!
|
||||||
@@ -324,16 +336,35 @@ fn (mut c Checker) check_expr(e Expr) !TypeInfo {
|
|||||||
}
|
}
|
||||||
TypeInfo{ kind: .unknown }
|
TypeInfo{ kind: .unknown }
|
||||||
}
|
}
|
||||||
.method_call {
|
.method_call {
|
||||||
recv := c.check_expr(*e.left)!
|
// module call: os.exists(x) — the receiver is an imported module name
|
||||||
if recv.kind == .int_t || recv.kind == .float_t || recv.kind == .bool_t || recv.kind == .none_t {
|
if e.left.kind == .ident && e.left.name in c.modules {
|
||||||
return error('cannot call a method on a ${type_name(recv.kind)} (line ${e.line})')
|
|
||||||
}
|
|
||||||
for a in e.args {
|
for a in e.args {
|
||||||
_ = c.check_expr(a)!
|
_ = c.check_expr(a)!
|
||||||
}
|
}
|
||||||
TypeInfo{ kind: .unknown }
|
key := '${e.left.name}.${e.name}'
|
||||||
|
if key in c.fns {
|
||||||
|
sig := c.fns[key]
|
||||||
|
if !sig.variadic {
|
||||||
|
if e.args.len < sig.min_args {
|
||||||
|
return error('${key}() 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('${key}() expects at most ${sig.has_defs.len} argument(s), got ${e.args.len} (line ${e.line})')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TypeInfo{ kind: .unknown }
|
||||||
}
|
}
|
||||||
|
recv := c.check_expr(*e.left)!
|
||||||
|
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 {
|
||||||
|
_ = c.check_expr(a)!
|
||||||
|
}
|
||||||
|
TypeInfo{ kind: .unknown }
|
||||||
|
}
|
||||||
.slice {
|
.slice {
|
||||||
base := c.check_expr(*e.left)!
|
base := c.check_expr(*e.left)!
|
||||||
_ = c.check_expr(*e.right)!
|
_ = c.check_expr(*e.right)!
|
||||||
@@ -560,6 +591,8 @@ fn builtin_result_type(name string) TypeInfo {
|
|||||||
// stdlib: JSON + string formatting
|
// stdlib: JSON + string formatting
|
||||||
'json_encode', 'format', 'replace', 'pad', 'pad_left', 'repeat' { TypeInfo{ kind: .string_t } }
|
'json_encode', 'format', 'replace', 'pad', 'pad_left', 'repeat' { TypeInfo{ kind: .string_t } }
|
||||||
'json_decode', 'split_lines' { TypeInfo{ kind: .unknown } }
|
'json_decode', 'split_lines' { TypeInfo{ kind: .unknown } }
|
||||||
|
'cwd', 'json_pretty' { TypeInfo{ kind: .string_t } }
|
||||||
|
'build_is_dir' { TypeInfo{ kind: .int_t } }
|
||||||
// build-module builtins (.vrmm)
|
// build-module builtins (.vrmm)
|
||||||
'build_compile', 'build_assemble', 'build_link', 'build_exec', 'build_base',
|
'build_compile', 'build_assemble', 'build_link', 'build_exec', 'build_base',
|
||||||
'build_dir', 'build_join', 'build_root' { TypeInfo{ kind: .string_t } }
|
'build_dir', 'build_join', 'build_root' { TypeInfo{ kind: .string_t } }
|
||||||
|
|||||||
+60
-20
@@ -43,6 +43,7 @@ mut:
|
|||||||
loops []LoopCtx
|
loops []LoopCtx
|
||||||
enter_off u32
|
enter_off u32
|
||||||
next_lbl int
|
next_lbl int
|
||||||
|
modules map[string]bool // imported module names (bare `import os`)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen(prog Program) !obj.Obj {
|
fn gen(prog Program) !obj.Obj {
|
||||||
@@ -78,22 +79,41 @@ fn gen(prog Program) !obj.Obj {
|
|||||||
}
|
}
|
||||||
g.structs[sd.name] = sd.fields
|
g.structs[sd.name] = sd.fields
|
||||||
}
|
}
|
||||||
// compile imported files and merge their objects
|
// compile imported files and merge their objects. Bare module imports
|
||||||
|
// (`import os`) prefix the module's function symbols and internal call
|
||||||
|
// relocations with "os.", so programs call os.exists(...) and modules can
|
||||||
|
// never collide with each other or with the program's own functions.
|
||||||
for imp in prog.imports {
|
for imp in prog.imports {
|
||||||
|
mod_name := imp.name
|
||||||
|
prefix := if mod_name.len > 0 { mod_name + '.' } else { '' }
|
||||||
imported := compile_file(resolve_import(imp.path)!)!
|
imported := compile_file(resolve_import(imp.path)!)!
|
||||||
// merge symbols from the imported object
|
if mod_name.len > 0 {
|
||||||
|
g.modules[mod_name] = true
|
||||||
|
}
|
||||||
|
// the imported object's own symbol names (for rewriting call sites)
|
||||||
|
mut own := map[string]bool{}
|
||||||
for s in imported.symbols {
|
for s in imported.symbols {
|
||||||
g.symbols << s
|
own[s.name] = true
|
||||||
|
}
|
||||||
|
// append imported bytecode first so symbol entries can be rebased
|
||||||
|
code_off := g.code.len
|
||||||
|
g.code << imported.code
|
||||||
|
// merge symbols from the imported object (prefixed and rebased: entries
|
||||||
|
// are relative to the imported code, which now sits at code_off)
|
||||||
|
for s in imported.symbols {
|
||||||
|
g.symbols << obj.Symbol{ name: prefix + s.name, entry: code_off + s.entry }
|
||||||
}
|
}
|
||||||
// merge strings
|
// merge strings
|
||||||
for s in imported.strings {
|
for s in imported.strings {
|
||||||
g.strings << s
|
g.strings << s
|
||||||
}
|
}
|
||||||
// append imported bytecode and adjust relocations
|
// adjust relocations (and prefix module-internal call targets)
|
||||||
code_off := g.code.len
|
|
||||||
g.code << imported.code
|
|
||||||
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 }
|
mut rname := r.name
|
||||||
|
if r.kind == 0 && prefix.len > 0 && r.name in own {
|
||||||
|
rname = prefix + r.name
|
||||||
|
}
|
||||||
|
g.relocs << obj.Reloc{ offset: u32(code_off) + r.offset, name: rname, kind: r.kind }
|
||||||
}
|
}
|
||||||
// merge debug info, rebasing offsets into this object's code space
|
// merge debug info, rebasing offsets into this object's code space
|
||||||
for l in imported.lines {
|
for l in imported.lines {
|
||||||
@@ -190,12 +210,14 @@ fn (mut g Gen) gen_fn(fd FnDecl) ! {
|
|||||||
// than declared (default parameters) or more (variadic), so the frame must
|
// than declared (default parameters) or more (variadic), so the frame must
|
||||||
// always cover slots 0..local_cnt-1
|
// always cover slots 0..local_cnt-1
|
||||||
obj.patch_i64(mut g.code, g.enter_off, i64(g.local_cnt))
|
obj.patch_i64(mut g.code, g.enter_off, i64(g.local_cnt))
|
||||||
// resolve intra-function jump targets
|
// resolve intra-function jump targets. Targets are encoded PC-relative
|
||||||
|
// (delta from the end of the 8-byte operand), so bytecode stays
|
||||||
|
// position-independent when module objects are merged or linked.
|
||||||
for f in g.fixups {
|
for f in g.fixups {
|
||||||
target := g.labels[f.name] or {
|
target := g.labels[f.name] or {
|
||||||
return error('internal error: unresolved label ${f.name} in fn ${fd.name}')
|
return error('internal error: unresolved label ${f.name} in fn ${fd.name}')
|
||||||
}
|
}
|
||||||
obj.patch_i64(mut g.code, f.off, i64(target))
|
obj.patch_i64(mut g.code, f.off, i64(target - (int(f.off) + 8)))
|
||||||
}
|
}
|
||||||
g.fixups.clear()
|
g.fixups.clear()
|
||||||
g.labels.clear()
|
g.labels.clear()
|
||||||
@@ -603,13 +625,24 @@ fn (mut g Gen) gen_expr(e Expr) ! {
|
|||||||
g.emit_field_name(e.name)
|
g.emit_field_name(e.name)
|
||||||
g.code << op_sget
|
g.code << op_sget
|
||||||
}
|
}
|
||||||
.method_call {
|
.method_call {
|
||||||
// p.dist(x) → call <Type>.dist p, x
|
// module call: os.exists(x) — the receiver is an imported module name
|
||||||
recv_t := g.method_receiver_type(e)
|
if e.left.kind == .ident && e.left.name in g.modules {
|
||||||
// string methods: s.len(), s.to_upper(), s.contains(x), ... —
|
for a in e.args {
|
||||||
// the receiver type is known when it is a literal or a local that
|
g.gen_expr(a)!
|
||||||
// was assigned a string literal
|
}
|
||||||
if recv_t == 'string' || e.left.kind == .str_lit {
|
g.code << op_call
|
||||||
|
g.code << obj.encode_i64(0) // placeholder — patched by the linker
|
||||||
|
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: '${e.left.name}.${e.name}', kind: 0 }
|
||||||
|
g.code << obj.encode_i64(i64(e.args.len))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// p.dist(x) → call <Type>.dist p, x
|
||||||
|
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)!
|
g.gen_expr(*e.left)!
|
||||||
for a in e.args {
|
for a in e.args {
|
||||||
g.gen_expr(a)!
|
g.gen_expr(a)!
|
||||||
@@ -713,8 +746,9 @@ fn (mut g Gen) gen_expr(e Expr) ! {
|
|||||||
g.local_cnt = saved_local_cnt
|
g.local_cnt = saved_local_cnt
|
||||||
g.enter_off = saved_enter_off
|
g.enter_off = saved_enter_off
|
||||||
g.argc = saved_argc
|
g.argc = saved_argc
|
||||||
// Patch the skip jump to land at the closure opcode we emit next.
|
// 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))
|
// (PC-relative, like all other jump targets).
|
||||||
|
obj.patch_i64(mut g.code, skip_fix_off, i64(g.code.len - (int(skip_fix_off) + 8)))
|
||||||
g.code << op_closure
|
g.code << op_closure
|
||||||
g.code << obj.encode_i64(0)
|
g.code << obj.encode_i64(0)
|
||||||
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: name, kind: 0 }
|
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: name, kind: 0 }
|
||||||
@@ -830,10 +864,12 @@ 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
|
// closure call: ident(args) where ident is a local holding a closure.
|
||||||
|
// The local's value is pushed as the call sequence's first slot;
|
||||||
|
// op_call_closure consumes it along with the args, leaving only the
|
||||||
|
// result on the stack.
|
||||||
if e.name in g.locals {
|
if e.name in g.locals {
|
||||||
g.gen_expr(Expr{ kind: .ident, name: e.name, line: e.line })!
|
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 {
|
for a in e.args {
|
||||||
g.gen_expr(a)!
|
g.gen_expr(a)!
|
||||||
}
|
}
|
||||||
@@ -935,6 +971,9 @@ fn builtin_spec(name string) (int, int) {
|
|||||||
'pad' { native_pad, 2 }
|
'pad' { native_pad, 2 }
|
||||||
'pad_left' { native_pad_left, 2 }
|
'pad_left' { native_pad_left, 2 }
|
||||||
'repeat' { native_repeat, 2 }
|
'repeat' { native_repeat, 2 }
|
||||||
|
'build_is_dir' { native_build_is_dir, 1 }
|
||||||
|
'cwd' { native_cwd, 0 }
|
||||||
|
'json_pretty' { native_json_pretty, 1 }
|
||||||
else { -1, 0 }
|
else { -1, 0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1166,6 +1205,7 @@ fn (mut g Gen) expr_type(e Expr) string {
|
|||||||
'build_compile', 'build_assemble', 'build_link', 'build_exec', 'build_base',
|
'build_compile', 'build_assemble', 'build_link', 'build_exec', 'build_base',
|
||||||
'build_dir', 'build_join', 'build_root' { 'string' }
|
'build_dir', 'build_join', 'build_root' { 'string' }
|
||||||
'json_encode', 'format', 'replace', 'pad', 'pad_left', 'repeat' { 'string' }
|
'json_encode', 'format', 'replace', 'pad', 'pad_left', 'repeat' { 'string' }
|
||||||
|
'cwd', 'json_pretty' { 'string' }
|
||||||
else { '' }
|
else { '' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-4
@@ -4,6 +4,10 @@ module compiler
|
|||||||
import os
|
import os
|
||||||
import obj
|
import obj
|
||||||
|
|
||||||
|
// stdlib_dir is the toolchain's bundled library (lib/*.vr), resolved at build
|
||||||
|
// time so `import os` works from any working directory.
|
||||||
|
const stdlib_dir = @VMODROOT + '/lib'
|
||||||
|
|
||||||
// compile parses and compiles VuurRaaf source into an object file.
|
// compile parses and compiles VuurRaaf source into an object file.
|
||||||
pub fn compile(src string) !obj.Obj {
|
pub fn compile(src string) !obj.Obj {
|
||||||
toks := tokenize(src)!
|
toks := tokenize(src)!
|
||||||
@@ -19,16 +23,19 @@ pub fn compile_file(path string) !obj.Obj {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// resolve_import turns an import path into a readable source file. It tries
|
// 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
|
// the path as given first, then the package-manager layout (vendor/) and the
|
||||||
// `import "pkg/file.vr"` finds vendor/pkg/file.vr.
|
// toolchain's own standard library (lib/), so both `import "pkg/file.vr"`
|
||||||
|
// and the bare module form `import os` resolve from anywhere.
|
||||||
pub fn resolve_import(path string) !string {
|
pub fn resolve_import(path string) !string {
|
||||||
if os.exists(path) {
|
if os.exists(path) {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
for cand in ['vendor/${path}', 'vendor/${path}.vr', 'vendor/${path}/main.vr', 'vendor/${path}/src/main.vr'] {
|
for cand in ['vendor/${path}', 'vendor/${path}.vr', 'vendor/${path}/main.vr',
|
||||||
|
'vendor/${path}/src/main.vr', os.join_path(stdlib_dir, path + '.vr'),
|
||||||
|
os.join_path(stdlib_dir, path, 'main.vr')] {
|
||||||
if os.exists(cand) {
|
if os.exists(cand) {
|
||||||
return cand
|
return cand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return error('cannot resolve import "${path}" (tried vendor/)"')
|
return error('cannot resolve import "${path}" (tried vendor/ and lib/)')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,3 +138,6 @@ const native_split_lines = 161
|
|||||||
const native_pad = 162
|
const native_pad = 162
|
||||||
const native_pad_left = 163
|
const native_pad_left = 163
|
||||||
const native_repeat = 164
|
const native_repeat = 164
|
||||||
|
const native_build_is_dir = 165
|
||||||
|
const native_cwd = 166
|
||||||
|
const native_json_pretty = 167
|
||||||
|
|||||||
+9
-4
@@ -2,7 +2,7 @@
|
|||||||
//
|
//
|
||||||
// Grammar (informal):
|
// Grammar (informal):
|
||||||
// program := import* (struct | enum | const | fn)*
|
// program := import* (struct | enum | const | fn)*
|
||||||
// import := 'import' STRING
|
// import := 'import' STRING | 'import' IDENT
|
||||||
// struct := 'struct' IDENT '{' [IDENT (',' IDENT)*] '}'
|
// struct := 'struct' IDENT '{' [IDENT (',' IDENT)*] '}'
|
||||||
// enum := 'enum' IDENT '{' [IDENT (',' IDENT)*] '}'
|
// enum := 'enum' IDENT '{' [IDENT (',' IDENT)*] '}'
|
||||||
// const := 'const' IDENT '=' expr
|
// const := 'const' IDENT '=' expr
|
||||||
@@ -123,11 +123,16 @@ fn (mut p Parser) parse_struct_decl() !StructDecl {
|
|||||||
return StructDecl{ name: name.lit, fields: fields, line: t.line }
|
return StructDecl{ name: name.lit, fields: fields, line: t.line }
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse_import parses `import "path/to/file.vr"`.
|
// parse_import parses `import "path/to/file.vr"` (flat file merge) or a
|
||||||
|
// bare module name like `import os` (namespaced stdlib/vendor module).
|
||||||
fn (mut p Parser) parse_import() !ImportDecl {
|
fn (mut p Parser) parse_import() !ImportDecl {
|
||||||
t := p.expect(.kw_import, "'import'")!
|
t := p.expect(.kw_import, "'import'")!
|
||||||
path := p.expect(.str_lit, 'import path')!
|
if p.cur().kind == .str_lit {
|
||||||
return ImportDecl{ path: path.lit, line: t.line }
|
path := p.advance().lit
|
||||||
|
return ImportDecl{ path: path, line: t.line }
|
||||||
|
}
|
||||||
|
name := p.expect(.ident, 'module name')!.lit
|
||||||
|
return ImportDecl{ name: name, path: name, line: t.line }
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse_enum_decl parses `enum Name { variant1 variant2 ... }`.
|
// parse_enum_decl parses `enum Name { variant1 variant2 ... }`.
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// imports.vr — the bundled standard library, imported by name like in V.
|
||||||
|
// vr run examples/imports.vr
|
||||||
|
//
|
||||||
|
// The toolchain ships lib/*.vr; `import os` resolves against it (and vendor/)
|
||||||
|
// from any working directory. Module functions are namespaced: os.exists(),
|
||||||
|
// json.encode(), strings.join(), math.clamp().
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import strings
|
||||||
|
import math
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// --- os: filesystem helpers ---
|
||||||
|
println("cwd: " + os.cwd())
|
||||||
|
println("lib has json.vr: " + str(os.exists("lib/json.vr")))
|
||||||
|
for f in os.list_dir("lib") {
|
||||||
|
println(" lib/" + f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- json: encode / decode / pretty ---
|
||||||
|
let user = {
|
||||||
|
name: "amy",
|
||||||
|
age: 30,
|
||||||
|
score: 87.5,
|
||||||
|
tags: ["admin", "beta"],
|
||||||
|
active: 1
|
||||||
|
}
|
||||||
|
println(json.pretty(user))
|
||||||
|
|
||||||
|
let back = json.decode(json.encode(user))
|
||||||
|
println("decoded back: " + back.name + ", " + str(back.age))
|
||||||
|
|
||||||
|
// --- strings: namespaced string API ---
|
||||||
|
println(strings.join(["one", "two", "three"], ", "))
|
||||||
|
println(strings.capitalize("vuurraaf"))
|
||||||
|
println(strings.pad_left("42", 5) + "|")
|
||||||
|
println(strings.repeat("ab", 3))
|
||||||
|
|
||||||
|
// --- math ---
|
||||||
|
println(str(math.clamp(150, 0, 100)))
|
||||||
|
println(str(math.sign(-42)))
|
||||||
|
println(strings.format(math.pi(), "%.4f"))
|
||||||
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
// tests.vr — functions named test_* are picked up by `vr test`.
|
// tests.vr — functions named test_* are picked up by `vr test`.
|
||||||
// vr test examples/tests.vr
|
// vr test examples/tests.vr
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import strings
|
||||||
|
import math
|
||||||
|
|
||||||
fn add(a, b) {
|
fn add(a, b) {
|
||||||
return a + b
|
return a + b
|
||||||
}
|
}
|
||||||
@@ -292,6 +297,43 @@ fn test_none() {
|
|||||||
assert not (none == 1)
|
assert not (none == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_module_imports() {
|
||||||
|
// os module
|
||||||
|
if not os.exists("examples") {
|
||||||
|
assert false
|
||||||
|
}
|
||||||
|
if not os.is_dir("examples") {
|
||||||
|
assert false
|
||||||
|
}
|
||||||
|
if not os.is_file("main.v") {
|
||||||
|
assert false
|
||||||
|
}
|
||||||
|
assert os.base("a/b/c.vr") == "c.vr"
|
||||||
|
assert os.dir("a/b/c.vr") == "a/b"
|
||||||
|
assert len(os.glob("lib/*.vr")) == 4
|
||||||
|
let tmp = os.join(os.cwd(), "tmp_test.txt")
|
||||||
|
os.write_lines(tmp, ["x", "y"])
|
||||||
|
assert len(os.read_lines(tmp)) == 2
|
||||||
|
os.remove(tmp)
|
||||||
|
|
||||||
|
// json module
|
||||||
|
let data = json.decode("{\"a\": 1, \"b\": [2, 3]}")
|
||||||
|
assert json.encode(data) == "{\"a\":1,\"b\":[2,3]}"
|
||||||
|
assert strings.contains(json.pretty(data), "\n")
|
||||||
|
|
||||||
|
// strings module
|
||||||
|
assert strings.join(["a", "b"], "-") == "a-b"
|
||||||
|
assert strings.capitalize("hello") == "Hello"
|
||||||
|
assert strings.replace("foo", "o", "0") == "f00"
|
||||||
|
assert strings.pad_left("7", 3) == " 7"
|
||||||
|
assert strings.format(3.14159, "%.2f") == "3.14"
|
||||||
|
|
||||||
|
// math module
|
||||||
|
assert math.clamp(150, 0, 100) == 100
|
||||||
|
assert math.sign(-3) == -1
|
||||||
|
assert math.abs(-7) == 7
|
||||||
|
}
|
||||||
|
|
||||||
fn test_native_errors_are_catchable() {
|
fn test_native_errors_are_catchable() {
|
||||||
try {
|
try {
|
||||||
read_file("/no/such/file.vr")
|
read_file("/no/such/file.vr")
|
||||||
@@ -307,6 +349,38 @@ fn test_native_errors_are_catchable() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_closures() {
|
||||||
|
// closures in expression contexts must not corrupt surrounding values
|
||||||
|
let pick = fn (x) {
|
||||||
|
if x > 10 {
|
||||||
|
return "big"
|
||||||
|
}
|
||||||
|
return "small"
|
||||||
|
}
|
||||||
|
assert "a: " + pick(50) == "a: big"
|
||||||
|
assert pick(3) + "!" == "small!"
|
||||||
|
assert pick(50) + " " + pick(3) == "big small"
|
||||||
|
let dbl = fn (x) {
|
||||||
|
return x * 2
|
||||||
|
}
|
||||||
|
assert str(dbl(21)) == "42"
|
||||||
|
assert "v=" + str(dbl(4)) == "v=8"
|
||||||
|
// closure containing a loop
|
||||||
|
let sum = fn () {
|
||||||
|
let total = 0
|
||||||
|
for i in 0..5 {
|
||||||
|
total = total + i
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
assert sum() == 10
|
||||||
|
// closure call as a statement argument
|
||||||
|
let tag = fn (t) {
|
||||||
|
return "[" + t + "]"
|
||||||
|
}
|
||||||
|
assert tag("warn") == "[warn]"
|
||||||
|
}
|
||||||
|
|
||||||
fn test_failing() {
|
fn test_failing() {
|
||||||
// this one is meant to fail — shows up in `vr test` output
|
// this one is meant to fail — shows up in `vr test` output
|
||||||
assert 1 == 2
|
assert 1 == 2
|
||||||
|
|||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
// json.vr — JSON helpers (part of the VuurRaaf stdlib).
|
||||||
|
//
|
||||||
|
// import json
|
||||||
|
// let data = json.decode("{\"a\": 1}")
|
||||||
|
// println(json.encode(data))
|
||||||
|
// println(json.pretty(data))
|
||||||
|
|
||||||
|
fn encode(x) {
|
||||||
|
return json_encode(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(s) {
|
||||||
|
return json_decode(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pretty(x) {
|
||||||
|
return json_pretty(x)
|
||||||
|
}
|
||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
// math.vr — math helpers (part of the VuurRaaf stdlib).
|
||||||
|
//
|
||||||
|
// import math
|
||||||
|
// println(math.clamp(150, 0, 100))
|
||||||
|
// println(math.sign(-3))
|
||||||
|
|
||||||
|
fn abs(x) {
|
||||||
|
return abs(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn min(a, b) {
|
||||||
|
return min(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max(a, b) {
|
||||||
|
return max(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn floor(x) {
|
||||||
|
return floor(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ceil(x) {
|
||||||
|
return ceil(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn round(x) {
|
||||||
|
return round(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sqrt(x) {
|
||||||
|
return sqrt(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pow(a, b) {
|
||||||
|
return pow(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clamp(x, lo, hi) {
|
||||||
|
if x < lo {
|
||||||
|
return lo
|
||||||
|
}
|
||||||
|
if x > hi {
|
||||||
|
return hi
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign(x) {
|
||||||
|
if x < 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if x > 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pi() {
|
||||||
|
return 3.14159
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// os.vr — operating system and filesystem helpers (part of the VuurRaaf stdlib).
|
||||||
|
//
|
||||||
|
// import os
|
||||||
|
// if os.exists("build.vrmm") {
|
||||||
|
// for f in os.list_dir(".") {
|
||||||
|
// println(f)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn exists(path) {
|
||||||
|
return build_exists(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_dir(path) {
|
||||||
|
return build_is_dir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_file(path) {
|
||||||
|
return build_exists(path) and not build_is_dir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mkdir(path) {
|
||||||
|
build_mkdir(path)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(path) {
|
||||||
|
return build_rm(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy(src, dst) {
|
||||||
|
build_copy(src, dst)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_dir(path) {
|
||||||
|
return build_ls(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn glob(pattern) {
|
||||||
|
return build_glob(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn join(a, b) {
|
||||||
|
return build_join(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base(path) {
|
||||||
|
return build_base(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dir(path) {
|
||||||
|
return build_dir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_lines(path) {
|
||||||
|
return split_lines(read_file(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_lines(path, lines) {
|
||||||
|
write_file(path, join(lines, "\n") + "\n")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cwd() {
|
||||||
|
return cwd()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn env(name) {
|
||||||
|
return getenv(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exec(cmd) {
|
||||||
|
return build_exec(cmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// strings.vr — string helpers (part of the VuurRaaf stdlib).
|
||||||
|
//
|
||||||
|
// import strings
|
||||||
|
// println(strings.join(["a", "b"], ", "))
|
||||||
|
// println(strings.capitalize("hello"))
|
||||||
|
|
||||||
|
fn length(s) {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lines(s) {
|
||||||
|
return split_lines(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split(s, delim) {
|
||||||
|
return split(s, delim)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn join(parts, delim) {
|
||||||
|
return join(parts, delim)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace(s, from, to) {
|
||||||
|
return replace(s, from, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains(s, sub) {
|
||||||
|
return contains(s, sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn starts_with(s, prefix) {
|
||||||
|
return starts_with(s, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ends_with(s, suffix) {
|
||||||
|
return ends_with(s, suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upper(s) {
|
||||||
|
return upper(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lower(s) {
|
||||||
|
return lower(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trim(s) {
|
||||||
|
return trim(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pad(s, width) {
|
||||||
|
return pad(s, width)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pad_left(s, width) {
|
||||||
|
return pad_left(s, width)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repeat(s, n) {
|
||||||
|
return repeat(s, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format(x, spec) {
|
||||||
|
return format(x, spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capitalize(s) {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return upper(s[0]) + s[1..]
|
||||||
|
}
|
||||||
@@ -421,6 +421,52 @@ fn (mut v Vm) json_encode_value(x i64, depth int) !string {
|
|||||||
return v.dec_int(x).str()
|
return v.dec_int(x).str()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// json_pretty_value renders a value as indented, multi-line JSON (the
|
||||||
|
// `json_pretty` builtin used by the json stdlib module).
|
||||||
|
fn (mut v Vm) json_pretty_value(x i64, depth int) !string {
|
||||||
|
ind := ' '.repeat(depth)
|
||||||
|
ind1 := ' '.repeat(depth + 1)
|
||||||
|
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) {
|
||||||
|
a := v.arrays[v.hand(x)]
|
||||||
|
if a.len == 0 {
|
||||||
|
return '[]'
|
||||||
|
}
|
||||||
|
mut parts := []string{}
|
||||||
|
for el in a {
|
||||||
|
parts << ind1 + v.json_pretty_value(el, depth + 1)!
|
||||||
|
}
|
||||||
|
return '[\n' + parts.join(',\n') + '\n' + ind + ']'
|
||||||
|
}
|
||||||
|
if v.is_struct(x) && v.valid_struct_handle(x) {
|
||||||
|
s := v.structs[v.hand(x)]
|
||||||
|
if s.fields.len == 0 {
|
||||||
|
return '{}'
|
||||||
|
}
|
||||||
|
mut parts := []string{}
|
||||||
|
for f in s.fields {
|
||||||
|
parts << ind1 + json_quote(f.name) + ': ' + v.json_pretty_value(f.val, depth + 1)!
|
||||||
|
}
|
||||||
|
return '{\n' + parts.join(',\n') + '\n' + ind + '}'
|
||||||
|
}
|
||||||
|
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
|
// json_quote escapes a string into a JSON string literal. UTF-8 bytes pass
|
||||||
// through untouched; control characters become \\u00XX escapes.
|
// through untouched; control characters become \\u00XX escapes.
|
||||||
fn json_quote(s string) string {
|
fn json_quote(s string) string {
|
||||||
|
|||||||
+12
@@ -513,6 +513,10 @@ fn (mut v Vm) native(id int, _argc int) ! {
|
|||||||
p := v.pop_str()!
|
p := v.pop_str()!
|
||||||
v.push(v.enc_int(bool_i64(os.exists(p))))!
|
v.push(v.enc_int(bool_i64(os.exists(p))))!
|
||||||
}
|
}
|
||||||
|
native_build_is_dir {
|
||||||
|
p := v.pop_str()!
|
||||||
|
v.push(v.enc_int(bool_i64(os.is_dir(p))))!
|
||||||
|
}
|
||||||
native_build_mkdir {
|
native_build_mkdir {
|
||||||
p := v.pop_str()!
|
p := v.pop_str()!
|
||||||
os.mkdir_all(p) or { return error('build_mkdir: cannot create ${p}: ${err.msg()}') }
|
os.mkdir_all(p) or { return error('build_mkdir: cannot create ${p}: ${err.msg()}') }
|
||||||
@@ -635,6 +639,14 @@ fn (mut v Vm) native(id int, _argc int) ! {
|
|||||||
}
|
}
|
||||||
v.push(v.alloc_str(s.repeat(n)))!
|
v.push(v.alloc_str(s.repeat(n)))!
|
||||||
}
|
}
|
||||||
|
native_cwd {
|
||||||
|
v.push(v.alloc_str(os.getwd()))!
|
||||||
|
}
|
||||||
|
native_json_pretty {
|
||||||
|
x := v.pop()!
|
||||||
|
s := v.json_pretty_value(x, 0) or { return error('json_pretty: ${err.msg()}') }
|
||||||
|
v.push(v.alloc_str(s))!
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
return error('unknown native builtin ${id}')
|
return error('unknown native builtin ${id}')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,3 +137,6 @@ const native_split_lines = 161
|
|||||||
const native_pad = 162
|
const native_pad = 162
|
||||||
const native_pad_left = 163
|
const native_pad_left = 163
|
||||||
const native_repeat = 164
|
const native_repeat = 164
|
||||||
|
const native_build_is_dir = 165
|
||||||
|
const native_cwd = 166
|
||||||
|
const native_json_pretty = 167
|
||||||
|
|||||||
@@ -239,20 +239,22 @@ fn (mut v Vm) exec() ! {
|
|||||||
}
|
}
|
||||||
op_jmp {
|
op_jmp {
|
||||||
v.ip++
|
v.ip++
|
||||||
v.ip = int(v.read_i64())
|
// jump targets are PC-relative (delta from the end of the
|
||||||
|
// operand), so merged/linked bytecode stays position-independent
|
||||||
|
v.ip += int(v.read_i64())
|
||||||
}
|
}
|
||||||
op_jz {
|
op_jz {
|
||||||
v.ip++
|
v.ip++
|
||||||
target := int(v.read_i64())
|
target := int(v.read_i64())
|
||||||
if !v.truthy(v.pop()!) {
|
if !v.truthy(v.pop()!) {
|
||||||
v.ip = target
|
v.ip += target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
op_jnz {
|
op_jnz {
|
||||||
v.ip++
|
v.ip++
|
||||||
target := int(v.read_i64())
|
target := int(v.read_i64())
|
||||||
if v.truthy(v.pop()!) {
|
if v.truthy(v.pop()!) {
|
||||||
v.ip = target
|
v.ip += target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
op_call {
|
op_call {
|
||||||
@@ -499,7 +501,7 @@ fn (mut v Vm) exec() ! {
|
|||||||
}
|
}
|
||||||
op_try {
|
op_try {
|
||||||
v.ip++
|
v.ip++
|
||||||
catch_ip := int(v.read_i64())
|
catch_ip := int(v.read_i64()) + v.ip
|
||||||
v.handlers << Handler{ ip: catch_ip, bp: v.bp, sp: v.sp }
|
v.handlers << Handler{ ip: catch_ip, bp: v.bp, sp: v.sp }
|
||||||
}
|
}
|
||||||
op_throw {
|
op_throw {
|
||||||
@@ -536,15 +538,15 @@ fn (mut v Vm) exec() ! {
|
|||||||
return error('cannot call a non-function value')
|
return error('cannot call a non-function value')
|
||||||
}
|
}
|
||||||
entry := v.closures[v.hand(h)].entry
|
entry := v.closures[v.hand(h)].entry
|
||||||
// Shift args left to overwrite the closure slot, then
|
// Shift args left to overwrite the closure slot (and drop the
|
||||||
// insert a return-value placeholder so the callee's
|
// duplicated tail), so the callee's retv lands exactly where the
|
||||||
// retv never overwrites the caller's local that held
|
// call sequence began and no stale value is left below the
|
||||||
// the closure handle.
|
// result. The caller's own local holding the closure sits below
|
||||||
|
// the pushed sequence and is never touched.
|
||||||
for i := 0; i < argc; i++ {
|
for i := 0; i < argc; i++ {
|
||||||
v.stack[v.sp - argc - 1 + i] = v.stack[v.sp - 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-- // drop the duplicated arg tail; closure slot was consumed
|
||||||
v.sp-- // closure was removed; unwind one slot
|
|
||||||
v.call(entry, argc)
|
v.call(entry, argc)
|
||||||
}
|
}
|
||||||
op_argc {
|
op_argc {
|
||||||
|
|||||||
Reference in New Issue
Block a user