diff options
Diffstat (limited to 'tests')
378 files changed, 5333 insertions, 1071 deletions
diff --git a/tests/actor/accept/declaration.zc b/tests/actor/accept/declaration.zc new file mode 100644 index 0000000..099c0eb --- /dev/null +++ b/tests/actor/accept/declaration.zc @@ -0,0 +1,11 @@ +namespace; +include rt, actor; +typecheck on; +actor Counter { int total; int handle(int value) { this.total += value; return this.total; } } +fn main() { + Counter* a = rt.new(Counter), b = rt.new(Counter); let left = 0, right = 0; + a.spawn(); b.spawn(); a.recv(fn(int n) { left = n; return 0; }); b.recv(fn(int n) { right = n; return 0; }); + a.send(2); a.send(3); b.send(7); a.await(); b.await(); + rt.put_n(left); rt.put_c(' '); rt.put_n(right); rt.put_c(' '); rt.put_n(a.total); rt.put_c(' '); rt.put_n(b.total); rt.put_c(10); + a.close(); b.close(); return 0; +} diff --git a/tests/actor/accept/failed_worker.zc b/tests/actor/accept/failed_worker.zc new file mode 100644 index 0000000..056a6b6 --- /dev/null +++ b/tests/actor/accept/failed_worker.zc @@ -0,0 +1,11 @@ +namespace; +include rt, actor; +typecheck on; +int fail(int n) { rt.syscall(7, 2, 0, 0); return n; } +int healthy(int n) { return n + 1; } +fn main() { + actor.Actor<int,int>* a = rt.new(actor.Actor<int,int>); a.spawn(fail); a.recv(fn(int n) { return 0; }); a.send(1); + rt.put_n(a.await()); rt.put_c(' '); rt.put_n(a.send(2)); rt.put_c(' '); a.close(); + actor.Actor<int,int>* b = rt.new(actor.Actor<int,int>); let result = 0; b.spawn(healthy); b.recv(fn(int n) { result = n; return 0; }); b.send(2); b.await(); + rt.put_n(result); rt.put_c(10); b.close(); return 0; +} diff --git a/tests/actor/accept/functions.zc b/tests/actor/accept/functions.zc new file mode 100644 index 0000000..f869789 --- /dev/null +++ b/tests/actor/accept/functions.zc @@ -0,0 +1,12 @@ +namespace; +include rt, actor; +typecheck on; +type Operation = int(int); +int handle(Operation operation) { return operation(3); } +fn main() { + actor.Actor<Operation,int>* a = rt.new(actor.Actor<Operation,int>); + a.spawn(handle); let result = 0; a.recv(fn(int n) { result = n; return 0; }); + char* text = rt.new(char, 3); text[0] = 'a'; text[1] = 'b'; let offset = 7; float scale = 2.0; + Operation operation = fn:int(int n) { if (scale == 2.0) return n + offset + rt.len_w(text); return -1; }; + a.send(operation); a.await(); rt.put_n(result); rt.put_c(10); a.close(); return 0; +} diff --git a/tests/actor/accept/generic_declaration.zc b/tests/actor/accept/generic_declaration.zc new file mode 100644 index 0000000..a5e5e77 --- /dev/null +++ b/tests/actor/accept/generic_declaration.zc @@ -0,0 +1,15 @@ +namespace; +include rt, actor; +typecheck on; +actor Counter<T> { T total; T handle(T input) { this.total += input; return this.total; } } +fn main() { + Counter<int>* a = rt.new(Counter<int>), b = rt.new(Counter<int>); + Counter<float>* c = rt.new(Counter<float>); int left = 0, right = 0, floating = 0; + a.spawn(); b.spawn(); c.spawn(); + a.recv(fn(int n) { left = n; return 0; }); b.recv(fn(int n) { right = n; return 0; }); + c.recv(fn(float n) { floating = n; return 0; }); + a.send(2); a.send(3); b.send(7); c.send(1.5); c.send(2.5); + a.await(); b.await(); c.await(); + rt.put_n(left); rt.put_c(' '); rt.put_n(right); rt.put_c(' '); rt.put_n(floating); rt.put_c(' '); rt.put_n(a.total); rt.put_c(10); + a.close(); b.close(); c.close(); return 0; +} diff --git a/tests/actor/accept/graphs.zc b/tests/actor/accept/graphs.zc new file mode 100644 index 0000000..f90469c --- /dev/null +++ b/tests/actor/accept/graphs.zc @@ -0,0 +1,14 @@ +namespace; +include rt, actor; +typecheck on; +struct Node { int value; Node* next; } +struct Message { char* text; int[?] numbers; Node* node; } +int handle(Message* m) { m.text[0] = 'X'; m.numbers[0] += 10; return rt.len_w(m.text) + m.numbers[0] + m.node.next.value; } +fn main() { + actor.Actor<Message*, int>* a = rt.new(actor.Actor<Message*, int>); + a.spawn(handle); let result = 0; a.recv(fn(int n) { result = n; return 0; }); + Message* m = rt.new(Message); m.text = rt.new(char, 4); m.text[0] = 'a'; m.text[1] = 'b'; m.text[2] = 'c'; + m.numbers = rt.new(int[?]); push(m.numbers, 5); m.node = rt.new(Node); m.node.value = 7; m.node.next = m.node; + a.send(m); a.await(); + rt.put_n(result); rt.put_c(' '); rt.put_c(m.text[0]); rt.put_c(' '); rt.put_n(m.numbers[0]); rt.put_c(10); a.close(); return 0; +} diff --git a/tests/actor/accept/lifecycle.zc b/tests/actor/accept/lifecycle.zc new file mode 100644 index 0000000..0c953e4 --- /dev/null +++ b/tests/actor/accept/lifecycle.zc @@ -0,0 +1,10 @@ +namespace; +include rt, actor; +typecheck on; +int handle(int n) { return n; } +fn main() { + let total = 0; + { actor.Actor<int,int>* a = rt.new(actor.Actor<int,int>); a.spawn(handle); a.recv(fn(int n) { total += n; return 0; }); a.send(1); a.await(); a.close(); rt.put_n(a.send(3)); rt.put_c(' '); a.close(); } + { actor.Actor<int,int>* b = rt.new(actor.Actor<int,int>); b.spawn(handle); b.recv(fn(int n) { total += n; return 0; }); b.send(2); b.await(); b.close(); } + rt.put_n(total); rt.put_c(10); return 0; +} diff --git a/tests/actor/accept/parallel.zc b/tests/actor/accept/parallel.zc new file mode 100644 index 0000000..3d47f34 --- /dev/null +++ b/tests/actor/accept/parallel.zc @@ -0,0 +1,18 @@ +namespace; +include rt, actor; +typecheck on; +int delayed(int n) { rt.syscall(48, 300, 0, 0); return n + 1; } +fn main() { + actor.Actor<int, int>* a = rt.new(actor.Actor<int, int>), b = rt.new(actor.Actor<int, int>); + let first = 0, second = 0; + a.spawn(delayed); b.spawn(delayed); + a.recv(fn(int value) { first = value; return 0; }); + b.recv(fn(int value) { second = value; return 0; }); + let started = rt.syscall(50, 0, 0, 0); + a.send(10); b.send(20); + rt.put_n(first); rt.put_c(' '); rt.put_n(second); rt.put_c(10); + a.await(); b.await(); + rt.put_n(first); rt.put_c(' '); rt.put_n(second); rt.put_c(10); + rt.put_n(rt.syscall(50, 0, 0, 0) - started); rt.put_c(10); + a.close(); b.close(); return 0; +} diff --git a/tests/actor/accept/pointer_scalar.zc b/tests/actor/accept/pointer_scalar.zc new file mode 100644 index 0000000..219129a --- /dev/null +++ b/tests/actor/accept/pointer_scalar.zc @@ -0,0 +1,13 @@ +namespace; +include rt, actor; +typecheck on; +struct Node { int value; } +struct Message { int marker; Node* node; } +fn main() { + Node* original = rt.new(Node); original.value = 4; let marker = (original as int) + 0; + actor.Actor<Message*,int>* a = rt.new(actor.Actor<Message*,int>); + a.spawn(fn:int(Message* m) { return m.marker == marker && m.node as int != m.marker && m.node.value == 4; }); + let result = 0; a.recv(fn(int n) { result = n; return 0; }); + Message* message = rt.new(Message); message.marker = marker; message.node = original; + a.send(message); a.await(); rt.put_n(result); rt.put_c(10); a.close(); return 0; +} diff --git a/tests/actor/accept/results.zc b/tests/actor/accept/results.zc new file mode 100644 index 0000000..9cc557e --- /dev/null +++ b/tests/actor/accept/results.zc @@ -0,0 +1,9 @@ +namespace; +include rt, actor; +typecheck on; +char*.u respond(int n) { char* p = rt.new(char, 3); p[0] = 'o'; p[1] = 'k'; return u*p; } +fn main() { + actor.Actor<int, char*>* a = rt.new(actor.Actor<int, char*>); + a.spawn(respond); a.recv(fn(char* value) { rt.put_s(value); rt.put_c(10); return 0; }); + a.send(1); a.await(); a.close(); return 0; +} diff --git a/tests/actor/accept/safepoint.zc b/tests/actor/accept/safepoint.zc new file mode 100644 index 0000000..23c8eca --- /dev/null +++ b/tests/actor/accept/safepoint.zc @@ -0,0 +1,10 @@ +namespace; +include rt, actor; +typecheck on; +int handle(int n) { return n * 2; } +fn main() { + actor.Actor<int,int>* a = rt.new(actor.Actor<int,int>); + let result = 0; a.spawn(handle); a.recv(fn(int n) { result = n; return 0; }); + a.send(4); rt.syscall(48, 50, 0, 0); + rt.put_n(result); rt.put_c(10); a.close(); return 0; +} diff --git a/tests/actor/accept/state.zc b/tests/actor/accept/state.zc new file mode 100644 index 0000000..e53a12a --- /dev/null +++ b/tests/actor/accept/state.zc @@ -0,0 +1,15 @@ +namespace; +include rt, actor; +typecheck on; +actor Counter { int value; fn add(int n) { this.value += n; return this.value; } int handle(int n) { return this.add(n); } } +fn main() { + actor.Actor<int, int>* a = rt.new(actor.Actor<int, int>), b = rt.new(actor.Actor<int, int>); + let state = 0, aresult = 0, bresult = 0; + a.spawn(fn(int n) { state += n; return state; }); + b.spawn(fn(int n) { state += n; return state; }); + a.recv(fn(int n) { aresult = n; return 0; }); b.recv(fn(int n) { bresult = n; return 0; }); + a.send(2); a.send(3); b.send(7); a.await(); b.await(); + rt.put_n(state); rt.put_c(' '); rt.put_n(aresult); rt.put_c(' '); rt.put_n(bresult); rt.put_c(10); + Counter c; c.value = 0; c.add(4); rt.put_n(c.value); rt.put_c(10); + a.close(); b.close(); return 0; +} diff --git a/tests/actor/accept/values.zc b/tests/actor/accept/values.zc new file mode 100644 index 0000000..2f6c103 --- /dev/null +++ b/tests/actor/accept/values.zc @@ -0,0 +1,12 @@ +namespace; +include rt, actor; +typecheck on; +struct Payload { i16 small; u32 large; float fraction; bstr text; } +float handle(Payload* p) { return p.small + p.large + p.fraction + rt.len_s(p.text); } +fn main() { + actor.Actor<Payload*,float>* a = rt.new(actor.Actor<Payload*,float>); + a.spawn(handle); let answer = 0; + a.recv(fn(float n) { answer = n == 6.5; return 0; }); + Payload p; p.small = -2; p.large = 4; p.fraction = 1.5; p.text = b"abc"; + a.send(&p); a.await(); rt.put_n(answer); rt.put_c(10); a.close(); return 0; +} diff --git a/tests/actor/regression/generic_extensions.zc b/tests/actor/regression/generic_extensions.zc new file mode 100644 index 0000000..4a4476d --- /dev/null +++ b/tests/actor/regression/generic_extensions.zc @@ -0,0 +1,8 @@ +namespace; +include rt, run; +run.syntax("record", run.kw_struct); +record Value { int amount; } +int calls; +fn tick() { calls++; return 0; } +run.statement_hook(tick); +fn main() { int value = 1; value++; rt.put_n(calls); rt.put_c(10); return 0; } diff --git a/tests/actor/regression/generic_typed_method.zc b/tests/actor/regression/generic_typed_method.zc new file mode 100644 index 0000000..2038ad3 --- /dev/null +++ b/tests/actor/regression/generic_typed_method.zc @@ -0,0 +1,5 @@ +namespace; +include rt; +typecheck on; +struct Box<T> { T value; T echo(T input) { this.value = input; return this.value; } } +fn main() { Box<int> a; Box<float> b; rt.put_n(a.echo(7)); rt.put_c(' '); int result = b.echo(2.5); rt.put_n(result); rt.put_c(10); return 0; } diff --git a/tests/actor/regression/module_helpers.zc b/tests/actor/regression/module_helpers.zc new file mode 100644 index 0000000..90f481f --- /dev/null +++ b/tests/actor/regression/module_helpers.zc @@ -0,0 +1,7 @@ +namespace helpers; +include rt; +int offset = 6; +T add<T>(T value) { return value + offset; } +namespace; +include rt, helpers; +fn main() { rt.put_n(helpers.add(4)); rt.put_c(10); return 0; } diff --git a/tests/actor/regression/plain.zc b/tests/actor/regression/plain.zc new file mode 100644 index 0000000..307d80c --- /dev/null +++ b/tests/actor/regression/plain.zc @@ -0,0 +1,4 @@ +namespace; +include rt; +struct Counter { int value; fn add(int n) { this.value += n; return this.value; } } +fn main() { Counter c; rt.put_n(c.add(3)); rt.put_c(10); return 0; } diff --git a/tests/actor/reject/explicit_this.zc b/tests/actor/reject/explicit_this.zc new file mode 100644 index 0000000..caa30d0 --- /dev/null +++ b/tests/actor/reject/explicit_this.zc @@ -0,0 +1,4 @@ +namespace; +include rt, actor; +actor Counter { int value; fn add(Counter* this, int n) { this.value += n; return this.value; } } +fn main() { Counter c; return c.add(1); } diff --git a/tests/actor/reject/generic_input.zc b/tests/actor/reject/generic_input.zc new file mode 100644 index 0000000..d13bc7c --- /dev/null +++ b/tests/actor/reject/generic_input.zc @@ -0,0 +1,5 @@ +namespace; +include rt, actor; +typecheck on; +actor Counter<T> { T handle(T value) { return value; } } +fn main() { Counter<int>* a = rt.new(Counter<int>); a.spawn(); a.send("wrong"); return 0; } diff --git a/tests/actor/reject/generic_method_return.zc b/tests/actor/reject/generic_method_return.zc new file mode 100644 index 0000000..4b2ba9b --- /dev/null +++ b/tests/actor/reject/generic_method_return.zc @@ -0,0 +1,5 @@ +namespace; +include rt; +typecheck on; +struct Box<T> { T wrong(T input) { return "wrong"; } } +fn main() { Box<int> a; a.wrong(7); return 0; } diff --git a/tests/actor/reject/input_type.zc b/tests/actor/reject/input_type.zc new file mode 100644 index 0000000..259f772 --- /dev/null +++ b/tests/actor/reject/input_type.zc @@ -0,0 +1,5 @@ +namespace; +include rt, actor; +typecheck on; +int handle(int n) { return n; } +fn main() { actor.Actor<int,int>* a = rt.new(actor.Actor<int,int>); a.spawn(handle); a.send("wrong"); a.close(); return 0; } diff --git a/tests/actor/reject/nested_missing_import.zc b/tests/actor/reject/nested_missing_import.zc new file mode 100644 index 0000000..bdf42bd --- /dev/null +++ b/tests/actor/reject/nested_missing_import.zc @@ -0,0 +1,3 @@ +namespace; +include run; +fn main() { run.evals("return rt.len_w(\"abc\");"); return 0; } diff --git a/tests/actor/reject/no_include.zc b/tests/actor/reject/no_include.zc new file mode 100644 index 0000000..662bb4f --- /dev/null +++ b/tests/actor/reject/no_include.zc @@ -0,0 +1,4 @@ +namespace; +include rt; +actor Counter { int value; } +fn main() { return 0; } diff --git a/tests/actor/reject/output_type.zc b/tests/actor/reject/output_type.zc new file mode 100644 index 0000000..1d0df34 --- /dev/null +++ b/tests/actor/reject/output_type.zc @@ -0,0 +1,5 @@ +namespace; +include rt, actor; +typecheck on; +int handle(int n) { return n; } +fn main() { actor.Actor<int,int>* a = rt.new(actor.Actor<int,int>); a.spawn(handle); a.recv(fn(char* n) { return 0; }); a.close(); return 0; } diff --git a/tests/bench/bench_bytes.sh b/tests/bench/bench_bytes.sh index 3287a91..acff074 100644 --- a/tests/bench/bench_bytes.sh +++ b/tests/bench/bench_bytes.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e # byte-load loop, timed on a run-only image so the resident compiler is not # in the process. the slope between 1 repetition and N drops startup. # prints: backend ms/rep checksum @@ -6,10 +7,8 @@ cd "$(dirname "$0")/../.." T=$(mktemp -d) trap 'rm -rf "$T"' EXIT gcc -O2 -w src/tools/genmap.c -o "$T/genmap" || exit 1 -"$T/genmap" --preset run -o "$T/run" >/dev/null || exit 1 -cp src/tools/zc0.c src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h "$T/run/" -gcc -O1 -w "$T/run/zc0.c" -o "$T/zc0" || exit 1 -cat "$T/run/rt.zc" src/runtime/run.zc tests/programs/bench_bytes.zc | "$T/zc0" > "$T/run/genome.h" || exit 1 +GENMAP="$T/genmap" bash src/tools/automap.sh run "$T/run" src/runtime/run.zc tests/programs/bench_bytes.zc >/dev/null || exit 1 +cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h "$T/run/" gcc -O2 -w -DJTYPE=int "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zy" || exit 1 gcc -O2 -w "$T/run/micro.c" "$T/run/port_linux.c" -o "$T/zym" || exit 1 vms=("$T/zy" "$T/zym"); names=("interp" "micro") @@ -22,12 +21,15 @@ if command -v aarch64-linux-gnu-gcc >/dev/null && command -v qemu-aarch64 >/dev/ && { vms+=("qemu-aarch64 $T/zyj"); names+=("arm64"); } fi # seconds for $2 repetitions on vm $1 -once(){ local s e; s=$(date +%s.%N); printf '%s' "$2" | $1 >/tmp/bench_bytes.out; e=$(date +%s.%N); python3 -c "print($e - $s)"; } +once(){ python3 -c 'import shlex, statistics, subprocess, sys, time; samples=[]; output=b""; cmd=shlex.split(sys.argv[1]); data=sys.argv[2].encode(); +for _ in range(5): + start=time.perf_counter(); p=subprocess.run(cmd, input=data, stdout=subprocess.PIPE); p.check_returncode(); samples.append(time.perf_counter()-start); output=p.stdout +open("/tmp/bench_bytes.out", "wb").write(output); print(statistics.median(samples))' "$1" "$2"; } printf "%-10s %12s %s\n" backend "ms/rep" checksum for i in "${!vms[@]}"; do one=$(once "${vms[$i]}" 1) - many=$(once "${vms[$i]}" 40) - ms=$(python3 -c "print(round(($many - $one) / 39 * 1000, 3))") + many=$(once "${vms[$i]}" 1000) + ms=$(python3 -c "print(round(($many - $one) / 999 * 1000, 3))") sum=$(tr -d '\n' < /tmp/bench_bytes.out) printf "%-10s %12s %s\n" "${names[$i]}" "$ms" "$sum" done diff --git a/tests/bench/run.py b/tests/bench/run.py index 69cb943..cb28b92 100755 --- a/tests/bench/run.py +++ b/tests/bench/run.py @@ -260,8 +260,8 @@ def main(): if key not in bins: continue add(sieve_rows, label, c_call(bins[key], mode), SIEVE_N) - add(sieve_rows, "zc interp, plain, bounds-checked []int", zc_call(vms["interp"], sieve_src, "a"), SIEVE_N) - add(sieve_rows, "zc interp, o3, bounds-checked []int", zc_call(vms["interp"], sieve_src, "b"), SIEVE_N) + add(sieve_rows, "zc interp, plain, bounds-checked int[?]", zc_call(vms["interp"], sieve_src, "a"), SIEVE_N) + add(sieve_rows, "zc interp, o3, bounds-checked int[?]", zc_call(vms["interp"], sieve_src, "b"), SIEVE_N) add(sieve_rows, "zc interp, o3, raw *int", zc_call(vms["interp"], sieve_src, "c"), SIEVE_N) if "llvm" in vms: hot = {"ZYHOT": "1"} diff --git a/tests/bench/sieve.zc b/tests/bench/sieve.zc index 1fb579b..9259bf4 100644 --- a/tests/bench/sieve.zc +++ b/tests/bench/sieve.zc @@ -1,11 +1,11 @@ // primes below N. the same loops as tests/bench/sieve.c: one word per flag, // marks start at i*i. the count is 9592. -// a fixed []int, bounds-checked, plain bytecode +// a fixed int[?], bounds-checked, plain bytecode // b the same function lifted by o3 // c o3 through a raw *int, no per-index check const int N = 100000; -int sieve(fixed []int f) { +int sieve(fixed int[?] f) { int n = len(f), c = 0; for (int i = 2; i < n; i++) f[i] = 1; for (int i = 2; i * i < n; i++) @@ -13,7 +13,7 @@ int sieve(fixed []int f) { for (int i = 2; i < n; i++) c += f[i]; return c; } -o3 int sieve3(fixed []int f) { +o3 int sieve3(fixed int[?] f) { int n = len(f), c = 0; for (int i = 2; i < n; i++) f[i] = 1; for (int i = 2; i * i < n; i++) @@ -39,7 +39,7 @@ int main() { free(d*raw); return 0; } - fixed []int f = new(fixed []int, N); + fixed int[?] f = new(fixed int[?], N); while (reps > 0) { if (which == 'b') r = sieve3(f); else r = sieve(f); diff --git a/tests/data/readfile_growth.txt b/tests/data/readfile_growth.txt new file mode 100644 index 0000000..033198b --- /dev/null +++ b/tests/data/readfile_growth.txt @@ -0,0 +1 @@ +0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 diff --git a/tests/drivers/actor_image_test.py b/tests/drivers/actor_image_test.py new file mode 100644 index 0000000..9df8828 --- /dev/null +++ b/tests/drivers/actor_image_test.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# source syntax callbacks execute in the host while actor code is emitted separately. +from pathlib import Path +import argparse +import subprocess +import tempfile + +parser = argparse.ArgumentParser() +parser.add_argument('--host', default='bin/zy') +parser.add_argument('--case', default='') +args = parser.parse_args() +prefix = b''.join(Path(f).read_bytes() + b'\n' for f in ('src/runtime/rt.zc', 'src/core/core.zc', 'src/runtime/run.zc')) +cases = [('declaration', Path('tests/actor/accept/declaration.zc').read_text(), b'5 7 0 0\n'), ('generic', Path('tests/actor/accept/generic_declaration.zc').read_text(), b'5 7 4 0\n')] +for name, source, expected in cases: + if args.case and args.case not in name: + continue + with tempfile.TemporaryDirectory(prefix='tinyvm-actor-image-') as work: + directory = Path(work) + compiled = subprocess.run([args.host], input=Path('src/tools/build.zc').read_bytes() + b'`' + prefix + source.encode(), capture_output=True, timeout=30) + assert compiled.returncode == 0 and len(compiled.stdout) >= 56 and not compiled.stdout.startswith(b'zc:'), (name, compiled.stdout[:2000], compiled.stderr) + header = subprocess.run(['bin/mkgenome'], input=compiled.stdout, capture_output=True, check=True) + (directory / 'genome.h').write_bytes(header.stdout) + for f in ('src/core/memmap.h', 'src/core/fops.h', 'src/core/bops.h', 'src/core/stack.h', 'src/micro/micro.c', 'src/micro/micro_port.h', 'src/micro/port_linux.c'): + (directory / Path(f).name).write_bytes(Path(f).read_bytes()) + subprocess.run(['cc', '-O2', '-w', '-fwrapv', '-pthread', '-DZY_THREADS', str(directory / 'micro.c'), str(directory / 'port_linux.c'), '-I' + work, '-o', str(directory / 'image')], check=True, timeout=30) + result = subprocess.run([str(directory / 'image')], capture_output=True, timeout=12) + assert result.returncode == 0 and result.stdout == expected, (name, result.returncode, result.stdout, result.stderr) + print('ok actor compiler-free image: ' + name) diff --git a/tests/drivers/actor_test.py b/tests/drivers/actor_test.py new file mode 100644 index 0000000..43a110f --- /dev/null +++ b/tests/drivers/actor_test.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# exercise syntax injection, isolated workers, typed graph copying and regressions. +from pathlib import Path +import argparse +import subprocess +import time + +parser = argparse.ArgumentParser() +parser.add_argument('vm', nargs='?', default='bin/zy') +parser.add_argument('--case', default='') +parser.add_argument('--library', default='') +args = parser.parse_args() +root = Path(__file__).resolve().parents[2] +expected = {'parallel': '0 0\n11 21\n', 'state': '0 5 7\n4\n', 'graphs': '25 a 5\n', 'results': 'ok\n', 'plain': '3\n', 'module_helpers': '10\n', 'safepoint': '8\n', 'values': '1\n', 'functions': '12\n', 'pointer_scalar': '1\n', 'declaration': '5 7 0 0\n', 'lifecycle': '-1 3\n', 'generic_extensions': '3\n', 'failed_worker': '-1 -1 3\n', 'generic_declaration': '5 7 4 0\n', 'generic_typed_method': '7 2\n'} +count = 0 +for case in sorted((root / 'tests/actor').glob('*/*.zc')): + if args.case and args.case not in str(case): + continue + start = time.monotonic() + source = case.read_text() + if args.library: + source = source.replace('include rt, actor;', f'include rt; include \"{args.library}\"; include actor;') + proc = subprocess.run([str(root / args.vm)], input=source, text=True, capture_output=True, cwd=root, timeout=15) + elapsed = time.monotonic() - start + if case.parent.name == 'reject': + required = {'no_include': "actor: isn't a type", 'input_type': 'type error:', 'output_type': 'type error:', 'explicit_this': 'this is implicit', 'nested_missing_import': 'module access requires `include rt;`', 'generic_input': 'type error:', 'generic_method_return': 'type error:'}[case.stem] + assert required in proc.stdout, f'{case}: expected {required!r}, got {proc.stdout!r}' + else: + actual = proc.stdout + if case.stem == 'parallel': + lines = actual.splitlines(keepends=True) + assert len(lines) == 3, f'invalid parallel output: {actual!r}' + runtime_ms = int(lines[-1]) + assert 280 <= runtime_ms < 580, f'workers did not overlap: {runtime_ms}ms' + actual = ''.join(lines[:-1]) + assert proc.returncode == 0 and actual == expected[case.stem], f'{case}: stdout={proc.stdout!r}, stderr={proc.stderr!r}' + print(f'ok {case.relative_to(root)} ({elapsed:.3f}s)') + count += 1 +assert count, 'no actor fixtures selected' +print(f'{count} actor fixtures passed') diff --git a/tests/drivers/analysis_cache_test.js b/tests/drivers/analysis_cache_test.js new file mode 100644 index 0000000..3ffde33 --- /dev/null +++ b/tests/drivers/analysis_cache_test.js @@ -0,0 +1,45 @@ +// Repeated replies use cached results; edits, include updates and reopen invalidate them. +const assert = require('assert'), cp = require('child_process'), path = require('path'); +const uri = 'file:///cache/main.zc', text = 'include "dep.zc";\nfn main() { int n = 7; return n + value(); }'; +const msgs = []; let id = 1; +const request = (method, params = {}) => { const rid = id++; msgs.push({ id: rid, method, params }); return rid; }; +const note = (method, params) => msgs.push({ method, params }); +const stats = () => request('zc/cacheStats'); +const hover = () => request('textDocument/hover', { textDocument: { uri }, position: { line: 1, character: 30 } }); +request('initialize'); note('zc/file', { path: '/cache/dep.zc', text: 'int value() { return 1; }' }); +note('textDocument/didOpen', { textDocument: { uri, version: 1, text } }); +const a = hover(), before = stats(), b = hover(), after = stats(); +const methods = ['textDocument/semanticTokens/full', 'textDocument/definition', 'textDocument/references', 'textDocument/completion']; +const pairs = methods.map(method => [request(method, { textDocument: { uri }, position: { line: 1, character: 30 } }), request(method, { textDocument: { uri }, position: { line: 1, character: 30 } })]); +const all = stats(); +note('textDocument/didChange', { textDocument: { uri, version: 2 }, contentChanges: [{ text: text.replace('= 7', '= 9') }] }); +const changed = hover(), editStats = stats(); +note('zc/file', { path: '/cache/dep.zc', text: 'int value() { return 2; }' }); +hover(); const includeStats = stats(); +note('textDocument/didClose', { textDocument: { uri } }); +note('textDocument/didOpen', { textDocument: { uri, version: 1, text } }); +const reopened = hover(), reopenStats = stats(); note('exit', {}); +const frame = m => { const b = Buffer.from(JSON.stringify({ jsonrpc: '2.0', ...m })); return Buffer.concat([Buffer.from(`Content-Length: ${b.length}\r\n\r\n`), b]); }; +let out; +if (process.argv[2] === '--wasm' || process.argv[2] === '--vm') { + const wasm = process.argv[2] === '--wasm', host = require(path.resolve('editors/vscode/client', wasm ? 'wasmproc.js' : 'vmproc.js')); + const p = (wasm ? host.wasmProcess : host.vmProcess)(path.resolve(process.argv[3]), { log: () => {} }); + const chunks = []; p.stdout.on('data', d => chunks.push(d)); msgs.forEach(m => p.stdin.write(frame(m))); out = Buffer.concat(chunks); +} else { + const r = cp.spawnSync(process.argv[2], process.argv.slice(3), { input: Buffer.concat(msgs.map(frame)), timeout: 60000, maxBuffer: 16 << 20 }); + assert.strictEqual(r.status, 0, String(r.stderr)); out = r.stdout; +} +const replies = new Map(); +while (out.length) { const e = out.indexOf('\r\n\r\n'), n = Number(/Content-Length:\s*(\d+)/i.exec(out.subarray(0, e).toString())[1]); const m = JSON.parse(out.subarray(e + 4, e + 4 + n)); replies.set(m.id, m.result); out = out.subarray(e + 4 + n); } +assert.deepStrictEqual(replies.get(a), replies.get(b)); +assert.ok(replies.get(a)?.contents.value.includes('7'), 'first hover has value'); +assert.strictEqual(replies.get(after).hits, replies.get(before).hits + 1); +assert.strictEqual(replies.get(after).checks, replies.get(before).checks); +pairs.forEach(([x, y]) => assert.deepStrictEqual(replies.get(x), replies.get(y))); +assert.strictEqual(replies.get(all).hits, replies.get(after).hits + methods.length); +assert.ok(replies.get(changed)?.contents.value.includes('9'), 'edit invalidates value'); +assert.strictEqual(replies.get(editStats).misses, replies.get(all).misses + 1); +assert.strictEqual(replies.get(includeStats).misses, replies.get(editStats).misses + 1); +assert.ok(replies.get(reopened)?.contents.value.includes('7'), 'reopen invalidates value'); +assert.strictEqual(replies.get(reopenStats).misses, replies.get(includeStats).misses + 1); +console.log('ok analysis cache reuses all request types and invalidates on edits, includes and reopen'); diff --git a/tests/drivers/analyzer_property_test.py b/tests/drivers/analyzer_property_test.py new file mode 100644 index 0000000..d816f47 --- /dev/null +++ b/tests/drivers/analyzer_property_test.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +"""deterministic safe/invalid neighbors for ownership and projection analysis.""" +import argparse +import random +import subprocess + +parser = argparse.ArgumentParser() +parser.add_argument('host') +parser.add_argument('--cases', type=int, default=32) +args = parser.parse_args() +rng = random.Random(0xB0770) +for i in range(args.cases): + n = rng.randrange(1, 12); at = rng.randrange(n) + prefix = f'include rt; int* saved; fn main() {{ let p = rt.new(int, {n}); p[{at}] = {i}; ' + variants = [ + (f'return p[{at}]; }}', True), + (f'saved = p; return saved[{at}]; }}', True), + (f'saved = p; return p[{at}]; }}', False), + (f'rt.free(p); return p[{at}]; }}', False), + (f'int* q = p; rt.free(p); return q[{at}]; }}', False), + ] + for tail, accepted in variants: + src = prefix + tail + r = subprocess.run([args.host], input=src.encode(), capture_output=True, timeout=5) + text = (r.stdout + r.stderr).decode(errors='replace') + error = 'zc:' in text or r.returncode != 0 + assert r.returncode >= 0 and (not error) == accepted, (i, accepted, src, text) +print(f'ok {args.cases * 5} generated ownership accept/reject neighbors (seed 0xB0770)') diff --git a/tests/drivers/array_methods_test.py b/tests/drivers/array_methods_test.py new file mode 100644 index 0000000..82fef67 --- /dev/null +++ b/tests/drivers/array_methods_test.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""array receiver calls: execution, type checks and mutation contracts.""" +import argparse +import re +import subprocess + +parser = argparse.ArgumentParser() +parser.add_argument('host', nargs='?', default='bin/zy') +parser.add_argument('--case', default='') +args = parser.parse_args() + +cases = [ + ('growable', True, 'include rt; fn main() { int[?] a = {2,3}; a.push(7); rt.put_n(a.len()); rt.put_c(32); rt.put_n(a.cap() >= a.len()); rt.put_c(32); rt.put_n(a.pop()); a.resize(4); rt.put_c(32); rt.put_n(a[3]); rt.put_c(32); rt.put_n(a.len()); }', '3 1 7 0 4'), + ('fixed length', True, 'include rt; fn main() { int[3] a = {2,3,4}; rt.put_n(a.len()); rt.put_c(32); rt.put_n(a.cap()); fixed int[?] b = rt.new(fixed int[?], 5); rt.put_c(32); rt.put_n(b.len()); }', '3 3 5'), + ('packed', True, 'include rt; fn main() { u8[?] a = {1}; a.push(300); rt.put_n(a.pop()); rt.put_c(32); i16[?] b = {-5}; rt.put_n(b.pop()); rt.put_c(32); f32[?] c = {2.5}; c.push(1.5); rt.put_n(c.pop() == 1.5); }', '44 -5 1'), + ('optimized', True, 'include rt; o3 int change(int[?] a) { a.push(9); int n = a.pop(); a.resize(3); return n + a.len(); } fn main() { int[?] a = {1}; rt.put_n(change(a)); rt.put_c(32); rt.put_n(a.len()); }', '12 3'), + ('field receiver', True, 'include rt; struct box { int[?] items; }; fn main() { box b; b.items = rt.new(int[?]); b.items.push(8); rt.put_n(b.items.pop()); }', '8'), + ('nested arguments', True, 'include rt; fn main() { int[?] a = {1}, b = {2,3}; a.push(b.pop() + b.len()); a.resize(b.len() + 2); rt.put_n(a[1]); rt.put_c(32); rt.put_n(a.len()); }', '4 3'), + ('fixed push', False, 'fn main() { int[2] a; a.push(1); }', None), + ('fixed pop', False, 'fn main() { int[2] a; return a.pop(); }', None), + ('fixed resize', False, 'fn main() { int[2] a; a.resize(3); }', None), + ('fixed handle push', False, 'include rt; fn main() { fixed int[?] a = rt.new(fixed int[?], 2); a.push(1); }', None), + ('element mismatch', False, 'include rt; fn main() { int[?] a = {}; a.push("wrong"); }', None), + ('extra argument', False, 'include rt; fn main() { int[?] a = {}; a.len(1); }', None), + ('invalidated borrow', False, 'include rt; fn main() { int[?] a = {1}; int* p = &a[0]; a.resize(10); return *p; }', None), + ('free fixed push', False, 'fn main() { int[2] a; push(a,1); }', None), + ('free fixed pop', False, 'fn main() { int[2] a; return pop(a); }', None), + ('registered generic', True, 'include rt, run; T first<T>(fixed T[?] a) { return a[0]; } immediate fn register() { run.next(); run.add_method("first", "first"); } register fn main() { int[?] a = {9}; rt.put_n(a.first()); int[2] b = {7,8}; rt.put_c(32); rt.put_n(b.first()); }', '9 7'), + ('registered callback', True, 'include rt, run; U apply<T,U>(fixed T[?] a, U(T) f) { return f(a[0]); } immediate fn register() { run.next(); run.add_method("apply", "apply"); } register fn main() { int[?] a = {4}; rt.put_n(a.apply(fn (e) { return e + 2; })); rt.put_c(32); float result = a.apply(fn:float(int e) { return e * 0.5; }); rt.put_n(result == 2.0); }', '6 1'), + ('registered callback mismatch', False, 'include rt, run; U apply<T,U>(fixed T[?] a, U(T) f) { return f(a[0]); } immediate fn register() { run.next(); run.add_method("apply", "apply"); } register fn main() { int[?] a = {4}; return a.apply(fn:int(char* e) { return e[0]; }); }', None), + ('registration requires receiver', False, 'include run; int empty() { return 0; } immediate fn register() { run.next(); run.add_method("empty", "empty"); } register fn main() { return 0; }', None), + ('declarative registration', True, 'include rt, run; T first<T>(fixed T[?] a) { return a[0]; } run.method("first", first); fn main() { int[3] a = {6,7,8}; rt.put_n(a.first()); }', '6'), + ('map inferred', True, 'include rt, std; fn main() { int[?] a = {1,2,3}; int[?] b = a.map(fn(e) { return e + 2; }); rt.put_n(b[0]); rt.put_c(32); rt.put_n(b[2]); rt.put_c(32); float[?] c = a.map(fn(e) { return e * 0.5; }); rt.put_n(c[2] == 1.5); }', '3 5 1'), + ('filter fixed', True, 'include rt, std; fn main() { int[5] a = {1,2,3,4,5}; let b = a.filter(fn(e) { return e % 2; }); rt.put_n(b.len()); rt.put_c(32); rt.put_n(b[2]); }', '3 5'), + ('search methods', True, 'include rt, std; fn main() { int[?] a = {2,4,6}; rt.put_n(a.find(4)); rt.put_c(32); rt.put_n(a.find(5)); rt.put_c(32); rt.put_n(a.contains(6)); rt.put_c(32); rt.put_n(a.find_if(fn(e) { return e > 4; })); }', '1 -1 1 2'), + ('reduce and predicates', True, 'include rt, std; fn main() { int[3] a = {2,4,6}; rt.put_n(a.reduce(1, fn(acc,e) { return acc + e; })); rt.put_c(32); rt.put_n(a.all(fn(e) { return e % 2 == 0; })); rt.put_c(32); rt.put_n(a.any(fn(e) { return e > 5; })); }', '13 1 1'), + ('empty algorithms', True, 'include rt, std; fn main() { int[?] a = {}; int calls = 0; let b = a.map(fn(e) { calls++; return e; }); let c = a.filter(fn(e) { calls++; return e; }); rt.put_n(b.len() + c.len() + calls); rt.put_c(32); rt.put_n(a.all(fn(e) { return e; })); rt.put_c(32); rt.put_n(a.any(fn(e) { return e; })); rt.put_c(32); rt.put_n(a.reduce(9, fn(acc,e) { return acc + e; })); }', '0 1 0 9'), + ('shared filter references', True, 'include rt, std; fn main() { char*[?] a = rt.new(char*[?]); char* s = rt.new(char, 2); s[0] = 65; a.push(s); let b = a.filter(fn(e) { return e[0] == 65; }); rt.free(a); rt.gc(); rt.put_c(b[0][0]); }', 'A'), + ('map needs import', False, 'include rt; fn main() { int[?] a = {1}; let b = a.map(fn(e) { return e; }); }', None), + ('pop transfers ownership', True, 'include rt; fn main() { int before = rt.live_objects(); { int*[?] a = rt.new(int*[?]); int* p = rt.new(int); *p = 7; a.push(p); let saved = a.pop(); rt.free(a); rt.put_n(rt.isobj(saved)); rt.put_c(32); rt.put_n(*saved); } rt.put_c(32); rt.put_n(rt.live_objects() - before); }', '1 7 0'), + ('returned pop survives array', True, 'include rt; int*.u take() { int*[?] a = rt.new(int*[?]); int* p = rt.new(int); *p = 8; a.push(p); return a.pop(); } fn main() { let p = take(); rt.put_n(rt.isobj(p)); rt.put_c(32); rt.put_n(*p); }', '1 8'), + ('single field struct push', False, 'include rt; struct item { int value; }; fn main() { item[?] a = rt.new(item[?]); item x; x.value = 7; a.push(x); }', None), + ('single field struct pop', False, 'include rt; struct item { int value; }; fn main() { item[?] a = rt.new(item[?], 1); return a.pop().value; }', None), + ('resize drops children', True, 'include rt; fn main() { int before = rt.live_objects(); int*[?] a = rt.new(int*[?]); a.push(rt.new(int)); a.resize(0); rt.put_n(rt.live_objects() - before); }', '2'), + ('callback borrows references', False, 'include rt, std; fn main() { int*[?] a = rt.new(int*[?]); a.push(rt.new(int)); let b = a.filter(fn(e) { rt.free(e); return 1; }); }', None), + ('packed map and filter', True, 'include rt, std; fn main() { i16[?] a = {-5,7}; i16[?] b = a.filter(fn(e) { return e < 0; }); rt.put_n(b[0]); rt.put_c(32); f32[2] c = {1.5,2.5}; f32[?] d = c.map(fn(e) { return e; }); rt.put_n(d[1] == 2.5); }', '-5 1'), + ('numeric coercion', True, 'include rt; fn main() { float[?] a = rt.new(float[?]); a.push(2); rt.put_n(a[0] == 2.0); rt.put_c(32); int[?] b = {}; b.push(2.5); b.resize(3.0); rt.put_n(b[0]); rt.put_c(32); rt.put_n(b.len()); }', '1 2 3'), + ('invalid resize length', False, 'include rt; fn main() { int[?] a = {}; a.resize("bad"); }', None), + ('free pop ownership', True, 'include rt; fn main() { int*[?] a = rt.new(int*[?]); int* p = rt.new(int); *p = 9; push(a,p); let saved = pop(a); rt.free(a); rt.put_n(rt.isobj(saved)); rt.put_c(32); rt.put_n(*saved); }', '1 9'), + ('packed string values', True, 'include rt; struct box { bstr text; }; fn main() { bstr[?] a = {b"hi"}; let s = a.pop(); rt.put_n(s[1]); rt.put_c(32); box b; b.text = b"ok"; rt.put_n(b.text[1]); }', '105 107'), + ('field array borrow', False, 'include rt; struct box { int[?] items; }; fn main() { box* b = rt.new(box); b.items = rt.new(int[?], 1); let borrowed = b.items; rt.free(b); return borrowed[0]; }', None), +] + +count = 0 +for name, accept, source, expected in cases: + if args.case and args.case not in name: + continue + source = 'typecheck on;\n' + source + check = subprocess.run([args.host], input=source, text=True, capture_output=True, timeout=10) + diagnostics = check.stdout + check.stderr + failed = check.returncode != 0 or bool(re.search(r'(zc:|zc0:|error:)', diagnostics)) + assert failed != accept, f'{name}: expected {"accept" if accept else "reject"}\n{diagnostics}' + if accept: + assert check.returncode == 0 and check.stdout == expected, f'{name}: {check.stdout!r}, {check.stderr!r}; expected {expected!r}' + print('ok array methods: ' + name) + count += 1 +assert count, 'no cases selected' diff --git a/tests/drivers/cli_help_test.py b/tests/drivers/cli_help_test.py new file mode 100644 index 0000000..1b32fa4 --- /dev/null +++ b/tests/drivers/cli_help_test.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""bounded command-line help and option rejection contracts.""" +import argparse +import os +import subprocess +from pathlib import Path + +parser = argparse.ArgumentParser() +parser.add_argument('--group', choices=['binaries', 'scripts', 'js', 'all'], default='all') +parser.add_argument('--bin-dir', default='bin') +args = parser.parse_args() +commands = [] +if args.group in ('binaries', 'all'): + commands += [[str(Path(args.bin_dir) / name)] for name in ['zy', 'zym', 'zclsp', 'zc0', 'mkgenome', 'genmap']] +if args.group in ('scripts', 'all'): + commands += [['bash', name] for name in ['tinyvm.sh', 'src/tools/zcc', 'src/tools/automap.sh', 'src/tools/embed.sh', 'src/tools/mklsp.sh', 'src/tools/mklspwasm.sh', 'src/tools/mkvsix.sh', 'src/web/build.sh', 'src/baremetal/run.sh']] +if args.group in ('js', 'all'): + commands += [['node', name] for name in ['src/js/tinyvm.js', 'src/js/extract_genome.js', 'src/js/extract_lsp.js']] + +def invoke(command, option): + # keep stdin open: help and rejected options must finish without reading it. + env = dict(os.environ, TINYVM_MEMORY_WORDS='invalid', TINYVM_MAX_MEMORY_WORDS='invalid') + child = subprocess.Popen([*command, option], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) + try: + child.wait(timeout=3) + return child.returncode, child.stdout.read(), child.stderr.read() + finally: + if child.poll() is None: + child.kill() + child.wait() + child.stdin.close() + child.stdout.close() + child.stderr.close() + +failures = 0 +for command in commands: + try: + for option in ['--help', '-h']: + status, out, err = invoke(command, option) + assert status == 0 and b'usage:' in out.lower() and not err, (option, status, out[:200], err[:200]) + status, out, err = invoke(command, '--unknown-option') + assert status > 0 and err and not out, (status, out[:200], err[:200]) + print('ok help and option rejection: ' + ' '.join(command)) + except (AssertionError, subprocess.TimeoutExpired) as error: + failures += 1 + print('FAIL ' + ' '.join(command) + ': ' + str(error)) +if args.group in ('binaries', 'all'): + source = b'int identity(int n) { return n; }\n' + for option in ['--format', '--annotate', '--types']: + result = subprocess.run([str(Path(args.bin_dir) / 'zclsp'), option], input=source, capture_output=True, timeout=10) + assert result.returncode == 0 and not result.stderr, (option, result.stdout, result.stderr) + if option != '--types': + assert b'identity' in result.stdout, (option, result.stdout) + print('ok lsp source options remain available alongside help') +raise SystemExit(bool(failures)) diff --git a/tests/drivers/codeactions_client_test.js b/tests/drivers/codeactions_client_test.js new file mode 100644 index 0000000..f1936e7 --- /dev/null +++ b/tests/drivers/codeactions_client_test.js @@ -0,0 +1,24 @@ +const assert = require('assert'); +const { convertCodeActions } = require('../../editors/vscode/client/codeactions'); +class Range { constructor(...coordinates) { this.coordinates = coordinates; } } +class WorkspaceEdit { + constructor() { this.edits = []; } + replace(uri, range, text) { this.edits.push({ uri, range, text }); } +} +class CodeAction { constructor(title, kind) { this.title = title; this.kind = kind; } } +const vscode = { Range, WorkspaceEdit, CodeAction, CodeActionKind: { QuickFix: 'quickfix' }, Uri: { parse: s => s } }; +const uri = 'file:///test.zc', doc = { uri: { toString: () => uri }, version: 3 }; +const edit = { range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, newText: 'include rt;\n' }; +const source = { title: 'include rt;', kind: 'quickfix', isPreferred: true, edit: { documentChanges: [{ textDocument: { uri, version: 3 }, edits: [edit] }] } }; +const actions = convertCodeActions(vscode, [source], doc); +assert.strictEqual(actions.length, 1); +assert.strictEqual(actions[0].kind, 'quickfix'); +assert.strictEqual(actions[0].isPreferred, true); +assert.deepStrictEqual(actions[0].edit.edits[0], { uri, range: new Range(0, 0, 0, 0), text: 'include rt;\n' }); +assert.deepStrictEqual(convertCodeActions(vscode, [source], { ...doc, version: 4 }), [], 'stale document versions cannot become editor edits'); +assert.strictEqual(convertCodeActions(vscode, [source], { ...doc, version: 1 }, 3).length, 1, 'protocol version epochs are distinct from editor versions'); +assert.strictEqual(convertCodeActions(vscode, [{ ...source, edit: { changes: { [uri]: [edit] } } }], doc).length, 1); +assert.deepStrictEqual(convertCodeActions(vscode, null, doc), []); +assert.deepStrictEqual(convertCodeActions(vscode, [{ title: 'no edit' }, { ...source, edit: { changes: {} } }], doc), []); +assert.deepStrictEqual(source.edit.documentChanges[0].edits, [edit], 'conversion keeps protocol objects intact'); +console.log('ok quick-fix client conversion, stale-version rejection and empty responses'); diff --git a/tests/drivers/compiler_contract_test.py b/tests/drivers/compiler_contract_test.py new file mode 100644 index 0000000..63e476c --- /dev/null +++ b/tests/drivers/compiler_contract_test.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +"""accept/reject contracts, selectable in small groups before implementation.""" +import argparse +import json +import re +import subprocess +from pathlib import Path + +parser = argparse.ArgumentParser() +parser.add_argument('host') +parser.add_argument('--group') +parser.add_argument('--case') +args = parser.parse_args() +cases = json.loads(Path('tests/spec/compiler_contracts.json').read_text()) +cases = [c for c in cases if (not args.group or c['group'] == args.group) and (not args.case or args.case in c['name'])] +assert cases, 'no selected contracts' +failures = [] +for c in cases: + source = ('typecheck on;\n' + c['source']).encode() + r = subprocess.run([args.host], input=source, capture_output=True, timeout=10) + text = (r.stdout + r.stderr).decode(errors='replace') + error = bool(re.search(r'(zc:|zc0:|error:|undefined|not defined)', text)) or r.returncode != 0 + valid = (not error) == c['accept'] and r.returncode >= 0 + if valid and 'output' in c: + valid = r.returncode == 0 and r.stdout.decode() == c['output'] + print(('ok ' if valid else 'FAIL ') + c['group'] + ': ' + c['name']) + if not valid: + failures.append(c['name']) + print(text.strip()[:1000]) +raise SystemExit(1 if failures else 0) diff --git a/tests/drivers/compiler_growth_test.py b/tests/drivers/compiler_growth_test.py index 982611c..e56bf43 100644 --- a/tests/drivers/compiler_growth_test.py +++ b/tests/drivers/compiler_growth_test.py @@ -18,65 +18,68 @@ def run(source, expected): assert result.stdout == expected, result.stdout[:1000] -locals_source = 'fn main() {\n' +locals_source = 'include rt; fn main() {\n' locals_source += ''.join(f'int local_{i} = {i};\n' for i in range(900)) -locals_source += 'put_n(local_899); put_c(10); return 0; }\n' +locals_source += 'rt.put_n(local_899); rt.put_c(10); return 0; }\n' run(locals_source, '899\n') -types_source = ''.join(f'type array_{i} = [{i + 1}]int;\n' for i in range(1500)) -types_source += 'fn main() { put_n(ntl > 1500); put_c(10); gc(); put_n(tsz(tarr(tint, 1500))); put_c(10); return 0; }\n' +types_source = ''.join(f'type array_{i} = int[{i + 1}];\n' for i in range(1500)) +types_source = 'include rt, run;\n' + types_source +types_source += 'fn main() { rt.put_n(run.ntl > 1500); rt.put_c(10); rt.gc(); rt.put_n(run.tsz(run.tarr(run.tint, 1500))); rt.put_c(10); return 0; }\n' run(types_source, '1\n1500\n') -names_source = ''.join(f'const int repeated_long_identifier_for_pool_and_hash_growth_{i} = {i};\n' for i in range(options.names)) +names_source = 'include rt, run;\n' + ''.join(f'const int repeated_long_identifier_for_pool_and_hash_growth_{i} = {i};\n' for i in range(options.names)) names_source += f'''fn main() {{ - word saved = intern("a name that must survive growth"); - reserve_names(hashcap * 4); reserve_symbols(symcap * 2); reserve_locals(loccap * 2); - reserve_fixes(fixcap * 2); reserve_R_SRC(4096); reserve_R_CTX(4096); - reserve_R_LOOP(4096); reserve_R_SNAP(32768); reserve_R_QBUF(32768); reserve_R_EXIT(8192); - gc(); - put_n(saved == intern("a name that must survive growth")); put_c(10); - put_n(repeated_long_identifier_for_pool_and_hash_growth_{options.names - 1}); put_c(10); - put_n(poolblocks != 0 && symcap > NS && hashcap > IHN); put_c(10); + word saved = run.intern("a name that must survive growth"); + run.reserve_names(run.hashcap * 4); run.reserve_symbols(run.symcap * 2); run.reserve_locals(run.loccap * 2); + run.reserve_fixes(run.fixcap * 2); run.reserve_R_SRC(4096); run.reserve_R_CTX(4096); + run.reserve_R_LOOP(4096); run.reserve_R_SNAP(32768); run.reserve_R_QBUF(32768); run.reserve_R_EXIT(8192); + rt.gc(); + rt.put_n(saved == run.intern("a name that must survive growth")); rt.put_c(10); + rt.put_n(repeated_long_identifier_for_pool_and_hash_growth_{options.names - 1}); rt.put_c(10); + rt.put_n(run.poolblocks != 0 && run.symcap > run.NS && run.hashcap > run.IHN); rt.put_c(10); return 0; }} ''' run(names_source, f'1\n{options.names - 1}\n1\n') long_name = 'identifier_' + 'x' * 1500 -run(f'const int {long_name} = 7; fn main() {{ put_n({long_name}); put_c(10); return 0; }}\n', '7\n') +run(f'include rt; const int {long_name} = 7; fn main() {{ rt.put_n({long_name}); rt.put_c(10); return 0; }}\n', '7\n') -mark_source = '''let roots[60000]; +mark_source = '''include rt; let roots[60000]; unchecked fn main() { - int i = 0; - while (i < 60000) { roots[i] = new(1); i++; } - gc(); put_n(markcap > MARKN); put_c(32); put_n(live_objects() == 60000); put_c(32); - fill_w(roots, 0, 60000); scrub(); gc(); - // conservative roots can retain incidental addresses in code or untyped words. - put_n(live_objects() < 1000); put_c(10); return 0; + int i = 0, before = rt.live_objects(); + while (i < 60000) { roots[i] = rt.new(1); i++; } + rt.gc(); rt.put_n(rt.markcap > rt.MARKN); rt.put_c(32); rt.put_n(rt.live_objects() - before == 60000); rt.put_c(32); + // explicitly release survivors: conservative instruction roots may resemble heap addresses. + i = 0; while (i < 60000) { rt.free(roots[i]); i++; } + rt.fill_w(roots, 0, 60000); rt.scrub(); rt.gc(); + rt.put_n(rt.live_objects() == before); rt.put_c(10); return 0; } ''' run(mark_source, '1 1 1\n') generics_source = ''.join(f'int generic_{i}<T>() {{ return sizeof(T); }}\n' for i in range(90)) generics_source += 'int size_of<T>() { return sizeof(T); }\n' +generics_source = 'include rt;\n' + generics_source generics_source += 'fn main() { int total = 0;\n' generics_source += ''.join(f'total += generic_{i}<int>();\n' for i in range(90)) -generics_source += ''.join(f'total += size_of<[{i + 1}]int>();\n' for i in range(150)) -generics_source += 'gc(); put_n(total); put_c(10); return 0; }\n' +generics_source += ''.join(f'total += size_of<int[{i + 1}]>();\n' for i in range(150)) +generics_source += 'rt.gc(); rt.put_n(total); rt.put_c(10); return 0; }\n' run(generics_source, '11415\n') -captures_source = 'fn main() {\n' + ''.join(f'int captured_{i} = {i};\n' for i in range(70)) +captures_source = 'include rt; fn main() {\n' + ''.join(f'int captured_{i} = {i};\n' for i in range(70)) captures_source += 'let f = fn() { return ' + ' + '.join(f'captured_{i}' for i in range(70)) + '; };\n' -captures_source += 'gc(); put_n(f()); put_c(10); return 0; }\n' +captures_source += 'rt.gc(); rt.put_n(f()); rt.put_c(10); return 0; }\n' run(captures_source, '2415\n') -nested_source = 'fn main() { int captured = 7;\n' +nested_source = 'include rt; fn main() { int captured = 7;\n' nested_source += ''.join(f'let nested_{i} = fn() {{\n' for i in range(12)) nested_source += 'return captured;\n' nested_source += ''.join(f'}}; return nested_{i}();\n' for i in reversed(range(1, 12))) -nested_source += '}; gc(); put_n(nested_0()); put_c(10); return 0; }\n' +nested_source += '}; rt.gc(); rt.put_n(nested_0()); rt.put_c(10); return 0; }\n' run(nested_source, '7\n') -assembly_source = 'fn main() { int value = asm { ' + 'future _ ' * 300 -assembly_source += ''.join(f':label_{i} ' for i in range(200)) + ':future 7 }; gc(); put_n(value); put_c(10); return 0; }\n' +assembly_source = 'include rt; fn main() { int value = asm { ' + 'future _ ' * 300 +assembly_source += ''.join(f':label_{i} ' for i in range(200)) + ':future 7 }; rt.gc(); rt.put_n(value); rt.put_c(10); return 0; }\n' run(assembly_source, '7\n') print('ok compiler tables, long identifiers, and mark work grow without losing references') diff --git a/tests/drivers/completion_test.js b/tests/drivers/completion_test.js new file mode 100644 index 0000000..22c5fa5 --- /dev/null +++ b/tests/drivers/completion_test.js @@ -0,0 +1,105 @@ +// compiler-backed completion, including incomplete source and position-aware lifetimes. +// usage: node tests/drivers/completion_test.js path/to/zclsp [server arguments] +const assert = require('assert'); +const cp = require('child_process'); +const path = require('path'); +const uri = 'file://' + path.resolve('tests/drivers/completion-fixture.zc'); +const frame = m => { const b = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from(`Content-Length: ${b.length}\r\n\r\n`), b]); }; +function complete(source, extra = []) { + const at = source.indexOf('|'); assert.ok(at >= 0); + const before = source.slice(0, at), text = source.replace('|', ''); + const position = { line: before.split('\n').length - 1, character: before.length - before.lastIndexOf('\n') - 1 }; + const msgs = [ + { id: 1, method: 'initialize', params: {} }, + { method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, text } } }, + { id: 2, method: 'textDocument/completion', params: { textDocument: { uri }, position } }, + ...extra.map(m => ({ ...m, params: { textDocument: { uri }, position, ...m.params } })), + { method: 'exit' } + ].map(m => ({ jsonrpc: '2.0', ...m })); + let output; + if (process.argv[2] === '--wasm' || process.argv[2] === '--vm') { + const wasm = process.argv[2] === '--wasm'; + const host = require(path.resolve('editors/vscode/client', wasm ? 'wasmproc.js' : 'vmproc.js')); + const proc = (wasm ? host.wasmProcess : host.vmProcess)(path.resolve(process.argv[3]), { log: () => {} }); + const chunks = []; proc.stdout.on('data', b => chunks.push(b)); + for (const m of msgs) proc.stdin.write(frame(m)); + output = Buffer.concat(chunks); + } else { + const r = cp.spawnSync(process.argv[2], process.argv.slice(3), { input: Buffer.concat(msgs.map(frame)), timeout: 30000, maxBuffer: 32 << 20 }); + assert.strictEqual(r.status, 0, String(r.stderr)); output = r.stdout; + } + let b = output; const replies = []; + while (b.length) { + const e = b.indexOf('\r\n\r\n'); assert.ok(e >= 0, b.toString()); + const n = Number(/Content-Length:\s*(\d+)/i.exec(b.subarray(0, e).toString())[1]); + replies.push(JSON.parse(b.subarray(e + 4, e + 4 + n))); b = b.subarray(e + 4 + n); + } + assert.ok(replies.find(m => m.id === 1).result.capabilities.completionProvider); + const reply = replies.find(m => m.id === 2); assert.ok(reply && reply.result, JSON.stringify(reply)); + return { items: reply.result.items, replies }; +} +const get = (items, label) => { const item = items.find(c => c.label === label); assert.ok(item, `${label} missing: ${items.map(c => c.label)}`); return item; }; +let r = complete('/* computes a total */\nint total(int count) { return count + 1; }\nfn main() {\n int tally = 3;\n ta|\n}\n'); +assert.match(get(r.items, 'tally').detail, /int tally/); +assert.ok(!r.items.some(c => c.label === 'count'), 'other function parameters are out of scope'); +r = complete('/* computes a total */\nint total(int count) { return count + 1; }\nfn main() { to| }'); +assert.match(get(r.items, 'total').detail, /int total\(int count\)/); +assert.match(get(r.items, 'total').documentation.value, /computes a total/); +r = complete('fn main() {\n int count = 1;\n { int hidden = 3; }\n |\n}\n'); +get(r.items, 'count'); assert.ok(!r.items.some(c => c.label === 'hidden')); +r = complete('include rt; fn main(int input) {\n let p = rt.new(int, 3);\n rt.free(p);\n |\n}\n'); +const p = get(r.items, 'p'); assert.deepStrictEqual(p.tags, [1]); +assert.match(p.documentation.value, /lifetime ended/); +get(r.items, 'input'); +assert.ok(r.items.findIndex(c => c.label === 'p') > r.items.findIndex(c => c.label === 'input')); +assert.ok(r.items.slice(r.items.findIndex(c => c.tags)).every(c => c.tags), 'all consumed variables sort last'); +r = complete('include rt; fn main() {\n let p = rt.new(int, 3);\n p|;\n rt.free(p);\n return 0;\n}'); +assert.ok(!get(r.items, 'p').tags, 'later consumption does not expire earlier completion'); +r = complete('fn main() {\n int old = 1;\n int bold = 2;\n int other_local_data = 3;\n old|\n}'); +assert.strictEqual(r.items[0].label, 'old', 'exact match comes first'); get(r.items, 'other_local_data'); +r = complete('include std;\nfn main() { std.Text.at| }'); +assert.match(get(r.items, 'atoi').detail, /atoi\(/); +assert.ok(r.items.every(c => !c.label.includes('.')), 'member labels omit receiver'); +r = complete('struct point { int x; int y; };\nfn main() { point p; p.| }'); +get(r.items, 'x'); get(r.items, 'y'); assert.ok(!r.items.some(c => c.label === 'main')); +r = complete('fn main() { Te| }'); assert.ok(!r.items.some(c => c.label === 'Text'), 'std is not imported automatically'); +r = complete('int shared;\nfn main() { char shared = 1; sh| }'); +assert.match(get(r.items, 'shared').detail, /char shared/); +assert.strictEqual(r.items.filter(c => c.label === 'shared').length, 1); +r = complete('fn main() { let hello = "hi"; hel| }'); +assert.match(get(r.items, 'hello').detail, /char\* hello/); +r = complete('fn main() { let bytes = b"hi"; byt| }'); +assert.match(get(r.items, 'bytes').detail, /bstr bytes/); +r = complete('fn main() { int count = 1; cou|nt; return count; }', [{ id: 3, method: 'textDocument/hover' }]); +assert.ok(r.replies.find(m => m.id === 3).result, 'hover still works after a partial completion check'); +assert.strictEqual(get(r.items, 'count').textEdit.newText, 'count'); +assert.strictEqual(get(r.items, 'count').textEdit.range.end.character - get(r.items, 'count').textEdit.range.start.character, 5); +r = complete('include rt; fn main() {\n let p = rt.new(int, 3);\n let alias = p;\n rt.free(p);\n |\n}'); +assert.deepStrictEqual(get(r.items, 'alias').tags, [1], 'freed aliases are also unavailable'); +r = complete('include rt; fn take(.d p) { rt.free(p); }\nfn main() {\n let p = rt.new(int, 3);\n take(d*p);\n |\n}'); +assert.deepStrictEqual(get(r.items, 'p').tags, [1], 'ownership transfers are unavailable'); +r = complete('fn main() {\n int outer = 1;\n let f = fn(int inner) {\n ou|\n };\n}'); +get(r.items, 'outer'); +r = complete('fn first() { int wrong = 1; return wrong; }\n|\nfn second() { int other = 2; return other; }'); +assert.ok(!r.items.some(c => c.label === 'wrong' || c.label === 'other'), 'top-level completion excludes function locals'); +r = complete('fn main() {\n /* the latest count */\n int latest = 1;\n la|'); +assert.match(get(r.items, 'latest').documentation.value, /the latest count/); +r = complete('include rt; fn main() { int[?] items = {}; items.| }'); +for (const name of ['len', 'cap', 'push', 'pop', 'resize']) get(r.items, name); +assert.match(get(r.items, 'push').detail, /push\(int value\)/); +assert.match(get(r.items, 'pop').detail, /int pop\(\)/); +assert.match(get(r.items, 'resize').documentation.value, /length/); +assert.ok(!r.items.some(c => c.label === 'main'), 'array member completion excludes globals'); +r = complete('fn main() { int[3] items = {1,2,3}; items.| }'); +get(r.items, 'len'); get(r.items, 'cap'); +assert.ok(!r.items.some(c => ['push', 'pop', 'resize'].includes(c.label)), 'fixed arrays omit mutation methods'); +r = complete('include rt; fn main() { fixed int[?] items = rt.new(fixed int[?], 3); items.| }'); +get(r.items, 'len'); get(r.items, 'cap'); +assert.ok(!r.items.some(c => ['push', 'pop', 'resize'].includes(c.label)), 'fixed handles omit mutation methods'); +r = complete('include std; fn main() { int[3] items = {1,2,3}; items.| }'); +for (const name of ['find', 'contains', 'find_if', 'any', 'all', 'count', 'map', 'filter', 'reduce']) get(r.items, name); +assert.match(get(r.items, 'map').documentation.value, /transform.*each element/); +assert.ok(!r.items.some(c => ['push', 'pop', 'resize'].includes(c.label)), 'library algorithms do not make a fixed array growable'); +r = complete('include rt; fn main() { int[?] items = {}; items.| }'); +assert.ok(!r.items.some(c => ['map', 'filter'].includes(c.label)), 'library methods require an explicit import'); +console.log('ok completion: signatures, docs, fuzzy ranking, scope, array members, shadowing and consumed lifetimes'); diff --git a/tests/drivers/core_lsp_test.js b/tests/drivers/core_lsp_test.js index 41d0095..983f9aa 100644 --- a/tests/drivers/core_lsp_test.js +++ b/tests/drivers/core_lsp_test.js @@ -4,7 +4,7 @@ const fs = require('fs'); const cp = require('child_process'); const path = require('path'); const target = process.argv[2]; -const files = ['src/runtime/rt.zc', 'src/compiler/compiler.zc', 'src/runtime/run.zc', 'src/lib/std.zc', 'src/lsp/server.zc', 'src/tools/build.zc']; +const files = ['src/core/core.zc', 'src/runtime/rt.zc', 'src/compiler/compiler.zc', 'src/runtime/run.zc', 'src/lib/std.zc', 'src/lib/icon.zc', 'src/lsp/server.zc', 'src/tools/build.zc']; const frame = message => { const body = Buffer.from(JSON.stringify(message)); return Buffer.concat([Buffer.from('Content-Length: ' + body.length + '\r\n\r\n'), body]); @@ -18,6 +18,7 @@ for (const file of files) { { jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, languageId: 'zc', text } } }, { jsonrpc: '2.0', id: 2, method: 'textDocument/semanticTokens/full', params: { textDocument: { uri } } }, { jsonrpc: '2.0', method: 'textDocument/didChange', params: { textDocument: { uri, version: 2 }, contentChanges: [{ text: text + '\n' }] } }, + { jsonrpc: '2.0', id: 4, method: 'textDocument/semanticTokens/full', params: { textDocument: { uri } } }, { jsonrpc: '2.0', method: 'textDocument/didClose', params: { textDocument: { uri } } }, { jsonrpc: '2.0', id: 3, method: 'shutdown', params: null }, { jsonrpc: '2.0', method: 'exit' }, @@ -34,10 +35,11 @@ for (const file of files) { data = data.subarray(end + 4 + length); } const diagnostics = replies.filter(m => m.method === 'textDocument/publishDiagnostics'); - assert.strictEqual(diagnostics.length, 3, file + ': open, change, and close publish diagnostics'); + assert.ok(diagnostics.length >= 3, file + ': open and change are checked before close, with streamed publications allowed'); const errors = diagnostics.flatMap(m => m.params.diagnostics).filter(d => d.severity === 1); assert.deepStrictEqual(errors, [], file + ': ' + errors.map(d => (d.range.start.line + 1) + ': ' + d.message).join('\n')); assert.ok(replies.find(m => m.id === 2).result.data.length, file + ': semantic tokens are available'); - assert.deepStrictEqual(diagnostics[2].params.diagnostics, [], 'close clears diagnostics'); + assert.ok(replies.find(m => m.id === 4).result.data.length, file + ': the changed source also has semantic tokens'); + assert.deepStrictEqual(diagnostics.at(-1).params.diagnostics, [], 'close clears diagnostics'); console.log('ok editor open, highlight, change, and close: ' + file); } diff --git a/tests/drivers/ext_cache_test.js b/tests/drivers/ext_cache_test.js index 11bf50b..f389fcc 100644 --- a/tests/drivers/ext_cache_test.js +++ b/tests/drivers/ext_cache_test.js @@ -29,8 +29,15 @@ const stub = { appendMarkdown(v) { this.value += v; return this; } appendCodeblock(v, language) { this.value += '\n```' + language + '\n' + v + '\n```\n'; return this; } }, + CompletionItem: class { constructor(label, kind) { Object.assign(this, { label, kind }); } }, + CompletionList: class { constructor(items, isIncomplete) { Object.assign(this, { items, isIncomplete }); } }, + CompletionItemKind: { Variable: 5 }, + CompletionItemTag: { Deprecated: 1 }, + CodeActionKind: { QuickFix: 'quickfix' }, + CodeAction: class { constructor(title, kind) { Object.assign(this, { title, kind }); } }, + WorkspaceEdit: class { constructor() { this.edits = []; } replace(uri, range, newText) { this.edits.push({ uri, range, newText }); } }, Hover: class { constructor(c) { this.contents = c; } }, - Uri: { parse: s => ({ toString: () => s, fsPath: decodeURIComponent(s.replace(/^file:\/\//, '')) }) }, + Uri: { file: p => ({ toString: () => 'file://' + p, fsPath: p }), parse: s => ({ toString: () => s, fsPath: decodeURIComponent(s.replace(/^file:\/\//, '')) }) }, workspace: { workspaceFolders: undefined, textDocuments: world.textDocuments, @@ -41,6 +48,7 @@ const stub = { for (const k of ['onDidChange', 'onDidCreate', 'onDidDelete']) w[k] = f => { w.handlers[k] = f; return { dispose() {} }; }; return w; }, + registerTextDocumentContentProvider: () => ({ dispose() {} }), ...['onDidChangeConfiguration', 'onDidOpenTextDocument', 'onDidChangeTextDocument', 'onDidSaveTextDocument', 'onDidCloseTextDocument'] .reduce((o, k) => { o[k] = f => { world.listeners[k] = f; return { dispose() {} }; }; return o; }, {}), }, @@ -54,7 +62,12 @@ const stub = { delete() {}, dispose() {}, }), registerDocumentSemanticTokensProvider: () => ({ dispose() {} }), + registerCompletionItemProvider: (sel, p) => { world.completion = p; return { dispose() {} }; }, + registerCodeActionsProvider: (sel, p) => { world.actions = p; return { dispose() {} }; }, registerHoverProvider: (sel, p) => { world.hover = p; return { dispose() {} }; }, + registerDocumentFormattingEditProvider: () => ({ dispose() {} }), + registerDefinitionProvider: (sel, p) => { world.definition = p; return { dispose() {} }; }, + registerReferenceProvider: (sel, p) => { world.references = p; return { dispose() {} }; }, }, commands: { registerCommand: () => ({ dispose() {} }) }, }; @@ -116,6 +129,9 @@ const mainUri = 'file://' + main; const hv = world.hover && await world.hover.provideHover(mainDoc, { line: 1, character: 4 }); const hc = hv && hv.contents[0]; check('the hover is markdown with a zc code block, parsed and not escaped', hc && !hc.escaped && /^```zc\n/.test(hc.value) && /\n```/.test(hc.value), JSON.stringify(hc)); + const suggestions = await world.completion.provideCompletionItems(mainDoc, { line: 1, character: 4 }, { isCancellationRequested: false }); + check('completion provider returns signatures and replacement ranges', suggestions instanceof stub.CompletionList && suggestions.items.some(c => c.detail && c.range && c.insertText), JSON.stringify(suggestions && suggestions.items.slice(0, 2))); + check('cancelled completion requests are ignored', await world.completion.provideCompletionItems(mainDoc, { line: 1, character: 4 }, { isCancellationRequested: true }) === null); check('session one: the answer is saved under a key', savedKeys.length === 1 && /^[0-9a-f]{64}$/.test(savedKeys[0]), JSON.stringify(savedKeys)); ext.deactivate(); await sleep(700); @@ -146,6 +162,15 @@ const mainUri = 'file://' + main; await sleep(200); check('the old tree is answered from the cache, without asking the server', world.lines.some(l => /from the cache/.test(l)) && !world.lines.some(l => /-> textDocument\/didChange/.test(l)), JSON.stringify(world.lines.slice(-4))); + const expiredDoc = doc(path.join(root, 'expired.zc'), 'include rt; fn main() {\n let used = rt.new(int, 3);\n rt.free(used);\n \n}'); + const expiredList = await world.completion.provideCompletionItems(expiredDoc, { line: 3, character: 1 }, { isCancellationRequested: false }); + const expiredItem = expiredList && expiredList.items.find(c => c.label === 'used'); + check('completion maps consumed lifetimes to the editor strikethrough tag', expiredItem && expiredItem.tags?.[0] === stub.CompletionItemTag.Deprecated && /lifetime ended/.test(expiredItem.documentation?.value)); + const actionDoc = doc(path.join(root, 'imports.zc'), 'fn main() { rt.put_n(7); }'); + const actions = await world.actions.provideCodeActions(actionDoc, new stub.Range(0, 15, 0, 15), {}, { isCancellationRequested: false }); + check('quick fixes reach the editor as workspace edits', actions.some(a => a.kind === 'quickfix' && a.edit.edits.some(e => e.newText === 'include rt;\n'))); + const cancelled = await world.actions.provideCodeActions(actionDoc, new stub.Range(0, 15, 0, 15), {}, { isCancellationRequested: true }); + check('cancelled quick-fix requests produce no editor edits', cancelled.length === 0); ext.deactivate(); await sleep(700); fs.rmSync(root, { recursive: true, force: true }); diff --git a/tests/drivers/float_backends_test.py b/tests/drivers/float_backends_test.py index 2cb9473..cdcf48b 100644 --- a/tests/drivers/float_backends_test.py +++ b/tests/drivers/float_backends_test.py @@ -5,19 +5,19 @@ import subprocess root = pathlib.Path(__file__).resolve().parents[2] commands = [['bin/zy'], ['bin/zym'], ['node', 'src/js/tinyvm.js']] -sources = [(root / 'tests/programs/float.zc').read_text(), '''fn render(bits, precision, mode) { asm { bits @ precision @ mode @ #1236 } return 0; } +sources = [(root / 'tests/programs/float.zc').read_text(), '''include rt; fn render(bits, precision, mode) { asm { bits @ precision @ mode @ #1236 } return 0; } fn convert(bits) { return asm { bits @ #1218 }; } fn main() { - [4]f32 values; + f32[4] values; values[0] = 1.5; values[1] = -2.25; values[2] = 1e-45; values[3] = 1e40; - put_n(values[0] == 1.5); put_c(32); put_n(values[1] == -2.25); put_c(32); - put_n(values[2] > 0.0); put_c(32); put_n(values[3] == 1.0 / 0.0); put_c(10); - render(0.125, 2, 1); put_c(32); render(-0.125, 2, 1); put_c(32); - render(1e30, 6, 1); put_c(32); render(1.7976931348623157e308, 6, 1); put_c(10); - render(0.1, 0, 0); put_c(32); render(1e-5, 0, 0); put_c(32); render(1e16, 0, 0); put_c(10); - render(9.999, 3, 2); put_c(32); render(5e-324, 3, 2); put_c(32); render(0, 3, 2); put_c(10); - let negative_zero = 1 << 63; render(negative_zero, 6, 1); put_c(32); render(negative_zero, 0, 0); put_c(10); - put_n(convert(0.0 / 0.0)); put_c(32); put_n(convert(1.0 / 0.0)); put_c(32); put_n(convert(-1.0 / 0.0)); put_c(10); + rt.put_n(values[0] == 1.5); rt.put_c(32); rt.put_n(values[1] == -2.25); rt.put_c(32); + rt.put_n(values[2] > 0.0); rt.put_c(32); rt.put_n(values[3] == 1.0 / 0.0); rt.put_c(10); + render(0.125, 2, 1); rt.put_c(32); render(-0.125, 2, 1); rt.put_c(32); + render(1e30, 6, 1); rt.put_c(32); render(1.7976931348623157e308, 6, 1); rt.put_c(10); + render(0.1, 0, 0); rt.put_c(32); render(1e-5, 0, 0); rt.put_c(32); render(1e16, 0, 0); rt.put_c(10); + render(9.999, 3, 2); rt.put_c(32); render(5e-324, 3, 2); rt.put_c(32); render(0, 3, 2); rt.put_c(10); + let negative_zero = 1 << 63; render(negative_zero, 6, 1); rt.put_c(32); render(negative_zero, 0, 0); rt.put_c(10); + rt.put_n(convert(0.0 / 0.0)); rt.put_c(32); rt.put_n(convert(1.0 / 0.0)); rt.put_c(32); rt.put_n(convert(-1.0 / 0.0)); rt.put_c(10); return 0; } '''] @@ -40,7 +40,7 @@ for i, source in enumerate(sources): # an exponent after a long fractional spelling must reach the parser intact. literal = '1.' + '0' * 300 + 'e1' -source = f'fn main() {{ put_n(({literal} as word) == 0x4024000000000000); put_c(10); return 0; }}\n' +source = f'include rt; fn main() {{ rt.put_n(({literal} as word) == 0x4024000000000000); rt.put_c(10); return 0; }}\n' for command in commands: result = subprocess.run(command, input=source, text=True, capture_output=True, timeout=15) assert result.returncode == 0 and result.stdout == '1\n', (command, result.stdout, result.stderr) diff --git a/tests/drivers/format_test.js b/tests/drivers/format_test.js new file mode 100644 index 0000000..94890ae --- /dev/null +++ b/tests/drivers/format_test.js @@ -0,0 +1,88 @@ +// the pretty-printer: it never changes a program's tokens, it is stable (formatting twice is the same as once), +// it follows .zcformat and the editor's tab settings, and it leaves asm blocks, strings and comments alone. +// usage: node tests/drivers/format_test.js path/to/zclsp (run from the repository root) +const assert = require('assert'); +const cp = require('child_process'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const server = process.argv[2]; +const root = path.resolve(__dirname, '..', '..'); +const format = (text, args = []) => cp.spawnSync(server, ['--format', ...args], { input: text, cwd: root, maxBuffer: 1 << 28 }).stdout.toString(); +// tokens of a source, white space dropped; the lines of a block comment are compared without their indent +function tokens(src) { + const out = []; + let i = 0; + const n = src.length; + while (i < n) { + const c = src[i]; + if (/\s/.test(c)) { i++; continue; } + if (c === '`') { out.push(src.slice(i)); break; } + if (c === '/' && src[i + 1] === '/') { let j = src.indexOf('\n', i); if (j < 0) j = n; out.push(src.slice(i, j).trimEnd()); i = j; continue; } + if (c === '/' && src[i + 1] === '*') { let j = src.indexOf('*/', i + 2); j = j < 0 ? n : j + 2; out.push(src.slice(i, j).split('\n').map(l => l.trim()).join('\n')); i = j; continue; } + if (c === '"' || c === "'") { let j = i + 1; while (j < n && src[j] !== c && src[j] !== '\n') { if (src[j] === '\\') j++; j++; } out.push(src.slice(i, j + 1)); i = j + 1; continue; } + if (/[A-Za-z0-9_\u0080-￿]/.test(c)) { let j = i; while (j < n && /[A-Za-z0-9_\u0080-￿]/.test(src[j])) j++; out.push(src.slice(i, j)); i = j; continue; } + out.push(c); i++; + } + // adjacent symbol characters are compared one by one, so merged or split operators still match + return out; +} +const files = []; +for (const dir of ['examples', 'examples/learn', 'tests/programs', 'tests/errors', 'tests/typeerrors', 'tests/langerrors', 'src/lib', 'src/compiler', 'src/runtime', 'src/lsp', 'src/tools']) + for (const f of fs.readdirSync(path.join(root, dir))) if (f.endsWith('.zc')) files.push(path.join(dir, f)); +assert.ok(files.length > 100, 'the corpus: ' + files.length); +let checked = 0; +for (const f of files) { + const src = fs.readFileSync(path.join(root, f), 'utf8'); + const once = format(src); + assert.deepStrictEqual(tokens(once), tokens(src), f + ': formatting changed the tokens'); + assert.strictEqual(format(once), once, f + ': formatting is not stable'); + assert.ok(once === '' || once.endsWith('\n'), f + ': ends with a line break'); + if (!src.includes('`')) assert.ok(!/[ \t]+$/m.test(once), f + ': trailing white space'); + checked++; +} +// the behaviour +const sample = 'fn main(){\nint x=1+2 ;\n if(x>1){\n put_n( x );\n}\n\n\n\nreturn 0;}\n'; +assert.strictEqual(format(sample), 'fn main() {\n\tint x = 1+2;\n\tif (x>1) {\n\t\tput_n(x);\n\t}\n\n\treturn 0; }\n'); +assert.strictEqual(format('fn f(a,b){return a*b;}\n'), 'fn f(a, b) { return a*b; }\n'); +assert.strictEqual(format('fn main() {\n\tswitch (k) {\n\tcase 1: return 1;\n\tdefault:\n\t\treturn 2;\n\t}\n}\n'), 'fn main() {\n\tswitch (k) {\n\t\tcase 1: return 1;\n\t\tdefault:\n\t\t\treturn 2;\n\t}\n}\n'); +assert.strictEqual(format('fn main() {\nlet a = 1 +\n2;\nif (a)\nreturn 1;\nreturn 0;\n}\n'), 'fn main() {\n\tlet a = 1 +\n\t\t2;\n\tif (a)\n\t\treturn 1;\n\treturn 0;\n}\n'); +assert.strictEqual(format('fn put_f(x) { asm { x @ #1220 } return 0; }\n'), 'fn put_f(x) { asm { x @ #1220 } return 0; }\n'); +assert.strictEqual(format('fn k() { asm {\n 0 , ?\n} return 1; }\n'), 'fn k() { asm {\n\t\t0 , ?\n\t} return 1; }\n'); +assert.strictEqual(format('/* a\n * b\n */\nint g = 1; // note\nint h;\n'), '/* a\n * b\n */\nint g = 1; // note\nint h;\n'); +assert.strictEqual(format('fn main() {\n\tif (x)\n\t{\n\t\ty();\n\t}\n\telse\n\t{\n\t\tz();\n\t}\n}'), 'fn main() {\n\tif (x)\n\t{\n\t\ty();\n\t}\n\telse\n\t{\n\t\tz();\n\t}\n}\n'); +assert.strictEqual(format('fn main() { return 0; }\n`data stays\n as it is\n'), 'fn main() { return 0; }\n`data stays\n as it is\n'); +// .zcformat next to a document: found from the document's directory upward; the editor's tab settings apply without one +const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'zcfmt-')); +fs.mkdirSync(path.join(dir, 'a', 'b'), { recursive: true }); +fs.writeFileSync(path.join(dir, '.zcformat'), '# comment\nUseTab: false\nIndentWidth: 2\nMaxEmptyLines: 0\nNormalizeSpacing: false\n'); +const frame = m => { const b = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from('Content-Length: ' + b.length + '\r\n\r\n'), b]); }; +function lsp(uri, text, options) { + const input = Buffer.concat([ + { jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }, + { jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, languageId: 'zc', text } } }, + { jsonrpc: '2.0', id: 2, method: 'textDocument/formatting', params: { textDocument: { uri }, options } }, + { jsonrpc: '2.0', method: 'exit' }].map(frame)); + const r = cp.spawnSync(server, process.argv.slice(3), { input, cwd: root, maxBuffer: 1 << 26 }); + let d = r.stdout, caps, res; + while (d.length) { + const e = d.indexOf('\r\n\r\n'), n = Number(/Content-Length:\s*(\d+)/i.exec(d.subarray(0, e).toString())[1]); + const m = JSON.parse(d.subarray(e + 4, e + 4 + n)); d = d.subarray(e + 4 + n); + if (m.id === 1) caps = m.result.capabilities; + if (m.id === 2) res = m.result; + } + return { caps, res }; +} +const messy = 'fn main(){\nif(1){\nput_n( 1 );\n\n\nput_c(10);}\nreturn 0;}\n'; +let r = lsp('file://' + path.join(dir, 'a', 'b', 'x.zc'), messy, { tabSize: 8, insertSpaces: false }); +assert.ok(r.caps.documentFormattingProvider, 'formatting is advertised'); +assert.strictEqual(r.res.length, 1); +assert.strictEqual(r.res[0].newText, 'fn main(){\n if(1){\n put_n( 1 );\n put_c(10);}\n return 0;}\n', '.zcformat from two directories up wins over the editor'); +assert.deepStrictEqual(r.res[0].range.start, { line: 0, character: 0 }); +fs.rmSync(path.join(dir, '.zcformat')); +r = lsp('file://' + path.join(dir, 'a', 'y.zc'), messy, { tabSize: 3, insertSpaces: true }); +assert.strictEqual(r.res[0].newText, 'fn main() {\n if (1) {\n put_n(1);\n\n put_c(10); }\n return 0; }\n', 'the editor settings without a .zcformat'); +r = lsp('file://' + path.join(dir, 'a', 'y.zc'), 'fn main() {\n\treturn 0;\n}\n', { tabSize: 4, insertSpaces: false }); +assert.deepStrictEqual(r.res, [], 'a formatted document gets no edit'); +fs.rmSync(dir, { recursive: true }); +console.log('ok formatter: ' + checked + ' files keep their tokens and format stably; options and edits'); diff --git a/tests/drivers/frame_growth_test.py b/tests/drivers/frame_growth_test.py index 74156e3..6e03cbc 100644 --- a/tests/drivers/frame_growth_test.py +++ b/tests/drivers/frame_growth_test.py @@ -4,22 +4,22 @@ import subprocess import sys command = sys.argv[1:] -source = '''unchecked int descend(int depth, int* root) { - [2000]int padding; +source = '''include rt; unchecked int descend(int depth, int* root) { + int[2000] padding; padding[0] = depth; - if (depth == 0) { gc(); return *root; } + if (depth == 0) { rt.gc(); return *root; } return descend(depth - 1, root) + padding[0]; } unchecked int oversized(int* root) { - [300000]int values; + int[300000] values; values[0] = *root; values[299999] = 9; - gc(); return values[0] + values[299999]; + rt.gc(); return values[0] + values[299999]; } unchecked fn main() { int value = 7; - put_n(descend(300, &value)); put_c(32); - put_n(oversized(&value)); put_c(32); - gc(); put_n(frame_node == 0); put_c(10); + rt.put_n(descend(300, &value)); rt.put_c(32); + rt.put_n(oversized(&value)); rt.put_c(32); + rt.gc(); rt.put_n(rt.frame_node == 0); rt.put_c(10); return 0; } ''' diff --git a/tests/drivers/fuzz_compiler.py b/tests/drivers/fuzz_compiler.py new file mode 100644 index 0000000..512ec26 --- /dev/null +++ b/tests/drivers/fuzz_compiler.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""Deterministic grammar fuzzing for the parser, runtime, and ownership paths. + +Usage: python3 tests/drivers/fuzz_compiler.py [path/to/zy] [path/to/zym] [--js] +""" +import random +import subprocess +import sys + + +args = [arg for arg in sys.argv[1:] if arg != "--js"] +js = "--js" in sys.argv[1:] +zy = args[0] if len(args) > 0 else "bin/zy" +zym = args[1] if len(args) > 1 else "bin/zym" +rng = random.Random(0x5A17) +values = [] +library_values = [] + + +def expr(depth): + if depth == 0 or rng.randrange(4) == 0: + value = rng.randrange(10) + return str(value), value + left, a = expr(depth - 1) + right, b = expr(depth - 1) + op = rng.choice(("+", "-", "*")) + if op == "+": + value = a + b + elif op == "-": + value = a - b + else: + value = a * b + return f"({left} {op} {right})", value + + +lines = ["include std;", "fn churn(n) {", " int i = 0;", " while (i < n) {", " { int* p = new(int); *p = i; }", " i++;", " }", " return 0;", "}", "fn stable() { return 73; }", "fn main() {", " let base = live_objects();"] +for _ in range(128): + source, value = expr(4) + lines.append(f" put_n({source}); put_c(32);") + values.append(value) +for _ in range(32): + a = "".join(rng.choice("abc") for _ in range(rng.randrange(11))) + b = "".join(rng.choice("abc") for _ in range(rng.randrange(6))) + lines.append(f' put_n(Text.len("{a}")); put_c(32); put_n(Text.eq("{a}", "{b}")); put_c(32);') + lines.append(f' put_n(Text.starts("{a}", "{b}")); put_c(32); put_n(Text.ends("{a}", "{b}")); put_c(32); put_n(Text.find("{a}", "{b}")); put_c(32);') + lines.append(f' put_n(Bytes.len(b"{a}")); put_c(32); put_n(Bytes.eq(b"{a}", b"{b}")); put_c(32);') + lines.append(f' put_n(Bytes.starts(b"{a}", b"{b}")); put_c(32); put_n(Bytes.ends(b"{a}", b"{b}")); put_c(32); put_n(Bytes.find(b"{a}", b"{b}")); put_c(32);') + library_values += [len(a), int(a == b), int(a.startswith(b)), int(a.endswith(b)), a.find(b), len(a.encode()), int(a == b), int(a.startswith(b)), int(a.endswith(b)), a.find(b)] +lines += [ + " churn(300); gc(); put_n(live_objects() - base); put_c(32);", + ' char* source = "fn f() { return 7; }"; let parsed = tokens(source);', + ' put_n(parsed[0] > 0); put_c(32); put_n(eq_w(source, "fn f() { return 7; }")); put_c(32);', + ' char* eval_source = "return 42;"; int e = 0;', + ' while (e < 16) { let q = tokens(eval_source); put_n(eval(q)); put_c(32); e++; }', + ' put_n(stable()); put_c(32); put_n(eq_w(eval_source, "return 42;")); put_c(10);', + " return 0;", + "}", +] +program = "\n".join(lines) + "\n" +expected = " ".join(str(x) for x in values + library_values + [0, 1, 1, *([42] * 16), 73, 1]) + "\n" + + +def run(binary): + result = subprocess.run(binary, input=program, text=True, capture_output=True, timeout=30) + if result.returncode or result.stdout != expected: + actual_words = result.stdout.split() + expected_words = expected.split() + mismatch = next((i for i, pair in enumerate(zip(actual_words, expected_words)) if pair[0] != pair[1]), min(len(actual_words), len(expected_words))) + raise SystemExit( + f"{' '.join(binary)} failed deterministic fuzz seed 0x5a17\n" + f"exit={result.returncode}, token mismatch at {mismatch}\n" + f"expected={expected_words[max(0, mismatch - 3):mismatch + 4]!r}\n" + f"stdout={actual_words[max(0, mismatch - 3):mismatch + 4]!r}\n" + f"stderr={result.stderr!r}" + ) + return result.stdout + + +hosts = [(zy, [zy]), (zym, [zym])] +if js: + hosts.append(("javascript", ["node", "src/js/tinyvm.js"])) +outputs = [run(command) for _, command in hosts] +if any(output != outputs[0] for output in outputs[1:]): + raise SystemExit("compiler hosts disagreed on deterministic fuzz output") +print("ok compiler/runtime/stdlib fuzz: 128 generated expressions, 300 scoped allocations and GC, " + "16 eval recompiles, unchanged source strings, and 32 randomized Text/Bytes cases on " + + ", ".join(name for name, _ in hosts)) diff --git a/tests/drivers/fuzz_lsp.js b/tests/drivers/fuzz_lsp.js new file mode 100644 index 0000000..e22a44b --- /dev/null +++ b/tests/drivers/fuzz_lsp.js @@ -0,0 +1,89 @@ +// Deterministic source/protocol fuzzing for diagnostics, caching, and recovery. +// Usage: node tests/drivers/fuzz_lsp.js path/to/zclsp +const { spawnSync } = require('child_process'); +const server = process.argv[2] || 'bin/zclsp'; +let state = 0x5a17; +function next() { state = (Math.imul(state, 1664525) + 1013904223) >>> 0; return state; } +function frame(m) { + const body = Buffer.from(JSON.stringify(m), 'utf8'); + return Buffer.concat([Buffer.from('Content-Length: ' + body.length + '\r\n\r\n'), body]); +} +function parse(buf) { + const out = []; let at = 0; + while (at < buf.length) { + const end = buf.indexOf('\r\n\r\n', at); + if (end < 0) throw new Error('truncated LSP header'); + const match = /Content-Length: (\d+)/.exec(buf.slice(at, end).toString()); + if (!match) throw new Error('missing Content-Length'); + const size = Number(match[1]), start = end + 4; + if (start + size > buf.length) throw new Error('truncated LSP body'); + out.push(JSON.parse(buf.slice(start, start + size).toString('utf8'))); + at = start + size; + } + return out; +} +const msgs = [{ jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }]; +const docs = []; +for (let i = 0; i < 32; i++) { + const uri = `file:///fuzz-${i}.zc`, n = next() % 1000; + const broken = i % 3 === 0; + const initial = `fn calc_${i}(x) { return (x + ${n}) * 2; }\nfn main() { let y = calc_${i}(${n})${broken ? '' : ';'} return y; }\n`; + const fixed = `fn calc_${i}(x) { return (x + ${n}) * 2; }\nfn main() { let y = calc_${i}(${n}); return y; }\n`; + docs.push({ uri, broken }); + msgs.push({ jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, languageId: 'zc', version: 1, text: initial } } }); + msgs.push({ jsonrpc: '2.0', id: 100 + i, method: 'textDocument/semanticTokens/full', params: { textDocument: { uri } } }); + msgs.push({ jsonrpc: '2.0', method: 'textDocument/didChange', params: { textDocument: { uri, version: 2 }, contentChanges: [{ text: fixed }] } }); +} +const stdUri = 'file:///fuzz-std.zc'; +msgs.push({ jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri: stdUri, languageId: 'zc', version: 1, text: 'include std;\nfn main() { return Text.starts("alpha", "al"); }\n' } } }); + +// Keep one server alive while documents are repeatedly opened, made malformed, +// repaired, closed, and reopened at a new version. This exercises cache +// invalidation and stale-diagnostic cleanup across a long-lived editor session. +const churn = new Map(); +for (let i = 0; i < 96; i++) { + const slot = i % 4, uri = `file:///churn-${slot}.zc`, n = next() % 10000; + const malformed = `fn churn_${slot}(x) { let y = (x + ${n}); return y; }\nfn main() { return churn_${slot}(${n}) }\n`; + const valid = `fn churn_${slot}(x) { let y = (x + ${n}); return y; }\nfn main() { return churn_${slot}(${n}); }\n`; + const version = Math.floor(i / 4) * 3 + 1; + msgs.push({ jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, languageId: 'zc', version, text: malformed } } }); + msgs.push({ jsonrpc: '2.0', id: 1000 + i, method: 'textDocument/semanticTokens/full', params: { textDocument: { uri } } }); + msgs.push({ jsonrpc: '2.0', method: 'textDocument/didChange', params: { textDocument: { uri, version: version + 1 }, contentChanges: [{ text: valid }] } }); + msgs.push({ jsonrpc: '2.0', method: 'textDocument/didClose', params: { textDocument: { uri } } }); + churn.set(uri, valid); +} +let churnVersion = 100; +for (const [uri, text] of churn) { + msgs.push({ jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, languageId: 'zc', version: churnVersion++, text } } }); +} +msgs.push({ jsonrpc: '2.0', method: 'exit' }); +const run = spawnSync(server, [], { input: Buffer.concat(msgs.map(frame)), timeout: 60000, maxBuffer: 1 << 28 }); +if (run.error || run.status !== 0) throw run.error || new Error(`server exit ${run.status}: ${run.stderr}`); +const out = parse(run.stdout); +const diagnostics = new Map(); +for (const m of out) { + if (m.method === 'textDocument/publishDiagnostics') { + const items = diagnostics.get(m.params.uri) || []; + items.push(m.params.diagnostics); + diagnostics.set(m.params.uri, items); + } +} +for (let i = 0; i < docs.length; i++) { + const { uri, broken } = docs[i], batches = diagnostics.get(uri) || []; + if (batches.length < 2) throw new Error(`${uri}: expected diagnostics for open and change`); + if (broken && !batches[0].some(d => d.severity === 1)) throw new Error(`${uri}: malformed source was not diagnosed`); + if (batches[batches.length - 1].some(d => d.severity === 1)) throw new Error(`${uri}: valid edit retained an error`); + const response = out.find(m => m.id === 100 + i); + if (!response || response.error || !response.result || response.result.data.length % 5) throw new Error(`${uri}: semantic tokens response was malformed`); +} +for (let i = 0; i < 96; i++) { + const response = out.find(m => m.id === 1000 + i); + if (!response || response.error || !response.result || response.result.data.length % 5) throw new Error(`churn request ${i}: malformed semantic tokens response`); +} +const stdDiag = diagnostics.get(stdUri) || []; +if (!stdDiag.length || stdDiag[stdDiag.length - 1].some(d => d.severity === 1)) throw new Error('stdlib include failed LSP source analysis'); +for (const uri of churn.keys()) { + const batches = diagnostics.get(uri) || []; + if (!batches.length || batches[batches.length - 1].some(d => d.severity === 1)) throw new Error(`${uri}: reopened document retained stale diagnostics`); +} +console.log(`ok lsp fuzz: ${docs.length} deterministic open/change sequences, 96 long-session cache churns, semantic tokens, and include std`); diff --git a/tests/drivers/fuzz_malformed.py b/tests/drivers/fuzz_malformed.py new file mode 100644 index 0000000..5bf97cf --- /dev/null +++ b/tests/drivers/fuzz_malformed.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +"""Deterministic malformed-source differential fuzzing for compiler hosts. + +Usage: python3 tests/drivers/fuzz_malformed.py [native] [microcore] +""" +import random +import subprocess +import sys + + +bins = sys.argv[1:] or ["bin/zy", "bin/zym"] +if len(bins) == 1: + bins.append(bins[0]) +rng = random.Random(0xC0DE) +tokens = [ + b"fn", b"let", b"int", b"char*", b"word", b"return", b"if", b"else", + b"while", b"for", b"include", b"std", b";", b"{", b"}", b"(", b")", + b"[", b"]", b"=", b",", b".", b":", b"?", b"+", b"-", b"*", b"&", + b"|", b"!", b"~", b'"text"', b"'x'", b"0", b"1", b"_", b"NIF", + b"asm", b"unchecked", b"\x00", b"\xff", "λ".encode(), +] +cases = [] +for i in range(256): + words = [rng.choice(tokens) for _ in range(rng.randrange(1, 100))] + cases.append(b" ".join(words) + b"\n") + +# Mutate a small valid program around parser and analyzer boundaries. Keeping +# each mutation bounded makes a failing seed easy to reproduce and minimize. +base = b"include std;\nfn main() { let x = 1; return x; }\n" +for _ in range(64): + data = bytearray(base) + for __ in range(rng.randrange(1, 5)): + at = rng.randrange(len(data)) + if rng.randrange(2): + data[at:at + 1] = rng.choice(tokens) + else: + del data[at:at + 1] + cases.append(bytes(data)) + + +def run(binary, source): + return subprocess.run( + [binary], input=b"typecheck on;\n" + source, capture_output=True, timeout=3 + ) + + +for i, source in enumerate(cases): + results = [] + for binary in bins: + try: + result = run(binary, source) + except subprocess.TimeoutExpired: + raise SystemExit(f"{binary} timed out on malformed seed 0xC0DE case {i}: {source!r}") + if result.returncode < 0: + raise SystemExit( + f"{binary} crashed on malformed seed 0xC0DE case {i}: " + f"signal {-result.returncode}, source={source!r}, stderr={result.stderr!r}" + ) + results.append((result.returncode, result.stdout, result.stderr)) + if any(result != results[0] for result in results[1:]): + raise SystemExit(f"compiler hosts disagreed on malformed seed 0xC0DE case {i}: {source!r}") + +print(f"ok malformed-source fuzz: {len(cases)} bounded token and byte mutations agree on {len(bins)} compiler hosts") diff --git a/tests/drivers/fuzz_wasm.js b/tests/drivers/fuzz_wasm.js new file mode 100644 index 0000000..b0982d4 --- /dev/null +++ b/tests/drivers/fuzz_wasm.js @@ -0,0 +1,38 @@ +// Deterministic compiler/runtime/stdlib fuzzing through the WebAssembly host. +// Usage: node tests/drivers/fuzz_wasm.js path/to/zc.wasm +const fs = require('fs'); +const ZC = require('../../src/web/zc.js'); + +let state = 0x5a17; +function next() { state = (Math.imul(state, 1664525) + 1013904223) >>> 0; return state; } +function expr(depth) { + if (!depth || next() % 4 === 0) { const n = next() % 10; return [String(n), n]; } + const [left, a] = expr(depth - 1), [right, b] = expr(depth - 1), op = ['+', '-', '*'][next() % 3]; + return [`(${left} ${op} ${right})`, op === '+' ? a + b : op === '-' ? a - b : a * b]; +} +function quote(s) { return '"' + s + '"'; } + +(async () => { + const lines = ['include std;', 'fn churn(n) { int i = 0; while (i < n) { { int* p = new(int); *p = i; } i++; } return 0; }', 'fn stable() { return 73; }', 'fn main() {', ' let base = live_objects();']; + const expected = []; + for (let i = 0; i < 128; i++) { + const [source, value] = expr(4); + lines.push(` put_n(${source}); put_c(32);`); expected.push(value); + } + for (let i = 0; i < 32; i++) { + const a = Array.from({ length: next() % 11 }, () => 'abc'[next() % 3]).join(''); + const b = Array.from({ length: next() % 6 }, () => 'abc'[next() % 3]).join(''); + lines.push(` put_n(Text.len(${quote(a)})); put_c(32); put_n(Text.eq(${quote(a)}, ${quote(b)})); put_c(32);`); + lines.push(` put_n(Text.starts(${quote(a)}, ${quote(b)})); put_c(32); put_n(Text.ends(${quote(a)}, ${quote(b)})); put_c(32); put_n(Text.find(${quote(a)}, ${quote(b)})); put_c(32);`); + lines.push(` put_n(Bytes.len(b${quote(a)})); put_c(32); put_n(Bytes.eq(b${quote(a)}, b${quote(b)})); put_c(32);`); + lines.push(` put_n(Bytes.starts(b${quote(a)}, b${quote(b)})); put_c(32); put_n(Bytes.ends(b${quote(a)}, b${quote(b)})); put_c(32); put_n(Bytes.find(b${quote(a)}, b${quote(b)})); put_c(32);`); + expected.push(a.length, Number(a === b), Number(a.startsWith(b)), Number(a.endsWith(b)), a.indexOf(b), a.length, Number(a === b), Number(a.startsWith(b)), Number(a.endsWith(b)), a.indexOf(b)); + } + lines.push(' churn(300); gc(); put_n(live_objects() - base); put_c(32);', ' put_n(stable()); put_c(10);', ' return 0;', '}'); + expected.push(0, 73); + const source = lines.join('\n') + '\n', want = expected.join(' ') + '\n'; + const zc = await ZC.load(fs.readFileSync(process.argv[2] || 'src/web/zc.wasm')); + const result = await zc.run(source); + if (result.output !== want) throw new Error(`wasm fuzz mismatch\nexpected: ${want.slice(0, 180)}\nactual: ${result.output.slice(0, 180)}\nmessages: ${result.messages}`); + console.log('ok wasm compiler/runtime/stdlib fuzz: 128 expressions, 300 scoped allocations and GC, and 32 Text/Bytes cases'); +})().catch(error => { console.error(error); process.exitCode = 1; }); diff --git a/tests/drivers/hints_test.js b/tests/drivers/hints_test.js new file mode 100644 index 0000000..8bb1934 --- /dev/null +++ b/tests/drivers/hints_test.js @@ -0,0 +1,31 @@ +// hints are informational diagnostics (severity 3) and follow positional hints on / small / off directives. +// usage: node tests/drivers/hints_test.js path/to/zclsp +const assert = require('assert'); +const cp = require('child_process'); +const server = process.argv[2]; +const frame = m => { const b = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from('Content-Length: ' + b.length + '\r\n\r\n'), b]); }; +function diagnostics(text) { + const uri = 'file:///hints.zc'; + const input = Buffer.concat([ + { jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }, + { jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, languageId: 'zc', text } } }, + { jsonrpc: '2.0', method: 'exit' }].map(frame)); + const r = cp.spawnSync(server, process.argv.slice(3), { input, timeout: 20000, maxBuffer: 4 << 20 }); + assert.strictEqual(r.status, 0, String(r.stderr)); + let d = r.stdout, out = []; + while (d.length) { + const e = d.indexOf('\r\n\r\n'), n = Number(/Content-Length:\s*(\d+)/i.exec(d.subarray(0, e).toString())[1]); + const m = JSON.parse(d.subarray(e + 4, e + 4 + n)); d = d.subarray(e + 4 + n); + if (m.method === 'textDocument/publishDiagnostics') out = m.params.diagnostics; + } + return out; +} +const names = ds => ds.map(d => d.message.split(' ')[0]).sort().join(','); +const flow = 'fn main() {\n let a = 1;\n hints small;\n let b = 2;\n int c = 5 * 8;\n hints off;\n let d = 3;\n int k = 3 * 4;\n hints on;\n let e = 4;\n return 0;\n}\n'; +const ds = diagnostics(flow); +assert.ok(ds.length > 0 && ds.every(d => d.severity === (d.range.start.line === 3 || d.range.start.line === 4 ? 4 : 3)), 'small uses compact severity 4; on uses informational severity 3: ' + JSON.stringify(ds)); +assert.strictEqual(names(ds), 'a,b,c,e,multiplying', 'off suppresses hints; small retains them with compact markers'); +const style = diagnostics('fn main() {\n int n = 1;\n n = n * 8;\n hints small;\n n = n * 16;\n return n;\n}\n'); +assert.strictEqual(style.filter(d => /is a shift/.test(d.message)).length, 2, 'small retains style hints with compact markers'); +assert.strictEqual(diagnostics('fn main() { int x = ; return 0; }').filter(d => d.severity === 1).length > 0, true); +console.log('ok hints are blue information diagnostics with positional on, small and off'); diff --git a/tests/drivers/hover_features_test.js b/tests/drivers/hover_features_test.js index 895e923..d6e1226 100644 --- a/tests/drivers/hover_features_test.js +++ b/tests/drivers/hover_features_test.js @@ -38,15 +38,15 @@ for (const type of ['word', 'int', 'char', 'bool', 'float', 'f64', 'f32', 'byte' assert.ok(value && value.startsWith('```zc\n') && value.includes('\n\n'), type + ' has formatted built-in documentation'); } assert.match(hover('void noop() {}\nfn main() { return 0; }\n', 'void'), /no meaningful result/); -const array = 'fn main() {\n [4]u8 values;\n return values[0];\n}\n'; +const array = 'fn main() {\n u8[4] values;\n return values[0];\n}\n'; assert.match(hover(array, '[4]'), /fixed length of 4/); -assert.match(hover(array, 'values', 1), /type \[4\]u8/); +assert.match(hover(array, 'values', 1), /\(u8\[4\]\) let values/); const struct = 'struct pair { int left; float right; };\nfn main() { pair value; return value.left; }\n'; assert.match(hover(struct, 'pair', 1), /int left;[\s\S]*float right;/); -assert.match(hover(struct, 'value', 1), /type pair/); +assert.match(hover(struct, 'value', 1), /\(pair\) let value/); const fields = 'struct pair { int /* left field */ left, /* right field */ right; };\nfn main() { pair p; return p.left + p.right; }\n'; -assert.match(hover(fields, 'left +'), /field; type int[\s\S]*left field/); -assert.match(hover(fields, 'right;'), /field; type int[\s\S]*right field/); +assert.match(hover(fields, 'left +'), /\(int\) let left \/\/ field;[\s\S]*left field/); +assert.match(hover(fields, 'right;'), /\(int\) let right \/\/ field;[\s\S]*right field/); const empty = '/**/ int helper() { return 1; }\nfn main() { return helper(); }\n'; assert.match(hover(empty, 'helper();'), /int helper\(\)/); const variableDocs = 'fn main() {\n int /* first variable */ first = 1, /* second variable */ second = 2;\n return first + second;\n}\n'; @@ -63,23 +63,69 @@ const functionDocs = '/**\n * adds **one** to its argument.\n */\nint add(int x) assert.match(hover(functionDocs, 'add(2)'), /adds \*\*one\*\*/); const inferred = 'fn twice(x) { return x * 2; }\nfn main() { let value = twice(3); return value; }\n'; assert.match(hover(inferred, 'twice(3)'), /int twice\(int x\)/); -assert.match(hover(inferred, 'x *'), /type int/); -assert.match(hover(inferred, 'value;'), /type int/); +assert.match(hover(inferred, 'x *'), /\(int\) let x \/\/ parameter;/); +assert.match(hover(inferred, 'value;'), /\(int\) let value/); const pointer = 'int read(int* p) { return p[0]; }\nfn main() { int value = 3; return read(&value); }\n'; -assert.match(hover(pointer, 'p[0]'), /type int\*[\s\S]*pointer to `int`/); -const alias = 'type row = [3]int;\nfn main() { row values; return values[0]; }\n'; -assert.match(hover(alias, 'row values'), /\[3\]int[\s\S]*fixed length of 3/); +assert.match(hover(pointer, 'p[0]'), /\(int\*\) let p \/\/ parameter;[\s\S]*pointer to `int`/); +const alias = 'type row = int[3];\nfn main() { row values; return values[0]; }\n'; +assert.match(hover(alias, 'row values'), /int\[3\][\s\S]*fixed length of 3/); const enumeration = 'enum color { red, green = 5, blue };\nfn main() { color shade = green; return shade; }\n'; -assert.match(hover(enumeration, 'shade;'), /type color/); -const dynamic = 'fn main() { []int items; push(items, 3); return len(items); }\n'; -assert.match(hover(dynamic, 'items);'), /type \[\]int[\s\S]*growable array handle/); +assert.match(hover(enumeration, 'shade;'), /\(color\) let shade/); +const dynamic = 'fn main() { int[?] items; push(items, 3); return len(items); }\n'; +assert.match(hover(dynamic, 'items);'), /\(int\[\?\]\) let items[\s\S]*growable array handle/); const callable = 'fn main() { let apply = fn(int x) { return x + 1; }; return apply(2); }\n'; -assert.match(hover(callable, 'apply(2)'), /type .*\([\s\S]*callable value/); -assert.match(hover(callable, 'apply(2)'), /type int\(int\)/); +assert.match(hover(callable, 'apply(2)'), /\(.*\([\s\S]*callable value/); +assert.match(hover(callable, 'apply(2)'), /\(int\(int\)\) let.m apply/); const floatClosure = 'fn main() { let f = fn(float x) { return x + 1.5; }; let result = f(2.0); return result; }\n'; -assert.match(hover(floatClosure, 'result;'), /type float/); -const inferredPointer = 'struct node { int value; };\nfn make_node() { return new(node); }\nfn main() { let p = make_node(); return p.value; }\n'; -assert.match(hover(inferredPointer, 'p.value'), /type node\*/); -const aggregateAlias = 'fn main() { [3]int a; a[1] = 7; let alias = a; return alias[1]; }\n'; -assert.match(hover(aggregateAlias, 'alias[1]'), /type int\*/); +assert.match(hover(floatClosure, 'result;'), /\(float\) let result/); +const inferredPointer = 'include rt;\nstruct node { int value; };\nfn make_node() { return new(node); }\nfn main() { let p = make_node(); return p.value; }\n'; +assert.match(hover(inferredPointer, 'p.value'), /\(node\*\) let.m p/); +const aggregateAlias = 'fn main() { int[3] a; a[1] = 7; let alias = a; return alias[1]; }\n'; +assert.match(hover(aggregateAlias, 'alias[1]'), /\(int\*\) let alias/); +const sections = '// --- values ---\n// these declarations form a group.\nconst int limit = 7;\nfn main() { return limit; }'; +assert.ok(!hover(sections, 'limit;').includes('these declarations form a group')); +assert.ok(!hover(sections, 'limit;').includes('--- values ---')); +const blockSection = '/* ---- values ---- */\nint limit = 7;\nfn main() { return limit; }'; +assert.ok(!hover(blockSection, 'limit;').includes('---- values ----')); +const trailing = 'fn main() {\n int n = 7; // the current count\n int next = 2;\n return n + next;\n}'; +assert.match(hover(trailing, 'n +'), /the current count/); +assert.ok(!hover(trailing, 'next;').includes('the current count')); +const trailingBlock = 'fn main() { int a = 1, b = 2; /* final count */\n return a + b; }'; +assert.ok(!hover(trailingBlock, 'a +').includes('final count')); +assert.match(hover(trailingBlock, 'b;'), /final count/); +const adjacent = 'fn main() { int a /* first count */ = 1, b /* second count */ = 2; return a + b; }'; +assert.match(hover(adjacent, 'a +'), /first count/); +assert.ok(!hover(adjacent, 'a +').includes('second count')); +assert.match(hover(adjacent, 'b;'), /second count/); +// section headings end a doc block; trailing comments belong to their own line's declaration +const sectionDocs = '// ---- one ----\n\n// adds.\nint add(int a, int b) { return a + b; } // after add\n// overview\n// ---- two ----\nint sub(int a, int b) { return a - b; }\n// ---- three ----\n// own doc\nint mul(int a, int b) { return a * b; }\nint g = 3; /* after g */\nfn main() { return add(1, 2) + sub(3, 1) + mul(2, 2) + g; }\n'; +const addHover = hover(sectionDocs, 'add(1'); +assert.ok(addHover.includes('adds.') && addHover.includes('after add') && !addHover.includes('overview') && !addHover.includes('after g'), addHover); +assert.strictEqual(hover(sectionDocs, 'sub(3'), '```zc\nint sub(int a, int b)\n```'); +const mulHover = hover(sectionDocs, 'mul(2'); +assert.ok(!mulHover.includes('own doc') && !mulHover.includes('three') && !mulHover.includes('after'), mulHover); +assert.ok(hover(sectionDocs, 'g; }').includes('after g')); +// ownership and transfers explained in hovers +const lifeDocs = 'include rt; struct node { int v; node* next; };\nint*.u make(int n) { int* q = new(int); *q = n; return u*q; }\nfn take(.d p) { return p[0]; }\nfn main() {\n let a = make(1);\n node* b = new(node), c = new(node);\n b.next = c;\n node* r = b.next;\n let d = new(3);\n take(d*d);\n return a[0] + r.v;\n}\n'; +assert.match(hover(lifeDocs, 'q = new'), /let.m q[\s\S]*passed up: returned on line 2/); +assert.match(hover(lifeDocs, 'a ='), /let.m a[\s\S]*freed where its block ends on line 12/); +assert.match(hover(lifeDocs, 'r.v'), /borrows from `b`, declared on line 6/); +assert.match(hover(lifeDocs, 'c = new'), /passed down: stored in a container `b` on line 7/); +assert.match(hover(lifeDocs, 'd = new'), /passed down: given to a d parameter on line 10/); +const declaredResult = 'fn main() { let f = fn:float(int x) { return x; }; return f(2); }\n'; +assert.match(hover(declaredResult, 'f(2)'), /\(float\(int\)\) let.m f/); +const nonDocs = '//: scratch region overview\nint scratch = 1;\n/* public count */ int count = 2; //: implementation note\nfn main() { return scratch + count; }'; +assert.ok(!hover(nonDocs, 'scratch +').includes('scratch region overview')); +assert.match(hover(nonDocs, 'count;'), /public count/); +assert.ok(!hover(nonDocs, 'count;').includes('implementation note')); +const globals = 'let hello = "hello", prog[20000];\nint count = 3;\nfn main() { return hello[0] + prog[0] + count; }\n'; +for (const [name, type, use] of [['hello', 'char*', 'hello[0]'], ['prog', 'word[20000]', 'prog[0]'], ['count', 'int', 'count;']]) { + for (const needle of [name, use]) { + const value = hover(globals, needle); + assert.ok(value.startsWith('```zc\n(' + type + ') let ' + name + ' // global;\n```'), value); + assert.ok(!value.includes('; type '), value); + } +} +assert.match(hover('include run; fn main() { return run.sval[0]; }', 'sval'), /symbol[\s\S]*value/i); +assert.match(hover('include rt; fn main() { return rt.len_s(b"bytes"); }', 'len_s'), /number of bytes in a packed byte string/i); console.log('ok built-in and composite types, inferred signatures, and separate block docs'); diff --git a/tests/drivers/hover_surface_test.js b/tests/drivers/hover_surface_test.js new file mode 100644 index 0000000..13a9a3f --- /dev/null +++ b/tests/drivers/hover_surface_test.js @@ -0,0 +1,65 @@ +// hover declarations and instructions in the source-only documents opened by f12. +const assert = require('assert'); +const fs = require('fs'); +const cp = require('child_process'); +const path = require('path'); +const frame = m => { const b = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from(`Content-Length: ${b.length}\r\n\r\n`), b]); }; +const messages = [{ id: 1, method: 'initialize', params: {} }], checks = []; +let id = 2; +function doc(uri, text) { + messages.push({ method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, text } } }); + return (needle, offset = 0) => { + const at = text.indexOf(needle) + offset; assert.ok(at >= 0, needle); + const before = text.slice(0, at), rid = id++; + messages.push({ id: rid, method: 'textDocument/hover', params: { textDocument: { uri }, position: { line: before.split('\n').length - 1, character: before.length - before.lastIndexOf('\n') - 1 } } }); + return rid; + }; +} +const compiler = fs.readFileSync('src/compiler/compiler.zc', 'utf8'); +const ask = doc('tinyvm:/compiler.zc', compiler); +checks.push([ask('ifrcap = NIF', 9), /const int NIF = 431/, /431/]); +checks.push([ask('capture_width = NCAP', 16), /const int NCAP = 32/, /initial capture capacity/, /32/]); +checks.push([ask('closure_cap = NCLO', 14), /const int NCLO = 8/, /initial closure nesting capacity/]); +checks.push([ask('clo_n = clo_n_initial', 8), /word\[NCLO\] clo_n_initial/]); +checks.push([ask('int nclo, closure_cap', 10), /\(int\) let closure_cap/, /value at this position:[\s\S]*\n8\n/]); +const rt = doc('tinyvm:/rt.zc', fs.readFileSync('src/runtime/rt.zc', 'utf8')); +checks.push([rt('SYS_CLOSE = 2'), /const int SYS_CLOSE = 2/]); +const fixture = doc('tinyvm:/fixture.zc', 'const int FIRST = 1, SECOND = 2;\nword[4] backing; word active = backing;\nint x = SECOND;\n'); +checks.push([fixture('x = SECOND', 4), /const int SECOND = 2/]); +checks.push([fixture('active = backing', 9), /word\[4\] backing/]); +const mnemonic = 'q@!$_~cjzr,?.+-*&|^><%'; +for (const op of mnemonic) { + const get = doc('tinyvm:/asm-' + op.charCodeAt(0) + '.zc', `fn f() { asm { ${op} } return 0; }`); + checks.push([get(`{ ${op}`, 2), /opcode \d+/, /(stack:|return stack|data stack)/]); +} +const special = doc('tinyvm:/special.zc', 'fn f() { asm { #1224 } }\ninclude std;\n'); +checks.push([special('#1224'), /load byte/, /byte address/]); +checks.push([special('1224'), /load byte/]); +checks.push([special('std;'), /include std;/, /embedded standard library/, /nothing imports it automatically/]); +const normal = doc('file:///ordinary.zc', 'fn main() { int std = 7; int c = 2; return std + c; }'); +checks.push([normal('std +'), /\(int\) let std/, /value at this position/]); +checks.push([normal('c;'), /\(int\) let c/]); +messages.push({ method: 'exit' }); +let out; +if (process.argv[2] === '--wasm' || process.argv[2] === '--vm') { + const wasm = process.argv[2] === '--wasm', host = require(path.resolve('editors/vscode/client', wasm ? 'wasmproc.js' : 'vmproc.js')); + const p = (wasm ? host.wasmProcess : host.vmProcess)(path.resolve(process.argv[3]), { log: () => {} }); + const chunks = []; p.stdout.on('data', d => chunks.push(d)); + for (const m of messages) p.stdin.write(frame({ jsonrpc: '2.0', ...m })); + out = Buffer.concat(chunks); +} else { + const r = cp.spawnSync(process.argv[2], process.argv.slice(3), { input: Buffer.concat(messages.map(m => frame({ jsonrpc: '2.0', ...m }))), timeout: 60000, maxBuffer: 32 << 20 }); + assert.strictEqual(r.status, 0, String(r.stderr)); out = r.stdout; +} +const replies = new Map(); +while (out.length) { + const e = out.indexOf('\r\n\r\n'), n = Number(/Content-Length:\s*(\d+)/i.exec(out.subarray(0, e).toString())[1]); + const m = JSON.parse(out.subarray(e + 4, e + 4 + n)); replies.set(m.id, m); out = out.subarray(e + 4 + n); +} +for (const [rid, ...patterns] of checks) { + const value = replies.get(rid)?.result?.contents?.value || ''; + for (const pattern of patterns) assert.match(value, pattern, `hover #${rid}: ${value}`); +} +assert.ok(!replies.get(checks[2][0]).result.contents.value.includes('closures being compiled'), 'the section overview is not NCLO documentation'); +assert.ok(!replies.get(checks[checks.length - 1][0]).result.contents.value.includes('opcode'), 'ordinary c is a variable'); +console.log('ok embedded declaration hovers, constant values, asm instructions and explicit std'); diff --git a/tests/drivers/hover_values_test.js b/tests/drivers/hover_values_test.js new file mode 100644 index 0000000..badc2ed --- /dev/null +++ b/tests/drivers/hover_values_test.js @@ -0,0 +1,68 @@ +// value previews follow the compiler's bindings and the position of each use. +const assert = require('assert'); +const cp = require('child_process'); +const server = process.argv[2]; +function hover(text, needle, nth = 0) { + let at = -1; + for (let n = 0; n <= nth; n++) at = text.indexOf(needle, at + 1); + assert.ok(at >= 0, needle); + const before = text.slice(0, at), uri = 'file:///hover-values.zc'; + const messages = [ + { jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }, + { jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, text } } }, + { jsonrpc: '2.0', id: 2, method: 'textDocument/hover', params: { textDocument: { uri }, position: { line: before.split('\n').length - 1, character: before.length - before.lastIndexOf('\n') - 1 } } }, + { jsonrpc: '2.0', method: 'exit' } + ]; + const input = Buffer.concat(messages.map(m => { + const body = Buffer.from(JSON.stringify(m)); + return Buffer.concat([Buffer.from('Content-Length: ' + body.length + '\r\n\r\n'), body]); + })); + const result = cp.spawnSync(server, process.argv.slice(3), { input, timeout: 20000, maxBuffer: 8 << 20 }); + assert.strictEqual(result.status, 0, String(result.stderr)); + let data = result.stdout; + while (data.length) { + const end = data.indexOf('\r\n\r\n'); + const n = Number(/Content-Length:\s*(\d+)/i.exec(data.subarray(0, end).toString())[1]); + const reply = JSON.parse(data.subarray(end + 4, end + 4 + n)); + data = data.subarray(end + 4 + n); + if (reply.id === 2) return reply.result?.contents?.value || ''; + } + assert.fail('missing hover response'); +} +function value(text, needle, expected, nth = 0) { + const md = hover(text, needle, nth); + if (expected === null) assert.ok(!md.includes('value at this position:'), md); + else assert.ok(md.includes('value at this position:\n```zc\n' + expected + '\n```'), md); +} +const flow = 'fn unknown() { return 9; }\nfn main() { let n = 42; put_n(n); n = 7; put_n(n); n = unknown(); return n; }'; +value(flow, 'n = 42', '42'); +value(flow, 'n);', '42'); +value(flow, 'n);', '7', 1); +value(flow, 'n;', null); +value('const answer = 6 * 7; fn main() { return answer; }', 'answer;', '42'); +value('const answer = 42; fn main() { return answer; }', 'answer =', '42'); +value('enum color { red, green = 5 }; fn main() { color shade = green; return shade; }', 'shade;', '5'); +value('fn main() { let n = -9223372036854775808; return n; }', 'n;', '-9223372036854775808'); +value('fn main() { u8 n = 300; return n; }', 'n;', '44'); +value('fn main() { i8 n = 255; return n; }', 'n;', '-1'); +value('fn main() { let n = 3.25; return n; }', 'n;', '3.25'); +value('fn main() { float n = 2; return n; }', 'n;', '2'); +value('fn main() { int n = 3.75; return n; }', 'n;', '3'); +value('fn main() { f32 n = 0.1; return n; }', 'n;', '0.10000000149011612'); +value('fn main() { let n = -0.0; return n; }', 'n;', '0'); +value('fn main() { let text = "hello\\nworld"; return text; }', 'text;', '"hello\\nworld"'); +value('fn main() { let text = b"hello"; return text; }', 'text;', 'b"hello"'); +value('fn main() { let n = 7; let copy = n; n = 8; return copy; }', 'copy;', '7'); +value('fn main() { let n = 1; { let n = 2; put_n(n); } return n; }', 'n);', '2'); +value('fn main() { let n = 1; { let n = 2; put_n(n); } return n; }', 'n;', '1'); +value('fn change(int* p) { p[0] = 9; } fn main() { int n = 7; let p = &n; n = 8; change(p); return n; }', 'n;', null, 1); +value('fn main() { int n = 7; let p = &n; n = 8; p[0] = 9; return n; }', 'n;', null, 1); +value('fn main() { let text = "hey"; text[0] = 120; return text; }', 'text;', null); +value('fn main() { let n = 1; if (1) n = 2; return n; }', 'n;', null); +value('fn main() { let n = 1; while (0) n = 2; return n; }', 'n;', null); +value('fn main() { let n = 1; let ignored = 0 && (n = 2); return n; }', 'n;', null); +value('fn main() { let p = new(int, 3); return p; }', 'p;', null); +value('fn main() { let n = clz(0); return n; }', 'n;', null); +value('let global = 7; fn main() { return global; }', 'global;', null); +value('let global = 7; fn main() { global = 8; return global; }', 'global;', '8'); +console.log('ok position-aware integer, float, string, constant, and alias values; unknown effects invalidate previews'); diff --git a/tests/drivers/icon_test.py b/tests/drivers/icon_test.py new file mode 100644 index 0000000..560c622 --- /dev/null +++ b/tests/drivers/icon_test.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# execute isolated icon acceptance, rejection and regression fixtures. +from pathlib import Path +import argparse +import subprocess + +parser = argparse.ArgumentParser() +parser.add_argument('vm', nargs='?', default='bin/zy') +parser.add_argument('--case', default='') +parser.add_argument('--js', action='store_true') +parser.add_argument('--source', action='store_true', help='test the working library source instead of the embedded module') +args = parser.parse_args() +root = Path(__file__).resolve().parents[2] +cases = sorted((root / 'tests/icon').glob('*/*.zc')) +count = 0 +for case in cases: + if args.case and args.case not in str(case): + continue + expected = case.with_suffix('.expected').read_text() + command = ['node', str(root / 'src/js/tinyvm.js')] if args.js else [str(root / args.vm)] + program = case.read_text() + if args.source: + # keep module imports while suppressing its embedded source loader. + library = (root / 'src/lib/icon.zc').read_text() + setup = '\nnamespace;\ninclude run;\nimmediate fn icon_working_source() { run.icon_done = 1; run.next(); return 0; }\nicon_working_source\n' + program = library + setup + program + proc = subprocess.run(command, input=program, text=True, capture_output=True, cwd=root, timeout=30) + assert proc.returncode == 0 and proc.stdout == expected, f'{case.relative_to(root)}\nstdout: {proc.stdout}\nstderr: {proc.stderr}\nexpected: {expected}' + print(f'ok {case.relative_to(root)}') + count += 1 +assert count, 'no icon fixtures selected' +print(f'{count} icon fixtures passed') diff --git a/tests/drivers/include_test.js b/tests/drivers/include_test.js new file mode 100644 index 0000000..6ba4e5f --- /dev/null +++ b/tests/drivers/include_test.js @@ -0,0 +1,94 @@ +// include: a path is found next to the file that has it, then in the workspace folder, then as written; the +// error for one that cannot be read is on its string; include std; needs no file; a server with no files of its own +// is sent the text of each (zc/file). usage: node tests/drivers/include_test.js path/to/zclsp [path/to/lsp.wasm] +const assert = require('assert'); +const cp = require('child_process'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const server = path.resolve(process.argv[2]); +const wasm = process.argv[3]; +const frame = m => { const b = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from('Content-Length: ' + b.length + '\r\n\r\n'), b]); }; +function parse(out) { + const res = []; + let d = out; + while (d.length) { + const e = d.indexOf('\r\n\r\n'), n = Number(/Content-Length:\s*(\d+)/i.exec(d.subarray(0, e).toString())[1]); + res.push(JSON.parse(d.subarray(e + 4, e + 4 + n))); d = d.subarray(e + 4 + n); + } + return res; +} +// runs one session on the native server (cwd is where includes are read as written) or the wasm host +function check(uri, text, { files = [], root = null, cwd = os.tmpdir(), host = 'native', navigation = false } = {}) { + const msgs = [{ jsonrpc: '2.0', id: 1, method: 'initialize', params: { rootUri: root } }, + ...files.map(([p, t]) => ({ jsonrpc: '2.0', method: 'zc/file', params: { path: p, text: t } })), + { jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, languageId: 'zc', text } } }, + ...(navigation ? [2, 3].map(id => ({ jsonrpc: '2.0', id, method: 'textDocument/definition', params: { textDocument: { uri }, position: { line: 2, character: 20 } } })) : []), + { jsonrpc: '2.0', method: 'exit' }]; + let out; + if (host === 'native') out = cp.spawnSync(server, [], { input: Buffer.concat(msgs.map(frame)), cwd, maxBuffer: 1 << 26 }).stdout; + else { + const { wasmProcess } = require(path.join(__dirname, '..', '..', 'editors', 'vscode', 'client', 'wasmproc.js')); + const p = wasmProcess(path.resolve(wasm), { log: () => {} }); + const chunks = []; + p.stdout.on('data', d => chunks.push(d)); + for (const m of msgs) p.stdin.write(frame(m)); + out = Buffer.concat(chunks); + } + const replies = parse(out); + if (navigation) { + for (const id of [2, 3]) assert.deepStrictEqual(replies.find(m => m.id === id).result, [{ uri: 'file:///nowhere/proj/lib/virt.zc', range: { start: { line: 0, character: 4 }, end: { line: 0, character: 10 } } }], host + ': navigation uses client-provided includes repeatedly'); + } + const note = replies.filter(m => m.method === 'textDocument/publishDiagnostics').pop(); + return note ? note.params.diagnostics : null; +} +const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'zcinc-')); +const w = (f, t) => { fs.mkdirSync(path.dirname(path.join(dir, f)), { recursive: true }); fs.writeFileSync(path.join(dir, f), t); }; +w('src/lib/util.zc', 'int twice(int v) { return v * 2; }\n'); +w('src/lib/deep.zc', 'include "util.zc";\nint quad(int v) { return twice(twice(v)); }\n'); +w('src/tools/main.zc', 'include "../lib/deep.zc";\nfn main() { return quad(1); }\n'); +w('top.zc', 'int fromtop() { return 7; }\n'); +const uri = f => 'file://' + path.join(dir, f); +const read = f => fs.readFileSync(path.join(dir, f), 'utf8'); +const errors = d => d.filter(x => x.severity === 1); +// next to the file, and nested includes next to the file that has them (the cwd is not the document's directory) +assert.deepStrictEqual(errors(check(uri('src/tools/main.zc'), read('src/tools/main.zc'))), [], 'a relative include, nested'); +// the original build.zc reproduction, checked from an unrelated working directory +const buildFile = path.resolve(__dirname, '../../src/tools/build.zc'); +const buildText = fs.readFileSync(buildFile, 'utf8').replace('include std;', 'include "../lib/std.zc";'); +assert.deepStrictEqual(errors(check('file://' + buildFile, buildText)), [], 'build.zc finds ../lib/std.zc'); +// the workspace folder +assert.deepStrictEqual(errors(check(uri('src/tools/a.zc'), 'include "top.zc";\nfn main() { return fromtop(); }\n', { root: 'file://' + dir })), [], 'found in the workspace folder'); +// as written, from the working directory +assert.deepStrictEqual(errors(check(uri('src/tools/b.zc'), 'include "top.zc";\nfn main() { return fromtop(); }\n', { cwd: dir })), [], 'found from the working directory'); +// an include that is not there: the error is on its string +let d = check(uri('src/tools/c.zc'), 'include "../lib/nothere.zc";\nfn main() { return 0; }\n'); +assert.strictEqual(errors(d).length, 1); +assert.deepStrictEqual(errors(d)[0].range, { start: { line: 0, character: 8 }, end: { line: 0, character: 27 } }, 'the error is on the string: ' + JSON.stringify(errors(d)[0].range)); +assert.match(errors(d)[0].message, /can't read that include/); +// include std; and the same twice +assert.deepStrictEqual(errors(check(uri('src/tools/d.zc'), 'include std;\ninclude std;\nfn main() { return std.Text.atoi("4"); }\n')), [], 'include std;'); +d = check(uri('src/tools/e.zc'), 'include std\nfn main() { return 0; }\n'); +assert.ok(errors(d).length >= 1, 'include std without a semicolon is an error'); +d = check(uri('src/tools/f.zc'), 'include other;\nfn main() { return 0; }\n'); +assert.match(errors(d)[0].message, /can't read that module include/); +assert.deepStrictEqual(errors(d)[0].range.start, { line: 0, character: 8 }, 'an unknown library is reported at its name'); +// findings inside an included file: hints are left out, an error is shown on the include with where it is +w('src/lib/loud.zc', 'int a() { int unused = 1; return 2 * 2; }\nint b() { return 1 }\n'); +d = check(uri('src/tools/g.zc'), 'include "../lib/loud.zc";\nfn main() { return a(); }\n'); +assert.strictEqual(d.filter(x => x.severity !== 1).length, 0, 'no hints from a library: ' + JSON.stringify(d)); +assert.strictEqual(errors(d).length, 1); +assert.match(errors(d)[0].message, /^in .*loud\.zc, line 2: .*expected ';'/); +assert.deepStrictEqual(errors(d)[0].range.start, { line: 0, character: 8 }); +// a server that cannot read files is sent them +const virtual = '/nowhere/proj/lib/virt.zc'; +const vdoc = 'file:///nowhere/proj/app/main.zc'; +const vtext = 'include "../lib/virt.zc";\ninclude "../lib/gone.zc";\nfn main() { return thrice(2); }\n'; +const files = [[virtual, 'int thrice(int v) { return v * 3; }\n']]; +for (const host of wasm ? ['native', 'wasm'] : ['native']) { + d = check(vdoc, vtext, { files, host, navigation: true }); + assert.strictEqual(errors(d).length, 1, host + ': ' + JSON.stringify(d)); + assert.deepStrictEqual(errors(d)[0].range.start, { line: 1, character: 8 }, host + ': only the missing file is an error'); +} +fs.rmSync(dir, { recursive: true }); +console.log('ok includes: found next to the file, in the workspace and as written; errors on the string; include std; files sent by the client'); diff --git a/tests/drivers/lsp_longevity_test.js b/tests/drivers/lsp_longevity_test.js new file mode 100644 index 0000000..5d450ad --- /dev/null +++ b/tests/drivers/lsp_longevity_test.js @@ -0,0 +1,44 @@ +// repeated document changes must not accumulate unbounded live analysis storage. +const assert = require('assert'); +const cp = require('child_process'); +const host = cp.spawn(process.argv[2], process.argv.slice(3), { stdio: ['pipe', 'pipe', 'pipe'] }); +const cycles = Number(process.env.LSP_CYCLES || 256), uri = 'file:///longevity.zc'; +let buffer = Buffer.alloc(0), errors = '', next = 1; +const pending = new Map(); +const timeout = setTimeout(() => { host.kill(); console.error('FAIL lsp longevity timed out'); process.exitCode = 1; }, 60000); +function send(m) { const b = Buffer.from(JSON.stringify({ jsonrpc: '2.0', ...m })); host.stdin.write(Buffer.concat([Buffer.from(`Content-Length: ${b.length}\r\n\r\n`), b])); } +function request(method, params) { const id = next++; return new Promise((resolve, reject) => { pending.set(id, { resolve, reject }); send({ id, method, params }); }); } +host.stderr.on('data', b => { errors += b; }); +host.stdout.on('data', b => { + buffer = Buffer.concat([buffer, b]); + while (true) { + const e = buffer.indexOf('\r\n\r\n'); if (e < 0) break; + const n = Number(/Content-Length:\s*(\d+)/i.exec(buffer.subarray(0, e).toString())[1]); if (buffer.length < e + 4 + n) break; + const m = JSON.parse(buffer.subarray(e + 4, e + 4 + n)); buffer = buffer.subarray(e + 4 + n); + if (pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.reject(new Error(JSON.stringify(m.error))) : p.resolve(m.result); } + if (m.method === 'textDocument/publishDiagnostics') assert.ok(!m.params.diagnostics.some(d => d.severity === 1), JSON.stringify(m.params)); + } +}); +host.on('error', e => { for (const p of pending.values()) p.reject(e); }); +function rss() { const r = cp.spawnSync('ps', ['-o', 'rss=', '-p', String(host.pid)], { encoding: 'utf8' }); assert.strictEqual(r.status, 0); return Number(r.stdout.trim()); } +(async () => { + try { + await request('initialize', {}); + const text = i => `fn square(int n) { return n * n; } fn main() { let answer = square(${i % 16}); return answer; }`; + send({ method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, text: text(0) } } }); + let baseline; + for (let i = 0; i < cycles; i++) { + if (i) send({ method: 'textDocument/didChange', params: { textDocument: { uri, version: i + 1 }, contentChanges: [{ text: text(i) }] } }); + const tokens = await request('textDocument/semanticTokens/full', { textDocument: { uri } }); + assert.ok(tokens.data.length); + if (i === 31 || (cycles <= 32 && i === 0)) baseline = rss(); + if (i % 32 === 0) { + const r = await request('textDocument/definition', { textDocument: { uri }, position: { line: 0, character: text(i).lastIndexOf('answer') } }); assert.ok(r?.length); + } + } + const end = rss(); assert.ok(end - baseline < 96 * 1024, `rss grew by ${end - baseline} KiB`); + send({ method: 'textDocument/didClose', params: { textDocument: { uri } } }); + await request('shutdown', null); send({ method: 'exit' }); host.stdin.end(); + console.log(`ok ${cycles} editor changes with tokens/navigation; post-warmup rss delta ${end - baseline} KiB`); + } finally { clearTimeout(timeout); host.kill(); } +})().catch(e => { console.error(e); console.error(errors); process.exitCode = 1; }); diff --git a/tests/drivers/lsp_test.js b/tests/drivers/lsp_test.js index a783e1e..ab43c41 100644 --- a/tests/drivers/lsp_test.js +++ b/tests/drivers/lsp_test.js @@ -51,7 +51,7 @@ check(nf && nf.error && nf.error.code === -32601, 'unknown request gets MethodNo check(r.msgs.length === 2, 'an unknown notification is ignored'); // a clean program has no diagnostics -const good = 'fn main() {\n\tput_n(1 + 2);\n\treturn 0;\n}\n'; +const good = 'fn main() {\n\treturn 1 + 2;\n}\n'; r = run([init(1), open(A, good)]); let d = diags(r.msgs, A); check(d.length === 1 && d[0].params.diagnostics.length === 0, 'a clean program publishes an empty list', d); @@ -64,7 +64,7 @@ const e1 = d.length && d[0].params.diagnostics[0]; check(e1 && e1.severity === 1 && e1.range.start.line === 1 && /expected ';', found 'return'/.test(e1.message), 'a missing semicolon is reported on its line', e1); // utf-16 columns: an emoji (two code units) and an accented letter before the error -const uni = '// café 😀\nfn main() { put_s("é😀"); return undefined_name; }\n'; +const uni = '// café 😀\nfn main() { let text = "é😀"; return text[0] + undefined_name; }\n'; r = run([init(1), open(A, uni)]); d = diags(r.msgs, A); const e2 = d.length && d[0].params.diagnostics[0]; @@ -82,7 +82,7 @@ const da = diags(r.msgs, A), db = diags(r.msgs, B2); check(da.length === 2 && JSON.stringify(da[0].params.diagnostics) === JSON.stringify(da[1].params.diagnostics) && db[0].params.diagnostics.length === 0, 'documents are checked in isolation'); // syntax modifications made by the program itself apply to the check, like the compiler -const pipeDef = 'immediate fn pipe_op() { next(); add_binop("|>", 1, "c"); return 0; }\npipe_op\n'; +const pipeDef = 'include run; immediate fn pipe_op() { run.next(); run.add_binop("|>", 1, "c"); return 0; }\npipe_op\n'; const pipeUse = 'fn double(x) { return x * 2; }\nfn main() { return 4 |> double; }\n'; r = run([init(1), open(A, pipeDef + pipeUse), open(B2, pipeUse)]); check(diags(r.msgs, A)[0].params.diagnostics.filter(x => x.severity === 1).length === 0, 'a parse word that adds an operator is understood'); @@ -108,11 +108,11 @@ function tokens(text) { } return out; } -const meth = 'struct point {\n\tlet x, y;\n\tfn sum() { return this.x + this.y; }\n}\nfn main() {\n\tpoint* p = new(point);\n\treturn p.sum();\n}\n'; +const meth = 'struct point {\n\tlet x, y;\n\tfn sum() { return this.x + this.y; }\n}\nfn main() {\n\tpoint p;\n\treturn p.sum();\n}\n'; const tk = tokens(meth); check(tk.filter(t => t === 'sum:function').length === 2, 'a method is a function where it is declared and where it is called', tk); check(tk.includes('this:parameter') && tk.includes('x:property'), 'this is a parameter and a field is a property', tk); -check(tk.filter(t => t === 'point:type').length >= 3, 'a user type is a type, also before a postfix star', tk); +check(tk.filter(t => t === 'point:type').length >= 2, 'a user type is a type, also before a postfix star', tk); // a large document goes through the streaming reader let big = ''; @@ -120,7 +120,8 @@ for (let i = 0; i < 2500; i++) big += 'fn f' + i + '(a, b) { return a * b + ' + big += 'fn main() { return f0(1, 2); }\n'; r = run([init(1), open(A, big)]); d = diags(r.msgs, A); -check(d.length === 1 && d[0].params.diagnostics.filter(x => x.severity === 1).length === 0, 'a document of ' + big.length + ' bytes is checked', d.length); +// a large check streams what it has found, so there may be several lists; the last is the whole answer +check(d.length >= 1 && d[d.length - 1].params.diagnostics.filter(x => x.severity === 1).length === 0, 'a document of ' + big.length + ' bytes is checked', d.length); // local tables grow past their initial reservation, and the next document still checks let many = 'fn main() {\n'; @@ -141,54 +142,58 @@ const hsrc = '// adds two numbers\nint add(int a, int b) { return a + b; }\nint let hv = hover(hsrc, 4, at(hsrc, 4, 'add')); check(hv && hv.includes('int add(int a, int b)') && hv.includes('adds two numbers'), 'hover on a call shows the signature and the doc', hv); check(hv && /^```zc\n/.test(hv) && /\n```\n\nadds two numbers/.test(hv), 'hover separates the code fence from Markdown documentation', hv); -const unicodeHover = 'fn main() { put_s("é😀"); put_c(10); return 0; }\n'; -hv = hover(unicodeHover, 0, at(unicodeHover, 0, 'put_c')); -check(hv && hv.includes('put_c('), 'hover converts UTF-16 columns after BMP and astral characters', hv); +const unicodeHover = 'fn main() { let text = "é😀"; int value = 1; return value; }\n'; +hv = hover(unicodeHover, 0, at(unicodeHover, 0, 'value')); +check(hv && hv.includes('(int) let value'), 'hover converts UTF-16 columns after BMP and astral characters', hv); hv = hover(hsrc, 0, hsrc.split('\n')[0].length + 10); check(hv === null, 'an out-of-range hover column cannot select a name on the following line', hv); hv = hover(hsrc, 6, at(hsrc, 6, 'twice')); check(hv && hv.includes('int twice(int a)') && !hv.includes('adds two numbers'), 'a function with no doc still shows its signature', hv); hv = hover(hsrc, 1, at(hsrc, 1, 'a', 2)); -check(hv && hv.includes('parameter') && hv.includes('int a'), 'hover on a parameter shows its declaration', hv); +check(hv && hv.includes('(int) let a // parameter;'), 'hover on a parameter shows its declaration', hv); hv = hover(hsrc, 5, at(hsrc, 5, 'i')); check(hv && hv.includes('let i') && hv.includes('local'), 'hover on a local shows its declaration', hv); hv = hover(hsrc, 6, at(hsrc, 6, 'i', 1)); check(hv && hv.includes('let i') && hv.includes('local'), 'hover on a use of a local shows its declaration', hv); -const tsrc = 'fn main() {\n\tlet i = 3;\n\tlet p = new(int);\n\treturn i;\n}\n'; +const tsrc = 'fn main() {\n\tlet i = 3;\n\tint* p;\n\treturn i;\n}\n'; // an untyped parameter and an untyped function show the types annotate gives them const isrc = 'fn twice(x) { return x * 2; }\nfn main() {\n\tlet n = twice(4);\n\treturn n;\n}\n'; let hi = hover(isrc, 0, 9); -check(hi && hi.includes('x // parameter; type int'), 'an untyped parameter shows its inferred type', hi); +check(hi && hi.includes('(int) let x // parameter;'), 'an untyped parameter shows its inferred type', hi); hi = hover(isrc, 0, 3); check(hi && hi.includes('int twice(int x)'), 'an untyped function shows its inferred parameter and return types', hi); hv = hover(tsrc, 1, 5); -check(hv && hv.includes('type int'), 'an untyped number local shows the type annotate gives it', hv); -hv = hover(tsrc, 2, 5); -check(hv && hv.includes('type int*'), 'a pointer local shows its pointer type on its declaration line', hv); +check(hv && hv.includes('(int) let i'), 'an untyped number local shows the type annotate gives it', hv); +hv = hover(tsrc, 2, 6); +check(hv && hv.includes('(int*) let p; // local'), 'a pointer local shows its pointer type on its declaration line', hv); hv = hover('fn main() {\n\tlet i = 3;\n\treturn i;\n}\n', 2, 8); check(hv && hv.includes('let i'), 'a return of a local is a use, not a declaration', hv); hv = hover(hsrc, 6, at(hsrc, 6, 'return')); check(hv && hv.includes('leaves the function with a value'), 'a keyword shows what it does', hv); -hv = hover('fn main() {\n\tput_c(32);\n\treturn 0;\n}\n', 1, 1); +const builtinSource = 'include rt; fn main() {\n\trt.put_c(32);\n\treturn 0;\n}\n'; +hv = hover(builtinSource, 1, at(builtinSource, 1, 'put_c')); check(hv && hv.includes('put_c('), 'a built-in function shows its signature', hv); -const kwsrc = 'fn main() {\n\tint* p = new(int);\n\tfree(p);\n\treturn 0;\n}\n'; +const kwsrc = 'include rt; fn main() {\n\tint* p = new(int);\n\trt.free(p);\n\treturn 0;\n}\n'; hv = hover(kwsrc, 1, at(kwsrc, 1, 'new')); -check(hv && hv.includes('new(T)') && hv.includes('fresh object'), 'hover on new shows its form and what it does', hv); +check(hv && hv.includes('new(T)') && hv.includes('fresh heap object') && hv.includes('owns it'), 'hover on new shows its form and ownership behavior', hv); hv = hover(kwsrc, 2, at(kwsrc, 2, 'free')); -check(hv && hv.includes('frees a heap object'), 'hover on free shows its doc', hv); +check(hv && hv.includes('releases a heap object') && hv.includes('consumes an owned object automatically'), 'hover on free shows inferred consumption', hv); const kdoc = '// the number of lines\nfn main() {\n\treturn 0;\n}\n'; hv = hover(kdoc, 2, 1); check(hv === null || !hv.includes('number of lines'), 'a doc comment is not attached to a keyword in a body', hv); // a method of a struct is indented in the body, and its hover is still its line and doc -const msrc = 'struct point {\n\tlet x;\n\t// the sum of the parts\n\tfn sum() { return this.x; }\n\tstatic fn zero() { return 0; }\n}\nfn main() {\n\tpoint* p = new(point);\n\treturn p.sum() + point.zero();\n}\n'; +const msrc = 'struct point {\n\tlet x;\n\t// the sum of the parts\n\tfn sum() { return this.x; }\n\tstatic fn zero() { return 0; }\n}\nfn main() {\n\tpoint p;\n\treturn p.sum() + point.zero();\n}\n'; hv = hover(msrc, 8, at(msrc, 8, 'sum')); check(hv && hv.includes('fn sum()') && hv.includes('the sum of the parts'), 'a method called through a pointer shows its signature and doc', hv); hv = hover(msrc, 8, at(msrc, 8, 'zero')); check(hv && hv.includes('static fn zero()'), 'a static function of a struct shows its signature', hv); +hv = hover(msrc, 4, at(msrc, 4, 'static')); +check(hv && hv.includes('static fn name') && hv.includes('hidden this'), 'static keyword hover explains the declaration form', hv); +check(tokens(msrc).includes('static:keyword'), 'static is highlighted as a keyword', tokens(msrc)); // a typed method shows its return type and parameters -const tmsrc = 'struct point {\n\tlet x;\n\t// the sum of the parts\n\tint sum(int k) { return this.x + k; }\n}\nfn main() {\n\tpoint* p = new(point);\n\treturn p.sum(1);\n}\n'; +const tmsrc = 'struct point {\n\tlet x;\n\t// the sum of the parts\n\tint sum(int k) { return this.x + k; }\n}\nfn main() {\n\tpoint p;\n\treturn p.sum(1);\n}\n'; hv = hover(tmsrc, 7, at(tmsrc, 7, 'sum')); check(hv && hv.includes('int sum(int k)') && hv.includes('the sum of the parts'), 'a typed method shows its return type and parameters', hv); @@ -219,7 +224,8 @@ check(dirty.length === 0, 'every example and test program checks clean (' + clea let missed = [], n = 0; for (const dir of ['errors', 'typeerrors', 'langerrors']) { for (const f of fs.readdirSync('tests/' + dir).filter(x => x.endsWith('.zc'))) { - const want = fs.readFileSync('tests/' + dir + '/' + f, 'utf8').split(/\r?\n/)[0].replace('// expect: ', ''); + const wantLine = fs.readFileSync('tests/' + dir + '/' + f, 'utf8').split(/\r?\n/).find(line => line.startsWith('// expect: ')); + const want = wantLine ? wantLine.slice('// expect: '.length) : ''; const out = cli('tests/' + dir + '/' + f); n++; if (!out.includes(want)) missed.push(dir + '/' + f + ' wanted [' + want + '] got [' + out.split('\n')[0] + ']'); @@ -236,7 +242,7 @@ const sct = tokens(scp); check(sct.includes('cap:parameter') && sct.includes('cap:variable') && !sct.includes('cap:keyword') && !sct.includes('cap:function'), 'a name like a built-in is a variable or parameter where the scope says', sct); check(sct.includes('len:keyword'), 'a built-in that is a keyword in the compiler is still a keyword', sct); let hcv = hover(scp, 4, at(scp, 4, 'cap')); -check(hcv && hcv.includes('int cap') && hcv.includes('local'), 'hover on a use of a local named like a built-in shows the local', hcv); +check(hcv && hcv.includes('(int) let cap') && hcv.includes('local'), 'hover on a use of a local named like a built-in shows the local', hcv); hcv = hover(scp, 1, at(scp, 1, 'cap', 1)); check(hcv && hcv.includes('parameter'), 'hover on a use of a parameter named like a built-in shows the parameter', hcv); hcv = hover(scp, 4, at(scp, 4, 'len')); diff --git a/tests/drivers/lsp_vm.js b/tests/drivers/lsp_vm.js index 24c1a5b..e1f5d7a 100644 --- a/tests/drivers/lsp_vm.js +++ b/tests/drivers/lsp_vm.js @@ -79,6 +79,20 @@ const diagsOf = (s, uri) => s.notes.filter(m => m.method === 'textDocument/publi const h2 = s2.replies[6]; check(h2 && h2.result && /packed documentation/.test(h2.result.contents.value), 'hover shows the docs of a compiler name', h2); } +// definition navigation returns the embedded runtime source through the same vm host. +{ + const src = 'include rt; fn main() { return rt.s_to_n(b"42"); }\n'; + const s = session([['file:///nav.zc', src]]); + const at = src.indexOf('s_to_n'), before = src.slice(0, at); + s.send({ jsonrpc: '2.0', id: 7, method: 'textDocument/definition', params: { textDocument: { uri: 'file:///nav.zc' }, position: { line: before.split('\n').length - 1, character: before.length - before.lastIndexOf('\n') - 1 } } }); + s.send({ jsonrpc: '2.0', id: 8, method: 'zc/source', params: { path: '/rt.zc' } }); + s.send({ jsonrpc: '2.0', method: 'exit' }); + const loc = s.replies[7] && s.replies[7].result && s.replies[7].result[0]; + check(loc && loc.uri === 'tinyvm:/rt.zc', 's_to_n opens the embedded runtime source', loc); + const source = s.replies[8] && s.replies[8].result; + const line = source && loc && source.split('\n')[loc.range.start.line]; + check(line && line.slice(loc.range.start.character, loc.range.end.character) === 's_to_n' && source === fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'runtime', 'rt.zc'), 'utf8'), 'the virtual runtime source matches its declaration'); +} // a document that is bigger than one feed: the input is split and comes out whole { let big = 'int main() {\n'; diff --git a/tests/drivers/lsp_wasm.js b/tests/drivers/lsp_wasm.js index 4ff2d16..e49bcc5 100644 --- a/tests/drivers/lsp_wasm.js +++ b/tests/drivers/lsp_wasm.js @@ -79,6 +79,20 @@ const diagsOf = (s, uri) => s.notes.filter(m => m.method === 'textDocument/publi const h2 = s2.replies[6]; check(h2 && h2.result && /packed documentation/.test(h2.result.contents.value), 'hover shows the docs of a compiler name', h2); } +// definition navigation returns the embedded runtime source through the wasm host. +{ + const src = 'include rt; fn main() { return rt.s_to_n(b"42"); }\n'; + const s = session([['file:///nav.zc', src]]); + const at = src.indexOf('s_to_n'), before = src.slice(0, at); + s.send({ jsonrpc: '2.0', id: 7, method: 'textDocument/definition', params: { textDocument: { uri: 'file:///nav.zc' }, position: { line: before.split('\n').length - 1, character: before.length - before.lastIndexOf('\n') - 1 } } }); + s.send({ jsonrpc: '2.0', id: 8, method: 'zc/source', params: { path: '/rt.zc' } }); + s.send({ jsonrpc: '2.0', method: 'exit' }); + const loc = s.replies[7] && s.replies[7].result && s.replies[7].result[0]; + check(loc && loc.uri === 'tinyvm:/rt.zc', 's_to_n opens the embedded runtime source', loc); + const source = s.replies[8] && s.replies[8].result; + const line = source && loc && source.split('\n')[loc.range.start.line]; + check(line && line.slice(loc.range.start.character, loc.range.end.character) === 's_to_n' && source === fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'runtime', 'rt.zc'), 'utf8'), 'the virtual runtime source matches its declaration'); +} // a document that is bigger than one feed: the input is split and comes out whole { let big = 'int main() {\n'; diff --git a/tests/drivers/nav-library.zc b/tests/drivers/nav-library.zc new file mode 100644 index 0000000..55943d0 --- /dev/null +++ b/tests/drivers/nav-library.zc @@ -0,0 +1,2 @@ +struct NavValue { int value; }; +int nav_helper(int x) { return x + 1; } diff --git a/tests/drivers/nav_host_test.js b/tests/drivers/nav_host_test.js new file mode 100644 index 0000000..3ffb563 --- /dev/null +++ b/tests/drivers/nav_host_test.js @@ -0,0 +1,52 @@ +// verify embedded built-in navigation through the extension's vm and wasm hosts. +// usage: node tests/drivers/nav_host_test.js vm|wasm path/to/server +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const mode = process.argv[2]; +const server = path.resolve(process.argv[3]); +const client = path.resolve(__dirname, '..', '..', 'editors', 'vscode', 'client'); +const processHost = mode === 'vm' + ? require(path.join(client, 'vmproc.js')).vmProcess + : mode === 'wasm' + ? require(path.join(client, 'wasmproc.js')).wasmProcess + : null; +assert.ok(processHost, 'host must be vm or wasm'); +const proc = processHost(server, { log: () => {} }); +const replies = new Map(); +let inbox = Buffer.alloc(0); +proc.stdout.on('data', chunk => { + inbox = Buffer.concat([inbox, chunk]); + while (true) { + const end = inbox.indexOf('\r\n\r\n'); + if (end < 0) return; + const size = Number(/Content-Length:\s*(\d+)/i.exec(inbox.subarray(0, end).toString())[1]); + if (inbox.length < end + 4 + size) return; + const message = JSON.parse(inbox.subarray(end + 4, end + 4 + size).toString()); + inbox = inbox.subarray(end + 4 + size); + if (message.id !== undefined) replies.set(message.id, message); + } +}); +function send(message) { + const body = Buffer.from(JSON.stringify({ jsonrpc: '2.0', ...message })); + proc.stdin.write(Buffer.concat([Buffer.from(`Content-Length: ${body.length}\r\n\r\n`), body])); +} +const source = 'include rt; fn main() { return rt.s_to_n(b"42"); }\n'; +const needle = 's_to_n'; +const offset = source.indexOf(needle); +const before = source.slice(0, offset); +const position = { line: before.split('\n').length - 1, character: before.length - before.lastIndexOf('\n') - 1 }; +send({ id: 1, method: 'initialize', params: { processId: 1, capabilities: {} } }); +send({ method: 'initialized', params: {} }); +send({ method: 'textDocument/didOpen', params: { textDocument: { uri: 'file:///nav.zc', languageId: 'zc', version: 1, text: source } } }); +send({ id: 2, method: 'textDocument/definition', params: { textDocument: { uri: 'file:///nav.zc' }, position } }); +send({ id: 3, method: 'zc/source', params: { path: '/rt.zc' } }); +send({ method: 'exit' }); +const location = replies.get(2)?.result?.[0]; +assert.ok(location, 's_to_n has a definition'); +assert.strictEqual(location.uri, 'tinyvm:/rt.zc'); +const runtime = replies.get(3)?.result; +assert.strictEqual(runtime, fs.readFileSync(path.resolve(__dirname, '..', '..', 'src', 'runtime', 'rt.zc'), 'utf8')); +const line = runtime.split('\n')[location.range.start.line]; +assert.strictEqual(line.slice(location.range.start.character, location.range.end.character), needle); +console.log(`ok ${mode} host opens the embedded runtime source for s_to_n`); diff --git a/tests/drivers/nav_test.js b/tests/drivers/nav_test.js new file mode 100644 index 0000000..23afbf8 --- /dev/null +++ b/tests/drivers/nav_test.js @@ -0,0 +1,75 @@ +// go to definition and find references, in the document and across includes. +// usage: node tests/drivers/nav_test.js path/to/zclsp (run from the repository root) +const assert = require('assert'); +const cp = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const server = process.argv[2]; +const root = path.resolve(__dirname, '..', '..'); +const uri = 'file://' + path.join(root, 'tests', 'drivers', 'nav-fixture.zc'); +const frame = m => { const b = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from('Content-Length: ' + b.length + '\r\n\r\n'), b]); }; +function request(text, needle, method, extra = {}) { + const at = text.indexOf(needle); + assert.ok(at >= 0, needle); + const before = text.slice(0, at); + const position = { line: before.split('\n').length - 1, character: before.length - before.lastIndexOf('\n') - 1 }; + const input = Buffer.concat([ + { jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }, + { jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, languageId: 'zc', text } } }, + { jsonrpc: '2.0', id: 2, method, params: { textDocument: { uri }, position, ...extra } }, + { jsonrpc: '2.0', method: 'exit' }].map(frame)); + const r = cp.spawnSync(server, process.argv.slice(3), { input, cwd: root, timeout: 30000, maxBuffer: 8 << 20 }); + assert.strictEqual(r.status, 0, String(r.stderr)); + let d = r.stdout, init; + while (d.length) { + const e = d.indexOf('\r\n\r\n'), n = Number(/Content-Length:\s*(\d+)/i.exec(d.subarray(0, e).toString())[1]); + const m = JSON.parse(d.subarray(e + 4, e + 4 + n)); d = d.subarray(e + 4 + n); + if (m.id === 1) init = m.result.capabilities; + if (m.id === 2) return { result: m.result, init }; + } + assert.fail('no reply'); +} +const lines = r => (r || []).map(l => l.range.start.line + ':' + l.range.start.character).sort((a, b) => a.localeCompare(b, undefined, { numeric: true })); +const src = 'include "tests/drivers/nav-library.zc";\nstruct pt { int x; int y; };\nint twice(int v) { return v * 2; }\nfn main() {\n\tpt p;\n\tp.x = twice(2);\n\tlet n = p.x + twice(3);\n\tNavValue value;\n\treturn n + p.y + nav_helper(value.value);\n}\n'; +const def = (needle, nth) => request(src, needle, 'textDocument/definition').result; +assert.ok(request(src, 'twice(2', 'textDocument/definition').init.definitionProvider && request(src, 'twice(2', 'textDocument/definition').init.referencesProvider); +assert.deepStrictEqual(lines(def('twice(2')), ['2:4'], 'a function goes to its definition'); +assert.deepStrictEqual(lines(def('p.y')), ['4:4'], 'a local goes to its declaration'); +assert.deepStrictEqual(lines(def('pt p')), ['1:7'], 'a struct goes to its declaration'); +const inc = def('NavValue value'); +assert.ok(inc && inc.length === 1 && inc[0].uri === 'file://' + path.join(root, 'tests', 'drivers', 'nav-library.zc'), 'a type from an include goes to the included file: ' + JSON.stringify(inc)); +const includeText = fs.readFileSync(path.join(root, 'tests', 'drivers', 'nav-library.zc'), 'utf8').split('\n'); +assert.ok(/NavValue/.test(includeText[inc[0].range.start.line].slice(inc[0].range.start.character, inc[0].range.end.character)), 'the range covers the name'); +assert.strictEqual(request(src, 'return', 'textDocument/definition').result, null, 'a keyword has no definition'); +const refs = (needle, includeDeclaration) => request(src, needle, 'textDocument/references', { context: { includeDeclaration } }).result; +assert.deepStrictEqual(lines(refs('twice(2', true)), ['2:4', '5:7', '6:15'], 'references with the declaration'); +assert.deepStrictEqual(lines(refs('twice(2', false)), ['5:7', '6:15'], 'references without the declaration'); +assert.deepStrictEqual(lines(refs('n + p.y', true)), ['6:5', '8:8'], 'a local: declaration and uses'); +const shared = refs('nav_helper(value', true); +assert.ok(shared.some(loc => loc.uri === 'file://' + path.join(root, 'tests', 'drivers', 'nav-library.zc')), 'references include the declaration in another file'); +assert.deepStrictEqual(lines(refs('y; };', true)).length >= 1, true); +// built-ins: the image carries its sources. the runtime, the compiler and (for include std;) the standard library +const std = 'include std, rt;\nfn main() {\n\trt.put_s("hi");\n\tlet n = std.Text.atoi("4");\n\tlet m = rt.s_to_n(b"1");\n\treturn n + m + std.Host.now();\n}\n'; +const bdef = needle => request(std, needle, 'textDocument/definition').result; +const source = name => request(std, 'main', 'zc/source', { path: name }).result; +const slice = (text, loc) => { const l = text.split('\n')[loc.range.start.line]; return l.slice(loc.range.start.character, loc.range.end.character); }; +let loc = bdef('put_s'); +assert.ok(loc && loc.length === 1 && loc[0].uri === 'tinyvm:/rt.zc', 'a runtime function goes to the runtime source: ' + JSON.stringify(loc)); +assert.strictEqual(slice(source('/rt.zc'), loc[0]), 'put_s'); +assert.strictEqual(source('/rt.zc'), fs.readFileSync(path.join(root, 'src', 'runtime', 'rt.zc'), 'utf8'), 'the embedded runtime is the file'); +loc = bdef('Text.atoi'); +assert.ok(loc && loc[0].uri === 'tinyvm:/std.zc', 'a std type goes to the std source'); +assert.strictEqual(slice(source('std.zc'), loc[0]), 'Text'); +loc = bdef('atoi('); +assert.strictEqual(loc[0].uri, 'tinyvm:/std.zc'); +assert.strictEqual(slice(source('/std.zc'), loc[0]), 'atoi', 'a method goes to its own line'); +loc = bdef('now('); +assert.strictEqual(slice(source('/std.zc'), loc[0]), 'now'); +assert.strictEqual(source('/std.zc'), fs.readFileSync(path.join(root, 'src', 'lib', 'std.zc'), 'utf8'), 'the embedded std is the file'); +assert.strictEqual(source('/compiler.zc'), fs.readFileSync(path.join(root, 'src', 'compiler', 'compiler.zc'), 'utf8'), 'the embedded compiler is the file'); +assert.strictEqual(source('/server.zc'), fs.readFileSync(path.join(root, 'src', 'lsp', 'server.zc'), 'utf8'), 'the embedded server is the file'); +loc = bdef('s_to_n('); +assert.ok(loc && loc.length === 1 && loc[0].uri === 'tinyvm:/rt.zc', 's_to_n opens the runtime source: ' + JSON.stringify(loc)); +assert.strictEqual(slice(source('/rt.zc'), loc[0]), 's_to_n'); +assert.strictEqual(source('/nope.zc'), null); +console.log('ok definition and references across the document and includes'); diff --git a/tests/drivers/parser_growth_test.py b/tests/drivers/parser_growth_test.py index 4cbfbb2..9aee57e 100644 --- a/tests/drivers/parser_growth_test.py +++ b/tests/drivers/parser_growth_test.py @@ -22,6 +22,13 @@ source = 'struct methods { int value;\n' source += ''.join(f'int get_{i}() {{ return this.value + {i}; }}\n' for i in range(100)) source += '}\nfn main() { methods item; item.value = 7; gc(); put_n(item.get_99()); put_c(10); return 0; }\n' run(source, '106\n') +# grabfn owns a single token buffer while repeated growth moves replacements +# into its released slot; the replayed method must survive compilation and gc. +source = 'struct long_method { int seed; int count() { int value = 0;\n' +source += 'value += 1;\n' * 400 +source += 'return value + this.seed; } };\n' +source += 'fn main() { long_method item; item.seed = 7; gc(); put_n(item.count()); put_c(10); return 0; }\n' +run(source, '407\n') source = 'enum expanded {\n' source += ',\n'.join(f'member_with_long_generated_name_{i} {{ ' + ''.join(f'int field_with_long_generated_name_{j}; ' for j in range(8)) + '}' for i in range(40)) source += '\n};\nfn main() { gc(); put_n(ebcap > 8192); put_c(10); return 0; }\n' diff --git a/tests/drivers/runtime_image_test.py b/tests/drivers/runtime_image_test.py new file mode 100644 index 0000000..c1f4b9c --- /dev/null +++ b/tests/drivers/runtime_image_test.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""self-hosted run-only images retain enhancements and generic method bodies.""" +import argparse +import subprocess +import tempfile +from pathlib import Path + +parser = argparse.ArgumentParser() +parser.add_argument('--case', default='') +parser.add_argument('--host', default='bin/zy') +parser.add_argument('--mkgenome', default='bin/mkgenome') +args = parser.parse_args() +cases = [ + ('enhancements', 'include rt; fn main() { rt.put_n(17 % 5); rt.put_c(32); rt.put_n(17 / 5); }', b'2 3'), + ('array algorithms', Path('examples/learn/31-array-algorithms.zc').read_text(), Path('examples/learn/31-array-algorithms.out').read_bytes()), + ('owned pop', 'include rt; int*.u take() { int*[?] a = rt.new(int*[?]); int* p = rt.new(int); *p = 8; a.push(p); return a.pop(); } fn main() { let p = take(); rt.put_n(rt.isobj(p)); rt.put_c(32); rt.put_n(*p); }', b'1 8'), +] +prefix = b''.join(Path(f).read_bytes() + b'\n' for f in ['src/runtime/rt.zc', 'src/core/core.zc', 'src/runtime/run.zc']) +for name, source, expected in cases: + if args.case and args.case not in name: + continue + with tempfile.TemporaryDirectory(prefix='tinyvm-image-') as work: + directory = Path(work) + r = subprocess.run([args.host], input=Path('src/tools/build.zc').read_bytes() + b'`' + prefix + source.encode(), capture_output=True, timeout=30) + assert r.returncode == 0 and len(r.stdout) >= 56 and not r.stdout.startswith(b'zc:'), (name, r.stdout[:1000], r.stderr) + header = subprocess.run([args.mkgenome], input=r.stdout, capture_output=True, timeout=5) + assert header.returncode == 0, (name, header.stderr) + (directory / 'genome.h').write_bytes(header.stdout) + for f in ['src/core/memmap.h', 'src/core/fops.h', 'src/core/bops.h', 'src/core/stack.h', 'src/micro/micro.c', 'src/micro/micro_port.h', 'src/micro/port_linux.c']: + (directory / Path(f).name).write_bytes(Path(f).read_bytes()) + subprocess.run(['cc', '-O2', '-w', '-fwrapv', '-pthread', '-DZY_THREADS', str(directory / 'micro.c'), str(directory / 'port_linux.c'), '-I' + work, '-o', str(directory / 'image')], check=True, timeout=30) + r = subprocess.run([str(directory / 'image')], capture_output=True, timeout=10) + assert r.returncode == 0 and r.stdout == expected, (name, r.returncode, r.stdout, r.stderr, expected) + print('ok self-hosted run-only image: ' + name) diff --git a/tests/drivers/sourceprovider_test.js b/tests/drivers/sourceprovider_test.js new file mode 100644 index 0000000..f9551da --- /dev/null +++ b/tests/drivers/sourceprovider_test.js @@ -0,0 +1,31 @@ +// verify the extension's virtual source provider and its language-server request. +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const { createSourceProvider } = require('../../editors/vscode/client/sourceprovider.js'); + +async function main() { + const calls = []; + const runtime = fs.readFileSync(path.resolve(__dirname, '..', '..', 'src', 'runtime', 'rt.zc'), 'utf8'); + const provider = createSourceProvider(async (method, params) => { + calls.push({ method, params }); + return runtime; + }, () => true); + assert.strictEqual(await provider.provideTextDocumentContent({ path: '/rt.zc' }), runtime); + assert.deepStrictEqual(calls, [{ method: 'zc/source', params: { path: '/rt.zc' } }]); + + const missing = createSourceProvider(async () => null, () => true); + assert.strictEqual(await missing.provideTextDocumentContent({ path: '/missing.zc' }), '// no such built-in source\n'); + const failed = createSourceProvider(async () => { throw new Error('closed'); }, () => true); + assert.strictEqual(await failed.provideTextDocumentContent({ path: '/rt.zc' }), '// the language server could not give this source\n'); + + let readyChecks = 0, delays = 0; + const starting = createSourceProvider(async () => runtime, () => ++readyChecks > 2, async () => { delays++; }); + assert.strictEqual(await starting.provideTextDocumentContent({ path: '/rt.zc' }), runtime); + assert.strictEqual(delays, 2); + const stopped = createSourceProvider(async () => runtime, () => false, async () => {}); + assert.strictEqual(await stopped.provideTextDocumentContent({ path: '/rt.zc' }), '// the language server is not running\n'); + console.log('ok virtual source provider waits for the server and returns embedded source text'); +} + +main().catch(error => { console.error(error); process.exitCode = 1; }); diff --git a/tests/drivers/storage_analysis_test.js b/tests/drivers/storage_analysis_test.js index da967f5..774fa89 100644 --- a/tests/drivers/storage_analysis_test.js +++ b/tests/drivers/storage_analysis_test.js @@ -26,7 +26,7 @@ function check(text, needle) { return { diagnostics, warnings: diagnostics.filter(d => d.severity === 2), hover: replies.find(m => m.id === 2)?.result?.contents?.value }; } const prefix = 'unchecked fn main() {\n'; -for (const [type, length] of [['int', 3], ['u8', 3], ['fixed []int', 3], ['[]u8', 3]]) { +for (const [type, length] of [['int', 3], ['u8', 3], ['fixed int[?]', 3], ['u8[?]', 3]]) { const r = check(prefix + ` let p = new(${type}, ${length});\n let alias = p;\n return alias[${length}];\n}\n`, 'alias['); assert.strictEqual(r.warnings.length, 1, JSON.stringify(r.diagnostics)); assert.match(r.warnings[0].message, /out-of-bounds.*index 3.*length 3/); @@ -56,16 +56,21 @@ const safe = [ prefix + ' let p = new(int, 50); return p[300 as u8]; }', prefix + ' let p = new(int, 3); let q = p; q[2] = 7; free(p); return 0; }', prefix + ' let p = new(3); free(p); p = new(3); return p[0]; }', - 'fn unknown(int* p) {}\n' + prefix + ' let p = new(int, 3); unknown(p); return p[9]; }', + 'unchecked fn unknown(int* p) {}\n' + prefix + ' let p = new(int, 3); unknown(p); return p[9]; }', 'fn maybe(int n) { return n; }\n' + prefix + ' let p = new(int, 3); return p[maybe(9)]; }', prefix + ' let p = new(int, 3); if (1) free(p); return p[0]; }', prefix + ' let p = new(int, 3); while (0) free(p); return p[0]; }', prefix + ' let p = new(int, 3); let f = fn() { free(p); return 0; }; return p[0]; }', - prefix + ' let p = new([]int, 0); push(p, 9); return p[0]; }', + prefix + ' let p = new(int[?], 0); push(p, 9); return p[0]; }', ]; for (const source of safe) assert.deepStrictEqual(check(source).warnings, [], source); +const preserved = check('int read(int* p) { return p[0]; }\n' + prefix + ' let p = new(int, 3); read(p); return p[9]; }'); +assert.match(preserved.warnings[0]?.message || '', /index 9.*length 3/); const unknown = check('int read(int* p) { return p[0]; }\nfn main() { int n = 1; return read(&n); }\n', 'p['); assert.match(unknown.hover, /size: unknown.*lifetime: unknown/); const stack = check('fn main() { let a[3]; return a[2]; }\n', 'a['); assert.match(stack.hover, /size: 3 elements.*lifetime: stack storage/); +const replacement = check('fn main() {\n let owner = new(1), replacement = new(2);\n free(owner); owner = replacement;\n return owner[0];\n}\n', 'replacement;'); +assert.ok(!replacement.diagnostics.some(d => d.severity === 1), JSON.stringify(replacement.diagnostics)); +assert.match(replacement.hover, /moved: replaces a released owner in `owner` on line 3/); console.log('ok definite bounds, free and scope lifetimes, alias hovers, and conservative unknown cases'); diff --git a/tests/drivers/stream_host_test.js b/tests/drivers/stream_host_test.js new file mode 100644 index 0000000..99213e2 --- /dev/null +++ b/tests/drivers/stream_host_test.js @@ -0,0 +1,48 @@ +// the extension's two in-process servers (wasm and the javascript vm) stream partial diagnostics and drop a +// check when the next message is already written. a step of either host is synchronous, so the server +// suspends at its poll and the host lets the event loop deliver input before it goes on. +// usage: node tests/drivers/stream_host_test.js path/to/lsp.wasm path/to/vmserver-dir +const assert = require('assert'); +const path = require('path'); +const client = path.join(__dirname, '..', '..', 'editors', 'vscode', 'client'); +const { wasmProcess } = require(path.join(client, 'wasmproc.js')); +const { vmProcess } = require(path.join(client, 'vmproc.js')); +const frame = m => { const b = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from('Content-Length: ' + b.length + '\r\n\r\n'), b]); }; +const doc = (n, tail) => { let t = ''; for (let i = 0; i < n; i++) t += `int f${i}(int a) { return a + ${i}; }\n`; return t + tail; }; +function run(make, steps) { + return new Promise(resolve => { + const p = make(); + const got = []; + let inbox = Buffer.alloc(0); + p.stdout.on('data', d => { + inbox = Buffer.concat([inbox, d]); + for (;;) { + const e = inbox.indexOf('\r\n\r\n'); if (e < 0) return; + const n = parseInt(/Content-Length: (\d+)/.exec(inbox.slice(0, e).toString())[1], 10); + if (inbox.length < e + 4 + n) return; + got.push(JSON.parse(inbox.slice(e + 4, e + 4 + n).toString('utf8'))); + inbox = inbox.slice(e + 4 + n); + } + }); + for (const m of steps) p.stdin.write(frame(m)); + setTimeout(() => { p.stdin.write(frame({ jsonrpc: '2.0', method: 'exit' })); setTimeout(() => resolve(got), 200); }, 4000); + }); +} +const init = { jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }; +const open = (uri, version, text) => ({ jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version, languageId: 'zc', text } } }); +const change = (uri, version, text) => ({ jsonrpc: '2.0', method: 'textDocument/didChange', params: { textDocument: { uri, version }, contentChanges: [{ text }] } }); +const diags = (got, uri) => got.filter(m => m.method === 'textDocument/publishDiagnostics' && m.params.uri === uri); +(async () => { + const A = 'file:///host-stream.zc'; + const ok = doc(300, 'fn main() { return f0(1); }\n'); + const hosts = [['wasm', () => wasmProcess(path.resolve(process.argv[2]), { log: () => {} })], ['vm', () => vmProcess(path.resolve(process.argv[3]), { log: () => {} })]]; + for (const [name, make] of hosts) { + let got = await run(make, [init, open(A, 1, 'int broken( { }\n' + ok)]); + let d = diags(got, A); + assert.ok(d.length >= 2 && d[0].params.diagnostics.some(x => x.severity === 1), name + ': a large check streams: ' + d.length); + got = await run(make, [init, open(A, 1, ok), change(A, 2, 'fn main() { return 0; }\n')]); + d = diags(got, A); + assert.ok(d.length >= 1 && d.every(m => m.params.version === 2), name + ': the stale check is dropped: ' + JSON.stringify(d.map(m => m.params.version))); + console.log('ok ' + name + ' server streams and drops stale checks'); + } +})(); diff --git a/tests/drivers/stream_test.js b/tests/drivers/stream_test.js new file mode 100644 index 0000000..e783862 --- /dev/null +++ b/tests/drivers/stream_test.js @@ -0,0 +1,55 @@ +// large checks stream partial diagnostics, and a check is dropped when newer input is already waiting. +// usage: node tests/drivers/stream_test.js path/to/zclsp +const assert = require('assert'); +const cp = require('child_process'); +const server = process.argv[2]; +const frame = m => { const b = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from('Content-Length: ' + b.length + '\r\n\r\n'), b]); }; +const doc = (n, tail) => { let t = ''; for (let i = 0; i < n; i++) t += `int f${i}(int a) { return a + ${i}; }\n`; return t + tail; }; +function messages(out) { + const res = []; + let d = out; + while (d.length) { + const e = d.indexOf('\r\n\r\n'), n = Number(/Content-Length:\s*(\d+)/i.exec(d.subarray(0, e).toString())[1]); + res.push(JSON.parse(d.subarray(e + 4, e + 4 + n))); d = d.subarray(e + 4 + n); + } + return res; +} +const published = (msgs, uri) => msgs.filter(m => m.method === 'textDocument/publishDiagnostics' && m.params.uri === uri); +// spawn the server and write each step after the previous one has had time to run (delay in ms) +function session(steps) { + return new Promise(resolve => { + const p = cp.spawn(server, process.argv.slice(3)); + let out = Buffer.alloc(0); + p.stdout.on('data', c => { out = Buffer.concat([out, c]); }); + p.on('exit', () => resolve(messages(out))); + let at = 0; + const next = () => { + if (at >= steps.length) { p.stdin.write(frame({ jsonrpc: '2.0', method: 'exit' })); return; } + const [delay, msgs] = steps[at++]; + p.stdin.write(Buffer.concat(msgs.map(frame))); + setTimeout(next, delay); + }; + next(); + }); +} +const init = { jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }; +const open = (uri, version, text) => ({ jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version, languageId: 'zc', text } } }); +const change = (uri, version, text) => ({ jsonrpc: '2.0', method: 'textDocument/didChange', params: { textDocument: { uri, version }, contentChanges: [{ text }] } }); +(async () => { + const A = 'file:///stream.zc'; + // a large document with an error at the top: partial lists arrive before the end, the last holds everything + const bad = 'int broken( { }\n' + doc(1500, 'fn main() { return f0(1); }\n'); + let msgs = published(await session([[3000, [init, open(A, 1, bad)]]]), A); + assert.ok(msgs.length >= 2, 'a large check streams several lists: ' + msgs.length); + assert.ok(msgs[0].params.diagnostics.some(d => d.severity === 1), 'the first list already has the error'); + assert.ok(msgs[msgs.length - 1].params.diagnostics.length >= msgs[0].params.diagnostics.length, 'later lists grow'); + // a change that is already waiting drops the stale check: only the newer version is published + const ok = doc(1500, 'fn main() { return f0(1); }\n'); + msgs = published(await session([[3000, [init, open(A, 1, ok), change(A, 2, 'fn main() { return 0; }\n')]]]), A); + assert.ok(msgs.length >= 1 && msgs.every(m => m.params.version === 2), 'the stale version 1 is never published: ' + JSON.stringify(msgs.map(m => m.params.version))); + // an interrupted open of another document is still answered once nothing newer waits + const B = 'file:///other.zc'; + msgs = await session([[3000, [init, open(A, 1, ok), open(B, 1, 'fn main() { return 0; }\n')]]]); + assert.ok(published(msgs, A).length >= 1 && published(msgs, B).length >= 1, 'both documents are published'); + console.log('ok large checks stream partial diagnostics and drop stale checks'); +})(); diff --git a/tests/drivers/thread_context_test.py b/tests/drivers/thread_context_test.py new file mode 100644 index 0000000..fa3f144 --- /dev/null +++ b/tests/drivers/thread_context_test.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# verify actual threads, independent vm state, parallelism, cancellation and join. +from pathlib import Path +import argparse +import subprocess + +parser = argparse.ArgumentParser() +parser.add_argument('vm', nargs='?', default='bin/zy') +parser.add_argument('--case', default='') +parser.add_argument('--library', default='') +parser.add_argument('--backend', choices=('native', 'micro'), default='native') +args = parser.parse_args() +root = Path(__file__).resolve().parents[2] +expected = {'isolation': '21 16 7 16 7 4 1 1\n', 'parallel': '11 21\n', 'cancel': '0\n', 'cancel_o3': '0\n', 'invalid': '-1 -1\n', 'recycle': '66\n', 'channels': '-1 0\n', 'failed_worker': '5 7\n', 'ffi_o3': '1 5050 0\n', 'actor_identity': '1\n', 'resources': '1 24 0 0 0\n', 'close_order': '0 0 -1\n', 'resources_counts': '24 0 0 0\n', 'nested': '1 0 0 0\n'} +count = 0 +for case in sorted((root / 'tests/threads').glob('*/*.zc')): + if args.case and args.case not in str(case): + continue + if args.backend == 'micro' and case.stem in ('ffi_o3', 'resources'): + print(f'skip {case.relative_to(root)} (native ffi fixture)') + continue + source = case.read_text() + if args.library: + source = source.replace('include rt, actor;', f'include rt; include \"{args.library}\"; include actor;') + proc = subprocess.run([str(root / args.vm)], input=source, text=True, capture_output=True, cwd=root, timeout=12) + actual = proc.stdout + if case.stem in ('parallel', 'cancel', 'cancel_o3'): + lines = actual.splitlines(keepends=True) + assert len(lines) == 2, f'{case}: invalid output {actual!r}, stderr={proc.stderr!r}' + milliseconds = int(lines[-1]) + assert 0 <= milliseconds < (580 if case.stem == 'parallel' else 500), f'{case}: elapsed {milliseconds}ms' + actual = lines[0] + assert proc.returncode == 0 and actual == expected[case.stem], f'{case}: exit={proc.returncode}, stdout={proc.stdout!r}, stderr={proc.stderr!r}' + print(f'ok {case.relative_to(root)}') + count += 1 +assert count +print(f'{count} thread context fixtures passed') diff --git a/tests/drivers/toolchain_lsp_contract_test.js b/tests/drivers/toolchain_lsp_contract_test.js new file mode 100644 index 0000000..0d1b515 --- /dev/null +++ b/tests/drivers/toolchain_lsp_contract_test.js @@ -0,0 +1,306 @@ +// pending requirements have explicit protocol oracles, selectable in small groups. +const assert = require('assert'); +const cp = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); +const host = process.argv[2], group = process.env.CONTRACT_GROUP; +const frame = m => { const b = Buffer.from(JSON.stringify({ jsonrpc: '2.0', ...m })); return Buffer.concat([Buffer.from(`Content-Length: ${b.length}\r\n\r\n`), b]); }; +function run(text, requests = [], uri = 'file:///contracts.zc', files = []) { + const messages = [{ id: 1, method: 'initialize', params: {} }, ...files.map(([file, content]) => ({ method: 'zc/file', params: { path: file, text: content } })), { method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, text } } }, ...requests.map((r, i) => { + const at = text.indexOf(r.needle) + (r.offset || 0); assert.ok(at >= 0, r.needle); + const before = text.slice(0, at); + return { id: i + 2, method: r.method || 'textDocument/hover', params: { textDocument: { uri }, ...(r.method === 'textDocument/codeAction' ? {} : { position: { line: before.split('\n').length - 1, character: before.length - before.lastIndexOf('\n') - 1 } }), ...(r.params || {}) } }; + }), { method: 'exit' }]; + let output; + if (host === '--wasm' || host === '--vm') { + const wasm = host === '--wasm'; + const bridge = require(path.resolve('editors/vscode/client', wasm ? 'wasmproc.js' : 'vmproc.js')); + const proc = (wasm ? bridge.wasmProcess : bridge.vmProcess)(path.resolve(process.argv[3]), { log: () => {} }); + const chunks = []; proc.stdout.on('data', b => chunks.push(b)); + for (const message of messages) proc.stdin.write(frame(message)); + output = Buffer.concat(chunks); + } else { + const r = cp.spawnSync(host, process.argv.slice(3), { input: Buffer.concat(messages.map(frame)), timeout: 60000, maxBuffer: 32 << 20 }); + assert.strictEqual(r.status, 0, String(r.stderr)); output = r.stdout; + } + const replies = []; let data = output; + while (data.length) { + const end = data.indexOf('\r\n\r\n'); assert.ok(end >= 0); + const n = Number(/Content-Length:\s*(\d+)/i.exec(data.subarray(0, end).toString())[1]); + assert.ok(data.length >= end + 4 + n); + replies.push(JSON.parse(data.subarray(end + 4, end + 4 + n))); data = data.subarray(end + 4 + n); + } + const ds = replies.filter(r => r.method === 'textDocument/publishDiagnostics').at(-1)?.params.diagnostics || []; + return { ds, result: id => replies.find(r => r.id === id)?.result }; +} +let failures = 0, checks = 0; +function test(g, name, f) { + if (group && group !== g) return; + checks++; + try { f(); console.log('ok ' + g + ': ' + name); } + catch (e) { failures++; console.error('FAIL ' + g + ': ' + name + '\n' + e.message); } +} +for (const [uri, prefix] of [['file:///contracts.zc', ''], ['tinyvm:/fixture.zc', '']]) { + test('hovers', 'word-string global at declaration and use ' + uri, () => { + const text = prefix + 'let hello = "hi"; fn main() { return hello[0]; }'; + const r = run(text, [{ needle: 'hello =' }, { needle: 'hello[0]' }], uri); + for (const id of [2, 3]) { const md = r.result(id)?.contents.value || ''; assert.match(md, /\(char\*\) let hello \/\/ global;/); assert.ok(!md.includes('; type ')); } + }); + test('hovers', 'fixed global array at declaration and use ' + uri, () => { + const r = run('let prog[20000]; fn main() { return prog[0]; }', [{ needle: 'prog[20000]' }, { needle: 'prog[0]' }], uri); + for (const id of [2, 3]) assert.match(r.result(id)?.contents.value || '', /\(word\[20000\]\) let prog \/\/ global;/); + }); +} +test('strings', 'packed declaration, parameter, return and uses keep bstr', () => { + const r = run('fn echo(s) { return s; } fn main() { let hello = b"hi"; let other = echo(hello); return other[0]; }', [{ needle: 'hello =' }, { needle: 'hello);' }, { needle: 'other[0]' }, { needle: 'echo(hello)' }, { needle: 'return s;', offset: 7 }]); + assert.ok(!r.ds.some(d => d.severity === 1), JSON.stringify(r.ds)); + for (const id of [2, 3, 4, 6]) { const md = r.result(id)?.contents.value || ''; assert.match(md, /\(bstr\) let/); assert.ok(!/\(word\)/.test(md)); } + assert.match(r.result(5)?.contents.value || '', /bstr echo\(bstr s\)/); +}); +test('strings', 'grouped word-string local stays char*', () => { + const r = run('fn main() { let n = 1, text = "hi"; return text[0] + n; }', [{ needle: 'text[0]' }]); + assert.match(r.result(2)?.contents.value || '', /\(char\*\) let text/); +}); +test('strings', 'inferred packed return is shown in hover', () => { + const text = 'fn text() { return b"hi"; } fn main() { let result = text(); return result[0]; }'; + const r = run(text, [{ needle: 'text()' }, { needle: 'result = ' }, { needle: 'result[0]' }]); + assert.match(r.result(2)?.contents.value || '', /bstr text\(\)/); + for (const id of [3, 4]) assert.match(r.result(id)?.contents.value || '', /\(bstr\) let result/); +}); +test('strings', 'inferred word-string return is shown in hover', () => { + const text = 'fn text() { return "hi"; } fn main() { let result = text(); return result[0]; }'; + const r = run(text, [{ needle: 'text()' }, { needle: 'result = ' }, { needle: 'result[0]' }]); + assert.match(r.result(2)?.contents.value || '', /char\* text\(\)/); + for (const id of [3, 4]) assert.match(r.result(id)?.contents.value || '', /\(char\*\) let result/); +}); +for (const [name, src, expected, severity = 3] of [ + ['unused parameter', 'fn ignore(int unused) { return 0; }', true], + ['used parameter', 'fn use(int used) { return used; }', false], + ['explicit discard parameter', 'fn ignore(int _) { return 0; }', false], + ['duplicate discard parameters', 'fn ignore(int _, int _) { return 0; }', false], + ['write-only parameter', 'fn write(int unused) { unused = 2; return 0; }', true], + ['shadowed parameter', 'fn shadow(int unused) { { int unused = 2; return unused; } }', true], + ['unused method argument', 'struct point { fn f(int unused) { return 0; } }', true], + ['unused closure argument', 'fn main() { let f = fn:int(int unused) { return 0; }; return f(2); }', true], + ['unused generic argument', 'T first<T>(T used, T unused) { return used; } fn main() { return first<int>(1, 2); }', true], + ['on directive', 'hints on; fn ignore(int unused) { return 0; }', true, 3], + ['small directive', 'hints small; fn ignore(int unused) { return 0; }', true, 4], + ['off directive', 'hints off; fn ignore(int unused) { return 0; }', false] +]) test('arguments', name, () => { + const r = run(src + '\nfn marker() { return 0; }'); + const hints = r.ds.filter(d => /unused.*(argument|parameter)|(?:argument|parameter).*unused/.test(d.message)); + assert.strictEqual(hints.length > 0, expected, JSON.stringify(r.ds)); + if (expected) { assert.strictEqual(hints[0].severity, severity); assert.strictEqual(src.split('\n')[hints[0].range.start.line].slice(hints[0].range.start.character, hints[0].range.end.character), 'unused'); } +}); +for (const [name, src, warning] of [ + ['discard read warns', 'fn f(int _) { return _; }', true], + ['discard assignment is not a read', 'fn f(int _) { _ = 2; return 0; }', false], + ['duplicate payload discards', 'include rt; enum pair { item { int x; int y; }, other }; fn main() { let p = pair.item(1, 2); switch(p) { case item(_, _): return 0; case _: return 1; } }', false], + ['bound payload discard read', 'include rt; enum pair { item { int x; int y; }, other }; fn main() { let p = pair.item(1, 2); switch(p) { case item(_, _): return _; case _: return 1; } }', true], + ['numeric catch-all', 'fn f(int n) { switch(n) { case 1: return 1; case _: return 2; } }', false] +]) test('discards', name, () => { + const r = run(src); + assert.ok(!r.ds.some(d => d.severity === 1), JSON.stringify(r.ds)); + const ds = r.ds.filter(d => /discard.*read|read.*discard/.test(d.message)); + assert.strictEqual(ds.length > 0, warning, JSON.stringify(r.ds)); + for (const d of ds) { assert.strictEqual(d.severity, 2); assert.strictEqual(src.slice(d.range.start.character, d.range.end.character), '_'); } +}); +for (const [name, src, count, severity] of [ + ['uncommented literal', 'fn f(int n) { return n + 0x40ff; }', 1, 3], + ['commented literal', 'fn f(int n) { return n + /* offset in memory */ 0x40ff; }', 0, 3], + ['empty comment escape', 'fn f(int n) { return n + /**/ 0x40ff; }', 0, 3], + ['nearby comment does not hide it', 'fn f(int n) { return /* explanation of n */ n + 0x40ff; }', 1, 3], + ['compact dots', 'hints small; fn f(int n) { return n + 0x40ff; }', 1, 4], + ['off directive', 'hints off; fn f(int n) { return n + 0x40ff; }', 0, 3] +]) test('magic', name, () => { const ds = run(src).ds.filter(d => /magic|numeric literal|number .*has no name/i.test(d.message)); assert.strictEqual(ds.length, count, JSON.stringify(ds)); if (count) { assert.strictEqual(ds[0].severity, severity); assert.match(ds[0].message, /inline block comment/); assert.ok(!ds[0].message.includes('a const says what it means')); } }); +test('navigation', 's_to_n points to its runtime declaration', () => { + const r = run('include rt; fn main() { return rt.s_to_n(b"42"); }', [{ method: 'textDocument/definition', needle: 's_to_n' }]); + const loc = r.result(2)?.[0]; assert.ok(loc, 'missing definition'); assert.strictEqual(loc.uri, 'tinyvm:/rt.zc'); + const line = fs.readFileSync('src/runtime/rt.zc', 'utf8').split('\n')[loc.range.start.line]; + assert.strictEqual(line.slice(loc.range.start.character, loc.range.end.character), 's_to_n'); +}); +for (const [module, file] of [['core', 'core.zc'], ['rt', 'rt.zc']]) test('navigation', 'qualified duplicate names open the correct module: ' + module, () => { + const source = 'include rt, core; fn main() { return ' + module + '.syscall(9,0,0,0); }'; + const r = run(source, [{ needle: 'syscall(', method: 'textDocument/definition' }, { needle: 'syscall(' }]); + const location = r.result(2)?.[0]; + assert.strictEqual(location?.uri, 'tinyvm:/' + file); + const sourcePath = module === 'core' ? 'src/core/core.zc' : 'src/runtime/rt.zc'; + const line = fs.readFileSync(sourcePath, 'utf8').split('\n')[location.range.start.line]; + assert.strictEqual(line.slice(location.range.start.character, location.range.end.character), 'syscall'); + if (module === 'core') assert.match(r.result(3)?.contents.value || '', /int syscall\(int operation/); +}); +test('navigation', 'qualified references retain their resolved module identity', () => { + const source = 'include rt, core; fn main() { core.syscall(9,0,0,0); rt.syscall(9,0,0,0); return core.syscall(9,0,0,0); }'; + const r = run(source, [{ needle: 'core.syscall', offset: 5, method: 'textDocument/references', params: { context: { includeDeclaration: false } } }]); + const references = r.result(2) || []; + assert.strictEqual(references.length, 2); + for (const location of references) assert.ok(source.slice(location.range.start.character, location.range.end.character).includes('syscall')); +}); +test('modules', 'qualified completion exports only imported module', () => { + const r = run('include rt; fn main() { rt.put_', [{ method: 'textDocument/completion', needle: 'rt.put_', offset: 7 }]); + const items = r.result(2)?.items || r.result(2) || []; + assert.ok(items.some(i => i.label === 'put_s')); assert.ok(!items.some(i => i.label === 'evals')); +}); +test('modules', 'lsp exports require an explicit include and qualified name', () => { + const accepted = run('include lsp; fn main() { return lsp.hid(97); }'); + assert.ok(!accepted.ds.some(d => d.severity === 1), JSON.stringify(accepted.ds)); + const missing = run('fn main() { return lsp.hid(97); }'); + assert.ok(missing.ds.some(d => d.severity === 1 && /include lsp/.test(d.message)), JSON.stringify(missing.ds)); + const bare = run('include lsp; fn main() { return hid(97); }'); + assert.ok(bare.ds.some(d => d.severity === 1), JSON.stringify(bare.ds)); +}); +test('modules', 'missing include reports error at qualified member', () => { + const r = run('fn main() { rt.put_s("hi"); return 0; }'); + assert.ok(r.ds.some(d => d.severity === 1 && /include rt/.test(d.message)), JSON.stringify(r.ds)); +}); +for (const [name, description] of [['rt', /runtime/], ['run', /resident compiler/], ['core', /vm host/], ['lsp', /language server/], ['std', /standard library/], ['icon', /syntax transformations/]]) { + test('modulehover', 'module documentation in comma-separated include: ' + name, () => { + const text = 'include rt, run, core, lsp, std, icon; fn main() { return 0; }'; + const r = run(text, [{ needle: name + (name === 'icon' ? ';' : ',') }]); + const hover = r.result(2)?.contents; + assert.strictEqual(hover?.kind, 'markdown'); + assert.match(hover.value, new RegExp('```zc\\ninclude ' + name + ';\\n```')); + assert.match(hover.value, description); + assert.match(hover.value, new RegExp('`' + name + '\\.[^`]+`')); + }); +} +test('modulehover', 'a local with the same name retains its variable hover', () => { + const r = run('fn main() { int rt = 3; return rt; }', [{ needle: 'rt;' }]); + assert.match(r.result(2)?.contents.value || '', /\(int\) let rt/); + assert.ok(!r.result(2).contents.value.includes('include rt;')); +}); +test('modulehover', 'comments and newlines preserve the include list', () => { + const r = run('include /* runtime */ rt,\n /* compiler */ run; fn main() { return 0; }', [{ needle: 'run;' }]); + assert.match(r.result(2)?.contents.value || '', /include run;[\s\S]*resident compiler/); +}); +for (const [name, member] of [['rt', 'put_s'], ['run', 'evals'], ['core', 'syscall'], ['lsp', 'hid'], ['std', 'array_map'], ['icon', 'find_replace']]) { + test('namespacecompletion', 'qualified members, signatures and documentation: ' + name, () => { + const r = run('include rt, run, core, lsp, std, icon; fn main() { ' + name + '.', [{ needle: name + '.', offset: name.length + 1, method: 'textDocument/completion' }]); + const items = r.result(2)?.items || []; + const item = items.find(i => i.label === member); + assert.ok(item, JSON.stringify(items.map(i => i.label))); + assert.ok(item.detail && item.documentation?.value, JSON.stringify(item)); + assert.ok(!items.some(i => i.label === 'main')); + assert.strictEqual(new Set(items.map(i => i.label)).size, items.length); + }); +} +function applyWorkspaceEdit(text, edit, uri) { + const edits = edit.documentChanges?.flatMap(c => c.textDocument.uri === uri ? c.edits : []) || edit.changes?.[uri] || []; + const offset = p => text.split('\n').slice(0, p.line).reduce((n, l) => n + l.length + 1, 0) + p.character; + for (const e of edits.sort((a, b) => offset(b.range.start) - offset(a.range.start))) text = text.slice(0, offset(e.range.start)) + e.newText + text.slice(offset(e.range.end)); + return text; +} +for (const [name, source, needle] of [ + ['qualified runtime import', 'fn main() { rt.put_n(7); }', 'put_n'], + ['qualified compiler import', 'fn main() { run.evals("return 0;"); }', 'evals'], + ['qualified host import', 'fn main() { return core.syscall(9,0,0,0); }', 'syscall'], + ['bare runtime call needs qualification too', 'fn main() { put_n(7); }', 'put_n'], + ['imported module still needs a qualified name', 'include rt; fn main() { put_n(7); }', 'put_n'], + ['namespace and header are preserved', '// file overview\nnamespace demo;\n\n// prints the result\nfn main() { rt.put_n(7); }', 'put_n'], + ['crlf stays crlf', 'namespace demo;\r\nfn main() { rt.put_n(7); }\r\n', 'put_n'], +]) test('quickfix', name, () => { + const uri = 'file:///contracts.zc', before = source.slice(0, source.indexOf(needle)); + const position = { line: before.split('\n').length - 1, character: before.length - before.lastIndexOf('\n') - 1 }; + const r = run(source, [{ needle, method: 'textDocument/codeAction', params: { range: { start: position, end: position }, context: { diagnostics: [] } } }], uri); + assert.ok(r.result(1)?.capabilities.codeActionProvider); + const action = r.result(2)?.find(a => a.kind === 'quickfix' && a.edit); + assert.ok(action, JSON.stringify(r.result(2))); + const changed = applyWorkspaceEdit(source, action.edit, uri); + assert.notStrictEqual(changed, source); + if (source.includes('\r\n')) assert.ok(!/(?<!\r)\n/.test(changed)); + if (source.includes('// prints the result')) assert.ok(changed.includes('// prints the result\nfn main()')); + if (source.startsWith('include rt;')) assert.strictEqual((changed.match(/include rt;/g) || []).length, 1); + const checked = run(changed); + assert.ok(!checked.ds.some(d => d.severity === 1), JSON.stringify({ changed, ds: checked.ds })); +}); +for (const source of ['include rt; fn main() { rt.put_n(7); }', 'fn main() { int put_n = 7; return put_n; }', 'fn main() { return unknown_name; }']) test('quickfix', 'no unrelated import edits: ' + source, () => { + const needle = source.includes('put_n') ? 'put_n' : 'unknown_name'; + const r = run(source, [{ needle, method: 'textDocument/codeAction' }]); + assert.deepStrictEqual(r.result(2), []); +}); +test('qualifiers', 'fixed const array hover preserves both qualifiers and shape', () => { + const r = run('fn main() { fixed const int[2] data = {1,2}; return data[0]; }', [{ needle: 'data[0]' }]); + assert.ok(!r.ds.some(d => d.severity === 1), JSON.stringify(r.ds)); + assert.match(r.result(2)?.contents.value || '', /\(const int\[2\]\) let data/); +}); +test('qualifiers', 'const fixed global hover preserves its type', () => { + const r = run('const fixed int[2] data = {1,2}; fn main() { return data[0]; }', [{ needle: 'data[0]' }]); + assert.match(r.result(2)?.contents.value || '', /\(const int\[2\]\) let data \/\/ global;/); +}); +test('qualifiers', 'readonly qualifiers reject aliasing without optional type checking', () => { + const r = run('typecheck off; fn mutate(int* p) { p[0] = 9; } fn main() { fixed const int[2] data = {1,2}; mutate(data); }'); + assert.ok(r.ds.some(d => d.severity === 1 && /const storage cannot be converted/.test(d.message)), JSON.stringify(r.ds)); +}); +test('inference', 'typed callee constrains caller arguments and returned values', () => { + const source = 'int seed(int cap) { return cap + 1; } fn squares(n) { let l = seed(n); return l; } fn main() { return squares(3); }'; + const r = run(source, [{ needle: 'squares(n)' }, { needle: 'seed(n)', offset: 5 }, { needle: 'return l;', offset: 7 }]); + assert.match(r.result(2)?.contents.value || '', /int squares\(int n\)/); + assert.match(r.result(3)?.contents.value || '', /\(int\) let n/); + assert.match(r.result(4)?.contents.value || '', /\(int\) let l/); +}); +test('inference', 'twelve caller layers converge in reverse declaration order', () => { + let source = ''; + for (let i = 12; i > 0; i--) source += `fn layer${i}(n) { return layer${i - 1}(n); }\n`; + source += 'int layer0(int n) { return n + 1; } fn main() { return layer12(3); }'; + const r = run(source, [{ needle: 'layer12(n)' }, { needle: 'layer7(n)' }, { needle: 'layer1(n)' }]); + for (const [id, name] of [[2, 'layer12'], [3, 'layer7'], [4, 'layer1']]) assert.match(r.result(id)?.contents.value || '', new RegExp('int ' + name + '\\(int n\\)')); +}); +test('inference', 'recursive calls retain an anchored integer return and argument type', () => { + const source = 'int seed(int n) { return n; } fn recursive(n) { if (n <= 0) return seed(n); return recursive(n - 1); } fn main() { return recursive(3); }'; + const r = run(source, [{ needle: 'recursive(n)' }]); + assert.match(r.result(2)?.contents.value || '', /int recursive\(int n\)/); +}); +test('inference', 'uncalled caller parameters inherit typed callee requirements', () => { + const r = run('int seed(int n) { return n; } fn wrapper(n) { return seed(n); }', [{ needle: 'wrapper(n)' }]); + assert.match(r.result(2)?.contents.value || '', /int wrapper\(int n\)/); +}); +test('inference', 'conflicting caller types do not become a single inferred signature', () => { + const r = run('fn identity(value) { return value; } fn main() { identity(3); return identity("text")[0]; }', [{ needle: 'identity(value)' }]); + assert.match(r.result(2)?.contents.value || '', /fn identity\(value\)/); +}); +test('inference', 'unknown reassignment invalidates the previous initializer flow', () => { + const r = run('int seed(int n) { return n; } fn external(); fn changed(n) { let result = seed(n); result = external(); return result; }', [{ needle: 'changed(n)' }]); + assert.ok(!/int changed/.test(r.result(2)?.contents.value || ''), JSON.stringify(r.result(2))); +}); +test('inference', 'owned list allocation constrains capacity and caller types', () => { + const text = 'include rt; .u mklist(cap) { let l = rt.new(cap + 1); l[0] = 0; return l; } .u squares(n) { let l = mklist(n); return l; }'; + const r = run(text, [{ needle: 'mklist(cap)' }, { needle: 'squares(n)' }, { needle: 'mklist(n)', offset: 7 }]); + assert.ok(!r.ds.some(d => d.severity === 1), JSON.stringify(r.ds)); + assert.match(r.result(2)?.contents.value || '', /word\.u mklist\(int cap\)/); + assert.match(r.result(3)?.contents.value || '', /word\.u squares\(int n\)/); + assert.match(r.result(4)?.contents.value || '', /\(int\) let n/); +}); +test('inference', 'array indices do not constrain the type of an element result', () => { + const r = run('include rt; fn lookup(index) { let data = rt.new(4); return data[index]; } fn main() { return lookup(1); }', [{ needle: 'lookup(index)' }]); + assert.ok(!/int lookup/.test(r.result(2)?.contents.value || ''), JSON.stringify(r.result(2))); +}); +test('inference', 'typed fields do not constrain the containing parameter to a scalar', () => { + const r = run('struct point { int x; } fn coordinate(p) { return p.x; }', [{ needle: 'coordinate(p)' }]); + assert.ok(!/coordinate\(int p\)/.test(r.result(2)?.contents.value || ''), JSON.stringify(r.result(2))); +}); +test('quickfix', 'an entire-line request finds the compiler error inside the range', () => { + const source = 'fn main() { rt.put_n(7); }'; + const r = run(source, [{ needle: 'fn', method: 'textDocument/codeAction', params: { range: { start: { line: 0, character: 0 }, end: { line: 0, character: source.length } }, context: { diagnostics: [{ range: { start: { line: 99, character: 99 }, end: { line: 99, character: 99 } }, message: 'stale' }] } } }]); + assert.ok(r.result(2)?.some(a => a.title === 'include rt;'), JSON.stringify(r.result(2))); +}); +test('quickfix', 'an owner-name cursor can import a qualified module', () => { + const source = 'fn main() { rt.put_n(7); }', start = { line: 0, character: source.indexOf('rt.') }; + const r = run(source, [{ needle: 'rt.', method: 'textDocument/codeAction', params: { range: { start, end: start }, context: { diagnostics: [] } } }]); + assert.ok(r.result(2)?.some(a => a.title === 'include rt;')); +}); +test('modulehover', 'source library overview is available without a built-in module entry', () => { + const folder = fs.mkdtempSync(path.join(os.tmpdir(), 'zc-module-hover-')); + try { + fs.mkdirSync(path.join(folder, 'src/lib'), { recursive: true }); + fs.writeFileSync(path.join(folder, 'src/lib/widget.zc'), 'namespace widget;\n\n// converts widget coordinates into grid positions.\n\nint position(int x) { return x; }\n'); + const r = run('include widget; fn main() { return widget.position(3); }', [{ needle: 'widget;' }], 'file://' + path.join(folder, 'main.zc'), [[path.join(folder, 'src/lib/widget.zc'), fs.readFileSync(path.join(folder, 'src/lib/widget.zc'), 'utf8')]]); + assert.ok(!r.ds.some(d => d.severity === 1), JSON.stringify(r.ds)); + assert.match(r.result(2)?.contents.value || '', /converts widget coordinates into grid positions/); + fs.writeFileSync(path.join(folder, 'src/lib/widget.zc'), 'namespace widget;\n\n/** converts grid positions into widget coordinates. */\n\nint position(int x) { return x; }\n'); + const block = run('include widget; fn main() { return widget.position(3); }', [{ needle: 'widget;' }], 'file://' + path.join(folder, 'main.zc'), [[path.join(folder, 'src/lib/widget.zc'), fs.readFileSync(path.join(folder, 'src/lib/widget.zc'), 'utf8')]]); + assert.match(block.result(2)?.contents.value || '', /converts grid positions into widget coordinates/); + } finally { fs.rmSync(folder, { recursive: true, force: true }); } +}); +assert.ok(checks, 'no selected protocol contracts: ' + group); +process.exitCode = failures ? 1 : 0; diff --git a/tests/drivers/tools_contract_test.py b/tests/drivers/tools_contract_test.py new file mode 100644 index 0000000..df02b2b --- /dev/null +++ b/tests/drivers/tools_contract_test.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""bounded accept/reject tests for standalone build tools and generated images.""" +import argparse +import re +import struct +import subprocess +import tempfile +from pathlib import Path + +parser = argparse.ArgumentParser() +parser.add_argument('--group', choices=['image', 'map', 'runners'], default='image') +args = parser.parse_args() + +def command(argv, data=None): + r = subprocess.run(argv, input=data, capture_output=True, timeout=15) + assert r.returncode >= 0, (argv, r.returncode, r.stderr) + return r + +if args.group == 'image': + source = Path('src/core/genome.h').read_text() + body = source.split('{', 1)[1].split('}', 1)[0] + words = [int(x) for x in re.findall(r'-?\d+', body)] + pack = lambda xs: b''.join(struct.pack('<q', x) for x in xs) + good = command(['bin/mkgenome'], pack(words)) + assert good.returncode == 0 and f'#define GLEN {len(words)}'.encode() in good.stdout + for data in [b'', b'x', pack(words[:6]), pack(words) + b'x']: + r = command(['bin/mkgenome'], data) + assert r.returncode != 0 and b'incomplete' in r.stderr + for index in range(3): + changed = words[:]; changed[index] ^= 2 + r = command(['bin/mkgenome'], pack(changed)) + assert r.returncode != 0 and b'not a genome' in r.stderr + print('ok mkgenome roundtrip, truncation, alignment and map-header mutation contracts') +elif args.group == 'map': + for preset in ['default', 'small', 'run', 'lsp', 'fixed']: + with tempfile.TemporaryDirectory(prefix='tinyvm preset ') as directory: + r = command(['bin/genmap', '--preset', preset, '-o', directory]) + assert r.returncode == 0, (preset, r.stderr) + assert (Path(directory) / 'memmap.h').is_file() + assert command(['bin/genmap', '--preset', 'default', '--check']).returncode == 0 + for options in [['--words', '-1'], ['--heap', '0'], ['--data', '0'], ['--symbols', '-1'], ['--total', '999999999999999999999'], ['--preset', 'imaginary']]: + r = command(['bin/genmap', *options, '--check']) + assert r.returncode != 0, options + with tempfile.TemporaryDirectory(prefix='tinyvm map ') as directory: + r = command(['bin/genmap', '--preset', 'small', '--locals', '32', '--symbols', '64', '-o', directory]) + assert r.returncode == 0, r.stderr + for name in ['memmap.h', 'rt.zc', 'compiler.zc', 'map.txt']: + assert (Path(directory) / name).is_file(), name + print('ok genmap presets, invalid capacities and generated paths containing spaces') +else: + r = command(['bash', '-n', 'tinyvm.sh']) + assert r.returncode == 0, r.stderr + for script in ['src/tools/automap.sh', 'src/tools/mklsp.sh', 'src/tools/mklspwasm.sh', 'src/tools/mkvsix.sh', 'src/tools/embed.sh']: + assert command(['bash', '-n', script]).returncode == 0 + assert command(['bash', 'tinyvm.sh', 'imaginary-command']).returncode != 0 + assert Path('tinyvm.bat').is_file() + assert not list(Path('.').glob('run*.bat')), 'windows run files must be consolidated' + print('ok runner and build script syntax; unsupported commands fail explicitly') diff --git a/tests/drivers/vscode_hover_test.js b/tests/drivers/vscode_hover_test.js index 378dda2..8239afd 100644 --- a/tests/drivers/vscode_hover_test.js +++ b/tests/drivers/vscode_hover_test.js @@ -27,7 +27,7 @@ exports.run = async () => { assert.ok(md.value.includes('**two**') && md.value.includes('`integer`')); assert.strictEqual(md.isTrusted, false); const storageFile = path.join(root, 'build', 'review', 'hover-storage.zc'); - fs.writeFileSync(storageFile, 'unchecked fn main() {\n let p = new(int, 3);\n let /* borrowed array */ q = p;\n free(p);\n return q[0];\n}\n'); + fs.writeFileSync(storageFile, 'include rt; unchecked fn main() {\n let p = rt.new(int, 3);\n let /* borrowed array */ q = p;\n rt.free(p);\n return q[0];\n}\n'); const storageDoc = await vscode.workspace.openTextDocument(storageFile); await vscode.window.showTextDocument(storageDoc); let storageHover; @@ -42,7 +42,24 @@ exports.run = async () => { assert.ok(storageMd instanceof vscode.MarkdownString); assert.match(storageMd.value, /borrowed array/); assert.match(storageMd.value, /lifetime: ended on line 4/); - const result = 'ok real vscode markdown, code fences, block docs, inferred pointer types, and lifetime metadata\n'; + const valueFile = path.join(root, 'build', 'review', 'hover-values.zc'); + fs.writeFileSync(valueFile, 'include rt; fn unknown() { return 9; }\nfn main() {\n int count = 42;\n rt.put_n(count);\n count = unknown();\n return count;\n}\n'); + const valueDoc = await vscode.workspace.openTextDocument(valueFile); + await vscode.window.showTextDocument(valueDoc); + let valueHover; + for (let attempt = 0; attempt < 100; attempt++) { + const replies = await vscode.commands.executeCommand('vscode.executeHoverProvider', valueDoc.uri, new vscode.Position(3, 11)); + valueHover = replies && replies.find(reply => reply.contents.some(md => md.value.includes('value at this position:'))); + if (valueHover) break; + await new Promise(resolve => setTimeout(resolve, 100)); + } + assert.ok(valueHover, 'real editor returns a known value before the unknown assignment'); + assert.ok(valueHover.contents[0] instanceof vscode.MarkdownString); + assert.match(valueHover.contents[0].value, /value at this position:\n```zc\n42\n```/); + const unknownHover = await vscode.commands.executeCommand('vscode.executeHoverProvider', valueDoc.uri, new vscode.Position(5, 8)); + assert.ok(unknownHover.length, 'the later variable still has a type hover'); + assert.ok(!unknownHover.some(reply => reply.contents.some(md => md.value.includes('value at this position:')))); + const result = 'ok real vscode markdown, code fences, block docs, inferred types, lifetime metadata, and position-aware values\n'; fs.writeFileSync(path.join(root, 'build', 'review', 'vscode-hover.log'), result); console.log(result.trim()); }; diff --git a/tests/drivers/wasm_test.js b/tests/drivers/wasm_test.js index ddab3be..e4f7ecb 100644 --- a/tests/drivers/wasm_test.js +++ b/tests/drivers/wasm_test.js @@ -13,6 +13,7 @@ const ZC = require('../../src/web/zc.js'); ['examples/struct_closure_opt.zc', '299\n'], ['examples/opt_test.zc', '1303\n'], ['tests/programs/basics.zc', '285 610 42 512\nhey 65 -3 -2 1024 -8\n12456\n757\n9 100-101\n5\nok\n'], + ['tests/programs/replace_owner.zc', '98 7 9 0\n'], ]; let ok = 0, bad = 0; for (const [file, want] of cases) { @@ -24,5 +25,5 @@ const ZC = require('../../src/web/zc.js'); const r = await zc.run('fn main() { return undefined_name; }'); if (/undefined_name/.test(r.output)) { ok++; console.log('ok wasm compiler errors'); } else { bad++; console.log('FAIL wasm compiler errors: ' + JSON.stringify(r.output)); } console.log(ok + ' wasm checks passed, ' + bad + ' failed'); - process.exit(bad ? 1 : 0); + process.exitCode = bad ? 1 : 0; })(); diff --git a/tests/errors/alias_after_replace_move.zc b/tests/errors/alias_after_replace_move.zc new file mode 100644 index 0000000..befd081 --- /dev/null +++ b/tests/errors/alias_after_replace_move.zc @@ -0,0 +1,11 @@ +namespace; +include rt; + +// expect: borrowed: used after it was given away +fn main() { + let target = rt.new(1), replacement = rt.new(1); + let borrowed = replacement + 0; + rt.free(target); target = replacement; + rt.put_n(target[0]); + return borrowed[0]; +} diff --git a/tests/errors/alias_field.zc b/tests/errors/alias_field.zc index 09086ca..a8d109d 100644 --- a/tests/errors/alias_field.zc +++ b/tests/errors/alias_field.zc @@ -1,9 +1,12 @@ +namespace; +include rt, run; + // expect: c: used after it was given away struct node { int v; node* next; }; fn main() { - node* p = new(node), n = new(node); + node* p = rt.new(node), n = rt.new(node); p.next = n; node* c = p.next; - free(d*p); + rt.free(d*p); return c.v; } diff --git a/tests/errors/alias_index.zc b/tests/errors/alias_index.zc index 3c036fe..11ce57c 100644 --- a/tests/errors/alias_index.zc +++ b/tests/errors/alias_index.zc @@ -1,9 +1,12 @@ +namespace; +include rt; + // expect: c: used after it was given away fn main() { - int** list = new(int*, 2); - int* item = new(int); + int** list = rt.new(int*, 2); + int* item = rt.new(int); list[1] = item; int* c = list[1]; - free(d*list); + rt.free(d*list); return c[0]; } diff --git a/tests/errors/alias_plus.zc b/tests/errors/alias_plus.zc index d42d196..f295fa0 100644 --- a/tests/errors/alias_plus.zc +++ b/tests/errors/alias_plus.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + // expect: q: used after it was given away -fn main() { let p = new(2); p[0] = 1; let q = p + 0; free(d*p); return q[0]; } +fn main() { let p = rt.new(2); p[0] = 1; let q = p + 0; rt.free(d*p); return q[0]; } diff --git a/tests/errors/alias_scope.zc b/tests/errors/alias_scope.zc index b51bec5..b52bfa8 100644 --- a/tests/errors/alias_scope.zc +++ b/tests/errors/alias_scope.zc @@ -1,6 +1,9 @@ +namespace; +include rt; + // expect: q: used after it was given away fn main() { let q; - { let p = new(2); q = p + 0; } + { let p = rt.new(2); q = p + 0; } return q[0]; } diff --git a/tests/errors/array_literal_derived_return.zc b/tests/errors/array_literal_derived_return.zc new file mode 100644 index 0000000..229cdc2 --- /dev/null +++ b/tests/errors/array_literal_derived_return.zc @@ -0,0 +1,5 @@ +namespace; + +// expect: an array literal cannot escape its function through return +fn bad() { return (int[]){1,2} + 0; } +fn main() { return 0; } diff --git a/tests/errors/array_literal_global.zc b/tests/errors/array_literal_global.zc new file mode 100644 index 0000000..58d3c13 --- /dev/null +++ b/tests/errors/array_literal_global.zc @@ -0,0 +1,5 @@ +namespace; + +// expect: an array literal cannot escape into a global or container +int* saved; +fn main() { saved = (int[]){1,2}; return 0; } diff --git a/tests/errors/array_literal_retained.zc b/tests/errors/array_literal_retained.zc new file mode 100644 index 0000000..2ae9fb1 --- /dev/null +++ b/tests/errors/array_literal_retained.zc @@ -0,0 +1,6 @@ +namespace; + +// expect: a temporary argument requires a proved non-escaping borrow +int* saved; +fn store(int* p) { saved = p; } +fn main() { store((int[]){1,2,3}); } diff --git a/tests/errors/array_literal_return.zc b/tests/errors/array_literal_return.zc new file mode 100644 index 0000000..7ccad17 --- /dev/null +++ b/tests/errors/array_literal_return.zc @@ -0,0 +1,5 @@ +namespace; + +// expect: an array literal cannot escape its function through return +fn bad() { return (int[]){1,2}; } +fn main() { return 0; } diff --git a/tests/errors/back_to_m.zc b/tests/errors/back_to_m.zc index b8c160f..34397f0 100644 --- a/tests/errors/back_to_m.zc +++ b/tests/errors/back_to_m.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + // expect: x: can't be recast back to m -fn main() { let x = new(3); d*x; m*x; return 0; } +fn main() { let x = rt.new(3); d*x; m*x; return 0; } diff --git a/tests/errors/borrow_escape.zc b/tests/errors/borrow_escape.zc deleted file mode 100644 index ce24480..0000000 --- a/tests/errors/borrow_escape.zc +++ /dev/null @@ -1,3 +0,0 @@ -// expect: p: is owned and can't be returned -fn f(.m p) { return p; } -fn main() { let x = new(1); f(x); return 0; } diff --git a/tests/errors/borrow_local_return.zc b/tests/errors/borrow_local_return.zc new file mode 100644 index 0000000..0490b7c --- /dev/null +++ b/tests/errors/borrow_local_return.zc @@ -0,0 +1,6 @@ +namespace; +include rt; + +// expect: a borrow of a local owner cannot escape through return +char* escaped() { char* p = rt.new(2); return p + 0; } +fn main() { rt.put_s(escaped()); } diff --git a/tests/errors/borrow_projection_consume.zc b/tests/errors/borrow_projection_consume.zc new file mode 100644 index 0000000..913f25b --- /dev/null +++ b/tests/errors/borrow_projection_consume.zc @@ -0,0 +1,5 @@ +namespace; +include rt; + +// expect: a borrowed projection cannot transfer ownership +fn main() { int* p = rt.new(int); int* alias = p + 0; rt.free(alias); rt.put_n(p[0]); } diff --git a/tests/errors/borrow_resize.zc b/tests/errors/borrow_resize.zc new file mode 100644 index 0000000..8594f98 --- /dev/null +++ b/tests/errors/borrow_resize.zc @@ -0,0 +1,10 @@ +namespace; +include rt; + +// expect: element borrow was invalidated by array mutation on line 8; borrow created on line 7 +fn main() { + int[?] a = {1,2}; + int* p = &a[0]; + resize(a,3); + rt.put_n(p[0]); +} diff --git a/tests/errors/branch_mismatch.zc b/tests/errors/branch_mismatch.zc index bd19968..ce361ac 100644 --- a/tests/errors/branch_mismatch.zc +++ b/tests/errors/branch_mismatch.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // expect: x: ends the branches of an if in different states fn take(.d p) { return p[0]; } -fn main() { let x = new(1); if (x[0]) take(d*x); else u*x; return 0; } +fn main() { let x = rt.new(1); if (x[0]) take(d*x); else u*x; return 0; } diff --git a/tests/errors/child_dies.zc b/tests/errors/child_dies.zc index 67caca9..defacbb 100644 --- a/tests/errors/child_dies.zc +++ b/tests/errors/child_dies.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // expect: c: used after it was given away fn take(.d p) { return p[0]; } -fn main() { let p = new(2), c = new(1); p[0] = c; take(d*p); return c[0]; } +fn main() { let p = rt.new(2), c = rt.new(1); p[0] = c; take(d*p); return c[0]; } diff --git a/tests/errors/child_recast.zc b/tests/errors/child_recast.zc index c9ccd62..8d44a4c 100644 --- a/tests/errors/child_recast.zc +++ b/tests/errors/child_recast.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + // expect: c: belongs to a container; recast the container instead -fn main() { let p = new(2), c = new(1); p[0] = c; d*c; return 0; } +fn main() { let p = rt.new(2), c = rt.new(1); p[0] = c; d*c; return 0; } diff --git a/tests/errors/copy_owned.zc b/tests/errors/copy_owned.zc index 862c7ef..a56a81b 100644 --- a/tests/errors/copy_owned.zc +++ b/tests/errors/copy_owned.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + // expect: x: is an owned object and can't be copied -fn main() { let x = new(1); let y = x; return y[0]; } +fn main() { let x = rt.new(1); let y = x; return y[0]; } diff --git a/tests/errors/d_into_block.zc b/tests/errors/d_into_block.zc index 2d7f526..8222674 100644 --- a/tests/errors/d_into_block.zc +++ b/tests/errors/d_into_block.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + // expect: x: used after it was given away -fn main() { let x = new(1); d*x; { x[0] = 5; } return x[0]; } +fn main() { let x = rt.new(1); d*x; { x[0] = 5; } return x[0]; } diff --git a/tests/errors/d_kept.zc b/tests/errors/d_kept.zc index dcbfb86..6cd27e4 100644 --- a/tests/errors/d_kept.zc +++ b/tests/errors/d_kept.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + // expect: x: d object was never given away -fn main() { let x = new(3); d*x; x[0] = 1; return 0; } +fn main() { let x = rt.new(3); d*x; x[0] = 1; return 0; } diff --git a/tests/errors/did_you_mean.zc b/tests/errors/did_you_mean.zc index 734b903..a6f6e47 100644 --- a/tests/errors/did_you_mean.zc +++ b/tests/errors/did_you_mean.zc @@ -1,3 +1,5 @@ +namespace; + // expect: did you mean 'helper'? fn helper(x) { return x; } fn main() { return helpr(1); } diff --git a/tests/errors/discard_u.zc b/tests/errors/discard_u.zc index 2743f9e..85b38c4 100644 --- a/tests/errors/discard_u.zc +++ b/tests/errors/discard_u.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // expect: a new object (or a u result) can't be thrown away -.u mk() { let p = new(1); return u*p; } +.u mk() { let p = rt.new(1); return u*p; } fn main() { mk(); return 0; } diff --git a/tests/errors/found_token.zc b/tests/errors/found_token.zc index 73e63dc..afd677d 100644 --- a/tests/errors/found_token.zc +++ b/tests/errors/found_token.zc @@ -1,2 +1,4 @@ +namespace; + // expect: expected ';', found 'return' fn main() { let a = 1 return 0; } diff --git a/tests/errors/give_m.zc b/tests/errors/give_m.zc deleted file mode 100644 index cd67896..0000000 --- a/tests/errors/give_m.zc +++ /dev/null @@ -1,3 +0,0 @@ -// expect: x: is m; write d*x -fn take(.d p) { return p[0]; } -fn main() { let x = new(3); take(x); return 0; } diff --git a/tests/errors/global_borrowed.zc b/tests/errors/global_borrowed.zc new file mode 100644 index 0000000..3dc3760 --- /dev/null +++ b/tests/errors/global_borrowed.zc @@ -0,0 +1,7 @@ +namespace; +include rt; + +// expect: p: is not an owner, so it can't be stored in a global +let g; +fn keep(.m p) { g = p; return 0; } +fn main() { let x = rt.new(1); keep(x); return 0; } diff --git a/tests/errors/global_child.zc b/tests/errors/global_child.zc new file mode 100644 index 0000000..9b51da9 --- /dev/null +++ b/tests/errors/global_child.zc @@ -0,0 +1,7 @@ +namespace; +include rt; + +// expect: x: is not an owner, so it can't be stored in a global +struct box { int* v; }; +let g; +fn main() { box* b = rt.new(box); int* x = rt.new(int); b.v = x; g = x; return 0; } diff --git a/tests/errors/global_store.zc b/tests/errors/global_store.zc deleted file mode 100644 index dc0e546..0000000 --- a/tests/errors/global_store.zc +++ /dev/null @@ -1,3 +0,0 @@ -// expect: x: must be d to be stored in a global -let g; -fn main() { let x = new(1); g = x; return 0; } diff --git a/tests/errors/global_then_use.zc b/tests/errors/global_then_use.zc new file mode 100644 index 0000000..ce01872 --- /dev/null +++ b/tests/errors/global_then_use.zc @@ -0,0 +1,6 @@ +namespace; +include rt; + +// expect: x: used after it was given away +let g; +fn main() { let x = rt.new(1); g = x; return x[0]; } diff --git a/tests/errors/leak_overwrite.zc b/tests/errors/leak_overwrite.zc index d19b5fd..50fecfa 100644 --- a/tests/errors/leak_overwrite.zc +++ b/tests/errors/leak_overwrite.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + // expect: x: still owns an object -fn main() { let x = new(1); x = new(2); return x[0]; } +fn main() { let x = rt.new(1); x = rt.new(2); return x[0]; } diff --git a/tests/errors/lend_d.zc b/tests/errors/lend_d.zc index 55ac7e0..393a314 100644 --- a/tests/errors/lend_d.zc +++ b/tests/errors/lend_d.zc @@ -1,4 +1,7 @@ +namespace; +include rt; + // expect: x: is d; pass it to a d parameter fn look(.m p) { return p[0]; } fn take(.d p) { return p[0]; } -fn main() { let x = new(3); d*x; look(x); take(x); return 0; } +fn main() { let x = rt.new(3); d*x; look(x); take(x); return 0; } diff --git a/tests/errors/loop_move.zc b/tests/errors/loop_move.zc index df3c94a..17f3b4f 100644 --- a/tests/errors/loop_move.zc +++ b/tests/errors/loop_move.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // expect: x: is given away inside a loop that comes back around to it fn take(.d p) { return p[0]; } -fn main() { let x = new(1); let i = 0; d*x; while (i < 3) { take(x); i++; } return 0; } +fn main() { let x = rt.new(1); let i = 0; d*x; while (i < 3) { take(x); i++; } return 0; } diff --git a/tests/errors/old_alias_after_replace.zc b/tests/errors/old_alias_after_replace.zc new file mode 100644 index 0000000..7ca8616 --- /dev/null +++ b/tests/errors/old_alias_after_replace.zc @@ -0,0 +1,11 @@ +namespace; +include rt; + +// expect: borrowed: used after it was given away +fn main() { + let target = rt.new(1), replacement = rt.new(1); + let borrowed = target + 0; + rt.free(target); target = replacement; + rt.put_n(target[0]); + return borrowed[0]; +} diff --git a/tests/errors/plain_return.zc b/tests/errors/plain_return.zc index e23cc51..c44c533 100644 --- a/tests/errors/plain_return.zc +++ b/tests/errors/plain_return.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // expect: is called before its body, so its owned return can't be known there fn main() { return mk(); } -fn mk() { return new(1); } +fn mk() { return rt.new(1); } diff --git a/tests/errors/return_m.zc b/tests/errors/return_m.zc index c450711..4a6499b 100644 --- a/tests/errors/return_m.zc +++ b/tests/errors/return_m.zc @@ -1,3 +1,6 @@ -// expect: q: has to be u to be returned +namespace; +include rt; + +// expect: a borrowed parameter cannot be returned as an owned result .u mk(.m q) { return q; } -fn main() { let x = new(1); let y = mk(x); return y[0]; } +fn main() { let x = rt.new(1); let y = mk(x); return y[0]; } diff --git a/tests/errors/tag_mismatch.zc b/tests/errors/tag_mismatch.zc index 079f249..1a67766 100644 --- a/tests/errors/tag_mismatch.zc +++ b/tests/errors/tag_mismatch.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + // expect: c: its tag doesn't match its container's -fn main() { let p = new(2), c = new(1); u*p; p[0] = c; return 0; } +fn main() { let p = rt.new(2), c = rt.new(1); u*p; p[0] = c; return 0; } diff --git a/tests/errors/temporary_alias_escape.zc b/tests/errors/temporary_alias_escape.zc new file mode 100644 index 0000000..9b4c8fa --- /dev/null +++ b/tests/errors/temporary_alias_escape.zc @@ -0,0 +1,7 @@ +namespace; +include rt; + +// expect: a borrowed temporary cannot escape its expression +char*.u message() { return rt.new(2); } +char* alias(char* p) { return p; } +fn main() { char* escaped = alias(message()) + 1; rt.put_s(escaped); } diff --git a/tests/errors/temporary_alias_retained.zc b/tests/errors/temporary_alias_retained.zc new file mode 100644 index 0000000..46bdfa6 --- /dev/null +++ b/tests/errors/temporary_alias_retained.zc @@ -0,0 +1,9 @@ +namespace; +include rt; + +// expect: a temporary argument requires a proved non-escaping borrow +char* retained; +char*.u message() { return rt.new(2); } +char* alias(char* p) { return p; } +fn store(char* p) { retained = p; } +fn main() { store(alias(message())); } diff --git a/tests/errors/temporary_alias_return.zc b/tests/errors/temporary_alias_return.zc new file mode 100644 index 0000000..e0ceaf5 --- /dev/null +++ b/tests/errors/temporary_alias_return.zc @@ -0,0 +1,8 @@ +namespace; +include rt; + +// expect: a borrowed temporary cannot escape its expression through return +char*.u message() { return rt.new(2); } +char* alias(char* p) { return p; } +char* escaped() { return &alias(message())[0]; } +fn main() { rt.put_s(escaped()); } diff --git a/tests/errors/temporary_output.zc b/tests/errors/temporary_output.zc deleted file mode 100644 index 02a8a07..0000000 --- a/tests/errors/temporary_output.zc +++ /dev/null @@ -1,9 +0,0 @@ -// expect: a new object can only go straight to a d parameter; bind it to a name first -char*.u make_text(int n) { - let text = new(char, n); - return u*text; -} -fn main() { - put_s(make_text(3)); - return 0; -} diff --git a/tests/errors/temporary_retained.zc b/tests/errors/temporary_retained.zc new file mode 100644 index 0000000..3251a65 --- /dev/null +++ b/tests/errors/temporary_retained.zc @@ -0,0 +1,8 @@ +namespace; +include rt; + +// expect: a temporary argument requires a proved non-escaping borrow +char* retained; +char*.u message() { return rt.new(2); } +fn store(char* p) { let alias = p; retained = alias; } +fn main() { store(message()); } diff --git a/tests/errors/temporary_unknown.zc b/tests/errors/temporary_unknown.zc new file mode 100644 index 0000000..644e02a --- /dev/null +++ b/tests/errors/temporary_unknown.zc @@ -0,0 +1,7 @@ +namespace; +include rt; + +// expect: a temporary argument requires a proved non-escaping borrow +char*.u message() { return rt.new(2); } +unchecked fn unknown(char* p) { rt.put_s(p); } +fn main() { unknown(message()); } diff --git a/tests/errors/u_dropped.zc b/tests/errors/u_dropped.zc index e1ca68a..90ccc3f 100644 --- a/tests/errors/u_dropped.zc +++ b/tests/errors/u_dropped.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + // expect: x: u object must leave this block -fn main() { let x = new(3); u*x; return 0; } +fn main() { let x = rt.new(3); u*x; return 0; } diff --git a/tests/errors/u_skip_level.zc b/tests/errors/u_skip_level.zc index 6caeeb3..158e9ac 100644 --- a/tests/errors/u_skip_level.zc +++ b/tests/errors/u_skip_level.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + // expect: t: is u; it can only be handed to a variable of the enclosing block -fn main() { let r; { { let t = new(1); u*t; r = t; } } return r[0]; } +fn main() { let r; { { let t = rt.new(1); u*t; r = t; } } return r[0]; } diff --git a/tests/errors/unchecked_touch.zc b/tests/errors/unchecked_touch.zc index 141639d..b2e0edf 100644 --- a/tests/errors/unchecked_touch.zc +++ b/tests/errors/unchecked_touch.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + // expect: x: is tracked by the analyzer and can't be used in unchecked code -fn main() { let x = new(1); unchecked { x[0] = 1; } return 0; } +fn main() { let x = rt.new(1); unchecked { x[0] = 1; } return 0; } diff --git a/tests/errors/unused.zc b/tests/errors/unused.zc index 64aff1f..5c88ce3 100644 --- a/tests/errors/unused.zc +++ b/tests/errors/unused.zc @@ -1,2 +1,5 @@ -// expect: x: m object is never used -fn main() { let x = new(3); return 0; } +namespace; +include rt; + +// expect: x is never read +fn main() { let x = rt.new(3); return 0; } diff --git a/tests/errors/use_after_give.zc b/tests/errors/use_after_give.zc index 8a10922..72d3444 100644 --- a/tests/errors/use_after_give.zc +++ b/tests/errors/use_after_give.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // expect: x: used after it was given away fn take(.d p) { return p[0]; } -fn main() { let x = new(3); take(d*x); return x[0]; } +fn main() { let x = rt.new(3); take(d*x); return x[0]; } diff --git a/tests/errors/use_after_inferred_give.zc b/tests/errors/use_after_inferred_give.zc new file mode 100644 index 0000000..19841cb --- /dev/null +++ b/tests/errors/use_after_inferred_give.zc @@ -0,0 +1,6 @@ +namespace; +include rt; + +// expect: x: used after it was given away; owner x was transferred on line 6 (declared on line 6) +fn take(.d p) { return p[0]; } +fn main() { let x = rt.new(3); take(x); return x[0]; } diff --git a/tests/errors/use_after_replace_move.zc b/tests/errors/use_after_replace_move.zc new file mode 100644 index 0000000..81f5833 --- /dev/null +++ b/tests/errors/use_after_replace_move.zc @@ -0,0 +1,10 @@ +namespace; +include rt; + +// expect: replacement: used after it was given away +fn main() { + let target = rt.new(1), replacement = rt.new(1); + rt.free(target); target = replacement; + rt.put_n(target[0]); + return replacement[0]; +} diff --git a/tests/icon/accept/bound_global.expected b/tests/icon/accept/bound_global.expected new file mode 100644 index 0000000..938d790 --- /dev/null +++ b/tests/icon/accept/bound_global.expected @@ -0,0 +1 @@ +1 10 10 diff --git a/tests/icon/accept/bound_global.zc b/tests/icon/accept/bound_global.zc new file mode 100644 index 0000000..f49a76b --- /dev/null +++ b/tests/icon/accept/bound_global.zc @@ -0,0 +1,13 @@ +include rt; +include run; +include icon; +int counter = 6; +fn main() { + let q = run.tokens("counter += 4; return counter;"); + let frame = icon.begin(q); + rt.put_n(icon.bind_global(frame, "counter")); rt.put_c(' '); + icon.resume(frame, 3); + rt.put_n(counter); rt.put_c(' '); rt.put_n(icon.result(frame)); rt.put_c(10); + icon.close(d*frame); + return 0; +} diff --git a/tests/icon/accept/branch_edit.expected b/tests/icon/accept/branch_edit.expected new file mode 100644 index 0000000..9c1b765 --- /dev/null +++ b/tests/icon/accept/branch_edit.expected @@ -0,0 +1 @@ +9 7 diff --git a/tests/icon/accept/branch_edit.zc b/tests/icon/accept/branch_edit.zc new file mode 100644 index 0000000..b360a6a --- /dev/null +++ b/tests/icon/accept/branch_edit.zc @@ -0,0 +1,11 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("if (1) { return 7; } else { return 9; }"); + let pattern = run.tokens("if ($expr:condition) { $stmt:yes } else { $stmt:no }"); + let replacement = run.tokens("if ($condition) { $no } else { $yes }"); + let edited = icon.rewrite(q, pattern, replacement); + rt.put_n(run.eval(edited)); rt.put_c(' '); rt.put_n(run.eval(q)); rt.put_c(10); + return 0; +} diff --git a/tests/icon/accept/calculate.expected b/tests/icon/accept/calculate.expected new file mode 100644 index 0000000..d81cc07 --- /dev/null +++ b/tests/icon/accept/calculate.expected @@ -0,0 +1 @@ +42 diff --git a/tests/icon/accept/calculate.zc b/tests/icon/accept/calculate.zc new file mode 100644 index 0000000..77ba7ea --- /dev/null +++ b/tests/icon/accept/calculate.zc @@ -0,0 +1,8 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("(3 + 4) * (8 - 2)"); + rt.put_n(icon.calculate(q, 0, q[0])); rt.put_c(10); + return 0; +} diff --git a/tests/icon/accept/edit_declaration.expected b/tests/icon/accept/edit_declaration.expected new file mode 100644 index 0000000..f599e28 --- /dev/null +++ b/tests/icon/accept/edit_declaration.expected @@ -0,0 +1 @@ +10 diff --git a/tests/icon/accept/edit_declaration.zc b/tests/icon/accept/edit_declaration.zc new file mode 100644 index 0000000..59b7eb8 --- /dev/null +++ b/tests/icon/accept/edit_declaration.zc @@ -0,0 +1,12 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("int discarded = 8; int n = 5; return n * 2;"); + let a = icon.undeclare(q, "discarded"); + let type = run.tokens("i32"), block = run.tokens("int extra = 2;"); + let b = icon.change_type(a, "n", type); + let c = icon.insert_block(b, 0, block); + rt.put_n(run.eval(c)); rt.put_c(10); + return 0; +} diff --git a/tests/icon/accept/function_step.expected b/tests/icon/accept/function_step.expected new file mode 100644 index 0000000..c2096ed --- /dev/null +++ b/tests/icon/accept/function_step.expected @@ -0,0 +1 @@ +13 5 diff --git a/tests/icon/accept/function_step.zc b/tests/icon/accept/function_step.zc new file mode 100644 index 0000000..6bdf344 --- /dev/null +++ b/tests/icon/accept/function_step.zc @@ -0,0 +1,12 @@ +include rt; +include run; +include icon; +int target(int value) { int n = value * 2; return n + 1; } +fn main() { + let frame = icon.begin_function(target); + icon.set_local(frame, "value", 6); + icon.resume(frame, 8); + rt.put_n(icon.result(frame)); rt.put_c(' '); rt.put_n(target(2)); rt.put_c(10); + icon.close(d*frame); + return 0; +} diff --git a/tests/icon/accept/literal_replace.expected b/tests/icon/accept/literal_replace.expected new file mode 100644 index 0000000..9c67935 --- /dev/null +++ b/tests/icon/accept/literal_replace.expected @@ -0,0 +1 @@ +1 4 diff --git a/tests/icon/accept/literal_replace.zc b/tests/icon/accept/literal_replace.zc new file mode 100644 index 0000000..70f7527 --- /dev/null +++ b/tests/icon/accept/literal_replace.zc @@ -0,0 +1,9 @@ +include rt; +include run; +include icon; +int addition() { return 7 + 3; } +fn main() { + let changed = icon.find_replace(addition, ${ $int + $int }, ${ $int - $int }); + rt.put_n(changed); rt.put_c(' '); rt.put_n(addition()); rt.put_c(10); + return 0; +} diff --git a/tests/icon/accept/loop_step.expected b/tests/icon/accept/loop_step.expected new file mode 100644 index 0000000..15d5109 --- /dev/null +++ b/tests/icon/accept/loop_step.expected @@ -0,0 +1 @@ +1 8 1 diff --git a/tests/icon/accept/loop_step.zc b/tests/icon/accept/loop_step.zc new file mode 100644 index 0000000..7a0368e --- /dev/null +++ b/tests/icon/accept/loop_step.zc @@ -0,0 +1,12 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("int n = 0; while (n < limit) { n += 1; } if (n == 4) { n *= 2; } else { n = 99; } return n;"); + let frame = icon.begin(q); + icon.set_global(frame, "limit", 4); + icon.resume(frame, 3); rt.put_n(icon.local(frame, "n")); rt.put_c(' '); + icon.resume(frame, 30); rt.put_n(icon.result(frame)); rt.put_c(' '); rt.put_n(icon.state(frame)); rt.put_c(10); + icon.close(d*frame); + return 0; +} diff --git a/tests/icon/accept/named_capture.expected b/tests/icon/accept/named_capture.expected new file mode 100644 index 0000000..45a4fb7 --- /dev/null +++ b/tests/icon/accept/named_capture.expected @@ -0,0 +1 @@ +8 diff --git a/tests/icon/accept/named_capture.zc b/tests/icon/accept/named_capture.zc new file mode 100644 index 0000000..258004d --- /dev/null +++ b/tests/icon/accept/named_capture.zc @@ -0,0 +1,10 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("return (2 + 5) * (2 + 5);"); + let p = run.tokens("$expr:x * $expr:x"), r = run.tokens("$x + 1"); + let changed = icon.rewrite(q, p, r); + rt.put_n(run.eval(changed)); rt.put_c(10); + return 0; +} diff --git a/tests/icon/accept/nested_declaration.expected b/tests/icon/accept/nested_declaration.expected new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/tests/icon/accept/nested_declaration.expected @@ -0,0 +1 @@ +5 diff --git a/tests/icon/accept/nested_declaration.zc b/tests/icon/accept/nested_declaration.zc new file mode 100644 index 0000000..9b360e8 --- /dev/null +++ b/tests/icon/accept/nested_declaration.zc @@ -0,0 +1,11 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("int n = 2; { int unused = 9; int added = 3; n += added; } return n;"); + let type = run.tokens("i32"); + let a = icon.undeclare(q, "unused"); + let b = icon.change_type(a, "added", type); + rt.put_n(run.eval(b)); rt.put_c(10); + return 0; +} diff --git a/tests/icon/accept/step.expected b/tests/icon/accept/step.expected new file mode 100644 index 0000000..a370d71 --- /dev/null +++ b/tests/icon/accept/step.expected @@ -0,0 +1 @@ +4 12 14 diff --git a/tests/icon/accept/step.zc b/tests/icon/accept/step.zc new file mode 100644 index 0000000..d394e1e --- /dev/null +++ b/tests/icon/accept/step.zc @@ -0,0 +1,12 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("int n = 4; n = n * 3; return n + bias;"); + let frame = icon.begin(q); + icon.set_global(frame, "bias", 2); + icon.step(frame); rt.put_n(icon.local(frame, "n")); rt.put_c(' '); + icon.step(frame); rt.put_n(icon.local(frame, "n")); rt.put_c(' '); + icon.step(frame); rt.put_n(icon.result(frame)); rt.put_c(10); + return 0; +} diff --git a/tests/icon/accept/token_replace.expected b/tests/icon/accept/token_replace.expected new file mode 100644 index 0000000..8351c19 --- /dev/null +++ b/tests/icon/accept/token_replace.expected @@ -0,0 +1 @@ +14 diff --git a/tests/icon/accept/token_replace.zc b/tests/icon/accept/token_replace.zc new file mode 100644 index 0000000..bf80548 --- /dev/null +++ b/tests/icon/accept/token_replace.zc @@ -0,0 +1,11 @@ +include rt; +include run; +include icon; +fn main() { + let source = run.tokens("return (9 + 2) * 3;"); + let pattern = run.tokens("$expr:left * $int:right"); + let replacement = run.tokens("$left + $right"); + let edited = icon.rewrite(source, pattern, replacement); + rt.put_n(run.eval(edited)); rt.put_c(10); + return 0; +} diff --git a/tests/icon/regression/block_capture_boundary.expected b/tests/icon/regression/block_capture_boundary.expected new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/tests/icon/regression/block_capture_boundary.expected @@ -0,0 +1 @@ +7 diff --git a/tests/icon/regression/block_capture_boundary.zc b/tests/icon/regression/block_capture_boundary.zc new file mode 100644 index 0000000..29925c4 --- /dev/null +++ b/tests/icon/regression/block_capture_boundary.zc @@ -0,0 +1,10 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("{ return 1; } { return 2; } ;"); + let p = run.tokens("$block:x ;"), replacement = run.tokens("{}"); + let r = icon.rewrite(q, p, replacement); + rt.put_n(r[0]); rt.put_c(10); + return 0; +} diff --git a/tests/icon/regression/frame_cleanup.expected b/tests/icon/regression/frame_cleanup.expected new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/tests/icon/regression/frame_cleanup.expected @@ -0,0 +1 @@ +1 diff --git a/tests/icon/regression/frame_cleanup.zc b/tests/icon/regression/frame_cleanup.zc new file mode 100644 index 0000000..b55c27e --- /dev/null +++ b/tests/icon/regression/frame_cleanup.zc @@ -0,0 +1,16 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens(""); + int before = rt.live_objects(), i = 0; + while (i < 100) { + let frame = icon.begin(q); + icon.set_local(frame, "local", i); + icon.set_global(frame, "global", i + 1); + icon.close(d*frame); + i++; + } + rt.put_n(rt.live_objects() == before); rt.put_c(10); + return 0; +} diff --git a/tests/icon/regression/inspect_modify_resume.expected b/tests/icon/regression/inspect_modify_resume.expected new file mode 100644 index 0000000..90619d0 --- /dev/null +++ b/tests/icon/regression/inspect_modify_resume.expected @@ -0,0 +1 @@ +8 3 diff --git a/tests/icon/regression/inspect_modify_resume.zc b/tests/icon/regression/inspect_modify_resume.zc new file mode 100644 index 0000000..b9bfad1 --- /dev/null +++ b/tests/icon/regression/inspect_modify_resume.zc @@ -0,0 +1,12 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("int n = 1; while (n < 3) { n++; } return n;"); + let frame = icon.begin(q); + icon.step(frame); icon.set_local(frame, "n", 8); + icon.resume(frame, 5); + rt.put_n(icon.result(frame)); rt.put_c(' '); rt.put_n(icon.steps(frame)); rt.put_c(10); + icon.close(d*frame); + return 0; +} diff --git a/tests/icon/regression/literal_capture.expected b/tests/icon/regression/literal_capture.expected new file mode 100644 index 0000000..d81cc07 --- /dev/null +++ b/tests/icon/regression/literal_capture.expected @@ -0,0 +1 @@ +42 diff --git a/tests/icon/regression/literal_capture.zc b/tests/icon/regression/literal_capture.zc new file mode 100644 index 0000000..baf0385 --- /dev/null +++ b/tests/icon/regression/literal_capture.zc @@ -0,0 +1,11 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("return \"return\";"); + let p = run.tokens("return $expr:x;"); + let replacement = run.tokens("return 42;"); + let r = icon.rewrite(q, p, replacement); + rt.put_n(run.eval(r)); rt.put_c(10); + return 0; +} diff --git a/tests/icon/regression/mismatch_capture.expected b/tests/icon/regression/mismatch_capture.expected new file mode 100644 index 0000000..f6b91e0 --- /dev/null +++ b/tests/icon/regression/mismatch_capture.expected @@ -0,0 +1 @@ +56 diff --git a/tests/icon/regression/mismatch_capture.zc b/tests/icon/regression/mismatch_capture.zc new file mode 100644 index 0000000..53fb3c6 --- /dev/null +++ b/tests/icon/regression/mismatch_capture.zc @@ -0,0 +1,10 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("return (2 + 5) * (2 + 6);"); + let pattern = run.tokens("$expr:x * $expr:x"), replacement = run.tokens("$x + 1"); + let edited = icon.rewrite(q, pattern, replacement); + rt.put_n(run.eval(edited)); rt.put_c(10); + return 0; +} diff --git a/tests/icon/regression/no_match.expected b/tests/icon/regression/no_match.expected new file mode 100644 index 0000000..cfe9b7e --- /dev/null +++ b/tests/icon/regression/no_match.expected @@ -0,0 +1,2 @@ +12 +1 diff --git a/tests/icon/regression/no_match.zc b/tests/icon/regression/no_match.zc new file mode 100644 index 0000000..5686c4a --- /dev/null +++ b/tests/icon/regression/no_match.zc @@ -0,0 +1,11 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("return 12;"); + let pattern = run.tokens("1 + 2"), replacement = run.tokens("5"); + let r = icon.rewrite(q, pattern, replacement); + rt.put_n(run.eval(r)); rt.put_c(10); + rt.put_n(q[0] == r[0]); rt.put_c(10); + return 0; +} diff --git a/tests/icon/regression/preserve_const.expected b/tests/icon/regression/preserve_const.expected new file mode 100644 index 0000000..2fb73a0 --- /dev/null +++ b/tests/icon/regression/preserve_const.expected @@ -0,0 +1 @@ +1 1 diff --git a/tests/icon/regression/preserve_const.zc b/tests/icon/regression/preserve_const.zc new file mode 100644 index 0000000..4824413 --- /dev/null +++ b/tests/icon/regression/preserve_const.zc @@ -0,0 +1,9 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("const int total = 3;"), type = run.tokens("i32"); + let edited = icon.change_type(q, "total", type); + rt.put_n(edited[2] == run.intern("const")); rt.put_c(' '); rt.put_n(edited[5] == run.intern("i32")); rt.put_c(10); + return 0; +} diff --git a/tests/icon/regression/repeated_publication.expected b/tests/icon/regression/repeated_publication.expected new file mode 100644 index 0000000..f847703 --- /dev/null +++ b/tests/icon/regression/repeated_publication.expected @@ -0,0 +1 @@ +1 8 1 9 diff --git a/tests/icon/regression/repeated_publication.zc b/tests/icon/regression/repeated_publication.zc new file mode 100644 index 0000000..b8f4268 --- /dev/null +++ b/tests/icon/regression/repeated_publication.zc @@ -0,0 +1,10 @@ +include rt; +include run; +include icon; +int target() { return 7; } +fn main() { + let first = run.tokens("7"), second = run.tokens("8"), third = run.tokens("9"); + rt.put_n(icon.find_replace(target, first, second)); rt.put_c(' '); rt.put_n(target()); rt.put_c(' '); + rt.put_n(icon.find_replace(target, second, third)); rt.put_c(' '); rt.put_n(target()); rt.put_c(10); + return 0; +} diff --git a/tests/icon/regression/replace_all.expected b/tests/icon/regression/replace_all.expected new file mode 100644 index 0000000..d0f5ee9 --- /dev/null +++ b/tests/icon/regression/replace_all.expected @@ -0,0 +1 @@ +-2 24 diff --git a/tests/icon/regression/replace_all.zc b/tests/icon/regression/replace_all.zc new file mode 100644 index 0000000..acb4458 --- /dev/null +++ b/tests/icon/regression/replace_all.zc @@ -0,0 +1,10 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("return (1 + 2) * (5 + 3);"); + let pattern = run.tokens("$int + $int"), replacement = run.tokens("$int - $int"); + let edited = icon.rewrite(q, pattern, replacement); + rt.put_n(run.eval(edited)); rt.put_c(' '); rt.put_n(run.eval(q)); rt.put_c(10); + return 0; +} diff --git a/tests/icon/regression/shadow.expected b/tests/icon/regression/shadow.expected new file mode 100644 index 0000000..606c19a --- /dev/null +++ b/tests/icon/regression/shadow.expected @@ -0,0 +1 @@ +3 1 diff --git a/tests/icon/regression/shadow.zc b/tests/icon/regression/shadow.zc new file mode 100644 index 0000000..ca4220d --- /dev/null +++ b/tests/icon/regression/shadow.zc @@ -0,0 +1,10 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("int n = 3; { int n = 8; n++; } return n;"); + let frame = icon.begin(q); + icon.resume(frame, 10); rt.put_n(icon.result(frame)); rt.put_c(' '); rt.put_n(icon.local_count(frame)); rt.put_c(10); + icon.close(d*frame); + return 0; +} diff --git a/tests/icon/regression/statement_capture_boundary.expected b/tests/icon/regression/statement_capture_boundary.expected new file mode 100644 index 0000000..1e8b314 --- /dev/null +++ b/tests/icon/regression/statement_capture_boundary.expected @@ -0,0 +1 @@ +6 diff --git a/tests/icon/regression/statement_capture_boundary.zc b/tests/icon/regression/statement_capture_boundary.zc new file mode 100644 index 0000000..8316eb9 --- /dev/null +++ b/tests/icon/regression/statement_capture_boundary.zc @@ -0,0 +1,10 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("return 1; return 2; tail;"); + let p = run.tokens("$stmt:x tail;"), replacement = run.tokens("return 9;"); + let r = icon.rewrite(q, p, replacement); + rt.put_n(r[0]); rt.put_c(10); + return 0; +} diff --git a/tests/icon/regression/status_clear.expected b/tests/icon/regression/status_clear.expected new file mode 100644 index 0000000..87bde8e --- /dev/null +++ b/tests/icon/regression/status_clear.expected @@ -0,0 +1 @@ +0 3 0 diff --git a/tests/icon/regression/status_clear.zc b/tests/icon/regression/status_clear.zc new file mode 100644 index 0000000..89e8535 --- /dev/null +++ b/tests/icon/regression/status_clear.zc @@ -0,0 +1,14 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens(""); + let f = icon.begin(q); + icon.local(f, "missing"); + icon.set_local(f, "n", 3); + rt.put_n(icon.error()); rt.put_c(' '); + icon.local(f, "missing"); + rt.put_n(icon.local(f, "n")); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(10); + icon.close(d*f); + return 0; +} diff --git a/tests/icon/regression/strings.expected b/tests/icon/regression/strings.expected new file mode 100644 index 0000000..c1eac1e --- /dev/null +++ b/tests/icon/regression/strings.expected @@ -0,0 +1,2 @@ +5 5 11 +1 diff --git a/tests/icon/regression/strings.zc b/tests/icon/regression/strings.zc new file mode 100644 index 0000000..051a29d --- /dev/null +++ b/tests/icon/regression/strings.zc @@ -0,0 +1,12 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("return 8 + 3;"), p = run.tokens("$int + $int"), r = run.tokens("$int - $int"); + let a = icon.rewrite(q, p, r), b = icon.rewrite(q, p, r); + rt.put_n(run.eval(a)); rt.put_c(' '); rt.put_n(run.eval(b)); rt.put_c(' '); rt.put_n(run.eval(q)); rt.put_c(10); + let text = run.tokens("return \"{} 8 + 3\";"); + let same = icon.rewrite(text, p, r); + rt.put_n(text[0] == same[0]); rt.put_c(10); + return 0; +} diff --git a/tests/icon/reject/bad_delimiter_replacement.expected b/tests/icon/reject/bad_delimiter_replacement.expected new file mode 100644 index 0000000..363c912 --- /dev/null +++ b/tests/icon/reject/bad_delimiter_replacement.expected @@ -0,0 +1 @@ +1 1 3 diff --git a/tests/icon/reject/bad_delimiter_replacement.zc b/tests/icon/reject/bad_delimiter_replacement.zc new file mode 100644 index 0000000..faffd90 --- /dev/null +++ b/tests/icon/reject/bad_delimiter_replacement.zc @@ -0,0 +1,9 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("return 3;"), pattern = run.tokens("$int"), replacement = run.tokens("("); + let edited = icon.rewrite(q, pattern, replacement); + rt.put_n(edited == 0); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(' '); rt.put_n(run.eval(q)); rt.put_c(10); + return 0; +} diff --git a/tests/icon/reject/bind_function.expected b/tests/icon/reject/bind_function.expected new file mode 100644 index 0000000..65cd68b --- /dev/null +++ b/tests/icon/reject/bind_function.expected @@ -0,0 +1 @@ +0 2 diff --git a/tests/icon/reject/bind_function.zc b/tests/icon/reject/bind_function.zc new file mode 100644 index 0000000..1348c90 --- /dev/null +++ b/tests/icon/reject/bind_function.zc @@ -0,0 +1,10 @@ +include rt; +include run; +include icon; +int target() { return 1; } +fn main() { + let q = run.tokens("return 0;"), frame = icon.begin(q); + rt.put_n(icon.bind_global(frame, "target")); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(10); + icon.close(d*frame); + return 0; +} diff --git a/tests/icon/reject/bind_pointer.expected b/tests/icon/reject/bind_pointer.expected new file mode 100644 index 0000000..6517ee9 --- /dev/null +++ b/tests/icon/reject/bind_pointer.expected @@ -0,0 +1 @@ +0 2 preserved diff --git a/tests/icon/reject/bind_pointer.zc b/tests/icon/reject/bind_pointer.zc new file mode 100644 index 0000000..4834dde --- /dev/null +++ b/tests/icon/reject/bind_pointer.zc @@ -0,0 +1,10 @@ +include rt; +include run; +include icon; +char* message = "preserved"; +fn main() { + let q = run.tokens("return 0;"), frame = icon.begin(q); + rt.put_n(icon.bind_global(frame, "message")); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(' '); rt.put_s(message); rt.put_c(10); + icon.close(d*frame); + return 0; +} diff --git a/tests/icon/reject/expression_boundary.expected b/tests/icon/reject/expression_boundary.expected new file mode 100644 index 0000000..65cd68b --- /dev/null +++ b/tests/icon/reject/expression_boundary.expected @@ -0,0 +1 @@ +0 2 diff --git a/tests/icon/reject/expression_boundary.zc b/tests/icon/reject/expression_boundary.zc new file mode 100644 index 0000000..5c84dbd --- /dev/null +++ b/tests/icon/reject/expression_boundary.zc @@ -0,0 +1,8 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("(7)"); + rt.put_n(icon.calculate(q, 0, 2)); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(10); + return 0; +} diff --git a/tests/icon/reject/implicit_global.expected b/tests/icon/reject/implicit_global.expected new file mode 100644 index 0000000..3ff996e --- /dev/null +++ b/tests/icon/reject/implicit_global.expected @@ -0,0 +1 @@ +0 2 5 diff --git a/tests/icon/reject/implicit_global.zc b/tests/icon/reject/implicit_global.zc new file mode 100644 index 0000000..ce8e25d --- /dev/null +++ b/tests/icon/reject/implicit_global.zc @@ -0,0 +1,11 @@ +include rt; +include run; +include icon; +int hidden = 41; +fn main() { + let q = run.tokens("return hidden;"); + let f = icon.begin(q); + rt.put_n(icon.step(f)); rt.put_c(' '); rt.put_n(icon.state(f)); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(10); + icon.close(d*f); + return 0; +} diff --git a/tests/icon/reject/incomplete_expression.expected b/tests/icon/reject/incomplete_expression.expected new file mode 100644 index 0000000..65cd68b --- /dev/null +++ b/tests/icon/reject/incomplete_expression.expected @@ -0,0 +1 @@ +0 2 diff --git a/tests/icon/reject/incomplete_expression.zc b/tests/icon/reject/incomplete_expression.zc new file mode 100644 index 0000000..8a0366c --- /dev/null +++ b/tests/icon/reject/incomplete_expression.zc @@ -0,0 +1,8 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("1 +"); + rt.put_n(icon.calculate(q, 0, q[0])); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(10); + return 0; +} diff --git a/tests/icon/reject/multiple_bindings.expected b/tests/icon/reject/multiple_bindings.expected new file mode 100644 index 0000000..43cebf1 --- /dev/null +++ b/tests/icon/reject/multiple_bindings.expected @@ -0,0 +1 @@ +1 2 2 diff --git a/tests/icon/reject/multiple_bindings.zc b/tests/icon/reject/multiple_bindings.zc new file mode 100644 index 0000000..2b7b5e0 --- /dev/null +++ b/tests/icon/reject/multiple_bindings.zc @@ -0,0 +1,9 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("int a = 1, b = 2; return b;"); + let changed = icon.undeclare(q, "a"); + rt.put_n(changed == 0); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(' '); rt.put_n(run.eval(q)); rt.put_c(10); + return 0; +} diff --git a/tests/icon/reject/non_declaration.expected b/tests/icon/reject/non_declaration.expected new file mode 100644 index 0000000..8d04f96 --- /dev/null +++ b/tests/icon/reject/non_declaration.expected @@ -0,0 +1 @@ +1 2 diff --git a/tests/icon/reject/non_declaration.zc b/tests/icon/reject/non_declaration.zc new file mode 100644 index 0000000..b4233f9 --- /dev/null +++ b/tests/icon/reject/non_declaration.zc @@ -0,0 +1,9 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("rt.put_n(n); return 0;"); + let changed = icon.undeclare(q, "n"); + rt.put_n(changed == 0); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(10); + return 0; +} diff --git a/tests/icon/reject/null_inputs.expected b/tests/icon/reject/null_inputs.expected new file mode 100644 index 0000000..75f3fe4 --- /dev/null +++ b/tests/icon/reject/null_inputs.expected @@ -0,0 +1,4 @@ +0 2 +1 2 +1 2 +1 2 diff --git a/tests/icon/reject/null_inputs.zc b/tests/icon/reject/null_inputs.zc new file mode 100644 index 0000000..41a7c4e --- /dev/null +++ b/tests/icon/reject/null_inputs.zc @@ -0,0 +1,10 @@ +include rt; +include run; +include icon; +fn main() { + rt.put_n(icon.calculate(0, 0, 1)); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(10); + let a = icon.undeclare(0, "n"); rt.put_n(a == 0); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(10); + let b = icon.change_type(0, "n", 0); rt.put_n(b == 0); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(10); + let c = icon.insert_block(0, 0, 0); rt.put_n(c == 0); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(10); + return 0; +} diff --git a/tests/icon/reject/publication_return_type.expected b/tests/icon/reject/publication_return_type.expected new file mode 100644 index 0000000..3f070c0 --- /dev/null +++ b/tests/icon/reject/publication_return_type.expected @@ -0,0 +1 @@ +0 4 diff --git a/tests/icon/reject/publication_return_type.zc b/tests/icon/reject/publication_return_type.zc new file mode 100644 index 0000000..03822c5 --- /dev/null +++ b/tests/icon/reject/publication_return_type.zc @@ -0,0 +1,11 @@ +include rt; +include run; +include icon; +typecheck on; +int target() { return 4; } +fn main() { + let pattern = run.tokens("return $int;"), replacement = run.tokens("return \"invalid\";"); + rt.put_n(icon.find_replace(target, pattern, replacement)); rt.put_c(' '); + rt.put_n(target()); rt.put_c(10); + return 0; +} diff --git a/tests/icon/reject/publication_signature.expected b/tests/icon/reject/publication_signature.expected new file mode 100644 index 0000000..3f070c0 --- /dev/null +++ b/tests/icon/reject/publication_signature.expected @@ -0,0 +1 @@ +0 4 diff --git a/tests/icon/reject/publication_signature.zc b/tests/icon/reject/publication_signature.zc new file mode 100644 index 0000000..01646e0 --- /dev/null +++ b/tests/icon/reject/publication_signature.zc @@ -0,0 +1,11 @@ +include rt; +include run; +include icon; +typecheck on; +int target(int value) { return value + 1; } +fn main() { + let replacement = run.tokens("int target(char* value) { return 9; }"); + rt.put_n(run.replace_function(target, replacement)); rt.put_c(' '); + rt.put_n(target(3)); rt.put_c(10); + return 0; +} diff --git a/tests/icon/reject/range.expected b/tests/icon/reject/range.expected new file mode 100644 index 0000000..8d04f96 --- /dev/null +++ b/tests/icon/reject/range.expected @@ -0,0 +1 @@ +1 2 diff --git a/tests/icon/reject/range.zc b/tests/icon/reject/range.zc new file mode 100644 index 0000000..42bc0f4 --- /dev/null +++ b/tests/icon/reject/range.zc @@ -0,0 +1,9 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("return 1;"), r = run.tokens("2"); + let changed = icon.splice(q, -1, 1, r); + rt.put_n(changed == 0); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(10); + return 0; +} diff --git a/tests/icon/reject/sized_frame.expected b/tests/icon/reject/sized_frame.expected new file mode 100644 index 0000000..a57b07e --- /dev/null +++ b/tests/icon/reject/sized_frame.expected @@ -0,0 +1 @@ +2 5 diff --git a/tests/icon/reject/sized_frame.zc b/tests/icon/reject/sized_frame.zc new file mode 100644 index 0000000..43e44fa --- /dev/null +++ b/tests/icon/reject/sized_frame.zc @@ -0,0 +1,11 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("i8 n = 300; return n;"); + let frame = icon.begin(q); + icon.step(frame); + rt.put_n(icon.state(frame)); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(10); + icon.close(d*frame); + return 0; +} diff --git a/tests/icon/reject/step_else_side_effect.expected b/tests/icon/reject/step_else_side_effect.expected new file mode 100644 index 0000000..c24938b --- /dev/null +++ b/tests/icon/reject/step_else_side_effect.expected @@ -0,0 +1 @@ +0 2 0 diff --git a/tests/icon/reject/step_else_side_effect.zc b/tests/icon/reject/step_else_side_effect.zc new file mode 100644 index 0000000..43c0d5d --- /dev/null +++ b/tests/icon/reject/step_else_side_effect.zc @@ -0,0 +1,12 @@ +include rt; +include run; +include icon; +int calls; +int condition() { calls++; return 1; } +fn main() { + let q = run.tokens("if (condition()) { return 1; } else return 2;"); + let f = icon.begin(q); + rt.put_n(icon.step(f)); rt.put_c(' '); rt.put_n(icon.state(f)); rt.put_c(' '); rt.put_n(calls); rt.put_c(10); + icon.close(d*f); + return 0; +} diff --git a/tests/icon/reject/step_local_mutation.expected b/tests/icon/reject/step_local_mutation.expected new file mode 100644 index 0000000..f8c518e --- /dev/null +++ b/tests/icon/reject/step_local_mutation.expected @@ -0,0 +1 @@ +0 2 1 diff --git a/tests/icon/reject/step_local_mutation.zc b/tests/icon/reject/step_local_mutation.zc new file mode 100644 index 0000000..6ef0a51 --- /dev/null +++ b/tests/icon/reject/step_local_mutation.zc @@ -0,0 +1,11 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("int n = 1; return n++;"); + let f = icon.begin(q); + icon.step(f); + rt.put_n(icon.step(f)); rt.put_c(' '); rt.put_n(icon.state(f)); rt.put_c(' '); rt.put_n(icon.local(f, "n")); rt.put_c(10); + icon.close(d*f); + return 0; +} diff --git a/tests/icon/reject/step_multiple_bindings.expected b/tests/icon/reject/step_multiple_bindings.expected new file mode 100644 index 0000000..057d934 --- /dev/null +++ b/tests/icon/reject/step_multiple_bindings.expected @@ -0,0 +1 @@ +0 2 5 0 diff --git a/tests/icon/reject/step_multiple_bindings.zc b/tests/icon/reject/step_multiple_bindings.zc new file mode 100644 index 0000000..d1dba73 --- /dev/null +++ b/tests/icon/reject/step_multiple_bindings.zc @@ -0,0 +1,10 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("int n = 1, m = 2;"); + let f = icon.begin(q); + rt.put_n(icon.step(f)); rt.put_c(' '); rt.put_n(icon.state(f)); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(' '); rt.put_n(icon.local_count(f)); rt.put_c(10); + icon.close(d*f); + return 0; +} diff --git a/tests/icon/reject/type_name_binding.expected b/tests/icon/reject/type_name_binding.expected new file mode 100644 index 0000000..8d04f96 --- /dev/null +++ b/tests/icon/reject/type_name_binding.expected @@ -0,0 +1 @@ +1 2 diff --git a/tests/icon/reject/type_name_binding.zc b/tests/icon/reject/type_name_binding.zc new file mode 100644 index 0000000..7ee62a9 --- /dev/null +++ b/tests/icon/reject/type_name_binding.zc @@ -0,0 +1,9 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("int* pointer;"); + let r = icon.undeclare(q, "int"); + rt.put_n(r == 0); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(10); + return 0; +} diff --git a/tests/icon/reject/unbalanced.expected b/tests/icon/reject/unbalanced.expected new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/icon/reject/unbalanced.expected @@ -0,0 +1 @@ +0 diff --git a/tests/icon/reject/unbalanced.zc b/tests/icon/reject/unbalanced.zc new file mode 100644 index 0000000..d6ab110 --- /dev/null +++ b/tests/icon/reject/unbalanced.zc @@ -0,0 +1,8 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("return (1 + 2;"); + rt.put_n(icon.validate(q)); rt.put_c(10); + return 0; +} diff --git a/tests/icon/reject/unknown_pattern.expected b/tests/icon/reject/unknown_pattern.expected new file mode 100644 index 0000000..2fb73a0 --- /dev/null +++ b/tests/icon/reject/unknown_pattern.expected @@ -0,0 +1 @@ +1 1 diff --git a/tests/icon/reject/unknown_pattern.zc b/tests/icon/reject/unknown_pattern.zc new file mode 100644 index 0000000..74850c3 --- /dev/null +++ b/tests/icon/reject/unknown_pattern.zc @@ -0,0 +1,9 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("return 1;"), pattern = run.tokens("$unknown"), replacement = run.tokens("2"); + let changed = icon.rewrite(q, pattern, replacement); + rt.put_n(changed == 0); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(10); + return 0; +} diff --git a/tests/icon/reject/unresolved_capture.expected b/tests/icon/reject/unresolved_capture.expected new file mode 100644 index 0000000..4ef63d7 --- /dev/null +++ b/tests/icon/reject/unresolved_capture.expected @@ -0,0 +1 @@ +1 3 3 diff --git a/tests/icon/reject/unresolved_capture.zc b/tests/icon/reject/unresolved_capture.zc new file mode 100644 index 0000000..71584e3 --- /dev/null +++ b/tests/icon/reject/unresolved_capture.zc @@ -0,0 +1,9 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("return 1 + 2;"), p = run.tokens("$int + $int"), r = run.tokens("$missing"); + let changed = icon.rewrite(q, p, r); + rt.put_n(changed == 0); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(' '); rt.put_n(run.eval(q)); rt.put_c(10); + return 0; +} diff --git a/tests/icon/reject/unsupported_step.expected b/tests/icon/reject/unsupported_step.expected new file mode 100644 index 0000000..a57b07e --- /dev/null +++ b/tests/icon/reject/unsupported_step.expected @@ -0,0 +1 @@ +2 5 diff --git a/tests/icon/reject/unsupported_step.zc b/tests/icon/reject/unsupported_step.zc new file mode 100644 index 0000000..944c5b6 --- /dev/null +++ b/tests/icon/reject/unsupported_step.zc @@ -0,0 +1,10 @@ +include rt; +include run; +include icon; +fn main() { + let q = run.tokens("for (int i = 0; i < 2; i++) {}"); + let frame = icon.begin(q); + icon.step(frame); rt.put_n(icon.state(frame)); rt.put_c(' '); rt.put_n(icon.error()); rt.put_c(10); + icon.close(d*frame); + return 0; +} diff --git a/tests/langerrors/array_designator_negative.zc b/tests/langerrors/array_designator_negative.zc new file mode 100644 index 0000000..f779bc1 --- /dev/null +++ b/tests/langerrors/array_designator_negative.zc @@ -0,0 +1,4 @@ +namespace; + +// expect: a negative array designator +fn main() { int[] values = { [-1] = 2 }; return 0; } diff --git a/tests/langerrors/array_inferred_no_init.zc b/tests/langerrors/array_inferred_no_init.zc new file mode 100644 index 0000000..b11f731 --- /dev/null +++ b/tests/langerrors/array_inferred_no_init.zc @@ -0,0 +1,4 @@ +namespace; + +// expect: T[] needs an initializer to determine its length +fn main() { int[] values; return 0; } diff --git a/tests/langerrors/array_initializer_excess.zc b/tests/langerrors/array_initializer_excess.zc new file mode 100644 index 0000000..f727bf8 --- /dev/null +++ b/tests/langerrors/array_initializer_excess.zc @@ -0,0 +1,4 @@ +namespace; + +// expect: too many elements in array initializer +fn main() { int[2] values = {1,2,3}; return 0; } diff --git a/tests/langerrors/array_old_prefix.zc b/tests/langerrors/array_old_prefix.zc new file mode 100644 index 0000000..c427d3d --- /dev/null +++ b/tests/langerrors/array_old_prefix.zc @@ -0,0 +1,4 @@ +namespace; + +// expect: array types are postfix: T[N], T[] or T[?] +fn main() { [3]int old; return 0; } diff --git a/tests/langerrors/cfunc_name.zc b/tests/langerrors/cfunc_name.zc index 0b0b0d4..1c0ef52 100644 --- a/tests/langerrors/cfunc_name.zc +++ b/tests/langerrors/cfunc_name.zc @@ -1,2 +1,4 @@ +namespace; + // expect: isn't a function fn main() { let p = cfunc(nothere); return 0; } diff --git a/tests/langerrors/const_index.zc b/tests/langerrors/const_index.zc index 03e927b..ea99986 100644 --- a/tests/langerrors/const_index.zc +++ b/tests/langerrors/const_index.zc @@ -1,2 +1,4 @@ +namespace; + // expect: the index is out of range for this array -fn main() { [4]int a; a[4] = 1; return 0; } +fn main() { int[4] a; a[4] = 1; return 0; } diff --git a/tests/langerrors/extern_bigret.zc b/tests/langerrors/extern_bigret.zc index 37de467..38c87c8 100644 --- a/tests/langerrors/extern_bigret.zc +++ b/tests/langerrors/extern_bigret.zc @@ -1,3 +1,5 @@ +namespace; + // expect: a native call can return at most 7 words struct Big { int a; int b; int c; int d; int e; int f; int g; int h; } extern struct Big mkbig(long a); diff --git a/tests/langerrors/extern_struct.zc b/tests/langerrors/extern_struct.zc index 52d9ecc..9830766 100644 --- a/tests/langerrors/extern_struct.zc +++ b/tests/langerrors/extern_struct.zc @@ -1,3 +1,5 @@ +namespace; + // expect: isn't a zc struct extern int stat2(struct stat s); fn main() { return 0; } diff --git a/tests/langerrors/extern_type.zc b/tests/langerrors/extern_type.zc index c0655ba..a42a57e 100644 --- a/tests/langerrors/extern_type.zc +++ b/tests/langerrors/extern_type.zc @@ -1,3 +1,5 @@ +namespace; + // expect: isn't a c type extern knows extern widget frob(int); fn main() { return 0; } diff --git a/tests/langerrors/fixed_nolen.zc b/tests/langerrors/fixed_nolen.zc index b79c31e..0c53e27 100644 --- a/tests/langerrors/fixed_nolen.zc +++ b/tests/langerrors/fixed_nolen.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + // expect: a fixed array needs its length -fn main() { fixed []int f = new(fixed []int); free(d*f); return 0; } +fn main() { fixed int[?] f = rt.new(fixed int[?]); rt.free(d*f); return 0; } diff --git a/tests/langerrors/fixed_push.zc b/tests/langerrors/fixed_push.zc index a19bcb4..faba741 100644 --- a/tests/langerrors/fixed_push.zc +++ b/tests/langerrors/fixed_push.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + // expect: a fixed array can't grow or shrink -fn main() { fixed []int f = new(fixed []int, 3); push(f, 1); free(d*f); return 0; } +fn main() { fixed int[?] f = rt.new(fixed int[?], 3); push(f, 1); rt.free(d*f); return 0; } diff --git a/tests/langerrors/fixed_to_dyn.zc b/tests/langerrors/fixed_to_dyn.zc index 483f62f..8afbef6 100644 --- a/tests/langerrors/fixed_to_dyn.zc +++ b/tests/langerrors/fixed_to_dyn.zc @@ -1,4 +1,7 @@ -// expect: type error: argument: expected []int, got fixed []int +namespace; +include rt; + +// expect: type error: argument: expected int[?], got fixed int[?] typecheck on; -int take([]int.m g) { return len(g); } -fn main() { fixed []int f = new(fixed []int, 3); take(f); free(d*f); return 0; } +int take(int[?].m g) { return len(g); } +fn main() { fixed int[?] f = rt.new(fixed int[?], 3); take(f); rt.free(d*f); return 0; } diff --git a/tests/langerrors/gen_undeclared.zc b/tests/langerrors/gen_undeclared.zc index ef85a7b..dad1566 100644 --- a/tests/langerrors/gen_undeclared.zc +++ b/tests/langerrors/gen_undeclared.zc @@ -1,3 +1,5 @@ +namespace; + // expect: T: isn't a type T first(int* l) { return l[0]; } fn main() { return 0; } diff --git a/tests/langerrors/o3_addr.zc b/tests/langerrors/o3_addr.zc index 9ff029f..1028c6f 100644 --- a/tests/langerrors/o3_addr.zc +++ b/tests/langerrors/o3_addr.zc @@ -1,2 +1,4 @@ +namespace; + // expect: an o3 variable has no address fn main() { o3 int z = 2; int* q = &z; return *q; } diff --git a/tests/langerrors/o3_asm.zc b/tests/langerrors/o3_asm.zc index c8afb44..ebc223c 100644 --- a/tests/langerrors/o3_asm.zc +++ b/tests/langerrors/o3_asm.zc @@ -1,2 +1,4 @@ +namespace; + // expect: asm can't reach an o3 variable fn main() { o3 int zz = 2; asm { zz @ _ } return 0; } diff --git a/tests/langerrors/o3_let.zc b/tests/langerrors/o3_let.zc index d2aff9a..2afa952 100644 --- a/tests/langerrors/o3_let.zc +++ b/tests/langerrors/o3_let.zc @@ -1,2 +1,4 @@ +namespace; + // expect: o3 variables need a type fn main() { o3 { let x = 1; x++; } return 0; } diff --git a/tests/langerrors/o3_param.zc b/tests/langerrors/o3_param.zc index 5694720..84cb6c2 100644 --- a/tests/langerrors/o3_param.zc +++ b/tests/langerrors/o3_param.zc @@ -1,3 +1,5 @@ +namespace; + // expect: o3 parameters need a type o3 int f(x) { return x; } fn main() { return f(1); } diff --git a/tests/langerrors/parseword_noread.zc b/tests/langerrors/parseword_noread.zc index e7c5b1a..8de985c 100644 --- a/tests/langerrors/parseword_noread.zc +++ b/tests/langerrors/parseword_noread.zc @@ -1,3 +1,5 @@ +namespace; + // expect: a parse word has to read its own name // a parse word that never calls next() would be called on the same token forever immediate fn pw() { return 0; } diff --git a/tests/langerrors/pk_addr.zc b/tests/langerrors/pk_addr.zc index 17ada12..52863d7 100644 --- a/tests/langerrors/pk_addr.zc +++ b/tests/langerrors/pk_addr.zc @@ -1,3 +1,5 @@ +namespace; + // expect: has no address fn main() { u8 buf[8]; diff --git a/tests/langerrors/tagenum_args.zc b/tests/langerrors/tagenum_args.zc index 4d1850f..23fcf2b 100644 --- a/tests/langerrors/tagenum_args.zc +++ b/tests/langerrors/tagenum_args.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // expect: wrong number of arguments enum shape { circle { int r; }, dot }; -fn main() { shape* s = shape.circle(1, 2); free(d*s); return 0; } +fn main() { shape* s = shape.circle(1, 2); rt.free(d*s); return 0; } diff --git a/tests/langerrors/tagenum_bind.zc b/tests/langerrors/tagenum_bind.zc index 9fcb9bf..8a5b0e0 100644 --- a/tests/langerrors/tagenum_bind.zc +++ b/tests/langerrors/tagenum_bind.zc @@ -1,3 +1,5 @@ +namespace; + // expect: a case binds every field of its member enum shape { circle { int r; }, dot }; fn f(shape* s) { diff --git a/tests/langerrors/tagenum_value.zc b/tests/langerrors/tagenum_value.zc index dbd807e..be66bdc 100644 --- a/tests/langerrors/tagenum_value.zc +++ b/tests/langerrors/tagenum_value.zc @@ -1,3 +1,5 @@ +namespace; + // expect: a tagged enum's members take no explicit values enum shape { circle { int r; }, dot = 5 }; fn main() { return 0; } diff --git a/tests/programs/actor.zc b/tests/programs/actor.zc new file mode 100644 index 0000000..5256aca --- /dev/null +++ b/tests/programs/actor.zc @@ -0,0 +1,12 @@ +namespace; +include rt, actor; +typecheck on; +int add_two(int n) { return n + 2; } +fn main() { + actor.Actor<int,int>* a = rt.new(actor.Actor<int,int>); + let total = 0; + a.spawn(add_two); a.recv(fn(int value) { total += value; return 0; }); + a.send(2); a.send(3); a.await(); rt.put_n(total); rt.put_c(' '); + a.send(1); a.await(); rt.put_n(total); rt.put_c(' '); + a.close(); rt.put_n(a.send(4)); rt.put_c(10); return 0; +} diff --git a/tests/programs/alias.zc b/tests/programs/alias.zc index 2ebc3e5..7d394c5 100644 --- a/tests/programs/alias.zc +++ b/tests/programs/alias.zc @@ -1,21 +1,24 @@ +namespace; +include rt, run; + // a pointer computed from a live object is a borrow of it: fine while the owner lives struct node { int v; node* next; }; fn main() { - let p = new(4); + let p = rt.new(4); p[0] = 11; p[1] = 22; let q = p + 1; - put_n(q[-1]); put_c(' '); - put_n(q[0]); put_c(' '); - node* a = new(node), b = new(node); + rt.put_n(q[-1]); rt.put_c(' '); + rt.put_n(q[0]); rt.put_c(' '); + node* a = rt.new(node), b = rt.new(node); a.v = 3; b.v = 4; a.next = b; node* c = a.next; - put_n(c.v); put_c(' '); - int** list = new(int*, 2); - int* item = new(int); + rt.put_n(c.v); rt.put_c(' '); + int** list = rt.new(int*, 2); + int* item = rt.new(int); *item = 8; list[1] = item; int* e = list[1]; - put_n(*e); put_c(10); - free(d*p); free(d*a); free(d*list); + rt.put_n(*e); rt.put_c(10); + rt.free(d*p); rt.free(d*a); rt.free(d*list); return 0; } diff --git a/tests/programs/allocfast.zc b/tests/programs/allocfast.zc index 5ef2df2..d7b1d93 100644 --- a/tests/programs/allocfast.zc +++ b/tests/programs/allocfast.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // repeated allocations of a size known at compile time: new(T) of a struct of // numbers, and new(n) with a constant n. each pass of the loop makes an object // and frees it at the end of the block, so the allocator should hand back the @@ -8,7 +11,7 @@ struct line { point* a; int len; }; fn loop_leaf(int n) { let total = 0; for (int i = 0; i < n; i++) { - point* p = new(point); + point* p = rt.new(point); total += p.x + p.y; // zero from the allocator, every pass p.x = i; p.y = 2; } @@ -17,7 +20,7 @@ fn loop_leaf(int n) { fn loop_words(int n) { let total = 0, i = 0; while (i < n) { - let w = new(6); + let w = rt.new(6); total += w[0] + w[5]; w[0] = 7; w[5] = 9; i++; @@ -27,8 +30,8 @@ fn loop_words(int n) { fn loop_owner(int n) { let i = 0, total = 0; while (i < n) { - line* l = new(line); - l.a = new(point); + line* l = rt.new(line); + l.a = rt.new(point); l.a.x = i; total += l.a.x + l.len; i++; @@ -36,10 +39,10 @@ fn loop_owner(int n) { return total; } fn main() { - let before = live_objects(); - put_n(loop_leaf(50000)); put_c(' '); - put_n(loop_words(50000)); put_c(' '); - put_n(loop_owner(1000)); put_c(' '); - put_n(live_objects() - before); put_c(10); + let before = rt.live_objects(); + rt.put_n(loop_leaf(50000)); rt.put_c(' '); + rt.put_n(loop_words(50000)); rt.put_c(' '); + rt.put_n(loop_owner(1000)); rt.put_c(' '); + rt.put_n(rt.live_objects() - before); rt.put_c(10); return 0; } diff --git a/tests/programs/annotate.expected b/tests/programs/annotate.expected index 553353e..c22eb66 100644 --- a/tests/programs/annotate.expected +++ b/tests/programs/annotate.expected @@ -1,10 +1,10 @@ -// zclsp --annotate writes the types the compiler can see into a program that -// has none. this one has none: locals take the type of what they start as, a -// parameter takes the type every call gives it, and a function the type of -// what it returns. what it can't tell it leaves alone (twice is called with -// a plain word, so its parameter stays), and the program still means the -// same thing: tests/test.sh runs both and compares. -include "src/lib/std.zc"; +namespace; +include rt; + +// --annotate adds type annotations inferred from declarations and calls. +// unresolved types remain unchanged, and annotations do not add conversions. +// tests/test.sh compares this program's output before and after annotation. +include std; struct point { int x, y; }; int calls = 0; char* name = "annotate"; @@ -12,12 +12,12 @@ int table[4]; int add(int a, int b) { calls = calls + 1; return a + b; } point* scale(point* p, int k) { p.x = p.x * k; p.y = p.y * k; return p; } point*.u fresh(int x, int y) { - point* p = new(point); + point* p = rt.new(point); p.x = x; p.y = y; return u*p; } -int twice(int v) { return v + v; } -fn len3(char* s) { return len_w(s) + 3; } +fn twice(v) { return v + v; } +fn len3(char* s) { return rt.len_w(s) + 3; } fn half(f) { return f / 2.0; } int main() { int i = 0, sum = 0, buf[6]; @@ -27,7 +27,7 @@ int main() { while (i < 4) { table[i] = add(i, 10); sum = sum + table[i]; i++; } scale(pt, 3); buf[0] = twice(sum); - put_n(sum); put_c(32); put_n(pt.x + pt.y); put_c(32); put_n(buf[0]); put_c(32); - put_n(len3(text)); put_c(32); put_n(calls); put_c(32); put_n(half(r) * 4); put_c(10); + rt.put_n(sum); rt.put_c(32); rt.put_n(pt.x + pt.y); rt.put_c(32); rt.put_n(buf[0]); rt.put_c(32); + rt.put_n(len3(text)); rt.put_c(32); rt.put_n(calls); rt.put_c(32); rt.put_n(half(r) * 4); rt.put_c(10); return 0; } diff --git a/tests/programs/annotate.zc b/tests/programs/annotate.zc index 75dad97..3a39f5f 100644 --- a/tests/programs/annotate.zc +++ b/tests/programs/annotate.zc @@ -1,10 +1,10 @@ -// zclsp --annotate writes the types the compiler can see into a program that -// has none. this one has none: locals take the type of what they start as, a -// parameter takes the type every call gives it, and a function the type of -// what it returns. what it can't tell it leaves alone (twice is called with -// a plain word, so its parameter stays), and the program still means the -// same thing: tests/test.sh runs both and compares. -include "src/lib/std.zc"; +namespace; +include rt; + +// --annotate adds type annotations inferred from declarations and calls. +// unresolved types remain unchanged, and annotations do not add conversions. +// tests/test.sh compares this program's output before and after annotation. +include std; struct point { int x, y; }; let calls = 0; let name = "annotate"; @@ -12,12 +12,12 @@ let table[4]; fn add(a, b) { calls = calls + 1; return a + b; } fn scale(p, k) { p.x = p.x * k; p.y = p.y * k; return p; } .u fresh(x, y) { - let p = new(point); + let p = rt.new(point); p.x = x; p.y = y; return u*p; } fn twice(v) { return v + v; } -fn len3(s) { return len_w(s) + 3; } +fn len3(s) { return rt.len_w(s) + 3; } fn half(f) { return f / 2.0; } fn main() { let i = 0, sum = 0, buf[6]; @@ -27,7 +27,7 @@ fn main() { while (i < 4) { table[i] = add(i, 10); sum = sum + table[i]; i++; } scale(pt, 3); buf[0] = twice(sum); - put_n(sum); put_c(32); put_n(pt.x + pt.y); put_c(32); put_n(buf[0]); put_c(32); - put_n(len3(text)); put_c(32); put_n(calls); put_c(32); put_n(half(r) * 4); put_c(10); + rt.put_n(sum); rt.put_c(32); rt.put_n(pt.x + pt.y); rt.put_c(32); rt.put_n(buf[0]); rt.put_c(32); + rt.put_n(len3(text)); rt.put_c(32); rt.put_n(calls); rt.put_c(32); rt.put_n(half(r) * 4); rt.put_c(10); return 0; } diff --git a/tests/programs/arena.zc b/tests/programs/arena.zc index 5156b80..4ca7e7c 100644 --- a/tests/programs/arena.zc +++ b/tests/programs/arena.zc @@ -1,16 +1,19 @@ +namespace; +include rt; + // the bump arena: interior pointers, a rewind, and a full bump. fn main() { - let a = arena_new(32), p, q, r; - p = arena_alloc(a, 4); + let a = rt.arena_new(32), p, q, r; + p = rt.arena_alloc(a, 4); p[0] = 7; - q = arena_alloc(a, 4); - put_n(q - p); put_c(' '); - put_n(p[0]); put_c(' '); - put_n(q[0]); put_c(' '); - arena_reset(a); - r = arena_alloc(a, 4); - put_n(r == p); put_c(' '); - put_n(arena_alloc(a, 100) == 0); put_c(10); - free(d*a); + q = rt.arena_alloc(a, 4); + rt.put_n(q - p); rt.put_c(' '); + rt.put_n(p[0]); rt.put_c(' '); + rt.put_n(q[0]); rt.put_c(' '); + rt.arena_reset(a); + r = rt.arena_alloc(a, 4); + rt.put_n(r == p); rt.put_c(' '); + rt.put_n(rt.arena_alloc(a, 100) == 0); rt.put_c(10); + rt.free(d*a); return 0; } diff --git a/tests/programs/args.zc b/tests/programs/args.zc index aac72ec..4e0947e 100644 --- a/tests/programs/args.zc +++ b/tests/programs/args.zc @@ -1,16 +1,19 @@ +namespace; +include rt, std; + // the command line and the environment, as the host hands them over. argv[0] // is the program path, and <eval> when the source came from stdin, so argc is // 1 even with nothing on the command line. run through a shell with no extra // arguments, which is how test.sh calls it, so the line is fixed. -include "src/lib/std.zc"; +include std; fn main() { - put_n(argc); put_c(32); put_s(argv[0]); - put_c(32); put_c(32); put_s(argv[argc - 1]); + rt.put_n(std.argc); rt.put_c(32); rt.put_s(std.argv[0]); + rt.put_c(32); rt.put_c(32); rt.put_s(std.argv[std.argc - 1]); // envc counts every entry, envp holds NAME=value strings - put_c(32); put_n(envc > 0); + rt.put_c(32); rt.put_n(std.envc > 0); let hit = 0; - for (int i = 0; i < envc; i++) if (eq_w(envp[i], "ZC_ARG_TEST=ok")) hit = 1; - put_c(32); put_n(hit); + for (int i = 0; i < std.envc; i++) if (rt.eq_w(std.envp[i], "ZC_ARG_TEST=ok")) hit = 1; + rt.put_c(32); rt.put_n(hit); return 0; }
\ No newline at end of file diff --git a/tests/programs/array_values.expected b/tests/programs/array_values.expected new file mode 100644 index 0000000..792708f --- /dev/null +++ b/tests/programs/array_values.expected @@ -0,0 +1 @@ +7 0 8 9 0 3 9 7 1 255 6 6 5 4 7 9 5 9 ok 66 6 7 2 3 3 9 diff --git a/tests/programs/array_values.zc b/tests/programs/array_values.zc new file mode 100644 index 0000000..01dd3aa --- /dev/null +++ b/tests/programs/array_values.zc @@ -0,0 +1,30 @@ +namespace; +include rt; + +int[] global = { 1, [5] = 9, 7 }; +int* static_values = (int[]){6, [3] = 9}; +int calls; +int next_value() { calls++; return calls; } +u8[4] packed = { 257, [3] = 255 }; +int sum(int[] a, int n) { int out = 0; for (int i = 0; i < n; i++) out += a[i]; return out; } +fn main() { + int[] effects = {next_value(), [0] = next_value(), next_value()}; + int[] a = { 1, 2, [5] = 8, 9, }; + int[3] b = { 7 }; + int[2][2] matrix = { { 1, 2 }, { 3, 4 } }; + char[] text = "ok"; + u8[] bytes = b"AB"; + int[2][3] asymmetric = {1,2,3,4,5,6}; + int[2][2] flat = {1, 2, 3, 4}; + int[2][2] designated = { [1][0] = 8, 9, [0][1] = 7 }; + int[2][2][2] cube = { [1][1] = {5,6} }; + int[?] grow = { 6 }; push(grow, 7); + rt.put_n(sizeof(a)); rt.put_c(' '); rt.put_n(a[2]); rt.put_c(' '); rt.put_n(a[5]); rt.put_c(' '); rt.put_n(a[6]); rt.put_c(' '); + rt.put_n(b[1]); rt.put_c(' '); rt.put_n(matrix[1][0]); rt.put_c(' '); rt.put_n(global[5]); rt.put_c(' '); rt.put_n(global[6]); rt.put_c(' '); + rt.put_n(packed[0]); rt.put_c(' '); rt.put_n(packed[3]); rt.put_c(' '); rt.put_n(grow[0]); rt.put_c(' '); + rt.put_n(sum((int[]){1, 2, 3}, 3)); rt.put_c(' '); + let literal = { 4, 5 }; rt.put_n(literal[1]); rt.put_c(' '); + rt.put_n(flat[1][1]); rt.put_c(' '); rt.put_n(designated[0][1]); rt.put_c(' '); rt.put_n(designated[1][1]); rt.put_c(' '); rt.put_n(cube[1][1][0]); rt.put_c(' '); + rt.put_n((int[]){8,9}[1]); rt.put_c(' '); rt.put_s(text); rt.put_c(' '); rt.put_n(bytes[1]); rt.put_c(' '); rt.put_n(asymmetric[1][2]); rt.put_c(' '); rt.put_n(grow[1]); rt.put_c(' '); rt.put_n(effects[0]); rt.put_c(' '); rt.put_n(effects[1]); rt.put_c(' '); rt.put_n(calls); rt.put_c(' '); rt.put_n(static_values[3]); rt.put_c(10); + return 0; +} diff --git a/tests/programs/arrays.zc b/tests/programs/arrays.zc index a8a12bf..b1fa43b 100644 --- a/tests/programs/arrays.zc +++ b/tests/programs/arrays.zc @@ -1,23 +1,26 @@ +namespace; +include rt; + // growable arrays and fixed ones struct point { int x, y; }; fn main() { - let before = live_objects(); + let before = rt.live_objects(); { - []int a = new([]int); + int[?] a = rt.new(int[?]); for (int i = 0; i < 100; i++) push(a, i * i); - put_n(len(a)); put_c(' '); put_n(cap(a)); put_c(' '); put_n(a[99]); put_c(' '); - put_n(pop(a)); put_c(' '); put_n(len(a)); put_c(' '); - resize(a, 3); put_n(len(a)); put_c(' '); put_n(a[2]); put_c(' '); - fixed []int f = new(fixed []int, 5); - f[4] = 7; put_n(f[4] + len(f)); put_c(' '); - []point ps = new([]point, 2); - ps[1].y = 9; resize(ps, 10); put_n(ps[1].y + len(ps)); put_c(' '); - [8]int st; put_n(len(st)); put_c(' '); - []point* owned = new([]point*); - for (int i = 0; i < 20; i++) { point* p = new(point); p.x = i; push(owned, d*p); } - put_n(owned[19].x); put_c(' '); - put_n(live_objects() - before); put_c(10); + rt.put_n(len(a)); rt.put_c(' '); rt.put_n(cap(a)); rt.put_c(' '); rt.put_n(a[99]); rt.put_c(' '); + rt.put_n(pop(a)); rt.put_c(' '); rt.put_n(len(a)); rt.put_c(' '); + resize(a, 3); rt.put_n(len(a)); rt.put_c(' '); rt.put_n(a[2]); rt.put_c(' '); + fixed int[?] f = rt.new(fixed int[?], 5); + f[4] = 7; rt.put_n(f[4] + len(f)); rt.put_c(' '); + point[?] ps = rt.new(point[?], 2); + ps[1].y = 9; resize(ps, 10); rt.put_n(ps[1].y + len(ps)); rt.put_c(' '); + int[8] st; rt.put_n(len(st)); rt.put_c(' '); + point*[?] owned = rt.new(point*[?]); + for (int i = 0; i < 20; i++) { point* p = rt.new(point); p.x = i; push(owned, d*p); } + rt.put_n(owned[19].x); rt.put_c(' '); + rt.put_n(rt.live_objects() - before); rt.put_c(10); } - put_n(live_objects() - before); put_c(10); + rt.put_n(rt.live_objects() - before); rt.put_c(10); return 0; } diff --git a/tests/programs/backtick.zc b/tests/programs/backtick.zc index 3610f70..548eac8 100644 --- a/tests/programs/backtick.zc +++ b/tests/programs/backtick.zc @@ -1,9 +1,12 @@ +namespace; +include rt; + // a backtick ends the source only where the lexer would read it as a token. // this one, in a line comment: ` does not end anything. /* nor does this, in a block comment ` with a star / and a slash */ fn main() { let s = "a`b", t = "q\"`", c = '`', d = '\''; - put_n(len_w(s)); put_c(' '); put_n(len_w(t)); put_c(' '); put_n(c); put_c(' '); put_n(d); put_c(' '); - while (!eof()) put_c(get_c()); + rt.put_n(rt.len_w(s)); rt.put_c(' '); rt.put_n(rt.len_w(t)); rt.put_c(' '); rt.put_n(c); rt.put_c(' '); rt.put_n(d); rt.put_c(' '); + while (!rt.eof()) rt.put_c(rt.get_c()); return 0; } diff --git a/tests/programs/bareexit.zc b/tests/programs/bareexit.zc index 905710e..1cede83 100644 --- a/tests/programs/bareexit.zc +++ b/tests/programs/bareexit.zc @@ -1,7 +1,10 @@ +namespace; +include rt; + // exit(3) must stop the machine before the next print. fn main() { - put_s("bye\n"); - syscall(7, 3, 0, 0); - put_s("still\n"); + rt.put_s("bye\n"); + rt.syscall(7, 3, 0, 0); + rt.put_s("still\n"); return 0; } diff --git a/tests/programs/bareio.zc b/tests/programs/bareio.zc index 6da50b1..a06ef38 100644 --- a/tests/programs/bareio.zc +++ b/tests/programs/bareio.zc @@ -1,28 +1,31 @@ +namespace; +include rt; + // bare-metal syscalls: clock, a RAM file, random, and the machine size. // zc0 subset, so a run-only image can carry it. names stay off u/m/d. fn main() { let buf[16], n, t0, t1, i, fd, r, board[8]; - t0 = syscall(6, 0, 0, 0); + t0 = rt.syscall(6, 0, 0, 0); i = 0; while (i < 20000) i = i + 1; - t1 = syscall(6, 0, 0, 0); - if (t0 >= 0 && t1 >= t0) put_s("tick "); - else put_s("notick "); - fd = syscall(1, "note", 1, 0); - n = syscall(4, fd, "zy", 2); - syscall(2, fd, 0, 0); - fd = syscall(1, "note", 0, 0); - n = syscall(3, fd, buf, 8); - syscall(2, fd, 0, 0); + t1 = rt.syscall(6, 0, 0, 0); + if (t0 >= 0 && t1 >= t0) rt.put_s("tick "); + else rt.put_s("notick "); + fd = rt.syscall(1, "note", 1, 0); + n = rt.syscall(4, fd, "zy", 2); + rt.syscall(2, fd, 0, 0); + fd = rt.syscall(1, "note", 0, 0); + n = rt.syscall(3, fd, buf, 8); + rt.syscall(2, fd, 0, 0); buf[n] = 0; - put_n(n); put_c(' '); put_s(buf); put_c(' '); - r = syscall(8, 0, 0, 0); - if (r != -1) put_s("rand "); - else put_s("norand "); - n = syscall(5, "BOARD", board, 8); - if (n > 0) put_s("board "); - else put_s("noboard "); - put_n(syscall(9, 0, 0, 0)); - put_c(10); + rt.put_n(n); rt.put_c(' '); rt.put_s(buf); rt.put_c(' '); + r = rt.syscall(8, 0, 0, 0); + if (r != -1) rt.put_s("rand "); + else rt.put_s("norand "); + n = rt.syscall(5, "BOARD", board, 8); + if (n > 0) rt.put_s("board "); + else rt.put_s("noboard "); + rt.put_n(rt.syscall(9, 0, 0, 0)); + rt.put_c(10); return 0; } diff --git a/tests/programs/basics.zc b/tests/programs/basics.zc index 9865527..89ea882 100644 --- a/tests/programs/basics.zc +++ b/tests/programs/basics.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + let table[10]; let greeting = "hey"; const N = 10, BIG = 0x100 * 2; @@ -8,14 +11,14 @@ fn main() { let i, s = 0, a[5]; for (i = 0; i < N; i++) table[i] = i * i; for (let j = 0; j < N; j++) s += table[j]; - put_n(s); put_c(' '); put_n(fib(15)); put_c(' '); put_n(twice(inc, 40)); put_c(' '); put_n(BIG); put_c(10); - put_s(greeting); put_c(' '); put_n('A'); put_c(' '); put_n(-17 / 5); put_c(' '); put_n(-17 % 5); put_c(' '); put_n(1 << 10); put_c(' '); put_n(-64 >> 3); put_c(10); - i = 0; while (1) { i++; if (i == 3) continue; if (i > 6) break; put_n(i); } put_c(10); - a[0] = 5; a[1] = a[0]++; a[2] = ++a[0]; put_n(a[0]); put_n(a[1]); put_n(a[2]); put_c(10); + rt.put_n(s); rt.put_c(' '); rt.put_n(fib(15)); rt.put_c(' '); rt.put_n(twice(inc, 40)); rt.put_c(' '); rt.put_n(BIG); rt.put_c(10); + rt.put_s(greeting); rt.put_c(' '); rt.put_n('A'); rt.put_c(' '); rt.put_n(-17 / 5); rt.put_c(' '); rt.put_n(-17 % 5); rt.put_c(' '); rt.put_n(1 << 10); rt.put_c(' '); rt.put_n(-64 >> 3); rt.put_c(10); + i = 0; while (1) { i++; if (i == 3) continue; if (i > 6) break; rt.put_n(i); } rt.put_c(10); + a[0] = 5; a[1] = a[0]++; a[2] = ++a[0]; rt.put_n(a[0]); rt.put_n(a[1]); rt.put_n(a[2]); rt.put_c(10); let b = { let t = 3; t * t }; - put_n(b); put_c(' '); put_n(3 > 2 && 2 > 1); put_n(0 || 0); put_n(!5); put_n(~0); put_n(5 != 5); put_n(4 >= 4); put_c(10); + rt.put_n(b); rt.put_c(' '); rt.put_n(3 > 2 && 2 > 1); rt.put_n(0 || 0); rt.put_n(!5); rt.put_n(~0); rt.put_n(5 != 5); rt.put_n(4 >= 4); rt.put_c(10); let x = asm { 2 3 + }; - put_n(x); put_c(10); + rt.put_n(x); rt.put_c(10); asm { 'o' . 'k' . 10 . } return 0; } diff --git a/tests/programs/bench.zc b/tests/programs/bench.zc index 7d34ead..06d0782 100644 --- a/tests/programs/bench.zc +++ b/tests/programs/bench.zc @@ -1,17 +1,20 @@ +namespace; +include rt; + // four kernels with and without o3; bench.sh runs each one on each backend int csum(int lim) { int s = 0; for (int n = 1; n < lim; n++) { int x = n; while (x != 1) { if (x & 1) x = 3 * x + 1; else x = x / 2; s++; } } return s; } o3 int csum3(int lim) { int s = 0; for (int n = 1; n < lim; n++) { int x = n; while (x != 1) { if (x & 1) x = 3 * x + 1; else x = x / 2; s++; } } return s; } -int sieve(fixed []int f) { int c = 0, n = len(f); for (int i = 0; i < n; i++) f[i] = 1; for (int i = 2; i < n; i++) if (f[i]) { c++; for (int j = i + i; j < n; j += i) f[j] = 0; } return c; } -o3 int sieve3(fixed []int f) { int c = 0, n = len(f); for (int i = 0; i < n; i++) f[i] = 1; for (int i = 2; i < n; i++) if (f[i]) { c++; for (int j = i + i; j < n; j += i) f[j] = 0; } return c; } +int sieve(fixed int[?] f) { int c = 0, n = len(f); for (int i = 0; i < n; i++) f[i] = 1; for (int i = 2; i < n; i++) if (f[i]) { c++; for (int j = i + i; j < n; j += i) f[j] = 0; } return c; } +o3 int sieve3(fixed int[?] f) { int c = 0, n = len(f); for (int i = 0; i < n; i++) f[i] = 1; for (int i = 2; i < n; i++) if (f[i]) { c++; for (int j = i + i; j < n; j += i) f[j] = 0; } return c; } int digits(int n) { int s = 0; for (int i = 0; i < n; i++) { int x = i; while (x) { s = s + x % 10; x = x / 10; } } return s; } o3 int digits3(int n) { int s = 0; for (int i = 0; i < n; i++) { int x = i; while (x) { s = s + x % 10; x = x / 10; } } return s; } int fib(int n) { if (n < 2) return n; return fib(n - 1) + fib(n - 2); } o3 int fib3(int n) { if (n < 2) return n; return fib3(n - 1) + fib3(n - 2); } // input: a letter (the kernel; z runs none) and how many times to run it fn main() { - fixed []int f = new(fixed []int, 50000); - let which = get_c(), reps = 0, ch = get_c(), r = 0; - while (ch >= '0' && ch <= '9') { reps = reps * 10 + ch - '0'; ch = get_c(); } + fixed int[?] f = rt.new(fixed int[?], 50000); + let which = rt.get_c(), reps = 0, ch = rt.get_c(), r = 0; + while (ch >= '0' && ch <= '9') { reps = reps * 10 + ch - '0'; ch = rt.get_c(); } while (reps > 0) { if (which == 'a') r = csum(30000); if (which == 'b') r = csum3(30000); @@ -23,7 +26,7 @@ fn main() { if (which == 'h') r = fib3(22); reps--; } - put_n(r); put_c(10); - free(d*f); + rt.put_n(r); rt.put_c(10); + rt.free(d*f); return 0; } diff --git a/tests/programs/bench_bytes.zc b/tests/programs/bench_bytes.zc index cb878f6..086d752 100644 --- a/tests/programs/bench_bytes.zc +++ b/tests/programs/bench_bytes.zc @@ -1,19 +1,22 @@ +namespace; +include rt; + fn sumbytes(n) { let buf[64], i = 0, s = 0, b, reps, ch; while (i < 64) { buf[i] = i * 0x0101010101010101; i++; } - b = addr_b(buf); - ch = get_c(); + b = rt.addr_b(buf); + ch = rt.get_c(); reps = 0; - while (ch >= '0' && ch <= '9') { reps = reps * 10 + ch - '0'; ch = get_c(); } + while (ch >= '0' && ch <= '9') { reps = reps * 10 + ch - '0'; ch = rt.get_c(); } while (reps > 0) { i = 0; s = 0; - while (i < n) { s = s + ld8(b + (i & 511)); i++; } + while (i < n) { s = s + rt.ld8(b + (i & 511)); i++; } reps--; } return s; } fn main() { - put_n(sumbytes(4000)); - put_c(10); + rt.put_n(sumbytes(4000)); + rt.put_c(10); return 0; } diff --git a/tests/programs/bench_share.zc b/tests/programs/bench_share.zc index 6fa478f..04306fe 100644 --- a/tests/programs/bench_share.zc +++ b/tests/programs/bench_share.zc @@ -1,30 +1,33 @@ +namespace; +include rt; + // walk one shared spine from two parents. the checksum is one repetition. fn build() { let i = 0, spine = 0; - while (i < 40) { spine = cons(i, spine); i++; } - return cons(cons(spine, 1), cons(spine, 2)); + while (i < 40) { spine = rt.cons(i, spine); i++; } + return rt.cons(rt.cons(spine, 1), rt.cons(spine, 2)); } fn sum(t) { let a = 0; - while (t) { a = a + car(t); t = cdr(t); } + while (t) { a = a + rt.car(t); t = rt.cdr(t); } return a; } fn work() { - let ch = get_c(), reps = 0, acc = 0, root, spine, k; - while (ch >= '0' && ch <= '9') { reps = reps * 10 + ch - '0'; ch = get_c(); } + let ch = rt.get_c(), reps = 0, acc = 0, root, spine, k; + while (ch >= '0' && ch <= '9') { reps = reps * 10 + ch - '0'; ch = rt.get_c(); } root = build(); - spine = car(car(root)); + spine = rt.car(rt.car(root)); while (reps > 0) { k = 0; acc = 0; while (k < 200) { acc = acc + sum(spine); k++; } - acc = acc + cdr(car(root)) + cdr(cdr(root)); + acc = acc + rt.cdr(rt.car(root)) + rt.cdr(rt.cdr(root)); reps--; } return acc; } fn main() { - put_n(work()); - put_c(10); + rt.put_n(work()); + rt.put_c(10); return 0; } diff --git a/tests/programs/bench_strings.zc b/tests/programs/bench_strings.zc index 455cff9..6f224db 100644 --- a/tests/programs/bench_strings.zc +++ b/tests/programs/bench_strings.zc @@ -1,27 +1,30 @@ +namespace; +include rt; + fn work() { let s = b"abcdefghijklmnopqrstuvwxyz0123456789"; let i, acc, n, t, part, k, reps, ch; - ch = get_c(); + ch = rt.get_c(); reps = 0; - while (ch >= '0' && ch <= '9') { reps = reps * 10 + ch - '0'; ch = get_c(); } + while (ch >= '0' && ch <= '9') { reps = reps * 10 + ch - '0'; ch = rt.get_c(); } while (reps > 0) { - k = 0; acc = 0; n = len_s(s); + k = 0; acc = 0; n = rt.len_s(s); while (k < 200) { i = 0; - while (i < n) { acc = acc + at_s(s, i); i++; } + while (i < n) { acc = acc + rt.at_s(s, i); i++; } k++; } t = s + b"!"; part = t[1:8]; - acc = acc + len_s(t) + len_s(part) + at_s(part, 0); - free(part); - free(t); + acc = acc + rt.len_s(t) + rt.len_s(part) + rt.at_s(part, 0); + rt.free(part); + rt.free(t); reps--; } return acc; } fn main() { - put_n(work()); - put_c(10); + rt.put_n(work()); + rt.put_c(10); return 0; } diff --git a/tests/programs/bitops.zc b/tests/programs/bitops.zc index 97817b9..3b6b113 100644 --- a/tests/programs/bitops.zc +++ b/tests/programs/bitops.zc @@ -1,26 +1,29 @@ +namespace; +include rt; + // bit tricks and float32 conversion as micro-ops -fn show(v) { put_n(v); put_c(' '); } +fn show(v) { rt.put_n(v); rt.put_c(' '); } fn once() { - show(clz(1)); show(clz(0)); show(ctz(8)); show(ctz(0)); - show(popcnt(255)); show(popcnt(-1)); show(popcnt(0)); - show(bswap(1) == 72057594037927936); - show(rotl(1, 65)); show(rotr(1, 1) < 0); show(rotl(rotr(12345, 7), 7)); - show(mulhi(-1, 5)); show(umulhi(-1, 5)); - show(f2s(0x3ff8000000000000)); show(s2f(0x3fc00000) == 0x3ff8000000000000); - show(f2s(0x3ff0000000000000)); show(f2s(0x3810000000000000)); - put_c(10); + show(rt.clz(1)); show(rt.clz(0)); show(rt.ctz(8)); show(rt.ctz(0)); + show(rt.popcnt(255)); show(rt.popcnt(-1)); show(rt.popcnt(0)); + show(rt.bswap(1) == 72057594037927936); + show(rt.rotl(1, 65)); show(rt.rotr(1, 1) < 0); show(rt.rotl(rt.rotr(12345, 7), 7)); + show(rt.mulhi(-1, 5)); show(rt.umulhi(-1, 5)); + show(rt.f2s(0x3ff8000000000000)); show(rt.s2f(0x3fc00000) == 0x3ff8000000000000); + show(rt.f2s(0x3ff0000000000000)); show(rt.f2s(0x3810000000000000)); + rt.put_c(10); } // the same in a loop, so o3 lifts and fuses them o3 int sums(int n) { int a = 0; for (int i = 0; i < n; i++) { - a += popcnt(i) + ctz(i + 1) + clz(i + 1); - a ^= rotl(a, 3) + bswap(i) + f2s(s2f(i)); + a += rt.popcnt(i) + rt.ctz(i + 1) + rt.clz(i + 1); + a ^= rt.rotl(a, 3) + rt.bswap(i) + rt.f2s(rt.s2f(i)); } return a; } int main() { once(); - put_n(sums(1000)); put_c(10); + rt.put_n(sums(1000)); rt.put_c(10); return 0; } diff --git a/tests/programs/borrow_rules.expected b/tests/programs/borrow_rules.expected new file mode 100644 index 0000000..71bce3a --- /dev/null +++ b/tests/programs/borrow_rules.expected @@ -0,0 +1 @@ +7 14 7 7 11 8 3 10 11 11 1 2 1 2 6 11 ok diff --git a/tests/programs/borrow_rules.zc b/tests/programs/borrow_rules.zc new file mode 100644 index 0000000..ed2b199 --- /dev/null +++ b/tests/programs/borrow_rules.zc @@ -0,0 +1,39 @@ +namespace; +include rt; + +// last-use borrows do not block an owner's later move; disjoint projections remain independently usable. +struct pair { int left, right; }; +int*.u make(int n) { int* p = rt.new(int); *p = n; return p; } +int take(int*.d p) { return p[0]; } +int twice(int*.m p, int*.m q) { return p[0] + q[0]; } +char* alias(char*.m p) { return p; } +word.u counter() { + int n = 0; + let next = fn() { n++; return n; }; + return next; +} +word.u object_counter() { + int* value = rt.new(int); + let next = fn() { value[0]++; return value[0]; }; + return next; +} +fn main() { + let p = make(7); int* borrowed = p + 0; + rt.put_n(borrowed[0]); rt.put_c(32); rt.put_n(twice(p,p)); rt.put_c(32); + rt.put_n(take(p)); rt.put_c(32); + let disposable = rt.new(2); rt.free(disposable); + pair* item = rt.new(pair); int* left = &item.left; int* right = &item.right; + *left = 3; *right = 4; rt.put_n(*left + *right); rt.put_c(32); + int[3] a = {1,2,3}; int* middle = &a[1]; middle[-1] = 5; a[2] = 6; + rt.put_n(middle[-1] + middle[1]); rt.put_c(32); + int[?] values = {8,9}; int* first = &values[0]; rt.put_n(first[0]); rt.put_c(32); resize(values,3); + rt.put_n(len(values)); rt.put_c(32); + int n = 0; let next = fn() { return ++n; }; let current = fn() { return n; }; + next(); n = 10; rt.put_n(current()); rt.put_c(32); rt.put_n(next()); rt.put_c(32); rt.put_n(n); rt.put_c(32); + let escaping = counter(); rt.gc(); rt.put_n(escaping()); rt.put_c(32); rt.put_n(escaping()); rt.put_c(32); + let heap = object_counter(); rt.gc(); rt.put_n(heap()); rt.put_c(32); rt.put_n(heap()); rt.put_c(32); + int sum = 0, i = 0; while (i < 3) { sum += i; let f = fn() { return ++sum; }; f(); i++; } + rt.put_n(sum); rt.put_c(32); + let raw = fn() { return asm { n @ }; }; rt.put_n(raw()); rt.put_c(32); + rt.put_s(alias("ok")); rt.put_c(10); +} diff --git a/tests/programs/borrowmove.zc b/tests/programs/borrowmove.zc index b0f0e42..e229fab 100644 --- a/tests/programs/borrowmove.zc +++ b/tests/programs/borrowmove.zc @@ -1,2 +1,5 @@ -fn put(.m box, int v) { int* c = new(int); *c = v; box[0] = c; return 0; } -fn main() { let b = new(1); put(b, 6); put_n(*(b[0] as int*)); put_c(10); return 0; } +namespace; +include rt; + +fn put(.m box, int v) { int* c = rt.new(int); *c = v; box[0] = c; return 0; } +fn main() { let b = rt.new(1); put(b, 6); rt.put_n(*(b[0] as int*)); rt.put_c(10); return 0; } diff --git a/tests/programs/bounds.zc b/tests/programs/bounds.zc index 42c3997..6908683 100644 --- a/tests/programs/bounds.zc +++ b/tests/programs/bounds.zc @@ -1,17 +1,20 @@ +namespace; +include rt; + // a +1 loop whose bound is len of the fixed array: the check is gone, the sum is not -fn acc(fixed []int f) { +fn acc(fixed int[?] f) { int n = len(f), i = 0, s = 0; for (; i < n; i += 1) f[i] = i + 1; for (i = 0; i < n; i++) s = s + f[i]; return s; } -o3 fn acc3(fixed []int f) { +o3 fn acc3(fixed int[?] f) { int n = len(f), i = 0, s = 0; for (; i < n; i++) f[i] = i + 1; for (i = 0; i < n; i++) s += f[i]; return s; } -o3 fn small(fixed []int f) { +o3 fn small(fixed int[?] f) { int n = len(f), c = 0, i, j; for (i = 2; i < n; i++) f[i] = 1; for (i = 2; i * i < n; i++) @@ -20,9 +23,9 @@ o3 fn small(fixed []int f) { return c; } fn main() { - fixed []int f = new(fixed []int, 10); - fixed []int g = new(fixed []int, 20); - put_n(acc(f)); put_c(32); put_n(acc3(f)); put_c(32); put_n(small(g)); put_c(10); - free(d*f); free(d*g); + fixed int[?] f = rt.new(fixed int[?], 10); + fixed int[?] g = rt.new(fixed int[?], 20); + rt.put_n(acc(f)); rt.put_c(32); rt.put_n(acc3(f)); rt.put_c(32); rt.put_n(small(g)); rt.put_c(10); + rt.free(d*f); rt.free(d*g); return 0; } diff --git a/tests/programs/bounds_hi.zc b/tests/programs/bounds_hi.zc index d61179d..49639c5 100644 --- a/tests/programs/bounds_hi.zc +++ b/tests/programs/bounds_hi.zc @@ -1,8 +1,11 @@ +namespace; +include rt; + // the index is not the induction variable. i + 1 walks off the end. o3 fn main() { - fixed []int f = new(fixed []int, 4); + fixed int[?] f = rt.new(fixed int[?], 4); int n = len(f), i; for (i = 0; i < n; i++) f[i + 1] = 1; - free(d*f); + rt.free(d*f); return 0; } diff --git a/tests/programs/bounds_lo.zc b/tests/programs/bounds_lo.zc index 896d864..b6a82f4 100644 --- a/tests/programs/bounds_lo.zc +++ b/tests/programs/bounds_lo.zc @@ -1,8 +1,11 @@ +namespace; +include rt; + // i starts negative. i < len is true for the first pass, and the index is still illegal. fn main() { - fixed []int f = new(fixed []int, 4); + fixed int[?] f = rt.new(fixed int[?], 4); int n = len(f), i; for (i = -1; i < n; i++) f[i] = 1; - free(d*f); + rt.free(d*f); return 0; } diff --git a/tests/programs/bstr_io.zc b/tests/programs/bstr_io.zc index 9cf7ca9..8b1f2af 100644 --- a/tests/programs/bstr_io.zc +++ b/tests/programs/bstr_io.zc @@ -1,22 +1,25 @@ +namespace; +include rt; + fn main() { let path = b"/tmp/zc-bstr.txt"; let msg = b"byte-io"; - let fd = bopen(path, 1); - let n = bwrite(fd, msg, len_s(msg)); - syscall(SYS_CLOSE, fd, 0, 0); - fd = bopen(path, 0); - let buf = new_s(7); - let g = bread(fd, buf, 7); - syscall(SYS_CLOSE, fd, 0, 0); + let fd = rt.bopen(path, 1); + let n = rt.bwrite(fd, msg, rt.len_s(msg)); + rt.syscall(rt.SYS_CLOSE, fd, 0, 0); + fd = rt.bopen(path, 0); + let buf = rt.new_s(7); + let g = rt.bread(fd, buf, 7); + rt.syscall(rt.SYS_CLOSE, fd, 0, 0); let name = b"ZC_BSTR"; - let envb = new_s(16); - let k = bgetenv(name, envb, 16); - put_s(buf); put_c(32); - put_n(n); put_c(32); - put_n(g); put_c(32); - put_n(buf == msg); put_c(32); - put_n(k); put_c(32); - put_n(at_s(envb, 0)); put_c(32); - put_n(at_s(envb, 5)); put_c(10); + let envb = rt.new_s(16); + let k = rt.bgetenv(name, envb, 16); + rt.put_s(buf); rt.put_c(32); + rt.put_n(n); rt.put_c(32); + rt.put_n(g); rt.put_c(32); + rt.put_n(buf == msg); rt.put_c(32); + rt.put_n(k); rt.put_c(32); + rt.put_n(rt.at_s(envb, 0)); rt.put_c(32); + rt.put_n(rt.at_s(envb, 5)); rt.put_c(10); return 0; } diff --git a/tests/programs/buddy.zc b/tests/programs/buddy.zc index ab351d2..c92a790 100644 --- a/tests/programs/buddy.zc +++ b/tests/programs/buddy.zc @@ -1,16 +1,19 @@ +namespace; +include rt; + // the buddy allocator: 20000 small blocks, freed, merge back into one big one let keep[20000]; fn main() { let i = 0, p; - while (i < 20000) { keep[i] = new(2); i++; } // 20000 8-word blocks - put_n(largest_free()); put_c(' '); - i = 0; while (i < 20000) { free(keep[i]); keep[i] = 0; i++; } - scrub(); // stale stack words would keep the quarantined blocks - gc(); - put_n(largest_free()); put_c(' '); - p = new(200000); // only possible if the small blocks merged back - put_n(osize(p) >= 200000); put_c(' '); - free(d*p); - put_n(live_objects()); put_c(10); + while (i < 20000) { keep[i] = rt.new(2); i++; } // 20000 8-word blocks + rt.put_n(rt.largest_free()); rt.put_c(' '); + i = 0; while (i < 20000) { rt.free(keep[i]); keep[i] = 0; i++; } + rt.scrub(); // stale stack words would keep the quarantined blocks + rt.gc(); + rt.put_n(rt.largest_free()); rt.put_c(' '); + p = rt.new(200000); // only possible if the small blocks merged back + rt.put_n(rt.osize(p) >= 200000); rt.put_c(' '); + rt.free(d*p); + rt.put_n(rt.live_objects()); rt.put_c(10); return 0; } diff --git a/tests/programs/bytes.zc b/tests/programs/bytes.zc index b4e1079..1332c26 100644 --- a/tests/programs/bytes.zc +++ b/tests/programs/bytes.zc @@ -1,44 +1,47 @@ -fn sp() { put_c(32); return 0; } +namespace; +include rt; + +fn sp() { rt.put_c(32); return 0; } fn litw(v) { return v + v + 1; } -fn f() { put_c('A'); return 0; } +fn f() { rt.put_c('A'); return 0; } fn sumbytes(n) { let buf[64], i = 0, s = 0, b; while (i < 64) { buf[i] = i * 0x0101010101010101; i++; } - b = addr_b(buf); + b = rt.addr_b(buf); i = 0; - while (i < n) { s = s + ld8(b + (i & 511)); i++; } + while (i < n) { s = s + rt.ld8(b + (i & 511)); i++; } return s; } fn main() { let buf[3], b; buf[0] = 0; buf[1] = 0; buf[2] = 0; - b = addr_b(buf); - st8(b, 0x11); - st8(b + 1, 0x22); - st8(b + 7, 0x7f); - st16(b + 7, 0xabcd); - st32(b + 10, 0x01020304); - put_n(ld8(b)); sp(); - put_n(ld8(b + 1)); sp(); - put_n(ld16(b + 7)); sp(); - put_n(ld32(b + 10)); sp(); - put_n(addr_w(b) == buf); sp(); - put_n(ld8(b + 7)); sp(); - put_n(ld8(b + 8)); sp(); - put_n(buf[0]); sp(); - put_n(buf[1]); sp(); - st8(0 - 1, 99); - put_n(ld8(0 - 1)); sp(); - put_n(ld16(addr_b(MW) - 1)); sp(); - put_n(sumbytes(1000)); - put_c(10); + b = rt.addr_b(buf); + rt.st8(b, 0x11); + rt.st8(b + 1, 0x22); + rt.st8(b + 7, 0x7f); + rt.st16(b + 7, 0xabcd); + rt.st32(b + 10, 0x01020304); + rt.put_n(rt.ld8(b)); sp(); + rt.put_n(rt.ld8(b + 1)); sp(); + rt.put_n(rt.ld16(b + 7)); sp(); + rt.put_n(rt.ld32(b + 10)); sp(); + rt.put_n(rt.addr_w(b) == buf); sp(); + rt.put_n(rt.ld8(b + 7)); sp(); + rt.put_n(rt.ld8(b + 8)); sp(); + rt.put_n(buf[0]); sp(); + rt.put_n(buf[1]); sp(); + rt.st8(0 - 1, 99); + rt.put_n(rt.ld8(0 - 1)); sp(); + rt.put_n(rt.ld16(rt.addr_b(rt.MW) - 1)); sp(); + rt.put_n(sumbytes(1000)); + rt.put_c(10); let p = f; f(); while (*p != litw('A')) p++; - st8(addr_b(p), litw('B') & 255); + rt.st8(rt.addr_b(p), litw('B') & 255); f(); - st8(addr_b(p), litw('C') & 255); + rt.st8(rt.addr_b(p), litw('C') & 255); f(); - put_c(10); + rt.put_c(10); return 0; } diff --git a/tests/programs/cabi.zc b/tests/programs/cabi.zc index 6a608ab..da9518b 100644 --- a/tests/programs/cabi.zc +++ b/tests/programs/cabi.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // native calls: extern declares a c function, cfunc hands a zc function to c extern double sqrt(double); extern double pow(double x, double y); @@ -10,7 +13,7 @@ extern int snprintf2(word *buf, long cap, str fmt, long a, double b) = "snprintf extern double hypot(double, double); fn put_f(x) { asm { x @ #1220 } return 0; } -fn sp() { put_c(32); return 0; } +fn sp() { rt.put_c(32); return 0; } int cmp(int* a, int* b) { return *a - *b; } // a callback that makes a native call of its own, and an o3 one @@ -23,24 +26,24 @@ fn main() { put_f(sqrt(2.0)); sp(); put_f(pow(2.0, 10.0)); sp(); put_f(sqrtf(16.0)); sp(); - put_n(c_strlen(b"hello, world")); sp(); - put_n(c_abs(0 - 42)); sp(); - put_n(c_atoi(b"1234xyz")); sp(); + rt.put_n(c_strlen(b"hello, world")); sp(); + rt.put_n(c_abs(0 - 42)); sp(); + rt.put_n(c_atoi(b"1234xyz")); sp(); a[0] = 5; a[1] = 3; a[2] = 9; a[3] = 1; a[4] = 7; a[5] = 2; qsort(a, 6, 8, cfunc(cmp)); - while (i < 6) { put_n(a[i]); sp(); i++; } - put_c(10); + while (i < 6) { rt.put_n(a[i]); sp(); i++; } + rt.put_c(10); qsort(a, 6, 8, cfunc(cmpdesc)); - i = 0; while (i < 6) { put_n(a[i]); sp(); i++; } - put_c(10); + i = 0; while (i < 6) { rt.put_n(a[i]); sp(); i++; } + rt.put_c(10); qsort(a, 6, 8, cfunc(cmp)); - i = 0; while (i < 6) { put_n(a[i]); sp(); i++; } + i = 0; while (i < 6) { rt.put_n(a[i]); sp(); i++; } qsort(a, 6, 8, cfunc(cmpo3)); - i = 0; while (i < 6) { put_n(a[i]); sp(); i++; } - put_c(10); - put_n(snprintf2(a, 48, b"%ld:%.2f", 7, 2.5)); sp(); - i = 0; while (i < 6) { put_c(ld8(addr_b(a) + i)); i++; } - put_c(10); - put_f(hypot(3.0, 4.0)); put_c(10); + i = 0; while (i < 6) { rt.put_n(a[i]); sp(); i++; } + rt.put_c(10); + rt.put_n(snprintf2(a, 48, b"%ld:%.2f", 7, 2.5)); sp(); + i = 0; while (i < 6) { rt.put_c(rt.ld8(rt.addr_b(a) + i)); i++; } + rt.put_c(10); + put_f(hypot(3.0, 4.0)); rt.put_c(10); return 0; } diff --git a/tests/programs/cabi_o3.zc b/tests/programs/cabi_o3.zc index 5c9a55a..6020dd8 100644 --- a/tests/programs/cabi_o3.zc +++ b/tests/programs/cabi_o3.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // native calls from o3 code: lifted into the region as micro-ops, called directly by llvm extern double sqrt(double); extern double pow(double, double); @@ -20,11 +23,11 @@ o3 int absum(int n) { fn main() { let buf[16]; let i = 0; - put_f(norm(100)); put_c(32); - put_f(pow(2.0, 0.5)); put_c(32); - put_n(absum(100)); put_c(10); - put_n(snprintf9(buf, 100, b"%ld-%ld-%ld-%ld-%ld-%ld", 1, 2, 3, 4, 5, 6)); put_c(32); - while (i < 11) { put_c(ld8(addr_b(buf) + i)); i++; } - put_c(10); + put_f(norm(100)); rt.put_c(32); + put_f(pow(2.0, 0.5)); rt.put_c(32); + rt.put_n(absum(100)); rt.put_c(10); + rt.put_n(snprintf9(buf, 100, b"%ld-%ld-%ld-%ld-%ld-%ld", 1, 2, 3, 4, 5, 6)); rt.put_c(32); + while (i < 11) { rt.put_c(rt.ld8(rt.addr_b(buf) + i)); i++; } + rt.put_c(10); return 0; } diff --git a/tests/programs/cabi_sized.zc b/tests/programs/cabi_sized.zc index d1dc2ba..8460107 100644 --- a/tests/programs/cabi_sized.zc +++ b/tests/programs/cabi_sized.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // extern and cfunc with f32 and the sized integers extern f32 sqrtf(f32); extern f64 hypot(f64, f64); @@ -5,24 +8,24 @@ extern i32 abs(i32); extern u8 toupper(u8); extern long labs(long); fn put_f(x) { asm { x @ #1220 } return 0; } -fn sp() { put_c(32); return 0; } +fn sp() { rt.put_c(32); return 0; } extern void qsort(word *base, long n, long size, void *cmp); i32 cmpi(int* a, int* b) { return *a - *b; } f32 half(f32 v) { return v / 2; } fn main() { f32 r = sqrtf(2.0); put_f(r); sp(); - put_n(r == sqrtf(2.0)); sp(); + rt.put_n(r == sqrtf(2.0)); sp(); put_f(hypot(3.0, 4.0)); sp(); - put_n(abs(0 - 77)); sp(); - put_n(toupper(97)); sp(); + rt.put_n(abs(0 - 77)); sp(); + rt.put_n(toupper(97)); sp(); i32 big = 0 - 5; - put_n(abs(big)); sp(); + rt.put_n(abs(big)); sp(); int v[4]; v[0] = 4; v[1] = 1; v[2] = 3; v[3] = 2; qsort(v, 4, 8, cfunc(cmpi)); - put_n(v[0]); put_n(v[1]); put_n(v[2]); put_n(v[3]); sp(); + rt.put_n(v[0]); rt.put_n(v[1]); rt.put_n(v[2]); rt.put_n(v[3]); sp(); put_f(half(5.0)); - put_c(10); + rt.put_c(10); return 0; } diff --git a/tests/programs/capture.zc b/tests/programs/capture.zc index c178791..34d03a6 100644 --- a/tests/programs/capture.zc +++ b/tests/programs/capture.zc @@ -1,31 +1,34 @@ +namespace; +include rt, std; + // capture.start() sends everything the program writes into a string instead of -// the terminal, and buf.end() stops it and terminates the string. nothing may +// the terminal, and capture.end(buf) stops it and terminates the string. nothing may // be printed during a capture, or it lands in the buffer too. -include "src/lib/std.zc"; +include std; fn main() { - let buf = capture.start(); - put_s("hello world!"); - put_n(42); - put_c(10); - buf.end(); // stops the capture and terminates the string + let buf = std.capture.start(); + rt.put_s("hello world!"); + rt.put_n(42); + rt.put_c(10); + std.capture.end(buf); // stops the capture and terminates the string // the capture is off again, so these go to the terminal - put_n(outmode); put_c(10); // 0 - put_n(outbuf == 0); put_c(10); // 1, the buffer is handed over - put_n(len_w(buf)); put_c(10); // 15 - put_s(buf); // hello world!42 + rt.put_n(rt.outmode); rt.put_c(10); // 0 + rt.put_n(rt.outbuf == 0); rt.put_c(10); // 1, the buffer is handed over + rt.put_n(rt.len_w(buf)); rt.put_c(10); // 15 + rt.put_s(buf); // hello world!42 // a second capture does not disturb the first, and both read back - let two = capture.start(); - put_s("second"); - two.end(); - put_n(eq_w(buf, "hello world!42\n")); put_c(10); // 1 - put_n(eq_w(two, "second")); put_c(10); // 1 + let two = std.capture.start(); + rt.put_s("second"); + std.capture.end(two); + rt.put_n(rt.eq_w(buf, "hello world!42\n")); rt.put_c(10); // 1 + rt.put_n(rt.eq_w(two, "second")); rt.put_c(10); // 1 // capture_stdin returns an empty string, so an unused capture is empty - let three = capture.start(); - three.end(); - put_n(len_w(three)); put_c(10); // 0 + let three = std.capture.start(); + std.capture.end(three); + rt.put_n(rt.len_w(three)); rt.put_c(10); // 0 return 0; }
\ No newline at end of file diff --git a/tests/programs/closure.zc b/tests/programs/closure.zc index 660c32a..eee6226 100644 --- a/tests/programs/closure.zc +++ b/tests/programs/closure.zc @@ -1,4 +1,7 @@ -// closures: calls through values, captures by value, state in a capture, +namespace; +include rt; + +// closures: calls through values, shared captures, state in a capture, // nested captures, closures passed as arguments and returned from functions fn apply(f, x) { return f(x); } fn twice(g, x) { return g(g(x)); } @@ -10,30 +13,30 @@ word.u make(n) { fn main() { let k = 10, m = 3; let add = fn(x) { return x + k * m; }; - put_n(add(1)); put_c(10); - put_n(apply(add, 5)); put_c(10); + rt.put_n(add(1)); rt.put_c(10); + rt.put_n(apply(add, 5)); rt.put_c(10); let none = fn() { return 42; }; - put_n(none()); put_c(10); + rt.put_n(none()); rt.put_c(10); let n = 0; let bump = fn(d) { n = n + d; return n; }; - put_n(bump(2)); put_c(10); - put_n(bump(5)); put_c(10); - put_n(n); put_c(10); + rt.put_n(bump(2)); rt.put_c(10); + rt.put_n(bump(5)); rt.put_c(10); + rt.put_n(n); rt.put_c(10); let a = 2; let outer = fn(x) { let inner = fn(y) { return y + a + x; }; return inner(100); }; - put_n(outer(7)); put_c(10); + rt.put_n(outer(7)); rt.put_c(10); let base = 5; let scale = fn(i) { return i * base; }; let s = 0, i = 0; while (i < 4) { s = s + scale(i); i++; } - put_n(s); put_c(10); + rt.put_n(s); rt.put_c(10); let c = 3; let plus = fn(v) { return v + c; }; - put_n(twice(plus, 1)); put_c(10); - put_n(sq(4)); put_c(10); + rt.put_n(twice(plus, 1)); rt.put_c(10); + rt.put_n(sq(4)); rt.put_c(10); let total = 0, j = 0; while (j < 3) { let kk = j; @@ -41,12 +44,12 @@ fn main() { total = total + g(10); j++; } - put_n(total); put_c(10); + rt.put_n(total); rt.put_c(10); let f = make(4); - put_n(f(5)); put_c(10); + rt.put_n(f(5)); rt.put_c(10); let h = 1; let frozen = fn(x) { return x + h; }; h = 100; - put_n(frozen(1)); put_c(10); + rt.put_n(frozen(1)); rt.put_c(10); return 0; } diff --git a/tests/programs/closure_result.zc b/tests/programs/closure_result.zc new file mode 100644 index 0000000..57b666e --- /dev/null +++ b/tests/programs/closure_result.zc @@ -0,0 +1,12 @@ +namespace; +include rt; + +// fn:type(args) declares a closure's result; returns are coerced to it +fn put_f(x) { asm { x @ #1220 } return 0; } +fn main() { + let half = fn:float(int x) { return x; }; + let twice = fn:int(int x) { return x * 2; }; + rt.put_n(twice(4)); rt.put_c(32); + put_f(half(3) / 2.0); rt.put_c(10); + return 0; +} diff --git a/tests/programs/csyn.zc b/tests/programs/csyn.zc index 0099d1c..1bf8949 100644 --- a/tests/programs/csyn.zc +++ b/tests/programs/csyn.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // do/while, enum constants, and a C cast typecheck on; enum color { RED, GREEN = 5, BLUE }; @@ -10,9 +13,9 @@ fn main() { } while (i < 5); int* p = (int*)0; int z = (int)p; - put_n(n); put_c(' '); - put_n(RED); put_c(' '); put_n(GREEN); put_c(' '); - put_n(BLUE); put_c(' '); - put_n(z); put_c(10); + rt.put_n(n); rt.put_c(' '); + rt.put_n(RED); rt.put_c(' '); rt.put_n(GREEN); rt.put_c(' '); + rt.put_n(BLUE); rt.put_c(' '); + rt.put_n(z); rt.put_c(10); return 0; } diff --git a/tests/programs/decl_undefined.zc b/tests/programs/decl_undefined.zc index 0ee9312..860e560 100644 --- a/tests/programs/decl_undefined.zc +++ b/tests/programs/decl_undefined.zc @@ -1,2 +1,4 @@ +namespace; + fn helper(); fn main() { helper(); return 0; } diff --git a/tests/programs/discard_m.zc b/tests/programs/discard_m.zc index 35e9f3b..85accfb 100644 --- a/tests/programs/discard_m.zc +++ b/tests/programs/discard_m.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // m* on an unnamed u result lets the value be discarded; the plain call is an error -.u mk(int v) { let p = new(1); p[0] = v; return u*p; } -fn main() { m*mk(7); m*mk(8); put_n(7); put_c(10); return 0; } +.u mk(int v) { let p = rt.new(1); p[0] = v; return u*p; } +fn main() { m*mk(7); m*mk(8); rt.put_n(7); rt.put_c(10); return 0; } diff --git a/tests/programs/doublefree.zc b/tests/programs/doublefree.zc index 296b194..c0793e3 100644 --- a/tests/programs/doublefree.zc +++ b/tests/programs/doublefree.zc @@ -1,9 +1,12 @@ +namespace; +include rt; + memdebug on; unchecked fn main() { - let p = new(1); - free(p); - free(p); - put_s("ok\n"); + let p = rt.new(1); + rt.free(p); + rt.free(p); + rt.put_s("ok\n"); return 0; } diff --git a/tests/programs/enhance.zc b/tests/programs/enhance.zc index 327b4bd..2d6ea43 100644 --- a/tests/programs/enhance.zc +++ b/tests/programs/enhance.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // custom opcodes: recipes, lifted vm code, several outputs, and a fallback enhance clampadd(a, b) micro "a = add a b; a = min a 100; a = max a 0"; enhance cube(x) vm { 0 $ 0 $ * * }; @@ -11,18 +14,18 @@ let qq, rr, seed = 7; fn rnd() { seed = seed * 1103515245 + 12345; return seed ^ (seed >> 11); } fn main() { let i = 0, a, bad = 0; - put_n(clampadd(70, 50)); put_c(' '); put_n(clampadd(-50, 2)); put_c(' '); put_n(cube(7)); put_c(' '); + rt.put_n(clampadd(70, 50)); rt.put_c(' '); rt.put_n(clampadd(-50, 2)); rt.put_c(' '); rt.put_n(cube(7)); rt.put_c(' '); asm { 17 5 divmod rr ! qq ! } - put_n(qq); put_c(','); put_n(rr); put_c(' '); - put_n(noisy(4)); put_c(' '); - put_n(hi(-1, -1)); put_c(' '); + rt.put_n(qq); rt.put_c(','); rt.put_n(rr); rt.put_c(' '); + rt.put_n(noisy(4)); rt.put_c(' '); + rt.put_n(hi(-1, -1)); rt.put_c(' '); while (i < 5000) { a = rnd(); - if (div7(a) != zdiv(a, 7)) bad++; + if (div7(a) != rt.zdiv(a, 7)) bad++; if (urem10(a) < 0 || urem10(a) > 9) bad++; - if (a / (i + 1) != zdiv(a, i + 1)) bad++; + if (a / (i + 1) != rt.zdiv(a, i + 1)) bad++; i++; } - put_n(bad); put_c(10); + rt.put_n(bad); rt.put_c(10); return 0; } diff --git a/tests/programs/enumscope.zc b/tests/programs/enumscope.zc index 5c6d7d0..6c66ee8 100644 --- a/tests/programs/enumscope.zc +++ b/tests/programs/enumscope.zc @@ -1,9 +1,12 @@ +namespace; +include rt; + // enum members are reached through the enum's name as well as plainly enum color { RED, GREEN = 5, BLUE }; fn main() { int c = color.BLUE; - put_n(color.RED); put_c(' '); put_n(GREEN); put_c(' '); - put_n(c); put_c(' '); - switch (c) { case color.BLUE: put_s("blue"); break; default: put_s("other"); } + rt.put_n(color.RED); rt.put_c(' '); rt.put_n(GREEN); rt.put_c(' '); + rt.put_n(c); rt.put_c(' '); + switch (c) { case color.BLUE: rt.put_s("blue"); break; default: rt.put_s("other"); } return 0; } diff --git a/tests/programs/enumtype.zc b/tests/programs/enumtype.zc index 9a6e89b..229be99 100644 --- a/tests/programs/enumtype.zc +++ b/tests/programs/enumtype.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // enums are a type of their own in typed code: their members, and only those, take them; an enum still becomes an int typecheck on; enum color { RED, GREEN = 5, BLUE }; @@ -6,7 +9,7 @@ int main() { color c = color.BLUE; color d = RED; int n = c; - put_n(n); put_c(32); put_n(paint(GREEN)); put_c(32); put_n(d == color.RED); - put_c(10); + rt.put_n(n); rt.put_c(32); rt.put_n(paint(GREEN)); rt.put_c(32); rt.put_n(d == color.RED); + rt.put_c(10); return 0; } diff --git a/tests/programs/f32.zc b/tests/programs/f32.zc index 0f60dcd..718ed8d 100644 --- a/tests/programs/f32.zc +++ b/tests/programs/f32.zc @@ -1,8 +1,11 @@ +namespace; +include rt, run; + // f32: single precision values and packed arrays of them struct vec3 { f32 x, y, z; }; f32 gsum; fn put_f(x) { asm { x @ #1220 } return 0; } -[8]f32 gbuf; +f32[8] run.gbuf; f32 half(f32 v) { return v / 2; } @@ -13,25 +16,25 @@ fn main() { float c = 16777216.0 + 1.0; f32 t = 0.1; float u = 0.1; - put_f(b); put_c(' '); put_f(c); put_c(' '); - put_n(t == 0.1); put_n(t == u); put_n(t == (f32)u); put_c(' '); - put_f(half(3.0)); put_c(' '); - put_f(gsum * 10); put_c(' '); // 1.000000 (0.1f * 10 in double) + put_f(b); rt.put_c(' '); put_f(c); rt.put_c(' '); + rt.put_n(t == 0.1); rt.put_n(t == u); rt.put_n(t == (f32)u); rt.put_c(' '); + put_f(half(3.0)); rt.put_c(' '); + put_f(gsum * 10); rt.put_c(' '); // 1.000000 (0.1f * 10 in double) vec3 v; v.x = 1.5; v.y = 0.1; v.z = 2; - put_f(v.x + v.y + v.z); put_c(' '); + put_f(v.x + v.y + v.z); rt.put_c(' '); int i = 0; - gbuf[0] = 0.1; put_n(gbuf[0] == t); put_n(gbuf[0] == u); put_c(' '); - while (i < 8) { gbuf[i] = i * 0.25 + 0.1; i++; } + run.gbuf[0] = 0.1; rt.put_n(run.gbuf[0] == t); rt.put_n(run.gbuf[0] == u); rt.put_c(' '); + while (i < 8) { run.gbuf[i] = i * 0.25 + 0.1; i++; } float s = 0; i = 0; - while (i < 8) { s += gbuf[i]; i++; } - put_f(s); put_c(' '); - f32* p = new(f32, 100); + while (i < 8) { s += run.gbuf[i]; i++; } + put_f(s); rt.put_c(' '); + f32* p = rt.new(f32, 100); i = 0; while (i < 100) { p[i] = i; i++; } s = 0; i = 0; while (i < 100) { s += p[i]; i++; } - put_f(s); put_c(' '); - put_n(sizeof(gbuf)); put_n(sizeof([100]f32)); put_c(10); - free(d*p); + put_f(s); rt.put_c(' '); + rt.put_n(sizeof(run.gbuf)); rt.put_n(sizeof(f32[100])); rt.put_c(10); + rt.free(d*p); } diff --git a/tests/programs/f32o3.zc b/tests/programs/f32o3.zc index 8f1ec2b..94b058c 100644 --- a/tests/programs/f32o3.zc +++ b/tests/programs/f32o3.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // f32 packed arrays and rounding inside an o3 region fn put_f(x) { asm { x @ #1220 } return 0; } o3 float dot(f32* p, int n) { @@ -7,8 +10,8 @@ o3 float dot(f32* p, int n) { return s + acc; } fn main() { - f32* p = new(f32, 50); + f32* p = rt.new(f32, 50); for (int i = 0; i < 50; i++) p[i] = i * 0.5; - put_f(dot(p, 50)); put_c(' '); put_f(p[3]); put_c(' '); put_n(p[3] == 1.6); put_c(10); - free(d*p); + put_f(dot(p, 50)); rt.put_c(' '); put_f(p[3]); rt.put_c(' '); rt.put_n(p[3] == 1.6); rt.put_c(10); + rt.free(d*p); } diff --git a/tests/programs/fbench.zc b/tests/programs/fbench.zc index 1658ba7..c3b7086 100644 --- a/tests/programs/fbench.zc +++ b/tests/programs/fbench.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // float kernel, plain and o3. bench_float.sh times them. the letter picks the // kernel and the digits are how many times to run it, same shape as bench.zc. float poly(int n) { @@ -23,15 +26,15 @@ o3 float poly3(int n) { return s; } fn main() { - let which = get_c(), reps = 0, ch = get_c(), r = 0; - while (ch >= 48 && ch <= 57) { reps = reps * 10 + ch - 48; ch = get_c(); } + let which = rt.get_c(), reps = 0, ch = rt.get_c(), r = 0; + while (ch >= 48 && ch <= 57) { reps = reps * 10 + ch - 48; ch = rt.get_c(); } while (reps > 0) { if (which == 97) r = (int)poly(20000); if (which == 98) r = (int)poly3(20000); if (which == 99) r = (int)poly(2000); reps = reps - 1; } - put_n(r); - put_c(10); + rt.put_n(r); + rt.put_c(10); return 0; } diff --git a/tests/programs/float.zc b/tests/programs/float.zc index 81b8668..8e9cd35 100644 --- a/tests/programs/float.zc +++ b/tests/programs/float.zc @@ -1,7 +1,10 @@ +namespace; +include rt; + fn put_f(x) { asm { x @ #1220 } return 0; } fn f2i(x) { return asm { x @ #1218 }; } fn i2f(x) { return asm { x @ #1216 }; } -fn sp() { put_c(32); return 0; } +fn sp() { rt.put_c(32); return 0; } fn main() { let a = 1.5; @@ -17,16 +20,16 @@ fn main() { put_f(1.0 / 0.0); sp(); put_f(-1.0 / 0.0); sp(); put_f(0.0 / 0.0); sp(); - put_n(1.5 < 2.0); sp(); - put_n(2.0 <= 2.0); sp(); - put_n(2.0 == 2); sp(); - put_n(1.5 == 1); sp(); - put_n(3.0 > 3.5); sp(); - put_n(f2i(3.9)); sp(); - put_n(f2i(-3.9)); sp(); + rt.put_n(1.5 < 2.0); sp(); + rt.put_n(2.0 <= 2.0); sp(); + rt.put_n(2.0 == 2); sp(); + rt.put_n(1.5 == 1); sp(); + rt.put_n(3.0 > 3.5); sp(); + rt.put_n(f2i(3.9)); sp(); + rt.put_n(f2i(-3.9)); sp(); put_f(i2f(3)); sp(); put_f(i2f(-2)); sp(); - put_n(10 / 4); - put_c(10); + rt.put_n(10 / 4); + rt.put_c(10); return 0; } diff --git a/tests/programs/fo3.zc b/tests/programs/fo3.zc index 593e88a..b2ac93e 100644 --- a/tests/programs/fo3.zc +++ b/tests/programs/fo3.zc @@ -1,7 +1,10 @@ +namespace; +include rt; + // o3 lifts float ops: the plain kernel and the o3 kernel agree, a constant // float expression folds, x - x stays a NaN, and -0 is false inside o3. fn put_f(x) { asm { x @ #1220 } return 0; } -fn sp() { put_c(32); return 0; } +fn sp() { rt.put_c(32); return 0; } float poly(int n) { float s = 0.0; float x = 1.25; @@ -36,15 +39,15 @@ o3 int zfalse() { o3 float addtwo(float a) { return a + 2; } o3 int below(float a, float b) { return a < b; } fn main() { - put_n((int)poly(2000)); sp(); - put_n((int)poly3(2000)); sp(); + rt.put_n((int)poly(2000)); sp(); + rt.put_n((int)poly3(2000)); sp(); put_f(folded()); sp(); put_f(subself(0.0 / 0.0)); sp(); - put_n(zfalse()); sp(); + rt.put_n(zfalse()); sp(); put_f(addtwo(1.25)); sp(); - put_n(below(1.0, 2.0)); sp(); - put_n(below(2.0, 2.0)); sp(); - put_n(below(0.0 / 0.0, 1.0)); - put_c(10); + rt.put_n(below(1.0, 2.0)); sp(); + rt.put_n(below(2.0, 2.0)); sp(); + rt.put_n(below(0.0 / 0.0, 1.0)); + rt.put_c(10); return 0; } diff --git a/tests/programs/ftext.zc b/tests/programs/ftext.zc index b1c0acf..bb52d1a 100644 --- a/tests/programs/ftext.zc +++ b/tests/programs/ftext.zc @@ -1,9 +1,12 @@ -fn sp() { put_c(32); return 0; } +namespace; +include rt; + +fn sp() { rt.put_c(32); return 0; } fn pm(x, prec, mode) { asm { x @ prec @ mode @ #1236 } return 0; } fn parsed(s) { return asm { s @ #1222 }; } fn ok(a, b) { let w = asm { a @ b @ ^ }; - if (w) put_n(0); else put_n(1); + if (w) rt.put_n(0); else rt.put_n(1); return 0; } @@ -28,10 +31,10 @@ fn main() { ok(parsed("-inf"), -1.0 / 0.0); ok(parsed("-0"), z); ok(parsed("9007199254740993"), lo); - if (hi == lo) put_n(0); else put_n(1); - if (parsed("5e-324")) put_n(1); else put_n(0); - if (parsed("1e-400")) put_n(0); else put_n(1); - if (parsed("-nan") == parsed("nan")) put_n(0); else put_n(1); - put_c(10); + if (hi == lo) rt.put_n(0); else rt.put_n(1); + if (parsed("5e-324")) rt.put_n(1); else rt.put_n(0); + if (parsed("1e-400")) rt.put_n(0); else rt.put_n(1); + if (parsed("-nan") == parsed("nan")) rt.put_n(0); else rt.put_n(1); + rt.put_c(10); return 0; } diff --git a/tests/programs/ftruth.zc b/tests/programs/ftruth.zc index 9d24a42..e99f9ed 100644 --- a/tests/programs/ftruth.zc +++ b/tests/programs/ftruth.zc @@ -1,22 +1,25 @@ +namespace; +include rt; + fn main() { let z = 0.0 * -1.0; let p = 0.0; let n = 1.5; - if (z) put_n(1); else put_n(0); - if (p) put_n(1); else put_n(0); - if (n) put_n(1); else put_n(0); - if (!z) put_n(1); else put_n(0); - if (!p) put_n(1); else put_n(0); - if (!n) put_n(1); else put_n(0); - if (z && n) put_n(1); else put_n(0); - if (n && z) put_n(1); else put_n(0); - if (z || n) put_n(1); else put_n(0); - if (n || z) put_n(1); else put_n(0); - if (p || z) put_n(1); else put_n(0); - for (; z;) put_n(9); - put_n(z == p); - put_n(z < p); - put_n(7); - put_c(10); + if (z) rt.put_n(1); else rt.put_n(0); + if (p) rt.put_n(1); else rt.put_n(0); + if (n) rt.put_n(1); else rt.put_n(0); + if (!z) rt.put_n(1); else rt.put_n(0); + if (!p) rt.put_n(1); else rt.put_n(0); + if (!n) rt.put_n(1); else rt.put_n(0); + if (z && n) rt.put_n(1); else rt.put_n(0); + if (n && z) rt.put_n(1); else rt.put_n(0); + if (z || n) rt.put_n(1); else rt.put_n(0); + if (n || z) rt.put_n(1); else rt.put_n(0); + if (p || z) rt.put_n(1); else rt.put_n(0); + for (; z;) rt.put_n(9); + rt.put_n(z == p); + rt.put_n(z < p); + rt.put_n(7); + rt.put_c(10); return 0; } diff --git a/tests/programs/ftype.zc b/tests/programs/ftype.zc index 6db7f5a..cbc2444 100644 --- a/tests/programs/ftype.zc +++ b/tests/programs/ftype.zc @@ -1,5 +1,8 @@ +namespace; +include rt; + fn put_f(x) { asm { x @ #1220 } return 0; } -fn sp() { put_c(32); return 0; } +fn sp() { rt.put_c(32); return 0; } struct cell { int tag; float field; } @@ -31,17 +34,17 @@ fn main() { put_f(c.field); sp(); buf[0] = 6.5; put_f(buf[0]); sp(); - put_n(&(1.field)); sp(); + rt.put_n(&(1.field)); sp(); w = (&c) as word; - put_n(w.tag); sp(); + rt.put_n(w.tag); sp(); put_f(w.field); sp(); w.field = 8.5; put_f(c.field); sp(); - put_n(g > 3); sp(); + rt.put_n(g > 3); sp(); if (z) n = 1; do { if (z) n = n + 2; } while (z); if (!z && g) n = n + 4; - put_n(n); - put_c(10); + rt.put_n(n); + rt.put_c(10); return 0; } diff --git a/tests/programs/gc.zc b/tests/programs/gc.zc index 81412a3..7d2441b 100644 --- a/tests/programs/gc.zc +++ b/tests/programs/gc.zc @@ -1,18 +1,21 @@ +namespace; +include rt; + let keep; unchecked fn churn(n) { let i = 0, p; - while (i < n) { p = new(10); p[0] = i; if (i % 100 == 0) keep = p; i++; } + while (i < n) { p = rt.new(10); p[0] = i; if (i % 100 == 0) keep = p; i++; } return 0; } fn main() { - let base = live_objects(), f; + let base = rt.live_objects(), f; churn(1000); - put_n(live_objects() - base); put_c(' '); - f = gc(); - put_n(f >= 990); put_c(' '); // conservative: a stale word or two may keep one alive - put_n(keep[0]); put_c(' '); // the one a global still points at survives + rt.put_n(rt.live_objects() - base); rt.put_c(' '); + f = rt.gc(); + rt.put_n(f >= 990); rt.put_c(' '); // conservative: a stale word or two may keep one alive + rt.put_n(keep[0]); rt.put_c(' '); // the one a global still points at survives churn(200000); // 3.2m words through a 393k word heap: needs the automatic collector - put_n(gc_runs > 1); put_c(' '); - put_n(keep[0]); put_c(10); + rt.put_n(rt.gc_runs > 1); rt.put_c(' '); + rt.put_n(keep[0]); rt.put_c(10); return 0; } diff --git a/tests/programs/generics.zc b/tests/programs/generics.zc index e841b19..3623031 100644 --- a/tests/programs/generics.zc +++ b/tests/programs/generics.zc @@ -1,3 +1,6 @@ +namespace; +include rt, run; + // generics are compiled per set of type arguments: T can be any size, new(T) // stamps the real type, and a struct can take several parameters struct pt { int x, y; }; @@ -7,10 +10,11 @@ struct box<T> { int n; T* items; }; K rkey<K, V>(record<K, V>* r) { return r.key; } V* rval<K, V>(record<K, V>* r) { return &r.val; } int tsize<T>() { return sizeof(T); } -T*.u make<T>(int n) { T* p = new(T, n) as T*; return u*p; } +T*.u make<T>(int n) { T* p = rt.new(T, n) as T*; return u*p; } record<K, V>*.u mkrec<K, V>(K k) { - record<K, V>* r = new(record<K, V>); - r.key = k; + record<K, V>* r = rt.new(record<K, V>); + // copied references are shared so the record does not consume its input. + r.key = run.share_value(k); return u*r; } T pick<T>(T* a, int i) { return a[i]; } @@ -19,7 +23,7 @@ int sumn<T>(T* a, int n) { for (int i = 0; i < n; i++) s += a[i]; return s; } -fn nameof<T>() { return tname(typeof(T)); } +fn nameof<T>() { return run.tname(typeof(T)); } o3 int osum<T>(T* a, int n) { int s = 0; for (int i = 0; i < n; i++) s += a[i].x + a[i].y; @@ -32,23 +36,23 @@ int depth<T>(int n) { int main() { record<int, pt>* a = mkrec<int, pt>(9); a.val.x = 5; a.val.y = 6; - put_n(rkey<int, pt>(a)); put_c(' '); - put_n(rval<int, pt>(a).y); put_c(' '); - put_n(sizeof(record<int, pt>)); put_c(' '); - put_n(tsize<int>()); put_c(' '); - put_n(tsize<pt>()); put_c(' '); - put_n(tsize<record<int, pt>>()); put_c(' '); - put_n(depth<pt>(5)); put_c(10); + rt.put_n(rkey<int, pt>(a)); rt.put_c(' '); + rt.put_n(rval<int, pt>(a).y); rt.put_c(' '); + rt.put_n(sizeof(record<int, pt>)); rt.put_c(' '); + rt.put_n(tsize<int>()); rt.put_c(' '); + rt.put_n(tsize<pt>()); rt.put_c(' '); + rt.put_n(tsize<record<int, pt>>()); rt.put_c(' '); + rt.put_n(depth<pt>(5)); rt.put_c(10); pt* ps = make<pt>(3); ps[2].x = 40; ps[2].y = 2; - put_n(pick<pt>(ps, 2).x + ps[2].y); put_c(' '); - put_s(nameof<pt>()); put_c(' '); put_s(nameof<int>()); put_c(' '); - put_n(osum<pt>(ps, 3)); put_c(' '); + rt.put_n(pick<pt>(ps, 2).x + ps[2].y); rt.put_c(' '); + rt.put_s(nameof<pt>()); rt.put_c(' '); rt.put_s(nameof<int>()); rt.put_c(' '); + rt.put_n(osum<pt>(ps, 3)); rt.put_c(' '); int* is = make<int>(4); is[0] = 1; is[1] = 2; is[2] = 3; is[3] = 4; - put_n(sumn<int>(is, 4)); put_c(' '); + rt.put_n(sumn<int>(is, 4)); rt.put_c(' '); record<pt*, int>* b = mkrec<pt*, int>(ps); - put_n(b.key == ps); put_c(10); - free(d*a); free(d*b); free(d*ps); free(d*is); + rt.put_n(b.key == ps); rt.put_c(10); + rt.free(d*a); rt.free(d*b); rt.free(d*ps); rt.free(d*is); return 0; } diff --git a/tests/programs/genmeth.zc b/tests/programs/genmeth.zc index 305c55c..73f0ced 100644 --- a/tests/programs/genmeth.zc +++ b/tests/programs/genmeth.zc @@ -1,11 +1,14 @@ +namespace; +include rt; + // methods on generic structs: each set of type arguments gets its own copy struct pt { int x, y; }; struct table<K, V> { int n; K* keys; V* vals; fn init(int cap) { this.n = 0; - this.keys = new(K, cap) as K*; - this.vals = new(V, cap) as V*; + this.keys = rt.new(K, cap) as K*; + this.vals = rt.new(V, cap) as V*; return 0; } fn put(K k, V v) { @@ -25,12 +28,12 @@ int main() { table<int, int> a; a.init(8); a.put(1, 10); a.put(2, 20); a.put(3, 30); - put_n(a.n); put_c(' '); put_n(a.total()); put_c(' '); put_n(a.width()); put_c(' '); - table<pt, int>* b = new(table<pt, int>); + rt.put_n(a.n); rt.put_c(' '); rt.put_n(a.total()); rt.put_c(' '); rt.put_n(a.width()); rt.put_c(' '); + table<pt, int>* b = rt.new(table<pt, int>); b.init(4); - put_n(b.width()); put_c(' '); - table<int, pt>* c = new(table<int, pt>); + rt.put_n(b.width()); rt.put_c(' '); + table<int, pt>* c = rt.new(table<int, pt>); c.init(4); - put_n(c.width()); put_c(10); + rt.put_n(c.width()); rt.put_c(10); return 0; } diff --git a/tests/programs/global_transfer.zc b/tests/programs/global_transfer.zc new file mode 100644 index 0000000..b36ad73 --- /dev/null +++ b/tests/programs/global_transfer.zc @@ -0,0 +1,16 @@ +namespace; +include rt; + +// an m or u owner stored into a global is handed over by an automatic u transfer +let g; +let h; +int*.u make() { int* b = rt.new(int); *b = 9; return u*b; } +fn main() { + let x = rt.new(1); + x[0] = 5; + g = x; + let y = make(); + h = y; + rt.put_n(g[0]); rt.put_c(32); rt.put_n(h[0]); rt.put_c(10); + return 0; +} diff --git a/tests/programs/growheap.zc b/tests/programs/growheap.zc index 07e10a6..ebb3f8b 100644 --- a/tests/programs/growheap.zc +++ b/tests/programs/growheap.zc @@ -1,10 +1,13 @@ +namespace; +include rt; + // the heap grows past its initial size: eight live blocks of 300000 words // don't fit in the 393216-word start, so the allocator takes more memory from // the host, and a block bigger than the start fits once they are freed let keep[8]; unchecked fn fill(n) { let i = 0, a; - while (i < n) { a = new(300000); a[0] = i + 1; a[299999] = (i + 1) * 3; keep[i] = a; i++; } + while (i < n) { a = rt.new(300000); a[0] = i + 1; a[299999] = (i + 1) * 3; keep[i] = a; i++; } return 0; } unchecked fn total(n) { @@ -14,19 +17,19 @@ unchecked fn total(n) { } unchecked fn drop(n) { let i = 0; - while (i < n) { free(keep[i]); keep[i] = 0; i++; } + while (i < n) { rt.free(keep[i]); keep[i] = 0; i++; } return 0; } fn main() { - let a, start = heap_size(); + let a, start = rt.heap_size(); fill(8); - put_n(total(8)); put_c(' '); - put_n(heap_size() > start); put_c(' '); + rt.put_n(total(8)); rt.put_c(' '); + rt.put_n(rt.heap_size() > start); rt.put_c(' '); drop(8); - scrub(); gc(); - a = new(3000000); + rt.scrub(); rt.gc(); + a = rt.new(3000000); a[2999999] = 7; - put_n(a[2999999]); put_c(' '); - put_n(heap_size() >= 3000000); put_c(10); + rt.put_n(a[2999999]); rt.put_c(' '); + rt.put_n(rt.heap_size() >= 3000000); rt.put_c(10); return 0; } diff --git a/tests/programs/growone.zc b/tests/programs/growone.zc index 12980ff..174f35a 100644 --- a/tests/programs/growone.zc +++ b/tests/programs/growone.zc @@ -1,9 +1,12 @@ +namespace; +include rt; + // one block bigger than the initial heap: the machine grows, and the block is usable fn main() { - let a = new(400000); + let a = rt.new(400000); a[0] = 5; a[399999] = 7; - put_n(a[0] + a[399999]); put_c(' '); - put_n(heap_size() >= 400000); put_c(10); + rt.put_n(a[0] + a[399999]); rt.put_c(' '); + rt.put_n(rt.heap_size() >= 400000); rt.put_c(10); return 0; } diff --git a/tests/programs/infer_return.zc b/tests/programs/infer_return.zc index a9f12bd..cd82f18 100644 --- a/tests/programs/infer_return.zc +++ b/tests/programs/infer_return.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // a owned return with no .u on the signature: the return type is inferred from the body -fn mk(v) { int* p = new(1); p[0] = v; return p; } -fn main() { int* q = mk(3); put_n(q[0]); put_c(10); return 0; } +fn mk(v) { int* p = rt.new(1); p[0] = v; return p; } +fn main() { int* q = mk(3); rt.put_n(q[0]); rt.put_c(10); return 0; } diff --git a/tests/programs/interior.zc b/tests/programs/interior.zc index bccf377..d87a14c 100644 --- a/tests/programs/interior.zc +++ b/tests/programs/interior.zc @@ -1,17 +1,20 @@ +namespace; +include rt; + unchecked fn main() { - let p = new(4), r, i = 0, t, bad = 0; - mem_set(4); + let p = rt.new(4), r, i = 0, t, bad = 0; + rt.mem_set(4); p[0] = 111; p[1] = 222; p[2] = 333; p[3] = 444; r = p + 1; - free(p); - gc(); + rt.free(p); + rt.gc(); while (i < 8) { - t = new(4); + t = rt.new(4); if (t == r - 1) bad = 1; i++; } if (r[0] != 222) bad = bad + 2; - put_n(bad); - put_c(10); + rt.put_n(bad); + rt.put_c(10); return 0; } diff --git a/tests/programs/joins.zc b/tests/programs/joins.zc index 98275ad..ec3f862 100644 --- a/tests/programs/joins.zc +++ b/tests/programs/joins.zc @@ -1,31 +1,34 @@ +namespace; +include rt; + // joins: something given away on one path is dead afterwards, and the paths // that still own it free it on the way in. nothing here leaks. fn take(.d p) { return p[0]; } fn later(.d p); // not needed any more, but still allowed -int*.u mk(int v) { int* p = new(int); *p = v; return u*p; } +int*.u mk(int v) { int* p = rt.new(int); *p = v; return u*p; } fn count(int n) { let s = 0; switch (n % 4) { case 0: s = 10; case 1, 2: s = 20; break; default: s = 30; } return s; } fn main() { - let base = live_objects(), i = 0, hits = 0, k; + let base = rt.live_objects(), i = 0, hits = 0, k; while (i < 50) { let x = mk(i); if (i % 3 == 0) take(d*x); // else: freed here by the fixup i++; } - put_n(live_objects() - base); put_c(' '); + rt.put_n(rt.live_objects() - base); rt.put_c(' '); // give away and break: fine now; the normal exit frees it i = 0; let y = mk(7); while (i < 10) { if (i == 4) { take(d*y); break; } i++; } - put_n(live_objects() - base); put_c(' '); + rt.put_n(rt.live_objects() - base); rt.put_c(' '); let z = mk(5); while (1) { if (z[0] == 5) { hits = take(d*z); break; } } - put_n(hits); put_c(' '); - put_n(count(0) + count(1) + count(2) + count(3)); put_c(' '); + rt.put_n(hits); rt.put_c(' '); + rt.put_n(count(0) + count(1) + count(2) + count(3)); rt.put_c(' '); k = i > 3 ? i * 2 : 0 - 1; - put_n(k); put_c(' '); - put_n(forward(new(int))); put_c(' '); // tagged and defined below: the prescan knows .d - int** pp; int* q = new(int); *q = 9; pp = &q; put_n(**pp); free(d*q); put_c(' '); - put_n(live_objects() - base); put_c(10); + rt.put_n(k); rt.put_c(' '); + rt.put_n(forward(rt.new(int))); rt.put_c(' '); // tagged and defined below: the prescan knows .d + int** pp; int* q = rt.new(int); *q = 9; pp = &q; rt.put_n(**pp); rt.free(d*q); rt.put_c(' '); + rt.put_n(rt.live_objects() - base); rt.put_c(10); return 0; } fn forward(int*.d p) { return 3 + *p; } diff --git a/tests/programs/manyerrors.zc b/tests/programs/manyerrors.zc index 3ba2c5a..d13ade2 100644 --- a/tests/programs/manyerrors.zc +++ b/tests/programs/manyerrors.zc @@ -1,6 +1,9 @@ +namespace; +include rt; + // several errors in one run: a syntax error, an analyzer error, a type error typecheck on; fn f() { let a = ; return 0; } -fn g() { let x = new(3); return 0; } +fn g() { let x = rt.new(3); return 0; } int h() { char* s = 5; return 1; } fn main() { return 0 } diff --git a/tests/programs/memleak.zc b/tests/programs/memleak.zc index a61c331..ad4c8c0 100644 --- a/tests/programs/memleak.zc +++ b/tests/programs/memleak.zc @@ -1,11 +1,14 @@ +namespace; +include rt; + memdebug on; int* g; fn main() { - int* p = new(int); + int* p = rt.new(int); *p = 7; g = d*p; - put_n(memreport()); - put_c(10); + rt.put_n(rt.memreport()); + rt.put_c(10); return 0; } diff --git a/tests/programs/memory_bounds.zc b/tests/programs/memory_bounds.zc index 0863d03..a929de3 100644 --- a/tests/programs/memory_bounds.zc +++ b/tests/programs/memory_bounds.zc @@ -1,13 +1,16 @@ +namespace; +include rt; + // raw word loads, stores, and stack picks outside memory follow portable semantics. unchecked int raw_pick(int depth) { return asm { depth @ #6 }; } unchecked fn main() { int p = -1; - put_n(*p); put_c(32); *p = 7; + rt.put_n(*p); rt.put_c(32); *p = 7; p = 0x7FFFFFFF; - put_n(*p); put_c(32); *p = 9; + rt.put_n(*p); rt.put_c(32); *p = 9; p = 0x100000000; - put_n(*p); put_c(32); - put_n(raw_pick(0x100000000)); put_c(32); - put_n(raw_pick(0 - 0x100000000)); put_c(10); + rt.put_n(*p); rt.put_c(32); + rt.put_n(raw_pick(0x100000000)); rt.put_c(32); + rt.put_n(raw_pick(0 - 0x100000000)); rt.put_c(10); return 0; } diff --git a/tests/programs/meta.zc b/tests/programs/meta.zc index 6afdf36..57c5d5a 100644 --- a/tests/programs/meta.zc +++ b/tests/programs/meta.zc @@ -1,43 +1,46 @@ +namespace; +include rt, run; + fn ipow(a, b) { let r = 1; while (b > 0) { r *= a; b--; } return r; } // a parse word that adds an operator to the compiler's tables -immediate fn pow_op() { next(); add_binop("**", 11, "ipow c"); return 0; } +immediate fn pow_op() { run.next(); run.add_binop("**", 11, "ipow c"); return 0; } pow_op // unless (cond) stmt: a parse word that emits code itself immediate fn unless() { let a; - next(); - expect(intern("(")); rval(); plainuse(expr()); rval(); expect(intern(")")); - emitseq(useq[0], ulen[0]); // not - a = hole(); op(O_JZ); // skip the body when the condition was true - substmt(); - setlit(a, here); + run.next(); + run.expect(run.intern("(")); run.rval(); run.plainuse(run.expr()); run.rval(); run.expect(run.intern(")")); + run.emitseq(run.useq[0], run.ulen[0]); // not + a = run.hole(); run.op(run.O_JZ); // skip the body when the condition was true + run.substmt(); + run.setlit(a, run.here); return 0; } // repeat N { body }: a macro, built from token lists immediate fn repeat() { let n, body, i = 0; - next(); n = tv; next(); - body = grab(); - while (i < n) { feed(qdup(body)); i++; } - free(d*body); + run.next(); n = run.tv; run.next(); + body = run.grab(); + while (i < n) { run.feed(run.qdup(body)); i++; } + rt.free(d*body); return 0; } fn main() { - put_n(2 ** 10); put_c(' '); put_n(3 + 2 ** 3 * 2); put_c(10); - unless (1 > 2) put_s("unless ok\n"); - unless (2 > 1) put_s("wrong\n"); - repeat 3 { put_c('r'); } - put_c(10); - let q = quote { put_n(40 + 2); put_c(10); return 7; }; - put_n(q[0]); put_c(' '); - put_n(eval(q)); put_c(10); - let f = compile(q); - put_n(f()); put_c(10); - free(d*q); + rt.put_n(2 ** 10); rt.put_c(' '); rt.put_n(3 + 2 ** 3 * 2); rt.put_c(10); + unless (1 > 2) rt.put_s("unless ok\n"); + unless (2 > 1) rt.put_s("wrong\n"); + repeat 3 { rt.put_c('r'); } + rt.put_c(10); + let q = quote { rt.put_n(40 + 2); rt.put_c(10); return 7; }; + rt.put_n(q[0]); rt.put_c(' '); + rt.put_n(run.eval(q)); rt.put_c(10); + let f = run.compile(q); + rt.put_n(f()); rt.put_c(10); + rt.free(d*q); let defs = quote { fn sq(x) { return x * x; } }; - load(defs); - free(d*defs); - put_n(evals("return sq(12);")); put_c(10); + run.load(defs); + rt.free(d*defs); + rt.put_n(run.evals("return sq(12);")); rt.put_c(10); return 0; } diff --git a/tests/programs/methodown.zc b/tests/programs/methodown.zc index 98ba146..2d90f4b 100644 --- a/tests/programs/methodown.zc +++ b/tests/programs/methodown.zc @@ -1,15 +1,18 @@ +namespace; +include rt; + // a struct method can return an owned object: its header declares the .u return struct Node { int v; } struct Maker { - static Node*.u make(int n) { Node* p = new(Node); p.v = n; return u*p; } + static Node*.u make(int n) { Node* p = rt.new(Node); p.v = n; return u*p; } } -Node*.u mk(int n) { Node* p = new(Node); p.v = n; return u*p; } +Node*.u mk(int n) { Node* p = rt.new(Node); p.v = n; return u*p; } int main() { Node*.u a = Maker.make(7); - put_n(a.v); put_c(32); - free(d*a); + rt.put_n(a.v); rt.put_c(32); + rt.free(d*a); Node*.u b = mk(9); - put_n(b.v); put_c(10); - free(d*b); + rt.put_n(b.v); rt.put_c(10); + rt.free(d*b); return 0; } diff --git a/tests/programs/misc.zc b/tests/programs/misc.zc index 530322a..2b0f0c2 100644 --- a/tests/programs/misc.zc +++ b/tests/programs/misc.zc @@ -1,15 +1,18 @@ +namespace; +include rt, run; + // ct { ... } runs a block while compiling and leaves its result as a constant immediate fn ct() { let q, v; - next(); - q = grab(); - v = eval(q); - free(d*q); - lit(v); + run.next(); + q = run.grab(); + v = run.eval(q); + rt.free(d*q); + run.lit(v); return 0; } // teach the asm syntax that p means out, like the old repl demo -immediate fn teach() { next(); mnem[12] = 'p'; return 0; } +immediate fn teach() { run.next(); run.mnem[12] = 'p'; return 0; } teach fn countdown() { @@ -17,11 +20,11 @@ fn countdown() { return 0; } fn main() { - put_n(ct { let i = 0, s = 0; while (i < 10) { s += i; i++; } return s; }); put_c(10); + rt.put_n(ct { let i = 0, s = 0; while (i < 10) { s += i; i++; } return s; }); rt.put_c(10); countdown(); - put_s(lazy()); put_c(10); + rt.put_s(lazy()); rt.put_c(10); return 0; } // the analyzer is off from here on unchecked; -fn lazy() { let p = new(3); p[0] = 'o'; p[1] = 'k'; return p; } +fn lazy() { let p = rt.new(3); p[0] = 'o'; p[1] = 'k'; return p; } diff --git a/tests/programs/names.zc b/tests/programs/names.zc index 6ccb2eb..e5b9d4b 100644 --- a/tests/programs/names.zc +++ b/tests/programs/names.zc @@ -1,9 +1,12 @@ +namespace; +include rt; + fn f(d) { let u = 3, m = 4; if (d > 0) return u + m + d; return 0; } fn dmul(d) { return d * 4; } fn main() { let u = 1; - put_n(f(5)); put_c(' '); - put_n(u); put_c(' '); - put_n(dmul(2)); put_c(10); + rt.put_n(f(5)); rt.put_c(' '); + rt.put_n(u); rt.put_c(' '); + rt.put_n(dmul(2)); rt.put_c(10); return 0; } diff --git a/tests/programs/o3.zc b/tests/programs/o3.zc index c875d09..d7026db 100644 --- a/tests/programs/o3.zc +++ b/tests/programs/o3.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // o3 regions: every kind of control flow, against the same code without o3 struct pt { int x; int y; }; int g = 3; @@ -31,20 +34,20 @@ o3 int mix(int n) { return acc; } o3 int dyn(int n) { - []int a = new([]int, n); - fixed []int f = new(fixed []int, n); + int[?] a = rt.new(int[?], n); + fixed int[?] f = rt.new(fixed int[?], n); int s = 0; for (int i = 0; i < len(a); i++) { a[i] = i * i; f[i] = n - i; } for (int i = 0; i < n; i++) s = s + a[i] * f[i]; - free(d*a); free(d*f); + rt.free(d*a); rt.free(d*f); return s; } o3 int pts(int n) { - pt* p = new(pt); + pt* p = rt.new(pt); int s = 0; p->x = 1; p->y = 2; for (int i = 0; i < n; i++) { p->x = p->x + p->y; p->y = p->x - p->y; s = s + p->x % 1000; } - free(d*p); + rt.free(d*p); return s; } o3 int divs(int x) { @@ -59,17 +62,17 @@ fn main() { o3 { int i = 0, s = 0; while (i < 100) { s = s + collatz(i + 1); i++; } - put_n(s); put_c(' '); - put_n(lone); put_c(' '); + rt.put_n(s); rt.put_c(' '); + rt.put_n(lone); rt.put_c(' '); lone++; } - put_n(lone); put_c(' '); - put_n(ack(2, 3)); put_c(' '); - put_n(many(1, 2, 3)); put_c(' '); - put_n(mix(100)); put_c(' '); - put_n(dyn(50)); put_c(' '); - put_n(pts(30)); put_c(' '); - put_n(divs(1000)); put_c(' '); - put_n(divs(-77)); put_c(10); + rt.put_n(lone); rt.put_c(' '); + rt.put_n(ack(2, 3)); rt.put_c(' '); + rt.put_n(many(1, 2, 3)); rt.put_c(' '); + rt.put_n(mix(100)); rt.put_c(' '); + rt.put_n(dyn(50)); rt.put_c(' '); + rt.put_n(pts(30)); rt.put_c(' '); + rt.put_n(divs(1000)); rt.put_c(' '); + rt.put_n(divs(-77)); rt.put_c(10); return 0; } diff --git a/tests/programs/o3_bounds.zc b/tests/programs/o3_bounds.zc index fb347c0..eaf92eb 100644 --- a/tests/programs/o3_bounds.zc +++ b/tests/programs/o3_bounds.zc @@ -1,2 +1,5 @@ -// an index outside a []T inside o3 code: caught by the inline check -fn main() { []int a = new([]int, 5); o3 { int i = 7; a[i] = 1; } free(d*a); return 0; } +namespace; +include rt; + +// an index outside a T[?] inside o3 code: caught by the inline check +fn main() { int[?] a = rt.new(int[?], 5); o3 { int i = 7; a[i] = 1; } rt.free(d*a); return 0; } diff --git a/tests/programs/o3bytes.zc b/tests/programs/o3bytes.zc index bada583..7c971ed 100644 --- a/tests/programs/o3bytes.zc +++ b/tests/programs/o3bytes.zc @@ -1,52 +1,55 @@ +namespace; +include rt; + // byte ops called from o3 stay in the region. ld8/st8 and the 16- and 32-bit // pair are opcodes there, on the interpreter, the arm64 jit and llvm. -fn sp() { put_c(32); return 0; } +fn sp() { rt.put_c(32); return 0; } fn litw(v) { return v + v + 1; } -fn f() { put_c('A'); return 0; } +fn f() { rt.put_c('A'); return 0; } fn main() { let buf[4], b, i, s, t, p, v, w; i = 0; while (i < 4) { buf[i] = 0; i++; } - b = addr_b(buf); + b = rt.addr_b(buf); i = 0; s = 0; o3 { while (i < 20) { t = b + i; - st8(t, i); - s = s + ld8(t); + rt.st8(t, i); + s = s + rt.ld8(t); i++; } } - put_n(s); sp(); - o3 { st16(b, 4660); w = ld16(b); } - put_n(w); sp(); + rt.put_n(s); sp(); + o3 { rt.st16(b, 4660); w = rt.ld16(b); } + rt.put_n(w); sp(); t = b + 7; - o3 { w = ld8(t); } - put_n(w); sp(); - o3 { w = ld16(t); } - put_n(w); sp(); + o3 { w = rt.ld8(t); } + rt.put_n(w); sp(); + o3 { w = rt.ld16(t); } + rt.put_n(w); sp(); t = b + 10; v = 16909060; - o3 { st32(t, v); w = ld32(t); } - put_n(w); sp(); + o3 { rt.st32(t, v); w = rt.ld32(t); } + rt.put_n(w); sp(); t = 0 - 1; - o3 { st8(t, v); w = ld8(t); } - put_n(w); sp(); - t = addr_b(MW) - 1; - o3 { w = ld16(t); } - put_n(w); - put_c(10); + o3 { rt.st8(t, v); w = rt.ld8(t); } + rt.put_n(w); sp(); + t = rt.addr_b(rt.MW) - 1; + o3 { w = rt.ld16(t); } + rt.put_n(w); + rt.put_c(10); p = f; f(); while (*p != litw('A')) p++; - t = addr_b(p); + t = rt.addr_b(p); v = litw('B') & 255; - o3 { st8(t, v); } + o3 { rt.st8(t, v); } f(); v = litw('C') & 255; - o3 { st8(t, v); } + o3 { rt.st8(t, v); } f(); - put_c(10); + rt.put_c(10); return 0; } diff --git a/tests/programs/o3call.zc b/tests/programs/o3call.zc index b2be717..d2292ad 100644 --- a/tests/programs/o3call.zc +++ b/tests/programs/o3call.zc @@ -1,8 +1,11 @@ +namespace; +include rt, run; + // new stamps the header, so an o3 caller and an ordinary caller both see a type. // the call itself is not a boundary: no fatten, and the callee's prologue does not run. struct node { int v; node* c; }; unchecked o3 node* leaf(int v) { - node* n = new(node); + node* n = rt.new(node); n.v = v; n.c = 0; return n; } @@ -19,11 +22,11 @@ unchecked o3 int nest(int n) { unchecked fn main() { node* n = leaf(4); int* p = n; - put_n(p[-1] < 0); put_c(' '); - put_n(inner() < 0); put_c(' '); - put_n(n.v); put_c(' '); - put_n(add(20, 3)); put_c(' '); - put_n(nest(10)); put_c(' '); - tprint(typeof_obj(n)); put_c(10); + rt.put_n(p[-1] < 0); rt.put_c(' '); + rt.put_n(inner() < 0); rt.put_c(' '); + rt.put_n(n.v); rt.put_c(' '); + rt.put_n(add(20, 3)); rt.put_c(' '); + rt.put_n(nest(10)); rt.put_c(' '); + run.tprint(rt.typeof_obj(n)); rt.put_c(10); return 0; } diff --git a/tests/programs/o3copy.zc b/tests/programs/o3copy.zc index 90b9ade..29ac278 100644 --- a/tests/programs/o3copy.zc +++ b/tests/programs/o3copy.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // copies of a variable that a loop reads again: the global pass reads the variable itself o3 int g(int n, int b) { int s = 0, c = b, i = 0; @@ -9,4 +12,4 @@ o3 int h(int n, int k) { while (i < n) { t = t + a; if (t > 1000) t = t - 1000; a = k; i++; } return t + a; } -fn main() { put_n(g(1000, 7)); put_c(32); put_n(h(1000, 5)); put_c(10); return 0; } +fn main() { rt.put_n(g(1000, 7)); rt.put_c(32); rt.put_n(h(1000, 5)); rt.put_c(10); return 0; } diff --git a/tests/programs/o3deep.zc b/tests/programs/o3deep.zc index e715d9e..c07199f 100644 --- a/tests/programs/o3deep.zc +++ b/tests/programs/o3deep.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // o3 recursion nests on the host stack as deep as the machine's own stacks allow o3 int deep(int n) { if (n == 0) return 0; return 1 + deep(n - 1); } -fn main() { put_n(deep(30000)); put_c(10); return 0; } +fn main() { rt.put_n(deep(30000)); rt.put_c(10); return 0; } diff --git a/tests/programs/o3deep_over.zc b/tests/programs/o3deep_over.zc index 45816f6..226543e 100644 --- a/tests/programs/o3deep_over.zc +++ b/tests/programs/o3deep_over.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // recursion can grow beyond the original operand and frame reservations o3 int deep(int n) { if (n == 0) return 0; return 1 + deep(n - 1); } -fn main() { put_n(deep(100000)); put_c(10); return 0; } +fn main() { rt.put_n(deep(100000)); rt.put_c(10); return 0; } diff --git a/tests/programs/o3direct.zc b/tests/programs/o3direct.zc index 3c5d7ee..7998129 100644 --- a/tests/programs/o3direct.zc +++ b/tests/programs/o3direct.zc @@ -1,3 +1,6 @@ +namespace; +include rt, run; + // o3 calls between functions: arguments in registers, results on the stack, and the ways a call can leave the fast path fn put_f(x) { asm { x @ #1220 } return 0; } struct node { int v; node *next; }; @@ -10,24 +13,24 @@ o3 int four(int a, int b, int c, int d) { return three(a, b, c) * 10 + d; } o3 int zero() { return 7; } o3 int ack(int m, int n) { if (m == 0) return n + 1; if (n == 0) return ack(m - 1, 1); return ack(m - 1, ack(m, n - 1)); } o3 float half(float x, int k) { if (k == 0) return x; return half(x / 2.0, k - 1); } -o3 int noisy(int n) { if (n == 0) return 0; put_n(n); put_c(' '); return n + noisy(n - 1); } +o3 int noisy(int n) { if (n == 0) return 0; rt.put_n(n); rt.put_c(' '); return n + noisy(n - 1); } unchecked o3 int sumlist(node *p) { int s = 0; while (p) { s = s + p.v; p = p.next; } return s; } unchecked o3 int build(int n) { node *h = 0; - for (int i = 1; i <= n; i++) { node *x = new(node); x.v = i; x.next = h; h = x; } + for (int i = 1; i <= n; i++) { node *x = rt.new(node); x.v = i; x.next = h; h = x; } int s = sumlist(h); - while (h) { node *t = h.next; free(h); h = t; } + while (h) { node *t = h.next; rt.free(h); h = t; } return s; } unchecked o3 int mix(int a, int b) { return three(a, zero(), b) + fib(a) + four(1, 2, 3, b); } unchecked fn main() { - put_n(fib(20)); put_c(' '); - put_n(ev(10)); put_c(' '); put_n(ev(7)); put_c(' '); - put_n(three(1, 2, 3)); put_c(' '); put_n(four(4, 5, 6, 7)); put_c(' '); put_n(zero()); put_c(' '); - put_n(ack(2, 3)); put_c(' '); - put_f(half(64.0, 3)); put_c(' '); - put_n(build(100)); put_c(' '); - put_n(mix(6, 9)); put_c(10); - put_n(noisy(4)); put_c(10); + rt.put_n(fib(20)); rt.put_c(' '); + rt.put_n(ev(10)); rt.put_c(' '); rt.put_n(ev(7)); rt.put_c(' '); + rt.put_n(three(1, 2, 3)); rt.put_c(' '); rt.put_n(four(4, 5, 6, 7)); rt.put_c(' '); rt.put_n(zero()); rt.put_c(' '); + rt.put_n(ack(2, 3)); rt.put_c(' '); + put_f(half(64.0, 3)); rt.put_c(' '); + rt.put_n(build(100)); rt.put_c(' '); + rt.put_n(mix(6, 9)); rt.put_c(10); + rt.put_n(noisy(4)); rt.put_c(10); return 0; } diff --git a/tests/programs/o3fat.zc b/tests/programs/o3fat.zc index 937b0f3..487f279 100644 --- a/tests/programs/o3fat.zc +++ b/tests/programs/o3fat.zc @@ -1,20 +1,23 @@ +namespace; +include rt, run; + // o3 objects reaching normal code get their types: through a return (all the way down the list), an assignment, a store into a field struct node { int v; node* next; }; -struct box { [3]node* slots; int n; }; +struct box { node*[3] slots; int n; }; unchecked o3 node* build(int n) { node* head = 0; - for (int i = 0; i < n; i++) { node* k = new(node); k.v = i; k.next = head; head = k; } + for (int i = 0; i < n; i++) { node* k = rt.new(node); k.v = i; k.next = head; head = k; } return head; } unchecked fn main() { node* l = build(5); - tprint(typeof_obj(l)); put_c(' '); tprint(typeof_obj(l.next.next.next)); put_c(' '); - put_n(l.next.v); put_c(' '); + run.tprint(rt.typeof_obj(l)); rt.put_c(' '); run.tprint(rt.typeof_obj(l.next.next.next)); rt.put_c(' '); + rt.put_n(l.next.v); rt.put_c(' '); box* b = 0; - o3 { b = new(box); b.slots[1] = new(node); b.n = 2; } - tprint(typeof_obj(b)); put_c(' '); tprint(typeof_obj(b.slots[1])); put_c(' '); + o3 { b = rt.new(box); b.slots[1] = rt.new(node); b.n = 2; } + run.tprint(rt.typeof_obj(b)); rt.put_c(' '); run.tprint(rt.typeof_obj(b.slots[1])); rt.put_c(' '); int* raw = 0; - o3 { raw = new(int, 6); } - tprint(typeof_obj(raw)); put_c(10); + o3 { raw = rt.new(int, 6); } + run.tprint(rt.typeof_obj(raw)); rt.put_c(10); return 0; } diff --git a/tests/programs/o3licm.zc b/tests/programs/o3licm.zc index 61e6a46..43abfb9 100644 --- a/tests/programs/o3licm.zc +++ b/tests/programs/o3licm.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // loop invariants are built once in front of the loop, and a value two blocks both build is shared. // every result below has to match the plain run (ZYNOSHARE=1 turns the pass off). unchecked o3 int nest(int n, int k) { @@ -34,13 +37,13 @@ unchecked o3 int twice(int x, int y) { return r + (x * y + 3) * 7; } unchecked fn main() { - int* a = new(int, 4); + int* a = rt.new(int, 4); a[0] = 5; a[1] = 6; - put_n(nest(30, 3)); put_c(' '); - put_n(loads(a, 100)); put_c(' '); - put_n(stores(a, 10)); put_c(' '); - put_n(branchy(3, 4, 21)); put_c(' '); - put_n(twice(5, 2)); put_c(' '); - put_n(twice(-5, 2)); put_c(10); + rt.put_n(nest(30, 3)); rt.put_c(' '); + rt.put_n(loads(a, 100)); rt.put_c(' '); + rt.put_n(stores(a, 10)); rt.put_c(' '); + rt.put_n(branchy(3, 4, 21)); rt.put_c(' '); + rt.put_n(twice(5, 2)); rt.put_c(' '); + rt.put_n(twice(-5, 2)); rt.put_c(10); return 0; } diff --git a/tests/programs/o3push.zc b/tests/programs/o3push.zc index e5f0090..acb9fab 100644 --- a/tests/programs/o3push.zc +++ b/tests/programs/o3push.zc @@ -1,37 +1,40 @@ +namespace; +include rt; + // push in o3 code: the quick path when there is room, dpush when there is not -o3 int fill([]int a, int n) { +o3 int fill(int[?] a, int n) { for (int i = 0; i < n; i++) push(a, i * 3); return len(a); } -o3 int sum([]int a) { +o3 int sum(int[?] a) { int s = 0; for (int i = 0; i < len(a); i++) s += a[i]; return s; } -o3 float fsum([]float a) { +o3 float fsum(float[?] a) { float s = 0.0; for (int i = 0; i < len(a); i++) s += a[i]; return s; } -o3 int ffill([]float a, int n) { +o3 int ffill(float[?] a, int n) { for (int i = 0; i < n; i++) push(a, 0.5 * i); return len(a); } int main() { - []int a = new([]int, 0); - put_n(fill(a, 1000)); put_c(' '); - put_n(sum(a)); put_c(' '); - put_n(a[999]); put_c(' '); - put_n(fill(a, 5)); put_c(' '); - put_n(cap(a) >= 1005); put_c(' '); - []float f = new([]float, 0); + int[?] a = rt.new(int[?], 0); + rt.put_n(fill(a, 1000)); rt.put_c(' '); + rt.put_n(sum(a)); rt.put_c(' '); + rt.put_n(a[999]); rt.put_c(' '); + rt.put_n(fill(a, 5)); rt.put_c(' '); + rt.put_n(cap(a) >= 1005); rt.put_c(' '); + float[?] f = rt.new(float[?], 0); ffill(f, 10); - put_n(len(f)); put_c(' '); put_n(fsum(f) == 22.5); put_c(' '); - put_n(drain(a)); put_c(' '); put_n(len(a)); put_c(10); - free(d*a); free(d*f); + rt.put_n(len(f)); rt.put_c(' '); rt.put_n(fsum(f) == 22.5); rt.put_c(' '); + rt.put_n(drain(a)); rt.put_c(' '); rt.put_n(len(a)); rt.put_c(10); + rt.free(d*a); rt.free(d*f); return 0; } -o3 int drain([]int a) { +o3 int drain(int[?] a) { int s = 0; while (len(a) > 0) s += pop(a); return s; diff --git a/tests/programs/o3scopes.zc b/tests/programs/o3scopes.zc index 4dbf68c..a23a622 100644 --- a/tests/programs/o3scopes.zc +++ b/tests/programs/o3scopes.zc @@ -1,5 +1,8 @@ +namespace; +include rt; + // registers reused by scopes that never overlap, and a fixed array read through its cached items and length -o3 int scopes(fixed []int f) { +o3 int scopes(fixed int[?] f) { int total = 0; for (int a = 0; a < 3; a++) { int x = a * 2; total += x; } for (int b = 0; b < 3; b++) { int y = b * 3; total += y; } @@ -9,9 +12,9 @@ o3 int scopes(fixed []int f) { return total; } fn main() { - fixed []int f = new(fixed []int, 4); + fixed int[?] f = rt.new(fixed int[?], 4); for (int i = 0; i < 4; i++) f[i] = 10 * i; - put_n(scopes(f)); put_c(10); - free(d*f); + rt.put_n(scopes(f)); rt.put_c(10); + rt.free(d*f); return 0; } diff --git a/tests/programs/o3zero.zc b/tests/programs/o3zero.zc index cfb3061..7309c1d 100644 --- a/tests/programs/o3zero.zc +++ b/tests/programs/o3zero.zc @@ -1,8 +1,11 @@ +namespace; +include rt; + unchecked o3 int raw() { - int* p = new(4); + int* p = rt.new(4); return p[0] + p[1] + p[2] + p[3] + (p[-1] < 0); } fn main() { - put_n(raw()); put_c(10); + rt.put_n(raw()); rt.put_c(10); return 0; } diff --git a/tests/programs/overflow_data.zc b/tests/programs/overflow_data.zc index db24f8d..da21472 100644 --- a/tests/programs/overflow_data.zc +++ b/tests/programs/overflow_data.zc @@ -1 +1,4 @@ -fn main() { syscall(9, 5, syscall(9, 0, 0, 0), 0); while (1) asm { 1 } return 0; } +namespace; +include rt; + +fn main() { rt.syscall(9, 5, rt.syscall(9, 0, 0, 0), 0); while (1) asm { 1 } return 0; } diff --git a/tests/programs/overflow_frame.zc b/tests/programs/overflow_frame.zc index c017453..bc20178 100644 --- a/tests/programs/overflow_frame.zc +++ b/tests/programs/overflow_frame.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + fn big(n) { let a[500]; a[0] = n; return big(n + 1) + a[0]; } -fn main() { syscall(9, 5, syscall(9, 0, 0, 0), 0); return big(0); } +fn main() { rt.syscall(9, 5, rt.syscall(9, 0, 0, 0), 0); return big(0); } diff --git a/tests/programs/overflow_return.zc b/tests/programs/overflow_return.zc index 91e572d..decc5a8 100644 --- a/tests/programs/overflow_return.zc +++ b/tests/programs/overflow_return.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + fn deep(n) { return deep(n + 1) + 1; } -fn main() { syscall(9, 5, syscall(9, 0, 0, 0), 0); return deep(0); } +fn main() { rt.syscall(9, 5, rt.syscall(9, 0, 0, 0), 0); return deep(0); } diff --git a/tests/programs/packedfields.zc b/tests/programs/packedfields.zc index 8f6fe38..97afef0 100644 --- a/tests/programs/packedfields.zc +++ b/tests/programs/packedfields.zc @@ -1,29 +1,32 @@ +namespace; +include rt; + // struct fields of the sized types and f32 pack into shared words struct vtx { f32 x, y, z; u8 r, g, b, a; i16 id; u16 flags; int next; }; struct pair { u8 a; int b; u8 c; }; fn put_f(x) { asm { x @ #1220 } return 0; } -fn sp() { put_c(32); return 0; } +fn sp() { rt.put_c(32); return 0; } vtx gv; fn main() { vtx v; - vtx* p = new(vtx); + vtx* p = rt.new(vtx); pair q; - [3]vtx arr; - put_n(sizeof(vtx)); sp(); put_n(sizeof(pair)); sp(); + vtx[3] arr; + rt.put_n(sizeof(vtx)); sp(); rt.put_n(sizeof(pair)); sp(); v.x = 1.5; v.y = 0.1; v.z = 2; v.r = 200; v.g = 255; v.b = 300; v.a = 7; v.id = 0 - 3; v.flags = 65535; v.next = 99; - put_f(v.x + v.z); sp(); put_n(v.y == 0.1); sp(); - put_n(v.r); sp(); put_n(v.g); sp(); put_n(v.b); sp(); put_n(v.a); sp(); put_n(v.id); sp(); put_n(v.flags); sp(); put_n(v.next); sp(); + put_f(v.x + v.z); sp(); rt.put_n(v.y == 0.1); sp(); + rt.put_n(v.r); sp(); rt.put_n(v.g); sp(); rt.put_n(v.b); sp(); rt.put_n(v.a); sp(); rt.put_n(v.id); sp(); rt.put_n(v.flags); sp(); rt.put_n(v.next); sp(); v.r += 100; v.flags++; v.id--; v.g = v.g + 1; - put_n(v.r); sp(); put_n(v.flags); sp(); put_n(v.id); sp(); put_n(v.g); sp(); + rt.put_n(v.r); sp(); rt.put_n(v.flags); sp(); rt.put_n(v.id); sp(); rt.put_n(v.g); sp(); p.r = 9; p.a = 8; p.id = 1000; p.next = 5; p.x = 3.25; - put_n(p.r); put_n(p.a); sp(); put_n(p.id); sp(); put_n(p.next); sp(); put_f(p.x); sp(); - gv.b = 77; gv.flags = 5; put_n(gv.b); sp(); put_n(gv.flags); sp(); + rt.put_n(p.r); rt.put_n(p.a); sp(); rt.put_n(p.id); sp(); rt.put_n(p.next); sp(); put_f(p.x); sp(); + gv.b = 77; gv.flags = 5; rt.put_n(gv.b); sp(); rt.put_n(gv.flags); sp(); arr[1].a = 42; arr[2].r = 17; arr[1].next = 3; arr[0].id = 0 - 9; - put_n(arr[1].a); sp(); put_n(arr[2].r); sp(); put_n(arr[1].next); sp(); put_n(arr[0].id); sp(); put_n(arr[0].a); sp(); - put_n(sizeof(arr)); sp(); - q.a = 1; q.b = 123456789; q.c = 2; put_n(q.a); put_n(q.c); sp(); put_n(q.b); - put_c(10); - free(d*p); + rt.put_n(arr[1].a); sp(); rt.put_n(arr[2].r); sp(); rt.put_n(arr[1].next); sp(); rt.put_n(arr[0].id); sp(); rt.put_n(arr[0].a); sp(); + rt.put_n(sizeof(arr)); sp(); + q.a = 1; q.b = 123456789; q.c = 2; rt.put_n(q.a); rt.put_n(q.c); sp(); rt.put_n(q.b); + rt.put_c(10); + rt.free(d*p); second(); return 0; } @@ -34,9 +37,9 @@ o3 int bright(rgba* px, int n) { return s; } fn second() { - rgba* px = new(rgba, 50); + rgba* px = rt.new(rgba, 50); for (int i = 0; i < 50; i++) { px[i].r = i; px[i].g = i * 2; px[i].b = 255 - i; } - put_n(bright(px, 50)); put_c(' '); put_n(px[10].a); put_c(' '); put_n(px[49].a); put_c(10); - free(d*px); + rt.put_n(bright(px, 50)); rt.put_c(' '); rt.put_n(px[10].a); rt.put_c(' '); rt.put_n(px[49].a); rt.put_c(10); + rt.free(d*px); return 0; } diff --git a/tests/programs/quarantine.zc b/tests/programs/quarantine.zc index 5e3e188..061c4da 100644 --- a/tests/programs/quarantine.zc +++ b/tests/programs/quarantine.zc @@ -1,25 +1,28 @@ +namespace; +include rt; + // free quarantines the block. a stale alias still reads it, and freeing // through the alias does nothing because the header already says free. // unchecked so the analyzer doesn't reject the use after free. unchecked fn main() { - let p = new(4), q, r, i = 0, t, bad = 0; + let p = rt.new(4), q, r, i = 0, t, bad = 0; p[0] = 111; p[1] = 222; p[2] = 333; p[3] = 444; q = p; r = p + 2; - free(p); - while (i < 400) { t = new(4); t[0] = 999; t[1] = 888; t[2] = 777; t[3] = 666; i++; } + rt.free(p); + while (i < 400) { t = rt.new(4); t[0] = 999; t[1] = 888; t[2] = 777; t[3] = 666; i++; } if (q[0] != 111 || q[1] != 222 || r[0] != 333 || r[1] != 444) bad = 1; - free(q); + rt.free(q); if (q[0] != 111 || q[1] != 222) bad = 2; - gc(); // q still points at the block, so the sweep must not reuse it + rt.gc(); // q still points at the block, so the sweep must not reuse it if (q[0] != 111 || r[0] != 333) bad = 3; p = 0; q = 0; r = 0; t = 0; - scrub(); - gc(); - t = new(4); + rt.scrub(); + rt.gc(); + t = rt.new(4); t[0] = 1234; - put_n(bad); put_c(' '); - put_n(t[0]); put_c(10); - free(t); + rt.put_n(bad); rt.put_c(' '); + rt.put_n(t[0]); rt.put_c(10); + rt.free(t); return 0; } diff --git a/tests/programs/rawpid.zc b/tests/programs/rawpid.zc index b3e7c2a..e9fec7d 100644 --- a/tests/programs/rawpid.zc +++ b/tests/programs/rawpid.zc @@ -1,6 +1,9 @@ -include "src/lib/std.zc"; +namespace; +include rt, std; + +include std; // syscall 39 is getpid on x86-64 only; the arm64 number differs, so test.sh runs this there alone. fn main() { - put_n(Host.raw(39, 0, 0, 0, 0, 0, 0) > 0); put_c(10); + rt.put_n(std.Host.raw(39, 0, 0, 0, 0, 0, 0) > 0); rt.put_c(10); return 0; } diff --git a/tests/programs/rawsys.zc b/tests/programs/rawsys.zc index 753e1b7..7d52040 100644 --- a/tests/programs/rawsys.zc +++ b/tests/programs/rawsys.zc @@ -1,5 +1,8 @@ -include "src/lib/std.zc"; +namespace; +include rt, std; + +include std; fn main() { - put_n(Host.raw(9999, 0, 0, 0, 0, 0, 0)); put_c(10); + rt.put_n(std.Host.raw(9999, 0, 0, 0, 0, 0, 0)); rt.put_c(10); return 0; } diff --git a/tests/programs/recycle.zc b/tests/programs/recycle.zc index 98c9b50..55951a5 100644 --- a/tests/programs/recycle.zc +++ b/tests/programs/recycle.zc @@ -1,23 +1,26 @@ +namespace; +include rt; + // a uniquely owned free returns the block to its size class at once. // a plain free still quarantines; that contract lives in quarantine.zc. struct node { node* c; int n; }; unchecked fn addr(p) { return p; } fn main() { - let base = live_objects(), p, q, slot; + let base = rt.live_objects(), p, q, slot; node* a; node* b; - p = new(4); - a = new(node); - b = new(node); + p = rt.new(4); + a = rt.new(node); + b = rt.new(node); p[0] = 111; a.c = b; a.n = 1; b.c = 0; b.n = 2; slot = addr(p); - free(d*p); - q = new(4); - free(d*a); - put_n(q == slot); put_c(' '); - put_n(q[0]); put_c(' '); - put_n(live_objects() - base); put_c(10); - free(d*q); + rt.free(d*p); + q = rt.new(4); + rt.free(d*a); + rt.put_n(q == slot); rt.put_c(' '); + rt.put_n(q[0]); rt.put_c(' '); + rt.put_n(rt.live_objects() - base); rt.put_c(10); + rt.free(d*q); return 0; } diff --git a/tests/programs/replace_owner.expected b/tests/programs/replace_owner.expected new file mode 100644 index 0000000..c0cbecc --- /dev/null +++ b/tests/programs/replace_owner.expected @@ -0,0 +1 @@ +98 7 9 0 diff --git a/tests/programs/replace_owner.zc b/tests/programs/replace_owner.zc new file mode 100644 index 0000000..8318616 --- /dev/null +++ b/tests/programs/replace_owner.zc @@ -0,0 +1,27 @@ +namespace; +include rt; + +// a released owner can receive a same-scope replacement, without copying owners. +int*.u growing(int n) { + int* buffer = rt.new(int), next; int i = 0; + while (i < n) { + if (!(i & 1)) { + next = rt.new(int); + *next = i; + rt.free(buffer); + buffer = next; + } + i++; + } + return buffer; +} +fn main() { + let base = rt.live_objects(); + { + let result = growing(100); rt.put_n(*result); rt.put_c(32); + int* slot = rt.new(int), replacement = rt.new(int); *replacement = 7; + rt.free(slot); slot = replacement; rt.put_n(*slot); rt.put_c(32); + rt.free(slot); slot = rt.new(int); *slot = 9; rt.put_n(*slot); rt.put_c(32); + } + rt.gc(); rt.put_n(rt.live_objects() - base); rt.put_c(10); +} diff --git a/tests/programs/roots.zc b/tests/programs/roots.zc index 22f12a5..4f05844 100644 --- a/tests/programs/roots.zc +++ b/tests/programs/roots.zc @@ -1,17 +1,20 @@ +namespace; +include rt; + // a pointer left below the live stack is not a root. scrub then clears that word. unchecked fn main() { let p, slot, before, after; - p = new(2); + p = rt.new(2); p[0] = 7; p[1] = 8; - slot = DSLO + 8; + slot = rt.DSLO + 8; *slot = p; - before = live_objects(); + before = rt.live_objects(); p = 0; - gc(); - after = live_objects(); - put_n(after < before); put_c(32); - scrub(); - put_n(*slot); put_c(10); + rt.gc(); + after = rt.live_objects(); + rt.put_n(after < before); rt.put_c(32); + rt.scrub(); + rt.put_n(*slot); rt.put_c(10); return 0; } diff --git a/tests/programs/scrub.zc b/tests/programs/scrub.zc index ebc6cfa..2ee9dcf 100644 --- a/tests/programs/scrub.zc +++ b/tests/programs/scrub.zc @@ -1,9 +1,12 @@ +namespace; +include rt; + unchecked fn main() { - mem_set(2); - let p = new(2); + rt.mem_set(2); + let p = rt.new(2); p[0] = 11; - free(p); - put_n(p[0]); - put_c(10); + rt.free(p); + rt.put_n(p[0]); + rt.put_c(10); return 0; } diff --git a/tests/programs/share.zc b/tests/programs/share.zc index e9c381f..91a3918 100644 --- a/tests/programs/share.zc +++ b/tests/programs/share.zc @@ -1,34 +1,51 @@ +namespace; +include rt; + // shared cons cells, a shared string, and a cycle. the collector reclaims // them once nothing points at them. free of a shared cell does nothing. +// the shared cells again without printing, moved by a few blocks. returns the blocks left over +fn churn(base) { + let a = rt.cons(1, 2), b = rt.cons(a, 3), c = rt.cons(a, 4), cyc = rt.cons(0, 1), tri = rt.triple(5, 6, 7); + cyc[0] = cyc; + a = 0; b = 0; c = 0; cyc = 0; tri = 0; + rt.scrub(); + rt.gc(); + return rt.live_objects() - base; +} fn main() { - let base = live_objects(), a, b, c, s, p, q, cyc, n, tri; - a = cons(1, 2); - b = cons(a, 3); - c = cons(a, 4); - put_n(car(a)); put_c(32); - put_n(cdr(a)); put_c(32); - put_n(car(car(b))); put_c(32); - put_n(cdr(c)); put_c(32); - put_n(car(b) == a); put_c(32); - put_n(is_shared(a)); put_c(32); - n = live_objects(); - free(a); - put_n(live_objects() == n); put_c(32); - put_n(car(a)); put_c(32); - s = n_to_s(42); - p = cons(s, 0); - q = cons(s, 1); - put_n(s_to_n(car(p))); put_c(32); - put_n(car(p) == car(q)); put_c(32); - cyc = cons(0, 1); + let base = rt.live_objects(), a, b, c, s, p, q, cyc, n, tri, k, pad; + a = rt.cons(1, 2); + b = rt.cons(a, 3); + c = rt.cons(a, 4); + rt.put_n(rt.car(a)); rt.put_c(32); + rt.put_n(rt.cdr(a)); rt.put_c(32); + rt.put_n(rt.car(rt.car(b))); rt.put_c(32); + rt.put_n(rt.cdr(c)); rt.put_c(32); + rt.put_n(rt.car(b) == a); rt.put_c(32); + rt.put_n(rt.is_shared(a)); rt.put_c(32); + n = rt.live_objects(); + rt.free(a); + rt.put_n(rt.live_objects() == n); rt.put_c(32); + rt.put_n(rt.car(a)); rt.put_c(32); + s = rt.n_to_s(42); + p = rt.cons(s, 0); + q = rt.cons(s, 1); + rt.put_n(rt.s_to_n(rt.car(p))); rt.put_c(32); + rt.put_n(rt.car(p) == rt.car(q)); rt.put_c(32); + cyc = rt.cons(0, 1); cyc[0] = cyc; - put_n(cdr(cyc)); put_c(32); - put_n(car(cyc) == cyc); put_c(32); - tri = triple(5, 6, 7); - put_n(tget(tri, 1)); put_c(32); + rt.put_n(rt.cdr(cyc)); rt.put_c(32); + rt.put_n(rt.car(cyc) == cyc); rt.put_c(32); + tri = rt.triple(5, 6, 7); + rt.put_n(rt.tget(tri, 1)); rt.put_c(32); a = 0; b = 0; c = 0; s = 0; p = 0; q = 0; cyc = 0; tri = 0; - scrub(); - gc(); - put_n(live_objects() - base); put_c(10); + rt.scrub(); + rt.gc(); + // the collector marks conservatively: on a small map a word in the code can equal a block's address by + // chance and keep it. shifting where the blocks land must therefore reach a clean count + n = rt.live_objects() - base; + k = 0; + while (n && k < 40) { pad = rt.cons(k, k); rt.scrub(); rt.gc(); n = churn(rt.live_objects()); k++; } + rt.put_n(n); rt.put_c(10); return 0; } diff --git a/tests/programs/share_dbg.zc b/tests/programs/share_dbg.zc index 5485b51..63b8cba 100644 --- a/tests/programs/share_dbg.zc +++ b/tests/programs/share_dbg.zc @@ -1,10 +1,13 @@ +namespace; +include rt; + // memdebug still checks a shared cell. free does not quarantine it, and car // reads the payload through checkptr. memdebug on; fn main() { - let a = cons(7, 8); - free(a); - put_n(car(a)); put_c(32); - put_n(is_shared(a)); put_c(10); + let a = rt.cons(7, 8); + rt.free(a); + rt.put_n(rt.car(a)); rt.put_c(32); + rt.put_n(rt.is_shared(a)); rt.put_c(10); return 0; } diff --git a/tests/programs/sized.zc b/tests/programs/sized.zc index 0a18467..1be0b31 100644 --- a/tests/programs/sized.zc +++ b/tests/programs/sized.zc @@ -1,9 +1,12 @@ +namespace; +include rt, run; + // sized integers: scalars wrap to their size, arrays and pointers of them are packed -struct pix { u8 r, g, b, a; i16 dx; [4]u8 tag; }; +struct pix { u8 r, g, b, a; i16 dx; u8[4] tag; }; u8 gbyte = 300; i8 gneg = 200; u16 gword[5]; -[10]u8 gbuf; +u8[10] run.gbuf; u8 add8(u8 a, u8 b) { return a + b; } @@ -12,13 +15,13 @@ fn scalars() { i8 s = 100; i16 h = 32767; u32 w = 4294967295; - a = a + 10; put_n(a); put_c(' '); // 4 - s = s + 100; put_n(s); put_c(' '); // -56 - h++; put_n(h); put_c(' '); // -32768 - w++; put_n(w); put_c(' '); // 0 - put_n(gbyte); put_c(' '); put_n(gneg); put_c(' '); // 44 -56 - put_n(add8(200, 100)); put_c(' '); // 44 - put_n((i8)200); put_c(' '); put_n(300 as u8); put_c(10); // -56 44 + a = a + 10; rt.put_n(a); rt.put_c(' '); // 4 + s = s + 100; rt.put_n(s); rt.put_c(' '); // -56 + h++; rt.put_n(h); rt.put_c(' '); // -32768 + w++; rt.put_n(w); rt.put_c(' '); // 0 + rt.put_n(gbyte); rt.put_c(' '); rt.put_n(gneg); rt.put_c(' '); // 44 -56 + rt.put_n(add8(200, 100)); rt.put_c(' '); // 44 + rt.put_n((i8)200); rt.put_c(' '); rt.put_n(300 as u8); rt.put_c(10); // -56 44 return 0; } fn arrays() { @@ -29,36 +32,36 @@ fn arrays() { int i = 0, sum = 0; while (i < 20) { buf[i] = i * 20; i++; } // wraps past 255 i = 0; while (i < 20) { sum = sum + buf[i]; i++; } - put_n(sum); put_c(' '); // 1250 - sh[3] = 40000; put_n(sh[3]); put_c(' '); // -25536 - sh[3] += 30000; put_n(sh[3]); put_c(' '); // 4464 - ws[1] = 5000000000; put_n(ws[1]); put_c(' '); // 705032704 - sb[8] = 127; sb[8]++; put_n(sb[8]); put_c(' '); // -128 - sb[0] = 200; put_n(sb[0] < 0); put_c(' '); // 1: -56 is negative - ws[0] = 4000000000; put_n(ws[0] + ws[0]); put_c(' '); // arithmetic is done in int: 8000000000 - put_n(sizeof(buf)); put_c(' '); // 3 words - put_n(sizeof(sh)); put_c(10); // 2 words + rt.put_n(sum); rt.put_c(' '); // 1250 + sh[3] = 40000; rt.put_n(sh[3]); rt.put_c(' '); // -25536 + sh[3] += 30000; rt.put_n(sh[3]); rt.put_c(' '); // 4464 + ws[1] = 5000000000; rt.put_n(ws[1]); rt.put_c(' '); // 705032704 + sb[8] = 127; sb[8]++; rt.put_n(sb[8]); rt.put_c(' '); // -128 + sb[0] = 200; rt.put_n(sb[0] < 0); rt.put_c(' '); // 1: -56 is negative + ws[0] = 4000000000; rt.put_n(ws[0] + ws[0]); rt.put_c(' '); // arithmetic is done in int: 8000000000 + rt.put_n(sizeof(buf)); rt.put_c(' '); // 3 words + rt.put_n(sizeof(sh)); rt.put_c(10); // 2 words return 0; } fn heap() { - u8* p = new(u8, 1000); + u8* p = rt.new(u8, 1000); int i = 0, sum = 0; while (i < 1000) { p[i] = i; i++; } i = 0; while (i < 1000) { sum = sum + p[i]; i++; } - put_n(sum); put_c(' '); // 1000 bytes of 0..255: 3 * 32640 + 28 * ... = 127.. - put_n(osize(p) < 256); put_c(32); // 125 words plus the header, not 1000 - *p = 77; put_n(p[0]); put_c(' '); - free(d*p); - gbuf[9] = 255; gword[4] = 65535; gword[4]++; - put_n(gbuf[9]); put_c(' '); put_n(gword[4]); put_c(10); + rt.put_n(sum); rt.put_c(' '); // 1000 bytes of 0..255: 3 * 32640 + 28 * ... = 127.. + rt.put_n(rt.osize(p) < 256); rt.put_c(32); // 125 words plus the header, not 1000 + *p = 77; rt.put_n(p[0]); rt.put_c(' '); + rt.free(d*p); + run.gbuf[9] = 255; gword[4] = 65535; gword[4]++; + rt.put_n(run.gbuf[9]); rt.put_c(' '); rt.put_n(gword[4]); rt.put_c(10); return 0; } fn fields() { - pix* q = new(pix); + pix* q = rt.new(pix); q.r = 255; q.g = 256; q.dx = 65535; q.tag[2] = 7; q.tag[3] = 300; - put_n(q.r); put_c(' '); put_n(q.g); put_c(' '); put_n(q.dx); put_c(' '); - put_n(q.tag[2]); put_c(' '); put_n(q.tag[3]); put_c(' '); put_n(sizeof(pix)); put_c(10); - free(d*q); + rt.put_n(q.r); rt.put_c(' '); rt.put_n(q.g); rt.put_c(' '); rt.put_n(q.dx); rt.put_c(' '); + rt.put_n(q.tag[2]); rt.put_c(' '); rt.put_n(q.tag[3]); rt.put_c(' '); rt.put_n(sizeof(pix)); rt.put_c(10); + rt.free(d*q); return 0; } o3 fn checksum(u8* p, int n) { @@ -67,11 +70,11 @@ o3 fn checksum(u8* p, int n) { return b * 256 + a; } fn o3run() { - u8* p = new(u8, 5000); + u8* p = rt.new(u8, 5000); int i = 0; while (i < 5000) { p[i] = i * 7; i++; } - put_n(checksum(p, 5000)); put_c(10); - free(d*p); + rt.put_n(checksum(p, 5000)); rt.put_c(10); + rt.free(d*p); return 0; } fn main() { diff --git a/tests/programs/sizedvec.zc b/tests/programs/sizedvec.zc index 2055ade..1b84df3 100644 --- a/tests/programs/sizedvec.zc +++ b/tests/programs/sizedvec.zc @@ -1,39 +1,42 @@ -// growable arrays of the sized types and f32: []u8, []i16, []f32 +namespace; +include rt; + +// growable arrays of the sized types and f32: u8[?], i16[?], f32[?] fn put_f(x) { asm { x @ #1220 } return 0; } -fn sp() { put_c(32); return 0; } +fn sp() { rt.put_c(32); return 0; } fn main() { - []u8 b = new([]u8, 0); - []i16 h = new([]i16, 3); - []f32 f = new([]f32, 0); + u8[?] b = rt.new(u8[?], 0); + i16[?] h = rt.new(i16[?], 3); + f32[?] f = rt.new(f32[?], 0); int i = 0, s = 0; while (i < 100) { push(b, i * 7); i++; } i = 0; while (i < 100) { s = s + b[i]; i++; } - put_n(len(b)); sp(); put_n(s); sp(); put_n(b[99]); sp(); - b[5] = 300; put_n(b[5]); sp(); - put_n(pop(b)); sp(); put_n(len(b)); sp(); + rt.put_n(len(b)); sp(); rt.put_n(s); sp(); rt.put_n(b[99]); sp(); + b[5] = 300; rt.put_n(b[5]); sp(); + rt.put_n(pop(b)); sp(); rt.put_n(len(b)); sp(); h[0] = 40000; h[2] = 0 - 5; push(h, 70000); - put_n(h[0]); sp(); put_n(h[1]); sp(); put_n(h[2]); sp(); put_n(h[3]); sp(); + rt.put_n(h[0]); sp(); rt.put_n(h[1]); sp(); rt.put_n(h[2]); sp(); rt.put_n(h[3]); sp(); push(f, 0.1); push(f, 2.5); push(f, 1.0); - put_n(f[0] == 0.1); sp(); put_f(f[1]); sp(); put_f(f[0] + f[2]); sp(); + rt.put_n(f[0] == 0.1); sp(); put_f(f[1]); sp(); put_f(f[0] + f[2]); sp(); resize(b, 3); resize(b, 12); - put_n(b[2]); put_n(b[3]); put_n(b[11]); sp(); - put_n(cap(b) >= 12); - put_c(10); - free(d*b); free(d*h); free(d*f); + rt.put_n(b[2]); rt.put_n(b[3]); rt.put_n(b[11]); sp(); + rt.put_n(cap(b) >= 12); + rt.put_c(10); + rt.free(d*b); rt.free(d*h); rt.free(d*f); second(); return 0; } -o3 int sumv([]u8 v) { +o3 int sumv(u8[?] v) { int s = 0; for (int i = 0; i < len(v); i++) s += v[i]; return s; } fn second() { - []u8 v = new([]u8, 0); + u8[?] v = rt.new(u8[?], 0); int i = 0; while (i < 300) { push(v, i); i++; } - put_n(sumv(v)); put_c(10); - free(d*v); + rt.put_n(sumv(v)); rt.put_c(10); + rt.free(d*v); return 0; } diff --git a/tests/programs/static.zc b/tests/programs/static.zc index ef54339..1b792dc 100644 --- a/tests/programs/static.zc +++ b/tests/programs/static.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + struct Str { int len; static fn size(s) { return s * 2; } static fn none() { return 5; } fn get() { return this.len; } } -int main() { Str a; a.len = 9; put_n(Str.size(7)); put_c(32); put_n(Str.none()); put_c(32); put_n(a.get()); put_c(10); return 0; } +int main() { Str a; a.len = 9; rt.put_n(Str.size(7)); rt.put_c(32); rt.put_n(Str.none()); rt.put_c(32); rt.put_n(a.get()); rt.put_c(10); return 0; } diff --git a/tests/programs/stdbuf.zc b/tests/programs/stdbuf.zc index ab8f9dc..acb1d2f 100644 --- a/tests/programs/stdbuf.zc +++ b/tests/programs/stdbuf.zc @@ -1,18 +1,21 @@ -include "src/lib/std.zc"; +namespace; +include rt, std; + +include std; fn main() { - let a = arena(8), p, q, b, i = 0, s = 0; - p = Arena.alloc(a, 4); - q = Arena.alloc(a, 4); - put_n(q - p); put_c(' '); - Arena.reset(a); - put_n(Arena.alloc(a, 4) == p); put_c(' '); - b = bnew(8); - while (i < 4) { Buf.set(b, i, 'A' + i); i++; } + let a = std.arena(8), p, q, b, i = 0, s = 0; + p = std.Arena.alloc(a, 4); + q = std.Arena.alloc(a, 4); + rt.put_n(q - p); rt.put_c(' '); + std.Arena.reset(a); + rt.put_n(std.Arena.alloc(a, 4) == p); rt.put_c(' '); + b = std.bnew(8); + while (i < 4) { std.Buf.set(b, i, 'A' + i); i++; } i = 0; - while (i < Buf.length(b)) { s = s + Buf.get(b, i); i++; } - put_n(s); put_c(' '); - put_n(Buf.capacity(b)); put_c(10); - free(d*a); - free(d*b); + while (i < std.Buf.length(b)) { s = s + std.Buf.get(b, i); i++; } + rt.put_n(s); rt.put_c(' '); + rt.put_n(std.Buf.capacity(b)); rt.put_c(10); + rt.free(d*a); + rt.free(d*b); return 0; } diff --git a/tests/programs/stdmore.zc b/tests/programs/stdmore.zc index fb225ec..e7975e6 100644 --- a/tests/programs/stdmore.zc +++ b/tests/programs/stdmore.zc @@ -1,32 +1,44 @@ -include "src/lib/std.zc"; +namespace; +include rt, std; + +include std; fn main() { let buf[32]; - put_n(Text.len("hello")); put_c(' '); - put_n(Text.eq("abc", "abc")); put_c(' '); - put_n(Text.eq("abc", "abd")); put_c(' '); - put_n(Text.eq("ab", "abc")); put_c(' '); - put_n(Text.copy(buf, "copy me")); put_c(' '); - put_s(buf); put_c(' '); - put_n(Text.find("needle in a haystack", "hay")); put_c(' '); - put_n(Text.find("abc", "zz")); put_c(' '); - put_n(Text.find("abc", "")); put_c(10); - Text.copy(buf, "MiXeD 123"); - put_n(Text.lower(buf)); put_c(' '); - put_s(buf); put_c(' '); - put_n(Text.upper(buf)); put_c(' '); - put_s(buf); put_c(10); - Text.copy(buf, " \t padded line \r\n"); - put_n(Text.trim(buf)); put_c(' '); - put_c('['); put_s(buf); put_c(']'); put_c(10); - put_n(Num.abs(0 - 42)); put_c(' '); - put_n(Num.min(3, 9)); put_c(' '); - put_n(Num.max(3, 9)); put_c(' '); - put_n(Num.clamp(50, 0, 10)); put_c(' '); - put_n(Num.gcd(0 - 84, 36)); put_c(' '); - put_n(Num.pow(3, 4)); put_c(' '); - put_n(Num.isqrt(99)); put_c(' '); - put_n(Num.isqrt(100)); put_c(' '); - put_n(Num.sign(0 - 5)); put_c(' '); - put_n(Num.sign(0)); put_c(10); + rt.put_n(std.Text.len("hello")); rt.put_c(' '); + rt.put_n(std.Text.eq("abc", "abc")); rt.put_c(' '); + rt.put_n(std.Text.eq("abc", "abd")); rt.put_c(' '); + rt.put_n(std.Text.eq("ab", "abc")); rt.put_c(' '); + rt.put_n(std.Text.copy(buf, "copy me")); rt.put_c(' '); + rt.put_s(buf); rt.put_c(' '); + rt.put_n(std.Text.find("needle in a haystack", "hay")); rt.put_c(' '); + rt.put_n(std.Text.find("abc", "zz")); rt.put_c(' '); + rt.put_n(std.Text.find("abc", "")); rt.put_c(' '); + rt.put_n(std.Text.ends("needle", "dle")); rt.put_c(' '); + rt.put_n(std.Text.ends("a", "abc")); rt.put_c(' '); + rt.put_n(std.Text.ends("a", "")); rt.put_c(10); + std.Text.copy(buf, "MiXeD 123"); + rt.put_n(std.Text.lower(buf)); rt.put_c(' '); + rt.put_s(buf); rt.put_c(' '); + rt.put_n(std.Text.upper(buf)); rt.put_c(' '); + rt.put_s(buf); rt.put_c(10); + std.Text.copy(buf, " \t padded line \r\n"); + rt.put_n(std.Text.trim(buf)); rt.put_c(' '); + rt.put_c('['); rt.put_s(buf); rt.put_c(']'); rt.put_c(10); + rt.put_n(std.Num.abs(0 - 42)); rt.put_c(' '); + rt.put_n(std.Num.min(3, 9)); rt.put_c(' '); + rt.put_n(std.Num.max(3, 9)); rt.put_c(' '); + rt.put_n(std.Num.clamp(50, 0, 10)); rt.put_c(' '); + rt.put_n(std.Num.gcd(0 - 84, 36)); rt.put_c(' '); + rt.put_n(std.Num.pow(3, 4)); rt.put_c(' '); + rt.put_n(std.Num.isqrt(99)); rt.put_c(' '); + rt.put_n(std.Num.isqrt(100)); rt.put_c(' '); + rt.put_n(std.Num.sign(0 - 5)); rt.put_c(' '); + rt.put_n(std.Num.sign(0)); rt.put_c(10); + rt.put_n(std.Bytes.len(b"packed")); rt.put_c(' '); + rt.put_n(std.Bytes.eq(b"abc", b"abc")); rt.put_c(' '); + rt.put_n(std.Bytes.find(b"needle", b"ed")); rt.put_c(' '); + rt.put_n(std.Bytes.find(b"abc", b"z")); rt.put_c(' '); + rt.put_n(std.Bytes.ends(b"packed", b"ed")); rt.put_c(' '); + rt.put_n(std.Bytes.ends(b"a", b"abc")); rt.put_c(10); return 0; } diff --git a/tests/programs/strings.zc b/tests/programs/strings.zc index 431bd4e..cde3a94 100644 --- a/tests/programs/strings.zc +++ b/tests/programs/strings.zc @@ -1,37 +1,40 @@ +namespace; +include rt; + fn main() { let a = b"hi", b = b"!", c = a + b; let mid = c[1:3]; - let neg = s_to_n(b"-42"); - let p = n_to_s(42); - let q = n_to_s(0 - 42); - let ten = s_to_n(b"10"); - let big = n_to_s(1 << 63); + let neg = rt.s_to_n(b"-42"); + let p = rt.n_to_s(42); + let q = rt.n_to_s(0 - 42); + let ten = rt.s_to_n(b"10"); + let big = rt.n_to_s(1 << 63); let empty = b""; let tail = c[2:99]; let esc = b"a\nb"; let z = b"" + b"ab"; - put_s(c); put_c(32); - put_n(len(c)); put_c(32); - put_n(len_s(c)); put_c(32); - put_n(c[0]); put_c(32); - put_s(mid); put_c(32); - put_n(c == b"hi!"); put_c(32); - put_n(c != b"hi"); put_c(32); - put_n(c < b"hj"); put_c(32); - put_n(neg); put_c(32); - put_s(p); put_c(32); - put_s(q); put_c(32); - put_n(ten + 1); put_c(32); - put_n(p == b"42"); put_c(32); - put_s(big); put_c(32); - put_n(len(empty)); put_c(32); - put_n(len(tail)); put_c(32); - put_n(len_s(esc)); put_c(32); - put_n(at_s(esc, 1)); put_c(32); - put_n(b"ab" > b"a"); put_c(32); - put_n(b"ab" >= b"ab"); put_c(32); - put_n(b"a" <= b"a"); put_c(32); - put_s("W"); put_c(32); - put_s(z); put_c(10); + rt.put_s(c); rt.put_c(32); + rt.put_n(len(c)); rt.put_c(32); + rt.put_n(rt.len_s(c)); rt.put_c(32); + rt.put_n(c[0]); rt.put_c(32); + rt.put_s(mid); rt.put_c(32); + rt.put_n(c == b"hi!"); rt.put_c(32); + rt.put_n(c != b"hi"); rt.put_c(32); + rt.put_n(c < b"hj"); rt.put_c(32); + rt.put_n(neg); rt.put_c(32); + rt.put_s(p); rt.put_c(32); + rt.put_s(q); rt.put_c(32); + rt.put_n(ten + 1); rt.put_c(32); + rt.put_n(p == b"42"); rt.put_c(32); + rt.put_s(big); rt.put_c(32); + rt.put_n(len(empty)); rt.put_c(32); + rt.put_n(len(tail)); rt.put_c(32); + rt.put_n(rt.len_s(esc)); rt.put_c(32); + rt.put_n(rt.at_s(esc, 1)); rt.put_c(32); + rt.put_n(b"ab" > b"a"); rt.put_c(32); + rt.put_n(b"ab" >= b"ab"); rt.put_c(32); + rt.put_n(b"a" <= b"a"); rt.put_c(32); + rt.put_s("W"); rt.put_c(32); + rt.put_s(z); rt.put_c(10); return 0; } diff --git a/tests/programs/structret.zc b/tests/programs/structret.zc index 708c83c..5e99653 100644 --- a/tests/programs/structret.zc +++ b/tests/programs/structret.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // structs returned from native functions: the stub makes the result (a new object, // owned by the caller) and the native call fills it in. the helper library is built // by test.sh (build/sbv.so). @@ -14,11 +17,11 @@ extern struct Mixed mkmixed(long a, double x); extern struct Vec3 mkv3(double x); int main() { Pair* p; Vec* v; Trio* t; Mixed* m; Vec3* w; - cload(b"build/sbv.so"); - p = mkpair(3, 4); put_n(p.a); put_c(32); put_n(p.b); put_c(32); - v = mkvec(2.5, 3.0); put_f(v.x); put_c(32); put_f(v.y); put_c(32); - t = mktrio(7, 8, 9); put_n(t.a + t.b + t.c); put_c(32); - m = mkmixed(7, 0.5); put_n(m.a); put_c(32); put_f(m.x); put_c(32); - w = mkv3(1.5); put_f(w.z); put_c(10); + rt.cload(b"build/sbv.so"); + p = mkpair(3, 4); rt.put_n(p.a); rt.put_c(32); rt.put_n(p.b); rt.put_c(32); + v = mkvec(2.5, 3.0); put_f(v.x); rt.put_c(32); put_f(v.y); rt.put_c(32); + t = mktrio(7, 8, 9); rt.put_n(t.a + t.b + t.c); rt.put_c(32); + m = mkmixed(7, 0.5); rt.put_n(m.a); rt.put_c(32); put_f(m.x); rt.put_c(32); + w = mkv3(1.5); put_f(w.z); rt.put_c(10); return 0; } diff --git a/tests/programs/structval.zc b/tests/programs/structval.zc index a1bd041..c7d0573 100644 --- a/tests/programs/structval.zc +++ b/tests/programs/structval.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // structs by value in native calls: a zc struct of word fields is passed to a c // function as its words. the helper library is built by test.sh (build/sbv.so). struct Pair { int a; int b; } @@ -11,16 +14,16 @@ extern long trio_sum(long a, long b, long c, long d, long e, long f, struct Trio extern long eight(long a, long b, long c, long d, long e, long f, long g, long h, struct Pair p); int main() { Pair p; Pair q; Vec v; Trio t; - cload(b"build/sbv.so"); + rt.cload(b"build/sbv.so"); p.a = 3; p.b = 4; - put_n(pair_sum(p)); put_c(32); + rt.put_n(pair_sum(p)); rt.put_c(32); q.a = 10; q.b = 20; - put_n(pair_sum2(p, q)); put_c(32); + rt.put_n(pair_sum2(p, q)); rt.put_c(32); v.x = 1.5; v.y = 2.0; - put_n(vec_scaled(v, 2)); put_c(32); - put_n(many(1, 2, 3, 4, 5, 6, p)); put_c(32); + rt.put_n(vec_scaled(v, 2)); rt.put_c(32); + rt.put_n(many(1, 2, 3, 4, 5, 6, p)); rt.put_c(32); t.a = 7; t.b = 8; t.c = 9; - put_n(trio_sum(1, 2, 3, 4, 5, 6, t)); put_c(32); - put_n(eight(1, 2, 3, 4, 5, 6, 7, 8, p)); put_c(10); + rt.put_n(trio_sum(1, 2, 3, 4, 5, 6, t)); rt.put_c(32); + rt.put_n(eight(1, 2, 3, 4, 5, 6, 7, 8, p)); rt.put_c(10); return 0; } diff --git a/tests/programs/sys.zc b/tests/programs/sys.zc index 52924fa..aa82097 100644 --- a/tests/programs/sys.zc +++ b/tests/programs/sys.zc @@ -1,11 +1,14 @@ -include "src/lib/std.zc"; +namespace; +include rt, std; + +include std; fn main() { let s, n; - n = Host.writefile("/tmp/zc-sys-out.txt", "zy"); - s = readfile("/tmp/zc-sys-out.txt"); - put_n(n); put_c(' '); put_s(s); put_c(' '); - if (Host.now() > 1000000000) put_s("time\n"); - else put_s("notime\n"); - free(d*s); + n = std.Host.writefile("/tmp/zc-sys-out.txt", "zy"); + s = rt.readfile("/tmp/zc-sys-out.txt"); + rt.put_n(n); rt.put_c(' '); rt.put_s(s); rt.put_c(' '); + if (std.Host.now() > 1000000000) rt.put_s("time\n"); + else rt.put_s("notime\n"); + rt.free(d*s); return 0; } diff --git a/tests/programs/tagenum.zc b/tests/programs/tagenum.zc index f438c5b..013f191 100644 --- a/tests/programs/tagenum.zc +++ b/tests/programs/tagenum.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // tagged enums: a member can carry fields, a constructor builds it, and a // switch matches on the member and binds its fields enum shape { circle { int r; }, rect { int w; int h; }, dot }; @@ -15,12 +18,12 @@ fn main() { shape* a = shape.circle(3); shape* b = shape.rect(4, 5); shape* c = shape.dot(); - put_n(area(a)); put_c(32); - put_n(area(b)); put_c(32); - put_n(area(c)); put_c(32); - put_n(a.tag); put_c(32); - put_n(b.tag); put_c(32); - put_n(c.tag); put_c(10); - free(d*a); free(d*b); free(d*c); + rt.put_n(area(a)); rt.put_c(32); + rt.put_n(area(b)); rt.put_c(32); + rt.put_n(area(c)); rt.put_c(32); + rt.put_n(a.tag); rt.put_c(32); + rt.put_n(b.tag); rt.put_c(32); + rt.put_n(c.tag); rt.put_c(10); + rt.free(d*a); rt.free(d*b); rt.free(d*c); return 0; } diff --git a/tests/programs/tagenum_two.zc b/tests/programs/tagenum_two.zc index eef45dd..8fda748 100644 --- a/tests/programs/tagenum_two.zc +++ b/tests/programs/tagenum_two.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // two tagged enums may share a member name: each keeps its own fields and tag enum a { item { int x; }, other }; enum b { item { int y; }, nope }; @@ -14,7 +17,7 @@ o3 int tot(a* p, int n) { int main() { a* p = a.item(3); b* q = b.item(8); - put_n(p.item_x); put_c(32); put_n(q.item_y); put_c(32); put_n(tot(p, 4)); put_c(10); - free(d*p); free(d*q); + rt.put_n(p.item_x); rt.put_c(32); rt.put_n(q.item_y); rt.put_c(32); rt.put_n(tot(p, 4)); rt.put_c(10); + rt.free(d*p); rt.free(d*q); return 0; } diff --git a/tests/programs/tagenum_warn.zc b/tests/programs/tagenum_warn.zc index f50ab7c..1f31311 100644 --- a/tests/programs/tagenum_warn.zc +++ b/tests/programs/tagenum_warn.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // a switch on a tagged enum that leaves out a member, with no default, warns enum shape { circle { int r; }, rect { int w; int h; }, dot }; @@ -11,7 +14,7 @@ fn name(shape* s) { fn main() { shape* a = shape.rect(2, 2); - put_n(name(a)); put_c(10); - free(d*a); + rt.put_n(name(a)); rt.put_c(10); + rt.free(d*a); return 0; } diff --git a/tests/programs/temporary_borrows.expected b/tests/programs/temporary_borrows.expected new file mode 100644 index 0000000..94e4967 --- /dev/null +++ b/tests/programs/temporary_borrows.expected @@ -0,0 +1 @@ +a0 b0 99 0 100 0 1 0 102 0 3 0 1 106 0 114 0 13 diff --git a/tests/programs/temporary_borrows.zc b/tests/programs/temporary_borrows.zc new file mode 100644 index 0000000..40e00ba --- /dev/null +++ b/tests/programs/temporary_borrows.zc @@ -0,0 +1,29 @@ +namespace; +include rt; + +// original allocations are released after their aliases' last expression use, across branches and loops. +word latest; int made; +unchecked char*.u message(int code) { + char* p = rt.new(2); p[0] = code; p[1] = 0; latest = p; made++; return p; +} +char* alias(char* p) { return p; } +int read(char* p) { return p[0]; } +int output(char* p) { rt.put_s(p); return 0; } +int recursive(char* p, int n) { if (n) return recursive(p, n - 1); return read(p); } +int returning() { return read(alias(message('r'))); } +fn main() { + rt.put_s(message('a')); rt.put_n(rt.isobj(latest)); rt.put_c(32); + output(message('b')); rt.put_n(rt.isobj(latest)); rt.put_c(32); + rt.put_n(read(alias(message('c')))); rt.put_c(32); rt.put_n(rt.isobj(latest)); rt.put_c(32); + rt.put_n(alias(message('d'))[0]); rt.put_c(32); rt.put_n(rt.isobj(latest)); rt.put_c(32); + rt.put_n(rt.eq_w(message('e'), message('e'))); rt.put_c(32); rt.put_n(rt.isobj(latest)); rt.put_c(32); + rt.put_n(recursive(message('f'), 3)); rt.put_c(32); rt.put_n(rt.isobj(latest)); rt.put_c(32); + int n = 0; while (n < 3 && read(message('g')) == 'g') { n++; } + rt.put_n(n); rt.put_c(32); rt.put_n(rt.isobj(latest)); rt.put_c(32); + int skipped = 0 && read(message('h')); int yes = 1 || read(message('i')); + rt.put_n(skipped + yes); rt.put_c(32); + int choice = alias(message(0)) ? read(message('j')) : 2; + rt.put_n(choice); rt.put_c(32); rt.put_n(rt.isobj(latest)); rt.put_c(32); + rt.put_n(returning()); rt.put_c(32); rt.put_n(rt.isobj(latest)); rt.put_c(32); + rt.put_n(made); rt.put_c(10); +} diff --git a/tests/programs/typedmethods.zc b/tests/programs/typedmethods.zc index facd9f1..570ae17 100644 --- a/tests/programs/typedmethods.zc +++ b/tests/programs/typedmethods.zc @@ -1,3 +1,6 @@ +namespace; +include rt; + // struct methods declare a return type and typed parameters, like functions do struct Str { int len; @@ -8,9 +11,9 @@ struct Str { int main() { Str a; a.len = 9; - put_n(Str.size(7)); put_c(32); - put_n(Str.none()); put_c(32); - put_n(a.plus(4)); - put_c(10); + rt.put_n(Str.size(7)); rt.put_c(32); + rt.put_n(Str.none()); rt.put_c(32); + rt.put_n(a.plus(4)); + rt.put_c(10); return 0; } diff --git a/tests/programs/typeoff.zc b/tests/programs/typeoff.zc index d82bca8..aa71bea 100644 --- a/tests/programs/typeoff.zc +++ b/tests/programs/typeoff.zc @@ -1,2 +1,5 @@ +namespace; +include rt; + // the typechecker is off unless asked for: this mismatch compiles and runs -fn main() { char* s = 5; put_n(s as int + 1); put_c(10); return 0; } +fn main() { char* s = 5; rt.put_n(s as int + 1); rt.put_c(10); return 0; } diff --git a/tests/programs/typeok.zc b/tests/programs/typeok.zc index 26ab3ad..72a254a 100644 --- a/tests/programs/typeok.zc +++ b/tests/programs/typeok.zc @@ -1,21 +1,24 @@ +namespace; +include rt; + // passes the typechecker: words mix with everything, literal 0 is null, // arrays decay to pointers, generic calls bind their parameters typecheck on; struct pair<A, B> { A a; B b; }; -pair<A, B>*.u mk<A, B>(A x, B y) { pair<A, B>* p = new(pair<A, B>); p.a = x; p.b = y; return u*p; } +pair<A, B>*.u mk<A, B>(A x, B y) { pair<A, B>* p = rt.new(pair<A, B>); p.a = x; p.b = y; return u*p; } A left<A, B>(pair<A, B>* p) { return p.a; } int strlen2(char* s) { int n = 0; while (s[n]) n++; return n; } int main() { pair<int, char*>* p = mk(3, "four"); int a = left(p); char* s = p.b; - [8]char buf; + char[8] buf; char* q = buf; int* nothing = 0; let w = 5; int mixed = w + 1; q[0] = 'h'; q[1] = 0; - put_n(a + strlen2(s) + strlen2(buf) + mixed); put_c(10); - free(d*p); + rt.put_n(a + strlen2(s) + strlen2(buf) + mixed); rt.put_c(10); + rt.free(d*p); return 0; } diff --git a/tests/programs/types.zc b/tests/programs/types.zc index 86488db..5eaa964 100644 --- a/tests/programs/types.zc +++ b/tests/programs/types.zc @@ -1,3 +1,6 @@ +namespace; +include rt, run; + struct point { int x, y; }; struct rect { point a, b; int tag; }; struct list<T> { int len; T* items; }; @@ -5,14 +8,14 @@ struct node { int v; node* next; }; type num = int; int counter = 5; -[4]int squares; +int[4] squares; point origin; int area(rect* r) { return (r.b.x - r.a.x) * (r.b.y - r.a.y); } list<T>*.u mklist<T>(int cap) { - list<T>* l = new(list<T>); + list<T>* l = rt.new(list<T>); l.len = 0; - l.items = new(T, cap) as T*; + l.items = rt.new(T, cap) as T*; return u*l; } T first<T>(list<T>* l) { return l.items[0]; } @@ -21,35 +24,35 @@ int sum(node* n) { int s = 0; while (n) { s += n.v; n = n.next; } return s; } fn main() { rect r; r.a.x = 1; r.a.y = 2; r.b.x = 4; r.b.y = 6; - put_n(area(&r)); put_c(' '); - put_n(sizeof(rect)); put_c(' '); put_n(sizeof(point)); put_c(' '); put_n(sizeof([3]rect)); put_c(' '); - let p = new(point); // let infers *point + rt.put_n(area(&r)); rt.put_c(' '); + rt.put_n(sizeof(rect)); rt.put_c(' '); rt.put_n(sizeof(point)); rt.put_c(' '); rt.put_n(sizeof(rect[3])); rt.put_c(' '); + let p = rt.new(point); // let infers *point p.x = 7; p->y = 8; - put_n(p.x * p.y); put_c(' '); - free(d*p); + rt.put_n(p.x * p.y); rt.put_c(' '); + rt.free(d*p); list<int>* l = mklist<int>(4); l.items[0] = 42; l.len = 1; - put_n(first(l)); put_c(' '); - free(d*l); - node* a = new(node), b = new(node); + rt.put_n(first(l)); rt.put_c(' '); + rt.free(d*l); + node* a = rt.new(node), b = rt.new(node); a.v = 3; b.v = 4; a.next = b; - put_n(sum(a)); put_c(' '); - free(d*a); // b went with it: a.next = b made a its owner - [3]point pts; + rt.put_n(sum(a)); rt.put_c(' '); + rt.free(d*a); // b went with it: a.next = b made a its owner + point[3] pts; pts[2].y = 9; - put_n(pts[2].y + counter); put_c(' '); + rt.put_n(pts[2].y + counter); rt.put_c(' '); num k = 3; - put_n(k); put_c(10); + rt.put_n(k); rt.put_c(10); // reflection - tprint(typeof(r)); put_c(' '); tprint(typeof(&r)); put_c(' '); tprint(typeof(l)); put_c(' '); - tprint(sigof(mklist)); put_c(' '); tprint(sigof(first)); put_c(' '); tprint(sigofs("free")); put_c(10); + run.tprint(typeof(r)); rt.put_c(' '); run.tprint(typeof(&r)); rt.put_c(' '); run.tprint(typeof(l)); rt.put_c(' '); + run.tprint(run.sigof(mklist)); rt.put_c(' '); run.tprint(run.sigof(first)); rt.put_c(' '); run.tprint(run.sigofs("free")); rt.put_c(10); let t = typeof(r), i = 0; - while (i < tnfields(t)) { put_s(tfname(t, i)); put_c(':'); tprint(tftype(t, i)); put_c('@'); put_n(tfoff(t, i)); put_c(' '); i++; } - put_c(10); - point* q = new(point); - tprint(typeof_obj(q)); put_c(' '); put_n(typeof_obj(q) == typeof(point)); put_c(' '); put_n(tkind(typeof_obj(q)) == TY_STRUCT); - free(d*q); - put_c(10); + while (i < run.tnfields(t)) { rt.put_s(run.tfname(t, i)); rt.put_c(':'); run.tprint(run.tftype(t, i)); rt.put_c('@'); rt.put_n(run.tfoff(t, i)); rt.put_c(' '); i++; } + rt.put_c(10); + point* q = rt.new(point); + run.tprint(rt.typeof_obj(q)); rt.put_c(' '); rt.put_n(rt.typeof_obj(q) == typeof(point)); rt.put_c(' '); rt.put_n(run.tkind(rt.typeof_obj(q)) == run.TY_STRUCT); + rt.free(d*q); + rt.put_c(10); return 0; } diff --git a/tests/programs/uaf.zc b/tests/programs/uaf.zc index 4c14e91..18cb663 100644 --- a/tests/programs/uaf.zc +++ b/tests/programs/uaf.zc @@ -1,8 +1,11 @@ +namespace; +include rt; + memdebug on; unchecked fn main() { - let p = new(2); + let p = rt.new(2); p[0] = 42; - free(p); + rt.free(p); return p[0]; } diff --git a/tests/programs/uaf_in.zc b/tests/programs/uaf_in.zc index 89ad9f9..d9354a0 100644 --- a/tests/programs/uaf_in.zc +++ b/tests/programs/uaf_in.zc @@ -1,9 +1,12 @@ +namespace; +include rt; + memdebug on; unchecked fn main() { - let p = new(4), r; + let p = rt.new(4), r; p[2] = 5; r = p + 2; - free(p); + rt.free(p); return r[0]; } diff --git a/tests/programs/umd.zc b/tests/programs/umd.zc index fb1640e..e7ddc88 100644 --- a/tests/programs/umd.zc +++ b/tests/programs/umd.zc @@ -1,16 +1,19 @@ +namespace; +include rt; + // lists as [len, items...], items owned by the list .u mklist(n) { - let l = new(n + 1); + let l = rt.new(n + 1); l[0] = 0; return u*l; } fn push(.m l, .d item) { l[l[0] + 1] = d*item; l[0] = l[0] + 1; return 0; } fn sum(.m l) { let s = 0, i = 1; while (i <= l[0]) { s += l[i][0]; i++; } return s; } -.u box(v) { let b = new(1); b[0] = v; return u*b; } +.u box(v) { let b = rt.new(1); b[0] = v; return u*b; } fn consume(.d b) { return b[0]; } fn main() { - let base = live_objects(); + let base = rt.live_objects(); { let l = mklist(8); // u from mklist arrives m*here let i = 0; @@ -20,10 +23,10 @@ fn main() { push(l, b); i++; } - put_n(sum(l)); put_c(' '); - put_n(live_objects() - base); put_c(' '); // list + 5 boxes + rt.put_n(sum(l)); rt.put_c(' '); + rt.put_n(rt.live_objects() - base); rt.put_c(' '); // list + 5 boxes } - put_n(live_objects() - base); put_c(' '); // freed at the block's end, children too + rt.put_n(rt.live_objects() - base); rt.put_c(' '); // freed at the block's end, children too // u handed up one block by assignment, and as a block value let outer; @@ -33,18 +36,18 @@ fn main() { outer = t; } let v = { let w = box(8); u*w }; - put_n(outer[0] + v[0]); put_c(' '); + rt.put_n(outer[0] + v[0]); rt.put_c(' '); // d given away to a callee let g = box(99); - put_n(consume(d*g)); put_c(' '); + rt.put_n(consume(d*g)); rt.put_c(' '); // containers: child takes its parent's tag - let p = new(2), c = new(1); + let p = rt.new(2), c = rt.new(1); c[0] = 5; p[0] = c; - p[1] = new(3); - put_n(p[0][0]); put_c(' '); - put_n(live_objects() - base); put_c(10); + p[1] = rt.new(3); + rt.put_n(p[0][0]); rt.put_c(' '); + rt.put_n(rt.live_objects() - base); rt.put_c(10); return 0; } diff --git a/tests/programs/undefined_call.zc b/tests/programs/undefined_call.zc index e6a3b48..1f527c8 100644 --- a/tests/programs/undefined_call.zc +++ b/tests/programs/undefined_call.zc @@ -1 +1,3 @@ +namespace; + fn main() { nope(); return 0; } diff --git a/tests/programs/use_std.zc b/tests/programs/use_std.zc index c15e49b..db319d9 100644 --- a/tests/programs/use_std.zc +++ b/tests/programs/use_std.zc @@ -1,9 +1,12 @@ -include "src/lib/std.zc"; +namespace; +include rt, std; + +include std; fn main() { let out[12]; - Text.itoa(42, out); - put_s(out); put_c(' '); - put_n(Text.atoi("-7")); put_c(' '); - put_n(Text.starts("hello", "he")); put_c(10); + std.Text.itoa(42, out); + rt.put_s(out); rt.put_c(' '); + rt.put_n(std.Text.atoi("-7")); rt.put_c(' '); + rt.put_n(std.Text.starts("hello", "he")); rt.put_c(10); return 0; } diff --git a/tests/programs/wide.zc b/tests/programs/wide.zc index c20004b..e31601d 100644 --- a/tests/programs/wide.zc +++ b/tests/programs/wide.zc @@ -1,9 +1,12 @@ +namespace; +include rt; + // a word is 64 bits: shifts past 31, and equality uses bit 63 fn main() { - put_n(1 << 40); put_c(' '); - put_n((1 << 40) >> 40); put_c(' '); - put_n((1 << 40) == (1 << 40)); put_c(' '); - put_n((1 << 40) != 0); put_c(' '); - put_n(~0 >> 63); put_c(10); + rt.put_n(1 << 40); rt.put_c(' '); + rt.put_n((1 << 40) >> 40); rt.put_c(' '); + rt.put_n((1 << 40) == (1 << 40)); rt.put_c(' '); + rt.put_n((1 << 40) != 0); rt.put_c(' '); + rt.put_n(~0 >> 63); rt.put_c(10); return 0; } diff --git a/tests/programs/zc0plain.zc b/tests/programs/zc0plain.zc index 1092f76..f573b17 100644 --- a/tests/programs/zc0plain.zc +++ b/tests/programs/zc0plain.zc @@ -1,3 +1,5 @@ +namespace; + // the stage 0 compiler (src/tools/zc0.c) has no types. it reads them and throws // them away, so a typed program and the same program without the types come // out as the same image. tests/programs/zc0typed.zc is this file with the types on. diff --git a/tests/programs/zc0typed.zc b/tests/programs/zc0typed.zc index 4b7f438..81209bf 100644 --- a/tests/programs/zc0typed.zc +++ b/tests/programs/zc0typed.zc @@ -1,20 +1,22 @@ +namespace; + // the stage 0 compiler (src/tools/zc0.c) has no types. it reads them and throws // them away, so a typed program and the same program without the types come // out as the same image. tests/programs/zc0plain.zc is this file with the types off. int total; -[8]int table; -[]word names; +int[8] table; +word[?] names; const int LIMIT = 10; int add(int a, int b) { return a + b; } -.u grow([]word v, int n) { return v; } +.u grow(word[?] v, int n) { return v; } bool keep(int.d x, .m y) { return x != y; } int apply(int(int) f, int* p, vec<vec<int>>*.u q) { return f(*p); } int sq(int x) { return x * x; } fn start() { int i = 0, sum = 0; - [4]int a; + int[4] a; int* p = a; for (int j = 0; j < LIMIT; j++) sum = add(sum, sq(j)); while (i < 4) { a[i] = i; i++; } diff --git a/tests/spec/compiler_contracts.json b/tests/spec/compiler_contracts.json new file mode 100644 index 0000000..69b2781 --- /dev/null +++ b/tests/spec/compiler_contracts.json @@ -0,0 +1,542 @@ +[ + { + "group": "modules", + "name": "explicit runtime export", + "accept": true, + "source": "include rt; fn main() { rt.put_n(7); return 0; }" + }, + { + "group": "modules", + "name": "no implicit runtime", + "accept": false, + "source": "fn main() { put_n(7); return 0; }" + }, + { + "group": "modules", + "name": "runtime needs include", + "accept": false, + "source": "fn main() { rt.put_n(7); return 0; }" + }, + { + "group": "modules", + "name": "import does not unqualify runtime", + "accept": false, + "source": "include rt; fn main() { put_n(7); return 0; }" + }, + { + "group": "modules", + "name": "explicit compiler export", + "accept": true, + "source": "include run; fn main() { run.evals(\"return 0;\"); return 0; }" + }, + { + "group": "modules", + "name": "compiler needs include", + "accept": false, + "source": "fn main() { run.evals(\"return 0;\"); return 0; }" + }, + { + "group": "modules", + "name": "no bare compiler exports", + "accept": false, + "source": "include run; fn main() { evals(\"return 0;\"); return 0; }" + }, + { + "group": "modules", + "name": "core reserved for vm", + "accept": false, + "source": "include core; fn main() { core.evals(\"return 0;\"); return 0; }" + }, + { + "group": "modules", + "name": "explicit vm export", + "accept": true, + "source": "include core; fn main() { return core.syscall(9, 0, 0, 0) > 0; }" + }, + { + "group": "modules", + "name": "server needs include", + "accept": false, + "source": "fn main() { return lsp.hid('a'); }" + }, + { + "group": "modules", + "name": "unknown module", + "accept": false, + "source": "include imaginary; fn main() { return 0; }" + }, + { + "group": "modules", + "name": "unknown export", + "accept": false, + "source": "include rt; fn main() { return rt.imaginary(); }" + }, + { + "group": "modules", + "name": "no import bypass with unchecked", + "accept": false, + "source": "unchecked fn main() { put_s(\"hi\"); return 0; }" + }, + { + "group": "modules", + "name": "runtime allocator remains typed", + "accept": true, + "source": "include rt; fn main() { let p = rt.new(int, 3); p[0] = 7; rt.put_n(p[0]); return 0; }" + }, + { + "group": "modules", + "name": "explicit standard library", + "accept": true, + "source": "include std; include rt; fn main() { rt.put_n(std.Text.atoi(\"4\")); return 0; }" + }, + { + "group": "modules", + "name": "dependency imports do not leak", + "accept": false, + "source": "include std; fn main() { rt.put_n(std.Text.atoi(\"4\")); return 0; }" + }, + { + "group": "modules", + "name": "namespace alias does not shadow local", + "accept": true, + "source": "fn main() { int rt = 7; return rt; }" + }, + { + "group": "strings", + "name": "word string parameter", + "accept": true, + "source": "char* echo(char* s) { return s; } fn main() { let s = echo(\"hi\"); return s[0]; }" + }, + { + "group": "strings", + "name": "packed string parameter", + "accept": true, + "source": "bstr echo(bstr s) { return s; } fn main() { let s = echo(b\"hi\"); return s[0]; }" + }, + { + "group": "strings", + "name": "packed and word strings are distinct", + "accept": false, + "source": "typecheck on; char* echo(char* s) { return s; } fn main() { return echo(b\"hi\")[0]; }" + }, + { + "group": "strings", + "name": "word and packed strings are distinct", + "accept": false, + "source": "typecheck on; bstr echo(bstr s) { return s; } fn main() { return echo(\"hi\")[0]; }" + }, + { + "group": "strings", + "name": "string is not raw word", + "accept": false, + "source": "typecheck on; word identity(word n) { return n; } fn main() { return identity(b\"hi\"); }" + }, + { + "group": "strings", + "name": "grouped string inference", + "accept": true, + "source": "fn main() { let a = \"hi\", b = b\"hi\"; return a[0] + b[0]; }" + }, + { + "group": "strings", + "name": "inferred word string remains assignable", + "accept": true, + "source": "include rt; typecheck on; fn main() { let s = \"hi\"; s = \"ok\"; rt.put_n(s[1]); return 0; }", + "output": "107" + }, + { + "group": "strings", + "name": "inferred packed string remains assignable", + "accept": true, + "source": "include rt; typecheck on; fn main() { let s = b\"hi\"; s = b\"ok\"; rt.put_n(s[1]); return 0; }", + "output": "107" + }, + { + "group": "strings", + "name": "inferred string types reject cross-assignment", + "accept": false, + "source": "typecheck on; fn main() { let s = \"hi\"; s = b\"ok\"; return 0; }" + }, + { + "group": "ownership", + "name": "global receives local owner", + "accept": true, + "source": "include rt; int* saved; fn main() { let p = rt.new(int); p[0] = 7; saved = p; return saved[0]; }" + }, + { + "group": "ownership", + "name": "global source is consumed", + "accept": false, + "source": "include rt; int* saved; fn main() { let p = rt.new(int); saved = p; return p[0]; }" + }, + { + "group": "ownership", + "name": "borrowed source cannot transfer", + "accept": false, + "source": "int* saved; fn store(int*.m p) { saved = p; } fn main() { return 0; }" + }, + { + "group": "ownership", + "name": "consumed source cannot transfer twice", + "accept": false, + "source": "include rt; int* a, b; fn main() { let p = rt.new(int); a = p; b = p; return 0; }" + }, + { + "group": "ownership", + "name": "temporary borrowed result cleanup", + "accept": true, + "source": "include rt; char*.u make() { let p = rt.new(char, 2); p[0] = 120; p[1] = 0; return p; } fn main() { rt.put_s(make()); return 0; }" + }, + { + "group": "closure", + "name": "explicit integer result", + "accept": true, + "source": "fn main() { let double = fn:int(int a) { return a * 2; }; return double(4); }" + }, + { + "group": "closure", + "name": "incompatible explicit result", + "accept": false, + "source": "typecheck on; fn main() { let f = fn:int() { return \"hi\"; }; return f(); }" + }, + { + "group": "closure", + "name": "captured state is shared", + "accept": true, + "source": "include rt; fn main() { int n = 0; let f = fn:int() { n++; return n; }; rt.put_n(f()); rt.put_n(f()); rt.put_n(n); return 0; }", + "output": "122" + }, + { + "group": "stdlib", + "name": "word string search", + "accept": true, + "source": "include std; fn main() { return std.Text.ends(\"abcd\", \"cd\"); }" + }, + { + "group": "stdlib", + "name": "packed string search", + "accept": true, + "source": "include std; fn main() { return std.Bytes.ends(b\"abcd\", b\"cd\"); }" + }, + { + "group": "stdlib", + "name": "word string trimming", + "accept": true, + "source": "include std; include rt; fn main() { rt.put_s(std.Text.trim(\" hi \")); return 0; }", + "output": "hi" + }, + { + "group": "stdlib", + "name": "word string substring", + "accept": true, + "source": "include std; include rt; fn main() { rt.put_s(std.Text.substr(\"abcd\", 1, 2)); return 0; }", + "output": "bc" + }, + { + "group": "stdlib", + "name": "negative substring bounds", + "accept": true, + "source": "include std; fn main() { return std.Text.substr(\"abcd\", -1, 2) == 0; }" + }, + { + "group": "runtime", + "name": "word copying borrowed buffers", + "accept": true, + "source": "include rt; fn main() { int a[2] = {1, 2}, b[2]; rt.copy_w(b, a, 2); return b[1]; }" + }, + { + "group": "runtime", + "name": "explicit free consumes owner", + "accept": false, + "source": "include rt; fn main() { let p = rt.new(int); rt.free(p); return p[0]; }" + }, + { + "group": "runtime", + "name": "borrow cannot free owner", + "accept": false, + "source": "include rt; fn f(int*.m p) { rt.free(p); } fn main() { return 0; }" + }, + { + "group": "runtime", + "name": "growable array owns transferred child", + "accept": true, + "source": "include rt; fn main() { let a = rt.new(int*[?]); let p = rt.new(int); p[0] = 7; push(a, p); return a[0][0]; }" + }, + { + "group": "runtime", + "name": "transferred child cannot outlive array", + "accept": false, + "source": "include rt; int* saved; fn main() { let a = rt.new(int*[?]); let p = rt.new(int); push(a, p); saved = p; return 0; }" + }, + { + "group": "runtime", + "name": "collection retains reachable objects", + "accept": true, + "source": "include rt; fn main() { let p = rt.new(int); p[0] = 7; rt.gc(); return p[0]; }" + }, + { + "group": "runtime", + "name": "zero-sized copy is safe", + "accept": true, + "source": "include rt; fn main() { return rt.copy_w(0, 0, 0); }" + }, + { + "group": "arguments", + "name": "explicit discard owns cleanup", + "accept": true, + "source": "include rt; fn ignore(int*.d _) { return 0; } fn main() { let before = rt.live_objects(); ignore(rt.new(int)); rt.put_n(rt.live_objects() - before); return 0; }", + "output": "0" + }, + { + "group": "arguments", + "name": "discard does not disable arity checking", + "accept": false, + "source": "fn ignore(int _, int _) { return 0; } fn main() { return ignore(1); }" + }, + { + "group": "arguments", + "name": "duplicate discard names are permitted", + "accept": true, + "source": "fn ignore(int _, int _) { return 0; } fn main() { return ignore(1, 2); }" + }, + { + "group": "modules", + "name": "multiple explicit imports", + "accept": true, + "source": "include rt, run, core; fn main() { rt.put_n(core.syscall(9, 0, 0, 0) > 0); run.evals(\"return 0;\"); return 0; }" + }, + { + "group": "modules", + "name": "repeated imports are idempotent", + "accept": true, + "source": "include rt, rt, run; include rt; fn main() { rt.put_n(1); return 0; }" + }, + { + "group": "modules", + "name": "unknown import within list", + "accept": false, + "source": "include rt, imaginary, run; fn main() { return 0; }" + }, + { + "group": "modules", + "name": "missing entry within list", + "accept": false, + "source": "include rt,,run; fn main() { return 0; }" + }, + { + "group": "callbacks", + "name": "explicit borrowed callback parameter", + "accept": true, + "source": "include rt; int first(int*.m p) { return p[0]; } fn main() { int(int*.m) f = first; return f(rt.new(1)); }" + }, + { + "group": "callbacks", + "name": "borrowed callback cannot consume source", + "accept": false, + "source": "include rt; fn bad(int*.m p) { rt.free(p); } fn main() { int(int*.m) f = bad; return 0; }" + }, + { + "group": "modules", + "name": "local struct shadows module name", + "accept": true, + "source": "struct point { int x; }; fn main() { point rt; rt.x = 7; return rt.x; }" + }, + { + "group": "strings", + "name": "capture returns a char string", + "accept": true, + "source": "include rt, std; typecheck on; fn main() { char* s = std.capture.start(); rt.put_s(\"hi\"); s = std.capture.end(s); rt.put_s(s); return 0; }", + "output": "hi" + }, + { + "group": "modules", + "name": "boot export needs its namespace", + "accept": false, + "source": "fn main() { return start; }" + }, + { + "group": "strings", + "name": "inferred packed result stays indexable", + "accept": true, + "source": "include rt; fn text() { return b\"hi\"; } fn main() { let s = text(); rt.put_n(s[0]); return 0; }", + "output": "104" + }, + { + "group": "strings", + "name": "typed argument with inferred packed return", + "accept": true, + "source": "include rt; typecheck on; fn echo(bstr s) { return s; } fn main() { let s = echo(b\"hi\"); rt.put_n(s[1]); return 0; }", + "output": "105" + }, + { + "group": "strings", + "name": "inferred word string result stays indexable", + "accept": true, + "source": "include rt; fn text() { return \"hi\"; } fn main() { let s = text(); rt.put_n(s[0]); return 0; }", + "output": "104" + }, + { + "group": "enums", + "name": "tagged enum constructors and repeated discard fields", + "accept": true, + "source": "include rt; enum pair { item { int x; int y; }, other }; fn main() { let p = pair.item(1, 2); switch(p) { case item(_, _): rt.put_n(p.item_x + p.item_y); case _: rt.put_n(0); } rt.free(d*p); return 0; }", + "output": "3" + }, + { + "group": "modules", + "name": "icon exports are loaded by explicit include", + "accept": true, + "source": "include rt, run, icon; fn main() { let q = run.tokens(\"return 7;\"); let frame = icon.begin(q); icon.resume(frame, 4); int result = icon.result(frame); rt.put_n(result); icon.close(d*frame); return 0; }", + "output": "7" + }, + { + "group": "modules", + "name": "icon access requires include", + "accept": false, + "source": "include rt, run; fn main() { return icon.error(); }" + }, + { + "group": "modules", + "name": "icon implementation names remain private", + "accept": false, + "source": "include rt, run, icon; fn main() { return icon_value(0, 0); }" + }, + { + "group": "modules", + "name": "repeated icon include is idempotent", + "accept": true, + "source": "include rt, run, icon, icon; fn main() { let q = run.tokens(\"return 1;\"); rt.put_n(icon.validate(q)); return 0; }", + "output": "1" + }, + { + "group": "qualifiers", + "name": "fixed const array", + "accept": true, + "source": "include rt; fn main() { fixed const int[3] a = {2,3,4}; rt.put_n(a[1]); }", + "output": "3" + }, + { + "group": "qualifiers", + "name": "const fixed array", + "accept": true, + "source": "include rt; fn main() { const fixed int[3] a = {2,3,4}; rt.put_n(a[1]); }", + "output": "3" + }, + { + "group": "qualifiers", + "name": "fixed const global array", + "accept": true, + "source": "include rt; fixed const int[2] data = {8,9}; fn main() { rt.put_n(data[1]); }", + "output": "9" + }, + { + "group": "qualifiers", + "name": "fixed const array rejects element write", + "accept": false, + "source": "fn main() { fixed const int[2] a = {1,2}; a[0] = 3; }" + }, + { + "group": "qualifiers", + "name": "const fixed rejects resize", + "accept": false, + "source": "include rt; fn main() { const fixed int[?] a = rt.new(fixed int[?],2); a.resize(3); }" + }, + { + "group": "qualifiers", + "name": "const fixed cannot be rebound", + "accept": false, + "source": "include rt; fn main() { const fixed int[?] a = rt.new(fixed int[?],2); a = rt.new(fixed int[?],3); }" + }, + { + "group": "qualifiers", + "name": "fixed mutable remains writable", + "accept": true, + "source": "include rt; fn main() { fixed int[2] a = {1,2}; a[0] = 3; rt.put_n(a[0]); }", + "output": "3" + }, + { + "group": "qualifiers", + "name": "const fixed borrow cannot mutate storage", + "accept": false, + "source": "fn mutate(int* p) { p[0] = 9; } fn main() { const fixed int[2] a = {1,2}; mutate(a); }" + }, + { + "group": "qualifiers", + "name": "readonly parameter accepts const fixed", + "accept": true, + "source": "include rt; int first(const fixed int[2] a) { return a[0]; } fn main() { const fixed int[2] a = {7,8}; rt.put_n(first(a)); }", + "output": "7" + }, + { + "group": "inference", + "name": "typed callee constrains untyped caller", + "accept": true, + "source": "include rt; int seed(int n) { return n + 1; } fn caller(n) { let result = seed(n); return result; } fn main() { rt.put_n(caller(4)); }", + "output": "5" + }, + { + "group": "inference", + "name": "recursive anchored return", + "accept": true, + "source": "include rt; int seed(int n) { return n; } fn recursive(n) { if (n <= 0) return seed(n); return recursive(n - 1); } fn main() { rt.put_n(recursive(3)); }", + "output": "0" + }, + { + "group": "qualifiers", + "name": "readonly array alias cannot remove qualifier", + "accept": false, + "source": "fn main() { fixed const int[2] a = {1,2}; int* alias = a; alias[0] = 8; }" + }, + { + "group": "qualifiers", + "name": "readonly array alias preserves qualifier", + "accept": true, + "source": "include rt; fn main() { fixed const int[2] a = {1,2}; let alias = a; rt.put_n(alias[0]); }", + "output": "1" + }, + { + "group": "qualifiers", + "name": "readonly inferred alias cannot write", + "accept": false, + "source": "fn main() { fixed const int[2] a = {1,2}; let alias = a; alias[0] = 8; }" + }, + { + "group": "qualifiers", + "name": "fixed const compound write rejected without typecheck", + "accept": false, + "source": "typecheck off; fn main() { fixed const int[2] a = {1,2}; a[0] += 2; }" + }, + { + "group": "qualifiers", + "name": "fixed const increment rejected", + "accept": false, + "source": "fn main() { fixed const int[2] a = {1,2}; a[0]++; }" + }, + { + "group": "qualifiers", + "name": "readonly packed fixed array", + "accept": true, + "source": "include rt; fn main() { fixed const u8[] a = {2,3}; rt.put_n(a[1]); }", + "output": "3" + }, + { + "group": "runtime", + "name": "owned readfile grows and returns exact character data", + "accept": true, + "source": "include rt; fn main() { char* text = rt.readfile(\"tests/data/readfile_growth.txt\"); rt.put_n(rt.len_w(text)); rt.put_c(32); rt.put_n(text[1029]); rt.put_c(32); rt.put_n(text[1030]); }", + "output": "1031 57 10" + }, + { + "group": "runtime", + "name": "readfile missing path returns null", + "accept": true, + "source": "include rt; fn main() { char* text = rt.readfile(\"tests/data/missing-readfile-contract.txt\"); rt.put_n(text == 0); }", + "output": "1" + }, + { + "group": "runtime", + "name": "owned file contents cannot be used after release", + "accept": false, + "source": "include rt; fn main() { char* text = rt.readfile(\"tests/data/readfile_growth.txt\"); rt.free(text); return text[0]; }" + } +] diff --git a/tests/test.sh b/tests/test.sh index 27d60f7..e7f8f7c 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -20,7 +20,7 @@ cd "$(dirname "$0")/.."; T=$(mktemp -d); trap 'rm -rf $T' EXIT ok=0; bad=0 chk(){ if [ "$1" = "$2" ]; then echo "ok $3"; ok=$((ok+1)); else echo "FAIL $3: got [$1]"; bad=$((bad+1)); fi; } -src(){ cat src/runtime/rt.zc src/compiler/compiler.zc; } +src(){ cat src/runtime/rt.zc src/core/core.zc src/compiler/compiler.zc; } # -j N runs N sections at a time: j is how many cores to use. each section # writes its own log, so the output still reads in the order it always did. JOBS=1; QUICK=0; SELECT="" @@ -45,7 +45,7 @@ ROOT=$T # built here rather than inside a section: interp runs the program list on # it, and the run-only and bare-metal sections read it as the reference. -gcc -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zy || exit 1 +gcc -pthread -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zy || exit 1 gcc -O2 -w -fwrapv src/tools/genmap.c -Isrc/core -o $T/genmap || exit 1 # the c helper for tests/programs/structval.zc: structs by value, loaded with cload mkdir -p build && gcc -shared -fPIC -w tests/data/sbv.c -o build/sbv.so || exit 1 @@ -57,9 +57,9 @@ gcc -O1 -w -fwrapv src/tools/zc0.c -Isrc/core -o $T/zc0 && gcc -O1 -w -fwrapv sr mkdir $T/s0 $T/s1 cp src/core/core.c src/core/io_linux.c src/core/memmap.h src/core/fops.h src/core/bops.h src/core/stack.h $T/s0/; cp src/core/core.c src/core/io_linux.c src/core/memmap.h src/core/fops.h src/core/bops.h src/core/stack.h $T/s1/ src | $T/zc0 > $T/s0/genome.h || exit 1 -gcc -O2 -w -fwrapv -DJTYPE=int $T/s0/core.c $T/s0/io_linux.c -o $T/zy0 +gcc -pthread -O2 -w -fwrapv -DJTYPE=int $T/s0/core.c $T/s0/io_linux.c -o $T/zy0 selfc $T/zy0 $T/g1.bin; $T/mkg < $T/g1.bin > $T/s1/genome.h -gcc -O2 -w -fwrapv -DJTYPE=int $T/s1/core.c $T/s1/io_linux.c -o $T/zy1 +gcc -pthread -O2 -w -fwrapv -DJTYPE=int $T/s1/core.c $T/s1/io_linux.c -o $T/zy1 selfc $T/zy1 $T/g2.bin cmp -s $T/g1.bin $T/g2.bin && chk 1 1 "fixpoint: the compiler compiles itself to the same $(($(wc -c < $T/g1.bin) / 8)) words" || chk 0 1 "fixpoint" cmp -s $T/s1/genome.h src/core/genome.h && chk 1 1 "genome.h is up to date" || chk 0 1 "genome.h is stale (cp the bootstrap output over it)" @@ -124,7 +124,7 @@ run(){ # $1 = vm command, $2 = label chk "$($vm < tests/programs/csyn.zc)" "13 0 5 6 0" "$l do/while, enum, and a C cast" chk "$($vm < tests/programs/use_std.zc)" "42 -7 1" "$l include and the string helpers" chk "$($vm < tests/programs/stdbuf.zc)" "4 1 266 8" "$l stdlib arena and packed byte buffer" - chk "$($vm < tests/programs/stdmore.zc)" "$(printf '5 1 0 0 7 copy me 12 -1 0\n9 mixed 123 9 MIXED 123\n11 [padded line]\n42 3 9 10 12 81 9 10 -1 0')" "$l stdlib text and num helpers" + chk "$($vm < tests/programs/stdmore.zc)" "$(printf '5 1 0 0 7 copy me 12 -1 0 1 0 1\n9 mixed 123 9 MIXED 123\n11 [padded line]\n42 3 9 10 12 81 9 10 -1 0\n6 1 2 -1 1 0')" "$l stdlib text, byte-string and num helpers" chk "$(ZC_FILE=tests/data/hi.txt $vm < examples/cli.zc)" "6 h" "$l reading a file named by the environment" chk "$(ZC_ARG_TEST=ok $vm < tests/programs/args.zc)" "1 <eval> <eval> 1 1" "$l argv and envp from the host, argv[0] is <eval> on stdin" chk "$(ZC_ARG_TEST=ok $vm < tests/programs/capture.zc)" "0 @@ -155,6 +155,8 @@ ABC" "$l o3 lifts byte loads and stores" chk "$($vm < tests/programs/strings.zc)" "hi! 3 3 104 i! 1 1 1 -42 42 -42 11 1 -9223372036854775808 0 1 3 10 1 1 1 W ab" "$l byte strings" chk "$($vm < tests/programs/share.zc)" "1 2 1 4 1 1 1 1 42 1 1 1 6 0" "$l shared cons cells" chk "$($vm < tests/programs/share_dbg.zc)" "7 1" "$l memdebug accepts a shared cell" + chk "$($vm < tests/programs/global_transfer.zc)" "5 9" "$l an m or u owner stored in a global is transferred" + chk "$($vm < tests/programs/closure_result.zc)" "8 1.500000" "$l a closure with a declared result type" chk "$($vm < examples/strings.zc)" "hello, zc 9 42" "$l byte string example" chk "$(ZC_BSTR=zybyte $vm < tests/programs/bstr_io.zc)" "byte-io 7 7 1 6 122 101" "$l byte-string file and getenv" @@ -174,7 +176,11 @@ ABC" "$l o3 lifts byte loads and stores" chk "$($vm < tests/programs/arrays.zc | tr '\n' '/')" "100 128 9801 9801 99 3 4 12 19 8 19 28/0/" "$l growable and fixed arrays" chk "$($vm < tests/programs/o3.zc)" "3142 42 43 9 57 98781 520625 10192 -18782 1152" "$l o3 regions: loops, calls, recursion, switch, arrays, more vars than registers" chk "$($vm < tests/programs/o3scopes.zc)" "116" "$l o3 registers shared by scopes, cached fixed arrays" - chk "$($vm < tests/programs/o3fat.zc)" "node node 3 box node [0]int" "$l o3 data gets fattened on the way out" + chk "$($vm < tests/programs/borrow_rules.zc)" "$(cat tests/programs/borrow_rules.expected)" "$l inferred transfers, projections and shared closure cells" + chk "$($vm < tests/programs/replace_owner.zc)" "$(cat tests/programs/replace_owner.expected)" "$l replacing a released owner moves ownership and cleans up once" + chk "$($vm < tests/programs/temporary_borrows.zc)" "$(cat tests/programs/temporary_borrows.expected)" "$l temporary borrows preserve aliases and release allocations" + chk "$($vm < tests/programs/array_values.zc)" "$(cat tests/programs/array_values.expected)" "$l array initializers, designators, zero filling, nested and compound literals" + chk "$($vm < tests/programs/o3fat.zc)" "node node 3 box node int[0]" "$l o3 data gets fattened on the way out" chk "$($vm < tests/programs/o3call.zc)" "0 0 4 23 55 node" "$l o3 calls o3 directly, and new stamps the header" chk "$($vm < tests/programs/o3direct.zc | md5sum)" "$(md5sum < tests/programs/o3direct.expected)" "$l o3 calls: arguments, results, recursion, floats, pointers, the ways out of a fast call" chk "$($vm < tests/programs/o3licm.zc)" "3184200 7950 315 50 260 -56" "$l o3 hoists loop invariants and shares values between blocks" @@ -193,8 +199,14 @@ ABC" "$l o3 lifts byte loads and stores" } s_interp() { T=$ROOT/interp - gcc -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zy + gcc -pthread -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zy run $T/zy "interp" + python3 tests/drivers/array_methods_test.py $T/zy > $T/arrays.out 2>&1; r=$? + grep -v '^ok' $T/arrays.out + chk $r 0 "native array receiver, algorithm and ownership contracts" + python3 tests/drivers/actor_test.py $T/zy > $T/actor.out 2>&1; r=$? + grep -v '^ok' $T/actor.out + chk $r 0 "native isolated actor acceptance, rejection and regression fixtures" chk "$(ZC_ARG_TEST=ok $T/zy tests/programs/args.zc alpha beta < tests/programs/args.zc)" "4 <eval> beta 1 1" "interp the command line reaches argv, after <eval>" chk "$($T/zy < tests/programs/cabi.zc | md5sum)" "$(md5sum < tests/programs/cabi.expected)" "interp native calls and callbacks (extern, cfunc)" chk "$($T/zy < tests/programs/cabi_o3.zc | md5sum)" "$(md5sum < tests/programs/cabi_o3.expected)" "interp native calls from o3 code, more arguments than registers" @@ -204,7 +216,7 @@ s_interp() { chk "$(ZYTRACE=1 $T/zy < tests/programs/o3.zc 2>&1 | grep -c 'micro-ops$')" "8" "every o3 region lifts" chk "$($T/zy < tests/programs/infer_return.zc)" "3" "an owned return with no .u is inferred from the body" chk "$($T/zy < tests/programs/discard_m.zc)" "7" "m* discards an unnamed u result" - chk "$($T/zy < tests/programs/closure.zc | tr '\n' ' ')" "31 35 42 2 7 0 109 30 7 16 33 20 2 " "closures: calls through values, captures, nesting, returns" + chk "$($T/zy < tests/programs/closure.zc | tr '\n' ' ')" "31 35 42 2 7 7 109 30 7 16 33 20 101 " "closures: calls through values, captures, nesting, returns" chk "$($T/zy < tests/programs/decl_undefined.zc 2>&1 | head -1)" "zc: line 1: helper is used but never defined" "a declared function nothing defines fails a build" if [ "$(uname -s)" = Linux ]; then chk "$($T/zy < tests/programs/rawsys.zc)" "-38" "Host.raw: an unknown syscall number gives -ENOSYS"; else chk "$($T/zy < tests/programs/rawsys.zc)" "-1" "Host.raw: unavailable on this host"; fi if [ "$(uname -m)" = x86_64 ]; then chk "$($T/zy < tests/programs/rawpid.zc)" "1" "Host.raw: getpid (39 on x86-64) answers"; fi @@ -212,12 +224,28 @@ s_interp() { s_micro() { T=$ROOT/micro - gcc -O2 -w -fwrapv src/micro/micro.c src/micro/port_linux.c -Isrc/core -Isrc/micro -o $T/zym || exit 1 + gcc -pthread -DZY_THREADS -O2 -w -fwrapv src/micro/micro.c src/micro/port_linux.c -Isrc/core -Isrc/micro -o $T/zym || exit 1 run $T/zym "micro" + python3 tests/drivers/array_methods_test.py $T/zym > $T/arrays.out 2>&1; r=$? + grep -v '^ok' $T/arrays.out + chk $r 0 "micro array receiver, algorithm and ownership contracts" + python3 tests/drivers/actor_test.py $T/zym > $T/actor.out 2>&1; r=$? + grep -v '^ok' $T/actor.out + chk $r 0 "micro isolated actor acceptance, rejection and regression fixtures" + if command -v node >/dev/null; then + python3 tests/drivers/fuzz_compiler.py $ROOT/zy $T/zym --js > $T/fuzz.out 2>&1; r=$? + else + python3 tests/drivers/fuzz_compiler.py $ROOT/zy $T/zym > $T/fuzz.out 2>&1; r=$? + fi + grep -v '^ok' $T/fuzz.out + chk $r 0 "compiler/runtime/stdlib differential fuzz across available hosts" + python3 tests/drivers/fuzz_malformed.py $ROOT/zy $T/zym > $T/fuzzmalformed.out 2>&1; r=$? + grep -v '^ok' $T/fuzzmalformed.out + chk $r 0 "malformed compiler input fuzz and host agreement" selfc $T/zym $T/gm.bin cmp -s $T/g1.bin $T/gm.bin && chk 1 1 "microcore self-compile matches" || chk 0 1 "microcore self-compile matches" gcc -ffreestanding -w -c src/micro/micro.c -Isrc/core -Isrc/micro -o $T/micro.o && gcc -ffreestanding -w -c src/micro/port_stub.c -Isrc/core -Isrc/micro -o $T/stub.o && chk 1 1 "microcore and the stub port compile freestanding" || chk 0 1 "microcore and the stub port compile freestanding" - gcc -O2 -w -fwrapv -DJTYPE=int -DZY_SOFTFLOAT -DZYINTERP src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zysoft || exit 1 + gcc -pthread -O2 -w -fwrapv -DJTYPE=int -DZY_SOFTFLOAT -DZYINTERP src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zysoft || exit 1 chk "$($T/zysoft < tests/programs/float.zc)" "4.000000 4.500000 3.000000 2.500000 2.500000 0.001500 0.100000 -0.125000 inf -inf nan 1 1 1 0 0 3 -3 3.000000 -2.000000 2" "soft-float fallback matches the hardware line" chk "$($T/zysoft < tests/programs/ftruth.zc)" "00111000110107" "soft-float negative zero is false" chk "$($T/zysoft < tests/programs/fo3.zc)" "5997 5997 4.000000 nan 0 3.250000 1 0 0" "soft-float o3 float ops match" @@ -231,7 +259,7 @@ s_small() { $T/genmap --preset default --check >/dev/null && chk 1 1 "default map matches the checked-in sources" || chk 0 1 "default map matches the checked-in sources" $T/genmap --preset small -o $T/small || exit 1 $T/genmap --mode compiler --name small --heap-lo 8192 --heap 20480 --types 8192 \ - --genome 245760 --frame 2048 --return 512 --mark 512 --data 3072 \ + --genome 393216 --frame 2048 --return 512 --mark 512 --data 3072 \ --snap-cap 1000 --quote-cap 300 -o $T/small-flags || exit 1 cmp -s $T/small/memmap.h $T/small-flags/memmap.h && cmp -s $T/small/rt.zc $T/small-flags/rt.zc \ && cmp -s $T/small/compiler.zc $T/small-flags/compiler.zc && cmp -s $T/small/map.txt $T/small-flags/map.txt \ @@ -240,8 +268,8 @@ s_small() { gcc -O1 -w -fwrapv $T/small/zc0.c -o $T/zc0s || exit 1 cat $T/small/rt.zc $T/small/compiler.zc | $T/zc0s > $T/small/genome.h || exit 1 cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h $T/small/ - gcc -O2 -w -fwrapv $T/small/micro.c $T/small/port_linux.c -o $T/zyms || exit 1 - gcc -O2 -w -fwrapv -DJTYPE=int $T/small/core.c $T/small/io_linux.c -o $T/zys || exit 1 + gcc -pthread -DZY_THREADS -O2 -w -fwrapv $T/small/micro.c $T/small/port_linux.c -o $T/zyms || exit 1 + gcc -pthread -O2 -w -fwrapv -DJTYPE=int $T/small/core.c $T/small/io_linux.c -o $T/zys || exit 1 gcc -ffreestanding -w -c $T/small/micro.c -o $T/micro_s.o && gcc -ffreestanding -w -c $T/small/port_stub.c -o $T/stub_s.o && chk 1 1 "small microcore compiles freestanding" || chk 0 1 "small microcore compiles freestanding" sameout() { # $1 = other vm, $2 = label, $3 = source file local a b @@ -274,6 +302,9 @@ s_small() { s_runonly() { T=$ROOT/runonly + python3 tests/drivers/runtime_image_test.py --host $T/zy --mkgenome $ROOT/mkg > $T/images.out 2>&1; r=$? + grep -v '^ok' $T/images.out + chk $r 0 "self-hosted run-only images install enhancements and preserve generic array methods" # its own run map: the run-only image is built against this one, and the # small-machine section lays out its own rather than sharing this $T/genmap --preset run -o $T/run || exit 1 @@ -285,7 +316,7 @@ s_runonly() { if [ "$1" = gc ]; then input=$T/collector-progress.zc; sed 's/f >= 990/f > 0/' "$2" > "$input"; fi GENMAP=$T/genmap bash src/tools/automap.sh run $d src/runtime/run.zc "$input" >/dev/null || { chk 0 1 "run $1 builds"; return; } cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h $d/ - gcc -O2 -w -fwrapv $d/micro.c $d/port_linux.c -o $T/zymr || { chk 0 1 "run $1 links"; return; } + gcc -pthread -DZY_THREADS -O2 -w -fwrapv $d/micro.c $d/port_linux.c -o $T/zymr || { chk 0 1 "run $1 links"; return; } a=$($T/zy < "$input" 2>&1 | tr '\n' '/') b=$($T/zymr </dev/null 2>&1 | tr '\n' '/') chk "$b" "$a" "run $1" @@ -303,7 +334,7 @@ s_runonly() { runonly strings-ex examples/strings.zc runonly share tests/programs/share.zc d=$T/run-echo; GENMAP=$T/genmap bash src/tools/automap.sh run $d src/runtime/run.zc examples/echo.zc >/dev/null && cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h $d/ \ - && gcc -O2 -w -fwrapv $d/micro.c $d/port_linux.c -o $T/zymr \ + && gcc -pthread -DZY_THREADS -O2 -w -fwrapv $d/micro.c $d/port_linux.c -o $T/zymr \ && chk "$(printf 'abc\n' | $T/zymr | tr '\n' '/')" "$( (cat examples/echo.zc; printf '`abc\n') | $T/zy | tr '\n' '/')" "run echo" \ || chk 0 1 "run echo" # zcc: a program becomes a static binary that runs without stdin and with a held-open pipe @@ -395,13 +426,13 @@ s_bare() { s_jit() { T=$ROOT/jit if command -v aarch64-linux-gnu-gcc >/dev/null && command -v qemu-aarch64 >/dev/null; then - aarch64-linux-gnu-gcc -O2 -w -fwrapv -DJTYPE=int -static src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zyj || exit 1 + aarch64-linux-gnu-gcc -pthread -O2 -w -fwrapv -DJTYPE=int -static src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zyj || exit 1 printf '#!/bin/sh\nexec qemu-aarch64 %s "$@"\n' $T/zyj > $T/jit; chmod +x $T/jit run $T/jit "jit" chk "$(ZYTRACE=1 $T/jit < tests/programs/o3.zc 2>&1 | grep -c 'bytes of arm64')" "8" "jit compiles every o3 region to arm64" selfc $T/jit $T/gj.bin; cmp -s $T/g1.bin $T/gj.bin && chk 1 1 "jit self-compile matches" || chk 0 1 "jit self-compile matches" # native calls need a dynamic binary (a static one has no dlsym), run against the aarch64 libc - if [ -d /usr/aarch64-linux-gnu/lib ] && aarch64-linux-gnu-gcc -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zyjd -ldl 2>/dev/null; then + if [ -d /usr/aarch64-linux-gnu/lib ] && aarch64-linux-gnu-gcc -pthread -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zyjd -ldl 2>/dev/null; then chk "$(qemu-aarch64 -L /usr/aarch64-linux-gnu $T/zyjd < tests/programs/cabi.zc | md5sum)" "$(md5sum < tests/programs/cabi.expected)" "jit native calls and callbacks (extern, cfunc)" chk "$(qemu-aarch64 -L /usr/aarch64-linux-gnu $T/zyjd < tests/programs/cabi_o3.zc | md5sum)" "$(md5sum < tests/programs/cabi_o3.expected)" "jit native calls from o3 code, stack arguments" chk "$(qemu-aarch64 -L /usr/aarch64-linux-gnu $T/zyjd < tests/programs/cabi_sized.zc | md5sum)" "$(md5sum < tests/programs/cabi_sized.expected)" "jit native calls with f32 and the sized integers" @@ -417,7 +448,7 @@ s_llvm() { T=$ROOT/llvm # llvm-config can exist without the headers and static libs (a runtime-only install), so the gate is a real build llvm_ok=0 - if command -v llvm-config >/dev/null && gcc -O2 -w -fwrapv -DJTYPE=int -DZYLLVM $(llvm-config --cflags) src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zyl $(llvm-config --ldflags --libs) 2>/dev/null; then llvm_ok=1; fi + if command -v llvm-config >/dev/null && gcc -pthread -O2 -w -fwrapv -DJTYPE=int -DZYLLVM $(llvm-config --cflags) src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zyl $(llvm-config --ldflags --libs) 2>/dev/null; then llvm_ok=1; fi if [ $llvm_ok = 1 ]; then run $T/zyl "llvm" chk "$(ZYTRACE=1 $T/zyl < tests/programs/o3.zc 2>&1 | grep -c 'compiled by llvm')" "8" "llvm compiles every o3 region" @@ -460,12 +491,12 @@ s_lsp() { chk "$($lsp < examples/hello.zc)" "no errors" "lsp: a clean program" chk "$($lsp < tests/errors/back_to_m.zc | head -1 | cut -d' ' -f2-)" "error: $(head -1 tests/errors/back_to_m.zc | sed 's#^// expect: ##')" "lsp: the compiler's own message" h=$($lsp < examples/opt_test.zc) - for w in "is never read" "is never reassigned; make it a const" "is a shift; write << 3" "the number 1234 has no name" "new inside a loop" "stack buffer"; do + for w in "is never read" "is never reassigned; make it a const" "is a shift; write << 3" "unannotated numeric literal 1234" "new inside a loop" "stack buffer"; do case "$h" in *"$w"*) chk 1 1 "lsp hint: $w";; *) chk 0 1 "lsp hint: $w";; esac done chk "$($lsp < examples/struct_closure_opt.zc | grep -c error)" "0" "lsp: methods and closures check clean" # a file of a larger build checks on its own: its bodyless declarations are not errors in check mode - for f in src/runtime/rt.zc src/runtime/run.zc src/lsp/server.zc; do chk "$($lsp < $f | grep -c ': error:')" "0" "lsp: $f checks alone"; done + for f in src/runtime/rt.zc src/runtime/run.zc src/compiler/compiler.zc src/lsp/server.zc; do chk "$($lsp < $f | grep -c ': error:')" "0" "lsp: $f checks alone"; done chk "$($lsp < tests/programs/decl_undefined.zc)" "no errors" "lsp: a bodyless declaration is not an error in check mode" chk "$($lsp < tests/programs/undefined_call.zc | grep -c ': error:')" "1" "lsp: a call to a name nothing defines is still an error" chk "$($lsp --annotate < tests/programs/annotate.zc | md5sum)" "$(md5sum < tests/programs/annotate.expected)" "lsp --annotate writes the types the compiler can see into a program" @@ -475,9 +506,44 @@ s_lsp() { printf 'Content-Length: 40\r\n\r\n{"jsonrpc": [2.0", "method": "x"}' | timeout 10 $lsp > /dev/null 2>&1; chk $? 0 "lsp: a stray brace in an array ends the message, and the server exits" if [ $QUICK = 1 ]; then echo "skip lsp protocol and corpus tests (quick)" elif command -v node >/dev/null; then + node tests/drivers/fuzz_lsp.js $lsp > $T/fuzzlsp.out 2>&1; r=$? + grep -v '^ok' $T/fuzzlsp.out + chk $r 0 "lsp fuzz: malformed recovery, long-session churn and cache invalidation" node tests/drivers/storage_analysis_test.js $lsp > $T/storage.out 2>&1; r=$? grep -v '^ok' $T/storage.out chk $r 0 "lsp: definite storage warnings and reference lifetime hovers" + node tests/drivers/hints_test.js $lsp > $T/hints.out 2>&1; r=$? + grep -v '^ok' $T/hints.out + chk $r 0 "lsp: hints are information diagnostics and obey hints on, small and off" + node tests/drivers/stream_test.js $lsp > $T/stream.out 2>&1; r=$? + grep -v '^ok' $T/stream.out + chk $r 0 "lsp: large checks stream and a stale check is dropped" + node tests/drivers/format_test.js $lsp > $T/format.out 2>&1; r=$? + grep -v '^ok' $T/format.out + chk $r 0 "lsp: the formatter keeps tokens, is stable and follows its options" + node tests/drivers/include_test.js $lsp > $T/include.out 2>&1; r=$? + grep -v '^ok' $T/include.out + chk $r 0 "lsp: relative includes, string diagnostics and explicit std" + for group in quickfix modulehover namespacecompletion navigation inference qualifiers; do + CONTRACT_GROUP=$group node tests/drivers/toolchain_lsp_contract_test.js $lsp > $T/contracts-$group.out 2>&1; r=$? + grep -v '^ok' $T/contracts-$group.out + chk $r 0 "lsp: $group contracts" + done + node tests/drivers/codeactions_client_test.js > $T/codeactions-client.out 2>&1; r=$? + grep -v '^ok' $T/codeactions-client.out + chk $r 0 "lsp: versioned import quick-fix edits" + node tests/drivers/completion_test.js $lsp > $T/completion.out 2>&1; r=$? + grep -v '^ok' $T/completion.out + chk $r 0 "lsp: fuzzy completions, signatures, scope and expired lifetimes" + node tests/drivers/analysis_cache_test.js $lsp > $T/analysiscache.out 2>&1; r=$? + grep -v '^ok' $T/analysiscache.out + chk $r 0 "lsp: analysis cache reuse and invalidation" + node tests/drivers/hover_surface_test.js $lsp > $T/hoversurface.out 2>&1; r=$? + grep -v '^ok' $T/hoversurface.out + chk $r 0 "lsp: embedded declarations, constant values, asm and std hovers" + node tests/drivers/nav_test.js $lsp > $T/nav.out 2>&1; r=$? + grep -v '^ok' $T/nav.out + chk $r 0 "lsp: definition and references across includes" node tests/drivers/lsp_test.js $lsp > $T/lsp.out 2>&1; r=$? grep -v '^ok' $T/lsp.out chk $r 0 "lsp: protocol and corpus tests ($(grep -c '^ok' $T/lsp.out) checks)" @@ -496,6 +562,12 @@ s_lsp() { chk $r 0 "lsp: the extension's cache, end to end ($(grep -c '^ok' $T/ecache.out) checks)" # the same server as WebAssembly, through the extension's host (needs clang) if command -v clang >/dev/null && clang --print-targets 2>/dev/null | grep -q wasm32 && LSP_BUILD_DIR=$T/lsp LSP_REUSE_BUILD=1 bash src/tools/mklspwasm.sh $T/lsp/lsp.wasm >/dev/null 2>&1; then + node tests/drivers/hover_surface_test.js --wasm $T/lsp/lsp.wasm > $T/hoversurfacewasm.out 2>&1; r=$? + grep -v '^ok' $T/hoversurfacewasm.out + chk $r 0 "lsp: embedded and asm hovers through the wasm host" + node tests/drivers/completion_test.js --wasm $T/lsp/lsp.wasm > $T/completionwasm.out 2>&1; r=$? + grep -v '^ok' $T/completionwasm.out + chk $r 0 "lsp: completions through the wasm host" node tests/drivers/lsp_wasm.js $T/lsp/lsp.wasm > $T/lspw.out 2>&1; r=$? grep -v '^ok' $T/lspw.out chk $r 0 "lsp: wasm server through the extension's host ($(grep -c '^ok' $T/lspw.out) checks)" @@ -503,9 +575,20 @@ s_lsp() { # the same server run by the javascript vm, the extension's fallback when wasm is not there. # the vm starts at the image's heap and grows on the heap's request, so a long document is the test node src/js/extract_lsp.js >/dev/null && mkdir -p $T/vmserver && cp src/js/tinyvm.js src/js/layout.js src/js/lsp.json $T/vmserver/ + node tests/drivers/hover_surface_test.js --vm $T/vmserver > $T/hoversurfacevm.out 2>&1; r=$? + grep -v '^ok' $T/hoversurfacevm.out + chk $r 0 "lsp: embedded and asm hovers through the javascript host" + node tests/drivers/completion_test.js --vm $T/vmserver > $T/completionvm.out 2>&1; r=$? + grep -v '^ok' $T/completionvm.out + chk $r 0 "lsp: completions through the javascript host" node tests/drivers/lsp_vm.js $T/vmserver > $T/lspvm.out 2>&1; r=$? grep -v '^ok' $T/lspvm.out chk $r 0 "lsp: the server in the javascript vm, through the extension's host ($(grep -c '^ok' $T/lspvm.out) checks)" + if [ -f $T/lsp/lsp.wasm ]; then + node tests/drivers/stream_host_test.js $T/lsp/lsp.wasm $T/vmserver > $T/streamhost.out 2>&1; r=$? + grep -v '^ok' $T/streamhost.out + chk $r 0 "lsp: the wasm and vm servers stream and drop stale checks" + fi else echo "skip lsp protocol tests (no node)"; fi else chk 0 1 "lsp server builds"; fi } @@ -519,10 +602,14 @@ s_js() { chk "$(node src/js/tinyvm.js examples/fact.zc | tail -1)" "3628800" "js vm: factorial" chk "$(node src/js/tinyvm.js examples/primes.zc | tail -1)" "widest gap: 72, after 31397" "js vm: primes" chk "$(node src/js/tinyvm.js tests/programs/growone.zc)" "12 1" "js vm: a block past the initial heap grows the machine" + chk "$(node src/js/tinyvm.js tests/programs/replace_owner.zc)" "$(cat tests/programs/replace_owner.expected)" "js vm: replacement ownership and cleanup" if command -v clang >/dev/null && command -v wasm-ld >/dev/null && clang --print-targets 2>/dev/null | grep -q wasm32; then bash src/web/build.sh $T/zc.wasm >/dev/null && node tests/drivers/wasm_test.js $T/zc.wasm > $T/wasm.out 2>&1; r=$? grep -v '^ok' $T/wasm.out chk $r 0 "wasm: the microcore in webassembly ($(grep -c '^ok' $T/wasm.out) checks)" + node tests/drivers/fuzz_wasm.js $T/zc.wasm > $T/fuzzw.out 2>&1; r=$? + grep -v '^ok' $T/fuzzw.out + chk $r 0 "wasm: compiler/runtime/stdlib differential fuzz" else echo "skip wasm (no clang with the wasm32 target, or no wasm-ld)"; fi else echo "skip js and wasm (no node)"; fi } @@ -532,6 +619,12 @@ s_make() { # the makefile builds the programs and the run-only image tool if command -v make >/dev/null; then make -s BIN=$T/mbin B=$T/mbuild all >/dev/null 2>&1 && chk "$($T/mbin/zy < examples/hello.zc)" "hello, world!" "make: bin/zy runs a program" || chk 0 1 "make: all builds" + python3 tests/drivers/cli_help_test.py --group binaries --bin-dir $T/mbin > $T/help.out 2>&1; r=$? + grep -v '^ok' $T/help.out + chk $r 0 "command-line help, invalid options and existing lsp source modes" + python3 tests/drivers/cli_help_test.py --group scripts > $T/scripthelp.out 2>&1; r=$? + grep -v '^ok' $T/scripthelp.out + chk $r 0 "build-script help exits without reading input or starting a build" make -s BIN=$T/mbin B=$T/mbuild run PROG=examples/hello.zc >/dev/null 2>&1 && chk "$($T/mbuild/run/zymr </dev/null)" "hello, world!" "make: run PROG= builds a run-only image" || chk 0 1 "make: run builds" else echo "skip make (not installed)"; fi } @@ -540,7 +633,7 @@ s_make() { # every block in it is one of those examples (tests/drivers/learn.py) s_learn() { T=$ROOT/learn - gcc -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zy + gcc -pthread -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zy # learn.py prints its own ok and FAIL lines, which the tally below counts python3 tests/drivers/learn.py $T/zy; true } diff --git a/tests/test_win.ps1 b/tests/test_win.ps1 index aee60e8..0a18728 100644 --- a/tests/test_win.ps1 +++ b/tests/test_win.ps1 @@ -120,7 +120,7 @@ try { @{ name = "growable and fixed arrays"; file = "tests\programs\arrays.zc"; expect = "100 128 9801 9801 99 3 4 12 19 8 19 28`n0`n" }, @{ name = "o3 regions"; file = "tests\programs\o3.zc"; expect = "3142 42 43 9 57 98781 520625 10192 -18782 1152" }, @{ name = "o3 scopes"; file = "tests\programs\o3scopes.zc"; expect = "116" }, - @{ name = "o3 fat data"; file = "tests\programs\o3fat.zc"; expect = "node node 3 box node [0]int" }, + @{ name = "o3 fat data"; file = "tests\programs\o3fat.zc"; expect = "node node 3 box node int[0]" }, @{ name = "o3 direct calls"; file = "tests\programs\o3call.zc"; expect = "0 0 4 23 55 node" }, @{ name = "bounds check optimization"; file = "tests\programs\bounds.zc"; expect = "55 55 8" } ) diff --git a/tests/threads/accept/actor_identity.zc b/tests/threads/accept/actor_identity.zc new file mode 100644 index 0000000..cf2d4bf --- /dev/null +++ b/tests/threads/accept/actor_identity.zc @@ -0,0 +1,10 @@ +namespace; +include rt, actor; +typecheck on; +fn main() { + let process = rt.syscall(53, 0, 0, 0), thread = rt.syscall(54, 0, 0, 0), result = 0; + actor.Actor<int,int>* a = rt.new(actor.Actor<int,int>); + a.spawn(fn(int n) { return rt.syscall(53, 0, 0, 0) == process && rt.syscall(54, 0, 0, 0) != thread; }); + a.recv(fn(int n) { result = n; return 0; }); a.send(1); a.await(); + rt.put_n(result); rt.put_c(10); a.close(); return 0; +} diff --git a/tests/threads/accept/cancel.zc b/tests/threads/accept/cancel.zc new file mode 100644 index 0000000..db4f7c7 --- /dev/null +++ b/tests/threads/accept/cancel.zc @@ -0,0 +1,8 @@ +namespace; +include rt; +unchecked fn main() { + int fd = 0; + let context = rt.syscall(51, fn() { while (1) {} return 0; } as word, &fd, 0); + rt.syscall(48, 20, 0, 0); let started = rt.syscall(50, 0, 0, 0); + rt.put_n(rt.syscall(52, context, 1, 0)); rt.put_c(10); rt.put_n(rt.syscall(50, 0, 0, 0) - started); rt.put_c(10); return 0; +} diff --git a/tests/threads/accept/cancel_o3.zc b/tests/threads/accept/cancel_o3.zc new file mode 100644 index 0000000..e196a13 --- /dev/null +++ b/tests/threads/accept/cancel_o3.zc @@ -0,0 +1,9 @@ +namespace; +include rt; +o3 int spin() { int n = 0; while (1) n++; return n; } +unchecked fn main() { + int fd = 0; + let context = rt.syscall(51, fn() { return spin(); } as word, &fd, 0); + rt.syscall(48, 20, 0, 0); let started = rt.syscall(50, 0, 0, 0); + rt.put_n(rt.syscall(52, context, 1, 0)); rt.put_c(10); rt.put_n(rt.syscall(50, 0, 0, 0) - started); rt.put_c(10); return 0; +} diff --git a/tests/threads/accept/channels.zc b/tests/threads/accept/channels.zc new file mode 100644 index 0000000..e1e966f --- /dev/null +++ b/tests/threads/accept/channels.zc @@ -0,0 +1,9 @@ +namespace; +include rt; +unchecked fn main() { + int first_fd = 0, second_fd = 0; + let first = rt.syscall(51, fn() { int value; rt.syscall(45, first_fd, &value, 1); return 0; } as word, &first_fd, 0); + let second = rt.syscall(51, fn() { int value = 9; int result = rt.syscall(44, first_fd, &value, 1); rt.syscall(44, second_fd, &result, 1); return 0; } as word, &second_fd, 0); + int result; rt.syscall(45, second_fd, &result, 1); rt.syscall(52, second, 0, 0); + rt.put_n(result); rt.put_c(' '); rt.put_n(rt.syscall(52, first, 1, 0)); rt.put_c(10); return 0; +} diff --git a/tests/threads/accept/close_order.zc b/tests/threads/accept/close_order.zc new file mode 100644 index 0000000..5c0949f --- /dev/null +++ b/tests/threads/accept/close_order.zc @@ -0,0 +1,10 @@ +namespace; +include rt; +unchecked fn main() { + int fd = 0; + let first = rt.syscall(51, fn() { rt.syscall(2, fd, 0, 0); return 0; } as word, &fd, 0); + rt.put_n(rt.syscall(52, first, 0, 0)); rt.put_c(' '); + let second = rt.syscall(51, fn() { int value; rt.syscall(45, fd, &value, 1); return 0; } as word, &fd, 0); + rt.syscall(2, fd, 0, 0); rt.put_n(rt.syscall(52, second, 1, 0)); rt.put_c(' '); + rt.put_n(rt.syscall(52, second, 1, 0)); rt.put_c(10); return 0; +} diff --git a/tests/threads/accept/failed_worker.zc b/tests/threads/accept/failed_worker.zc new file mode 100644 index 0000000..b214cac --- /dev/null +++ b/tests/threads/accept/failed_worker.zc @@ -0,0 +1,9 @@ +namespace; +include rt; +unchecked fn main() { + int fd = 0; + let first = rt.syscall(51, fn() { rt.syscall(7, 5, 0, 0); return 0; } as word, &fd, 0); + rt.put_n(rt.syscall(52, first, 0, 0)); rt.put_c(' '); + let second = rt.syscall(51, fn() { int value = 7; rt.syscall(44, fd, &value, 1); return 0; } as word, &fd, 0); + int result; rt.syscall(45, fd, &result, 1); rt.syscall(52, second, 0, 0); rt.put_n(result); rt.put_c(10); return 0; +} diff --git a/tests/threads/accept/isolation.zc b/tests/threads/accept/isolation.zc new file mode 100644 index 0000000..cb1a5e7 --- /dev/null +++ b/tests/threads/accept/isolation.zc @@ -0,0 +1,17 @@ +namespace; +include rt; +int global_state = 16; +unchecked fn main() { + int fd = 0, state = 7; word heap = rt.new(1); heap[0] = 4; + let parent_process = rt.syscall(53, 0, 0, 0), parent_thread = rt.syscall(54, 0, 0, 0); + let callback = fn() { + global_state += 5; state += 9; heap[0] += 3; + int reply[5]; reply[0] = global_state; reply[1] = state; reply[2] = heap[0]; reply[3] = rt.syscall(53, 0, 0, 0); reply[4] = rt.syscall(54, 0, 0, 0); + rt.syscall(44, fd, reply, 5); return 0; + }; + let context = rt.syscall(51, callback as word, &fd, 0); + int result[5]; rt.syscall(45, fd, result, 5); rt.syscall(52, context, 0, 0); + rt.put_n(result[0]); rt.put_c(' '); rt.put_n(result[1]); rt.put_c(' '); rt.put_n(result[2]); rt.put_c(' '); + rt.put_n(global_state); rt.put_c(' '); rt.put_n(state); rt.put_c(' '); rt.put_n(heap[0]); rt.put_c(' '); + rt.put_n(result[3] == parent_process); rt.put_c(' '); rt.put_n(result[4] != parent_thread); rt.put_c(10); return 0; +} diff --git a/tests/threads/accept/nested.zc b/tests/threads/accept/nested.zc new file mode 100644 index 0000000..e231aad --- /dev/null +++ b/tests/threads/accept/nested.zc @@ -0,0 +1,15 @@ +namespace; +include rt; +unchecked fn main() { + int fd = 0; + let worker = rt.syscall(51, fn() { + int child_fd = 0; + let run_child = fn() { while (1) {} return 0; }; + let child = rt.syscall(51, run_child as word, &child_fd, 0); + int reply = child > 0; rt.syscall(44, fd, &reply, 1); + while (1) {} return 0; + } as word, &fd, 0); + int result; rt.syscall(45, fd, &result, 1); rt.syscall(52, worker, 1, 0); + rt.put_n(result); rt.put_c(' '); rt.put_n(rt.syscall(55, 0, 0, 0)); rt.put_c(' '); + rt.put_n(rt.syscall(55, 1, 0, 0)); rt.put_c(' '); rt.put_n(rt.syscall(55, 2, 0, 0)); rt.put_c(10); return 0; +} diff --git a/tests/threads/accept/parallel.zc b/tests/threads/accept/parallel.zc new file mode 100644 index 0000000..567f829 --- /dev/null +++ b/tests/threads/accept/parallel.zc @@ -0,0 +1,11 @@ +namespace; +include rt; +unchecked fn main() { + int left_fd = 0, right_fd = 0; + let started = rt.syscall(50, 0, 0, 0); + let left = rt.syscall(51, fn() { int value = 11; rt.syscall(48, 300, 0, 0); rt.syscall(44, left_fd, &value, 1); return 0; } as word, &left_fd, 0); + let right = rt.syscall(51, fn() { int value = 21; rt.syscall(48, 300, 0, 0); rt.syscall(44, right_fd, &value, 1); return 0; } as word, &right_fd, 0); + int a, b; rt.syscall(45, left_fd, &a, 1); rt.syscall(45, right_fd, &b, 1); + rt.put_n(a); rt.put_c(' '); rt.put_n(b); rt.put_c(10); rt.put_n(rt.syscall(50, 0, 0, 0) - started); rt.put_c(10); + rt.syscall(52, left, 0, 0); rt.syscall(52, right, 0, 0); return 0; +} diff --git a/tests/threads/regression/ffi_o3.zc b/tests/threads/regression/ffi_o3.zc new file mode 100644 index 0000000..ecf8c0c --- /dev/null +++ b/tests/threads/regression/ffi_o3.zc @@ -0,0 +1,16 @@ +namespace; +include rt; +extern void qsort(word* base, long length, long size, void* compare); +int calls; +int compare(int* a, int* b) { calls++; return *a - *b; } +o3 int sum(int n) { int total = 0; for (int i = 0; i <= n; i++) total += i; return total; } +unchecked fn main() { + word comparator = cfunc(compare); int fd = 0; + let context = rt.syscall(51, fn() { + int values[3]; values[0] = 3; values[1] = 1; values[2] = 2; qsort(values, 3, 8, comparator); + int result[3]; result[0] = values[0] == 1 && values[2] == 3 && calls > 0; result[1] = sum(100); result[2] = calls; + rt.syscall(44, fd, result, 3); return 0; + } as word, &fd, 0); + int result[3]; rt.syscall(45, fd, result, 3); rt.syscall(52, context, 0, 0); + rt.put_n(result[0]); rt.put_c(' '); rt.put_n(result[1]); rt.put_c(' '); rt.put_n(calls); rt.put_c(10); return 0; +} diff --git a/tests/threads/regression/recycle.zc b/tests/threads/regression/recycle.zc new file mode 100644 index 0000000..4f0454a --- /dev/null +++ b/tests/threads/regression/recycle.zc @@ -0,0 +1,11 @@ +namespace; +include rt; +unchecked fn main() { + int i = 0, total = 0; + while (i < 12) { + int fd = 0; + let context = rt.syscall(51, fn() { int value = i; rt.syscall(44, fd, &value, 1); return 0; } as word, &fd, 0); + int result = 0; rt.syscall(45, fd, &result, 1); total += result; rt.syscall(52, context, 0, 0); i++; + } + rt.put_n(total); rt.put_c(10); return 0; +} diff --git a/tests/threads/regression/resources.zc b/tests/threads/regression/resources.zc new file mode 100644 index 0000000..abc0229 --- /dev/null +++ b/tests/threads/regression/resources.zc @@ -0,0 +1,15 @@ +namespace; +include rt; +extern int fcntl(int fd, int command); +int descriptors() { int count = 0; for (int fd = 0; fd < 256; fd++) if (fcntl(fd, 1) >= 0) count++; return count; } +unchecked fn main() { + int before = descriptors(), i = 0, closed = 0; + while (i < 24) { + int fd = 0; + let context = rt.syscall(51, fn() { int value = 1; rt.syscall(44, fd, &value, 1); return 0; } as word, &fd, 0); + int result; rt.syscall(45, fd, &result, 1); rt.syscall(52, context, 0, 0); + closed += rt.syscall(44, fd, &result, 1) == -1; i++; + } + rt.put_n(descriptors() == before); rt.put_c(' '); rt.put_n(closed); rt.put_c(' '); + rt.put_n(rt.syscall(55, 0, 0, 0)); rt.put_c(' '); rt.put_n(rt.syscall(55, 1, 0, 0)); rt.put_c(' '); rt.put_n(rt.syscall(55, 2, 0, 0)); rt.put_c(10); return 0; +} diff --git a/tests/threads/regression/resources_counts.zc b/tests/threads/regression/resources_counts.zc new file mode 100644 index 0000000..66cfc91 --- /dev/null +++ b/tests/threads/regression/resources_counts.zc @@ -0,0 +1,13 @@ +namespace; +include rt; +unchecked fn main() { + int closed = 0; + for (int i = 0; i < 24; i++) { + int fd = 0; + let context = rt.syscall(51, fn() { int value = 1; rt.syscall(44, fd, &value, 1); return 0; } as word, &fd, 0); + int result; rt.syscall(45, fd, &result, 1); rt.syscall(52, context, 0, 0); + closed += rt.syscall(44, fd, &result, 1) == -1; + } + rt.put_n(closed); rt.put_c(' '); rt.put_n(rt.syscall(55, 0, 0, 0)); rt.put_c(' '); + rt.put_n(rt.syscall(55, 1, 0, 0)); rt.put_c(' '); rt.put_n(rt.syscall(55, 2, 0, 0)); rt.put_c(10); return 0; +} diff --git a/tests/threads/reject/invalid.zc b/tests/threads/reject/invalid.zc new file mode 100644 index 0000000..e5d98aa --- /dev/null +++ b/tests/threads/reject/invalid.zc @@ -0,0 +1,3 @@ +namespace; +include rt; +unchecked fn main() { int fd = 0; rt.put_n(rt.syscall(51, -1, &fd, 0)); rt.put_c(' '); rt.put_n(rt.syscall(52, 123456, 0, 0)); rt.put_c(10); return 0; } diff --git a/tests/typeerrors/arg.zc b/tests/typeerrors/arg.zc index bf39e85..954e776 100644 --- a/tests/typeerrors/arg.zc +++ b/tests/typeerrors/arg.zc @@ -1,5 +1,8 @@ +namespace; +include rt; + // expect: type error: argument: expected point*, got int* typecheck on; struct point { int x, y; }; int f(point* p) { return p.x; } -fn main() { int* q = new(int); f(q); free(d*q); return 0; } +fn main() { int* q = rt.new(int); f(q); rt.free(d*q); return 0; } diff --git a/tests/typeerrors/arith.zc b/tests/typeerrors/arith.zc index eda4aec..fada770 100644 --- a/tests/typeerrors/arith.zc +++ b/tests/typeerrors/arith.zc @@ -1,3 +1,5 @@ +namespace; + // expect: type error: arithmetic needs numbers, got int* typecheck on; fn main() { int* p = 0; int x = p * 2; return x; } diff --git a/tests/typeerrors/assign.zc b/tests/typeerrors/assign.zc index b83e1a2..bbbf0eb 100644 --- a/tests/typeerrors/assign.zc +++ b/tests/typeerrors/assign.zc @@ -1,3 +1,5 @@ +namespace; + // expect: type error: initializer: expected char*, got int typecheck on; fn main() { char* s = 5; return 0; } diff --git a/tests/typeerrors/call.zc b/tests/typeerrors/call.zc index 7b03dbf..f717621 100644 --- a/tests/typeerrors/call.zc +++ b/tests/typeerrors/call.zc @@ -1,3 +1,5 @@ +namespace; + // expect: type error: only functions can be called, got int typecheck on; fn main() { int k = 3; return k(1); } diff --git a/tests/typeerrors/closure_result_mismatch.zc b/tests/typeerrors/closure_result_mismatch.zc new file mode 100644 index 0000000..2727d0b --- /dev/null +++ b/tests/typeerrors/closure_result_mismatch.zc @@ -0,0 +1,5 @@ +namespace; + +// expect: type error: return value: expected int, got char* +typecheck on; +fn main() { let f = fn:int(int x) { return "no"; }; return f(1); } diff --git a/tests/typeerrors/deref.zc b/tests/typeerrors/deref.zc index ed030f5..53869cf 100644 --- a/tests/typeerrors/deref.zc +++ b/tests/typeerrors/deref.zc @@ -1,3 +1,5 @@ +namespace; + // expect: type error: only pointers can be dereferenced, got int typecheck on; fn main() { int x = 3; return *x; } diff --git a/tests/typeerrors/enum_int.zc b/tests/typeerrors/enum_int.zc index c710d50..0d09ad9 100644 --- a/tests/typeerrors/enum_int.zc +++ b/tests/typeerrors/enum_int.zc @@ -1,3 +1,5 @@ +namespace; + // expect: type error: initializer: expected color, got int typecheck on; enum color { RED, GREEN = 5, BLUE }; diff --git a/tests/typeerrors/enum_other.zc b/tests/typeerrors/enum_other.zc index 5b27370..ffa36d7 100644 --- a/tests/typeerrors/enum_other.zc +++ b/tests/typeerrors/enum_other.zc @@ -1,3 +1,5 @@ +namespace; + // expect: type error: initializer: expected color, got size typecheck on; enum color { RED, GREEN = 5, BLUE }; diff --git a/tests/typeerrors/field.zc b/tests/typeerrors/field.zc index ba03697..747b6e3 100644 --- a/tests/typeerrors/field.zc +++ b/tests/typeerrors/field.zc @@ -1,3 +1,5 @@ +namespace; + // expect: point has no field z struct point { int x, y; }; fn main() { point p; return p.z; } diff --git a/tests/typeerrors/generic.zc b/tests/typeerrors/generic.zc index 04e5381..3e22519 100644 --- a/tests/typeerrors/generic.zc +++ b/tests/typeerrors/generic.zc @@ -1,3 +1,5 @@ +namespace; + // expect: type error: argument: expected int, got char* typecheck on; T same<T>(T a, T b) { return a; } diff --git a/tests/typeerrors/notstruct.zc b/tests/typeerrors/notstruct.zc index 0dff6bd..9dc4347 100644 --- a/tests/typeerrors/notstruct.zc +++ b/tests/typeerrors/notstruct.zc @@ -1,2 +1,4 @@ +namespace; + // expect: .x: that's a int, not a struct or a pointer to one fn main() { int n = 1; return n.x; } diff --git a/tests/typeerrors/ret.zc b/tests/typeerrors/ret.zc index d44267e..8ac4edb 100644 --- a/tests/typeerrors/ret.zc +++ b/tests/typeerrors/ret.zc @@ -1,3 +1,5 @@ +namespace; + // expect: type error: return value: expected bool, got char* typecheck on; bool f() { return "no"; } diff --git a/tests/typeerrors/voidret.zc b/tests/typeerrors/voidret.zc index 86c9c17..c273308 100644 --- a/tests/typeerrors/voidret.zc +++ b/tests/typeerrors/voidret.zc @@ -1,3 +1,5 @@ +namespace; + // expect: type error: return value: expected void, got int typecheck on; void f() { return 3; } |
