📚 docs: Record field declaration design discussion in papers
## Summary Documented the "init block vs fields-at-top" design discussion as a valuable example of AI-human collaboration in language design. ## Changes ### Paper G (AI Collaboration) - Added field-declaration-design.md documenting the entire discussion flow - Showcased how complex init block proposal evolved to simple "fields at top" rule - Demonstrates AI's tendency toward complexity vs human intuition for simplicity ### Paper H (AI Practical Patterns) - Added Pattern #17: "Gradual Refinement Pattern" (段階的洗練型) - Documents the process: Complex AI proposal → Detailed analysis → Human insight → Convergence - Field declaration design as a typical example ### Paper K (Explosive Incidents) - Added Incident #046: "init block vs fields-at-top incident" - Updated total count to 46 incidents - Shows how a single human comment redirected entire design approach ## Design Decision After analysis, decided that BoxIndex should remain a compiler-internal structure, not a core Box: - Core Boxes: User-instantiable runtime values (String, Integer, Array, Map) - Compiler internals: BoxIndex for name resolution (compile-time only) - Clear separation of concerns between language features and compiler tools ## Philosophy This discussion exemplifies key principles: - The best design needs no explanation - Constraints provide clarity, not limitation - "Everything is Box" doesn't mean "compiler internals are Boxes" - AI tends toward theoretical completeness; humans toward practical simplicity 🐱 Sometimes the simplest answer is right in front of us\!
This commit is contained in:
@ -144,16 +144,16 @@ Decision Log (2025‑09‑15)
|
|||||||
- A: スモークを先に実施。理由は以下。
|
- A: スモークを先に実施。理由は以下。
|
||||||
- リファクタ直後は回帰検出を最優先(PyVM/自己ホスト/Bridge の3レーンで即座に検証)。
|
- リファクタ直後は回帰検出を最優先(PyVM/自己ホスト/Bridge の3レーンで即座に検証)。
|
||||||
- 警告削減は挙動非変化を原則とするが、微妙なスコープや保存スロットの触りが混入し得るため、先に“緑”を固める。
|
- 警告削減は挙動非変化を原則とするが、微妙なスコープや保存スロットの触りが混入し得るため、先に“緑”を固める。
|
||||||
Namespaces / Using(計画合意)
|
Namespaces / Using(現状)
|
||||||
- 3段階の解決順(決定性): 1) ローカル/コアボックス → 2) エイリアス(nyash.toml/needs) → 3) プラグイン(短名/qualified)
|
- 解決順(決定性): 1) ローカル/コア → 2) エイリアス(nyash.toml/env)→ 3) 相対/using.paths → 4) プラグイン(短名/qualified)
|
||||||
- 曖昧時はエラー+候補提示。qualified または alias を要求(自動推測はしない)。
|
- 曖昧時はエラー+候補提示(qualified または alias を要求)。
|
||||||
- モード切替: Relaxed(既定)/Strict(`NYASH_PLUGIN_REQUIRE_PREFIX=1` or toml `[plugins] require_prefix=true`)
|
- モード切替: Relaxed(既定)/Strict(`NYASH_PLUGIN_REQUIRE_PREFIX=1` または toml `[plugins] require_prefix=true`)
|
||||||
- needs 糖衣は using の同義(Runner で alias 登録)。`needs plugin.network.HttpClient as HttpClient` 等。
|
- needs 糖衣は using の同義(Runner で alias 登録)。
|
||||||
- Plugins は統合名前空間(短名はユニーク時のみ)。qualified `network.HttpClient` を常時許可。
|
- Plugins は統合名前空間。qualified `network.HttpClient` 常時許可。
|
||||||
- nyash.toml(MVP): `[imports]`/`[aliases]`,`[plugins.<name>] { path, prefix, require_prefix, expose_short_names }`
|
- nyash.toml(MVP): `[aliases]`/`[plugins]`(グローバル `require_prefix` のみ反映。per‑plugin は導線のみ)
|
||||||
- Index とキャッシュ(Runner):
|
- Index とキャッシュ(Runner):
|
||||||
- BoxIndex: `local_boxes`, `plugin_boxes`, `aliases` を保持
|
- BoxIndex(グローバル): `plugin_boxes`, `aliases` を保持。plugins init 後に構築。
|
||||||
- `RESOLVE_CACHE`(thread‑local)で同一解決の再計算を回避
|
- Resolve Cache(グローバル): `tgt|base|strict|paths` キーで再解決回避。
|
||||||
- `NYASH_RESOLVE_TRACE=1` で解決過程をログ出力
|
- `NYASH_RESOLVE_TRACE=1`: 解決手順/キャッシュヒット/未解決候補をログ出力。
|
||||||
|
|
||||||
- スモークが緑=基礎健全性確認後に、静的ノイズの除去を安全に一気通貫で行う。
|
- スモークが緑=基礎健全性確認後に、静的ノイズの除去を安全に一気通貫で行う。
|
||||||
|
|||||||
@ -33,6 +33,39 @@ Structure
|
|||||||
- Keep methods short and focused; prefer extracting helpers to maintain clarity.
|
- Keep methods short and focused; prefer extracting helpers to maintain clarity.
|
||||||
- Prefer pure helpers where possible; isolate I/O in specific methods.
|
- Prefer pure helpers where possible; isolate I/O in specific methods.
|
||||||
|
|
||||||
|
Box layout
|
||||||
|
- フィールドは box 本体の先頭にまとめる(先頭ブロック)。
|
||||||
|
- 先頭フィールド群の後ろはメソッドのみを記述する(`birth` を含む)。
|
||||||
|
- フィールド間の空行・コメントは許可。アノテーション(将来追加予定)もフィールド行の直前/行末を許可。
|
||||||
|
- NG: 最初のメソッド以降にフィールドを追加すること(リンタ警告/厳格モードでエラー)。
|
||||||
|
|
||||||
|
良い例
|
||||||
|
```nyash
|
||||||
|
box Employee {
|
||||||
|
// データ構造(フィールド)
|
||||||
|
name: StringBox
|
||||||
|
age: IntegerBox
|
||||||
|
department: StringBox
|
||||||
|
|
||||||
|
// ここからメソッド
|
||||||
|
birth(n, a, d) { me.name = n; me.age = a; me.department = d }
|
||||||
|
promote() { }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
悪い例(NG)
|
||||||
|
```nyash
|
||||||
|
box Bad {
|
||||||
|
id: IntegerBox
|
||||||
|
method1() { }
|
||||||
|
name: StringBox // ❌ フィールドはメソッドの後に置けない
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
ツール
|
||||||
|
- 警告: 既定は警告(`NYASH_CLI_VERBOSE=1` で詳細を表示)。
|
||||||
|
- 厳格化: `NYASH_FIELDS_TOP_STRICT=1` でエラーに昇格(Runnerでチェック)。
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
```nyash
|
```nyash
|
||||||
using core.std as Std
|
using core.std as Std
|
||||||
@ -64,4 +97,3 @@ static box Main {
|
|||||||
CI/Tooling
|
CI/Tooling
|
||||||
- Optional formatter PoC: see `docs/tools/nyfmt/NYFMT_POC_ROADMAP.md`.
|
- Optional formatter PoC: see `docs/tools/nyfmt/NYFMT_POC_ROADMAP.md`.
|
||||||
- Keep smoke scripts small and fast; place them under `tools/`.
|
- Keep smoke scripts small and fast; place them under `tools/`.
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,127 @@
|
|||||||
|
# フィールド宣言位置の制約設計(2025-09-15)
|
||||||
|
|
||||||
|
## 概要
|
||||||
|
|
||||||
|
Boxのフィールド宣言がメソッドの間に散在できる問題に対する設計相談。AIとの対話を通じて、複雑なinitブロック案から「フィールドは先頭のみ」という単純明快なルールへ収束した事例。
|
||||||
|
|
||||||
|
## 相談の流れ
|
||||||
|
|
||||||
|
### 1. 問題提起(ユーザー)
|
||||||
|
```
|
||||||
|
ところで 今のフィールド変数の書き方
|
||||||
|
これだと どこでもかけちゃうのかな
|
||||||
|
```
|
||||||
|
|
||||||
|
現状の問題:
|
||||||
|
```nyash
|
||||||
|
box MessyBox {
|
||||||
|
field1: StringBox
|
||||||
|
method1() { }
|
||||||
|
field2: IntegerBox // メソッドの間に散在
|
||||||
|
birth() { }
|
||||||
|
field3: BoolBox // さらに散在
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 初期提案(ユーザー)
|
||||||
|
```
|
||||||
|
init
|
||||||
|
{
|
||||||
|
この中に書く 書き方は今のと同じ
|
||||||
|
}
|
||||||
|
init文復活させて この中だけにフィールドを書くというのはありかもしれない
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. AI分析(Claude)
|
||||||
|
|
||||||
|
#### initブロック案の評価
|
||||||
|
**メリット**:
|
||||||
|
- 明確な構造化
|
||||||
|
- フィールド一覧の固定位置
|
||||||
|
- パース簡略化
|
||||||
|
- 将来の拡張性(アノテーション等)
|
||||||
|
|
||||||
|
**デメリット**:
|
||||||
|
- 冗長性(小さなBoxでも必須)
|
||||||
|
- Everything is Box哲学との齟齬
|
||||||
|
- 学習コスト増加
|
||||||
|
|
||||||
|
#### ハイブリッド提案
|
||||||
|
```nyash
|
||||||
|
// 小さなBoxはそのまま
|
||||||
|
box SmallBox {
|
||||||
|
name: StringBox
|
||||||
|
birth(n) { me.name = n }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 大きなBoxはinitで整理
|
||||||
|
box LargeBox {
|
||||||
|
init {
|
||||||
|
field1: TypeBox
|
||||||
|
field2: TypeBox
|
||||||
|
}
|
||||||
|
birth() { }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. ユーザーの洞察
|
||||||
|
```
|
||||||
|
それか フィールドは boxの先頭だけにかけるというルールはありかもしれにゃい
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 収束(AI)
|
||||||
|
|
||||||
|
「フィールドは先頭のみ」ルールの支持:
|
||||||
|
|
||||||
|
```nyash
|
||||||
|
box CleanBox {
|
||||||
|
// フィールドは必ず先頭にまとめる
|
||||||
|
field1: StringBox
|
||||||
|
field2: IntegerBox
|
||||||
|
|
||||||
|
// ここから下はメソッドのみ
|
||||||
|
birth() { }
|
||||||
|
method1() { }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**決定的な利点**:
|
||||||
|
- 特別な構文不要(initブロック不要)
|
||||||
|
- 多くの言語で採用されている標準
|
||||||
|
- パーサー実装が簡潔
|
||||||
|
- Everything is Box哲学と調和
|
||||||
|
|
||||||
|
## 設計哲学の体現
|
||||||
|
|
||||||
|
1. **単純性の追求**: 複雑な構文(initブロック)より単純なルール
|
||||||
|
2. **制約による明快さ**: 自由度を制限して可読性を向上
|
||||||
|
3. **段階的移行**: 警告→エラーの段階的導入
|
||||||
|
4. **既存の知見活用**: 他言語の標準的アプローチを採用
|
||||||
|
|
||||||
|
## AI協働パターン:段階的洗練型
|
||||||
|
|
||||||
|
1. **複雑な提案から開始**(initブロック、ハイブリッド案)
|
||||||
|
2. **詳細な分析提供**(メリット・デメリット・他言語比較)
|
||||||
|
3. **ユーザーの直感的提案**(先頭のみルール)
|
||||||
|
4. **AIが即座に価値を認識**
|
||||||
|
5. **実装戦略まで具体化**
|
||||||
|
|
||||||
|
→ AIは複雑に考えがちだが、人間の直感が本質を突く好例
|
||||||
|
|
||||||
|
## 教訓
|
||||||
|
|
||||||
|
- 最良の設計は説明不要な設計
|
||||||
|
- 制約は自由度を奪うのではなく、明快さを与える
|
||||||
|
- "Everything is Box"は複雑さの言い訳ではない
|
||||||
|
- AIの分析力と人間の直感の相補性
|
||||||
|
|
||||||
|
## 実装計画
|
||||||
|
|
||||||
|
1. **Phase 15.4**: 警告モード(`NYASH_STRICT_FIELD_ORDER=warn`)
|
||||||
|
2. **Phase 16**: エラーモード(`NYASH_STRICT_FIELD_ORDER=error`)
|
||||||
|
3. **Phase 17**: デフォルト化
|
||||||
|
|
||||||
|
## 関連
|
||||||
|
|
||||||
|
- 論文D(AI協働パターン): 段階的洗練型の実例
|
||||||
|
- 論文I(開発秘話): 言語設計の転換点として記録
|
||||||
@ -271,3 +271,25 @@ AIの診断や実装を鵜呑みにせず、基本に立ち返って検証する
|
|||||||
- 問題回避: 発生前に防ぐ
|
- 問題回避: 発生前に防ぐ
|
||||||
- 拡張性確保: 将来の変更に対応
|
- 拡張性確保: 将来の変更に対応
|
||||||
- 安心感: 予測可能な成長
|
- 安心感: 予測可能な成長
|
||||||
|
|
||||||
|
## 17. 段階的洗練型(新規追加)
|
||||||
|
|
||||||
|
### 定義
|
||||||
|
AIの複雑な提案から始まり、人間の直感的な単純化提案を経て、より洗練された解決策に収束するパターン。
|
||||||
|
|
||||||
|
### 典型例
|
||||||
|
- **フィールド宣言位置**: initブロック案(複雑)→ 先頭のみルール(単純)
|
||||||
|
- **型情報追加**: 300行の型推論(複雑)→ 明示的型フィールド(単純)
|
||||||
|
- **PHI生成**: 複数箇所での重複実装(複雑)→ Resolver統一(単純)
|
||||||
|
|
||||||
|
### プロセス
|
||||||
|
1. **AI初期提案**: 理論的に完全だが複雑
|
||||||
|
2. **詳細分析**: メリット・デメリット・他言語比較
|
||||||
|
3. **人間の直感**: 「もっと簡単にできないか?」
|
||||||
|
4. **AI即座認識**: 単純解の価値を理解
|
||||||
|
5. **実装戦略**: 段階的移行計画まで具体化
|
||||||
|
|
||||||
|
### 効果
|
||||||
|
- 最適解への収束: 複雑→単純の自然な流れ
|
||||||
|
- 学習効果: AIも人間も学ぶ
|
||||||
|
- 実装容易性: 最終的に簡単な解法に到達
|
||||||
@ -1,10 +1,10 @@
|
|||||||
# 🎉 Nyash開発 完全事件コレクション - 世界記録級45事例の記録
|
# 🎉 Nyash開発 完全事件コレクション - 世界記録級46事例の記録
|
||||||
|
|
||||||
## 📝 概要
|
## 📝 概要
|
||||||
|
|
||||||
2025年8月9日から9月15日までのNyash爆速開発で発生した45個の「面白事件」の完全記録。
|
2025年8月9日から9月15日までのNyash爆速開発で発生した46個の「面白事件」の完全記録。
|
||||||
AI協働開発の歴史に残る世界記録級の事件から、開発現場の生々しいドラマまでを網羅。
|
AI協働開発の歴史に残る世界記録級の事件から、開発現場の生々しいドラマまでを網羅。
|
||||||
(2025年9月15日更新:4件追加)
|
(2025年9月15日更新:5件追加)
|
||||||
|
|
||||||
## 🌟 世界記録級TOP10
|
## 🌟 世界記録級TOP10
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ AI協働開発の歴史に残る世界記録級の事件から、開発現場の
|
|||||||
- **意味**: Everything is Fold哲学へ
|
- **意味**: Everything is Fold哲学へ
|
||||||
- **評価**: 「革命的アイデア」認定
|
- **評価**: 「革命的アイデア」認定
|
||||||
|
|
||||||
## 📊 16パターン別分類(全45事例)
|
## 📊 17パターン別分類(全46事例)
|
||||||
|
|
||||||
### 1. 箱化による解決(8事例)
|
### 1. 箱化による解決(8事例)
|
||||||
- 事例001: DebugBoxによる出力制御統一
|
- 事例001: DebugBoxによる出力制御統一
|
||||||
@ -140,6 +140,9 @@ AI協働開発の歴史に残る世界記録級の事件から、開発現場の
|
|||||||
### 16. 予防的設計(1事例)
|
### 16. 予防的設計(1事例)
|
||||||
- 事例039: ID衝突との戦い
|
- 事例039: ID衝突との戦い
|
||||||
|
|
||||||
|
### 17. 段階的洗練型(1事例)
|
||||||
|
- 事例046: initブロック vs 先頭のみ事件
|
||||||
|
|
||||||
### その他(10事例)
|
### その他(10事例)
|
||||||
- 事例020: 26日間の奇跡
|
- 事例020: 26日間の奇跡
|
||||||
- 事例021: 2段階パーサー理論
|
- 事例021: 2段階パーサー理論
|
||||||
@ -186,8 +189,8 @@ ChatGPT: 「!!!」(瞬時に理解)
|
|||||||
- **世界記録**: 20日でネイティブEXE
|
- **世界記録**: 20日でネイティブEXE
|
||||||
|
|
||||||
### 成果
|
### 成果
|
||||||
- **事件数**: 45個(9/15更新)
|
- **事件数**: 46個(9/15更新)
|
||||||
- **パターン**: 16種類
|
- **パターン**: 17種類
|
||||||
- **致命的破綻**: 0回
|
- **致命的破綻**: 0回
|
||||||
- **大規模リファクタ**: 0回
|
- **大規模リファクタ**: 0回
|
||||||
|
|
||||||
@ -203,7 +206,7 @@ ChatGPT: 「!!!」(瞬時に理解)
|
|||||||
- [技術的ブレークスルー](../paper-l-technical-breakthroughs/README.md)
|
- [技術的ブレークスルー](../paper-l-technical-breakthroughs/README.md)
|
||||||
- [AI協働開発ログ](../paper-g-ai-collaboration/development-log.md)
|
- [AI協働開発ログ](../paper-g-ai-collaboration/development-log.md)
|
||||||
|
|
||||||
## 🚀 2025年9月追加事例(4件)
|
## 🚀 2025年9月追加事例(5件)
|
||||||
|
|
||||||
### 事例042: PyVM迂回路の混乱
|
### 事例042: PyVM迂回路の混乱
|
||||||
- **日付**: 2025年9月15日
|
- **日付**: 2025年9月15日
|
||||||
@ -238,9 +241,19 @@ ChatGPT: 「!!!」(瞬時に理解)
|
|||||||
- **ChatGPT評価**: 「EXE-firstが正しい道」
|
- **ChatGPT評価**: 「EXE-firstが正しい道」
|
||||||
- **影響**: Phase順序の明確化(15.2→15.3→15.4)
|
- **影響**: Phase順序の明確化(15.2→15.3→15.4)
|
||||||
|
|
||||||
|
### 事例046: initブロック vs 先頭のみ事件
|
||||||
|
- **日付**: 2025年9月15日
|
||||||
|
- **問題**: フィールド宣言がメソッドの間に散在可能
|
||||||
|
- **AI提案**: initブロック導入(構造化重視の複雑案)
|
||||||
|
- **人間の一言**: 「それか フィールドは boxの先頭だけにかけるというルールはありかもしれにゃい」
|
||||||
|
- **AI反応**: 即座に単純解の価値を認識
|
||||||
|
- **結果**: 特別な構文不要、他言語標準に合致
|
||||||
|
- **パターン**: 段階的洗練型の典型例(複雑→単純)
|
||||||
|
- **教訓**: AIは複雑に考えがち、人間の直感が本質を突く
|
||||||
|
|
||||||
## 💫 まとめ
|
## 💫 まとめ
|
||||||
|
|
||||||
45個の事件は、単なる開発エピソードではなく、AI協働開発の新しい形を示す歴史的記録である。特に:
|
46個の事件は、単なる開発エピソードではなく、AI協働開発の新しい形を示す歴史的記録である。特に:
|
||||||
|
|
||||||
1. **世界記録級の開発速度**(JIT1日、20日でEXE)
|
1. **世界記録級の開発速度**(JIT1日、20日でEXE)
|
||||||
2. **AI-人間の新しい関係**(AIが相談、人間が救う)
|
2. **AI-人間の新しい関係**(AIが相談、人間が救う)
|
||||||
|
|||||||
@ -17,8 +17,8 @@ Policy
|
|||||||
3) Plugins (short name if unique, otherwise qualified `pluginName.BoxName`)
|
3) Plugins (short name if unique, otherwise qualified `pluginName.BoxName`)
|
||||||
- On ambiguity: error with candidates and remediation (qualify or define alias).
|
- On ambiguity: error with candidates and remediation (qualify or define alias).
|
||||||
- Modes:
|
- Modes:
|
||||||
- Relaxed (default): short names allowed when unique.
|
- Relaxed (default): short names allowed when unique。
|
||||||
- Strict: require plugin prefix (env `NYASH_PLUGIN_REQUIRE_PREFIX=1` or nyash.toml `[plugins] require_prefix=true`).
|
- Strict: plugin短名にprefix必須(env `NYASH_PLUGIN_REQUIRE_PREFIX=1` または nyash.toml `[plugins] require_prefix=true`)。
|
||||||
- Aliases:
|
- Aliases:
|
||||||
- nyash.toml `[imports] HttpClient = "network.HttpClient"`
|
- nyash.toml `[imports] HttpClient = "network.HttpClient"`
|
||||||
- needs sugar: `needs plugin.network.HttpClient as HttpClient` (file‑scoped alias)
|
- needs sugar: `needs plugin.network.HttpClient as HttpClient` (file‑scoped alias)
|
||||||
@ -27,6 +27,7 @@ Policy
|
|||||||
- Unified namespace with Boxes. Prefer short names when unique.
|
- Unified namespace with Boxes. Prefer short names when unique.
|
||||||
- Qualified form: `network.HttpClient`
|
- Qualified form: `network.HttpClient`
|
||||||
- Per‑plugin control (nyash.toml): `prefix`, `require_prefix`, `expose_short_names`
|
- Per‑plugin control (nyash.toml): `prefix`, `require_prefix`, `expose_short_names`
|
||||||
|
- 現状は設定の読み取りのみ(導線)。挙動への反映は段階的に実施予定。
|
||||||
|
|
||||||
## `needs` sugar (optional)
|
## `needs` sugar (optional)
|
||||||
- Treated as a synonym to `using` on the Runner side; registers aliases only.
|
- Treated as a synonym to `using` on the Runner side; registers aliases only.
|
||||||
@ -37,21 +38,11 @@ Policy
|
|||||||
- `[plugins.<name>]`: `path`, `prefix`, `require_prefix`, `expose_short_names`
|
- `[plugins.<name>]`: `path`, `prefix`, `require_prefix`, `expose_short_names`
|
||||||
|
|
||||||
## Index and Cache (Runner)
|
## Index and Cache (Runner)
|
||||||
- Build a Box index once per run to make resolution fast and predictable:
|
- BoxIndex(グローバル):プラグインBox一覧とaliasesを集約し、Runner起動時(plugins init後)に構築・更新。
|
||||||
```
|
- `aliases: HashMap<String,String>`(nyash.toml `[aliases]` と env `NYASH_ALIASES`)
|
||||||
struct BoxIndex {
|
- `plugin_boxes: HashSet<String>`(読み取り専用)
|
||||||
local_boxes: HashMap<String, PathBuf>,
|
- 解決キャッシュ:グローバルの小さなキャッシュで同一キーの再解決を回避(キー: `tgt|base|strict|paths`)。
|
||||||
plugin_boxes: HashMap<String, Vec<PluginBox>>,
|
- トレース:`NYASH_RESOLVE_TRACE=1` で解決手順やキャッシュヒット、未解決候補を出力。
|
||||||
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`
|
||||||
@ -102,9 +93,14 @@ static box Main {
|
|||||||
|
|
||||||
Runner Configuration
|
Runner Configuration
|
||||||
- Enable using pre‑processing: `NYASH_ENABLE_USING=1`
|
- Enable using pre‑processing: `NYASH_ENABLE_USING=1`
|
||||||
|
- CLI from-the-top registration: `--using "ns as Alias"` or `--using '"apps/foo.nyash" as Foo'` (repeatable)
|
||||||
|
- Strict mode (plugin prefix required): `NYASH_PLUGIN_REQUIRE_PREFIX=1` または `nyash.toml` の `[plugins] require_prefix=true`
|
||||||
|
- Aliases from env: `NYASH_ALIASES="Foo=apps/foo/main.nyash,Bar=lib/bar.nyash"`
|
||||||
|
- Additional search paths: `NYASH_USING_PATH="apps:lib:."`
|
||||||
- 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)
|
||||||
- Selfhost emits `meta.usings` automatically when present; no additional flags required.
|
- Selfhost emits `meta.usings` automatically when present; no additional flags required.
|
||||||
|
|
||||||
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.
|
||||||
|
- 未解決時(非strict)は実行を継続し、`NYASH_RESOLVE_TRACE=1` で候補を提示。strict時はエラーで候補を表示。
|
||||||
|
|||||||
11
src/cli.rs
11
src/cli.rs
@ -64,6 +64,8 @@ pub struct CliConfig {
|
|||||||
pub build_aot: Option<String>,
|
pub build_aot: Option<String>,
|
||||||
pub build_profile: Option<String>,
|
pub build_profile: Option<String>,
|
||||||
pub build_target: Option<String>,
|
pub build_target: Option<String>,
|
||||||
|
// Using (CLI)
|
||||||
|
pub cli_usings: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliConfig {
|
impl CliConfig {
|
||||||
@ -109,6 +111,13 @@ impl CliConfig {
|
|||||||
.value_name("ARGS")
|
.value_name("ARGS")
|
||||||
.help("Pass additional args to selfhost child compiler (equivalent to NYASH_NY_COMPILER_CHILD_ARGS)")
|
.help("Pass additional args to selfhost child compiler (equivalent to NYASH_NY_COMPILER_CHILD_ARGS)")
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("using")
|
||||||
|
.long("using")
|
||||||
|
.value_name("SPEC")
|
||||||
|
.help("Register a using entry (e.g., 'ns as Alias' or '\"apps/foo.nyash\" as Foo'). Repeatable.")
|
||||||
|
.action(clap::ArgAction::Append)
|
||||||
|
)
|
||||||
|
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("debug-fuel")
|
Arg::new("debug-fuel")
|
||||||
@ -423,6 +432,7 @@ impl CliConfig {
|
|||||||
build_aot: matches.get_one::<String>("build-aot").cloned(),
|
build_aot: matches.get_one::<String>("build-aot").cloned(),
|
||||||
build_profile: matches.get_one::<String>("build-profile").cloned(),
|
build_profile: matches.get_one::<String>("build-profile").cloned(),
|
||||||
build_target: matches.get_one::<String>("build-target").cloned(),
|
build_target: matches.get_one::<String>("build-target").cloned(),
|
||||||
|
cli_usings: matches.get_many::<String>("using").map(|v| v.cloned().collect()).unwrap_or_else(|| Vec::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -475,6 +485,7 @@ impl Default for CliConfig {
|
|||||||
build_aot: None,
|
build_aot: None,
|
||||||
build_profile: None,
|
build_profile: None,
|
||||||
build_target: None,
|
build_target: None,
|
||||||
|
cli_usings: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
134
src/runner/box_index.rs
Normal file
134
src/runner/box_index.rs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/*!
|
||||||
|
* BoxIndex — minimal view over aliases and plugin box types
|
||||||
|
*
|
||||||
|
* Purpose: allow using/namespace resolver to make decisions that depend
|
||||||
|
* on plugin-visible type names (e.g., enforcing strict prefix rules) and
|
||||||
|
* to surface aliases defined in nyash.toml/env.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct BoxIndex {
|
||||||
|
pub aliases: HashMap<String, String>,
|
||||||
|
pub plugin_boxes: HashSet<String>,
|
||||||
|
pub plugin_meta: HashMap<String, PluginMeta>,
|
||||||
|
pub plugins_require_prefix_global: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoxIndex {
|
||||||
|
pub fn build_current() -> Self {
|
||||||
|
// aliases from nyash.toml and env
|
||||||
|
let mut aliases: HashMap<String, String> = HashMap::new();
|
||||||
|
if let Ok(text) = std::fs::read_to_string("nyash.toml") {
|
||||||
|
if let Ok(doc) = toml::from_str::<toml::Value>(&text) {
|
||||||
|
if let Some(alias_tbl) = doc.get("aliases").and_then(|v| v.as_table()) {
|
||||||
|
for (k, v) in alias_tbl.iter() {
|
||||||
|
if let Some(target) = v.as_str() { aliases.insert(k.to_string(), target.to_string()); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(raw) = std::env::var("NYASH_ALIASES") {
|
||||||
|
for ent in raw.split(',') {
|
||||||
|
if let Some((k,v)) = ent.split_once('=') {
|
||||||
|
let k = k.trim(); let v = v.trim();
|
||||||
|
if !k.is_empty() && !v.is_empty() { aliases.insert(k.to_string(), v.to_string()); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// plugin box types (best-effort; may be empty if host not initialized yet)
|
||||||
|
let mut plugin_boxes: HashSet<String> = HashSet::new();
|
||||||
|
let mut plugin_meta: HashMap<String, PluginMeta> = HashMap::new();
|
||||||
|
let mut plugins_require_prefix_global = false;
|
||||||
|
|
||||||
|
// Read per-plugin meta and global flags from nyash.toml when available
|
||||||
|
if let Ok(text) = std::fs::read_to_string("nyash.toml") {
|
||||||
|
if let Ok(doc) = toml::from_str::<toml::Value>(&text) {
|
||||||
|
if let Some(plugins_tbl) = doc.get("plugins").and_then(|v| v.as_table()) {
|
||||||
|
// Global switch: [plugins].require_prefix = true
|
||||||
|
if let Some(v) = plugins_tbl.get("require_prefix").and_then(|v| v.as_bool()) {
|
||||||
|
plugins_require_prefix_global = v;
|
||||||
|
}
|
||||||
|
for (k, v) in plugins_tbl.iter() {
|
||||||
|
// Skip non-table entries (string entries are plugin roots)
|
||||||
|
if let Some(t) = v.as_table() {
|
||||||
|
let prefix = t.get("prefix").and_then(|x| x.as_str()).map(|s| s.to_string());
|
||||||
|
let require_prefix = t.get("require_prefix").and_then(|x| x.as_bool()).unwrap_or(false);
|
||||||
|
let expose_short_names = t.get("expose_short_names").and_then(|x| x.as_bool()).unwrap_or(true);
|
||||||
|
plugin_meta.insert(k.clone(), PluginMeta { prefix, require_prefix, expose_short_names });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let host = crate::runtime::get_global_plugin_host();
|
||||||
|
if let Ok(h) = host.read() {
|
||||||
|
if let Some(cfg) = h.config_ref() {
|
||||||
|
for (_lib, def) in &cfg.libraries {
|
||||||
|
for bt in &def.boxes { plugin_boxes.insert(bt.clone()); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { aliases, plugin_boxes, plugin_meta, plugins_require_prefix_global }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_known_plugin_short(name: &str) -> bool {
|
||||||
|
// Prefer global index view
|
||||||
|
if GLOBAL.read().ok().map(|g| g.plugin_boxes.contains(name)).unwrap_or(false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Env override list
|
||||||
|
if let Ok(raw) = std::env::var("NYASH_KNOWN_PLUGIN_SHORTNAMES") {
|
||||||
|
let set: HashSet<String> = raw.split(',').map(|s| s.trim().to_string()).collect();
|
||||||
|
if set.contains(name) { return true; }
|
||||||
|
}
|
||||||
|
// Minimal fallback set
|
||||||
|
const KNOWN: &[&str] = &[
|
||||||
|
"ArrayBox","MapBox","StringBox","ConsoleBox","FileBox","PathBox","MathBox","IntegerBox","TOMLBox"
|
||||||
|
];
|
||||||
|
KNOWN.iter().any(|k| *k == name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global BoxIndex view (rebuilt on-demand)
|
||||||
|
static GLOBAL: Lazy<RwLock<BoxIndex>> = Lazy::new(|| RwLock::new(BoxIndex::default()));
|
||||||
|
|
||||||
|
// Global resolve cache (keyed by tgt|base|strict|paths)
|
||||||
|
static RESOLVE_CACHE: Lazy<RwLock<HashMap<String, String>>> = Lazy::new(|| RwLock::new(HashMap::new()));
|
||||||
|
|
||||||
|
pub fn refresh_box_index() {
|
||||||
|
let next = BoxIndex::build_current();
|
||||||
|
if let Ok(mut w) = GLOBAL.write() { *w = next; }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_box_index() -> BoxIndex {
|
||||||
|
GLOBAL.read().ok().map(|g| g.clone()).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cache_get(key: &str) -> Option<String> {
|
||||||
|
RESOLVE_CACHE.read().ok().and_then(|m| m.get(key).cloned())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cache_put(key: &str, value: String) {
|
||||||
|
if let Ok(mut m) = RESOLVE_CACHE.write() { m.insert(key.to_string(), value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cache_clear() {
|
||||||
|
if let Ok(mut m) = RESOLVE_CACHE.write() { m.clear(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct PluginMeta {
|
||||||
|
pub prefix: Option<String>,
|
||||||
|
pub require_prefix: bool,
|
||||||
|
pub expose_short_names: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_plugin_meta(plugin: &str) -> Option<PluginMeta> {
|
||||||
|
GLOBAL.read().ok().and_then(|g| g.plugin_meta.get(plugin).cloned())
|
||||||
|
}
|
||||||
@ -21,6 +21,7 @@ mod json_v0_bridge;
|
|||||||
mod mir_json_emit;
|
mod mir_json_emit;
|
||||||
mod pipe_io;
|
mod pipe_io;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
|
mod box_index;
|
||||||
mod tasks;
|
mod tasks;
|
||||||
mod build;
|
mod build;
|
||||||
mod dispatch;
|
mod dispatch;
|
||||||
@ -61,7 +62,26 @@ impl NyashRunner {
|
|||||||
}
|
}
|
||||||
// Using/module overrides pre-processing
|
// Using/module overrides pre-processing
|
||||||
let mut using_ctx = self.init_using_context();
|
let mut using_ctx = self.init_using_context();
|
||||||
let pending_using: Vec<(String, Option<String>)> = Vec::new();
|
let mut pending_using: Vec<(String, Option<String>)> = Vec::new();
|
||||||
|
// CLI --using SPEC entries (SPEC: 'ns', 'ns as Alias', '"path" as Alias')
|
||||||
|
for spec in &self.config.cli_usings {
|
||||||
|
let s = spec.trim();
|
||||||
|
if s.is_empty() { continue; }
|
||||||
|
let (target, alias) = if let Some(pos) = s.find(" as ") {
|
||||||
|
(s[..pos].trim().to_string(), Some(s[pos+4..].trim().to_string()))
|
||||||
|
} else { (s.to_string(), None) };
|
||||||
|
// Normalize quotes for path
|
||||||
|
let is_path = target.starts_with('"') || target.starts_with("./") || target.starts_with('/') || target.ends_with(".nyash");
|
||||||
|
if is_path {
|
||||||
|
let path = target.trim_matches('"').to_string();
|
||||||
|
let name = alias.clone().unwrap_or_else(|| {
|
||||||
|
std::path::Path::new(&path).file_stem().and_then(|s| s.to_str()).unwrap_or("module").to_string()
|
||||||
|
});
|
||||||
|
pending_using.push((name, Some(path)));
|
||||||
|
} else {
|
||||||
|
pending_using.push((target, alias));
|
||||||
|
}
|
||||||
|
}
|
||||||
for (ns, path) in using_ctx.pending_modules.iter() {
|
for (ns, path) in using_ctx.pending_modules.iter() {
|
||||||
let sb = crate::box_trait::StringBox::new(path.clone());
|
let sb = crate::box_trait::StringBox::new(path.clone());
|
||||||
crate::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
crate::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
||||||
@ -135,6 +155,13 @@ impl NyashRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lint: fields must be at top of box
|
||||||
|
let strict_fields = std::env::var("NYASH_FIELDS_TOP_STRICT").ok().as_deref() == Some("1");
|
||||||
|
if let Err(e) = pipeline::lint_fields_top(&code, strict_fields, self.config.cli_verbose) {
|
||||||
|
eprintln!("❌ Lint error: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
// Env overrides for using rules
|
// Env overrides for using rules
|
||||||
// Merge late env overrides (if any)
|
// Merge late env overrides (if any)
|
||||||
if let Ok(paths) = std::env::var("NYASH_USING_PATH") {
|
if let Ok(paths) = std::env::var("NYASH_USING_PATH") {
|
||||||
@ -159,7 +186,7 @@ impl NyashRunner {
|
|||||||
let verbose = std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1");
|
let verbose = std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1");
|
||||||
let ctx = std::path::Path::new(filename).parent();
|
let ctx = std::path::Path::new(filename).parent();
|
||||||
for (ns, alias) in pending_using.iter() {
|
for (ns, alias) in pending_using.iter() {
|
||||||
let value = match resolve_using_target(ns, false, &using_ctx.pending_modules, &using_ctx.using_paths, ctx, strict, verbose) {
|
let value = match resolve_using_target(ns, false, &using_ctx.pending_modules, &using_ctx.using_paths, &using_ctx.aliases, ctx, strict, verbose) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => { eprintln!("❌ using: {}", e); std::process::exit(1); }
|
Err(e) => { eprintln!("❌ using: {}", e); std::process::exit(1); }
|
||||||
};
|
};
|
||||||
@ -184,13 +211,15 @@ impl NyashRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🏭 Phase 9.78b: Initialize unified registry
|
// 🏭 Phase 9.78b: Initialize unified registry
|
||||||
runtime::init_global_unified_registry();
|
runtime::init_global_unified_registry();
|
||||||
|
|
||||||
// Try to initialize BID plugins from nyash.toml (best-effort)
|
// Try to initialize BID plugins from nyash.toml (best-effort)
|
||||||
// Allow disabling during snapshot/CI via NYASH_DISABLE_PLUGINS=1
|
// Allow disabling during snapshot/CI via NYASH_DISABLE_PLUGINS=1
|
||||||
if std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") {
|
if std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") {
|
||||||
runner_plugin_init::init_bid_plugins();
|
runner_plugin_init::init_bid_plugins();
|
||||||
|
// Build BoxIndex after plugin host is initialized
|
||||||
|
crate::runner::box_index::refresh_box_index();
|
||||||
}
|
}
|
||||||
// Allow interpreter to create plugin-backed boxes via unified registry
|
// Allow interpreter to create plugin-backed boxes via unified registry
|
||||||
// Opt-in by default for FileBox/TOMLBox which are required by ny-config and similar tools.
|
// Opt-in by default for FileBox/TOMLBox which are required by ny-config and similar tools.
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use std::io::Read;
|
|||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
use crate::runner::pipeline::suggest_in_base;
|
use crate::runner::pipeline::{suggest_in_base, resolve_using_target};
|
||||||
|
|
||||||
// (moved) suggest_in_base is now in runner/pipeline.rs
|
// (moved) suggest_in_base is now in runner/pipeline.rs
|
||||||
|
|
||||||
@ -610,30 +610,26 @@ impl NyashRunner {
|
|||||||
cleaned_code_owned = out;
|
cleaned_code_owned = out;
|
||||||
code_ref = &cleaned_code_owned;
|
code_ref = &cleaned_code_owned;
|
||||||
|
|
||||||
// Register modules into minimal registry with best-effort path resolution
|
// Register modules with resolver (aliases/modules/paths)
|
||||||
|
let using_ctx = self.init_using_context();
|
||||||
|
let strict = std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1");
|
||||||
|
let verbose = std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1");
|
||||||
|
let ctx_dir = std::path::Path::new(filename).parent();
|
||||||
for (ns_or_alias, alias_or_path) in used_names {
|
for (ns_or_alias, alias_or_path) in used_names {
|
||||||
// alias_or_path Some(path) means this entry was a direct path using
|
|
||||||
if let Some(path) = alias_or_path {
|
if let Some(path) = alias_or_path {
|
||||||
let sb = crate::box_trait::StringBox::new(path);
|
let sb = crate::box_trait::StringBox::new(path);
|
||||||
crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb));
|
crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb));
|
||||||
} else {
|
} else {
|
||||||
let rel = format!("apps/{}.nyash", ns_or_alias.replace('.', "/"));
|
match resolve_using_target(&ns_or_alias, false, &using_ctx.pending_modules, &using_ctx.using_paths, &using_ctx.aliases, ctx_dir, strict, verbose) {
|
||||||
let exists = std::path::Path::new(&rel).exists();
|
Ok(value) => {
|
||||||
if !exists && std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
let sb = crate::box_trait::StringBox::new(value);
|
||||||
eprintln!("[using] unresolved namespace '{}'; tried '{}'. Hint: add @module {}={} or --module {}={}", ns_or_alias, rel, ns_or_alias, rel, ns_or_alias, rel);
|
crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb));
|
||||||
// naive candidates by suffix within common bases
|
}
|
||||||
let leaf = ns_or_alias.split('.').last().unwrap_or(&ns_or_alias);
|
Err(e) => {
|
||||||
let mut cands: Vec<String> = Vec::new();
|
eprintln!("❌ using: {}", e);
|
||||||
suggest_in_base("apps", leaf, &mut cands);
|
std::process::exit(1);
|
||||||
if cands.len() < 5 { suggest_in_base("lib", leaf, &mut cands); }
|
|
||||||
if cands.len() < 5 { suggest_in_base(".", leaf, &mut cands); }
|
|
||||||
if !cands.is_empty() {
|
|
||||||
eprintln!("[using] candidates: {}", cands.join(", "));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let path_or_ns = if exists { rel } else { ns_or_alias.clone() };
|
|
||||||
let sb = crate::box_trait::StringBox::new(path_or_ns);
|
|
||||||
crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
use crate::parser::NyashParser;
|
use crate::parser::NyashParser;
|
||||||
use crate::interpreter::NyashInterpreter;
|
use crate::interpreter::NyashInterpreter;
|
||||||
|
use crate::runner_plugin_init;
|
||||||
|
use crate::runner::pipeline::{resolve_using_target, UsingContext};
|
||||||
use std::{fs, process};
|
use std::{fs, process};
|
||||||
|
|
||||||
/// Execute Nyash file with interpreter
|
/// Execute Nyash file with interpreter
|
||||||
@ -12,6 +14,11 @@ pub fn execute_nyash_file(filename: &str, debug_fuel: Option<usize>) {
|
|||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// Initialize plugin host and mappings (idempotent)
|
||||||
|
if std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") {
|
||||||
|
runner_plugin_init::init_bid_plugins();
|
||||||
|
crate::runner::box_index::refresh_box_index();
|
||||||
|
}
|
||||||
|
|
||||||
println!("📝 File contents:\n{}", code);
|
println!("📝 File contents:\n{}", code);
|
||||||
println!("\n🚀 Parsing and executing...\n");
|
println!("\n🚀 Parsing and executing...\n");
|
||||||
@ -20,9 +27,60 @@ pub fn execute_nyash_file(filename: &str, debug_fuel: Option<usize>) {
|
|||||||
std::fs::create_dir_all("development/debug_hang_issue").ok();
|
std::fs::create_dir_all("development/debug_hang_issue").ok();
|
||||||
std::fs::write("development/debug_hang_issue/test.txt", "START").ok();
|
std::fs::write("development/debug_hang_issue/test.txt", "START").ok();
|
||||||
|
|
||||||
|
// Optional: using pre-processing (strip lines and register modules)
|
||||||
|
let enable_using = std::env::var("NYASH_ENABLE_USING").ok().as_deref() == Some("1");
|
||||||
|
let mut code_ref: std::borrow::Cow<'_, str> = std::borrow::Cow::Borrowed(&code);
|
||||||
|
if enable_using {
|
||||||
|
let mut out = String::with_capacity(code.len());
|
||||||
|
let mut used_names: Vec<(String, Option<String>)> = Vec::new();
|
||||||
|
for line in code.lines() {
|
||||||
|
let t = line.trim_start();
|
||||||
|
if t.starts_with("using ") {
|
||||||
|
let rest0 = t.strip_prefix("using ").unwrap().trim();
|
||||||
|
let rest0 = rest0.strip_suffix(';').unwrap_or(rest0).trim();
|
||||||
|
let (target, alias) = if let Some(pos) = rest0.find(" as ") {
|
||||||
|
(rest0[..pos].trim().to_string(), Some(rest0[pos+4..].trim().to_string()))
|
||||||
|
} else { (rest0.to_string(), None) };
|
||||||
|
let is_path = target.starts_with('"') || target.starts_with("./") || target.starts_with('/') || target.ends_with(".nyash");
|
||||||
|
if is_path {
|
||||||
|
let path = target.trim_matches('"').to_string();
|
||||||
|
let name = alias.clone().unwrap_or_else(|| {
|
||||||
|
std::path::Path::new(&path).file_stem().and_then(|s| s.to_str()).unwrap_or("module").to_string()
|
||||||
|
});
|
||||||
|
used_names.push((name, Some(path)));
|
||||||
|
} else {
|
||||||
|
used_names.push((target, alias));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out.push_str(line);
|
||||||
|
out.push('\n');
|
||||||
|
}
|
||||||
|
// Resolve and register
|
||||||
|
let using_ctx = UsingContext { using_paths: vec!["apps".into(), "lib".into(), ".".into()], pending_modules: vec![], aliases: std::collections::HashMap::new() };
|
||||||
|
let strict = std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1");
|
||||||
|
let verbose = std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1");
|
||||||
|
let ctx_dir = std::path::Path::new(filename).parent();
|
||||||
|
for (ns_or_alias, alias_or_path) in used_names {
|
||||||
|
if let Some(path) = alias_or_path {
|
||||||
|
let sb = crate::box_trait::StringBox::new(path);
|
||||||
|
crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb));
|
||||||
|
} else {
|
||||||
|
match resolve_using_target(&ns_or_alias, false, &using_ctx.pending_modules, &using_ctx.using_paths, &using_ctx.aliases, ctx_dir, strict, verbose) {
|
||||||
|
Ok(value) => {
|
||||||
|
let sb = crate::box_trait::StringBox::new(value);
|
||||||
|
crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb));
|
||||||
|
}
|
||||||
|
Err(e) => { eprintln!("❌ using: {}", e); std::process::exit(1); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code_ref = std::borrow::Cow::Owned(out);
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the code with debug fuel limit
|
// Parse the code with debug fuel limit
|
||||||
eprintln!("🔍 DEBUG: Starting parse with fuel: {:?}...", debug_fuel);
|
eprintln!("🔍 DEBUG: Starting parse with fuel: {:?}...", debug_fuel);
|
||||||
let ast = match NyashParser::parse_from_string_with_fuel(&code, debug_fuel) {
|
let ast = match NyashParser::parse_from_string_with_fuel(&*code_ref, debug_fuel) {
|
||||||
Ok(ast) => {
|
Ok(ast) => {
|
||||||
eprintln!("🔍 DEBUG: Parse completed, AST created");
|
eprintln!("🔍 DEBUG: Parse completed, AST created");
|
||||||
ast
|
ast
|
||||||
|
|||||||
@ -8,11 +8,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use super::box_index::BoxIndex;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// Using/module resolution context accumulated from config/env/nyash.toml
|
/// Using/module resolution context accumulated from config/env/nyash.toml
|
||||||
pub(super) struct UsingContext {
|
pub(super) struct UsingContext {
|
||||||
pub using_paths: Vec<String>,
|
pub using_paths: Vec<String>,
|
||||||
pub pending_modules: Vec<(String, String)>,
|
pub pending_modules: Vec<(String, String)>,
|
||||||
|
pub aliases: std::collections::HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NyashRunner {
|
impl NyashRunner {
|
||||||
@ -20,6 +23,7 @@ impl NyashRunner {
|
|||||||
pub(super) fn init_using_context(&self) -> UsingContext {
|
pub(super) fn init_using_context(&self) -> UsingContext {
|
||||||
let mut using_paths: Vec<String> = Vec::new();
|
let mut using_paths: Vec<String> = Vec::new();
|
||||||
let mut pending_modules: Vec<(String, String)> = Vec::new();
|
let mut pending_modules: Vec<(String, String)> = Vec::new();
|
||||||
|
let mut aliases: std::collections::HashMap<String, String> = std::collections::HashMap::new();
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
using_paths.extend(["apps", "lib", "."].into_iter().map(|s| s.to_string()));
|
using_paths.extend(["apps", "lib", "."].into_iter().map(|s| s.to_string()));
|
||||||
@ -45,6 +49,14 @@ impl NyashRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Optional: [aliases] table maps short name -> path or namespace token
|
||||||
|
if let Some(alias_tbl) = doc.get("aliases").and_then(|v| v.as_table()) {
|
||||||
|
for (k, v) in alias_tbl.iter() {
|
||||||
|
if let Some(target) = v.as_str() {
|
||||||
|
aliases.insert(k.to_string(), target.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,8 +79,17 @@ impl NyashRunner {
|
|||||||
if !s.is_empty() { using_paths.push(s.to_string()); }
|
if !s.is_empty() { using_paths.push(s.to_string()); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Env aliases: comma-separated k=v pairs
|
||||||
|
if let Ok(raw) = std::env::var("NYASH_ALIASES") {
|
||||||
|
for ent in raw.split(',') {
|
||||||
|
if let Some((k,v)) = ent.split_once('=') {
|
||||||
|
let k = k.trim(); let v = v.trim();
|
||||||
|
if !k.is_empty() && !v.is_empty() { aliases.insert(k.to_string(), v.to_string()); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UsingContext { using_paths, pending_modules }
|
UsingContext { using_paths, pending_modules, aliases }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,13 +128,81 @@ pub(super) fn resolve_using_target(
|
|||||||
is_path: bool,
|
is_path: bool,
|
||||||
modules: &[(String, String)],
|
modules: &[(String, String)],
|
||||||
using_paths: &[String],
|
using_paths: &[String],
|
||||||
|
aliases: &HashMap<String, String>,
|
||||||
context_dir: Option<&std::path::Path>,
|
context_dir: Option<&std::path::Path>,
|
||||||
strict: bool,
|
strict: bool,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
if is_path { return Ok(tgt.to_string()); }
|
if is_path { return Ok(tgt.to_string()); }
|
||||||
|
let trace = verbose || std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1");
|
||||||
|
// Strict plugin prefix: if enabled and target matches a known plugin box type
|
||||||
|
// and is not qualified (contains '.'), require a qualified/prefixed name.
|
||||||
|
// Strict mode: env or nyash.toml [plugins] require_prefix=true
|
||||||
|
let mut strict_effective = strict;
|
||||||
|
if !strict_effective {
|
||||||
|
if let Ok(text) = std::fs::read_to_string("nyash.toml") {
|
||||||
|
if let Ok(doc) = toml::from_str::<toml::Value>(&text) {
|
||||||
|
if let Some(tbl) = doc.get("plugins").and_then(|v| v.as_table()) {
|
||||||
|
if let Some(v) = tbl.get("require_prefix").and_then(|v| v.as_bool()) { if v { strict_effective = true; } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if std::env::var("NYASH_PLUGIN_REQUIRE_PREFIX").ok().as_deref() == Some("1") { strict_effective = true; }
|
||||||
|
|
||||||
|
if strict_effective {
|
||||||
|
let mut is_plugin_short = super::box_index::BoxIndex::is_known_plugin_short(tgt);
|
||||||
|
if !is_plugin_short {
|
||||||
|
// Fallback: heuristic list or env override
|
||||||
|
if let Ok(raw) = std::env::var("NYASH_KNOWN_PLUGIN_SHORTNAMES") {
|
||||||
|
let set: std::collections::HashSet<String> = raw.split(',').map(|s| s.trim().to_string()).collect();
|
||||||
|
is_plugin_short = set.contains(tgt);
|
||||||
|
} else {
|
||||||
|
// Minimal builtins set
|
||||||
|
const KNOWN: &[&str] = &[
|
||||||
|
"ArrayBox","MapBox","StringBox","ConsoleBox","FileBox","PathBox","MathBox","IntegerBox","TOMLBox"
|
||||||
|
];
|
||||||
|
is_plugin_short = KNOWN.iter().any(|k| *k == tgt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if is_plugin_short && !tgt.contains('.') {
|
||||||
|
return Err(format!("plugin short name '{}' requires prefix (strict)", tgt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let key = {
|
||||||
|
let base = context_dir.and_then(|p| p.to_str()).unwrap_or("");
|
||||||
|
format!("{}|{}|{}|{}", tgt, base, strict as i32, using_paths.join(":"))
|
||||||
|
};
|
||||||
|
if let Some(hit) = crate::runner::box_index::cache_get(&key) {
|
||||||
|
if trace { eprintln!("[using/cache] '{}' -> '{}'", tgt, hit); }
|
||||||
|
return Ok(hit);
|
||||||
|
}
|
||||||
|
// Resolve aliases early (provided map)
|
||||||
|
if let Some(v) = aliases.get(tgt) {
|
||||||
|
if trace { eprintln!("[using/resolve] alias '{}' -> '{}'", tgt, v); }
|
||||||
|
crate::runner::box_index::cache_put(&key, v.clone());
|
||||||
|
return Ok(v.clone());
|
||||||
|
}
|
||||||
|
// Also consult env aliases
|
||||||
|
if let Ok(raw) = std::env::var("NYASH_ALIASES") {
|
||||||
|
for ent in raw.split(',') {
|
||||||
|
if let Some((k,v)) = ent.split_once('=') {
|
||||||
|
if k.trim() == tgt {
|
||||||
|
let out = v.trim().to_string();
|
||||||
|
if trace { eprintln!("[using/resolve] env-alias '{}' -> '{}'", tgt, out); }
|
||||||
|
crate::runner::box_index::cache_put(&key, out.clone());
|
||||||
|
return Ok(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// 1) modules mapping
|
// 1) modules mapping
|
||||||
if let Some((_, p)) = modules.iter().find(|(n, _)| n == tgt) { return Ok(p.clone()); }
|
if let Some((_, p)) = modules.iter().find(|(n, _)| n == tgt) {
|
||||||
|
let out = p.clone();
|
||||||
|
if trace { eprintln!("[using/resolve] modules '{}' -> '{}'", tgt, out); }
|
||||||
|
crate::runner::box_index::cache_put(&key, out.clone());
|
||||||
|
return Ok(out);
|
||||||
|
}
|
||||||
// 2) build candidate list: relative then using-paths
|
// 2) build candidate list: relative then using-paths
|
||||||
let rel = tgt.replace('.', "/") + ".nyash";
|
let rel = tgt.replace('.', "/") + ".nyash";
|
||||||
let mut cand: Vec<String> = Vec::new();
|
let mut cand: Vec<String> = Vec::new();
|
||||||
@ -123,11 +212,123 @@ pub(super) fn resolve_using_target(
|
|||||||
if c.exists() { cand.push(c.to_string_lossy().to_string()); }
|
if c.exists() { cand.push(c.to_string_lossy().to_string()); }
|
||||||
}
|
}
|
||||||
if cand.is_empty() {
|
if cand.is_empty() {
|
||||||
if verbose { eprintln!("[using] unresolved '{}' (searched: rel+paths)", tgt); }
|
if trace {
|
||||||
|
// Try suggest candidates by leaf across bases (apps/lib/.)
|
||||||
|
let leaf = tgt.split('.').last().unwrap_or(tgt);
|
||||||
|
let mut cands: Vec<String> = Vec::new();
|
||||||
|
suggest_in_base("apps", leaf, &mut cands);
|
||||||
|
if cands.len() < 5 { suggest_in_base("lib", leaf, &mut cands); }
|
||||||
|
if cands.len() < 5 { suggest_in_base(".", leaf, &mut cands); }
|
||||||
|
if cands.is_empty() {
|
||||||
|
eprintln!("[using] unresolved '{}' (searched: rel+paths)", tgt);
|
||||||
|
} else {
|
||||||
|
eprintln!("[using] unresolved '{}' (searched: rel+paths) candidates: {}", tgt, cands.join(", "));
|
||||||
|
}
|
||||||
|
}
|
||||||
return Ok(tgt.to_string());
|
return Ok(tgt.to_string());
|
||||||
}
|
}
|
||||||
if cand.len() > 1 && strict {
|
if cand.len() > 1 && strict {
|
||||||
return Err(format!("ambiguous using '{}': {}", tgt, cand.join(", ")));
|
return Err(format!("ambiguous using '{}': {}", tgt, cand.join(", ")));
|
||||||
}
|
}
|
||||||
Ok(cand.remove(0))
|
let out = cand.remove(0);
|
||||||
|
if trace { eprintln!("[using/resolve] '{}' -> '{}'", tgt, out); }
|
||||||
|
crate::runner::box_index::cache_put(&key, out.clone());
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lint: enforce "fields must be at the top of box" rule.
|
||||||
|
/// - Warns by default (when verbose); when `strict` is true, returns Err on any violation.
|
||||||
|
pub(super) fn lint_fields_top(code: &str, strict: bool, verbose: bool) -> Result<(), String> {
|
||||||
|
let mut brace: i32 = 0;
|
||||||
|
let mut in_box = false;
|
||||||
|
let mut box_depth: i32 = 0;
|
||||||
|
let mut seen_method = false;
|
||||||
|
let mut cur_box: String = String::new();
|
||||||
|
let mut violations: Vec<(usize, String, String)> = Vec::new(); // (line, field, box)
|
||||||
|
|
||||||
|
for (idx, line) in code.lines().enumerate() {
|
||||||
|
let lno = idx + 1;
|
||||||
|
let pre_brace = brace;
|
||||||
|
let trimmed = line.trim();
|
||||||
|
// Count braces for this line
|
||||||
|
let opens = line.matches('{').count() as i32;
|
||||||
|
let closes = line.matches('}').count() as i32;
|
||||||
|
|
||||||
|
// Enter box on same-line K&R style: `box Name {` or `static box Name {`
|
||||||
|
if !in_box && trimmed.starts_with("box ") || trimmed.starts_with("static box ") {
|
||||||
|
// capture name
|
||||||
|
let mut name = String::new();
|
||||||
|
let after = if let Some(rest) = trimmed.strip_prefix("static box ") { rest } else { trimmed.strip_prefix("box ").unwrap_or("") };
|
||||||
|
for ch in after.chars() {
|
||||||
|
if ch.is_alphanumeric() || ch == '_' { name.push(ch); } else { break; }
|
||||||
|
}
|
||||||
|
// require K&R brace on same line to start tracking
|
||||||
|
if opens > 0 {
|
||||||
|
in_box = true;
|
||||||
|
cur_box = name;
|
||||||
|
box_depth = pre_brace + 1; // assume one level for box body
|
||||||
|
seen_method = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if in_box {
|
||||||
|
// Top-level inside box only
|
||||||
|
if pre_brace == box_depth {
|
||||||
|
// Skip empty/comment lines
|
||||||
|
if !trimmed.is_empty() && !trimmed.starts_with("//") {
|
||||||
|
// Detect method: name(args) {
|
||||||
|
let is_method = {
|
||||||
|
// starts with identifier then '(' and later '{'
|
||||||
|
let mut it = trimmed.chars();
|
||||||
|
let mut ident = String::new();
|
||||||
|
while let Some(c) = it.next() { if c.is_whitespace() { continue; } if c.is_alphabetic() || c=='_' { ident.push(c); break; } else { break; } }
|
||||||
|
while let Some(c) = it.next() { if c.is_alphanumeric() || c=='_' { ident.push(c); } else { break; } }
|
||||||
|
trimmed.contains('(') && trimmed.ends_with('{') && !ident.is_empty()
|
||||||
|
};
|
||||||
|
if is_method { seen_method = true; }
|
||||||
|
|
||||||
|
// Detect field: ident ':' Type (rough heuristic)
|
||||||
|
let is_field = {
|
||||||
|
let parts: Vec<&str> = trimmed.split(':').collect();
|
||||||
|
if parts.len() == 2 {
|
||||||
|
let lhs = parts[0].trim();
|
||||||
|
let rhs = parts[1].trim();
|
||||||
|
let lhs_ok = !lhs.is_empty() && lhs.chars().next().map(|c| c.is_alphabetic() || c=='_').unwrap_or(false);
|
||||||
|
let rhs_ok = !rhs.is_empty() && rhs.chars().next().map(|c| c.is_alphabetic() || c=='_').unwrap_or(false);
|
||||||
|
lhs_ok && rhs_ok && !trimmed.contains('(') && !trimmed.contains(')')
|
||||||
|
} else { false }
|
||||||
|
};
|
||||||
|
if is_field && seen_method {
|
||||||
|
violations.push((lno, trimmed.to_string(), cur_box.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Exit box when closing brace reduces depth below box_depth
|
||||||
|
let post_brace = pre_brace + opens - closes;
|
||||||
|
if post_brace < box_depth { in_box = false; cur_box.clear(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update brace after processing
|
||||||
|
brace += opens - closes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if violations.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if strict {
|
||||||
|
// Compose error message
|
||||||
|
let mut msg = String::from("Field declarations must appear at the top of box. Violations:\n");
|
||||||
|
for (lno, fld, bx) in violations.iter().take(10) {
|
||||||
|
msg.push_str(&format!(" line {} in box {}: '{}" , lno, if bx.is_empty(){"<unknown>"} else {bx}, fld));
|
||||||
|
msg.push_str("'\n");
|
||||||
|
}
|
||||||
|
if violations.len() > 10 { msg.push_str(&format!(" ... and {} more\n", violations.len()-10)); }
|
||||||
|
return Err(msg);
|
||||||
|
}
|
||||||
|
if verbose || std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
for (lno, fld, bx) in violations {
|
||||||
|
eprintln!("[lint] fields-top: line {} in box {} -> {}", lno, if bx.is_empty(){"<unknown>"} else {&bx}, fld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,6 +91,57 @@ impl NyashRunner {
|
|||||||
Err(e) => { eprintln!("[ny-compiler] open tmp failed: {}", e); return false; }
|
Err(e) => { eprintln!("[ny-compiler] open tmp failed: {}", e); return false; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Python MVP-first: prefer the lightweight harness to produce JSON v0
|
||||||
|
if let Ok(py3) = which::which("python3") {
|
||||||
|
let py = std::path::Path::new("tools/ny_parser_mvp.py");
|
||||||
|
if py.exists() {
|
||||||
|
let mut cmd = std::process::Command::new(&py3);
|
||||||
|
cmd.arg(py).arg(&tmp_path);
|
||||||
|
let out = match cmd.output() { Ok(o) => o, Err(e) => { eprintln!("[ny-compiler] python harness failed to spawn: {}", e); return false; } };
|
||||||
|
if out.status.success() {
|
||||||
|
if let Ok(line) = String::from_utf8(out.stdout).map(|s| s.lines().next().unwrap_or("").to_string()) {
|
||||||
|
if line.contains("\"version\"") && line.contains("\"kind\"") {
|
||||||
|
match super::json_v0_bridge::parse_json_v0_to_module(&line) {
|
||||||
|
Ok(module) => {
|
||||||
|
super::json_v0_bridge::maybe_dump_mir(&module);
|
||||||
|
let emit_only = std::env::var("NYASH_NY_COMPILER_EMIT_ONLY").unwrap_or_else(|_| "1".to_string()) == "1";
|
||||||
|
if emit_only { return false; }
|
||||||
|
// Prefer PyVM for selfhost pipeline (parity reference)
|
||||||
|
if std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1") {
|
||||||
|
// Reuse the common PyVM runner path
|
||||||
|
let tmp_dir = std::path::Path::new("tmp");
|
||||||
|
let _ = std::fs::create_dir_all(tmp_dir);
|
||||||
|
let mir_json_path = tmp_dir.join("nyash_pyvm_mir.json");
|
||||||
|
if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(&module, &mir_json_path) {
|
||||||
|
eprintln!("❌ PyVM MIR JSON emit error: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[Bridge] using PyVM (selfhost-py) → {}", mir_json_path.display());
|
||||||
|
}
|
||||||
|
let entry = if module.functions.contains_key("Main.main") { "Main.main" } else if module.functions.contains_key("main") { "main" } else { "Main.main" };
|
||||||
|
let status = std::process::Command::new(&py3)
|
||||||
|
.args(["tools/pyvm_runner.py", "--in", &mir_json_path.display().to_string(), "--entry", entry])
|
||||||
|
.status().map_err(|e| format!("spawn pyvm: {}", e)).unwrap();
|
||||||
|
let code = status.code().unwrap_or(1);
|
||||||
|
if !status.success() {
|
||||||
|
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("❌ PyVM (selfhost-py) failed (status={})", code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("Result: {}", code);
|
||||||
|
std::process::exit(code);
|
||||||
|
}
|
||||||
|
self.execute_mir_module(&module);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Err(e) => { eprintln!("[ny-compiler] json parse error: {}", e); return false; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// EXE-first: if requested, try external parser EXE (nyash_compiler)
|
// EXE-first: if requested, try external parser EXE (nyash_compiler)
|
||||||
if std::env::var("NYASH_USE_NY_COMPILER_EXE").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_USE_NY_COMPILER_EXE").ok().as_deref() == Some("1") {
|
||||||
// Resolve parser EXE path
|
// Resolve parser EXE path
|
||||||
@ -204,57 +255,60 @@ impl NyashRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: run compiler.nyash via VM(PyVM) and pick the JSON line
|
// Fallback: inline VM run (embed source into a tiny wrapper that prints JSON)
|
||||||
// Guard against recursion: ensure child does NOT enable selfhost pipeline.
|
// This avoids CLI arg forwarding complexity and does not require FileBox.
|
||||||
let mut raw = String::new();
|
let mut raw = String::new();
|
||||||
{
|
{
|
||||||
// Locate current nyash executable
|
// Escape source for embedding as string literal
|
||||||
|
let mut esc = String::with_capacity(code_ref.len());
|
||||||
|
for ch in code_ref.chars() {
|
||||||
|
match ch {
|
||||||
|
'\\' => esc.push_str("\\\\"),
|
||||||
|
'"' => esc.push_str("\\\""),
|
||||||
|
'\n' => esc.push_str("\n"),
|
||||||
|
'\r' => esc.push_str(""),
|
||||||
|
_ => esc.push(ch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let inline_path = std::path::Path::new("tmp").join("inline_selfhost_emit.nyash");
|
||||||
|
let inline_code = format!(
|
||||||
|
"include \"apps/selfhost-compiler/boxes/parser_box.nyash\"\ninclude \"apps/selfhost-compiler/boxes/emitter_box.nyash\"\nstatic box Main {{\n main(args) {{\n local s = \"{}\"\n local p = new ParserBox()\n local json = p.parse_program2(s)\n local e = new EmitterBox()\n json = e.emit_program(json, \"[]\")\n print(json)\n return 0\n }}\n}}\n",
|
||||||
|
esc
|
||||||
|
);
|
||||||
|
if let Err(e) = std::fs::write(&inline_path, inline_code) {
|
||||||
|
eprintln!("[ny-compiler] write inline failed: {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
let exe = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("target/release/nyash"));
|
let exe = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("target/release/nyash"));
|
||||||
let mut cmd = std::process::Command::new(exe);
|
let mut cmd = std::process::Command::new(exe);
|
||||||
cmd.arg("--backend").arg("vm").arg("apps/selfhost-compiler/compiler.nyash");
|
cmd.arg("--backend").arg("vm").arg(&inline_path);
|
||||||
// Pass script args to child when gated
|
|
||||||
if std::env::var("NYASH_NY_COMPILER_MIN_JSON").ok().as_deref() == Some("1") { cmd.arg("--").arg("--min-json"); }
|
|
||||||
if std::env::var("NYASH_SELFHOST_READ_TMP").ok().as_deref() == Some("1") { cmd.arg("--").arg("--read-tmp"); }
|
|
||||||
// Recursion guard and minimal, quiet env for child
|
|
||||||
cmd.env_remove("NYASH_USE_NY_COMPILER");
|
cmd.env_remove("NYASH_USE_NY_COMPILER");
|
||||||
cmd.env_remove("NYASH_CLI_VERBOSE");
|
cmd.env_remove("NYASH_CLI_VERBOSE");
|
||||||
cmd.env("NYASH_JSON_ONLY", "1");
|
cmd.env("NYASH_JSON_ONLY", "1");
|
||||||
if let Ok(v) = std::env::var("NYASH_JSON_INCLUDE_USINGS") { cmd.env("NYASH_JSON_INCLUDE_USINGS", v); }
|
|
||||||
if let Ok(v) = std::env::var("NYASH_ENABLE_USING") { cmd.env("NYASH_ENABLE_USING", v); }
|
|
||||||
|
|
||||||
// Timeout guard (default 2000ms)
|
|
||||||
let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(2000);
|
let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(2000);
|
||||||
let mut cmd = cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
|
let mut cmd = cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
|
||||||
let mut child = match cmd.spawn() {
|
let mut child = match cmd.spawn() { Ok(c) => c, Err(e) => { eprintln!("[ny-compiler] spawn inline vm failed: {}", e); return false; } };
|
||||||
Ok(c) => c,
|
|
||||||
Err(e) => { eprintln!("[ny-compiler] spawn nyash vm failed: {}", e); return false; }
|
|
||||||
};
|
|
||||||
let mut ch_stdout = child.stdout.take();
|
let mut ch_stdout = child.stdout.take();
|
||||||
let mut ch_stderr = child.stderr.take();
|
let mut ch_stderr = child.stderr.take();
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let mut timed_out = false;
|
let mut timed_out = false;
|
||||||
loop {
|
loop {
|
||||||
match child.try_wait() {
|
match child.try_wait() {
|
||||||
Ok(Some(_status)) => { break; }
|
Ok(Some(_)) => break,
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
if start.elapsed() >= Duration::from_millis(timeout_ms) {
|
if start.elapsed() >= Duration::from_millis(timeout_ms) {
|
||||||
let _ = child.kill();
|
let _ = child.kill(); let _ = child.wait(); timed_out = true; break;
|
||||||
let _ = child.wait();
|
|
||||||
timed_out = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
sleep(Duration::from_millis(10));
|
sleep(Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
Err(e) => { eprintln!("[ny-compiler] child wait error: {}", e); break; }
|
Err(e) => { eprintln!("[ny-compiler] inline wait error: {}", e); break; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut out_buf = Vec::new();
|
let mut out_buf = Vec::new();
|
||||||
let mut err_buf = Vec::new();
|
|
||||||
if let Some(mut s) = ch_stdout { let _ = s.read_to_end(&mut out_buf); }
|
if let Some(mut s) = ch_stdout { let _ = s.read_to_end(&mut out_buf); }
|
||||||
if let Some(mut s) = ch_stderr { let _ = s.read_to_end(&mut err_buf); }
|
|
||||||
if timed_out {
|
if timed_out {
|
||||||
let head = String::from_utf8_lossy(&out_buf).chars().take(200).collect::<String>();
|
let head = String::from_utf8_lossy(&out_buf).chars().take(200).collect::<String>();
|
||||||
eprintln!("[ny-compiler] child timeout after {} ms; stdout(head)='{}'", timeout_ms, head.replace('\n', "\\n"));
|
eprintln!("[ny-compiler] inline timeout after {} ms; stdout(head)='{}'", timeout_ms, head.replace('\n', "\\n"));
|
||||||
}
|
}
|
||||||
raw = String::from_utf8_lossy(&out_buf).to_string();
|
raw = String::from_utf8_lossy(&out_buf).to_string();
|
||||||
}
|
}
|
||||||
@ -269,11 +323,14 @@ impl NyashRunner {
|
|||||||
super::json_v0_bridge::maybe_dump_mir(&module);
|
super::json_v0_bridge::maybe_dump_mir(&module);
|
||||||
let emit_only = std::env::var("NYASH_NY_COMPILER_EMIT_ONLY").unwrap_or_else(|_| "1".to_string()) == "1";
|
let emit_only = std::env::var("NYASH_NY_COMPILER_EMIT_ONLY").unwrap_or_else(|_| "1".to_string()) == "1";
|
||||||
if emit_only { return false; }
|
if emit_only { return false; }
|
||||||
// Prefer PyVM when requested AND the module contains BoxCalls (Stage-2 semantics)
|
// Phase-15 policy: when NYASH_VM_USE_PY=1, prefer PyVM as reference executor
|
||||||
let needs_pyvm = module.functions.values().any(|f| {
|
// regardless of BoxCall presence to ensure semantics parity (e.g., PHI merges).
|
||||||
|
let prefer_pyvm = std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1");
|
||||||
|
// Backward compatibility: if not preferring PyVM explicitly, still auto-enable when BoxCalls exist.
|
||||||
|
let needs_pyvm = !prefer_pyvm && module.functions.values().any(|f| {
|
||||||
f.blocks.values().any(|bb| bb.instructions.iter().any(|inst| matches!(inst, crate::mir::MirInstruction::BoxCall { .. })))
|
f.blocks.values().any(|bb| bb.instructions.iter().any(|inst| matches!(inst, crate::mir::MirInstruction::BoxCall { .. })))
|
||||||
});
|
});
|
||||||
if needs_pyvm && std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1") {
|
if prefer_pyvm || needs_pyvm {
|
||||||
if let Ok(py3) = which::which("python3") {
|
if let Ok(py3) = which::which("python3") {
|
||||||
let runner = std::path::Path::new("tools/pyvm_runner.py");
|
let runner = std::path::Path::new("tools/pyvm_runner.py");
|
||||||
if runner.exists() {
|
if runner.exists() {
|
||||||
@ -285,7 +342,8 @@ impl NyashRunner {
|
|||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||||
eprintln!("[Bridge] using PyVM (selfhost-fallback) → {}", mir_json_path.display());
|
let mode = if prefer_pyvm { "selfhost" } else { "selfhost-fallback" };
|
||||||
|
eprintln!("[Bridge] using PyVM ({}) → {}", mode, mir_json_path.display());
|
||||||
}
|
}
|
||||||
let entry = if module.functions.contains_key("Main.main") { "Main.main" } else if module.functions.contains_key("main") { "main" } else { "Main.main" };
|
let entry = if module.functions.contains_key("Main.main") { "Main.main" } else if module.functions.contains_key("main") { "main" } else { "Main.main" };
|
||||||
let status = std::process::Command::new(py3)
|
let status = std::process::Command::new(py3)
|
||||||
|
|||||||
31
tools/using_prefix_strict_smoke.sh
Normal file
31
tools/using_prefix_strict_smoke.sh
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
[[ "${NYASH_CLI_VERBOSE:-0}" == "1" ]] && set -x
|
||||||
|
|
||||||
|
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
|
BIN="$ROOT_DIR/target/release/nyash"
|
||||||
|
|
||||||
|
if [ ! -x "$BIN" ]; then
|
||||||
|
cargo build --release >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
SRC=$(mktemp)
|
||||||
|
cat >"$SRC" <<'NY'
|
||||||
|
using ArrayBox
|
||||||
|
static box Main { main(args) { return 0 } }
|
||||||
|
NY
|
||||||
|
|
||||||
|
set +e
|
||||||
|
NYASH_ENABLE_USING=1 NYASH_PLUGIN_REQUIRE_PREFIX=1 "$BIN" --backend interpreter "$SRC" >/tmp/nyash-using-prefix-strict.out 2>&1
|
||||||
|
rc=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ $rc -ne 0 ] && rg -q "plugin short name 'ArrayBox' requires prefix" /tmp/nyash-using-prefix-strict.out; then
|
||||||
|
echo "PASS: plugin short name rejected in strict mode" >&2
|
||||||
|
else
|
||||||
|
echo "FAIL: strict plugin prefix not enforced" >&2
|
||||||
|
sed -n '1,120p' /tmp/nyash-using-prefix-strict.out >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "All PASS" >&2
|
||||||
Reference in New Issue
Block a user