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/検証。配布やバンドル化は後回し(基礎固めが先)。
|
||||
|
||||
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 受理を追加)
|
||||
- 進捗ガード(parse_program2/parse_block2/parse_stmt2)。
|
||||
- Stage‑2 受理一式: 単項/二項/比較/論理/呼出/メソッド/引数/if/else/loop/using/local/return/new。
|
||||
@ -64,6 +79,22 @@ Notes / Policies
|
||||
- Bridge は JSON v0 → MIR 降下で PHI を生成(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)
|
||||
- runner/mod.rs(~70K chars): “runner pipeline” を用途別に分割(TODO #15)
|
||||
- runner/pipeline.rs(入力正規化/using解決/環境注入)
|
||||
@ -91,3 +122,38 @@ Recommended Next (short list)
|
||||
- `builder/vars.rs` に SSA 変数正規化の小物を段階追加(変数名再束縛/スコープ終端の型ヒント伝搬など)。
|
||||
- Runner(仕上げ)
|
||||
- `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*
|
||||
| '(' expr ')'
|
||||
| '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
|
||||
| '(' args? ')' ; function call
|
||||
@ -35,4 +39,6 @@ Notes
|
||||
- Short-circuit: '&&' and '||' must not evaluate the RHS when not needed.
|
||||
- Unary minus has higher precedence than '*' and '/'.
|
||||
- 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.
|
||||
|
||||
@ -9,6 +9,50 @@ Policy
|
||||
- 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.
|
||||
|
||||
## 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
|
||||
- Namespace: `using core.std` or `using core.std as Std`
|
||||
- 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
|
||||
- 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)
|
||||
@ -42,4 +108,3 @@ Runner Configuration
|
||||
Notes
|
||||
- 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.
|
||||
|
||||
|
||||
23
src/ast.rs
23
src/ast.rs
@ -201,6 +201,7 @@ pub enum ExpressionNode {
|
||||
else_expr: Box<ASTNode>,
|
||||
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>,
|
||||
span: Span,
|
||||
},
|
||||
/// 配列リテラル(糖衣): [e1, e2, ...]
|
||||
ArrayLiteral {
|
||||
elements: Vec<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
/// マップリテラル(糖衣): { "k": v, ... } (Stage‑2: 文字列キー限定)
|
||||
MapLiteral {
|
||||
entries: Vec<(String, ASTNode)>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// 無名関数(最小P1: 値としてのみ。呼び出しは未対応)
|
||||
Lambda {
|
||||
@ -708,6 +719,8 @@ impl ASTNode {
|
||||
ASTNode::QMarkPropagate { .. } => "QMarkPropagate",
|
||||
ASTNode::PeekExpr { .. } => "PeekExpr",
|
||||
ASTNode::Lambda { .. } => "Lambda",
|
||||
ASTNode::ArrayLiteral { .. } => "ArrayLiteral",
|
||||
ASTNode::MapLiteral { .. } => "MapLiteral",
|
||||
}
|
||||
}
|
||||
|
||||
@ -740,6 +753,8 @@ impl ASTNode {
|
||||
ASTNode::PeekExpr { .. } => ASTNodeType::Expression,
|
||||
ASTNode::QMarkPropagate { .. } => ASTNodeType::Expression,
|
||||
ASTNode::Lambda { .. } => ASTNodeType::Expression,
|
||||
ASTNode::ArrayLiteral { .. } => ASTNodeType::Expression,
|
||||
ASTNode::MapLiteral { .. } => ASTNodeType::Expression,
|
||||
|
||||
// Statement nodes - 実行可能なアクション
|
||||
ASTNode::Program { .. } => ASTNodeType::Statement, // プログラム全体
|
||||
@ -903,6 +918,12 @@ impl ASTNode {
|
||||
ASTNode::Lambda { params, body, .. } => {
|
||||
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::QMarkPropagate { span, .. } => *span,
|
||||
ASTNode::Lambda { span, .. } => *span,
|
||||
ASTNode::ArrayLiteral { span, .. } => *span,
|
||||
ASTNode::MapLiteral { span, .. } => *span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,9 +135,9 @@ impl VM {
|
||||
|
||||
/// Execute a single function
|
||||
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::instance_v2::InstanceBox;
|
||||
// use crate::instance_v2::InstanceBox; // not used in this path
|
||||
use super::control_flow;
|
||||
|
||||
self.current_function = Some(function.signature.name.clone());
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
use crate::box_trait::NyashBox;
|
||||
use crate::mir::ValueId;
|
||||
use crate::backend::vm::ControlFlow;
|
||||
use crate::backend::{VM, VMError, VMValue};
|
||||
@ -7,7 +6,7 @@ impl VM {
|
||||
/// Small helpers to reduce duplication in vtable stub paths.
|
||||
#[inline]
|
||||
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 {
|
||||
VMValue::Integer(i) => Box::new(IBox::new(*i)),
|
||||
VMValue::String(s) => Box::new(SBox::new(s)),
|
||||
@ -900,3 +899,4 @@ impl VM {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
use crate::box_trait::NyashBox;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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 std::sync::Arc;
|
||||
use crate::backend::vm::ControlFlow;
|
||||
|
||||
@ -12,7 +12,7 @@ use crate::mir::{BasicBlockId, ValueId};
|
||||
use crate::runtime::NyashRuntime;
|
||||
use crate::scope_tracker::ScopeTracker;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Instant;
|
||||
// use std::time::Instant; // not used in this module
|
||||
|
||||
impl VM {
|
||||
fn jit_threshold_from_env() -> u32 {
|
||||
|
||||
@ -47,7 +47,7 @@ impl NyashInterpreter {
|
||||
}
|
||||
"MathBox" => {
|
||||
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.
|
||||
// Here we simply return void; method paths should prefer plugin invoke in VM.
|
||||
return Ok(Box::new(VoidBox::new()));
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::mir::{MirFunction, MirInstruction, ConstValue, BinaryOp, CompareOp, ValueId};
|
||||
use super::builder::{IRBuilder, BinOpKind, CmpKind};
|
||||
use crate::mir::{MirFunction, MirInstruction, ConstValue, ValueId};
|
||||
use super::builder::{IRBuilder, BinOpKind};
|
||||
|
||||
mod analysis;
|
||||
mod cfg;
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
use std::collections::{HashMap, HashSet, BTreeSet};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
|
||||
|
||||
use super::super::builder::IRBuilder;
|
||||
use super::super::core_ops; // ensure module link remains
|
||||
// removed unused imports
|
||||
use super::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 = inst.get("incoming", [])
|
||||
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:
|
||||
if not isinstance(pair, (list, tuple)) or len(pair) < 2:
|
||||
continue
|
||||
@ -134,10 +137,8 @@ class PyVM:
|
||||
# Case 1: [vid, pred]
|
||||
if prev is not None and int(b) == int(prev) and int(a) in regs:
|
||||
chosen = regs.get(int(a))
|
||||
break
|
||||
# Case 2: [pred, vid]
|
||||
if prev is not None and int(a) == int(prev) and int(b) in regs:
|
||||
chosen = regs.get(int(b))
|
||||
if dbg:
|
||||
print(f"[pyvm.phi] case1 match: use v{a} from pred {b} -> {chosen}")
|
||||
break
|
||||
if chosen is None and incoming:
|
||||
# Fallback to first element that resolves to a known vid
|
||||
@ -147,8 +148,9 @@ class PyVM:
|
||||
a, b = pair[0], pair[1]
|
||||
if int(a) in regs:
|
||||
chosen = regs.get(int(a)); break
|
||||
if int(b) in regs:
|
||||
chosen = regs.get(int(b)); break
|
||||
# Do not try to resolve by assuming [pred, vid] — avoid false matches
|
||||
if dbg:
|
||||
print(f"[pyvm.phi] chosen={chosen}")
|
||||
self._set(regs, inst.get("dst"), chosen)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
@ -487,6 +487,17 @@ impl MirBuilder {
|
||||
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
|
||||
ASTNode::Nowait { variable, expression, .. } => {
|
||||
self.build_nowait_statement(variable.clone(), *expression.clone())
|
||||
|
||||
@ -114,6 +114,28 @@ impl super::MirBuilder {
|
||||
ASTNode::New { class, arguments, .. } =>
|
||||
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, .. } =>
|
||||
self.build_nowait_statement(variable.clone(), *expression.clone()),
|
||||
|
||||
|
||||
@ -42,6 +42,29 @@ pub struct LoopBuilder<'a> {
|
||||
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> {
|
||||
/// 新しいループビルダーを作成
|
||||
pub fn new(parent: &'a mut super::builder::MirBuilder) -> Self {
|
||||
@ -325,6 +348,10 @@ impl<'a> LoopBuilder<'a> {
|
||||
let merge_bb = self.new_block();
|
||||
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
|
||||
self.set_current_block(then_bb)?;
|
||||
for s in then_body.iter().cloned() {
|
||||
@ -338,6 +365,7 @@ impl<'a> LoopBuilder<'a> {
|
||||
};
|
||||
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)
|
||||
{
|
||||
let cur_id = self.current_block()?;
|
||||
@ -351,7 +379,8 @@ impl<'a> LoopBuilder<'a> {
|
||||
|
||||
// else
|
||||
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() {
|
||||
let _ = self.build_statement(s)?;
|
||||
let cur_id = self.current_block()?;
|
||||
@ -362,6 +391,7 @@ impl<'a> LoopBuilder<'a> {
|
||||
};
|
||||
if terminated { break; }
|
||||
}
|
||||
else_var_map_end_opt = Some(self.get_current_variable_map());
|
||||
}
|
||||
{
|
||||
let cur_id = self.current_block()?;
|
||||
@ -375,6 +405,29 @@ impl<'a> LoopBuilder<'a> {
|
||||
|
||||
// Continue at merge
|
||||
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();
|
||||
self.emit_const(void_id, ConstValue::Void)?;
|
||||
Ok(void_id)
|
||||
|
||||
@ -631,9 +631,71 @@ impl NyashParser {
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
/// 基本式をパース: リテラル、変数、括弧、this、new
|
||||
/// 基本式をパース: リテラル、変数、括弧、this、new、配列リテラル(糖衣)
|
||||
fn parse_primary(&mut self) -> Result<ASTNode, ParseError> {
|
||||
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 => {
|
||||
// Allow include as an expression: include "path"
|
||||
self.parse_include()
|
||||
|
||||
@ -86,6 +86,12 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
||||
}
|
||||
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")]
|
||||
"jit-direct" => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
|
||||
@ -6,20 +6,8 @@
|
||||
*/
|
||||
|
||||
use nyash_rust::cli::CliConfig;
|
||||
use nyash_rust::{
|
||||
box_trait::{StringBox, IntegerBox, BoolBox, VoidBox, AddBox},
|
||||
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;
|
||||
// prune heavy unused imports here; modules import what they need locally
|
||||
// pruned unused runtime imports in this module
|
||||
|
||||
#[cfg(feature = "wasm-backend")]
|
||||
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.
|
||||
let (ety, sval) = if let Some(func) = compile_result.module.functions.get("main") {
|
||||
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;
|
||||
match &func.signature.return_type {
|
||||
MirType::Float => {
|
||||
@ -272,7 +272,7 @@ impl NyashRunner {
|
||||
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>) {
|
||||
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::box_trait::{NyashBox, BoxCore, StringBox, IntegerBox};
|
||||
use crate::box_trait::NyashBox;
|
||||
use crate::config::nyash_toml_v2::{NyashConfigV2, LibraryDefinition};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
@ -94,6 +94,8 @@ pub enum TokenType {
|
||||
DoubleColon, // :: (Parent::method) - P1用(定義のみ)
|
||||
LPAREN, // (
|
||||
RPAREN, // )
|
||||
LBRACK, // [
|
||||
RBRACK, // ]
|
||||
LBRACE, // {
|
||||
RBRACE, // }
|
||||
COMMA, // ,
|
||||
@ -395,6 +397,14 @@ impl NyashTokenizer {
|
||||
self.advance();
|
||||
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('{') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::LBRACE, start_line, start_column))
|
||||
@ -407,14 +417,7 @@ impl NyashTokenizer {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::COMMA, start_line, start_column))
|
||||
}
|
||||
Some('?') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::QUESTION, start_line, start_column))
|
||||
}
|
||||
Some(':') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::COLON, start_line, start_column))
|
||||
}
|
||||
// '?' and ':' are handled earlier (including variants); avoid duplicate arms
|
||||
Some('\n') => {
|
||||
self.advance();
|
||||
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