📚 Phase 15 - セルフホスティング戦略の明確化とEXE-first実装

## 主な変更点

### 🎯 戦略の転換と明確化
- PyVMを開発ツールとして位置づけ(本番経路ではない)
- EXE-first戦略を明確に優先(build_compiler_exe.sh実装済み)
- Phase順序の整理: 15.2(LLVM)→15.3(コンパイラ)→15.4(VM)

### 🚀 セルフホスティング基盤の実装
- apps/selfhost-compiler/にNyashコンパイラMVP実装
  - compiler.nyash: メインエントリー(位置引数対応)
  - boxes/: parser_box, emitter_box, debug_box分離
- tools/build_compiler_exe.sh: ネイティブEXEビルド+dist配布
- Python MVPパーサーStage-2完成(local/if/loop/call/method/new)

### 📝 ドキュメント整備
- Phase 15 README/ROADMAP更新(Self-Hosting優先明記)
- docs/guides/exe-first-wsl.md: WSLクイックスタート追加
- docs/private/papers/: 論文G~L、爆速事件簿41事例収録

### 🔧 技術的改善
- JSON v0 Bridge: If/Loop PHI生成実装(ChatGPT協力)
- PyVM/llvmliteパリティ検証スイート追加
- using/namespace機能(gated実装、Phase 15では非解決)

## 次のステップ
1. パーサー無限ループ修正(未実装関数の実装)
2. EXEビルドとセルフホスティング実証
3. c0→c1→c1'ブートストラップループ確立

🤖 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 18:44:49 +09:00
parent 8f11c79f19
commit d90216e9c4
68 changed files with 4521 additions and 1641 deletions

View File

@ -1,5 +1,10 @@
// Selfhost Compiler MVP (Phase 15.3)
// Reads tmp/ny_parser_input.ny and prints a minimal JSON v0 program.
// Components are split under boxes/ and included here.
include "apps/selfhost-compiler/boxes/debug_box.nyash"
include "apps/selfhost-compiler/boxes/parser_box.nyash"
include "apps/selfhost-compiler/boxes/emitter_box.nyash"
static box Main {
// ---- IO helper ----
@ -8,6 +13,7 @@ static box Main {
fb.open(path, "r")
local s = fb.read()
fb.close()
if s == null { return "return 1+2*3" }
return s
}
@ -26,283 +32,76 @@ static box Main {
return out
}
// ---- Lexer helpers ----
is_digit(ch) {
return ch == "0" || ch == "1" || ch == "2" || ch == "3" || ch == "4" || ch == "5" || ch == "6" || ch == "7" || ch == "8" || ch == "9"
}
is_space(ch) { return ch == " " || ch == "\t" || ch == "\n" || ch == "\r" || ch == ";" }
// ---- Parser (Stage-1/mini Stage-2) ----
// Global cursor for second-pass parser (no pack strings)
gpos_set(i) {
me.gpos = i
return 0
}
gpos_get() { return me.gpos }
parse_number2(src, i) {
local n = src.length()
local j = i
loop(j < n && me.is_digit(src.substring(j, j+1))) { j = j + 1 }
local s = src.substring(i, j)
me.gpos_set(j)
return "{\"type\":\"Int\",\"value\":" + s + "}"
}
parse_string2(src, i) {
local n = src.length()
local j = i + 1
local out = ""
local done = 0
loop(j < n && done == 0) {
local ch = src.substring(j, j+1)
if ch == "\"" {
j = j + 1
done = 1
} else {
if ch == "\\" && j + 1 < n {
local nx = src.substring(j+1, j+2)
if nx == "\"" { out = out + "\"" } else { if nx == "\\" { out = out + "\\" } else { out = out + nx } }
j = j + 2
} else {
out = out + ch
j = j + 1
}
}
}
me.gpos_set(j)
return "{\"type\":\"Str\",\"value\":\"" + me.esc_json(out) + "\"}"
}
parse_factor2(src, i) {
local j = me.skip_ws(src, i)
local ch = src.substring(j, j+1)
if ch == "(" {
local inner = me.parse_expr2(src, j + 1)
local k = me.gpos_get()
k = me.skip_ws(src, k)
if src.substring(k, k+1) == ")" { k = k + 1 }
me.gpos_set(k)
return inner
}
if ch == "\"" { return me.parse_string2(src, j) }
return me.parse_number2(src, j)
}
parse_term2(src, i) {
local lhs = me.parse_factor2(src, i)
local j = me.gpos_get()
local cont = 1
loop(cont == 1) {
j = me.skip_ws(src, j)
if j >= src.length() { cont = 0 } else {
local op = src.substring(j, j+1)
if op != "*" && op != "/" { cont = 0 } else {
local rhs = me.parse_factor2(src, j+1)
j = me.gpos_get()
lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}"
}
}
}
me.gpos_set(j)
return lhs
}
parse_expr2(src, i) {
local lhs = me.parse_term2(src, i)
local j = me.gpos_get()
local cont = 1
loop(cont == 1) {
j = me.skip_ws(src, j)
if j >= src.length() { cont = 0 } else {
local op = src.substring(j, j+1)
if op != "+" && op != "-" { cont = 0 } else {
local rhs = me.parse_term2(src, j+1)
j = me.gpos_get()
lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}"
}
}
}
me.gpos_set(j)
return lhs
}
parse_program2(src) {
local i = me.skip_ws(src, 0)
local j = me.skip_return_kw(src, i)
if j == i { j = i } // optional 'return'
local expr = me.parse_expr2(src, j)
return "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":" + expr + "}]}"
}
i2s(n) {
// integer to decimal string (non-negative only for MVP)
if n == 0 { return "0" }
local x = n
if x < 0 { x = 0 } // MVP: clamp negatives to 0 to avoid surprises
local out = ""
loop(x > 0) {
local q = x / 10
local d = x - q * 10
local ch = "0"
if d == 1 { ch = "1" } else {
if d == 2 { ch = "2" } else {
if d == 3 { ch = "3" } else {
if d == 4 { ch = "4" } else {
if d == 5 { ch = "5" } else {
if d == 6 { ch = "6" } else {
if d == 7 { ch = "7" } else {
if d == 8 { ch = "8" } else {
if d == 9 { ch = "9" }
}
}
}
}
}
}
}
}
out = ch + out
x = q
}
return out
}
to_int(s) {
local n = s.length()
if n == 0 { return 0 }
local i = 0
local acc = 0
loop(i < n) {
local d = s.substring(i, i+1)
local dv = 0
if d == "1" { dv = 1 } else { if d == "2" { dv = 2 } else { if d == "3" { dv = 3 } else { if d == "4" { dv = 4 } else { if d == "5" { dv = 5 } else { if d == "6" { dv = 6 } else { if d == "7" { dv = 7 } else { if d == "8" { dv = 8 } else { if d == "9" { dv = 9 } } } } } } } } }
acc = acc * 10 + dv
i = i + 1
}
return acc
}
parse_number(src, i) {
local n = src.length()
local j = i
loop(j < n && me.is_digit(src.substring(j, j+1))) { j = j + 1 }
local s = src.substring(i, j)
local json = "{\"type\":\"Int\",\"value\":" + s + "}"
return json + "@" + me.i2s(j)
}
parse_string(src, i) {
local n = src.length()
local j = i + 1
local out = ""
loop(j < n) {
local ch = src.substring(j, j+1)
if ch == "\"" {
j = j + 1
return "{\"type\":\"Str\",\"value\":\"" + me.esc_json(out) + "\"}@" + j
}
if ch == "\\" && j + 1 < n {
local nx = src.substring(j+1, j+2)
if nx == "\"" { out = out + "\"" } else { if nx == "\\" { out = out + "\\" } else { out = out + nx } }
j = j + 2
} else {
out = out + ch
j = j + 1
}
}
return "{\"type\":\"Str\",\"value\":\"" + me.esc_json(out) + "\"}@" + me.i2s(j)
}
skip_ws(src, i) {
local n = src.length()
loop(i < n && me.is_space(src.substring(i, i+1))) { i = i + 1 }
return i
}
skip_return_kw(src, i) {
// If source at i starts with "return", advance; otherwise return i unchanged
local n = src.length()
local j = i
if j < n && src.substring(j, j+1) == "r" { j = j + 1 } else { return i }
if j < n && src.substring(j, j+1) == "e" { j = j + 1 } else { return i }
if j < n && src.substring(j, j+1) == "t" { j = j + 1 } else { return i }
if j < n && src.substring(j, j+1) == "u" { j = j + 1 } else { return i }
if j < n && src.substring(j, j+1) == "r" { j = j + 1 } else { return i }
if j < n && src.substring(j, j+1) == "n" { j = j + 1 } else { return i }
return j
}
parse_factor(src, i) {
i = me.skip_ws(src, i)
local ch = src.substring(i, i+1)
if ch == "(" {
local p = me.parse_expr(src, i + 1)
local at = p.lastIndexOf("@")
local ej = p.substring(0, at)
local j = me.to_int(p.substring(at+1, p.length()))
j = me.skip_ws(src, j)
if src.substring(j, j+1) == ")" { j = j + 1 }
return ej + "@" + me.i2s(j)
}
if ch == "\"" { return me.parse_string(src, i) }
return me.parse_number(src, i)
}
parse_term(src, i) {
local p = me.parse_factor(src, i)
local at = p.lastIndexOf("@")
local lhs = p.substring(0, at)
local j = me.to_int(p.substring(at+1, p.length()))
local cont = 1
loop(cont == 1) {
j = me.skip_ws(src, j)
if j >= src.length() { cont = 0 } else {
local op = src.substring(j, j+1)
if op != "*" && op != "/" { cont = 0 } else {
local q = me.parse_factor(src, j+1)
local at2 = q.lastIndexOf("@")
local rhs = q.substring(0, at2)
j = me.to_int(q.substring(at2+1, q.length()))
lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}"
}
}
}
return lhs + "@" + me.i2s(j)
}
parse_expr(src, i) {
local p = me.parse_term(src, i)
local at = p.lastIndexOf("@")
local lhs = p.substring(0, at)
local j = me.to_int(p.substring(at+1, p.length()))
local cont = 1
loop(cont == 1) {
j = me.skip_ws(src, j)
if j >= src.length() { cont = 0 } else {
local op = src.substring(j, j+1)
if op != "+" && op != "-" { cont = 0 } else {
local q = me.parse_term(src, j+1)
local at2 = q.lastIndexOf("@")
local rhs = q.substring(0, at2)
j = me.to_int(q.substring(at2+1, q.length()))
lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}"
}
}
}
return lhs + "@" + me.i2s(j)
}
// Parser delegation
parse_program(src) {
// Legacy packed path (debug) removed; use parser2
return me.parse_program2(src)
local parser = new ParserBox()
// Collect using metadata (no-op acceptance in Stage15)
parser.extract_usings(src)
me._usings = parser.get_usings_json()
return parser.parse_program2(src)
}
main(args) {
// Parse the input and emit JSON v0
local src = me.read_all("tmp/ny_parser_input.ny")
if src == null { src = "return 1+2*3" }
local json = me.parse_program(src)
// Debug setup
me.dbg = new DebugBox()
me.dbg.set_enabled(0)
// Source selection (EXE-first friendly)
// - default: safe constant
// - positional arg: treat as input file path
// - --read-tmp: use tmp/ny_parser_input.ny (requires FileBox plugin)
local src = "return 1+2*3"
local read_tmp = 0
local input_path = null
if args != null {
local alen = args.length()
local i = 0
loop(i < alen) {
local a = args.get(i)
if a == "--read-tmp" { read_tmp = 1 } else {
if a == "--min-json" { /* handled later */ } else {
// First non-flag arg as input path
if input_path == null { input_path = a }
}
}
i = i + 1
}
}
if input_path != null {
// Prefer explicit file when provided (requires FileBox plugin)
local s1 = me.read_all(input_path)
if s1 != null { src = s1 }
} else {
if read_tmp == 1 {
// Optional: read tmp/ny_parser_input.ny (requires FileBox plugin; do not use in CI)
local s2 = me.read_all("tmp/ny_parser_input.ny")
if s2 != null { src = s2 }
}
}
// Gate: minimal JSON when requested via script arg
local min_mode = 0
if args != null {
local alen = args.length()
local i = 0
loop(i < alen) {
if args.get(i) == "--min-json" { min_mode = 1 }
i = i + 1
}
}
local json = null
if min_mode == 1 {
json = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":0}}]}"
} else {
json = me.parse_program(src)
}
// Emit via EmitterBox (attach meta.usings when available)
local emitter = new EmitterBox()
json = emitter.emit_program(json, me._usings)
// Output
local console = new ConsoleBox()
// console.println(json) -- final output only
console.println(json)
return 0
}