🚀 Break/Continue/Try-Catch構文のサポート追加とMIRループ制御強化

## 主な変更点

### 🎯 MIRループ制御の実装(根治対応)
- src/mir/loop_builder.rs: Break/Continue対応のループコンテキスト管理
  - ループのbreak/continueターゲットブロック追跡
  - ネストループの適切な処理
- src/mir/builder.rs: Break/Continue文のMIR生成実装
- src/tokenizer.rs: Break/Continue/Tryトークン認識追加

### 📝 セルフホストパーサーの拡張
- apps/selfhost-compiler/boxes/parser_box.nyash:
  - Stage-3: break/continue構文受理(no-op実装)
  - Stage-3: try-catch-finally構文受理(構文解析のみ)
  - エラー処理構文の将来対応準備

### 📚 ドキュメント更新
- 論文K(爆速事件簿): 45事例に更新(4件追加)
  - PyVM迂回路の混乱事件
  - Break/Continue無限ループ事件
  - EXE-first戦略の再発見
- 論文I(開発秘話): Day 38の重要決定追加

### 🧪 テストケース追加
- apps/tests/: ループ制御とPHIのテストケース
  - nested_loop_inner_break_isolated.nyash
  - nested_loop_inner_continue_isolated.nyash
  - loop_phi_one_sided.nyash
  - shortcircuit関連テスト

## 技術的詳細
- Break/ContinueをMIRレベルで適切に処理
- 無限ループ問題(CPU 99.9%暴走)の根本解決
- 将来の例外処理機能への準備

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Selfhosting Dev
2025-09-15 22:14:42 +09:00
parent d90216e9c4
commit 94d95dfbcd
34 changed files with 989 additions and 37 deletions

View File

@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail
[[ "${NYASH_CLI_VERBOSE:-0}" == "1" ]] && set -x
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
BIN="$ROOT_DIR/target/release/nyash"
if [[ ! -x "$BIN" ]]; then
(cd "$ROOT_DIR" && cargo build --release >/dev/null)
fi
VEC_DIR="$ROOT_DIR/tests/json_v0"
[[ -d "$VEC_DIR" ]] || { echo "No vectors at $VEC_DIR" >&2; exit 1; }
pass() { echo "$1" >&2; }
fail() { echo "$1" >&2; echo "$2" >&2; exit 1; }
run_vec() {
local base="$1"; shift
local expect_code="$1"; shift
local path="$VEC_DIR/$base.json"
[[ -f "$path" ]] || fail "$base (missing)" "Vector not found: $path"
set +e
OUT=$(cat "$path" | NYASH_PIPE_USE_PYVM=1 "$BIN" --ny-parser-pipe --backend vm 2>&1)
STATUS=$?
set -e
if [[ "$STATUS" == "$expect_code" ]]; then pass "$base"; else fail "$base" "$OUT"; fi
}
# Vectors: base name -> expected Result
run_vec arith 7
run_vec if_then_else 10
run_vec while_sum 3
run_vec logical_shortcircuit_and 0
run_vec logical_shortcircuit_or 0
run_vec method_string_length 3
run_vec logical_nested 1
run_vec string_chain 2
echo "All JSON v0 vectors PASS" >&2
exit 0

View File

@ -18,42 +18,97 @@ fail() { echo "❌ $1" >&2; echo "$2" >&2; exit 1; }
compile_json() {
local src_text="$1"
printf "%s\n" "$src_text" > "$TMP/ny_parser_input.ny"
# Build a local parser EXE (no pack) and run it
"$ROOT_DIR/tools/build_compiler_exe.sh" --no-pack -o nyash_compiler_smoke >/dev/null
"$ROOT_DIR/nyash_compiler_smoke" "$TMP/ny_parser_input.ny"
# Primary: Python MVP parser (fast, stable vectors)
if command -v python3 >/dev/null 2>&1; then
local pyjson
pyjson=$(python3 "$ROOT_DIR/tools/ny_parser_mvp.py" "$TMP/ny_parser_input.ny" 2>/dev/null | sed -n '1p')
if [[ -n "$pyjson" ]]; then printf '%s\n' "$pyjson"; return 0; fi
fi
# Fallback-2: inline VM run using ParserBox + EmitterBox (embed source string)
local inline="$TMP/inline_selfhost_emit.nyash"
# Read src text and escape quotes and backslashes for literal embedding; keep newlines
local esc
esc=$(sed -e 's/\\/\\\\/g' -e 's/\"/\\\"/g' "$TMP/ny_parser_input.ny")
cat > "$inline" << NY
include "apps/selfhost-compiler/boxes/parser_box.nyash"
include "apps/selfhost-compiler/boxes/emitter_box.nyash"
static box Main {
main(args) {
local src = "$esc"
local p = new ParserBox()
local json = p.parse_program2(src)
local e = new EmitterBox()
json = e.emit_program(json, "[]")
print(json)
return 0
}
}
NY
# Execute via VM (Rust interpreter) is fine since print is builtin and no plugins needed
local raw
raw=$("$BIN" --backend vm "$inline" 2>/dev/null || true)
local json
json=$(printf '%s\n' "$raw" | awk 'BEGIN{found=0} /^[ \t]*\{/{ if ($0 ~ /"version"/ && $0 ~ /"kind"/) { print; found=1; exit } } END{ if(found==0){} }')
if [[ -n "$json" ]]; then printf '%s\n' "$json"; return 0; fi
# Optional: build & run EXE if explicitly requested
if [[ "${NYASH_SELFHOST_USE_EXE:-0}" == "1" ]]; then
set +e
"$ROOT_DIR/tools/build_compiler_exe.sh" --no-pack -o nyash_compiler_smoke >/dev/null 2>&1
local build_status=$?
if [[ "$build_status" -eq 0 && -x "$ROOT_DIR/nyash_compiler_smoke" ]]; then
local out
out=$("$ROOT_DIR/nyash_compiler_smoke" "$TMP/ny_parser_input.ny" 2>/dev/null)
set -e
printf "%s\n" "$out"
return 0
fi
set -e
fi
echo ""
}
run_case_bridge() {
local name="$1"; shift
local src="$1"; shift
local regex="$1"; shift
local expect_code="$1"; shift
set +e
JSON=$(compile_json "$src")
OUT=$(printf '%s\n' "$JSON" | NYASH_VM_USE_PY=1 "$BIN" --ny-parser-pipe --backend vm 2>&1)
OUT=$(printf '%s\n' "$JSON" | NYASH_PIPE_USE_PYVM=1 "$BIN" --ny-parser-pipe --backend vm 2>&1)
STATUS=$?
set -e
if echo "$OUT" | rg -q "$regex"; then pass "$name"; else fail "$name" "$OUT"; fi
if [[ "$STATUS" == "$expect_code" ]]; then pass "$name"; else fail "$name" "$OUT"; fi
}
# A) arithmetic
run_case_bridge "arith (bridge)" 'return 1+2*3' '^Result:\s*7\b'
run_case_bridge "arith (bridge)" 'return 1+2*3' 7
# B) unary minus
run_case_bridge "unary (bridge)" 'return -3 + 5' '^Result:\s*2\b'
run_case_bridge "unary (bridge)" 'return -3 + 5' 2
# C) logical AND
run_case_bridge "and (bridge)" 'return (1 < 2) && (2 < 3)' '^Result:\s*true\b'
run_case_bridge "and (bridge)" 'return (1 < 2) && (2 < 3)' 1
# D) ArrayBox push/size -> 2
read -r -d '' SRC_ARR <<'NY'
SRC_ARR=$(cat <<'NY'
local a = new ArrayBox()
a.push(1)
a.push(2)
return a.size()
NY
run_case_bridge "array push/size (bridge)" "$SRC_ARR" '^Result:\s*2\b'
)
run_case_bridge "array push/size (bridge)" "$SRC_ARR" 2
# E) String.length() -> 3
run_case_bridge "string length (bridge)" 'local s = "abc"; return s.length()' '^Result:\s*3\b'
run_case_bridge "string length (bridge)" $'local s = "abc"\nreturn s.length()' 3
# F) assignment without 'local' (update)
SRC_ASSIGN=$(cat <<'NY'
local x = 1
local x = x + 2
return x
NY
)
run_case_bridge "assign update (bridge)" "$SRC_ASSIGN" 3
echo "All selfhost Stage-2 bridge smokes PASS" >&2
exit 0

View File

@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail
[[ "${NYASH_CLI_VERBOSE:-0}" == "1" ]] && set -x
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
BIN="$ROOT_DIR/target/release/nyash"
if [[ ! -x "$BIN" ]]; then
(cd "$ROOT_DIR" && cargo build --release >/dev/null)
fi
TMP="$ROOT_DIR/tmp"
mkdir -p "$TMP"
pass() { echo "$1" >&2; }
fail() { echo "$1" >&2; echo "$2" >&2; exit 1; }
compile_json_stage3() {
local src_text="$1"
local inline="$TMP/inline_selfhost_emit_stage3.nyash"
# Embed source (escape quotes and backslashes; preserve newlines)
local esc
esc=$(printf '%s' "$src_text" | sed -e 's/\\/\\\\/g' -e 's/\"/\\\"/g')
cat > "$inline" << NY
include "apps/selfhost-compiler/boxes/parser_box.nyash"
include "apps/selfhost-compiler/boxes/emitter_box.nyash"
static box Main {
main(args) {
local source_text = "$esc"
local p = new ParserBox()
local json = p.parse_program2(source_text)
local e = new EmitterBox()
json = e.emit_program(json, "[]")
print(json)
return 0
}
}
NY
local raw
raw=$("$BIN" --backend vm "$inline" 2>/dev/null || true)
# Extract the first JSON-looking line (contains version/kind)
printf '%s\n' "$raw" | awk 'BEGIN{found=0} /^[ \t]*\{/{ if ($0 ~ /"version"/ && $0 ~ /"kind"/) { print; found=1; exit } } END{ if(found==0){} }'
}
run_case_stage3() {
local name="$1"; shift
local src="$1"; shift
local expect_code="$1"; shift
set +e
JSON=$(compile_json_stage3 "$src")
OUT=$(printf '%s\n' "$JSON" | NYASH_PIPE_USE_PYVM=1 "$BIN" --ny-parser-pipe --backend vm 2>&1)
CODE=$?
set -e
if [[ "$CODE" == "$expect_code" ]]; then pass "$name"; else fail "$name" "$OUT"; fi
}
# A) try/catch/finally acceptance; final return 0
run_case_stage3 "try/catch/finally (accept)" $'try { local x = 1 } catch (Error e) { local y = 2 } finally { local z = 3 }\nreturn 0' 0
# B) break acceptance under dead branch
run_case_stage3 "break in dead branch (accept)" $'if false { break } else { }\nreturn 0' 0
# C) continue acceptance under dead branch
run_case_stage3 "continue in dead branch (accept)" $'if false { continue } else { }\nreturn 0' 0
# D) throw acceptance (degrade); final return 0
run_case_stage3 "throw (accept)" $'try { throw 123 } finally { }\nreturn 0' 0
echo "All selfhost Stage-3 acceptance smokes PASS" >&2
exit 0