diff --git a/README.MD b/README.MD index 5ebca81..abd0430 100644 --- a/README.MD +++ b/README.MD @@ -33,6 +33,9 @@ vr run compile+link+run, or run a binary vr debug run with an instruction trace vr test run every test_* function vr bench [iterations] benchmark main() +vr make [target] [args...] run build.vrmm (target = main) +vr make -f [target] [args...] run another build module +vr build [target] [args...] alias for make vr clean remove .vobj/.vbin artifacts vr up rebuild bin/vr vr symlink link bin/vr into your PATH @@ -71,6 +74,64 @@ Quick start: ./bin/vr run use_lib.vbin ``` +## Build modules (.vrmm) + +A **VuurRaaf Make Module** (`.vrmm`) is build instructions for the toolchain, +written in VuurRaaf itself — the same idea as V's `.vsh` scripts. The toolchain +compiles the module and runs one of its functions (a *target*) with the +`build_*` builtins available, so the script can drive every stage of the +pipeline: compile, assemble, link, run, test, bench, clean, and shell out to +the host. + +``` +# build.vrmm +fn main() { + build_compile("main.vr", "main.vobj") + build_link(["main.vobj"], "main.vbin") +} + +fn clean() { + build_clean() +} +``` + +```bash +vr make # runs main() from build.vrmm +vr make clean # runs the clean() target +vr make deploy --prod # runs deploy() with args() == ["--prod"] +vr make -f x.vrmm t # run target t from another module +``` + +A target that returns a nonzero integer, calls `exit(n)` with `n > 0`, or +`throw`s fails the build. Paths are relative to the working directory; +`build_root()` returns the module's own directory for absolute paths. + +Build builtins: + +| builtin | description | +|---------|-------------| +| `build_compile(src, out)` | source → object (out defaults to `src.vobj`); returns the out path | +| `build_assemble(src, out)` | `.vasm` → object; returns the out path | +| `build_link(objs, out)` | objects → executable; returns the out path | +| `build_run(file)` | compile+link+run a `.vr`, or run a `.vbin`; returns the exit code | +| `build_test(file)` | run every `test_*` function; throws if any fail | +| `build_bench(file, n)` | benchmark `main()` n times | +| `build_clean()` | remove `.vobj`/`.vbin` in the cwd; returns the count | +| `build_exec(cmd)` | run a shell command; returns its output (throws on nonzero exit) | +| `build_exec_status(cmd)` | run a shell command; returns its exit code | +| `build_exists(path)` | 1 if the path exists, else 0 | +| `build_mkdir(path)` | create a directory (and parents) | +| `build_rm(path)` | remove a file or directory tree; returns 1 if something was removed | +| `build_copy(src, dst)` | copy a file or a whole directory tree | +| `build_glob(pattern)` | list files matching a glob (e.g. `"src/*.vr"`) | +| `build_ls(dir)` | list a directory's entries | +| `build_base(path)` / `build_dir(path)` / `build_join(a, b)` | path helpers | +| `build_root()` | absolute directory of the running `.vrmm` | + +`vr init` scaffolds a project with a working `build.vrmm`; see +`examples/build.vrmm` for a tour (targets: `main`, `multi`, `test`, `bench`, +`deploy`, `clean`). + ## The VuurRaaf language A small, V-flavored language. Values are 64-bit integers, 64-bit floats, @@ -261,6 +322,7 @@ main.v CLI entry point (vr ...) repl.v interactive REPL fmt.v source formatter pkg.v package manager (init/get/install/list) +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 diff --git a/compiler/check.v b/compiler/check.v index 6d22f5d..b193ae6 100644 --- a/compiler/check.v +++ b/compiler/check.v @@ -552,6 +552,12 @@ 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 } } + // build-module builtins (.vrmm) + 'build_compile', 'build_assemble', 'build_link', 'build_exec', 'build_base', + 'build_dir', 'build_join', 'build_root' { TypeInfo{ kind: .string_t } } + 'build_glob', 'build_ls' { TypeInfo{ kind: .array_t } } + 'build_run', 'build_test', 'build_bench', 'build_clean', 'build_exec_status', + 'build_exists', 'build_mkdir', 'build_rm', 'build_copy' { TypeInfo{ kind: .int_t } } else { TypeInfo{ kind: .unknown } } } } diff --git a/compiler/codegen.v b/compiler/codegen.v index 4653267..b73d5d7 100644 --- a/compiler/codegen.v +++ b/compiler/codegen.v @@ -903,6 +903,26 @@ fn builtin_spec(name string) (int, int) { 'read_file' { native_read_file, 1 } 'write_file' { native_write_file, 2 } 'eprint' { native_eprint, 1 } + // build-module builtins (.vrmm) — see vm/native.v + 'build_compile' { native_build_compile, 2 } + 'build_assemble' { native_build_assemble, 2 } + 'build_link' { native_build_link, 2 } + 'build_run' { native_build_run, 1 } + 'build_test' { native_build_test, 1 } + 'build_bench' { native_build_bench, 2 } + 'build_clean' { native_build_clean, 0 } + 'build_exec' { native_build_exec, 1 } + 'build_exec_status' { native_build_exec_status, 1 } + 'build_exists' { native_build_exists, 1 } + 'build_mkdir' { native_build_mkdir, 1 } + 'build_rm' { native_build_rm, 1 } + 'build_copy' { native_build_copy, 2 } + 'build_glob' { native_build_glob, 1 } + 'build_ls' { native_build_ls, 1 } + 'build_base' { native_build_base, 1 } + 'build_dir' { native_build_dir, 1 } + 'build_join' { native_build_join, 2 } + 'build_root' { native_build_root, 0 } else { -1, 0 } } } @@ -1131,6 +1151,8 @@ fn (mut g Gen) expr_type(e Expr) string { if e.kind == .call { return match e.name { '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' } else { '' } } } diff --git a/compiler/opcodes.v b/compiler/opcodes.v index c30d491..0632579 100644 --- a/compiler/opcodes.v +++ b/compiler/opcodes.v @@ -105,3 +105,25 @@ const native_sleep = 134 const native_read_file = 135 const native_write_file = 136 const native_eprint = 137 + +// build-module builtins (.vrmm) — available to `vr make` scripts and to any +// program that wants to drive the toolchain +const native_build_compile = 138 +const native_build_assemble = 139 +const native_build_link = 140 +const native_build_run = 141 +const native_build_test = 142 +const native_build_bench = 143 +const native_build_clean = 144 +const native_build_exec = 145 +const native_build_exec_status = 146 +const native_build_exists = 147 +const native_build_mkdir = 148 +const native_build_rm = 149 +const native_build_copy = 150 +const native_build_glob = 151 +const native_build_ls = 152 +const native_build_base = 153 +const native_build_dir = 154 +const native_build_join = 155 +const native_build_root = 156 diff --git a/examples/build.vrmm b/examples/build.vrmm new file mode 100644 index 0000000..8c68d24 --- /dev/null +++ b/examples/build.vrmm @@ -0,0 +1,55 @@ +// build.vrmm — a VuurRaaf Make Module: build instructions for the toolchain, +// written in VuurRaaf itself (like V's .vsh, but for this language). +// +// ./bin/vr make -f examples/build.vrmm build + run hello +// ./bin/vr make -f examples/build.vrmm multi compile/link/run a 2-file program +// ./bin/vr make -f examples/build.vrmm test run tests.vr +// ./bin/vr make -f examples/build.vrmm clean remove artifacts +// ./bin/vr make -f examples/build.vrmm deploy --prod deploy() with args() +// +// Paths are relative to the working directory; build_root() returns this +// module's own directory for scripts that want absolute paths. + +fn main() { + // compile + link + run in one step + build_run("examples/hello.vr") +} + +fn multi() { + // drive each toolchain stage explicitly + let root = build_root() + let lib = build_compile(build_join(root, "lib.vr"), build_join(root, "lib.vobj")) + let use = build_compile(build_join(root, "use_lib.vr"), build_join(root, "use_lib.vobj")) + let bin = build_link([lib, use], build_join(root, "use_lib.vbin")) + build_run(bin) +} + +fn test() { + // run every test_* function; fails the build if any test fails + build_test("examples/tests.vr") +} + +fn bench() { + // benchmark main() of fib.vr 2000 times + build_bench("examples/fib.vr", 2000) +} + +fn deploy() { + // arguments after the target arrive via args() + let args = args() + if len(args) == 0 { + throw "deploy needs a flag: vr make deploy --prod" + } + println("deploying with args: " + join(args, " ")) + let out = build_exec("echo 'deploy step ok'") + println(out) +} + +fn clean() { + // remove this module's artifacts (build_rm returns 1 if it removed something) + let root = build_root() + for f in ["lib.vobj", "lib.vbin", "use_lib.vobj", "use_lib.vbin", "hello.vbin"] { + build_rm(build_join(root, f)) + } + println("cleaned") +} diff --git a/examples/gen_debug.vr b/examples/gen_debug.vr new file mode 100644 index 0000000..4493de7 --- /dev/null +++ b/examples/gen_debug.vr @@ -0,0 +1,9 @@ +// debug: does the function work at all? + +fn double(x) { + return x * 2 +} + +fn main() { + println(double(21)) +} diff --git a/examples/gen_debug2.vr b/examples/gen_debug2.vr new file mode 100644 index 0000000..65e64ae --- /dev/null +++ b/examples/gen_debug2.vr @@ -0,0 +1,11 @@ +// debug: generic function + +fn first[T](arr) { + return arr[0] +} + +fn main() { + let nums = [10, 20, 30] + let x = first(nums) + println(x) +} diff --git a/examples/gen_debug3.vr b/examples/gen_debug3.vr new file mode 100644 index 0000000..1d6c1eb --- /dev/null +++ b/examples/gen_debug3.vr @@ -0,0 +1,13 @@ +// debug: trace function execution + +fn first[T](arr) { + let x = arr[0] + println(x) + return x +} + +fn main() { + let nums = [10, 20, 30] + let x = first(nums) + println("main got: ${x}") +} diff --git a/examples/gen_debug4.vr b/examples/gen_debug4.vr new file mode 100644 index 0000000..1e511bc --- /dev/null +++ b/examples/gen_debug4.vr @@ -0,0 +1,16 @@ +// debug: trace everything + +fn first[T](arr) { + println("inside first") + println(arr) + let x = arr[0] + println("x = ${x}") + return x +} + +fn main() { + println("calling first") + let nums = [10, 20, 30] + let result = first(nums) + println("result = ${result}") +} diff --git a/examples/gen_debug5.vr b/examples/gen_debug5.vr new file mode 100644 index 0000000..07c6903 --- /dev/null +++ b/examples/gen_debug5.vr @@ -0,0 +1,12 @@ +// debug: test arr[0] in function body + +fn first(arr) { + println(arr[0]) + return arr[0] +} + +fn main() { + let nums = [10, 20, 30] + let result = first(nums) + println("result = ${result}") +} diff --git a/examples/gen_debug6.vr b/examples/gen_debug6.vr new file mode 100644 index 0000000..95bd7b7 --- /dev/null +++ b/examples/gen_debug6.vr @@ -0,0 +1,10 @@ +// debug: basic arr[0] + +fn get_first(arr) { + return arr[0] +} + +fn main() { + let nums = [10, 20, 30] + println(get_first(nums)) +} diff --git a/examples/gen_debug7.vr b/examples/gen_debug7.vr new file mode 100644 index 0000000..5ab75b6 --- /dev/null +++ b/examples/gen_debug7.vr @@ -0,0 +1,6 @@ +// debug: inline arr[0] + +fn main() { + let nums = [10, 20, 30] + println(nums[0]) +} diff --git a/examples/gen_test.vr b/examples/gen_test.vr new file mode 100644 index 0000000..f3c3f5d --- /dev/null +++ b/examples/gen_test.vr @@ -0,0 +1,10 @@ +// simple generic test + +fn first[T](arr) { + return arr[0] +} + +fn main() { + let nums = [10, 20, 30] + println(first[int](nums)) +} diff --git a/examples/gen_test2.vr b/examples/gen_test2.vr new file mode 100644 index 0000000..50faf13 --- /dev/null +++ b/examples/gen_test2.vr @@ -0,0 +1,10 @@ +// simple generic test - no type args + +fn first[T](arr) { + return arr[0] +} + +fn main() { + let nums = [10, 20, 30] + println(first(nums)) +} diff --git a/main.v b/main.v index 78fe250..494e77e 100644 --- a/main.v +++ b/main.v @@ -91,6 +91,9 @@ fn main() { 'clean' { toolchain_clean() } + 'make', 'm', 'build' { + toolchain_make(rest) or { die('make', err) } + } 'up' { toolchain_up() or { die('up', err) } } @@ -177,6 +180,9 @@ fn toolchain_help() { println(' get fetch a package into vendor/') println(' install install deps from vr.mod') println(' list show the project manifest') + println(' make [target] [args...] run build.vrmm (target = main)') + println(' make -f [target] [args...] run another build module (.vrmm)') + println(' build alias for make') println(' clean remove build artifacts') println(' up rebuild the vr binary into bin/') println(' symlink link bin/vr into your PATH') @@ -468,6 +474,73 @@ fn run_src_with_args(src string, entry string, trace bool, args []string) ! { // --------------------------------------------------------------------------- // housekeeping +// --------------------------------------------------------------------------- +// make — run a .vrmm build module + +// toolchain_make compiles a .vrmm build module and runs one of its targets. +// A build module is a VuurRaaf program that drives the toolchain through the +// build_* builtins (build_compile, build_link, build_run, build_exec, ...). +// +// vr make runs main() (or build()) from build.vrmm +// vr make clean runs the clean() target +// vr make deploy --prod runs deploy() with args() == ["--prod"] +// vr make -f x.vrmm t runs target t from x.vrmm +// +// A target that returns nonzero (or calls exit(n>0) / throws) fails the build. +fn toolchain_make(args []string) ! { + mut file := 'build.vrmm' + mut rest := []string{} + mut i := 0 + for i < args.len { + if args[i] == '-f' && i + 1 < args.len { + file = args[i + 1] + i += 2 + } else { + rest << args[i] + i++ + } + } + if !os.exists(file) { + return error('no build module "${file}" found (write one, or run `vr init` to scaffold it)') + } + mut target := 'main' + if rest.len > 0 { + target = rest[0] + rest = rest[1..].clone() + } + // compile and link the build module itself + tmp_obj := os.join_path(os.temp_dir(), 'vr_make_${os.getpid()}.vobj') + tmp_bin := os.join_path(os.temp_dir(), 'vr_make_${os.getpid()}.vbin') + defer { + os.rm(tmp_obj) or {} + os.rm(tmp_bin) or {} + } + o := compiler.compile_file(file)! + obj.write(tmp_obj, o)! + linker.link([tmp_obj], tmp_bin)! + bin := obj.read_bin(tmp_bin)! + // pick the entry: an explicit target, else main, else build + mut names := []string{} + for f in bin.fns { + names << f.name + } + mut entry := '' + if target == 'main' && 'main' in names { + entry = 'main' + } else if target == 'main' && 'build' in names { + entry = 'build' + } else if target in names { + entry = target + } else { + return error('no target function "${target}" in ${file} (available: ${names.join(', ')} or main)') + } + println('vr make: ${file} [${entry}]') + code := vm.run_build(bin, entry, rest, os.abs_path(os.dir(file)))! + if code != 0 { + return error('target ${entry} finished with exit code ${code}') + } +} + fn toolchain_clean() { mut n := 0 if files := os.ls('.') { diff --git a/pkg.v b/pkg.v index 6e29901..7482951 100644 --- a/pkg.v +++ b/pkg.v @@ -24,7 +24,29 @@ fn toolchain_init(args []string) ! { if !os.exists('main.vr') { os.write_file('main.vr', 'fn main() {\n\tprintln("hello from ${pkg}")\n}\n')! } - println('initialized project "${pkg}" (${manifest_file}, main.vr)') + if !os.exists('build.vrmm') { + os.write_file('build.vrmm', '// build.vrmm — build instructions for the VuurRaaf toolchain. +// +// vr make runs main() +// vr make clean runs the clean() target +// +// Build builtins: build_compile, build_assemble, build_link, build_run, +// build_test, build_bench, build_clean, build_exec, build_exec_status, +// build_exists, build_mkdir, build_rm, build_copy, build_glob, build_ls, +// build_base, build_dir, build_join, build_root. + +fn main() { + build_compile("main.vr", "main.vobj") + build_link(["main.vobj"], "main.vbin") + println("built main.vbin — run it with: vr run main.vbin") +} + +fn clean() { + build_clean() +} +')! + } + println('initialized project "${pkg}" (${manifest_file}, main.vr, build.vrmm)') } fn toolchain_get(args []string) ! { diff --git a/vm/native.v b/vm/native.v index ed09809..fb63a0b 100644 --- a/vm/native.v +++ b/vm/native.v @@ -11,6 +11,10 @@ import os import math import rand import time +import obj +import compiler +import assembler +import linker fn (mut v Vm) native(id int, _argc int) ! { match id { @@ -346,12 +350,260 @@ fn (mut v Vm) native(id int, _argc int) ! { x := v.pop()! eprintln(v.val_str(x, 0)) } + // ------------------------------------------------------------------- + // build-module builtins (.vrmm) — these let a VuurRaaf program drive + // the toolchain itself, the way V's .vsh scripts drive `v build`. + native_build_compile { + out := v.pop_str()! + src := v.pop_str()! + o := compiler.compile_file(src) or { return error('build_compile: ${err.msg()}') } + o_path := if out.len == 0 { src.all_before_last('.') + '.vobj' } else { out } + obj.write(o_path, o) or { return error('build_compile: cannot write ${o_path}: ${err.msg()}') } + println('compiled ${src} -> ${o_path} (${o.code.len} bytes code, ${o.symbols.len} symbols)') + v.push(v.alloc_str(o_path))! + } + native_build_assemble { + out := v.pop_str()! + src := v.pop_str()! + o := assembler.assemble_file(src) or { return error('build_assemble: ${err.msg()}') } + o_path := if out.len == 0 { src.all_before_last('.') + '.vobj' } else { out } + obj.write(o_path, o) or { return error('build_assemble: cannot write ${o_path}: ${err.msg()}') } + println('assembled ${src} -> ${o_path} (${o.code.len} bytes code)') + v.push(v.alloc_str(o_path))! + } + native_build_link { + out := v.pop_str()! + h := v.pop()! + if !v.is_arr(h) || !v.valid_arr_handle(h) { + return error('build_link() expects an array of object files as its first argument') + } + mut objs := []string{} + for x in v.arrays[v.hand(h)] { + if !v.is_str(x) || !v.valid_handle(x) { + return error('build_link() expects string paths inside the object array') + } + objs << v.strings[v.hand(x)] + } + if objs.len == 0 { + return error('build_link() needs at least one object file') + } + o_path := if out.len == 0 { os.base(objs[0]).all_before_last('.') + '.vbin' } else { out } + linker.link(objs, o_path) or { return error('build_link: ${err.msg()}') } + println('linked ${objs.len} object file(s) -> ${o_path}') + v.push(v.alloc_str(o_path))! + } + native_build_run { + f := v.pop_str()! + if f.ends_with('.vbin') { + bin := obj.read_bin(f) or { return error('build_run: ${err.msg()}') } + code := run_with_args(bin, 'main', false, []string{}) or { + return error('build_run: ${err.msg()}') + } + v.push(v.enc_int(code))! + return + } + if !f.ends_with('.vr') { + return error('build_run() expects a .vr or .vbin file') + } + tmp_obj := os.join_path(os.temp_dir(), 'vr_build_${os.getpid()}.vobj') + tmp_bin := os.join_path(os.temp_dir(), 'vr_build_${os.getpid()}.vbin') + defer { + os.rm(tmp_obj) or {} + os.rm(tmp_bin) or {} + } + o := compiler.compile_file(f) or { return error('build_run: ${err.msg()}') } + obj.write(tmp_obj, o) or { return error('build_run: ${err.msg()}') } + linker.link([tmp_obj], tmp_bin) or { return error('build_run: ${err.msg()}') } + bin := obj.read_bin(tmp_bin) or { return error('build_run: ${err.msg()}') } + code := run_with_args(bin, 'main', false, []string{}) or { + return error('build_run: ${err.msg()}') + } + v.push(v.enc_int(code))! + } + native_build_test { + src := v.pop_str()! + o := compiler.compile_file(src) or { return error('build_test: ${err.msg()}') } + mut tests := []string{} + for s in o.symbols { + if s.name.starts_with('test_') { + tests << s.name + } + } + if tests.len == 0 { + return error('build_test: no test_* functions found in ${src}') + } + tmp_obj := os.join_path(os.temp_dir(), 'vr_build_${os.getpid()}.vobj') + tmp_bin := os.join_path(os.temp_dir(), 'vr_build_${os.getpid()}.vbin') + defer { + os.rm(tmp_obj) or {} + os.rm(tmp_bin) or {} + } + obj.write(tmp_obj, o) or { return error('build_test: ${err.msg()}') } + linker.link([tmp_obj], tmp_bin) or { return error('build_test: ${err.msg()}') } + bin := obj.read_bin(tmp_bin) or { return error('build_test: ${err.msg()}') } + mut passes := 0 + mut fails := 0 + for t in tests { + run(bin, t, false) or { + eprintln(' FAIL ${t} — ${err}') + fails++ + continue + } + println(' PASS ${t}') + passes++ + } + if fails > 0 { + return error('build_test: ${fails} of ${tests.len} test(s) failed in ${src}') + } + println('${passes} test(s) passed') + v.push(v.enc_int(i64(passes)))! + } + native_build_bench { + n := int(v.dec_int(v.pop()!)) + src := v.pop_str()! + if n < 1 { + return error('build_bench() expects a positive iteration count') + } + tmp_obj := os.join_path(os.temp_dir(), 'vr_build_${os.getpid()}.vobj') + tmp_bin := os.join_path(os.temp_dir(), 'vr_build_${os.getpid()}.vbin') + defer { + os.rm(tmp_obj) or {} + os.rm(tmp_bin) or {} + } + o := compiler.compile_file(src) or { return error('build_bench: ${err.msg()}') } + obj.write(tmp_obj, o) or { return error('build_bench: ${err.msg()}') } + linker.link([tmp_obj], tmp_bin) or { return error('build_bench: ${err.msg()}') } + bin := obj.read_bin(tmp_bin) or { return error('build_bench: ${err.msg()}') } + start := time.now().unix_milli() + for _ in 0..n { + run(bin, 'main', false) or { return error('build_bench: ${err.msg()}') } + } + ms := time.now().unix_milli() - start + rate := if ms > 0 { f64(n) / (f64(ms) / 1000.0) } else { f64(0) } + println('bench: ${n} runs of main() in ${ms}ms (${rate:.0} runs/s)') + v.push(v.enc_int(0))! + } + native_build_clean { + mut n := 0 + if files := os.ls('.') { + for f in files { + if f.ends_with('.vobj') || f.ends_with('.vbin') { + os.rm(f) or {} + n++ + } + } + } + println('cleaned ${n} artifact(s)') + v.push(v.enc_int(i64(n)))! + } + native_build_exec { + cmd := v.pop_str()! + res := os.execute(cmd) + if res.exit_code != 0 { + return error('build_exec: "${cmd}" failed with exit code ${res.exit_code}: ${res.output}') + } + v.push(v.alloc_str(res.output))! + } + native_build_exec_status { + cmd := v.pop_str()! + res := os.execute(cmd) + v.push(v.enc_int(i64(res.exit_code)))! + } + native_build_exists { + p := v.pop_str()! + v.push(v.enc_int(bool_i64(os.exists(p))))! + } + native_build_mkdir { + p := v.pop_str()! + os.mkdir_all(p) or { return error('build_mkdir: cannot create ${p}: ${err.msg()}') } + v.push(v.enc_int(0))! + } + native_build_rm { + p := v.pop_str()! + if !os.exists(p) { + v.push(v.enc_int(0))! + return + } + if os.is_dir(p) { + os.rmdir_all(p) or { return error('build_rm: cannot remove ${p}: ${err.msg()}') } + } else { + os.rm(p) or { return error('build_rm: cannot remove ${p}: ${err.msg()}') } + } + v.push(v.enc_int(1))! + } + native_build_copy { + dst := v.pop_str()! + src := v.pop_str()! + if os.is_dir(src) { + v.copy_tree(src, dst) or { return error('build_copy: ${err.msg()}') } + } else { + os.cp(src, dst, os.CopyParams{}) or { + return error('build_copy: cannot copy ${src} -> ${dst}: ${err.msg()}') + } + } + v.push(v.enc_int(0))! + } + native_build_glob { + pat := v.pop_str()! + files := os.glob(pat) or { return error('build_glob: ${err.msg()}') } + mut arr := []i64{} + for f in files { + v.strings << f + arr << v.mkstr(v.strings.len - 1) + } + v.arrays << arr + v.push(v.mkarr(v.arrays.len - 1))! + } + native_build_ls { + dir := v.pop_str()! + entries := os.ls(dir) or { return error('build_ls: cannot read ${dir}: ${err.msg()}') } + mut arr := []i64{} + for f in entries { + v.strings << f + arr << v.mkstr(v.strings.len - 1) + } + v.arrays << arr + v.push(v.mkarr(v.arrays.len - 1))! + } + native_build_base { + p := v.pop_str()! + v.push(v.alloc_str(os.base(p)))! + } + native_build_dir { + p := v.pop_str()! + v.push(v.alloc_str(os.dir(p)))! + } + native_build_join { + b := v.pop_str()! + a := v.pop_str()! + v.push(v.alloc_str(os.join_path(a, b)))! + } + native_build_root { + v.push(v.alloc_str(v.build_root))! + } else { return error('unknown native builtin ${id}') } } } +// 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) { + return error('${src} is not a directory') + } + os.mkdir_all(dst) or { return error('cannot create ${dst}: ${err.msg()}') } + for entry in os.ls(src) or { return error('cannot read ${src}: ${err.msg()}') } { + sp := os.join_path(src, entry) + dp := os.join_path(dst, entry) + if os.is_dir(sp) { + v.copy_tree(sp, dp)! + } else if os.is_file(sp) { + os.cp(sp, dp, os.CopyParams{}) or { return error('cannot copy ${sp}: ${err.msg()}') } + } + } +} + // pop_str pops the top value and requires it to be a string. fn (mut v Vm) pop_str() !string { x := v.pop()! diff --git a/vm/opcodes.v b/vm/opcodes.v index 1836d39..27fbbf7 100644 --- a/vm/opcodes.v +++ b/vm/opcodes.v @@ -105,3 +105,24 @@ const native_sleep = 134 const native_read_file = 135 const native_write_file = 136 const native_eprint = 137 + +// build-module builtins (.vrmm) — driven by `vr make` +const native_build_compile = 138 +const native_build_assemble = 139 +const native_build_link = 140 +const native_build_run = 141 +const native_build_test = 142 +const native_build_bench = 143 +const native_build_clean = 144 +const native_build_exec = 145 +const native_build_exec_status = 146 +const native_build_exists = 147 +const native_build_mkdir = 148 +const native_build_rm = 149 +const native_build_copy = 150 +const native_build_glob = 151 +const native_build_ls = 152 +const native_build_base = 153 +const native_build_dir = 154 +const native_build_join = 155 +const native_build_root = 156 diff --git a/vm/types.v b/vm/types.v index 19d85de..95978d8 100644 --- a/vm/types.v +++ b/vm/types.v @@ -49,6 +49,7 @@ mut: lines []obj.LineInfo // debug info: code offset -> source line const_strs int // strings[0..const_strs] are bytecode constants, never collected last_heap int // heap size at the last GC check (allocation trigger) + build_root string // directory of the .vrmm build module (build_root() builtin) } fn bool_i64(b bool) i64 { diff --git a/vm/vm.v b/vm/vm.v index 837bfea..7b95d34 100644 --- a/vm/vm.v +++ b/vm/vm.v @@ -17,6 +17,17 @@ pub fn run(bin obj.Bin, entry string, trace bool) !i64 { // run_with_args is run() with command-line arguments exposed to the program // via the `args()` builtin. pub fn run_with_args(bin obj.Bin, entry string, trace bool, args []string) !i64 { + return run_internal(bin, entry, trace, args, '')! +} + +// run_build executes a .vrmm build module: the entry target receives the +// extra CLI arguments via `args()`, and `build_root()` reports the module's +// own directory so scripts can find files regardless of the working directory. +pub fn run_build(bin obj.Bin, entry string, args []string, root string) !i64 { + return run_internal(bin, entry, false, args, root)! +} + +fn run_internal(bin obj.Bin, entry string, trace bool, args []string, root string) !i64 { mut v := Vm{ code: bin.code strings: bin.strings.clone() @@ -25,6 +36,7 @@ pub fn run_with_args(bin obj.Bin, entry string, trace bool, args []string) !i64 prog_args: args lines: bin.lines const_strs: bin.strings.len + build_root: root } mut entry_ip := -1 for f in bin.fns {