🔍 Research: GPT-5-Codex capabilities and GitHub PR integration
## Summary
Investigated OpenAI's new GPT-5-Codex model and Codex GitHub PR review integration capabilities.
## GPT-5-Codex Analysis
### Benchmark Performance (Good)
- SWE-bench Verified: 74.5% (vs GPT-5's 72.8%)
- Refactoring tasks: 51.3% (vs GPT-5's 33.9%)
- Code review: Higher developer ratings
### Real-World Issues (Concerning)
- Users report degraded coding performance
- Scripts that previously worked now fail
- Less consistent than GPT-4.5
- Longer response times (minutes vs instant)
- "Creatively and emotionally flat"
- Basic errors (e.g., counting letters incorrectly)
### Key Finding
Classic case of "optimizing for benchmarks vs real usability" - scores well on tests but performs poorly in practice.
## Codex GitHub PR Integration
### Setup Process
1. Enable MFA and connect GitHub account
2. Authorize Codex GitHub app for repos
3. Enable "Code review" in repository settings
### Usage Methods
- **Manual**: Comment '@codex review' in PR
- **Automatic**: Triggers when PR moves from draft to ready
### Current Limitations
- One-way communication (doesn't respond to review comments)
- Prefers creating new PRs over updating existing ones
- Better for single-pass reviews than iterative feedback
## 'codex resume' Feature
New session management capability:
- Resume previous codex exec sessions
- Useful for continuing long tasks across days
- Maintains context from interrupted work
🐱 The investigation reveals that while GPT-5-Codex shows benchmark improvements, practical developer experience has declined - a reminder that metrics don't always reflect real-world utility\!
This commit is contained in:
@ -1,35 +1,44 @@
|
||||
# Current Task — Phase 15 Self‑Hosting (2025‑09‑15)
|
||||
# Current Task — Phase 15 Self‑Hosting (2025‑09‑16)
|
||||
|
||||
TL;DR
|
||||
- 目標は「自己ホスティング達成」= Nyash製パーサで Ny → JSON v0 → Bridge → MIR 実行を安定化すること。
|
||||
- PyVM は意味論の参照実行器(開発補助)。llvmlite は AOT/検証。配布やバンドル化は後回し(基礎固めが先)。
|
||||
|
||||
What Changed (today)
|
||||
- リファクタリング一式 完了(Runner/LLVM/MIR Builder の分割第2弾まで)。機能差分なしで整理のみ。
|
||||
- Phase‑15(自己ホスト)を再開。まずはスモークで挙動を再確認してから警告掃除へ進む方針に切り替え。
|
||||
- 決定: 先にスモーク(PyVM/自己ホスト/Bridge)を回して“緑”を確認→ その後に `ops_ext.rs` と `runner/selfhost.rs` の警告削減に着手する。
|
||||
- 構文糖衣の導入計画(A案)を承認: Stage‑1 で配列リテラル([a,b,c])を追加(IR変更なし、デシュガリング)。
|
||||
- Selfhost 経路の安定化(Python MVP 優先→PyVM 実行)。Selfhost Stage‑2(直/Bridge)スモークは緑化。
|
||||
- Using/Resolver を Runner 前処理に集約し、BoxIndex(グローバル)+解決キャッシュを導入。
|
||||
- nyash.toml の `[aliases]`/env `NYASH_ALIASES` 対応、候補提示、`NYASH_RESOLVE_TRACE=1` でトレース。
|
||||
- strict プレフィクス: `NYASH_PLUGIN_REQUIRE_PREFIX=1` または `[plugins] require_prefix=true`。
|
||||
- per‑plugin meta(`prefix/require_prefix/expose_short_names`)の読取導線を実装(挙動は現状据え置き)。
|
||||
- CLI `--using` を追加(`--using "ns as Alias"` / `--using '"apps/foo.nyash" as Foo'`)。
|
||||
- フィールドは box 先頭のみルールのリンタを Runner に追加(`NYASH_FIELDS_TOP_STRICT=1` でエラー)。
|
||||
- Syntax Torture スイートの実行正規化(末行比較)。一部テスト本文を Nyash 仕様に合わせて修正。
|
||||
|
||||
- llvmlite/AOT(本戦)強化 — コアコレクション配線とエントリ統一
|
||||
- Array/Map の BoxCall を NyRT ハンドルAPIに直結:
|
||||
- Array: `push`→`nyash.array.push_h`、`length/len`→`nyash.any.length_h`
|
||||
- Map: `set`→`nyash.map.set_hh`、`get`→`nyash.map.get_hh`、`has`→`nyash.map.has_hh`、`size`→`nyash.any.length_h`
|
||||
- `ny_main` を i64 戻りに統一し、`Main.main/1` を優先(既定 args は `new ArrayBox()`)。
|
||||
- Core Box 生成の安定化:`nyash.array.birth_h` / `nyash.map.birth_h` を追加し、llvmlite `new` は birth_h を優先。
|
||||
- AOT 実行確認:
|
||||
- `[1,2,3].length()` → `Result: 3`
|
||||
- `{"name":"Alice","age":25}.size()` → `Result: 2`
|
||||
- `m.has("name") ? m.get("name").length() : 0` → `Result: 5`
|
||||
|
||||
|
||||
Quick Next (today)
|
||||
- 短時間スモーク優先(挙動の健全性を早期確認):
|
||||
- `source tools/dev_env.sh pyvm`
|
||||
- `NYASH_VM_USE_PY=1 ./tools/pyvm_stage2_smoke.sh`(参照実行器・意味論確認)
|
||||
- `NYASH_USE_NY_COMPILER=1 ./tools/selfhost_stage2_smoke.sh`(自己ホスト直)
|
||||
- `NYASH_USE_NY_COMPILER=1 ./tools/selfhost_stage2_bridge_smoke.sh`(自己ホスト→JSON→PyVM)
|
||||
- 任意: `./tools/selfhost_stage3_accept_smoke.sh`(Stage‑3 受理のみ確認)
|
||||
- スモークが緑なら、警告の削減に移行:
|
||||
- `src/jit/lower/core/ops_ext.rs`(未使用・到達不能/冗長の解消、保存スロットの一貫化)
|
||||
- `src/runner/selfhost.rs`(到達不能の除去、変数寿命の短縮、細かな `let`/`mut` 是正)
|
||||
- ParserBox 強化(Stage‑2 完了 + Stage‑3 受理を追加)
|
||||
- 進捗ガード(parse_program2/parse_block2/parse_stmt2)。
|
||||
- Stage‑2 受理一式: 単項/二項/比較/論理/呼出/メソッド/引数/if/else/loop/using/local/return/new。
|
||||
- 代入文(identifier = expr)を受理(Stage‑2 は Local に正規化)。
|
||||
- Stage‑3 受理のみ(意味論降下は後続): break/continue/throw/try-catch-finally を no-op/Expr に降格して受理。
|
||||
- Smokes 追加/拡充
|
||||
- Stage‑2: `tools/selfhost_stage2_smoke.sh`(自己ホスト直)・`tools/selfhost_stage2_bridge_smoke.sh`(自己ホスト→JSON→PyVM)。
|
||||
- JSON 固定ベクトル: `tests/json_v0/*.json` と `tools/pyvm_json_vectors_smoke.sh`(arith/if/while/logical/strings 代表)。
|
||||
- 新規 apps/tests: try/finally 合成、短絡+PHI、二重ループ独立性、ループ片側PHI、メソッドチェーン。
|
||||
- Stage‑3 受理確認: `tools/selfhost_stage3_accept_smoke.sh`(受理のみを確認、実行意味論は降格)。
|
||||
- いよいよ「Nyash で書く」段階へ(Self‑Hosting 実装の着手):
|
||||
1) ParserBox 拡張(Stage‑2 の堅牢化・回帰修正)
|
||||
- 算術/比較/論理/呼出/メソッド/if/else/loop/local/return/new の受理を再確認。
|
||||
- 代入文の正規化(`identifier = expr` → Local/Store)。
|
||||
2) EmitterBox 拡張(JSON v0 の安定化)
|
||||
- `meta.usings` の付与一貫化、配列/Map リテラルの後方対応(将来拡張の下地)。
|
||||
3) 自己ホスト経路で Ny 実装切替のゲート準備(現状は Python MVP 優先を維持)。
|
||||
4) テスト:
|
||||
- `source tools/dev_env.sh pyvm`
|
||||
- `NYASH_VM_USE_PY=1 ./tools/selfhost_stage2_smoke.sh`
|
||||
- `NYASH_VM_USE_PY=1 ./tools/selfhost_stage2_bridge_smoke.sh`
|
||||
- Torture(VM中心): `(cd tests/nyash_syntax_torture_20250916 && BACKENDS="vm" NYASH_BIN=../../target/release/nyash bash run_spec_smoke.sh)`
|
||||
- Runner/Bridge 実行系
|
||||
- `--ny-parser-pipe` は `NYASH_PIPE_USE_PYVM=1` で PyVM に委譲(exit code 判定に統一)。
|
||||
- 自己ホスト JSON 生成は Python MVP を優先、LLVM EXE/インラインVMを段階フォールバック。
|
||||
@ -44,15 +53,22 @@ Quick Next (today)
|
||||
|
||||
Current Status
|
||||
- Stage‑2: 自己ホスト → JSON v0 → PyVM の代表スモークは緑(配列/文字列/論理/if/loop)。
|
||||
- Stage‑3: 構文受理のみ完了(break/continue/throw/try/catch/finally)。現時点では JSON 降格(no-op/Expr)で安全受理。
|
||||
- Runner: `--ny-parser-pipe` で PyVM 委譲(exit code 判定)。自己ホスト JSON は Python MVP/EXE/VM の3段フォールバックで生成可能。
|
||||
- Stage‑3: 構文受理のみ完了(break/continue/throw/try/catch/finally)。現時点では JSON 降格(no‑op/Expr)で安全受理。
|
||||
- Runner: Using/Resolver を前処理に統合(BoxIndex/キャッシュ/strict)。`--ny-parser-pipe` は PyVM 委譲(exit code 判定)。
|
||||
- llvmlite/AOT: Array/Map の基本操作(push/get/set/has/size, length)が NyRT ハンドルAPIで動作。`ny_main` は i64 戻り・`Main.main/1` 優先で起動。
|
||||
|
||||
Open
|
||||
- Bridge/PHI の正規化: 短絡(入れ子)における merge/PHI incoming を固定化(rhs_end/fall_bb の順序)。
|
||||
- JSON v0 の拡張方針: break/continue/try/catch/finally の表現(受け皿設計 or 受理時の事前降下)。
|
||||
- per‑plugin meta の反映: `require_prefix/expose_short_names/prefix` を Resolver 挙動へ段階適用(導線は実装済み)。
|
||||
- `me` の扱い: MVP は `NYASH_BRIDGE_ME_DUMMY=1` の仮注入を継続(将来撤去)。
|
||||
- LLVM 直結(任意): JSON v0 → LLVM の導線追加は後回し。
|
||||
|
||||
- NyRT 整頓:
|
||||
- FFI ヘルパー化(handles/boxing 正規化)/birth_h→new_i64x 統合/Core Box のプラグイン事前登録/FFI エクスポートのマクロ化。
|
||||
- llvmlite 整頓:
|
||||
- boxcall のテーブル駆動化、追加 API(delete/keys/values など)の段階配線。
|
||||
|
||||
Plan (to Self‑Hosting)
|
||||
1) Phase‑1: Stage‑2 完了+堅牢化(今ここ)
|
||||
- 正常系スモークを自己ホスト直/Bridge(PyVM)で常緑化(追加分を反映済み)。
|
||||
@ -157,3 +173,7 @@ Namespaces / Using(現状)
|
||||
- `NYASH_RESOLVE_TRACE=1`: 解決手順/キャッシュヒット/未解決候補をログ出力。
|
||||
|
||||
- スモークが緑=基礎健全性確認後に、静的ノイズの除去を安全に一気通貫で行う。
|
||||
|
||||
**AOT Quick**
|
||||
- Array literal: `NYASH_SYNTAX_SUGAR_LEVEL=basic ./tools/build_llvm.sh tmp/aot_array_literal_main.nyash -o app && ./app`
|
||||
- Map literal: `NYASH_SYNTAX_SUGAR_LEVEL=basic NYASH_ENABLE_MAP_LITERAL=1 ./tools/build_llvm.sh tmp/aot_map_literal_main.nyash -o app && ./app`
|
||||
|
||||
BIN
app_alit_print
Normal file
BIN
app_alit_print
Normal file
Binary file not shown.
BIN
app_alit_verbose
Normal file
BIN
app_alit_verbose
Normal file
Binary file not shown.
BIN
app_mlit_verbose
Normal file
BIN
app_mlit_verbose
Normal file
Binary file not shown.
@ -229,7 +229,160 @@ box ParserBox {
|
||||
parse_number2(src, i) { local n = src.length() local j = i local cont = 1 local guard = 0 local max = 100000 loop(cont == 1) { if guard > max { cont = 0 } else { guard = guard + 1 if j < n { if me.is_digit(src.substring(j, j+1)) { j = j + 1 } else { cont = 0 } } else { cont = 0 } } } 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 guard = 0 local max = 200000 loop(j < n) { if guard > max { break } guard = guard + 1 local ch = src.substring(j, j+1) if ch == "\"" { j = j + 1 me.gpos_set(j) return "{\"type\":\"Str\",\"value\":\"" + me.esc_json(out) + "\"}" } 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) } if me.starts_with_kw(src, j, "true") == 1 { me.gpos_set(j + 4) return "{\"type\":\"Bool\",\"value\":true}" } if me.starts_with_kw(src, j, "false") == 1 { me.gpos_set(j + 5) return "{\"type\":\"Bool\",\"value\":false}" } if me.starts_with_kw(src, j, "new") == 1 { local p = me.skip_ws(src, j + 3) local idp = me.read_ident2(src, p) local at = idp.lastIndexOf("@") local cls = idp.substring(0, at) local k = me.to_int(idp.substring(at+1, idp.length())) k = me.skip_ws(src, k) if src.substring(k, k+1) == "(" { k = k + 1 } local args_and_pos = me.parse_args2(src, k) local at2 = args_and_pos.lastIndexOf("@") local args_json = args_and_pos.substring(0, at2) k = me.to_int(args_and_pos.substring(at2+1, args_and_pos.length())) k = me.skip_ws(src, k) if src.substring(k, k+1) == ")" { k = k + 1 } me.gpos_set(k) return "{\"type\":\"New\",\"class\":\"" + cls + "\",\"args\":" + args_json + "}" } if me.is_alpha(ch) { local idp = me.read_ident2(src, j) local at = idp.lastIndexOf("@") local name = idp.substring(0, at) local k = me.to_int(idp.substring(at+1, idp.length())) local node = "{\"type\":\"Var\",\"name\":\"" + name + "\"}" local cont = 1 loop(cont == 1) { k = me.skip_ws(src, k) local tch = src.substring(k, k+1) if tch == "(" { k = k + 1 local args_and_pos = me.parse_args2(src, k) local at2 = args_and_pos.lastIndexOf("@") local args_json = args_and_pos.substring(0, at2) k = me.to_int(args_and_pos.substring(at2+1, args_and_pos.length())) k = me.skip_ws(src, k) if src.substring(k, k+1) == ")" { k = k + 1 } node = "{\"type\":\"Call\",\"name\":\"" + name + "\",\"args\":" + args_json + "}" } else { if tch == "." { k = k + 1 k = me.skip_ws(src, k) local midp = me.read_ident2(src, k) local at3 = midp.lastIndexOf("@") local mname = midp.substring(0, at3) k = me.to_int(midp.substring(at3+1, midp.length())) k = me.skip_ws(src, k) if src.substring(k, k+1) == "(" { k = k + 1 } local args2 = me.parse_args2(src, k) local at4 = args2.lastIndexOf("@") local args_json2 = args2.substring(0, at4) k = me.to_int(args2.substring(at4+1, args2.length())) k = me.skip_ws(src, k) if src.substring(k, k+1) == ")" { k = k + 1 } node = "{\"type\":\"Method\",\"recv\":" + node + ",\"method\":\"" + mname + "\",\"args\":" + args_json2 + "}" } else { cont = 0 } } } me.gpos_set(k) return node } return me.parse_number2(src, j) }
|
||||
parse_factor2(src, i) {
|
||||
local j = me.skip_ws(src, i)
|
||||
local ch = src.substring(j, j+1)
|
||||
// Parenthesized
|
||||
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
|
||||
}
|
||||
// String literal
|
||||
if ch == "\"" { return me.parse_string2(src, j) }
|
||||
// Map literal: {"k": v, ...} (string keys only) → Call{name:"map.of", args:[Str(k1), v1, Str(k2), v2, ...]}
|
||||
if ch == "{" {
|
||||
local n = src.length()
|
||||
j = j + 1
|
||||
local out = "["
|
||||
local first = 1
|
||||
local cont = 1
|
||||
local guard = 0
|
||||
local max = 400000
|
||||
loop(cont == 1) {
|
||||
if guard > max { cont = 0 } else { guard = guard + 1 }
|
||||
j = me.skip_ws(src, j)
|
||||
if j >= n { cont = 0 } else {
|
||||
if src.substring(j, j+1) == "}" { j = j + 1 cont = 0 } else {
|
||||
// key (string only for Stage-2)
|
||||
if src.substring(j, j+1) != "\"" {
|
||||
// degrade by skipping one char to avoid infinite loop
|
||||
j = j + 1
|
||||
continue
|
||||
}
|
||||
local key_raw = me.read_string_lit(src, j)
|
||||
j = me.gpos_get()
|
||||
j = me.skip_ws(src, j)
|
||||
if src.substring(j, j+1) == ":" { j = j + 1 }
|
||||
j = me.skip_ws(src, j)
|
||||
local val_json = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
local key_json = "{\"type\":\"Str\",\"value\":\"" + me.esc_json(key_raw) + "\"}"
|
||||
if first == 1 { out = out + key_json + "," + val_json first = 0 } else { out = out + "," + key_json + "," + val_json }
|
||||
// optional comma
|
||||
local before2 = j
|
||||
j = me.skip_ws(src, j)
|
||||
if j < n && src.substring(j, j+1) == "," { j = j + 1 }
|
||||
// progress guard (in case of malformed input)
|
||||
if j <= before2 { if j < n { j = j + 1 } else { j = n } }
|
||||
}
|
||||
}
|
||||
}
|
||||
out = out + "]"
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Call\",\"name\":\"map.of\",\"args\":" + out + "}"
|
||||
}
|
||||
// Array literal: [e1, e2, ...] → Call{name:"array.of", args:[...]}
|
||||
if ch == "[" {
|
||||
local n = src.length()
|
||||
j = j + 1
|
||||
local out = "["
|
||||
local first = 1
|
||||
local cont = 1
|
||||
local guard = 0
|
||||
local max = 400000
|
||||
loop(cont == 1) {
|
||||
if guard > max { cont = 0 } else { guard = guard + 1 }
|
||||
j = me.skip_ws(src, j)
|
||||
if j >= n { cont = 0 } else {
|
||||
if src.substring(j, j+1) == "]" { j = j + 1 cont = 0 } else {
|
||||
local before = j
|
||||
local ej = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
if first == 1 { out = out + ej first = 0 } else { out = out + "," + ej }
|
||||
// optional comma+whitespace
|
||||
local before2 = j
|
||||
j = me.skip_ws(src, j)
|
||||
if j < n && src.substring(j, j+1) == "," { j = j + 1 }
|
||||
// progress guard
|
||||
if j <= before { if j < n { j = j + 1 } else { j = n } }
|
||||
}
|
||||
}
|
||||
}
|
||||
out = out + "]"
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Call\",\"name\":\"array.of\",\"args\":" + out + "}"
|
||||
}
|
||||
// true/false
|
||||
if me.starts_with_kw(src, j, "true") == 1 { me.gpos_set(j + 4) return "{\"type\":\"Bool\",\"value\":true}" }
|
||||
if me.starts_with_kw(src, j, "false") == 1 { me.gpos_set(j + 5) return "{\"type\":\"Bool\",\"value\":false}" }
|
||||
// new Class(args)
|
||||
if me.starts_with_kw(src, j, "new") == 1 {
|
||||
local p = me.skip_ws(src, j + 3)
|
||||
local idp = me.read_ident2(src, p)
|
||||
local at = idp.lastIndexOf("@")
|
||||
local cls = idp.substring(0, at)
|
||||
local k = me.to_int(idp.substring(at+1, idp.length()))
|
||||
k = me.skip_ws(src, k)
|
||||
if src.substring(k, k+1) == "(" { k = k + 1 }
|
||||
local args_and_pos = me.parse_args2(src, k)
|
||||
local at2 = args_and_pos.lastIndexOf("@")
|
||||
local args_json = args_and_pos.substring(0, at2)
|
||||
k = me.to_int(args_and_pos.substring(at2+1, args_and_pos.length()))
|
||||
k = me.skip_ws(src, k)
|
||||
if src.substring(k, k+1) == ")" { k = k + 1 }
|
||||
me.gpos_set(k)
|
||||
return "{\"type\":\"New\",\"class\":\"" + cls + "\",\"args\":" + args_json + "}"
|
||||
}
|
||||
// Identifier / Call / Method chain
|
||||
if me.is_alpha(ch) {
|
||||
local idp = me.read_ident2(src, j)
|
||||
local at = idp.lastIndexOf("@")
|
||||
local name = idp.substring(0, at)
|
||||
local k = me.to_int(idp.substring(at+1, idp.length()))
|
||||
local node = "{\"type\":\"Var\",\"name\":\"" + name + "\"}"
|
||||
local cont2 = 1
|
||||
loop(cont2 == 1) {
|
||||
k = me.skip_ws(src, k)
|
||||
local tch = src.substring(k, k+1)
|
||||
if tch == "(" {
|
||||
k = k + 1
|
||||
local args_and_pos = me.parse_args2(src, k)
|
||||
local at2 = args_and_pos.lastIndexOf("@")
|
||||
local args_json = args_and_pos.substring(0, at2)
|
||||
k = me.to_int(args_and_pos.substring(at2+1, args_and_pos.length()))
|
||||
k = me.skip_ws(src, k)
|
||||
if src.substring(k, k+1) == ")" { k = k + 1 }
|
||||
node = "{\"type\":\"Call\",\"name\":\"" + name + "\",\"args\":" + args_json + "}"
|
||||
} else {
|
||||
if tch == "." {
|
||||
k = k + 1
|
||||
k = me.skip_ws(src, k)
|
||||
local midp = me.read_ident2(src, k)
|
||||
local at3 = midp.lastIndexOf("@")
|
||||
local mname = midp.substring(0, at3)
|
||||
k = me.to_int(midp.substring(at3+1, midp.length()))
|
||||
k = me.skip_ws(src, k)
|
||||
if src.substring(k, k+1) == "(" { k = k + 1 }
|
||||
local args2 = me.parse_args2(src, k)
|
||||
local at4 = args2.lastIndexOf("@")
|
||||
local args_json2 = args2.substring(0, at4)
|
||||
k = me.to_int(args2.substring(at4+1, args2.length()))
|
||||
k = me.skip_ws(src, k)
|
||||
if src.substring(k, k+1) == ")" { k = k + 1 }
|
||||
node = "{\"type\":\"Method\",\"recv\":" + node + ",\"method\":\"" + mname + "\",\"args\":" + args_json2 + "}"
|
||||
} else { cont2 = 0 }
|
||||
}
|
||||
}
|
||||
me.gpos_set(k)
|
||||
return node
|
||||
}
|
||||
// Fallback: number
|
||||
return me.parse_number2(src, j)
|
||||
}
|
||||
// unary minus binds tighter than * /
|
||||
parse_unary2(src, i) {
|
||||
local j = me.skip_ws(src, i)
|
||||
@ -392,6 +545,73 @@ box ParserBox {
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Loop\",\"cond\":" + cond + ",\"body\":" + body_json + "}"
|
||||
}
|
||||
// Stage-3 acceptance (syntax only): break / continue → no-op expression
|
||||
if me.starts_with_kw(src, j, "break") == 1 {
|
||||
j = j + 5
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Expr\",\"expr\":{\"type\":\"Int\",\"value\":0}}"
|
||||
}
|
||||
if me.starts_with_kw(src, j, "continue") == 1 {
|
||||
j = j + 8
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Expr\",\"expr\":{\"type\":\"Int\",\"value\":0}}"
|
||||
}
|
||||
// Stage-3 acceptance: throw expr → degrade to Expr(expr)
|
||||
if me.starts_with_kw(src, j, "throw") == 1 {
|
||||
j = j + 5
|
||||
j = me.skip_ws(src, j)
|
||||
local e_throw = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Expr\",\"expr\":" + e_throw + "}"
|
||||
}
|
||||
// Stage-3 acceptance: try { ... } (catch ...)* (finally { ... })? → degrade to no-op (syntax only)
|
||||
if me.starts_with_kw(src, j, "try") == 1 {
|
||||
j = j + 3
|
||||
j = me.skip_ws(src, j)
|
||||
// parse try block
|
||||
local try_res = me.parse_block2(src, j)
|
||||
local at_t = try_res.lastIndexOf("@")
|
||||
j = me.to_int(try_res.substring(at_t+1, try_res.length()))
|
||||
// zero or more catch
|
||||
local guard_ct = 0
|
||||
local max_ct = 100
|
||||
local cont_ct = 1
|
||||
loop(cont_ct == 1) {
|
||||
if guard_ct > max_ct { cont_ct = 0 } else { guard_ct = guard_ct + 1 }
|
||||
j = me.skip_ws(src, j)
|
||||
if me.starts_with_kw(src, j, "catch") == 1 {
|
||||
j = j + 5
|
||||
j = me.skip_ws(src, j)
|
||||
if src.substring(j, j+1) == "(" { j = j + 1 j = me.skip_ws(src, j)
|
||||
// optional type + name
|
||||
if me.is_alpha(src.substring(j, j+1)) { local id1 = me.read_ident2(src, j) local at1 = id1.lastIndexOf("@") j = me.to_int(id1.substring(at1+1, id1.length())) j = me.skip_ws(src, j) }
|
||||
if me.is_alpha(src.substring(j, j+1)) { local id2 = me.read_ident2(src, j) local at2 = id2.lastIndexOf("@") j = me.to_int(id2.substring(at2+1, id2.length())) j = me.skip_ws(src, j) }
|
||||
if src.substring(j, j+1) == ")" { j = j + 1 }
|
||||
}
|
||||
j = me.skip_ws(src, j)
|
||||
// catch body
|
||||
local c_res = me.parse_block2(src, j)
|
||||
local atc = c_res.lastIndexOf("@")
|
||||
j = me.to_int(c_res.substring(atc+1, c_res.length()))
|
||||
} else { cont_ct = 0 }
|
||||
}
|
||||
// optional finally
|
||||
j = me.skip_ws(src, j)
|
||||
if me.starts_with_kw(src, j, "finally") == 1 {
|
||||
j = j + 7
|
||||
j = me.skip_ws(src, j)
|
||||
local f_res = me.parse_block2(src, j)
|
||||
local atf = f_res.lastIndexOf("@")
|
||||
j = me.to_int(f_res.substring(atf+1, f_res.length()))
|
||||
}
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Expr\",\"expr\":{\"type\":\"Int\",\"value\":0}}"
|
||||
}
|
||||
// Fallback: expression or unknown token — ensure progress even on malformed input
|
||||
local expr_start = j
|
||||
local e = me.parse_expr2(src, j)
|
||||
@ -473,70 +693,3 @@ box ParserBox {
|
||||
}
|
||||
|
||||
static box ParserStub { main(args) { return 0 } }
|
||||
// Stage-3 acceptance (syntax only): break / continue → no-op expression
|
||||
if me.starts_with_kw(src, j, "break") == 1 {
|
||||
j = j + 5
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Expr\",\"expr\":{\"type\":\"Int\",\"value\":0}}"
|
||||
}
|
||||
if me.starts_with_kw(src, j, "continue") == 1 {
|
||||
j = j + 8
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Expr\",\"expr\":{\"type\":\"Int\",\"value\":0}}"
|
||||
}
|
||||
// Stage-3 acceptance: throw expr → degrade to Expr(expr)
|
||||
if me.starts_with_kw(src, j, "throw") == 1 {
|
||||
j = j + 5
|
||||
j = me.skip_ws(src, j)
|
||||
local e_throw = me.parse_expr2(src, j)
|
||||
j = me.gpos_get()
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Expr\",\"expr\":" + e_throw + "}"
|
||||
}
|
||||
// Stage-3 acceptance: try { ... } (catch ...)* (finally { ... })? → degrade to no-op (syntax only)
|
||||
if me.starts_with_kw(src, j, "try") == 1 {
|
||||
j = j + 3
|
||||
j = me.skip_ws(src, j)
|
||||
// parse try block
|
||||
local try_res = me.parse_block2(src, j)
|
||||
local at_t = try_res.lastIndexOf("@")
|
||||
j = me.to_int(try_res.substring(at_t+1, try_res.length()))
|
||||
// zero or more catch
|
||||
local guard_ct = 0
|
||||
local max_ct = 100
|
||||
local cont_ct = 1
|
||||
loop(cont_ct == 1) {
|
||||
if guard_ct > max_ct { cont_ct = 0 } else { guard_ct = guard_ct + 1 }
|
||||
j = me.skip_ws(src, j)
|
||||
if me.starts_with_kw(src, j, "catch") == 1 {
|
||||
j = j + 5
|
||||
j = me.skip_ws(src, j)
|
||||
if src.substring(j, j+1) == "(" { j = j + 1 j = me.skip_ws(src, j)
|
||||
// optional type + name
|
||||
if me.is_alpha(src.substring(j, j+1)) { local id1 = me.read_ident2(src, j) local at1 = id1.lastIndexOf("@") j = me.to_int(id1.substring(at1+1, id1.length())) j = me.skip_ws(src, j) }
|
||||
if me.is_alpha(src.substring(j, j+1)) { local id2 = me.read_ident2(src, j) local at2 = id2.lastIndexOf("@") j = me.to_int(id2.substring(at2+1, id2.length())) j = me.skip_ws(src, j) }
|
||||
if src.substring(j, j+1) == ")" { j = j + 1 }
|
||||
}
|
||||
j = me.skip_ws(src, j)
|
||||
// catch body
|
||||
local c_res = me.parse_block2(src, j)
|
||||
local atc = c_res.lastIndexOf("@")
|
||||
j = me.to_int(c_res.substring(atc+1, c_res.length()))
|
||||
} else { cont_ct = 0 }
|
||||
}
|
||||
// optional finally
|
||||
j = me.skip_ws(src, j)
|
||||
if me.starts_with_kw(src, j, "finally") == 1 {
|
||||
j = j + 7
|
||||
j = me.skip_ws(src, j)
|
||||
local f_res = me.parse_block2(src, j)
|
||||
local atf = f_res.lastIndexOf("@")
|
||||
j = me.to_int(f_res.substring(atf+1, f_res.length()))
|
||||
}
|
||||
if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } }
|
||||
me.gpos_set(j)
|
||||
return "{\"type\":\"Expr\",\"expr\":{\"type\":\"Int\",\"value\":0}}"
|
||||
}
|
||||
|
||||
@ -545,6 +545,23 @@ pub extern "C" fn nyash_console_birth_h_export() -> i64 {
|
||||
0
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ArrayBox birth shim for AOT/JIT handle-based creation
|
||||
#[export_name = "nyash.array.birth_h"]
|
||||
pub extern "C" fn nyash_array_birth_h_export() -> i64 {
|
||||
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> =
|
||||
std::sync::Arc::new(nyash_rust::boxes::array::ArrayBox::new());
|
||||
nyash_rust::jit::rt::handles::to_handle(arc) as i64
|
||||
}
|
||||
|
||||
// MapBox birth shim for AOT/JIT handle-based creation
|
||||
#[export_name = "nyash.map.birth_h"]
|
||||
pub extern "C" fn nyash_map_birth_h_export() -> i64 {
|
||||
let arc: std::sync::Arc<dyn nyash_rust::box_trait::NyashBox> =
|
||||
std::sync::Arc::new(nyash_rust::boxes::map_box::MapBox::new());
|
||||
nyash_rust::jit::rt::handles::to_handle(arc) as i64
|
||||
}
|
||||
// ---- Process entry (driver) ----
|
||||
#[cfg(not(test))]
|
||||
#[no_mangle]
|
||||
|
||||
@ -63,17 +63,15 @@ pub extern "C" fn nyash_map_get_h(handle: i64, key: i64) -> i64 {
|
||||
|
||||
// get_hh: (map_handle, key_handle) -> value_handle
|
||||
#[export_name = "nyash.map.get_hh"]
|
||||
pub extern "C" fn nyash_map_get_hh(handle: i64, key_h: i64) -> i64 {
|
||||
use nyash_rust::{box_trait::NyashBox, jit::rt::handles};
|
||||
if handle <= 0 || key_h <= 0 {
|
||||
return 0;
|
||||
}
|
||||
if let (Some(obj), Some(key)) = (handles::get(handle as u64), handles::get(key_h as u64)) {
|
||||
if let Some(map) = obj
|
||||
.as_any()
|
||||
.downcast_ref::<nyash_rust::boxes::map_box::MapBox>()
|
||||
{
|
||||
let v = map.get(key.clone_box());
|
||||
pub extern "C" fn nyash_map_get_hh(handle: i64, key_any: i64) -> i64 {
|
||||
use nyash_rust::{box_trait::{NyashBox, IntegerBox}, jit::rt::handles};
|
||||
if handle <= 0 { return 0; }
|
||||
if let Some(obj) = handles::get(handle as u64) {
|
||||
if let Some(map) = obj.as_any().downcast_ref::<nyash_rust::boxes::map_box::MapBox>() {
|
||||
let key_box: Box<dyn NyashBox> = if key_any > 0 {
|
||||
if let Some(k) = handles::get(key_any as u64) { k.clone_box() } else { Box::new(IntegerBox::new(key_any)) }
|
||||
} else { Box::new(IntegerBox::new(key_any)) };
|
||||
let v = map.get(key_box);
|
||||
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::from(v);
|
||||
let h = handles::to_handle(arc);
|
||||
return h as i64;
|
||||
@ -82,6 +80,7 @@ pub extern "C" fn nyash_map_get_hh(handle: i64, key_h: i64) -> i64 {
|
||||
0
|
||||
}
|
||||
|
||||
|
||||
// set_h: (map_handle, key_i64, val) -> i64 (ignored/0)
|
||||
#[export_name = "nyash.map.set_h"]
|
||||
pub extern "C" fn nyash_map_set_h(handle: i64, key: i64, val: i64) -> i64 {
|
||||
@ -126,6 +125,44 @@ pub extern "C" fn nyash_map_set_h(handle: i64, key: i64, val: i64) -> i64 {
|
||||
0
|
||||
}
|
||||
|
||||
|
||||
// set_hh: (map_handle, key_any: handle or i64, val_any: handle or i64) -> i64
|
||||
#[export_name = "nyash.map.set_hh"]
|
||||
pub extern "C" fn nyash_map_set_hh(handle: i64, key_any: i64, val_any: i64) -> i64 {
|
||||
use nyash_rust::{box_trait::{NyashBox, IntegerBox}, jit::rt::handles};
|
||||
if handle <= 0 { return 0; }
|
||||
if let Some(obj) = handles::get(handle as u64) {
|
||||
if let Some(map) = obj.as_any().downcast_ref::<nyash_rust::boxes::map_box::MapBox>() {
|
||||
let kbox: Box<dyn NyashBox> = if key_any > 0 {
|
||||
if let Some(k) = handles::get(key_any as u64) { k.clone_box() } else { Box::new(IntegerBox::new(key_any)) }
|
||||
} else { Box::new(IntegerBox::new(key_any)) };
|
||||
let vbox: Box<dyn NyashBox> = if val_any > 0 {
|
||||
if let Some(v) = handles::get(val_any as u64) { v.clone_box() } else { Box::new(IntegerBox::new(val_any)) }
|
||||
} else { Box::new(IntegerBox::new(val_any)) };
|
||||
let _ = map.set(kbox, vbox);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
// has_hh: (map_handle, key_any: handle or i64) -> i64 (0/1)
|
||||
#[export_name = "nyash.map.has_hh"]
|
||||
pub extern "C" fn nyash_map_has_hh(handle: i64, key_any: i64) -> i64 {
|
||||
use nyash_rust::{box_trait::{NyashBox, IntegerBox, BoolBox}, jit::rt::handles};
|
||||
if handle <= 0 { return 0; }
|
||||
if let Some(obj) = handles::get(handle as u64) {
|
||||
if let Some(map) = obj.as_any().downcast_ref::<nyash_rust::boxes::map_box::MapBox>() {
|
||||
let kbox: Box<dyn NyashBox> = if key_any > 0 {
|
||||
if let Some(k) = handles::get(key_any as u64) { k.clone_box() } else { Box::new(IntegerBox::new(key_any)) }
|
||||
} else { Box::new(IntegerBox::new(key_any)) };
|
||||
let v = map.has(kbox);
|
||||
if let Some(b) = v.as_any().downcast_ref::<BoolBox>() { return if b.value { 1 } else { 0 }; }
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
// has_h: (map_handle, key_i64) -> i64 (0/1)
|
||||
#[export_name = "nyash.map.has_h"]
|
||||
pub extern "C" fn nyash_map_has_h(handle: i64, key: i64) -> i64 {
|
||||
@ -139,10 +176,8 @@ pub extern "C" fn nyash_map_has_h(handle: i64, key: i64) -> i64 {
|
||||
.downcast_ref::<nyash_rust::boxes::map_box::MapBox>()
|
||||
{
|
||||
let kbox = Box::new(IntegerBox::new(key));
|
||||
let v = map.get(kbox);
|
||||
// Consider present if not VoidBox
|
||||
let present = !v.as_any().is::<nyash_rust::box_trait::VoidBox>();
|
||||
return if present { 1 } else { 0 };
|
||||
let v = map.has(kbox);
|
||||
if let Some(b) = v.as_any().downcast_ref::<nyash_rust::box_trait::BoolBox>() { return if b.value { 1 } else { 0 }; }
|
||||
}
|
||||
}
|
||||
0
|
||||
|
||||
@ -84,6 +84,15 @@ def lower_boxcall(
|
||||
vmap[dst_vid] = result
|
||||
return
|
||||
|
||||
if method_name == "size":
|
||||
# Map/Array size via any.length_h
|
||||
recv_h = _ensure_handle(builder, module, recv_val)
|
||||
callee = _declare(module, "nyash.any.length_h", i64, [i64])
|
||||
result = builder.call(callee, [recv_h], name="any_size_h")
|
||||
if dst_vid is not None:
|
||||
vmap[dst_vid] = result
|
||||
return
|
||||
|
||||
if method_name == "substring":
|
||||
# substring(start, end)
|
||||
# If receiver is a handle (i64), use handle-based helper; else pointer-based API
|
||||
@ -180,23 +189,60 @@ def lower_boxcall(
|
||||
|
||||
if method_name == "get":
|
||||
# ArrayBox.get(index) → nyash.array.get_h(handle, idx)
|
||||
# MapBox.get(key) → nyash.map.get_hh(handle, key_any)
|
||||
recv_h = _ensure_handle(builder, module, recv_val)
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
idx = resolver.resolve_i64(args[0], builder.block, preds, block_end_values, vmap, bb_map) if args else ir.Constant(i64, 0)
|
||||
k = resolver.resolve_i64(args[0], builder.block, preds, block_end_values, vmap, bb_map) if args else ir.Constant(i64, 0)
|
||||
else:
|
||||
idx = vmap.get(args[0], ir.Constant(i64, 0)) if args else ir.Constant(i64, 0)
|
||||
callee = _declare(module, "nyash.array.get_h", i64, [i64, i64])
|
||||
res = builder.call(callee, [recv_h, idx], name="arr_get_h")
|
||||
k = vmap.get(args[0], ir.Constant(i64, 0)) if args else ir.Constant(i64, 0)
|
||||
callee_map = _declare(module, "nyash.map.get_hh", i64, [i64, i64])
|
||||
res = builder.call(callee_map, [recv_h, k], name="map_get_hh")
|
||||
if dst_vid is not None:
|
||||
vmap[dst_vid] = res
|
||||
try:
|
||||
if resolver is not None and hasattr(resolver, 'mark_string'):
|
||||
# Heuristic: args array often stores strings for CLI; tag as string-ish
|
||||
resolver.mark_string(dst_vid)
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
if method_name == "push":
|
||||
# ArrayBox.push(val) → nyash.array.push_h(handle, val)
|
||||
recv_h = _ensure_handle(builder, module, recv_val)
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
v0 = resolver.resolve_i64(args[0], builder.block, preds, block_end_values, vmap, bb_map) if args else ir.Constant(i64, 0)
|
||||
else:
|
||||
v0 = vmap.get(args[0], ir.Constant(i64, 0)) if args else ir.Constant(i64, 0)
|
||||
callee = _declare(module, "nyash.array.push_h", i64, [i64, i64])
|
||||
res = builder.call(callee, [recv_h, v0], name="arr_push_h")
|
||||
if dst_vid is not None:
|
||||
vmap[dst_vid] = res
|
||||
return
|
||||
|
||||
if method_name == "set":
|
||||
# MapBox.set(key, val) → nyash.map.set_hh(handle, key_any, val_any)
|
||||
recv_h = _ensure_handle(builder, module, recv_val)
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
k = resolver.resolve_i64(args[0], builder.block, preds, block_end_values, vmap, bb_map) if len(args) > 0 else ir.Constant(i64, 0)
|
||||
v = resolver.resolve_i64(args[1], builder.block, preds, block_end_values, vmap, bb_map) if len(args) > 1 else ir.Constant(i64, 0)
|
||||
else:
|
||||
k = vmap.get(args[0], ir.Constant(i64, 0)) if len(args) > 0 else ir.Constant(i64, 0)
|
||||
v = vmap.get(args[1], ir.Constant(i64, 0)) if len(args) > 1 else ir.Constant(i64, 0)
|
||||
callee = _declare(module, "nyash.map.set_hh", i64, [i64, i64, i64])
|
||||
res = builder.call(callee, [recv_h, k, v], name="map_set_hh")
|
||||
if dst_vid is not None:
|
||||
vmap[dst_vid] = res
|
||||
return
|
||||
|
||||
if method_name == "has":
|
||||
# MapBox.has(key) → nyash.map.has_hh(handle, key_any)
|
||||
recv_h = _ensure_handle(builder, module, recv_val)
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
k = resolver.resolve_i64(args[0], builder.block, preds, block_end_values, vmap, bb_map) if args else ir.Constant(i64, 0)
|
||||
else:
|
||||
k = vmap.get(args[0], ir.Constant(i64, 0)) if args else ir.Constant(i64, 0)
|
||||
callee = _declare(module, "nyash.map.has_hh", i64, [i64, i64])
|
||||
res = builder.call(callee, [recv_h, k], name="map_has_hh")
|
||||
if dst_vid is not None:
|
||||
vmap[dst_vid] = res
|
||||
return
|
||||
|
||||
|
||||
if method_name in ("print", "println", "log"):
|
||||
# Console mapping (prefer pointer-API when possible to avoid handle registry mismatch)
|
||||
use_ptr = False
|
||||
|
||||
@ -29,9 +29,22 @@ def lower_newbox(
|
||||
vmap: Value map
|
||||
resolver: Optional resolver for type handling
|
||||
"""
|
||||
# Use NyRT shim: nyash.env.box.new(type_name: i8*) -> i64
|
||||
# Use NyRT shim: prefer birth_h for core boxes, otherwise env.box.new_i64x
|
||||
i64 = ir.IntType(64)
|
||||
i8p = ir.IntType(8).as_pointer()
|
||||
# Core fast paths
|
||||
if box_type in ("ArrayBox", "MapBox"):
|
||||
birth_name = "nyash.array.birth_h" if box_type == "ArrayBox" else "nyash.map.birth_h"
|
||||
birth = None
|
||||
for f in module.functions:
|
||||
if f.name == birth_name:
|
||||
birth = f
|
||||
break
|
||||
if not birth:
|
||||
birth = ir.Function(module, ir.FunctionType(i64, []), name=birth_name)
|
||||
handle = builder.call(birth, [], name=f"birth_{box_type}")
|
||||
vmap[dst_vid] = handle
|
||||
return
|
||||
# Prefer variadic shim: nyash.env.box.new_i64x(type_name, argc, a1, a2, a3, a4)
|
||||
new_i64x = None
|
||||
for f in module.functions:
|
||||
|
||||
@ -110,35 +110,64 @@ class NyashLLVMBuilder:
|
||||
|
||||
# Create ny_main wrapper if necessary
|
||||
has_ny_main = any(f.name == 'ny_main' for f in self.module.functions)
|
||||
main_fn = None
|
||||
# Prefer static box entry: Main.main/1; fallback to plain main (0-arity)
|
||||
fn_main_box = None
|
||||
fn_main_plain = None
|
||||
for f in self.module.functions:
|
||||
if f.name == 'main':
|
||||
main_fn = f
|
||||
break
|
||||
if main_fn is not None:
|
||||
# Hide the user main to avoid conflict with NyRT's main symbol
|
||||
if f.name == 'Main.main/1':
|
||||
fn_main_box = f
|
||||
elif f.name == 'main':
|
||||
fn_main_plain = f
|
||||
target_fn = fn_main_box or fn_main_plain
|
||||
if target_fn is not None and not has_ny_main:
|
||||
# Hide the target to avoid symbol conflicts
|
||||
try:
|
||||
main_fn.linkage = 'private'
|
||||
target_fn.linkage = 'private'
|
||||
except Exception:
|
||||
pass
|
||||
if not has_ny_main:
|
||||
# i32 ny_main() { return (i32) main(); }
|
||||
ny_main_ty = ir.FunctionType(self.i32, [])
|
||||
ny_main = ir.Function(self.module, ny_main_ty, name='ny_main')
|
||||
entry = ny_main.append_basic_block('entry')
|
||||
b = ir.IRBuilder(entry)
|
||||
if len(main_fn.args) == 0:
|
||||
rv = b.call(main_fn, [], name='call_user_main')
|
||||
# i32 ny_main() { return (i32) Main.main(args) | main(); }
|
||||
ny_main_ty = ir.FunctionType(self.i64, [])
|
||||
ny_main = ir.Function(self.module, ny_main_ty, name='ny_main')
|
||||
entry = ny_main.append_basic_block('entry')
|
||||
b = ir.IRBuilder(entry)
|
||||
if fn_main_box is not None:
|
||||
# Build default args = new ArrayBox() via nyash.env.box.new_i64x
|
||||
i64 = self.i64
|
||||
i8 = self.i8
|
||||
i8p = self.i8p
|
||||
# Declare callee
|
||||
callee = None
|
||||
for f in self.module.functions:
|
||||
if f.name == 'nyash.env.box.new_i64x':
|
||||
callee = f
|
||||
break
|
||||
if callee is None:
|
||||
callee = ir.Function(self.module, ir.FunctionType(i64, [i8p, i64, i64, i64, i64, i64]), name='nyash.env.box.new_i64x')
|
||||
# Create "ArrayBox\0" global
|
||||
sbytes = b"ArrayBox\0"
|
||||
arr_ty = ir.ArrayType(i8, len(sbytes))
|
||||
g = ir.GlobalVariable(self.module, arr_ty, name='.ny_main_arraybox')
|
||||
g.linkage = 'private'
|
||||
g.global_constant = True
|
||||
g.initializer = ir.Constant(arr_ty, bytearray(sbytes))
|
||||
c0 = ir.Constant(self.i32, 0)
|
||||
ptr = b.gep(g, [c0, c0], inbounds=True)
|
||||
zero = ir.Constant(i64, 0)
|
||||
args_handle = b.call(callee, [ptr, zero, zero, zero, zero, zero], name='ny_main_args')
|
||||
rv = b.call(fn_main_box, [args_handle], name='call_Main_main_1')
|
||||
else:
|
||||
# Plain main() fallback
|
||||
if len(fn_main_plain.args) == 0:
|
||||
rv = b.call(fn_main_plain, [], name='call_user_main')
|
||||
else:
|
||||
# If signature mismatches, return 0
|
||||
rv = ir.Constant(self.i64, 0)
|
||||
if hasattr(rv, 'type') and isinstance(rv.type, ir.IntType) and rv.type.width != 32:
|
||||
rv32 = b.trunc(rv, self.i32) if rv.type.width > 32 else b.zext(rv, self.i32)
|
||||
b.ret(rv32)
|
||||
elif hasattr(rv, 'type') and isinstance(rv.type, ir.IntType) and rv.type.width == 32:
|
||||
b.ret(rv)
|
||||
else:
|
||||
b.ret(ir.Constant(self.i32, 0))
|
||||
if hasattr(rv, 'type') and isinstance(rv.type, ir.IntType) and rv.type.width != 32:
|
||||
rv64 = b.trunc(rv, self.i64) if rv.type.width > 64 else b.zext(rv, self.i64)
|
||||
b.ret(rv64)
|
||||
elif hasattr(rv, 'type') and isinstance(rv.type, ir.IntType) and rv.type.width == 64:
|
||||
b.ret(rv)
|
||||
else:
|
||||
b.ret(ir.Constant(self.i64, 0))
|
||||
|
||||
ir_text = str(self.module)
|
||||
# Optional IR dump to file for debugging
|
||||
@ -159,7 +188,7 @@ class NyashLLVMBuilder:
|
||||
|
||||
def _create_dummy_main(self) -> str:
|
||||
"""Create dummy ny_main that returns 0"""
|
||||
ny_main_ty = ir.FunctionType(self.i32, [])
|
||||
ny_main_ty = ir.FunctionType(self.i64, [])
|
||||
ny_main = ir.Function(self.module, ny_main_ty, name="ny_main")
|
||||
block = ny_main.append_basic_block(name="entry")
|
||||
builder = ir.IRBuilder(block)
|
||||
|
||||
@ -202,7 +202,45 @@ fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<(
|
||||
Ok((out, merge_bb))
|
||||
}
|
||||
ExprV0::Call { name, args } => {
|
||||
// Fallback: no vars context; treat as normal call
|
||||
// Special: array literal lowering — Call{name:"array.of", args:[...]} → new ArrayBox(); push(...); result=array
|
||||
if name == "array.of" {
|
||||
// Create array first
|
||||
let arr = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.add_instruction(MirInstruction::NewBox { dst: arr, box_type: "ArrayBox".into(), args: vec![] });
|
||||
}
|
||||
// For each element: eval then push
|
||||
let mut cur = cur_bb;
|
||||
for e in args {
|
||||
let (v, c) = lower_expr(f, cur, e)?; cur = c;
|
||||
let tmp = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur) {
|
||||
bb.add_instruction(MirInstruction::BoxCall { dst: Some(tmp), box_val: arr, method: "push".into(), method_id: None, args: vec![v], effects: EffectMask::READ });
|
||||
}
|
||||
}
|
||||
return Ok((arr, cur));
|
||||
}
|
||||
// Special: map literal lowering — Call{name:"map.of", args:[k1, v1, k2, v2, ...]} → new MapBox(); set(k,v)...; result=map
|
||||
if name == "map.of" {
|
||||
let mapv = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.add_instruction(MirInstruction::NewBox { dst: mapv, box_type: "MapBox".into(), args: vec![] });
|
||||
}
|
||||
let mut cur = cur_bb;
|
||||
let mut it = args.iter();
|
||||
while let Some(k) = it.next() {
|
||||
if let Some(v) = it.next() {
|
||||
let (kv, cur2) = lower_expr(f, cur, k)?; cur = cur2;
|
||||
let (vv, cur3) = lower_expr(f, cur, v)?; cur = cur3;
|
||||
let tmp = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur) {
|
||||
bb.add_instruction(MirInstruction::BoxCall { dst: Some(tmp), box_val: mapv, method: "set".into(), method_id: None, args: vec![kv, vv], effects: EffectMask::READ });
|
||||
}
|
||||
} else { break; }
|
||||
}
|
||||
return Ok((mapv, cur));
|
||||
}
|
||||
// Fallback: treat as normal dynamic call
|
||||
let (arg_ids, cur) = lower_args(f, cur_bb, args)?;
|
||||
let fun_val = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur) {
|
||||
@ -273,6 +311,42 @@ fn lower_expr_with_vars(
|
||||
Err(format!("undefined variable: {}", name))
|
||||
}
|
||||
ExprV0::Call { name, args } => {
|
||||
// Special: array literal lowering in vars context
|
||||
if name == "array.of" {
|
||||
let arr = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.add_instruction(MirInstruction::NewBox { dst: arr, box_type: "ArrayBox".into(), args: vec![] });
|
||||
}
|
||||
let mut cur = cur_bb;
|
||||
for e in args {
|
||||
let (v, c) = lower_expr_with_vars(f, cur, e, vars)?; cur = c;
|
||||
let tmp = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur) {
|
||||
bb.add_instruction(MirInstruction::BoxCall { dst: Some(tmp), box_val: arr, method: "push".into(), method_id: None, args: vec![v], effects: EffectMask::READ });
|
||||
}
|
||||
}
|
||||
return Ok((arr, cur));
|
||||
}
|
||||
// Special: map literal lowering in vars context
|
||||
if name == "map.of" {
|
||||
let mapv = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.add_instruction(MirInstruction::NewBox { dst: mapv, box_type: "MapBox".into(), args: vec![] });
|
||||
}
|
||||
let mut cur = cur_bb;
|
||||
let mut it = args.iter();
|
||||
while let Some(k) = it.next() {
|
||||
if let Some(v) = it.next() {
|
||||
let (kv, cur2) = lower_expr_with_vars(f, cur, k, vars)?; cur = cur2;
|
||||
let (vv, cur3) = lower_expr_with_vars(f, cur, v, vars)?; cur = cur3;
|
||||
let tmp = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur) {
|
||||
bb.add_instruction(MirInstruction::BoxCall { dst: Some(tmp), box_val: mapv, method: "set".into(), method_id: None, args: vec![kv, vv], effects: EffectMask::READ });
|
||||
}
|
||||
} else { break; }
|
||||
}
|
||||
return Ok((mapv, cur));
|
||||
}
|
||||
// Lower args
|
||||
let (arg_ids, cur) = lower_args_with_vars(f, cur_bb, args, vars)?;
|
||||
// Encode as: const fun_name; call
|
||||
|
||||
BIN
tests/nyash_syntax_torture_20250916.zip
Normal file
BIN
tests/nyash_syntax_torture_20250916.zip
Normal file
Binary file not shown.
6
tests/nyash_syntax_torture_20250916/01_ops_assoc.nyash
Normal file
6
tests/nyash_syntax_torture_20250916/01_ops_assoc.nyash
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
# Expect: x=33 / y=33 (left-assoc + explicit coercion via "+")
|
||||
# Also verifies numeric-first vs string-first concatenation symmetry.
|
||||
local x = "x=" + 11 + 22 # if left-assoc with string + int => "x=1122" OR "x=33"? define spec
|
||||
local y = "x=" + (11 + 22) # force arithmetic then concat => "x=33"
|
||||
print(y) # authoritative: should be "x=33"
|
||||
5
tests/nyash_syntax_torture_20250916/02_deep_parens.nyash
Normal file
5
tests/nyash_syntax_torture_20250916/02_deep_parens.nyash
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
// Expect: 170 (deep parentheses stress)
|
||||
// 12 * 15 - 10 = 180 - 10 = 170
|
||||
local v = (5 + 7) * (10 + 5) - 10
|
||||
print(v) // 170
|
||||
@ -0,0 +1,4 @@
|
||||
|
||||
# Expect: 42
|
||||
local a = [1, [2, [3, [4, 42]]], {k: [{z: 7}, {z: 9}]}]
|
||||
print(a[1][1][1]) # 42 (0-based assumed; adjust if 1-based)
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
# Expect: OK:3-hello
|
||||
local m = { k: [ "hello", "world" ] }
|
||||
m.k.push("!") # verify method dispatch on nested box
|
||||
print("OK:" + 3 + "-" + m.k[0]) # "OK:3-hello"
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
# Expect: 🐱-9-nyash
|
||||
local cat = "🐱"
|
||||
local s = cat + "-" + (3*3) + "-" + "nyash"
|
||||
print(s)
|
||||
@ -0,0 +1,10 @@
|
||||
|
||||
# Expect: 01234|done
|
||||
local i = 0
|
||||
while i < 10 {
|
||||
if i == 5 { break }
|
||||
print(i)
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
print("|done")
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
# Expect: 3
|
||||
# Simulate async using tasks; if runtime not ready, keep as a spec stub.
|
||||
nowait t1 = 1 + 2
|
||||
print(await t1)
|
||||
@ -0,0 +1,13 @@
|
||||
|
||||
# Expect: base:42|sub:99
|
||||
box Base {
|
||||
private { x }
|
||||
birth() { me.x = 42 }
|
||||
getX() { return me.x }
|
||||
}
|
||||
box Sub from Base {
|
||||
getX() { return 99 } # override
|
||||
}
|
||||
local b = new Base()
|
||||
local s = new Sub()
|
||||
print("base:" + b.getX() + "|sub:" + s.getX())
|
||||
@ -0,0 +1,6 @@
|
||||
|
||||
# Expect: 7
|
||||
local x = 7
|
||||
local f = () => { return x }
|
||||
local x = 9 # shadow after closure creation should not affect captured value
|
||||
print(f())
|
||||
@ -0,0 +1,18 @@
|
||||
|
||||
# Expect: ok:42
|
||||
box Result {
|
||||
private { ok, val }
|
||||
birth(ok, val) { me.ok = ok; me.val = val }
|
||||
isOk() { return me.ok }
|
||||
get() { return me.val }
|
||||
}
|
||||
fn check(n) {
|
||||
if n < 0 { return new Result(false, 0) }
|
||||
return new Result(true, n*2)
|
||||
}
|
||||
local r = check(21)
|
||||
if r.isOk() {
|
||||
print("ok:" + r.get())
|
||||
} else {
|
||||
print("err")
|
||||
}
|
||||
41
tests/nyash_syntax_torture_20250916/README.md
Normal file
41
tests/nyash_syntax_torture_20250916/README.md
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
# Nyash Syntax Torture (10 minimal repros)
|
||||
Date: 2025-09-16
|
||||
|
||||
Purpose: stress parser → AST → MIR(Core-13/PURE) → Interpreter/VM/LLVM(AOT) consistency.
|
||||
Each test is **one phenomenon per file**, tiny and deterministic.
|
||||
|
||||
## How to run (suggested)
|
||||
```
|
||||
# 1) Run all modes and compare outputs
|
||||
bash run_spec_smoke.sh
|
||||
|
||||
# 2) PURE mode (surface MIR violations):
|
||||
NYASH_MIR_CORE13_PURE=1 bash run_spec_smoke.sh
|
||||
|
||||
# 3) Extra logging when a case fails:
|
||||
NYASH_VM_STATS=1 NYASH_VM_STATS_JSON=1 NYASH_VM_DEBUG_BOXCALL=1 bash run_spec_smoke.sh
|
||||
# For LLVM diagnostics (when applicable):
|
||||
# NYASH_LLVM_VINVOKE_TRACE=1 NYASH_LLVM_VINVOKE_PREFER_I64=1 bash run_spec_smoke.sh
|
||||
```
|
||||
|
||||
## Expected outputs (goldens)
|
||||
We deliberately **print a single line** per test to make diffing trivial.
|
||||
See inline comments in each `*.nyash`.
|
||||
|
||||
## File list
|
||||
1. 01_ops_assoc.nyash – operator associativity & coercion order
|
||||
2. 02_deep_parens.nyash – deep parentheses & arithmetic nesting
|
||||
3. 03_array_map_nested.nyash – nested array/map literal & access
|
||||
4. 04_map_array_mix.nyash – object/array cross indexing & updates
|
||||
5. 05_string_concat_unicode.nyash – string/number/Unicode concatenation
|
||||
6. 06_control_flow_loopform.nyash – break/continue/dispatch shape
|
||||
7. 07_await_nowait_mix.nyash – nowait/await interleave determinism
|
||||
8. 08_visibility_access.nyash – private/public & override routing
|
||||
9. 09_lambda_closure_scope.nyash – closure capture & shadowing
|
||||
10. 10_match_result_early_return.nyash – early return vs. branch merge
|
||||
|
||||
## CI hint
|
||||
- Add this suite **before** your self-host smokes:
|
||||
- `make spec-smoke` -> `make smoke-selfhost`
|
||||
- Fail fast on any diff across Interpreter/VM/LLVM.
|
||||
@ -0,0 +1 @@
|
||||
x=33
|
||||
@ -0,0 +1 @@
|
||||
x=33
|
||||
@ -0,0 +1 @@
|
||||
START
|
||||
81
tests/nyash_syntax_torture_20250916/run_spec_smoke.sh
Normal file
81
tests/nyash_syntax_torture_20250916/run_spec_smoke.sh
Normal file
@ -0,0 +1,81 @@
|
||||
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
NYASH_BIN=${NYASH_BIN:-./target/release/nyash}
|
||||
BACKENDS=${BACKENDS:-"interp vm llvm"} # choose subset: "interp vm", etc.
|
||||
OUTDIR=${OUTDIR:-_out}
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
fail=0
|
||||
|
||||
normalize_file () {
|
||||
local infile="$1"; local outfile="$2"
|
||||
# Drop runner/interpreter noise; keep only program prints
|
||||
local tmp="$outfile.tmp"
|
||||
rg -v -e '^📝' -e '^🚀' -e '^✅' -e '^🔍' -e '^🔌' \
|
||||
-e '^\[plugin-loader\]' -e '^\[Bridge\]' \
|
||||
-e '^Result(Type)?' -e '^MIR Module' \
|
||||
-e '^; ' -e '^;MIR' "$infile" > "$tmp" || true
|
||||
# Keep last non-empty line as canonical output
|
||||
awk 'NF{last=$0} END{if (last) print last;}' "$tmp" > "$outfile" || true
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
run_one () {
|
||||
local file="$1"
|
||||
local mode="$2"
|
||||
local out="$OUTDIR/$(basename "$file").$mode.out"
|
||||
case "$mode" in
|
||||
interp) "$NYASH_BIN" "$file" > "$out" ;;
|
||||
vm) "$NYASH_BIN" --backend vm "$file" > "$out" ;;
|
||||
llvm)
|
||||
# Requires LLVM features and NYASH_LLVM_OBJ_OUT env; emit and run
|
||||
local obj="$OUTDIR/nyash_llvm_temp.o"
|
||||
NYASH_LLVM_OBJ_OUT="$obj" "$NYASH_BIN" --backend llvm "$file" >/dev/null
|
||||
cc "$obj" -L crates/nyrt/target/release -Wl,--whole-archive -lnyrt -Wl,--no-whole-archive -lpthread -ldl -lm -o "$OUTDIR/app_$(basename "$file" .nyash)"
|
||||
"$OUTDIR/app_$(basename "$file" .nyash)" > "$out"
|
||||
;;
|
||||
*) echo "Unknown mode: $mode" >&2; return 2;;
|
||||
esac
|
||||
# Normalize
|
||||
normalize_file "$out" "$out.norm"
|
||||
}
|
||||
|
||||
compare_modes () {
|
||||
local file="$1"
|
||||
local modes=($BACKENDS)
|
||||
local ref="${modes[0]}"
|
||||
for m in "${modes[@]}"; do
|
||||
run_one "$file" "$m"
|
||||
done
|
||||
local refout="$OUTDIR/$(basename "$file").$ref.out.norm"
|
||||
for m in "${modes[@]:1}"; do
|
||||
local out="$OUTDIR/$(basename "$file").$m.out.norm"
|
||||
if ! diff -u "$refout" "$out"; then
|
||||
echo "[DIFF] $(basename "$file") ($ref vs $m) differs" >&2
|
||||
fail=1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
main () {
|
||||
local tests=( $(ls -1 tests/syntax_torture/*.nyash 2>/dev/null || true) )
|
||||
if [ ${#tests[@]} -eq 0 ]; then
|
||||
# fallback: assume running inside this folder
|
||||
tests=( $(ls -1 ./*.nyash) )
|
||||
fi
|
||||
|
||||
for t in "${tests[@]}"; do
|
||||
echo "==> $(basename "$t")"
|
||||
compare_modes "$t"
|
||||
done
|
||||
|
||||
if [ $fail -ne 0 ]; then
|
||||
echo "Some tests differ across backends." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "All tests matched across selected backends."
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@ -0,0 +1,6 @@
|
||||
|
||||
# Expect: x=33 / y=33 (left-assoc + explicit coercion via "+")
|
||||
# Also verifies numeric-first vs string-first concatenation symmetry.
|
||||
local x = "x=" + 11 + 22 # if left-assoc with string + int => "x=1122" OR "x=33"? define spec
|
||||
local y = "x=" + (11 + 22) # force arithmetic then concat => "x=33"
|
||||
print(y) # authoritative: should be "x=33"
|
||||
@ -0,0 +1,4 @@
|
||||
|
||||
# Expect: 170
|
||||
local v = (((((((((5 + 7) * 4) + 2) * 3) + 1) * 2) + 5))))
|
||||
print(v) # 170
|
||||
@ -0,0 +1,4 @@
|
||||
|
||||
# Expect: 42
|
||||
local a = [1, [2, [3, [4, 42]]], {k: [{z: 7}, {z: 9}]}]
|
||||
print(a[1][1][1]) # 42 (0-based assumed; adjust if 1-based)
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
# Expect: OK:3-hello
|
||||
local m = { k: [ "hello", "world" ] }
|
||||
m.k.push("!") # verify method dispatch on nested box
|
||||
print("OK:" + 3 + "-" + m.k[0]) # "OK:3-hello"
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
# Expect: 🐱-9-nyash
|
||||
local cat = "🐱"
|
||||
local s = cat + "-" + (3*3) + "-" + "nyash"
|
||||
print(s)
|
||||
@ -0,0 +1,10 @@
|
||||
|
||||
# Expect: 01234|done
|
||||
local i = 0
|
||||
while i < 10 {
|
||||
if i == 5 { break }
|
||||
print(i)
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
print("|done")
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
# Expect: 3
|
||||
# Simulate async using tasks; if runtime not ready, keep as a spec stub.
|
||||
nowait t1 = 1 + 2
|
||||
print(await t1)
|
||||
@ -0,0 +1,13 @@
|
||||
|
||||
# Expect: base:42|sub:99
|
||||
box Base {
|
||||
private { x }
|
||||
birth() { me.x = 42 }
|
||||
getX() { return me.x }
|
||||
}
|
||||
box Sub from Base {
|
||||
getX() { return 99 } # override
|
||||
}
|
||||
local b = new Base()
|
||||
local s = new Sub()
|
||||
print("base:" + b.getX() + "|sub:" + s.getX())
|
||||
@ -0,0 +1,6 @@
|
||||
|
||||
# Expect: 7
|
||||
local x = 7
|
||||
local f = () => { return x }
|
||||
local x = 9 # shadow after closure creation should not affect captured value
|
||||
print(f())
|
||||
@ -0,0 +1,18 @@
|
||||
|
||||
# Expect: ok:42
|
||||
box Result {
|
||||
private { ok, val }
|
||||
birth(ok, val) { me.ok = ok; me.val = val }
|
||||
isOk() { return me.ok }
|
||||
get() { return me.val }
|
||||
}
|
||||
fn check(n) {
|
||||
if n < 0 { return new Result(false, 0) }
|
||||
return new Result(true, n*2)
|
||||
}
|
||||
local r = check(21)
|
||||
if r.isOk() {
|
||||
print("ok:" + r.get())
|
||||
} else {
|
||||
print("err")
|
||||
}
|
||||
@ -18,6 +18,7 @@ Grammar (subset):
|
||||
term := unary (('*'|'/') unary)*
|
||||
unary := '-' unary | factor
|
||||
factor := INT | STRING | IDENT call_tail* | '(' expr ')' | 'new' IDENT '(' args? ')'
|
||||
| '{' map_entries? '}' # map literal (string keys only)
|
||||
call_tail:= '.' IDENT '(' args? ')' # method
|
||||
| '(' args? ')' # function call
|
||||
args := expr (',' expr)*
|
||||
@ -45,7 +46,7 @@ def lex(s: str):
|
||||
# two-char ops
|
||||
if s.startswith('==', i) or s.startswith('!=', i) or s.startswith('<=', i) or s.startswith('>=', i) or s.startswith('&&', i) or s.startswith('||', i):
|
||||
out.append(Tok('OP2', s[i:i+2], i)); i+=2; continue
|
||||
if c in '+-*/(){}.,<>=':
|
||||
if c in '+-*/(){}.,<>=[]:':
|
||||
out.append(Tok(c, c, i)); i+=1; continue
|
||||
if c=='"':
|
||||
j=i+1; buf=[]
|
||||
@ -144,6 +145,31 @@ class P:
|
||||
if self.eat('STR'): return {"type":"Str","value":tok.val}
|
||||
if self.eat('('):
|
||||
e=self.expr(); self.expect(')'); return e
|
||||
# Array literal: [e1, e2, ...] → Call{name:"array.of", args:[...]}
|
||||
if self.eat('['):
|
||||
args=[]
|
||||
if self.cur().kind != ']':
|
||||
args.append(self.expr())
|
||||
while self.eat(','):
|
||||
args.append(self.expr())
|
||||
self.expect(']')
|
||||
return {"type":"Call","name":"array.of","args":args}
|
||||
# Map literal: {"k": v, ...} (string keys only) → Call{name:"map.of", args:[Str(k1), v1, ...]}
|
||||
if self.eat('{'):
|
||||
args=[]
|
||||
if self.cur().kind != '}':
|
||||
# first entry
|
||||
k=self.cur(); self.expect('STR');
|
||||
self.expect(':'); v=self.expr();
|
||||
args.append({"type":"Str","value":k.val}); args.append(v)
|
||||
while self.eat(','):
|
||||
if self.cur().kind == '}':
|
||||
break
|
||||
k=self.cur(); self.expect('STR');
|
||||
self.expect(':'); v=self.expr();
|
||||
args.append({"type":"Str","value":k.val}); args.append(v)
|
||||
self.expect('}')
|
||||
return {"type":"Call","name":"map.of","args":args}
|
||||
if self.eat('NEW'):
|
||||
t=self.cur(); self.expect('IDENT'); self.expect('(')
|
||||
args=self.args_opt(); self.expect(')')
|
||||
|
||||
@ -110,5 +110,22 @@ NY
|
||||
)
|
||||
run_case_bridge "assign update (bridge)" "$SRC_ASSIGN" 3
|
||||
|
||||
# G) array literal [x,2,3] → size() == 3
|
||||
SRC_ALIT=$(cat <<'NY'
|
||||
local x = 1
|
||||
local arr = [x, 2, 3]
|
||||
return arr.size()
|
||||
NY
|
||||
)
|
||||
run_case_bridge "array literal (bridge)" "$SRC_ALIT" 3
|
||||
|
||||
# H) map literal {"name":"Alice", "age":25} → size() == 2
|
||||
SRC_MLIT=$(cat <<'NY'
|
||||
local m = {"name": "Alice", "age": 25}
|
||||
return m.size()
|
||||
NY
|
||||
)
|
||||
run_case_bridge "map literal (bridge)" "$SRC_MLIT" 2
|
||||
|
||||
echo "All selfhost Stage-2 bridge smokes PASS" >&2
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user