Audit follow-up after Wave 1's pcode `+=` fix surfaced a parallel
class of silent miscompiles in the *gengo* (native-Go) emit path.
Three real bugs hiding behind happy-path test coverage:
* `arr[i] += x` was ASSIGN-only — the IndexExpr branch returned
after emitting `arr[i] := x`, dropping the original element.
Now: PushArray + Push index, ArrayPush to read, fold with RHS,
re-do PushArray + index, ArrayPop to store.
* `alias->field += x` (and the M-> / MEMVAR-> namespace variants)
were ASSIGN-only too. Same shape of bug — `x->v += 7` compiled
as `x->v := 7`. Compound branch reads via PushAliasField (or
PushMemvar for M->), folds, stores via SetAliasField (or
PopMemvar).
* PRIVATE / PUBLIC mid-function declarations were treated as
extra LOCAL slots. emitMidVarDecl extended `locals` past the
function's declared count and emitted `PopLocalFast(idx)` for
the init. The slot didn't exist at runtime, so the init either
silently scribbled past the frame (small N) or panicked with
"local variable index out of range" once exercised. New logic:
PRIVATE/PUBLIC declarations bypass the locals table and emit
`PopMemvar(name)` for the init expression. The runtime auto-
creates the memvar.
* Memvar assignment fallback. After the LOCAL/STATIC checks miss
in emitAssign, the bottom path used to be a one-line WARN that
emitted RHS + `Pop()` — silently discarding the value. PRIVATE
pSum stayed at its initial value forever. Now: ASSIGN goes
through PopMemvar; compound forms read via PushMemvar, fold,
write back via PopMemvar.
Test fixture (tests/std_ch/test_compound_lhs.prg) covers all four
shapes. The std.ch runner picks it up so the regression suite now
stands at 15/15.
Other gates green:
go test ./... : PASS
FiveSql2 SQL:1999 : 43/43
Harbour compat : 56/56
std.ch suite : 15/15
FRB suite : 5/5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
74 lines
1.8 KiB
Bash
Executable File
74 lines
1.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# std.ch regression runner. Build each PRG against the current Five
|
|
# compiler (caller's PWD must be the repo root) and execute it; non-zero
|
|
# exit or "FAIL"/"NOT REJECTED" in stdout marks the run as failed.
|
|
#
|
|
# This deliberately runs in a temp scratch directory so the DBF/NTX
|
|
# artifacts don't collide with each other across tests.
|
|
set -e
|
|
|
|
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
FIVE="$ROOT/five"
|
|
if [ ! -x "$FIVE" ]; then
|
|
echo "five binary not found at $FIVE — run 'go build -o five ./cmd/five' first" >&2
|
|
exit 2
|
|
fi
|
|
|
|
TESTS=(
|
|
test_pp_stdch
|
|
test_count
|
|
test_sum_avg
|
|
test_sum_multi
|
|
test_copy
|
|
test_sort
|
|
test_list
|
|
test_list_to_file
|
|
test_total
|
|
test_join
|
|
test_update
|
|
test_set_deleted
|
|
test_unsupported
|
|
test_block_comma
|
|
test_compound_lhs
|
|
)
|
|
|
|
work="$(mktemp -d)"
|
|
trap 'rm -rf "$work"' EXIT
|
|
|
|
pass=0
|
|
fail=0
|
|
for name in "${TESTS[@]}"; do
|
|
src="$ROOT/tests/std_ch/${name}.prg"
|
|
bin="$work/${name}"
|
|
if ! "$FIVE" build "$src" -o "$bin" >/dev/null 2>"$work/${name}.err"; then
|
|
echo "FAIL build $name"
|
|
cat "$work/${name}.err" | sed 's/^/ /'
|
|
fail=$((fail+1))
|
|
continue
|
|
fi
|
|
pushd "$work" >/dev/null
|
|
if ! out="$("$bin" 2>&1)"; then
|
|
echo "FAIL run $name"
|
|
echo "$out" | sed 's/^/ /'
|
|
fail=$((fail+1))
|
|
popd >/dev/null
|
|
continue
|
|
fi
|
|
popd >/dev/null
|
|
if echo "$out" | grep -qE 'FAIL|NOT REJECTED|expect.*got'; then
|
|
echo "FAIL assert $name"
|
|
echo "$out" | sed 's/^/ /'
|
|
fail=$((fail+1))
|
|
continue
|
|
fi
|
|
echo "PASS $name"
|
|
pass=$((pass+1))
|
|
done
|
|
|
|
echo
|
|
echo "================================================================"
|
|
echo " Results: $pass / $((pass+fail)) passed"
|
|
echo "================================================================"
|
|
[ $fail -eq 0 ]
|