#!/bin/sh # Step-1 regression: M2 -> gen_ssa/*.ssa -> qbe -> cc -> exit code. # Usage: ./run_tests.sh (from the compiler directory) pass=0; fail=0 modof() { sed -n 's/^MODULE \([A-Za-z][A-Za-z0-9]*\).*/\1/p' "tests/$1" | head -n 1 } expect_run() { # $1 = test file (no dir), $2 = expected exit code name="$1"; want="$2"; mod=$(modof "$name") rm -f "gen_ssa/$mod.ssa" "gen_ssa/$mod.s" "gen_ssa/$mod" if ./M2 "tests/$name" 2>&1 | grep -q "Parsed correctly"; then :; else fail=$((fail+1)); echo "FAIL(run): $name rejected"; return fi if [ ! -f "gen_ssa/$mod.ssa" ]; then fail=$((fail+1)); echo "FAIL(run): $name no gen_ssa/$mod.ssa"; return fi if ! qbe -o "gen_ssa/$mod.s" "gen_ssa/$mod.ssa" 2> "gen_ssa/$mod.qbeerr"; then fail=$((fail+1)); echo "FAIL(run): $name qbe:"; cat "gen_ssa/$mod.qbeerr"; return fi if ! cc "gen_ssa/$mod.s" -o "gen_ssa/$mod" 2> "gen_ssa/$mod.ccerr"; then fail=$((fail+1)); echo "FAIL(run): $name cc:"; cat "gen_ssa/$mod.ccerr"; return fi timeout 10 "gen_ssa/$mod" > /dev/null 2>&1; got=$? if [ "$got" = "$want" ]; then pass=$((pass+1)); echo "PASS(run): $name -> $got" else fail=$((fail+1)); echo "FAIL(run): $name got $got want $want" fi } expect_fail() { # $1 = test file, $2 = message text expected in the .LST name="$1"; want="$2" if ./M2 "tests/$name" 2>&1 | grep -q "Incorrect source"; then :; else fail=$((fail+1)); echo "FAIL(fail): $name accepted"; return fi lst="tests/$(basename "$name" .mod).LST" if grep -q "$want" "$lst" 2>/dev/null; then pass=$((pass+1)); echo "PASS(fail): $name [$want]" else fail=$((fail+1)); echo "FAIL(fail): [$want] not found in $lst" fi } mkdir -p gen_ssa expect_run t_minimal.mod 0 expect_run t_exit.mod 7 expect_run t_arith.mod 25 expect_run showcase1.mod 50 expect_run t_types.mod 8 expect_run showcase2.mod 71 expect_run t_if.mod 25 expect_run t_while.mod 55 expect_run t_repeat.mod 55 expect_run t_loop.mod 55 expect_run t_for.mod 85 expect_run t_case.mod 103 expect_run t_bool.mod 42 expect_run t_real.mod 31 expect_run t_array.mod 108 expect_run t_nestidx.mod 42 expect_run t_proctype.mod 42 expect_run t_compat.mod 42 expect_run t_constfold.mod 42 expect_run t_emptystat.mod 42 expect_run t_highlen.mod 18 expect_run t_builtins.mod 42 expect_run t_long.mod 42 expect_run t_unicode.mod 31 expect_run t_charbounds.mod 3 expect_run t_record.mod 160 expect_run t_with.mod 114 expect_run t_set.mod 131 expect_run t_setchar.mod 77 expect_run t_setrange.mod 55 expect_fail t_classdef.mod "not supported yet" expect_fail t_class.mod "not supported yet" expect_fail t_bad_parent.mod "undeclared identifier" expect_run showcase3.mod 183 expect_run showcase4.mod 44 expect_fail showcase5.mod "not supported yet" expect_run showcase6.mod 34 expect_run showcase7.mod 219 expect_run showcase8.mod 170 expect_run showcase9.mod 140 expect_run showcase10.mod 86 expect_run t_pointer.mod 42 expect_run t_ptrarray.mod 15 expect_run t_ptrrec.mod 15 expect_run t_ptrnest.mod 10 expect_fail t_opaqueptr.mod "" expect_run t_enumdecl.mod 0 expect_run t_enum.mod 15 expect_run t_proc.mod 0 expect_run t_forward.mod 0 expect_run t_call.mod 125 expect_run t_varpar.mod 40 expect_run t_mutual.mod 11 expect_run t_withfield.mod 13 expect_run t_valarray.mod 6 expect_run t_openarr.mod 7 expect_run t_nestedproc.mod 20 expect_run t_nestdeep.mod 211 expect_run t_nestrec.mod 15 expect_fail t_bad_undecl.mod "undeclared identifier" expect_fail t_bad_dup.mod "duplicate identifier" expect_fail t_bad_mismatch.mod "module name mismatch" expect_fail t_bad_opaque.mod "opaque type outside definition" expect_fail t_bad_while.mod "BOOLEAN condition required" expect_fail t_bad_exit.mod "not supported yet" expect_fail t_bad_return.mod "bad RETURN" expect_fail t_bad_for.mod "FOR needs integer variable and bounds" expect_fail t_bad_mixed.mod "incompatible assignment" expect_fail t_bad_overlong.mod "invalid UTF-8" expect_fail t_bad_surrogate.mod "invalid UTF-8" expect_fail t_bad_utf8big.mod "invalid UTF-8" expect_fail t_bad_truncutf.mod "invalid UTF-8" expect_fail t_bad_arity.mod "invalid call" expect_fail t_bad_actual.mod "invalid call" expect_fail t_bad_funstmt.mod "invalid call" expect_fail t_bad_deref.mod "not a POINTER type" expect_fail t_bad_new.mod "not a POINTER type" expect_fail t_bad_field.mod "unknown field" expect_fail t_bad_with.mod "not a RECORD type" expect_fail t_bad_recassign.mod "incompatible assignment" expect_fail t_bad_setelem.mod "set operand mismatch" expect_fail t_bad_setbase.mod "not supported yet" expect_fail t_bad_in.mod "set operand mismatch" expect_fail t_bad_index.mod "array index must be integer" expect_fail t_bad_bounds.mod "not supported yet" expect_fail t_bad_varbound.mod "not supported yet" expect_fail t_bad_manydims.mod "not supported yet" expect_run t_openformal.mod 0 expect_run t_valopen.mod 15 expect_fail t_bad_open.mod "not supported yet" expect_trap() { # $1 = test file — must parse, assemble, link, then abort (rc=134) name="$1"; mod=$(modof "$name") rm -f "gen_ssa/$mod.ssa" "gen_ssa/$mod.s" "gen_ssa/$mod" if ./M2 "tests/$name" 2>&1 | grep -q "Parsed correctly"; then :; else fail=$((fail+1)); echo "FAIL(trap): $name rejected"; return fi if ! qbe -o "gen_ssa/$mod.s" "gen_ssa/$mod.ssa" 2> "gen_ssa/$mod.qbeerr"; then fail=$((fail+1)); echo "FAIL(trap): $name qbe"; return fi if ! cc "gen_ssa/$mod.s" -o "gen_ssa/$mod" 2> "gen_ssa/$mod.ccerr"; then fail=$((fail+1)); echo "FAIL(trap): $name cc"; return fi timeout 10 "gen_ssa/$mod" > /dev/null 2>&1; got=$? if [ "$got" = "134" ]; then pass=$((pass+1)); echo "PASS(trap): $name -> abort" else fail=$((fail+1)); echo "FAIL(trap): $name rc=$got want 134" fi } expect_trap t_trap_index.mod expect_trap t_trap_count.mod expect_trap t_trap_setspan.mod expect_trap t_trap_emptystr.mod expect_trap t_trap_chr8.mod expect_fail t_defunit.mod "" expect_fail t_implunit.mod "" expect_run t_nested.mod 42 # --- separate compilation (step 4.3): $1 prog module, $2 exit code, # then the session's source files (program last by convention) --- SHIM="../runtime/syslib/shim.c" # Build a session image; echoes 0 on success, non-zero on failure. build_session() { prog="$1"; shift files="" for f in "$@"; do case "$f" in */*) files="$files $f";; *) files="$files tests/$f";; esac done rm -f "gen_ssa/$prog.ssa" "gen_ssa/$prog.s" "gen_ssa/$prog" if ./M2 $files 2>&1 | grep -q "Parsed correctly"; then :; else echo "rejected"; return 1 fi [ -f "gen_ssa/$prog.ssa" ] || { echo "no image"; return 1; } qbe -o "gen_ssa/$prog.s" "gen_ssa/$prog.ssa" 2> "gen_ssa/$prog.qbeerr" \ || { echo "qbe"; cat "gen_ssa/$prog.qbeerr"; return 1; } cc "gen_ssa/$prog.s" "$SHIM" -o "gen_ssa/$prog" -lm 2> "gen_ssa/$prog.ccerr" \ || { echo "cc"; cat "gen_ssa/$prog.ccerr"; return 1; } return 0 } expect_run_files() { prog="$1"; want="$2"; shift 2 if ! build_session "$prog" "$@"; then fail=$((fail+1)); echo "FAIL(session): $prog $? ($(build_session "$prog" "$@" 2>&1))"; return fi timeout 10 "gen_ssa/$prog" > /dev/null 2>&1; got=$? if [ "$got" = "$want" ]; then pass=$((pass+1)); echo "PASS(session): $prog -> $got" else fail=$((fail+1)); echo "FAIL(session): $prog got $got want $want" fi } expect_run_files_out() { # $1 prog, $2 exit code, $3 expected first output line, then files prog="$1"; want="$2"; wantline="$3"; shift 3 if ! build_session "$prog" "$@"; then fail=$((fail+1)); echo "FAIL(session-out): $prog build failed"; return fi out=$(timeout 10 "gen_ssa/$prog" 2>/dev/null); got=$? first=$(printf '%s\n' "$out" | head -n 1) if [ "$got" = "$want" ] && [ "$first" = "$wantline" ]; then pass=$((pass+1)); echo "PASS(session-out): $prog -> $got [$first]" else fail=$((fail+1)); echo "FAIL(session-out): $prog rc=$got line=[$first] want=[$wantline]" fi } expect_run_files DBasicProg 49 d_basic.def d_basic.mod d_basic_prog.mod expect_run_files DQualProg 46 d_basic.def d_basic.mod d_qual_prog.mod expect_run_files DTypesProg 36 d_types.def d_types.mod d_types_prog.mod expect_run_files DOpaqueProg 55 d_opaque.def d_opaque.mod d_opaque_prog.mod expect_run_files FioProg 42 ../runtime/syslib/SysShim.def ../runtime/syslib/SysShim.mod ../runtime/syslib/FileIO.def ../runtime/syslib/FileIO.mod fio_prog.mod expect_run_files StorageProg 42 ../stdlib/storage.def ../stdlib/storage.mod storage_prog.mod expect_run_files ClashProg 60 d_clash_a.def d_clash_a.mod d_clash_b.def d_clash_b.mod d_clash_prog.mod expect_run_files_out Hello 0 "Hello, Modula-2!" ../stdlib/sysio.def ../stdlib/sysio.mod hello.mod expect_run_files_out StringsProg 0 "Hello World" ../stdlib/sysio.def ../stdlib/sysio.mod ../stdlib/strings.def ../stdlib/strings.mod strings_prog.mod expect_run_files_out Stdlib2Prog 42 "stdlib2 n=42" ../stdlib/sysio.def ../stdlib/sysio.mod ../stdlib/math.def ../stdlib/math.mod ../stdlib/strings.def ../stdlib/strings.mod ../stdlib/charclass.def ../stdlib/charclass.mod stdlib2_prog.mod expect_run_files MathProg 12 ../stdlib/sysio.def ../stdlib/sysio.mod ../stdlib/math.def ../stdlib/math.mod math_prog.mod expect_run_files Utf8Prog 127 ../runtime/syslib/Utf8.def ../runtime/syslib/Utf8.mod utf8_prog.mod expect_run_files ConvProg 127 ../stdlib/conversions.def ../stdlib/conversions.mod conv_prog.mod expect_run_files TStrEq 15 t_streq.mod expect_run_files_out UTextProg 42 "héllo" ../runtime/syslib/Utf8.def ../runtime/syslib/Utf8.mod ../stdlib/textio.def ../stdlib/textio.mod utext_prog.mod expect_run_files_in() { # $1 prog, $2 exit code, $3 expected first line, $4 stdin, then files prog="$1"; want="$2"; wantline="$3"; input="$4"; shift 4 if ! build_session "$prog" "$@"; then fail=$((fail+1)); echo "FAIL(session-in): $prog build failed"; return fi out=$(printf '%s\n' "$input" | timeout 10 "gen_ssa/$prog" 2>/dev/null); got=$? first=$(printf '%s\n' "$out" | head -n 1) if [ "$got" = "$want" ] && [ "$first" = "$wantline" ]; then pass=$((pass+1)); echo "PASS(session-in): $prog -> $got [$first]" else fail=$((fail+1)); echo "FAIL(session-in): $prog rc=$got line=[$first] want=[$wantline]" fi } expect_run_files_in ReadProg 0 "42" "20 22" ../stdlib/sysio.def ../stdlib/sysio.mod read_prog.mod expect_run_files_in WholeIOProg 0 "42" "6 7" ../stdlib/sysio.def ../stdlib/sysio.mod ../stdlib/wholeio.def ../stdlib/wholeio.mod wholeio_prog.mod expect_run_files_in RealProg 42 "3.500000E+00" "1.25" ../stdlib/realio.def ../stdlib/realio.mod ../stdlib/sysio.def ../stdlib/sysio.mod realio_prog.mod expect_run_files FilesProg 42 ../stdlib/files.def ../stdlib/files.mod files_prog.mod expect_run_files TBadFile 42 ../stdlib/files.def ../stdlib/files.mod t_badfile.mod rm -f gen_ssa/_files_tmp.txt expect_run_files_in Showcase11 80 "Modula-2" "42" \ ../stdlib/sysio.def ../stdlib/sysio.mod \ ../runtime/syslib/Utf8.def ../runtime/syslib/Utf8.mod \ ../stdlib/textio.def ../stdlib/textio.mod \ ../stdlib/wholeio.def ../stdlib/wholeio.mod \ ../stdlib/strings.def ../stdlib/strings.mod \ ../stdlib/math.def ../stdlib/math.mod \ ../stdlib/files.def ../stdlib/files.mod \ showcase11.mod rm -f gen_ssa/_showcase11.txt # step 8.2/8.3: const folding, forward types, trailing ';', empty # statements, 1-char strings, qualified type names. expect_run_files Showcase12 42 showcase12lib.def showcase12lib.mod showcase12.mod expect_run_files_out Showcase13 42 "Even(8)=T Odd(7)=T total=42" ../stdlib/sysio.def ../stdlib/sysio.mod showcase13.mod expect_run_files_out MixProg 0 "7" ../runtime/syslib/Utf8.def ../runtime/syslib/Utf8.mod ../stdlib/textio.def ../stdlib/textio.mod ../stdlib/sysio.def ../stdlib/sysio.mod ../stdlib/files.def ../stdlib/files.mod d_mix_prog.mod rm -f gen_ssa/_mix.txt expect_fail_files() { # $1 = listing basename, $2 = message (may be empty), then files lst="$1"; want="$2"; shift 2 files="" for f in "$@"; do files="$files tests/$f"; done if ./M2 $files 2>&1 | grep -q "Incorrect source"; then :; else fail=$((fail+1)); echo "FAIL(session-rej): $lst accepted"; return fi if [ -z "$want" ]; then pass=$((pass+1)); echo "PASS(session-rej): $lst" elif grep -q "$want" "tests/$lst.LST" 2>/dev/null; then pass=$((pass+1)); echo "PASS(session-rej): $lst [$want]" else fail=$((fail+1)); echo "FAIL(session-rej): [$want] not in tests/$lst.LST" fi } expect_fail_files d_badimp_prog "undeclared identifier" d_basic.def d_basic.mod d_badimp_prog.mod # --- step 7 gate: compiler sources free of gm2-only imports. # FileIO is the last module to port (see docs/summary_step7.md); # it is excluded here so the gate guards everything else. --- if grep -qE "(IMPORT|FROM)[[:space:]]+.*\b(Environment|FileSysOp|TextIO|RawIO|WholeIO|IOChan|ProgramArgs)\b" \ src/SymTab.def src/SymTab.mod src/QbeGen.def src/QbeGen.mod \ src/M2S.mod src/M2P.mod src/M2.mod src/*.frm 2>/dev/null; then fail=$((fail+1)); echo "FAIL(gate): gm2-only imports present" else pass=$((pass+1)); echo "PASS(gate): no gm2-only imports (FileIO port pending)" fi echo "--- $pass passed, $fail failed ---" [ "$fail" -eq 0 ]