Files
five/tests/std_ch/run.sh
CharlesKWON 6a30c4e50e fix(gengo): compound assign for non-LOCAL LHS
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>
2026-05-03 05:14:28 +09:00

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 ]