feat: 配列/Mapリテラル糖衣構文の実装とネームスペース解決の改善計画
- ArrayLiteral/MapLiteralのAST定義追加
- パーサーで[...]と{...}構文をサポート
- MIR Builderでnew Box() + push/setへのdesugaring実装
- テストケースとスモークスクリプト追加
- CURRENT_TASK.mdにネームスペース解決Phase-1計画を追記
- 3段階解決順序(ローカル→エイリアス→プラグイン)の設計合意
This commit is contained in:
@ -5,6 +5,21 @@ TL;DR
|
|||||||
- PyVM は意味論の参照実行器(開発補助)。llvmlite は AOT/検証。配布やバンドル化は後回し(基礎固めが先)。
|
- PyVM は意味論の参照実行器(開発補助)。llvmlite は AOT/検証。配布やバンドル化は後回し(基礎固めが先)。
|
||||||
|
|
||||||
What Changed (today)
|
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変更なし、デシュガリング)。
|
||||||
|
|
||||||
|
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 受理を追加)
|
- ParserBox 強化(Stage‑2 完了 + Stage‑3 受理を追加)
|
||||||
- 進捗ガード(parse_program2/parse_block2/parse_stmt2)。
|
- 進捗ガード(parse_program2/parse_block2/parse_stmt2)。
|
||||||
- Stage‑2 受理一式: 単項/二項/比較/論理/呼出/メソッド/引数/if/else/loop/using/local/return/new。
|
- Stage‑2 受理一式: 単項/二項/比較/論理/呼出/メソッド/引数/if/else/loop/using/local/return/new。
|
||||||
@ -64,6 +79,22 @@ Notes / Policies
|
|||||||
- Bridge は JSON v0 → MIR 降下で PHI を生成(Phase‑15 中は現行方式を維持)。
|
- Bridge は JSON v0 → MIR 降下で PHI を生成(Phase‑15 中は現行方式を維持)。
|
||||||
- 配布/バンドル/EXE 化は任意の実験導線として維持(Phase‑15 の主目的外)。
|
- 配布/バンドル/EXE 化は任意の実験導線として維持(Phase‑15 の主目的外)。
|
||||||
|
|
||||||
|
Smoke Snapshot (2025‑09‑15)
|
||||||
|
- 修正: `runner/dispatch.rs` に `vm` 分岐が欠落しており `--backend vm` が interpreter にフォールバックしていたため、PyVM スモークが作動せず。分岐を追加して復旧済み。
|
||||||
|
- PyVM Stage‑2 部分結果:
|
||||||
|
- PASS: string ops basic, me method call
|
||||||
|
- FAIL: loop/if/phi → 出力 `sum=4`(期待 `sum=9`)
|
||||||
|
- 原因分析: ループ内 if の merge で `sum` の Phi 正規化が入らず、latch 側スナップショットが else 系の一時値を優先(`16`)しうる構造。`LoopBuilder::build_statement(If)` が `normalize_if_else_phi` 相当を呼ばず、変数マップが φ 統合されていない。
|
||||||
|
- 対応方針(最小修正):
|
||||||
|
- LoopBuilder の If 降下で merge 到達時に「両枝が同一変数に代入」の場合は `phi(dst=[then,else])` を emit→その φ を対象変数に bind。
|
||||||
|
- latch スナップショットはこの φ 後の変数マップで採取する。
|
||||||
|
- 代替(短期): Builder 側の `normalize_if_else_phi` を呼ぶ薄いフックを設けて流用。
|
||||||
|
|
||||||
|
Fixes Applied (2025‑09‑15)
|
||||||
|
- LoopBuilder If 降下に φ 正規化を追加(両枝代入の変数を merge 時に φ で束ねて再束縛)。
|
||||||
|
- PyVM φ 解決ロジックを安定化(incoming を [value, pred] 形に限定し、[pred, value] の曖昧推測を削除)。偶然一致による誤選択を排除。
|
||||||
|
- これにより `tools/pyvm_stage2_smoke.sh` は全 PASS を確認済み。
|
||||||
|
|
||||||
Refactor Candidates (early plan)
|
Refactor Candidates (early plan)
|
||||||
- runner/mod.rs(~70K chars): “runner pipeline” を用途別に分割(TODO #15)
|
- runner/mod.rs(~70K chars): “runner pipeline” を用途別に分割(TODO #15)
|
||||||
- runner/pipeline.rs(入力正規化/using解決/環境注入)
|
- runner/pipeline.rs(入力正規化/using解決/環境注入)
|
||||||
@ -91,3 +122,38 @@ Recommended Next (short list)
|
|||||||
- `builder/vars.rs` に SSA 変数正規化の小物を段階追加(変数名再束縛/スコープ終端の型ヒント伝搬など)。
|
- `builder/vars.rs` に SSA 変数正規化の小物を段階追加(変数名再束縛/スコープ終端の型ヒント伝搬など)。
|
||||||
- Runner(仕上げ)
|
- Runner(仕上げ)
|
||||||
- `mod.rs` の残置ヘルパ(usingの候補提示・環境注入ログ)を `pipeline/dispatch` へ集約し、`mod.rs` を最小のオーケストレーションに。
|
- `mod.rs` の残置ヘルパ(usingの候補提示・環境注入ログ)を `pipeline/dispatch` へ集約し、`mod.rs` を最小のオーケストレーションに。
|
||||||
|
- Namespaces Phase‑1(実装着手): BoxIndex 構築・3段階解決・toml aliases・曖昧エラー改善・トレース
|
||||||
|
|
||||||
|
Array/Map Literals Plan(Syntax Sugar)
|
||||||
|
- Stage‑1: Array literal `[e1, e2, ...]` を実装(ゲート: `NYASH_SYNTAX_SUGAR_LEVEL=basic|full` または `NYASH_ENABLE_ARRAY_LITERAL=1`)。
|
||||||
|
- Lowering: `new ArrayBox()` → 各要素を評価 → `.push(elem)` を左から右に順に発行 → 最後に配列値を返す。
|
||||||
|
- 末尾カンマ許可。
|
||||||
|
- スモーク: `apps/tests/array_literal_basic.nyash`(size/順序/副作用1回性)。
|
||||||
|
- Stage‑2: Map literal `{ "k": v, ... }`(文字列キー限定)を実装(ゲート: `NYASH_SYNTAX_SUGAR_LEVEL=basic|full` or `NYASH_ENABLE_MAP_LITERAL=1`)。
|
||||||
|
- Lowering: `new MapBox()` → 各ペアを評価 → `.set("k", v)` を左から右に順に発行 → 最後に map 値を返す。
|
||||||
|
- 末尾カンマ許可。識別子キー糖 `{name: v}` は次フェーズ。
|
||||||
|
- スモーク: `apps/tests/map_literal_basic.nyash`(size/get/順序検証)。
|
||||||
|
- Stage‑3: 識別子キー糖 `{name: v}` と末尾カンマを強化(任意)。
|
||||||
|
|
||||||
|
Gates / Semantics
|
||||||
|
- 左から右で評価(一度だけ)。push/set 失敗は即時伝播(既存 BoxCall 規約に追従)。
|
||||||
|
- IR 変更なし(BoxCall/MethodCall のみ)。将来 `with_capacity(n)` 最適化は任意で追加。
|
||||||
|
|
||||||
|
Decision Log (2025‑09‑15)
|
||||||
|
- Q: 警告削減(`ops_ext.rs` / `selfhost.rs`)を先にやる?それとも挙動スモークを先に回す?
|
||||||
|
- A: スモークを先に実施。理由は以下。
|
||||||
|
- リファクタ直後は回帰検出を最優先(PyVM/自己ホスト/Bridge の3レーンで即座に検証)。
|
||||||
|
- 警告削減は挙動非変化を原則とするが、微妙なスコープや保存スロットの触りが混入し得るため、先に“緑”を固める。
|
||||||
|
Namespaces / Using(計画合意)
|
||||||
|
- 3段階の解決順(決定性): 1) ローカル/コアボックス → 2) エイリアス(nyash.toml/needs) → 3) プラグイン(短名/qualified)
|
||||||
|
- 曖昧時はエラー+候補提示。qualified または alias を要求(自動推測はしない)。
|
||||||
|
- モード切替: Relaxed(既定)/Strict(`NYASH_PLUGIN_REQUIRE_PREFIX=1` or toml `[plugins] require_prefix=true`)
|
||||||
|
- needs 糖衣は using の同義(Runner で alias 登録)。`needs plugin.network.HttpClient as HttpClient` 等。
|
||||||
|
- Plugins は統合名前空間(短名はユニーク時のみ)。qualified `network.HttpClient` を常時許可。
|
||||||
|
- nyash.toml(MVP): `[imports]`/`[aliases]`,`[plugins.<name>] { path, prefix, require_prefix, expose_short_names }`
|
||||||
|
- Index とキャッシュ(Runner):
|
||||||
|
- BoxIndex: `local_boxes`, `plugin_boxes`, `aliases` を保持
|
||||||
|
- `RESOLVE_CACHE`(thread‑local)で同一解決の再計算を回避
|
||||||
|
- `NYASH_RESOLVE_TRACE=1` で解決過程をログ出力
|
||||||
|
|
||||||
|
- スモークが緑=基礎健全性確認後に、静的ノイズの除去を安全に一気通貫で行う。
|
||||||
|
|||||||
11
apps/tests/array_literal_basic.nyash
Normal file
11
apps/tests/array_literal_basic.nyash
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
static box Main {
|
||||||
|
main(args) {
|
||||||
|
local console = new ConsoleBox()
|
||||||
|
local x = 1
|
||||||
|
// array literal sugar
|
||||||
|
local arr = [x, 2, 3]
|
||||||
|
console.println(arr.size())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
11
apps/tests/map_literal_basic.nyash
Normal file
11
apps/tests/map_literal_basic.nyash
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
static box Main {
|
||||||
|
main(args) {
|
||||||
|
local console = new ConsoleBox()
|
||||||
|
// map literal sugar (string keys)
|
||||||
|
local m = {"name": "Alice", "age": 25}
|
||||||
|
console.println(m.size())
|
||||||
|
console.println(m.get("name"))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
10
apps/tests/map_literal_ident_key.nyash
Normal file
10
apps/tests/map_literal_ident_key.nyash
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
static box Main {
|
||||||
|
main(args) {
|
||||||
|
local console = new ConsoleBox()
|
||||||
|
local m = {name: "A", age: 2,}
|
||||||
|
console.println(m.size())
|
||||||
|
console.println(m.get("name"))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -24,6 +24,10 @@ factor := INT
|
|||||||
| IDENT call_tail*
|
| IDENT call_tail*
|
||||||
| '(' expr ')'
|
| '(' expr ')'
|
||||||
| 'new' IDENT '(' args? ')'
|
| 'new' IDENT '(' args? ')'
|
||||||
|
| '[' args? ']' ; Array literal (Stage‑1 sugar, gated)
|
||||||
|
| '{' map_entries? '}' ; Map literal (Stage‑2 sugar, gated)
|
||||||
|
|
||||||
|
map_entries := (STRING | IDENT) ':' expr (',' (STRING | IDENT) ':' expr)* [',']
|
||||||
|
|
||||||
call_tail := '.' IDENT '(' args? ')' ; method
|
call_tail := '.' IDENT '(' args? ')' ; method
|
||||||
| '(' args? ')' ; function call
|
| '(' args? ')' ; function call
|
||||||
@ -35,4 +39,6 @@ Notes
|
|||||||
- Short-circuit: '&&' and '||' must not evaluate the RHS when not needed.
|
- Short-circuit: '&&' and '||' must not evaluate the RHS when not needed.
|
||||||
- Unary minus has higher precedence than '*' and '/'.
|
- Unary minus has higher precedence than '*' and '/'.
|
||||||
- IDENT names consist of [A-Za-z_][A-Za-z0-9_]*
|
- IDENT names consist of [A-Za-z_][A-Za-z0-9_]*
|
||||||
|
- Array literal is enabled when syntax sugar is on (NYASH_SYNTAX_SUGAR_LEVEL=basic|full) or when NYASH_ENABLE_ARRAY_LITERAL=1 is set.
|
||||||
|
- Map literal is enabled when syntax sugar is on (NYASH_SYNTAX_SUGAR_LEVEL=basic|full) or when NYASH_ENABLE_MAP_LITERAL=1 is set.
|
||||||
|
- Identifier keys (`{name: v}`) are Stage‑3 and require either NYASH_SYNTAX_SUGAR_LEVEL=full or NYASH_ENABLE_MAP_IDENT_KEY=1.
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# using — Imports and Namespaces (Phase 15)
|
# using — Imports and Namespaces (Phase 15+)
|
||||||
|
|
||||||
Status: Accepted (Runner‑side resolution). Selfhost parser accepts using as no‑op and attaches `meta.usings` for future use.
|
Status: Accepted (Runner‑side resolution). Selfhost parser accepts using as no‑op and attaches `meta.usings` for future use.
|
||||||
|
|
||||||
@ -9,6 +9,50 @@ Policy
|
|||||||
- Registers modules into an internal registry for path/namespace hints.
|
- Registers modules into an internal registry for path/namespace hints.
|
||||||
- Selfhost compiler (Ny→JSON v0) collects using lines and emits `meta.usings` when present. The bridge currently ignores this meta field.
|
- Selfhost compiler (Ny→JSON v0) collects using lines and emits `meta.usings` when present. The bridge currently ignores this meta field.
|
||||||
|
|
||||||
|
## Namespace Resolution (Runner‑side)
|
||||||
|
- Goal: keep IR/VM/JIT untouched. All resolution happens in Runner/Registry.
|
||||||
|
- Default search order (3 stages, deterministic):
|
||||||
|
1) Local/Core Boxes (nyrt)
|
||||||
|
2) Aliases (nyash.toml [imports] / `needs … as …`)
|
||||||
|
3) Plugins (short name if unique, otherwise qualified `pluginName.BoxName`)
|
||||||
|
- On ambiguity: error with candidates and remediation (qualify or define alias).
|
||||||
|
- Modes:
|
||||||
|
- Relaxed (default): short names allowed when unique.
|
||||||
|
- Strict: require plugin prefix (env `NYASH_PLUGIN_REQUIRE_PREFIX=1` or nyash.toml `[plugins] require_prefix=true`).
|
||||||
|
- Aliases:
|
||||||
|
- nyash.toml `[imports] HttpClient = "network.HttpClient"`
|
||||||
|
- needs sugar: `needs plugin.network.HttpClient as HttpClient` (file‑scoped alias)
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
- Unified namespace with Boxes. Prefer short names when unique.
|
||||||
|
- Qualified form: `network.HttpClient`
|
||||||
|
- Per‑plugin control (nyash.toml): `prefix`, `require_prefix`, `expose_short_names`
|
||||||
|
|
||||||
|
## `needs` sugar (optional)
|
||||||
|
- Treated as a synonym to `using` on the Runner side; registers aliases only.
|
||||||
|
- Examples: `needs utils.StringHelper`, `needs plugin.network.HttpClient as HttpClient`, `needs plugin.network.*`
|
||||||
|
|
||||||
|
## nyash.toml keys (MVP)
|
||||||
|
- `[imports]`/`[aliases]`: short name → fully qualified
|
||||||
|
- `[plugins.<name>]`: `path`, `prefix`, `require_prefix`, `expose_short_names`
|
||||||
|
|
||||||
|
## Index and Cache (Runner)
|
||||||
|
- Build a Box index once per run to make resolution fast and predictable:
|
||||||
|
```
|
||||||
|
struct BoxIndex {
|
||||||
|
local_boxes: HashMap<String, PathBuf>,
|
||||||
|
plugin_boxes: HashMap<String, Vec<PluginBox>>,
|
||||||
|
aliases: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Maintain a small resolve cache per thread:
|
||||||
|
```
|
||||||
|
thread_local! {
|
||||||
|
static RESOLVE_CACHE: RefCell<HashMap<String, ResolvedBox>> = /* ... */;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Trace: `NYASH_RESOLVE_TRACE=1` prints resolution steps (for debugging/CI logs).
|
||||||
|
|
||||||
Syntax
|
Syntax
|
||||||
- Namespace: `using core.std` or `using core.std as Std`
|
- Namespace: `using core.std` or `using core.std as Std`
|
||||||
- File path: `using "apps/examples/string_p0.nyash" as Strings`
|
- File path: `using "apps/examples/string_p0.nyash" as Strings`
|
||||||
@ -34,6 +78,28 @@ static box Main {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Qualified/Plugins/Aliases examples
|
||||||
|
```nyash
|
||||||
|
# nyash.toml
|
||||||
|
[plugins.network]
|
||||||
|
path = "plugins/network.so"
|
||||||
|
prefix = "network"
|
||||||
|
require_prefix = false
|
||||||
|
|
||||||
|
[imports]
|
||||||
|
HttpClient = "network.HttpClient"
|
||||||
|
|
||||||
|
# code
|
||||||
|
needs plugin.network.HttpClient as HttpClient
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main(args) {
|
||||||
|
let a = new HttpClient() # alias
|
||||||
|
let b = new network.HttpClient() # qualified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Runner Configuration
|
Runner Configuration
|
||||||
- Enable using pre‑processing: `NYASH_ENABLE_USING=1`
|
- Enable using pre‑processing: `NYASH_ENABLE_USING=1`
|
||||||
- Selfhost pipeline keeps child stdout quiet and extracts JSON only: `NYASH_JSON_ONLY=1` (set by Runner automatically for child)
|
- Selfhost pipeline keeps child stdout quiet and extracts JSON only: `NYASH_JSON_ONLY=1` (set by Runner automatically for child)
|
||||||
@ -42,4 +108,3 @@ Runner Configuration
|
|||||||
Notes
|
Notes
|
||||||
- Phase 15 keeps resolution in the Runner to minimize parser complexity. Future phases may leverage `meta.usings` for compiler decisions.
|
- Phase 15 keeps resolution in the Runner to minimize parser complexity. Future phases may leverage `meta.usings` for compiler decisions.
|
||||||
- Unknown fields in the top‑level JSON (like `meta`) are ignored by the current bridge.
|
- Unknown fields in the top‑level JSON (like `meta`) are ignored by the current bridge.
|
||||||
|
|
||||||
|
|||||||
23
src/ast.rs
23
src/ast.rs
@ -201,6 +201,7 @@ pub enum ExpressionNode {
|
|||||||
else_expr: Box<ASTNode>,
|
else_expr: Box<ASTNode>,
|
||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
|
// (Stage‑2 sugar for literals is represented in unified ASTNode, not here)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 文ノード - 実行可能なアクション
|
/// 文ノード - 実行可能なアクション
|
||||||
@ -476,6 +477,16 @@ pub enum ASTNode {
|
|||||||
else_expr: Box<ASTNode>,
|
else_expr: Box<ASTNode>,
|
||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
|
/// 配列リテラル(糖衣): [e1, e2, ...]
|
||||||
|
ArrayLiteral {
|
||||||
|
elements: Vec<ASTNode>,
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
/// マップリテラル(糖衣): { "k": v, ... } (Stage‑2: 文字列キー限定)
|
||||||
|
MapLiteral {
|
||||||
|
entries: Vec<(String, ASTNode)>,
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
|
||||||
/// 無名関数(最小P1: 値としてのみ。呼び出しは未対応)
|
/// 無名関数(最小P1: 値としてのみ。呼び出しは未対応)
|
||||||
Lambda {
|
Lambda {
|
||||||
@ -708,6 +719,8 @@ impl ASTNode {
|
|||||||
ASTNode::QMarkPropagate { .. } => "QMarkPropagate",
|
ASTNode::QMarkPropagate { .. } => "QMarkPropagate",
|
||||||
ASTNode::PeekExpr { .. } => "PeekExpr",
|
ASTNode::PeekExpr { .. } => "PeekExpr",
|
||||||
ASTNode::Lambda { .. } => "Lambda",
|
ASTNode::Lambda { .. } => "Lambda",
|
||||||
|
ASTNode::ArrayLiteral { .. } => "ArrayLiteral",
|
||||||
|
ASTNode::MapLiteral { .. } => "MapLiteral",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -740,6 +753,8 @@ impl ASTNode {
|
|||||||
ASTNode::PeekExpr { .. } => ASTNodeType::Expression,
|
ASTNode::PeekExpr { .. } => ASTNodeType::Expression,
|
||||||
ASTNode::QMarkPropagate { .. } => ASTNodeType::Expression,
|
ASTNode::QMarkPropagate { .. } => ASTNodeType::Expression,
|
||||||
ASTNode::Lambda { .. } => ASTNodeType::Expression,
|
ASTNode::Lambda { .. } => ASTNodeType::Expression,
|
||||||
|
ASTNode::ArrayLiteral { .. } => ASTNodeType::Expression,
|
||||||
|
ASTNode::MapLiteral { .. } => ASTNodeType::Expression,
|
||||||
|
|
||||||
// Statement nodes - 実行可能なアクション
|
// Statement nodes - 実行可能なアクション
|
||||||
ASTNode::Program { .. } => ASTNodeType::Statement, // プログラム全体
|
ASTNode::Program { .. } => ASTNodeType::Statement, // プログラム全体
|
||||||
@ -903,6 +918,12 @@ impl ASTNode {
|
|||||||
ASTNode::Lambda { params, body, .. } => {
|
ASTNode::Lambda { params, body, .. } => {
|
||||||
format!("Lambda({} params, {} statements)", params.len(), body.len())
|
format!("Lambda({} params, {} statements)", params.len(), body.len())
|
||||||
}
|
}
|
||||||
|
ASTNode::ArrayLiteral { elements, .. } => {
|
||||||
|
format!("ArrayLiteral({} elements)", elements.len())
|
||||||
|
}
|
||||||
|
ASTNode::MapLiteral { entries, .. } => {
|
||||||
|
format!("MapLiteral({} entries)", entries.len())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -947,6 +968,8 @@ impl ASTNode {
|
|||||||
ASTNode::PeekExpr { span, .. } => *span,
|
ASTNode::PeekExpr { span, .. } => *span,
|
||||||
ASTNode::QMarkPropagate { span, .. } => *span,
|
ASTNode::QMarkPropagate { span, .. } => *span,
|
||||||
ASTNode::Lambda { span, .. } => *span,
|
ASTNode::Lambda { span, .. } => *span,
|
||||||
|
ASTNode::ArrayLiteral { span, .. } => *span,
|
||||||
|
ASTNode::MapLiteral { span, .. } => *span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -135,9 +135,9 @@ impl VM {
|
|||||||
|
|
||||||
/// Execute a single function
|
/// Execute a single function
|
||||||
pub(super) fn execute_function(&mut self, function: &MirFunction) -> Result<VMValue, VMError> {
|
pub(super) fn execute_function(&mut self, function: &MirFunction) -> Result<VMValue, VMError> {
|
||||||
use crate::box_trait::{StringBox, IntegerBox, BoolBox, VoidBox};
|
// unused: local downcasts not required here
|
||||||
use crate::runtime::global_hooks;
|
use crate::runtime::global_hooks;
|
||||||
use crate::instance_v2::InstanceBox;
|
// use crate::instance_v2::InstanceBox; // not used in this path
|
||||||
use super::control_flow;
|
use super::control_flow;
|
||||||
|
|
||||||
self.current_function = Some(function.signature.name.clone());
|
self.current_function = Some(function.signature.name.clone());
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use crate::box_trait::NyashBox;
|
|
||||||
use crate::mir::ValueId;
|
use crate::mir::ValueId;
|
||||||
use crate::backend::vm::ControlFlow;
|
use crate::backend::vm::ControlFlow;
|
||||||
use crate::backend::{VM, VMError, VMValue};
|
use crate::backend::{VM, VMError, VMValue};
|
||||||
@ -7,7 +6,7 @@ impl VM {
|
|||||||
/// Small helpers to reduce duplication in vtable stub paths.
|
/// Small helpers to reduce duplication in vtable stub paths.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn vmvalue_to_box(val: &VMValue) -> Box<dyn crate::box_trait::NyashBox> {
|
fn vmvalue_to_box(val: &VMValue) -> Box<dyn crate::box_trait::NyashBox> {
|
||||||
use crate::box_trait::{NyashBox, StringBox as SBox, IntegerBox as IBox, BoolBox as BBox};
|
use crate::box_trait::{StringBox as SBox, IntegerBox as IBox, BoolBox as BBox};
|
||||||
match val {
|
match val {
|
||||||
VMValue::Integer(i) => Box::new(IBox::new(*i)),
|
VMValue::Integer(i) => Box::new(IBox::new(*i)),
|
||||||
VMValue::String(s) => Box::new(SBox::new(s)),
|
VMValue::String(s) => Box::new(SBox::new(s)),
|
||||||
@ -900,3 +899,4 @@ impl VM {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
use crate::box_trait::NyashBox;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use crate::mir::{ConstValue, BinaryOp, CompareOp, UnaryOp, ValueId, BasicBlockId, TypeOpKind, MirType};
|
use crate::mir::{ConstValue, BinaryOp, CompareOp, UnaryOp, ValueId, BasicBlockId, TypeOpKind, MirType};
|
||||||
use crate::box_trait::{NyashBox, BoolBox, VoidBox};
|
use crate::box_trait::{BoolBox, VoidBox};
|
||||||
use crate::boxes::ArrayBox;
|
use crate::boxes::ArrayBox;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use crate::backend::vm::ControlFlow;
|
use crate::backend::vm::ControlFlow;
|
||||||
|
|||||||
@ -12,7 +12,7 @@ use crate::mir::{BasicBlockId, ValueId};
|
|||||||
use crate::runtime::NyashRuntime;
|
use crate::runtime::NyashRuntime;
|
||||||
use crate::scope_tracker::ScopeTracker;
|
use crate::scope_tracker::ScopeTracker;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Instant;
|
// use std::time::Instant; // not used in this module
|
||||||
|
|
||||||
impl VM {
|
impl VM {
|
||||||
fn jit_threshold_from_env() -> u32 {
|
fn jit_threshold_from_env() -> u32 {
|
||||||
|
|||||||
@ -47,7 +47,7 @@ impl NyashInterpreter {
|
|||||||
}
|
}
|
||||||
"MathBox" => {
|
"MathBox" => {
|
||||||
if let Ok(reg) = self.runtime.box_registry.lock() {
|
if let Ok(reg) = self.runtime.box_registry.lock() {
|
||||||
if let Ok(b) = reg.create_box("MathBox", &[]) {
|
if let Ok(_b) = reg.create_box("MathBox", &[]) {
|
||||||
// Note: execute_math_method expects builtin MathBox; plugin path should route via VM/BoxCall in new pipeline.
|
// Note: execute_math_method expects builtin MathBox; plugin path should route via VM/BoxCall in new pipeline.
|
||||||
// Here we simply return void; method paths should prefer plugin invoke in VM.
|
// Here we simply return void; method paths should prefer plugin invoke in VM.
|
||||||
return Ok(Box::new(VoidBox::new()));
|
return Ok(Box::new(VoidBox::new()));
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use crate::mir::{MirFunction, MirInstruction, ConstValue, BinaryOp, CompareOp, ValueId};
|
use crate::mir::{MirFunction, MirInstruction, ConstValue, ValueId};
|
||||||
use super::builder::{IRBuilder, BinOpKind, CmpKind};
|
use super::builder::{IRBuilder, BinOpKind};
|
||||||
|
|
||||||
mod analysis;
|
mod analysis;
|
||||||
mod cfg;
|
mod cfg;
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
use std::collections::{HashMap, HashSet, BTreeSet};
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
|
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
|
||||||
|
|
||||||
use super::super::builder::IRBuilder;
|
// removed unused imports
|
||||||
use super::super::core_ops; // ensure module link remains
|
|
||||||
use super::LowerCore;
|
use super::LowerCore;
|
||||||
|
|
||||||
impl LowerCore {
|
impl LowerCore {
|
||||||
@ -114,4 +113,3 @@ impl LowerCore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -127,6 +127,9 @@ class PyVM:
|
|||||||
# incoming: prefer [[vid, pred_bid]], but accept [pred_bid, vid] robustly
|
# incoming: prefer [[vid, pred_bid]], but accept [pred_bid, vid] robustly
|
||||||
incoming = inst.get("incoming", [])
|
incoming = inst.get("incoming", [])
|
||||||
chosen: Any = None
|
chosen: Any = None
|
||||||
|
dbg = os.environ.get('NYASH_PYVM_DEBUG_PHI') == '1'
|
||||||
|
if dbg:
|
||||||
|
print(f"[pyvm.phi] prev={prev} incoming={incoming}")
|
||||||
for pair in incoming:
|
for pair in incoming:
|
||||||
if not isinstance(pair, (list, tuple)) or len(pair) < 2:
|
if not isinstance(pair, (list, tuple)) or len(pair) < 2:
|
||||||
continue
|
continue
|
||||||
@ -134,10 +137,8 @@ class PyVM:
|
|||||||
# Case 1: [vid, pred]
|
# Case 1: [vid, pred]
|
||||||
if prev is not None and int(b) == int(prev) and int(a) in regs:
|
if prev is not None and int(b) == int(prev) and int(a) in regs:
|
||||||
chosen = regs.get(int(a))
|
chosen = regs.get(int(a))
|
||||||
break
|
if dbg:
|
||||||
# Case 2: [pred, vid]
|
print(f"[pyvm.phi] case1 match: use v{a} from pred {b} -> {chosen}")
|
||||||
if prev is not None and int(a) == int(prev) and int(b) in regs:
|
|
||||||
chosen = regs.get(int(b))
|
|
||||||
break
|
break
|
||||||
if chosen is None and incoming:
|
if chosen is None and incoming:
|
||||||
# Fallback to first element that resolves to a known vid
|
# Fallback to first element that resolves to a known vid
|
||||||
@ -147,8 +148,9 @@ class PyVM:
|
|||||||
a, b = pair[0], pair[1]
|
a, b = pair[0], pair[1]
|
||||||
if int(a) in regs:
|
if int(a) in regs:
|
||||||
chosen = regs.get(int(a)); break
|
chosen = regs.get(int(a)); break
|
||||||
if int(b) in regs:
|
# Do not try to resolve by assuming [pred, vid] — avoid false matches
|
||||||
chosen = regs.get(int(b)); break
|
if dbg:
|
||||||
|
print(f"[pyvm.phi] chosen={chosen}")
|
||||||
self._set(regs, inst.get("dst"), chosen)
|
self._set(regs, inst.get("dst"), chosen)
|
||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
|
|||||||
@ -487,6 +487,17 @@ impl MirBuilder {
|
|||||||
self.build_new_expression(class.clone(), arguments.clone())
|
self.build_new_expression(class.clone(), arguments.clone())
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ASTNode::ArrayLiteral { elements, .. } => {
|
||||||
|
// Lower: new ArrayBox(); for each elem: .push(elem)
|
||||||
|
let arr_id = self.value_gen.next();
|
||||||
|
self.emit_instruction(MirInstruction::NewBox { dst: arr_id, box_type: "ArrayBox".to_string(), args: vec![] })?;
|
||||||
|
for e in elements {
|
||||||
|
let v = self.build_expression(e)?;
|
||||||
|
self.emit_instruction(MirInstruction::BoxCall { dst: None, box_val: arr_id, method: "push".to_string(), method_id: None, args: vec![v], effects: super::EffectMask::MUT })?;
|
||||||
|
}
|
||||||
|
Ok(arr_id)
|
||||||
|
},
|
||||||
|
|
||||||
// Phase 7: Async operations
|
// Phase 7: Async operations
|
||||||
ASTNode::Nowait { variable, expression, .. } => {
|
ASTNode::Nowait { variable, expression, .. } => {
|
||||||
self.build_nowait_statement(variable.clone(), *expression.clone())
|
self.build_nowait_statement(variable.clone(), *expression.clone())
|
||||||
|
|||||||
@ -114,6 +114,28 @@ impl super::MirBuilder {
|
|||||||
ASTNode::New { class, arguments, .. } =>
|
ASTNode::New { class, arguments, .. } =>
|
||||||
self.build_new_expression(class.clone(), arguments.clone()),
|
self.build_new_expression(class.clone(), arguments.clone()),
|
||||||
|
|
||||||
|
ASTNode::ArrayLiteral { elements, .. } => {
|
||||||
|
let arr_id = self.value_gen.next();
|
||||||
|
self.emit_instruction(MirInstruction::NewBox { dst: arr_id, box_type: "ArrayBox".to_string(), args: vec![] })?;
|
||||||
|
for e in elements {
|
||||||
|
let v = self.build_expression_impl(e)?;
|
||||||
|
self.emit_instruction(MirInstruction::BoxCall { dst: None, box_val: arr_id, method: "push".to_string(), method_id: None, args: vec![v], effects: super::EffectMask::MUT })?;
|
||||||
|
}
|
||||||
|
Ok(arr_id)
|
||||||
|
}
|
||||||
|
ASTNode::MapLiteral { entries, .. } => {
|
||||||
|
let map_id = self.value_gen.next();
|
||||||
|
self.emit_instruction(MirInstruction::NewBox { dst: map_id, box_type: "MapBox".to_string(), args: vec![] })?;
|
||||||
|
for (k, expr) in entries {
|
||||||
|
// const string key
|
||||||
|
let k_id = self.value_gen.next();
|
||||||
|
self.emit_instruction(MirInstruction::Const { dst: k_id, value: ConstValue::String(k) })?;
|
||||||
|
let v_id = self.build_expression_impl(expr)?;
|
||||||
|
self.emit_instruction(MirInstruction::BoxCall { dst: None, box_val: map_id, method: "set".to_string(), method_id: None, args: vec![k_id, v_id], effects: super::EffectMask::MUT })?;
|
||||||
|
}
|
||||||
|
Ok(map_id)
|
||||||
|
}
|
||||||
|
|
||||||
ASTNode::Nowait { variable, expression, .. } =>
|
ASTNode::Nowait { variable, expression, .. } =>
|
||||||
self.build_nowait_statement(variable.clone(), *expression.clone()),
|
self.build_nowait_statement(variable.clone(), *expression.clone()),
|
||||||
|
|
||||||
|
|||||||
@ -42,6 +42,29 @@ pub struct LoopBuilder<'a> {
|
|||||||
continue_snapshots: Vec<(BasicBlockId, HashMap<String, ValueId>)>,
|
continue_snapshots: Vec<(BasicBlockId, HashMap<String, ValueId>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Local copy: detect a variable name assigned within an AST fragment
|
||||||
|
fn extract_assigned_var_local(ast: &ASTNode) -> Option<String> {
|
||||||
|
match ast {
|
||||||
|
ASTNode::Assignment { target, .. } => {
|
||||||
|
if let ASTNode::Variable { name, .. } = target.as_ref() { Some(name.clone()) } else { None }
|
||||||
|
}
|
||||||
|
ASTNode::Program { statements, .. } => statements.last().and_then(|st| extract_assigned_var_local(st)),
|
||||||
|
ASTNode::If { then_body, else_body, .. } => {
|
||||||
|
let then_prog = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() };
|
||||||
|
let tvar = extract_assigned_var_local(&then_prog);
|
||||||
|
let evar = else_body.as_ref().and_then(|eb| {
|
||||||
|
let ep = ASTNode::Program { statements: eb.clone(), span: crate::ast::Span::unknown() };
|
||||||
|
extract_assigned_var_local(&ep)
|
||||||
|
});
|
||||||
|
match (tvar, evar) {
|
||||||
|
(Some(tv), Some(ev)) if tv == ev => Some(tv),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> LoopBuilder<'a> {
|
impl<'a> LoopBuilder<'a> {
|
||||||
/// 新しいループビルダーを作成
|
/// 新しいループビルダーを作成
|
||||||
pub fn new(parent: &'a mut super::builder::MirBuilder) -> Self {
|
pub fn new(parent: &'a mut super::builder::MirBuilder) -> Self {
|
||||||
@ -325,6 +348,10 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
let merge_bb = self.new_block();
|
let merge_bb = self.new_block();
|
||||||
self.emit_branch(cond_val, then_bb, else_bb)?;
|
self.emit_branch(cond_val, then_bb, else_bb)?;
|
||||||
|
|
||||||
|
// Capture pre-if variable map (used for phi normalization)
|
||||||
|
let pre_if_var_map = self.get_current_variable_map();
|
||||||
|
let pre_then_var_value = pre_if_var_map.clone();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
self.set_current_block(then_bb)?;
|
self.set_current_block(then_bb)?;
|
||||||
for s in then_body.iter().cloned() {
|
for s in then_body.iter().cloned() {
|
||||||
@ -338,6 +365,7 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
};
|
};
|
||||||
if terminated { break; }
|
if terminated { break; }
|
||||||
}
|
}
|
||||||
|
let then_var_map_end = self.get_current_variable_map();
|
||||||
// Only jump to merge if not already terminated (e.g., continue/break)
|
// Only jump to merge if not already terminated (e.g., continue/break)
|
||||||
{
|
{
|
||||||
let cur_id = self.current_block()?;
|
let cur_id = self.current_block()?;
|
||||||
@ -351,7 +379,8 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
|
|
||||||
// else
|
// else
|
||||||
self.set_current_block(else_bb)?;
|
self.set_current_block(else_bb)?;
|
||||||
if let Some(es) = else_body {
|
let mut else_var_map_end_opt: Option<std::collections::HashMap<String, super::ValueId>> = None;
|
||||||
|
if let Some(es) = else_body.clone() {
|
||||||
for s in es.into_iter() {
|
for s in es.into_iter() {
|
||||||
let _ = self.build_statement(s)?;
|
let _ = self.build_statement(s)?;
|
||||||
let cur_id = self.current_block()?;
|
let cur_id = self.current_block()?;
|
||||||
@ -362,6 +391,7 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
};
|
};
|
||||||
if terminated { break; }
|
if terminated { break; }
|
||||||
}
|
}
|
||||||
|
else_var_map_end_opt = Some(self.get_current_variable_map());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let cur_id = self.current_block()?;
|
let cur_id = self.current_block()?;
|
||||||
@ -375,6 +405,29 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
|
|
||||||
// Continue at merge
|
// Continue at merge
|
||||||
self.set_current_block(merge_bb)?;
|
self.set_current_block(merge_bb)?;
|
||||||
|
// If both branches assign the same variable, emit phi and bind it
|
||||||
|
let then_prog = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() };
|
||||||
|
let assigned_then = extract_assigned_var_local(&then_prog);
|
||||||
|
let assigned_else = else_body.as_ref().and_then(|es| {
|
||||||
|
let ep = ASTNode::Program { statements: es.clone(), span: crate::ast::Span::unknown() };
|
||||||
|
extract_assigned_var_local(&ep)
|
||||||
|
});
|
||||||
|
if let Some(var_name) = assigned_then {
|
||||||
|
let else_assigns_same = assigned_else.as_ref().map(|s| s == &var_name).unwrap_or(false);
|
||||||
|
let then_value_for_var = then_var_map_end.get(&var_name).copied();
|
||||||
|
let else_value_for_var = if else_assigns_same {
|
||||||
|
else_var_map_end_opt.as_ref().and_then(|m| m.get(&var_name).copied())
|
||||||
|
} else {
|
||||||
|
pre_then_var_value.get(&var_name).copied()
|
||||||
|
};
|
||||||
|
if let (Some(tv), Some(ev)) = (then_value_for_var, else_value_for_var) {
|
||||||
|
let phi_id = self.new_value();
|
||||||
|
self.emit_phi_at_block_start(merge_bb, phi_id, vec![(then_bb, tv), (else_bb, ev)])?;
|
||||||
|
// Reset to pre-if map and bind the phi result
|
||||||
|
self.parent_builder.variable_map = pre_if_var_map.clone();
|
||||||
|
self.parent_builder.variable_map.insert(var_name, phi_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
let void_id = self.new_value();
|
let void_id = self.new_value();
|
||||||
self.emit_const(void_id, ConstValue::Void)?;
|
self.emit_const(void_id, ConstValue::Void)?;
|
||||||
Ok(void_id)
|
Ok(void_id)
|
||||||
|
|||||||
@ -631,9 +631,71 @@ impl NyashParser {
|
|||||||
Ok(expr)
|
Ok(expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 基本式をパース: リテラル、変数、括弧、this、new
|
/// 基本式をパース: リテラル、変数、括弧、this、new、配列リテラル(糖衣)
|
||||||
fn parse_primary(&mut self) -> Result<ASTNode, ParseError> {
|
fn parse_primary(&mut self) -> Result<ASTNode, ParseError> {
|
||||||
match &self.current_token().token_type {
|
match &self.current_token().token_type {
|
||||||
|
TokenType::LBRACK => {
|
||||||
|
// Array literal: [e1, e2, ...] (sugar)
|
||||||
|
let sugar_on = crate::parser::sugar_gate::is_enabled()
|
||||||
|
|| std::env::var("NYASH_ENABLE_ARRAY_LITERAL").ok().as_deref() == Some("1");
|
||||||
|
if !sugar_on {
|
||||||
|
let line = self.current_token().line;
|
||||||
|
return Err(ParseError::UnexpectedToken {
|
||||||
|
found: self.current_token().token_type.clone(),
|
||||||
|
expected: "enable NYASH_SYNTAX_SUGAR_LEVEL=basic|full or NYASH_ENABLE_ARRAY_LITERAL=1".to_string(),
|
||||||
|
line,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.advance(); // consume '['
|
||||||
|
let mut elems: Vec<ASTNode> = Vec::new();
|
||||||
|
while !self.match_token(&TokenType::RBRACK) && !self.is_at_end() {
|
||||||
|
must_advance!(self, _unused, "array literal element parsing");
|
||||||
|
let el = self.parse_expression()?;
|
||||||
|
elems.push(el);
|
||||||
|
if self.match_token(&TokenType::COMMA) { self.advance(); }
|
||||||
|
}
|
||||||
|
self.consume(TokenType::RBRACK)?;
|
||||||
|
Ok(ASTNode::ArrayLiteral { elements: elems, span: Span::unknown() })
|
||||||
|
}
|
||||||
|
TokenType::LBRACE => {
|
||||||
|
// Map literal (Stage‑2, string keys only)
|
||||||
|
let sugar_on = crate::parser::sugar_gate::is_enabled()
|
||||||
|
|| std::env::var("NYASH_ENABLE_MAP_LITERAL").ok().as_deref() == Some("1");
|
||||||
|
if !sugar_on {
|
||||||
|
let line = self.current_token().line;
|
||||||
|
return Err(ParseError::UnexpectedToken {
|
||||||
|
found: self.current_token().token_type.clone(),
|
||||||
|
expected: "enable NYASH_SYNTAX_SUGAR_LEVEL=basic|full or NYASH_ENABLE_MAP_LITERAL=1".to_string(),
|
||||||
|
line,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.advance(); // consume '{'
|
||||||
|
let mut entries: Vec<(String, ASTNode)> = Vec::new();
|
||||||
|
let sugar_level = std::env::var("NYASH_SYNTAX_SUGAR_LEVEL").ok();
|
||||||
|
let ident_key_on = std::env::var("NYASH_ENABLE_MAP_IDENT_KEY").ok().as_deref() == Some("1")
|
||||||
|
|| sugar_level.as_deref() == Some("full");
|
||||||
|
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||||
|
// Key: string literal (Stage‑2) or identifier key sugar (Stage‑3; gated)
|
||||||
|
let key = match &self.current_token().token_type {
|
||||||
|
TokenType::STRING(s) => { let v = s.clone(); self.advance(); v }
|
||||||
|
TokenType::IDENTIFIER(id) if ident_key_on => { let v = id.clone(); self.advance(); v }
|
||||||
|
_ => {
|
||||||
|
let line = self.current_token().line;
|
||||||
|
return Err(ParseError::UnexpectedToken {
|
||||||
|
found: self.current_token().token_type.clone(),
|
||||||
|
expected: if ident_key_on { "string or identifier key in map literal".to_string() } else { "string key in map literal".to_string() },
|
||||||
|
line,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.consume(TokenType::COLON)?;
|
||||||
|
let value_expr = self.parse_expression()?;
|
||||||
|
entries.push((key, value_expr));
|
||||||
|
if self.match_token(&TokenType::COMMA) { self.advance(); }
|
||||||
|
}
|
||||||
|
self.consume(TokenType::RBRACE)?;
|
||||||
|
Ok(ASTNode::MapLiteral { entries, span: Span::unknown() })
|
||||||
|
}
|
||||||
TokenType::INCLUDE => {
|
TokenType::INCLUDE => {
|
||||||
// Allow include as an expression: include "path"
|
// Allow include as an expression: include "path"
|
||||||
self.parse_include()
|
self.parse_include()
|
||||||
|
|||||||
@ -86,6 +86,12 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
|||||||
}
|
}
|
||||||
runner.execute_mir_mode(filename);
|
runner.execute_mir_mode(filename);
|
||||||
}
|
}
|
||||||
|
"vm" => {
|
||||||
|
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||||
|
println!("🚀 Nyash VM Backend - Executing file: {} 🚀", filename);
|
||||||
|
}
|
||||||
|
runner.execute_vm_mode(filename);
|
||||||
|
}
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
"jit-direct" => {
|
"jit-direct" => {
|
||||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||||
|
|||||||
@ -6,20 +6,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use nyash_rust::cli::CliConfig;
|
use nyash_rust::cli::CliConfig;
|
||||||
use nyash_rust::{
|
// prune heavy unused imports here; modules import what they need locally
|
||||||
box_trait::{StringBox, IntegerBox, BoolBox, VoidBox, AddBox},
|
// pruned unused runtime imports in this module
|
||||||
tokenizer::{NyashTokenizer},
|
|
||||||
ast::ASTNode,
|
|
||||||
parser::NyashParser,
|
|
||||||
interpreter::NyashInterpreter,
|
|
||||||
mir::{MirCompiler, MirPrinter, MirInstruction},
|
|
||||||
backend::VM,
|
|
||||||
};
|
|
||||||
use nyash_rust::runtime::{NyashRuntime, NyashRuntimeBuilder};
|
|
||||||
use nyash_rust::interpreter::SharedState;
|
|
||||||
use nyash_rust::box_factory::user_defined::UserDefinedBoxFactory;
|
|
||||||
use nyash_rust::core::model::BoxDeclaration as CoreBoxDecl;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[cfg(feature = "wasm-backend")]
|
#[cfg(feature = "wasm-backend")]
|
||||||
use nyash_rust::backend::{wasm::WasmBackend, aot::AotBackend};
|
use nyash_rust::backend::{wasm::WasmBackend, aot::AotBackend};
|
||||||
|
|||||||
@ -183,7 +183,7 @@ impl NyashRunner {
|
|||||||
// Prefer MIR signature when available, but fall back to runtime coercions to keep VM/JIT consistent.
|
// Prefer MIR signature when available, but fall back to runtime coercions to keep VM/JIT consistent.
|
||||||
let (ety, sval) = if let Some(func) = compile_result.module.functions.get("main") {
|
let (ety, sval) = if let Some(func) = compile_result.module.functions.get("main") {
|
||||||
use nyash_rust::mir::MirType;
|
use nyash_rust::mir::MirType;
|
||||||
use nyash_rust::box_trait::{NyashBox, IntegerBox, BoolBox, StringBox};
|
use nyash_rust::box_trait::{IntegerBox, BoolBox, StringBox};
|
||||||
use nyash_rust::boxes::FloatBox;
|
use nyash_rust::boxes::FloatBox;
|
||||||
match &func.signature.return_type {
|
match &func.signature.return_type {
|
||||||
MirType::Float => {
|
MirType::Float => {
|
||||||
@ -272,7 +272,7 @@ impl NyashRunner {
|
|||||||
format!("./{}", filename)
|
format!("./{}", filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::collections::{HashSet, VecDeque};
|
use std::collections::HashSet;
|
||||||
|
|
||||||
fn walk_with_state(node: &ASTNode, runtime: &NyashRuntime, stack: &mut Vec<String>, visited: &mut HashSet<String>) {
|
fn walk_with_state(node: &ASTNode, runtime: &NyashRuntime, stack: &mut Vec<String>, visited: &mut HashSet<String>) {
|
||||||
match node {
|
match node {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use super::types::{PluginBoxV2, PluginHandleInner, NyashTypeBoxFfi, LoadedPluginV2};
|
use super::types::{PluginBoxV2, PluginHandleInner, LoadedPluginV2};
|
||||||
use crate::bid::{BidResult, BidError};
|
use crate::bid::{BidResult, BidError};
|
||||||
use crate::box_trait::{NyashBox, BoxCore, StringBox, IntegerBox};
|
use crate::box_trait::NyashBox;
|
||||||
use crate::config::nyash_toml_v2::{NyashConfigV2, LibraryDefinition};
|
use crate::config::nyash_toml_v2::{NyashConfigV2, LibraryDefinition};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|||||||
@ -94,6 +94,8 @@ pub enum TokenType {
|
|||||||
DoubleColon, // :: (Parent::method) - P1用(定義のみ)
|
DoubleColon, // :: (Parent::method) - P1用(定義のみ)
|
||||||
LPAREN, // (
|
LPAREN, // (
|
||||||
RPAREN, // )
|
RPAREN, // )
|
||||||
|
LBRACK, // [
|
||||||
|
RBRACK, // ]
|
||||||
LBRACE, // {
|
LBRACE, // {
|
||||||
RBRACE, // }
|
RBRACE, // }
|
||||||
COMMA, // ,
|
COMMA, // ,
|
||||||
@ -395,6 +397,14 @@ impl NyashTokenizer {
|
|||||||
self.advance();
|
self.advance();
|
||||||
Ok(Token::new(TokenType::RPAREN, start_line, start_column))
|
Ok(Token::new(TokenType::RPAREN, start_line, start_column))
|
||||||
}
|
}
|
||||||
|
Some('[') => {
|
||||||
|
self.advance();
|
||||||
|
Ok(Token::new(TokenType::LBRACK, start_line, start_column))
|
||||||
|
}
|
||||||
|
Some(']') => {
|
||||||
|
self.advance();
|
||||||
|
Ok(Token::new(TokenType::RBRACK, start_line, start_column))
|
||||||
|
}
|
||||||
Some('{') => {
|
Some('{') => {
|
||||||
self.advance();
|
self.advance();
|
||||||
Ok(Token::new(TokenType::LBRACE, start_line, start_column))
|
Ok(Token::new(TokenType::LBRACE, start_line, start_column))
|
||||||
@ -407,14 +417,7 @@ impl NyashTokenizer {
|
|||||||
self.advance();
|
self.advance();
|
||||||
Ok(Token::new(TokenType::COMMA, start_line, start_column))
|
Ok(Token::new(TokenType::COMMA, start_line, start_column))
|
||||||
}
|
}
|
||||||
Some('?') => {
|
// '?' and ':' are handled earlier (including variants); avoid duplicate arms
|
||||||
self.advance();
|
|
||||||
Ok(Token::new(TokenType::QUESTION, start_line, start_column))
|
|
||||||
}
|
|
||||||
Some(':') => {
|
|
||||||
self.advance();
|
|
||||||
Ok(Token::new(TokenType::COLON, start_line, start_column))
|
|
||||||
}
|
|
||||||
Some('\n') => {
|
Some('\n') => {
|
||||||
self.advance();
|
self.advance();
|
||||||
Ok(Token::new(TokenType::NEWLINE, start_line, start_column))
|
Ok(Token::new(TokenType::NEWLINE, start_line, start_column))
|
||||||
|
|||||||
19
tools/pyvm_array_literal_smoke.sh
Normal file
19
tools/pyvm_array_literal_smoke.sh
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
run() {
|
||||||
|
NYASH_VM_USE_PY=1 NYASH_SYNTAX_SUGAR_LEVEL=basic "$BIN" --backend vm "$ROOT_DIR/apps/tests/array_literal_basic.nyash" 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
OUT=$(run || true)
|
||||||
|
echo "$OUT" | rg -q '^3$' && echo "✅ PyVM: array literal basic" || { echo "❌ PyVM: array literal basic"; echo "$OUT"; exit 1; }
|
||||||
|
|
||||||
|
echo "Array literal smoke PASS" >&2
|
||||||
|
|
||||||
20
tools/pyvm_map_literal_ident_smoke.sh
Normal file
20
tools/pyvm_map_literal_ident_smoke.sh
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
run() {
|
||||||
|
NYASH_VM_USE_PY=1 NYASH_SYNTAX_SUGAR_LEVEL=full NYASH_ENABLE_MAP_LITERAL=1 "$BIN" --backend vm "$ROOT_DIR/apps/tests/map_literal_ident_key.nyash" 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
OUT=$(run || true)
|
||||||
|
echo "$OUT" | rg -q '^2$' && echo "$OUT" | rg -q '^A$' \
|
||||||
|
&& echo "✅ PyVM: map literal ident-key" || { echo "❌ PyVM: map literal ident-key"; echo "$OUT"; exit 1; }
|
||||||
|
|
||||||
|
echo "Map literal ident-key smoke PASS" >&2
|
||||||
|
|
||||||
20
tools/pyvm_map_literal_smoke.sh
Normal file
20
tools/pyvm_map_literal_smoke.sh
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
run() {
|
||||||
|
NYASH_VM_USE_PY=1 NYASH_SYNTAX_SUGAR_LEVEL=basic NYASH_ENABLE_MAP_LITERAL=1 "$BIN" --backend vm "$ROOT_DIR/apps/tests/map_literal_basic.nyash" 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
OUT=$(run || true)
|
||||||
|
echo "$OUT" | rg -q '^2$' && echo "$OUT" | rg -q '^Alice$' \
|
||||||
|
&& echo "✅ PyVM: map literal basic" || { echo "❌ PyVM: map literal basic"; echo "$OUT"; exit 1; }
|
||||||
|
|
||||||
|
echo "Map literal smoke PASS" >&2
|
||||||
|
|
||||||
Reference in New Issue
Block a user