aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-14 19:55:03 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-14 19:55:03 +0330
commit35d1811c7ef8e41baddb9ce0fbb1d28970ddc213 (patch)
tree387fbb068b0e47129e6faea30c7d0b5451921ca8
parentmerge work/exactsize: exact-size heap bounds (diff)
zy: nonzero exit on compile errors
-rw-r--r--src/compiler/base.zc8
-rw-r--r--tests/exitstatus.sh21
-rwxr-xr-xtests/test.sh2
3 files changed, 28 insertions, 3 deletions
diff --git a/src/compiler/base.zc b/src/compiler/base.zc
index 8c13201..c6abad7 100644
--- a/src/compiler/base.zc
+++ b/src/compiler/base.zc
@@ -537,24 +537,26 @@ int errat() {
nerr++;
return 0;
}
+// stop with exit status 1 after compile errors (a runtime trap keeps rt.halt).
+int errexit() { rt.syscall(rt.SYS_EXIT, 1, 0, 0); return 0; }
// finish an error line; the compiler stops after 20 errors.
int errend() {
if (cmode) { dg_close(); if (nerr >= 20) chkabort = 1; return 0; }
rt.put_c(10);
- if (nerr >= 20) { rt.put_s("zc: too many errors, stopping\n"); rt.halt(); }
+ if (nerr >= 20) { rt.put_s("zc: too many errors, stopping\n"); errexit(); }
return 0;
}
// stop compiling after errors and report how many there were.
int giveup() {
if (cmode) { dg_close(); chkabort = 1; return 0; }
- rt.put_s("zc: "); rt.put_n(nerr); if (nerr == 1) rt.put_s(" error\n"); else rt.put_s(" errors\n"); rt.halt();
+ rt.put_s("zc: "); rt.put_n(nerr); if (nerr == 1) rt.put_s(" error\n"); else rt.put_s(" errors\n"); errexit();
return 0;
}
// report a fatal error and stop, or record it in check mode.
int fatal(char* msg) {
errat(); rt.put_s(msg);
if (cmode) { dg_close(); chkabort = 1; return 0; }
- rt.put_c(10); rt.halt();
+ rt.put_c(10); errexit();
return 0;
}
// report a syntax error at the current token, once per recovery.
diff --git a/tests/exitstatus.sh b/tests/exitstatus.sh
new file mode 100644
index 0000000..d48ad1f
--- /dev/null
+++ b/tests/exitstatus.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+# exit status of zy: a program that fails to compile (syntax, type or analyzer
+# error, or too many errors) exits 1; a good program exits 0; a runtime trap
+# keeps its status (0 today). usage: tests/exitstatus.sh [VM]. prints one line
+# per case and exits 1 when any case is wrong.
+vm=${1:-build/bin/zy}
+T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
+fail=0
+want() { # status description < program
+ cat > "$T/p.zc"; "$vm" < "$T/p.zc" > "$T/out" 2>&1; local r=$?
+ if [ "$r" = "$1" ]; then echo "ok $2 (exit $r)"; else echo "FAIL $2: exit $r, want $1: $(head -c 200 "$T/out")"; fail=1; fi
+}
+want 0 "a good program" <<< $'namespace;\ninclude rt;\nfn main() { rt.put_n(1); return 0; }'
+want 1 "a syntax error" <<< $'namespace;\ninclude rt;\nfn main() { int a = ; return 0; }'
+want 1 "a module access without include" <<< $'fn main() { rt.put_n(1); return 0; }'
+want 1 "an undefined name" <<< $'namespace;\ninclude rt;\nfn main() { rt.put_n(nosuch); return 0; }'
+want 1 "a type error" < tests/typeerrors/arg.zc
+want 1 "an analyzer error" < tests/errors/alias_field.zc
+want 1 "twenty errors" < <(printf 'namespace;\ninclude rt;\nfn main() {\n'; for i in $(seq 25); do echo " int a$i = ;"; done; echo ' return 0; }')
+want 0 "a runtime trap keeps its status" < tests/programs/bounds_heap.zc
+exit $fail
diff --git a/tests/test.sh b/tests/test.sh
index 78b1f77..39e5949 100755
--- a/tests/test.sh
+++ b/tests/test.sh
@@ -237,6 +237,8 @@ ABC" "$l o3 lifts byte loads and stores"
chk "$r" "rejected: $(ls tests/typeerrors/*.zc | wc -l | tr -d ' ') passed, 0 failed" "$l typechecker rejects every bad program"
r=$(bash tests/runerr.sh "$vm" tests/langerrors | tail -1)
chk "$r" "rejected: $(ls tests/langerrors/*.zc | wc -l | tr -d ' ') passed, 0 failed" "$l o3 and array misuse is rejected"
+ bash tests/exitstatus.sh "$vm" > /tmp/zy-exitstatus.$$ 2>&1; r=$?; grep -v '^ok' /tmp/zy-exitstatus.$$; rm -f /tmp/zy-exitstatus.$$
+ chk $r 0 "$l a compile error exits 1, a good program and a runtime trap exit 0"
}
s_interp() {
T=$ROOT/interp