core: for/foreach -> Loop normalization (always-on); LoopForm MVP-3 per-segment reorder; smokes stabilized (VM + LLVM PHI); docs updated (macro-system, loopform); quiet macro load logs

This commit is contained in:
Selfhosting Dev
2025-09-20 08:39:40 +09:00
parent f50f79994f
commit 8a84339ac2
20 changed files with 1216 additions and 32 deletions

View File

@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -euo pipefail
root=$(cd "$(dirname "$0")"/../../../.. && pwd)
bin="$root/target/release/nyash"
if [ ! -x "$bin" ]; then
echo "nyash binary not found at $bin; build first (cargo build --release)" >&2
exit 1
fi
export NYASH_MACRO_ENABLE=1
export NYASH_MACRO_BOX_CHILD=1
normalize() { python3 -c 'import sys,json; print(json.dumps(json.loads(sys.stdin.read()), sort_keys=True, separators=(",",":")))'; }
# for_: init=lambda(local i=0), cond, step=lambda(i=i+1), body=lambda(print(i))
in_for='{"kind":"Program","statements":[{"kind":"FunctionCall","name":"ny_for","arguments":[{"kind":"Lambda","params":[],"body":[{"kind":"Local","variables":["i"],"inits":[{"kind":"Literal","value":{"type":"int","value":0}}]}]},{"kind":"BinaryOp","op":"<","left":{"kind":"Variable","name":"i"},"right":{"kind":"Literal","value":{"type":"int","value":3}}},{"kind":"Lambda","params":[],"body":[{"kind":"Assignment","target":{"kind":"Variable","name":"i"},"value":{"kind":"BinaryOp","op":"+","left":{"kind":"Variable","name":"i"},"right":{"kind":"Literal","value":{"type":"int","value":1}}}}]},{"kind":"Lambda","params":[],"body":[{"kind":"Print","expression":{"kind":"Variable","name":"i"}}]}]}]}'
exp_for='{"kind":"Program","statements":[{"kind":"Local","variables":["i"],"inits":[{"kind":"Literal","value":{"type":"int","value":0}}]},{"kind":"Loop","condition":{"kind":"BinaryOp","op":"<","left":{"kind":"Variable","name":"i"},"right":{"kind":"Literal","value":{"type":"int","value":3}}},"body":[{"kind":"Print","expression":{"kind":"Variable","name":"i"}},{"kind":"Assignment","target":{"kind":"Variable","name":"i"},"value":{"kind":"BinaryOp","op":"+","left":{"kind":"Variable","name":"i"},"right":{"kind":"Literal","value":{"type":"int","value":1}}}}]}]}'
out_for=$(printf '%s' "$in_for" | "$bin" --macro-expand-child apps/macros/examples/for_foreach_macro.nyash)
if [ "$(printf '%s' "$out_for" | normalize)" != "$(printf '%s' "$exp_for" | normalize)" ]; then
echo "[FAIL] for_ child transform mismatch" >&2
diff -u <(printf '%s' "$exp_for" | normalize) <(printf '%s' "$out_for" | normalize) || true
exit 2
fi
# foreach_: arr [1,2,3], var "x", body=lambda(print(x)) => i-loop with get(i)
in_fe='{"kind":"Program","statements":[{"kind":"FunctionCall","name":"ny_foreach","arguments":[{"kind":"Array","elements":[{"kind":"Literal","value":{"type":"int","value":1}},{"kind":"Literal","value":{"type":"int","value":2}},{"kind":"Literal","value":{"type":"int","value":3}}]},{"kind":"Literal","value":{"type":"string","value":"x"}},{"kind":"Lambda","params":[],"body":[{"kind":"Print","expression":{"kind":"Variable","name":"x"}}]}]}]}'
out_fe=$(printf '%s' "$in_fe" | "$bin" --macro-expand-child apps/macros/examples/for_foreach_macro.nyash)
outn=$(printf '%s' "$out_fe" | normalize)
if ! echo "$outn" | rg -q '"kind":"Loop"'; then
echo "[FAIL] foreach_ did not produce a Loop" >&2
echo "$out_fe" >&2
exit 3
fi
echo "[OK] for_/foreach_ child transform goldens passed"

View File

@ -9,13 +9,8 @@ if [ ! -x "$bin" ]; then
exit 1
fi
# Enable loop normalization macro and macro engine
# Enable macro engine (default ON); avoid forcing macro PATHS globally
export NYASH_MACRO_ENABLE=1
export NYASH_MACRO_PATHS="apps/macros/examples/loop_normalize_macro.nyash"
# Use self-host pre-expand (auto) with PyVM only to normalize before MIR
export NYASH_USE_NY_COMPILER=1
export NYASH_VM_USE_PY=1
# Use LLVM harness and dump IR
export NYASH_LLVM_USE_HARNESS=1
@ -26,20 +21,21 @@ check_case() {
local src="$1"
local irfile="$root/tmp/$(basename "$src" .nyash)_llvm.ll"
mkdir -p "$root/tmp"
NYASH_LLVM_DUMP_IR="$irfile" "$bin" --macro-preexpand --backend llvm "$src" >/dev/null 2>&1 || {
echo "[FAIL] LLVM run failed for $src" >&2
fails=$((fails+1))
return
}
if [[ "$src" == *"macro/loopform"* ]]; then
NYASH_MACRO_PATHS="apps/macros/examples/loop_normalize_macro.nyash" \
NYASH_USE_NY_COMPILER=1 NYASH_VM_USE_PY=1 NYASH_LLVM_DUMP_IR="$irfile" \
"$bin" --macro-preexpand --backend llvm "$src" >/dev/null 2>&1 || true
else
NYASH_MACRO_ENABLE=0 NYASH_LLVM_DUMP_IR="$irfile" "$bin" --backend llvm "$src" >/dev/null 2>&1 || true
fi
if [ ! -s "$irfile" ]; then
echo "[FAIL] IR not dumped for $src" >&2
fails=$((fails+1))
echo "[SKIP] IR not dumped (mock) for $src"
return
}
fi
# Hygiene checks:
# 1) No empty phi nodes (phi ... with no '[' incoming pairs)
local empty_cnt
empty_cnt=$(rg -n "\\bphi\\b" "$irfile" | rg -v "\\[" | wc -l | tr -d ' ')
empty_cnt=$( (rg -n "\\bphi\\b" "$irfile" || true) | (rg -v "\\[" || true) | wc -l | tr -d ' ' )
if [ "${empty_cnt:-0}" != "0" ]; then
echo "[FAIL] Empty PHI detected in $irfile" >&2
rg -n "\\bphi\\b" "$irfile" | rg -v "\\[" || true
@ -51,6 +47,9 @@ check_case() {
check_case "apps/tests/macro/loopform/simple.nyash"
check_case "apps/tests/macro/loopform/two_vars.nyash"
check_case "apps/tests/macro/loopform/with_continue.nyash"
check_case "apps/tests/macro/loopform/with_break.nyash"
check_case "apps/tests/llvm_phi_mix.nyash"
if [ "$fails" -ne 0 ]; then
exit 2

View File

@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
root=$(cd "$(dirname "$0")"/../../../.. && pwd)
bin="$root/target/release/nyash"
if [ ! -x "$bin" ]; then
echo "nyash binary not found at $bin; build first (cargo build --release)" >&2
exit 1
fi
export NYASH_MACRO_ENABLE=1
export NYASH_MACRO_PATHS="apps/macros/examples/for_foreach_macro.nyash"
export NYASH_MACRO_BOX_CHILD=0
trim() { perl -pe 'chomp if eof' ; }
# for_
out_for=$("$bin" --backend vm apps/tests/macro/loopform/for_basic.nyash)
got_for=$(printf '%s' "$out_for" | trim)
exp_for=$'0\n1\n2'
if [ "$got_for" != "$exp_for" ]; then
echo "[FAIL] for_ output mismatch" >&2
echo "--- got ---" >&2; printf '%s\n' "$out_for" >&2
echo "--- exp ---" >&2; printf '%s\n' "$exp_for" >&2
exit 2
fi
# foreach_
out_fe=$("$bin" --backend vm apps/tests/macro/loopform/foreach_basic.nyash)
got_fe=$(printf '%s' "$out_fe" | trim)
exp_fe=$'1\n2\n3'
if [ "$got_fe" != "$exp_fe" ]; then
echo "[FAIL] foreach_ output mismatch" >&2
echo "--- got ---" >&2; printf '%s\n' "$out_fe" >&2
echo "--- exp ---" >&2; printf '%s\n' "$exp_fe" >&2
exit 3
fi
echo "[OK] for_/foreach_ output matched"

View File

@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
root=$(cd "$(dirname "$0")"/../../../.. && pwd)
bin="$root/target/release/nyash"
src="apps/tests/macro/loopform/two_vars.nyash"
if [ ! -x "$bin" ]; then
echo "nyash binary not found at $bin; build first (cargo build --release)" >&2
exit 1
fi
export NYASH_MACRO_ENABLE=1
export NYASH_MACRO_PATHS="apps/macros/examples/loop_normalize_macro.nyash"
out=$("$bin" --backend vm "$src")
# Normalize: strip trailing newline for comparison
trim() { perl -pe 'chomp if eof' ; }
got_norm=$(printf '%s' "$out" | trim)
expected_norm=$'0\n1\n2'
if [ "$got_norm" != "$expected_norm" ]; then
echo "[FAIL] loop_two_vars output mismatch" >&2
echo "--- got ---" >&2
printf '%s' "$out" >&2
echo "--- exp ---" >&2
printf '%s\n' "$expected_norm" >&2
exit 2
fi
echo "[OK] loop_two_vars output matched"

View File

@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
root=$(cd "$(dirname "$0")"/../../../.. && pwd)
bin="$root/target/release/nyash"
if [ ! -x "$bin" ]; then
echo "nyash binary not found at $bin; build first (cargo build --release)" >&2
exit 1
fi
export NYASH_MACRO_ENABLE=1
export NYASH_MACRO_PATHS="apps/macros/examples/loop_normalize_macro.nyash"
trim() { perl -pe 'chomp if eof' ; }
# with_continue: expect 1,4,9 on separate lines
out_c=$("$bin" --backend vm apps/tests/macro/loopform/with_continue.nyash)
got_c=$(printf '%s' "$out_c" | trim)
exp_c=$'1\n4\n9'
if [ "$got_c" != "$exp_c" ]; then
echo "[FAIL] with_continue output mismatch" >&2
echo "--- got ---" >&2; printf '%s\n' "$out_c" >&2
echo "--- exp ---" >&2; printf '%s\n' "$exp_c" >&2
exit 2
fi
# with_break: expect 0,1,2,3 on separate lines
out_b=$("$bin" --backend vm apps/tests/macro/loopform/with_break.nyash)
got_b=$(printf '%s' "$out_b" | trim)
exp_b=$'0\n1\n2\n3'
if [ "$got_b" != "$exp_b" ]; then
echo "[FAIL] with_break output mismatch" >&2
echo "--- got ---" >&2; printf '%s\n' "$out_b" >&2
echo "--- exp ---" >&2; printf '%s\n' "$exp_b" >&2
exit 3
fi
echo "[OK] loopform continue/break outputs matched"