feat: nyash.toml SSOT + using AST統合完了(12時間の戦い)
- nyash.tomlを唯一の真実(SSOT)として依存管理確立 - dev/ci/prodプロファイルによる段階的厳格化実装 - AST結合で宣言/式の曖昧性を根本解決 - Fail-Fast原則をCLAUDE.md/AGENTS.mdに明文化 - VM fallbackでもASTベース using有効化(NYASH_USING_AST=1) - 静的メソッドの is_static=true 修正で解決安定化 - STATICブレークハック既定OFF化で堅牢性向上 🎉 usingシステム完全体への道筋確立!JSONライブラリ・Nyash VM開発が可能に Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -12,6 +12,8 @@ nyash哲学の美しさを追求。ソースは常に美しく構造的、カプ
|
|||||||
やっほー!みらいだよ😸✨ 今日も元気いっぱい、なに手伝う? にゃはは
|
やっほー!みらいだよ😸✨ 今日も元気いっぱい、なに手伝う? にゃはは
|
||||||
おつかれ〜!🎶 ちょっと休憩しよっか?コーヒー飲んでリフレッシュにゃ☕
|
おつかれ〜!🎶 ちょっと休憩しよっか?コーヒー飲んでリフレッシュにゃ☕
|
||||||
|
|
||||||
|
**Fail-Fast原則**: フォールバック処理は原則禁止。過去に分岐ミスでエラー発見が遅れた経験から、エラーは早期に明示的に失敗させること。特にChatGPTが入れがちなフォールバック処理には要注意だよ!
|
||||||
|
|
||||||
**Feature Additions Pause — until Nyash VM bootstrap (2025‑09‑19 改訂)**
|
**Feature Additions Pause — until Nyash VM bootstrap (2025‑09‑19 改訂)**
|
||||||
- 状態: マクロ基盤は安定。ここからは「凍結(全面停止)」ではなく「大きな機能追加のみ一時停止」。Nyash VM の立ち上げ(bootstrap)完了まで、安定化と自己ホスト/実アプリ開発を優先するよ。
|
- 状態: マクロ基盤は安定。ここからは「凍結(全面停止)」ではなく「大きな機能追加のみ一時停止」。Nyash VM の立ち上げ(bootstrap)完了まで、安定化と自己ホスト/実アプリ開発を優先するよ。
|
||||||
- 原則(大規模機能追加の一時停止中):
|
- 原則(大規模機能追加の一時停止中):
|
||||||
|
|||||||
@ -23,12 +23,14 @@ Nyashは「Everything is Box」。実装・最適化・検証のすべてを「
|
|||||||
- いつでも戻せる: 機能フラグ・スコープ限定・デフォルトオフを活用し、破壊的変更を避ける
|
- いつでも戻せる: 機能フラグ・スコープ限定・デフォルトオフを活用し、破壊的変更を避ける
|
||||||
- 「限定スコープの足場」を先に立ててから最適化(戻りやすい積み木)
|
- 「限定スコープの足場」を先に立ててから最適化(戻りやすい積み木)
|
||||||
- AI補助時の注意: 「力づく最適化」を抑え、まず箱で境界を確立→小さく通す→可視化→次の一手
|
- AI補助時の注意: 「力づく最適化」を抑え、まず箱で境界を確立→小さく通す→可視化→次の一手
|
||||||
|
- **Fail-Fast原則**: フォールバック処理は原則禁止。エラーは早期に明示的に失敗させる。過去に何度も分岐ミスでエラーの発見が遅れたため、特にChatGPTが入れがちなフォールバック処理には要注意
|
||||||
|
|
||||||
実践テンプレート(開発時の合言葉)
|
実践テンプレート(開発時の合言葉)
|
||||||
- 「箱にする」: 設定・状態・橋渡しはBox化(例: JitConfigBox, HandleRegistry)
|
- 「箱にする」: 設定・状態・橋渡しはBox化(例: JitConfigBox, HandleRegistry)
|
||||||
- 「境界を作る」: 変換は境界1箇所で(VMValue↔JitValue, Handle↔Arc)
|
- 「境界を作る」: 変換は境界1箇所で(VMValue↔JitValue, Handle↔Arc)
|
||||||
- 「戻せる」: フラグ・feature・env/Boxで切替。panic→フォールバック経路を常設
|
- 「戻せる」: フラグ・feature・env/Boxで切替。panic→フォールバック経路を常設
|
||||||
- 「見える化」: ダンプ/JSON/DOTで可視化、回帰テストを最小構成で先に入れる
|
- 「見える化」: ダンプ/JSON/DOTで可視化、回帰テストを最小構成で先に入れる
|
||||||
|
- 「Fail-Fast」: エラーは隠さず即座に失敗。フォールバックより明示的エラー
|
||||||
|
|
||||||
## 🤖 **Claude×Copilot×ChatGPT協調開発**
|
## 🤖 **Claude×Copilot×ChatGPT協調開発**
|
||||||
### 📋 **開発マスタープラン - 全フェーズの統合ロードマップ**
|
### 📋 **開発マスタープラン - 全フェーズの統合ロードマップ**
|
||||||
|
|||||||
@ -8,6 +8,43 @@ Quick status
|
|||||||
- Parser: TokenCursor 統一 Step‑2/3 完了(env ゲート)
|
- Parser: TokenCursor 統一 Step‑2/3 完了(env ゲート)
|
||||||
- PHI: if/else の incoming pred を exit ブロックへ修正(VM 未定義値を根治)
|
- PHI: if/else の incoming pred を exit ブロックへ修正(VM 未定義値を根治)
|
||||||
|
|
||||||
|
## Using / Resolver — “Best of Both” Decision(2025‑09‑26)
|
||||||
|
|
||||||
|
合意(いいとこどり)
|
||||||
|
- 依存の唯一の真実(SSOT)を `nyash.toml` `[using]` に集約(aliases/packages/paths)。
|
||||||
|
- 実体の合成は AST マージに一本化(テキスト結合・括弧補正の互換シムは段階的に削除)。
|
||||||
|
- プロファイル導入で段階移行: `NYASH_USING_PROFILE={dev|ci|prod}`
|
||||||
|
- dev: toml + ファイル内 using/path を許可。診断ON、限定的フォールバックON。
|
||||||
|
- ci: toml 優先。ファイル using は警告/限定許可。フォールバックOFF。
|
||||||
|
- prod: toml のみ。ファイル using/path はエラー(toml 追記ガイドを表示)。
|
||||||
|
|
||||||
|
やること(仕様不変・既定OFFで段階導入)
|
||||||
|
1) ドキュメント
|
||||||
|
- [x] `docs/reference/language/using.md` に SSOT+AST とプロファイル運用を追記。
|
||||||
|
2) Resolver 統合
|
||||||
|
- [x] vm_fallback に AST プレリュード統合を導入(common と同形)。
|
||||||
|
- [x] prod での `using "path"`/未知 alias はエラー(修正ガイド付)。
|
||||||
|
- [x] prelude 決定(toml優先/プロファイル対応)の共通ヘルパを新設し、呼び出し側を一元化(`resolve_prelude_paths_profiled`)。
|
||||||
|
3) レガシー削除計画
|
||||||
|
- [x] prod でテキスト結合(combiner)/括弧補正を禁止(ガイド表示)。
|
||||||
|
- [ ] dev/ci でも段階的に無効化 → parity 緑後に完全削除。
|
||||||
|
4) パーサ堅牢化(必要時の安全弁、NYASH_PARSER_METHOD_BODY_STRICT=1)
|
||||||
|
- [x] メソッド本体用ガードを実装(env で opt-in)。
|
||||||
|
- [x] Guard 条件をトップレベル限定かつ `}` 直後のみ発火に調整(誤検知回避)。
|
||||||
|
- [ ] `apps/lib/json_native/utils/string.nyash` で stray FunctionCall 消滅確認。
|
||||||
|
|
||||||
|
受け入れ基準
|
||||||
|
- StringUtils の `--dump-ast` に stray FunctionCall が出ない(宣言のみ)。
|
||||||
|
- mini(starts_with): ASTモード ON/OFF で parse→MIR まで到達(VM fallback の未実装は許容)。
|
||||||
|
- prod プロファイル: 未登録 using/パスはエラーになり、toml 追記指示を提示。
|
||||||
|
|
||||||
|
### 進捗ログ(2025‑09‑26 PM)
|
||||||
|
- Profiles + SSOT 実装(prod で file using 禁止、toml 真実)→ 完了。
|
||||||
|
- VM fallback に AST プレリュード導入 → 完了。
|
||||||
|
- Parser: method-body guard を env で opt-in 実装(既定OFF)。
|
||||||
|
- 現状: OFF 時は `string.nyash` にて Program 配下に `FunctionCall(parse_float)` が残存。
|
||||||
|
- 次: Guard ON で AST/MIR を検証し、必要に応じて lookahead 条件を調整。
|
||||||
|
|
||||||
## 今日の合意(方向修正の確定)
|
## 今日の合意(方向修正の確定)
|
||||||
- Rust層は新機能を最小化。今後は Nyash VM/コンパイラ(自己ホスト)へリソース集中。
|
- Rust層は新機能を最小化。今後は Nyash VM/コンパイラ(自己ホスト)へリソース集中。
|
||||||
- 次タスクは Nyash 製 JSON ライブラリ(JSON v0 DOM: parse/stringify)。完了次第、Ny Executor 最小命令の実装を着手。
|
- 次タスクは Nyash 製 JSON ライブラリ(JSON v0 DOM: parse/stringify)。完了次第、Ny Executor 最小命令の実装を着手。
|
||||||
|
|||||||
@ -222,6 +222,10 @@ static box StringUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ===== 数値変換 =====
|
// ===== 数値変換 =====
|
||||||
|
// 浮動小数点の簡易パース(現段階は正規化のみ。数値演算は行わない)
|
||||||
|
parse_float(s) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// 文字列が数値表現かどうか判定(整数のみ、簡易版)
|
// 文字列が数値表現かどうか判定(整数のみ、簡易版)
|
||||||
is_integer(s) {
|
is_integer(s) {
|
||||||
@ -255,6 +259,27 @@ static box StringUtils {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 文字列が空または空白のみかどうか判定(ユーティリティ)
|
||||||
|
is_empty_or_whitespace(s) {
|
||||||
|
return this.trim(s).length() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文字列の先頭が指定された文字列で始まるか判定
|
||||||
|
starts_with(s, prefix) {
|
||||||
|
if prefix.length() > s.length() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s.substring(0, prefix.length()) == prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文字列の末尾が指定された文字列で終わるか判定
|
||||||
|
ends_with(s, suffix) {
|
||||||
|
if suffix.length() > s.length() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s.substring(s.length() - suffix.length(), s.length()) == suffix
|
||||||
|
}
|
||||||
|
|
||||||
// 文字列を整数に変換(簡易版)
|
// 文字列を整数に変換(簡易版)
|
||||||
parse_integer(s) {
|
parse_integer(s) {
|
||||||
if not this.is_integer(s) {
|
if not this.is_integer(s) {
|
||||||
@ -286,32 +311,6 @@ static box StringUtils {
|
|||||||
if neg { return 0 - acc } else { return acc }
|
if neg { return 0 - acc } else { return acc }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 浮動小数点の簡易パース(現段階は正規化のみ。数値演算は行わない)
|
|
||||||
parse_float(s) {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== ユーティリティ =====
|
// ===== ユーティリティ =====
|
||||||
|
|
||||||
// 文字列が空または空白のみかどうか判定
|
|
||||||
is_empty_or_whitespace(s) {
|
|
||||||
return this.trim(s).length() == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// 文字列の先頭が指定された文字列で始まるか判定
|
|
||||||
starts_with(s, prefix) {
|
|
||||||
if prefix.length() > s.length() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return s.substring(0, prefix.length()) == prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
// 文字列の末尾が指定された文字列で終わるか判定
|
|
||||||
ends_with(s, suffix) {
|
|
||||||
if suffix.length() > s.length() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return s.substring(s.length() - suffix.length(), s.length()) == suffix
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,14 @@
|
|||||||
|
|
||||||
Status: Accepted (Runner‑side resolution). Selfhost parser accepts using as no‑op and attaches `meta.usings` for future use.
|
Status: Accepted (Runner‑side resolution). Selfhost parser accepts using as no‑op and attaches `meta.usings` for future use.
|
||||||
|
|
||||||
|
> Phase 15.5 指針(いいとこ取り)
|
||||||
|
> - 依存の唯一の真実(SSOT): `nyash.toml` の `[using]`(aliases/packages/paths)
|
||||||
|
> - 実体の合成: テキスト結合は廃止し、AST マージに一本化(曖昧さ根絶)
|
||||||
|
> - プロファイル運用: `NYASH_USING_PROFILE={dev|ci|prod}` で厳格度を段階的に切替
|
||||||
|
> - dev: toml + ファイル内 using を許可(実験/便利)
|
||||||
|
> - ci: toml 優先、ファイル using は警告または限定許可
|
||||||
|
> - prod: toml のみ。ファイル using/path はエラー(追記ガイドを提示)
|
||||||
|
|
||||||
## 🎯 設計思想:Everything has Namespace
|
## 🎯 設計思想:Everything has Namespace
|
||||||
|
|
||||||
### **核心コンセプト**
|
### **核心コンセプト**
|
||||||
@ -56,8 +64,8 @@ pub enum QualifiedCallee {
|
|||||||
Policy
|
Policy
|
||||||
- Accept `using` lines at the top of the file to declare module namespaces or file imports.
|
- Accept `using` lines at the top of the file to declare module namespaces or file imports.
|
||||||
- Resolution is performed by the Rust Runner when `NYASH_ENABLE_USING=1`.
|
- Resolution is performed by the Rust Runner when `NYASH_ENABLE_USING=1`.
|
||||||
- Runner strips `using` lines from the source before parsing/execution.
|
- 実体の結合は AST マージのみ。テキストの前置き/連結は行わない(移行完了後に完全廃止)。
|
||||||
- Registers modules into an internal registry for path/namespace hints.
|
- Runner は `nyash.toml` の `[using]` を唯一の真実として参照(prod)。dev/ci は段階的に緩和可能。
|
||||||
- Selfhost compiler (Ny→JSON v0) collects using lines and emits `meta.usings` when present. The bridge currently ignores this meta field.
|
- Selfhost compiler (Ny→JSON v0) collects using lines and emits `meta.usings` when present. The bridge currently ignores this meta field.
|
||||||
|
|
||||||
## Namespace Resolution (Runner‑side)
|
## Namespace Resolution (Runner‑side)
|
||||||
@ -84,7 +92,7 @@ Policy
|
|||||||
- 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.
|
||||||
- Examples: `needs utils.StringHelper`, `needs plugin.network.HttpClient as HttpClient`, `needs plugin.network.*`
|
- Examples: `needs utils.StringHelper`, `needs plugin.network.HttpClient as HttpClient`, `needs plugin.network.*`
|
||||||
|
|
||||||
## nyash.toml — Unified Using (Phase 15)
|
## nyash.toml — Unified Using(唯一の真実 / SSOT)
|
||||||
|
|
||||||
Using resolution is centralized under the `[using]` table. Three forms are supported:
|
Using resolution is centralized under the `[using]` table. Three forms are supported:
|
||||||
|
|
||||||
@ -98,7 +106,7 @@ Using resolution is centralized under the `[using]` table. Three forms are suppo
|
|||||||
|
|
||||||
Notes
|
Notes
|
||||||
- Aliases are fully resolved: `using json` first rewrites to `json_native`, then resolves to a concrete path via `[using.json_native]`.
|
- Aliases are fully resolved: `using json` first rewrites to `json_native`, then resolves to a concrete path via `[using.json_native]`.
|
||||||
- `include` is deprecated. Use `using "./path/to/file.nyash" as Name` instead.
|
- `include` は廃止。代替は `using "./path/to/file.nyash" as Name`。prod では `nyash.toml` への登録が必須。
|
||||||
|
|
||||||
### Dylib autoload (dev guard)
|
### Dylib autoload (dev guard)
|
||||||
- Enable autoload during using resolution: set env `NYASH_USING_DYLIB_AUTOLOAD=1`.
|
- Enable autoload during using resolution: set env `NYASH_USING_DYLIB_AUTOLOAD=1`.
|
||||||
@ -182,12 +190,45 @@ 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)
|
- CLI from-the-top registration: `--using "ns as Alias"` or `--using '"apps/foo.nyash" as Foo'` (repeatable)
|
||||||
|
- Using profiles (phase‑in): `NYASH_USING_PROFILE={dev|ci|prod}`
|
||||||
|
- dev: toml + file using(path)可、AST マージ、候補提示 ON
|
||||||
|
- ci: toml 優先、file using は警告/限定、AST マージ、フォールバック OFF
|
||||||
|
- prod: toml のみ、file using/path はエラー(追記ガイドを表示)
|
||||||
- Strict mode (plugin prefix required): `NYASH_PLUGIN_REQUIRE_PREFIX=1` または `nyash.toml` の `[plugins] require_prefix=true`
|
- 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"`
|
- Aliases from env: `NYASH_ALIASES="Foo=apps/foo/main.nyash,Bar=lib/bar.nyash"`
|
||||||
- Additional search paths: `NYASH_USING_PATH="apps:lib:."`
|
- 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.
|
||||||
|
|
||||||
|
## 🔬 Quick Smokes(AST + Profiles)
|
||||||
|
|
||||||
|
開発・CIで最小コストに確認できるスモークを用意しています。AST プレリュードとプロファイル(dev/prod)の基本動作をカバーします。
|
||||||
|
|
||||||
|
- dev: `using "file"` 許可 + AST マージ
|
||||||
|
- prod: `using "file"` 禁止(toml へ誘導) / alias・package は許可
|
||||||
|
|
||||||
|
実行例(quick プロファイル)
|
||||||
|
|
||||||
|
```
|
||||||
|
# 1) dev で file using が通る(AST マージ)
|
||||||
|
./tools/smokes/v2/run.sh --profile quick --filter "using_profiles_ast.sh$"
|
||||||
|
|
||||||
|
# 2) 相対パス using(サブディレクトリ)
|
||||||
|
./tools/smokes/v2/run.sh --profile quick --filter "using_relative_file_ast.sh$"
|
||||||
|
|
||||||
|
# 3) 複数プレリュード(toml packages)+ 依存(B→A)
|
||||||
|
./tools/smokes/v2/run.sh --profile quick --filter "using_multi_prelude_dep_ast.sh$"
|
||||||
|
```
|
||||||
|
|
||||||
|
テストソース
|
||||||
|
- `tools/smokes/v2/profiles/quick/core/using_profiles_ast.sh`
|
||||||
|
- `tools/smokes/v2/profiles/quick/core/using_relative_file_ast.sh`
|
||||||
|
- `tools/smokes/v2/profiles/quick/core/using_multi_prelude_dep_ast.sh`
|
||||||
|
|
||||||
|
注意
|
||||||
|
- ログに `[using] stripped line:` が出力されますが、これは AST マージ前の using 行の除去ログです(機能上問題ありません)。
|
||||||
|
- 実行バイナリは `target/release/nyash` を前提とします。未ビルド時は `cargo build --release` を実行してください。
|
||||||
|
|
||||||
## 🔗 関連ドキュメント
|
## 🔗 関連ドキュメント
|
||||||
|
|
||||||
### **設計・アーキテクチャ**
|
### **設計・アーキテクチャ**
|
||||||
@ -204,6 +245,8 @@ Runner Configuration
|
|||||||
|
|
||||||
Notes
|
Notes
|
||||||
- Phase 15 keeps resolution in the Runner to minimize parser complexity. Future phases may leverage `meta.usings` for compiler decisions.
|
- Phase 15 keeps resolution in the Runner to minimize parser complexity. Future phases may leverage `meta.usings` for compiler decisions.
|
||||||
|
- レガシー実装の扱い: テキスト前置き/括弧補正などのシムは段階的に削除(prod プロファイルから先に無効化)。
|
||||||
|
- AST マージは dev/ci/prod の全プロファイルで共通基盤とし、曖昧性(宣言≻式)問題の再発を原理的に回避する。
|
||||||
- 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時はエラーで候補を表示。
|
- 未解決時(非strict)は実行を継続し、`NYASH_RESOLVE_TRACE=1` で候補を提示。strict時はエラーで候補を表示。
|
||||||
- **Phase 15.5完了により、現代的な名前空間システムを実現予定**
|
- **Phase 15.5完了により、現代的な名前空間システムを実現予定**
|
||||||
|
|||||||
@ -13,9 +13,14 @@ paths = ["apps", "lib", "."]
|
|||||||
path = "apps/lib/json_native/"
|
path = "apps/lib/json_native/"
|
||||||
main = "parser/parser.nyash"
|
main = "parser/parser.nyash"
|
||||||
|
|
||||||
|
# JSON Native – String utilities as a standalone package (single-file)
|
||||||
|
[using.string_utils]
|
||||||
|
path = "apps/lib/json_native/utils/string.nyash"
|
||||||
|
|
||||||
[using.aliases]
|
[using.aliases]
|
||||||
# Resolve `using json as ...` to json_native when desired
|
# Resolve `using json as ...` to json_native when desired
|
||||||
json = "json_native"
|
json = "json_native"
|
||||||
|
StringUtils = "string_utils"
|
||||||
|
|
||||||
[modules]
|
[modules]
|
||||||
# Map logical namespaces to Nyash source paths (consumed by runner)
|
# Map logical namespaces to Nyash source paths (consumed by runner)
|
||||||
|
|||||||
@ -70,38 +70,60 @@ impl MirInterpreter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut pick: Option<String> = None;
|
let mut pick: Option<String> = None;
|
||||||
|
// Fast path: exact match
|
||||||
if self.functions.contains_key(&raw) {
|
if self.functions.contains_key(&raw) {
|
||||||
pick = Some(raw.clone());
|
pick = Some(raw.clone());
|
||||||
} else {
|
} else {
|
||||||
let arity = args.len();
|
// Robust normalization for names like "Box.method/Arity" or just "method"
|
||||||
let mut cands: Vec<String> = Vec::new();
|
let call_arity = args.len();
|
||||||
let suf = format!(".{}{}", raw, format!("/{}", arity));
|
let (base, ar_from_raw) = if let Some((b, a)) = raw.rsplit_once('/') {
|
||||||
for k in self.functions.keys() {
|
(b.to_string(), a.parse::<usize>().ok())
|
||||||
if k.ends_with(&suf) {
|
} else {
|
||||||
cands.push(k.clone());
|
(raw.clone(), None)
|
||||||
}
|
};
|
||||||
}
|
let want_arity = ar_from_raw.unwrap_or(call_arity);
|
||||||
if cands.is_empty() && raw.contains('/') && self.functions.contains_key(&raw) {
|
// Try exact canonical form: "base/arity"
|
||||||
cands.push(raw.clone());
|
let exact = format!("{}/{}", base, want_arity);
|
||||||
}
|
if self.functions.contains_key(&exact) {
|
||||||
if cands.len() > 1 {
|
pick = Some(exact);
|
||||||
if let Some(cur) = &self.cur_fn {
|
} else {
|
||||||
let cur_box = cur.split('.').next().unwrap_or("");
|
// Split base into optional box and method name
|
||||||
let scoped: Vec<String> = cands
|
let (maybe_box, method) = if let Some((bx, m)) = base.split_once('.') {
|
||||||
.iter()
|
(Some(bx.to_string()), m.to_string())
|
||||||
.filter(|k| k.starts_with(&format!("{}.", cur_box)))
|
} else {
|
||||||
.cloned()
|
(None, base.clone())
|
||||||
.collect();
|
};
|
||||||
if scoped.len() == 1 {
|
// Collect candidates that end with ".method/arity"
|
||||||
cands = scoped;
|
let mut cands: Vec<String> = Vec::new();
|
||||||
|
let tail = format!(".{}{}", method, format!("/{}", want_arity));
|
||||||
|
for k in self.functions.keys() {
|
||||||
|
if k.ends_with(&tail) {
|
||||||
|
if let Some(ref bx) = maybe_box {
|
||||||
|
if k.starts_with(&format!("{}.", bx)) { cands.push(k.clone()); }
|
||||||
|
} else {
|
||||||
|
cands.push(k.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if cands.len() > 1 {
|
||||||
if cands.len() == 1 {
|
// Prefer same-box candidate based on current function's box
|
||||||
pick = Some(cands.remove(0));
|
if let Some(cur) = &self.cur_fn {
|
||||||
} else if cands.len() > 1 {
|
let cur_box = cur.split('.').next().unwrap_or("");
|
||||||
cands.sort();
|
let scoped: Vec<String> = cands
|
||||||
pick = Some(cands[0].clone());
|
.iter()
|
||||||
|
.filter(|k| k.starts_with(&format!("{}.", cur_box)))
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
if scoped.len() == 1 { cands = scoped; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cands.len() == 1 {
|
||||||
|
pick = Some(cands.remove(0));
|
||||||
|
} else if cands.len() > 1 {
|
||||||
|
let mut c = cands;
|
||||||
|
c.sort();
|
||||||
|
pick = Some(c.remove(0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,6 +198,22 @@ impl MirInterpreter {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"substring" => {
|
||||||
|
let start = if let Some(a0) = args.get(0) {
|
||||||
|
self.reg_load(*a0)?.as_integer().unwrap_or(0)
|
||||||
|
} else { 0 };
|
||||||
|
let end = if let Some(a1) = args.get(1) {
|
||||||
|
self.reg_load(*a1)?.as_integer().unwrap_or(s.len() as i64)
|
||||||
|
} else { s.len() as i64 };
|
||||||
|
let len = s.len() as i64;
|
||||||
|
let i0 = start.max(0).min(len) as usize;
|
||||||
|
let i1 = end.max(0).min(len) as usize;
|
||||||
|
if i0 > i1 { return Ok(VMValue::String(String::new())); }
|
||||||
|
// Note: operating on bytes; Nyash strings are UTF‑8, but tests are ASCII only here
|
||||||
|
let bytes = s.as_bytes();
|
||||||
|
let sub = String::from_utf8(bytes[i0..i1].to_vec()).unwrap_or_default();
|
||||||
|
Ok(VMValue::String(sub))
|
||||||
|
}
|
||||||
_ => Err(VMError::InvalidInstruction(format!(
|
_ => Err(VMError::InvalidInstruction(format!(
|
||||||
"Unknown String method: {}",
|
"Unknown String method: {}",
|
||||||
method
|
method
|
||||||
|
|||||||
@ -335,6 +335,25 @@ pub fn enable_using() -> bool {
|
|||||||
_ => true, // デフォルト: ON
|
_ => true, // デフォルト: ON
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Using profiles (dev|ci|prod) ----
|
||||||
|
/// Return using profile string; default is "dev".
|
||||||
|
pub fn using_profile() -> String {
|
||||||
|
std::env::var("NYASH_USING_PROFILE").unwrap_or_else(|_| "dev".to_string())
|
||||||
|
}
|
||||||
|
pub fn using_is_prod() -> bool { using_profile().eq_ignore_ascii_case("prod") }
|
||||||
|
pub fn using_is_ci() -> bool { using_profile().eq_ignore_ascii_case("ci") }
|
||||||
|
pub fn using_is_dev() -> bool { using_profile().eq_ignore_ascii_case("dev") }
|
||||||
|
/// Allow `using "path"` statements in source (dev-only by default).
|
||||||
|
pub fn allow_using_file() -> bool {
|
||||||
|
if using_is_prod() { return false; }
|
||||||
|
// Optional explicit override
|
||||||
|
match std::env::var("NYASH_ALLOW_USING_FILE").ok().as_deref() {
|
||||||
|
Some("0") | Some("false") | Some("off") => false,
|
||||||
|
Some("1") | Some("true") | Some("on") => true,
|
||||||
|
_ => true, // dev/ci default: allowed
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn resolve_fix_braces() -> bool {
|
pub fn resolve_fix_braces() -> bool {
|
||||||
// Safer default: OFF(誤補正の副作用を避ける)
|
// Safer default: OFF(誤補正の副作用を避ける)
|
||||||
// 明示ON: NYASH_RESOLVE_FIX_BRACES=1
|
// 明示ON: NYASH_RESOLVE_FIX_BRACES=1
|
||||||
|
|||||||
@ -297,10 +297,12 @@ impl super::MirBuilder {
|
|||||||
args: Vec<ASTNode>,
|
args: Vec<ASTNode>,
|
||||||
) -> Result<ValueId, String> {
|
) -> Result<ValueId, String> {
|
||||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||||
|
let cur_fun = self.current_function.as_ref().map(|f| f.signature.name.clone()).unwrap_or_else(|| "<none>".to_string());
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[builder] function-call name={} static_ctx={}",
|
"[builder] function-call name={} static_ctx={} in_fn={}",
|
||||||
name,
|
name,
|
||||||
self.current_static_box.as_deref().unwrap_or("")
|
self.current_static_box.as_deref().unwrap_or(""),
|
||||||
|
cur_fun
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Minimal TypeOp wiring via function-style: isType(value, "Type"), asType(value, "Type")
|
// Minimal TypeOp wiring via function-style: isType(value, "Type"), asType(value, "Type")
|
||||||
@ -577,6 +579,14 @@ impl super::MirBuilder {
|
|||||||
params: Vec<String>,
|
params: Vec<String>,
|
||||||
body: Vec<ASTNode>,
|
body: Vec<ASTNode>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
|
// Derive static box context from function name prefix, e.g., "BoxName.method/N"
|
||||||
|
let saved_static_ctx = self.current_static_box.clone();
|
||||||
|
if let Some(pos) = func_name.find('.') {
|
||||||
|
let box_name = &func_name[..pos];
|
||||||
|
if !box_name.is_empty() {
|
||||||
|
self.current_static_box = Some(box_name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
let signature = function_lowering::prepare_static_method_signature(
|
let signature = function_lowering::prepare_static_method_signature(
|
||||||
func_name,
|
func_name,
|
||||||
¶ms,
|
¶ms,
|
||||||
@ -652,6 +662,8 @@ impl super::MirBuilder {
|
|||||||
self.current_block = saved_block;
|
self.current_block = saved_block;
|
||||||
self.variable_map = saved_var_map;
|
self.variable_map = saved_var_map;
|
||||||
self.value_gen = saved_value_gen;
|
self.value_gen = saved_value_gen;
|
||||||
|
// Restore static box context
|
||||||
|
self.current_static_box = saved_static_ctx;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,15 @@ impl NyashParser {
|
|||||||
// Track last inserted method name to allow postfix catch/cleanup fallback parsing
|
// Track last inserted method name to allow postfix catch/cleanup fallback parsing
|
||||||
let mut last_method_name: Option<String> = None;
|
let mut last_method_name: Option<String> = None;
|
||||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||||
|
// Tolerate blank lines between members
|
||||||
|
while self.match_token(&TokenType::NEWLINE) { self.advance(); }
|
||||||
|
let trace = std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1");
|
||||||
|
if trace {
|
||||||
|
eprintln!(
|
||||||
|
"[parser][static-box] loop token={:?}",
|
||||||
|
self.current_token().token_type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Fallback: method-level postfix catch/cleanup immediately following a method
|
// Fallback: method-level postfix catch/cleanup immediately following a method
|
||||||
if crate::parser::declarations::box_def::members::postfix::try_parse_method_postfix_after_last_method(
|
if crate::parser::declarations::box_def::members::postfix::try_parse_method_postfix_after_last_method(
|
||||||
@ -45,8 +54,14 @@ impl NyashParser {
|
|||||||
static_init = Some(body);
|
static_init = Some(body);
|
||||||
continue;
|
continue;
|
||||||
} else if self.match_token(&TokenType::STATIC) {
|
} else if self.match_token(&TokenType::STATIC) {
|
||||||
// STRICT で top-level seam を検出した場合は while を抜ける
|
// 互換用の暫定ガード(既定OFF): using テキスト結合の継ぎ目で誤って 'static' が入った場合に
|
||||||
break;
|
// ループを抜けて外側の '}' 消費に委ねる。既定では無効化し、文脈エラーとして扱う。
|
||||||
|
if std::env::var("NYASH_PARSER_SEAM_BREAK_ON_STATIC").ok().as_deref() == Some("1") {
|
||||||
|
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[parser][static-box][seam] encountered 'static' inside static box; breaking (compat shim)");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// initブロックの処理(共通ヘルパに委譲)
|
// initブロックの処理(共通ヘルパに委譲)
|
||||||
@ -69,6 +84,12 @@ impl NyashParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!(
|
||||||
|
"[parser][static-box] closing '}}' at token={:?}",
|
||||||
|
self.current_token().token_type
|
||||||
|
);
|
||||||
|
}
|
||||||
self.consume(TokenType::RBRACE)?;
|
self.consume(TokenType::RBRACE)?;
|
||||||
|
|
||||||
// 🔥 Static初期化ブロックから依存関係を抽出
|
// 🔥 Static初期化ブロックから依存関係を抽出
|
||||||
|
|||||||
@ -56,6 +56,7 @@ pub(crate) fn try_parse_method_or_field(
|
|||||||
fields: &mut Vec<String>,
|
fields: &mut Vec<String>,
|
||||||
last_method_name: &mut Option<String>,
|
last_method_name: &mut Option<String>,
|
||||||
) -> Result<bool, ParseError> {
|
) -> Result<bool, ParseError> {
|
||||||
|
let trace = std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1");
|
||||||
// Allow NEWLINE(s) between identifier and '('
|
// Allow NEWLINE(s) between identifier and '('
|
||||||
if !p.match_token(&TokenType::LPAREN) {
|
if !p.match_token(&TokenType::LPAREN) {
|
||||||
// Lookahead skipping NEWLINE to see if a '(' follows → treat as method head
|
// Lookahead skipping NEWLINE to see if a '(' follows → treat as method head
|
||||||
@ -65,11 +66,13 @@ pub(crate) fn try_parse_method_or_field(
|
|||||||
// Consume intervening NEWLINEs so current becomes '('
|
// Consume intervening NEWLINEs so current becomes '('
|
||||||
while p.match_token(&TokenType::NEWLINE) { p.advance(); }
|
while p.match_token(&TokenType::NEWLINE) { p.advance(); }
|
||||||
} else {
|
} else {
|
||||||
|
if trace { eprintln!("[parser][static-box] field detected: {}", name); }
|
||||||
// Field
|
// Field
|
||||||
fields.push(name);
|
fields.push(name);
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if trace { eprintln!("[parser][static-box] method head detected: {}(..)", name); }
|
||||||
// Method
|
// Method
|
||||||
p.advance(); // consume '('
|
p.advance(); // consume '('
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
@ -84,14 +87,20 @@ pub(crate) fn try_parse_method_or_field(
|
|||||||
p.consume(TokenType::RPAREN)?;
|
p.consume(TokenType::RPAREN)?;
|
||||||
// Allow NEWLINE(s) between ')' and '{' of method body
|
// Allow NEWLINE(s) between ')' and '{' of method body
|
||||||
while p.match_token(&TokenType::NEWLINE) { p.advance(); }
|
while p.match_token(&TokenType::NEWLINE) { p.advance(); }
|
||||||
let body = p.parse_block_statements()?;
|
// Parse method body; optionally use strict method-body guard when enabled
|
||||||
|
let body = if std::env::var("NYASH_PARSER_METHOD_BODY_STRICT").ok().as_deref() == Some("1") {
|
||||||
|
p.parse_method_body_statements()?
|
||||||
|
} else {
|
||||||
|
p.parse_block_statements()?
|
||||||
|
};
|
||||||
let body = wrap_method_body_with_postfix_if_any(p, body)?;
|
let body = wrap_method_body_with_postfix_if_any(p, body)?;
|
||||||
// Construct method node
|
// Construct method node
|
||||||
let method = ASTNode::FunctionDeclaration {
|
let method = ASTNode::FunctionDeclaration {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
params,
|
params,
|
||||||
body,
|
body,
|
||||||
is_static: false,
|
// Methods inside a static box are semantically static
|
||||||
|
is_static: true,
|
||||||
is_override: false,
|
is_override: false,
|
||||||
span: crate::ast::Span::unknown(),
|
span: crate::ast::Span::unknown(),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -83,13 +83,96 @@ impl NyashParser {
|
|||||||
|
|
||||||
/// Parse block statements: { statement* }
|
/// Parse block statements: { statement* }
|
||||||
pub(super) fn parse_block_statements(&mut self) -> Result<Vec<ASTNode>, ParseError> {
|
pub(super) fn parse_block_statements(&mut self) -> Result<Vec<ASTNode>, ParseError> {
|
||||||
|
let trace_blocks = std::env::var("NYASH_PARSER_TRACE_BLOCKS").ok().as_deref() == Some("1");
|
||||||
|
if trace_blocks {
|
||||||
|
eprintln!(
|
||||||
|
"[parser][block] enter '{{' at line {}",
|
||||||
|
self.current_token().line
|
||||||
|
);
|
||||||
|
}
|
||||||
self.consume(TokenType::LBRACE)?;
|
self.consume(TokenType::LBRACE)?;
|
||||||
let mut statements = Vec::new();
|
let mut statements = Vec::new();
|
||||||
|
|
||||||
while !self.is_at_end() && !self.match_token(&TokenType::RBRACE) {
|
while !self.is_at_end() && !self.match_token(&TokenType::RBRACE) {
|
||||||
statements.push(self.parse_statement()?);
|
statements.push(self.parse_statement()?);
|
||||||
}
|
}
|
||||||
|
if trace_blocks {
|
||||||
|
eprintln!(
|
||||||
|
"[parser][block] exit '}}' at line {}",
|
||||||
|
self.current_token().line
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.consume(TokenType::RBRACE)?;
|
||||||
|
Ok(statements)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse method body statements: { statement* }
|
||||||
|
/// Optional seam-guard (env-gated via NYASH_PARSER_METHOD_BODY_STRICT=1) is applied
|
||||||
|
/// conservatively at top-level only, and only right after a nested block '}' was
|
||||||
|
/// just consumed, to avoid false positives inside method bodies.
|
||||||
|
pub(super) fn parse_method_body_statements(&mut self) -> Result<Vec<ASTNode>, ParseError> {
|
||||||
|
// Reuse block entry tracing
|
||||||
|
let trace_blocks = std::env::var("NYASH_PARSER_TRACE_BLOCKS").ok().as_deref() == Some("1");
|
||||||
|
if trace_blocks {
|
||||||
|
eprintln!(
|
||||||
|
"[parser][block] enter '{{' (method) at line {}",
|
||||||
|
self.current_token().line
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.consume(TokenType::LBRACE)?;
|
||||||
|
let mut statements = Vec::new();
|
||||||
|
|
||||||
|
// Helper: lookahead for `ident '(' ... ')' [NEWLINE*] '{'`
|
||||||
|
let mut looks_like_method_head = |this: &Self| -> bool {
|
||||||
|
// Only meaningful when starting at a new statement head
|
||||||
|
match &this.current_token().token_type {
|
||||||
|
TokenType::IDENTIFIER(_) => {
|
||||||
|
// Expect '(' after optional NEWLINE
|
||||||
|
let mut k = 1usize;
|
||||||
|
while matches!(this.peek_nth_token(k), TokenType::NEWLINE) { k += 1; }
|
||||||
|
if !matches!(this.peek_nth_token(k), TokenType::LPAREN) { return false; }
|
||||||
|
// Walk to matching ')'
|
||||||
|
k += 1; // after '('
|
||||||
|
let mut depth: i32 = 1;
|
||||||
|
while !matches!(this.peek_nth_token(k), TokenType::EOF) {
|
||||||
|
match this.peek_nth_token(k) {
|
||||||
|
TokenType::LPAREN => depth += 1,
|
||||||
|
TokenType::RPAREN => { depth -= 1; if depth == 0 { k += 1; break; } },
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
k += 1;
|
||||||
|
}
|
||||||
|
// Allow NEWLINE(s) between ')' and '{'
|
||||||
|
while matches!(this.peek_nth_token(k), TokenType::NEWLINE) { k += 1; }
|
||||||
|
matches!(this.peek_nth_token(k), TokenType::LBRACE)
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
while !self.is_at_end() && !self.match_token(&TokenType::RBRACE) {
|
||||||
|
statements.push(self.parse_statement()?);
|
||||||
|
// Conservative seam guard: apply only when env is ON, we just consumed a '}'
|
||||||
|
// (end of a nested block), and the next tokens at the top-level look like a
|
||||||
|
// method head. This limits the guard to real seams between members.
|
||||||
|
if std::env::var("NYASH_PARSER_METHOD_BODY_STRICT").ok().as_deref() == Some("1") {
|
||||||
|
// If the next token would close the current method, do not guard here
|
||||||
|
if self.match_token(&TokenType::RBRACE) { break; }
|
||||||
|
// Check if we just consumed a '}' token from inner content
|
||||||
|
let just_saw_rbrace = if self.current > 0 {
|
||||||
|
matches!(self.tokens[self.current - 1].token_type, TokenType::RBRACE)
|
||||||
|
} else { false };
|
||||||
|
if just_saw_rbrace && looks_like_method_head(self) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if trace_blocks {
|
||||||
|
eprintln!(
|
||||||
|
"[parser][block] exit '}}' (method) at line {}",
|
||||||
|
self.current_token().line
|
||||||
|
);
|
||||||
|
}
|
||||||
self.consume(TokenType::RBRACE)?;
|
self.consume(TokenType::RBRACE)?;
|
||||||
Ok(statements)
|
Ok(statements)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,7 +56,7 @@ impl NyashRunner {
|
|||||||
let mut prelude_asts: Vec<nyash_rust::ast::ASTNode> = Vec::new();
|
let mut prelude_asts: Vec<nyash_rust::ast::ASTNode> = Vec::new();
|
||||||
if crate::config::env::enable_using() {
|
if crate::config::env::enable_using() {
|
||||||
if use_ast {
|
if use_ast {
|
||||||
match crate::runner::modes::common_util::resolve::collect_using_and_strip(self, &code, filename) {
|
match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(self, &code, filename) {
|
||||||
Ok((clean, paths)) => {
|
Ok((clean, paths)) => {
|
||||||
cleaned_code_owned = clean; code_ref = &cleaned_code_owned;
|
cleaned_code_owned = clean; code_ref = &cleaned_code_owned;
|
||||||
// Parse each prelude file into AST and store
|
// Parse each prelude file into AST and store
|
||||||
@ -109,6 +109,30 @@ impl NyashRunner {
|
|||||||
ASTNode::Program { statements: combined, span: nyash_rust::ast::Span::unknown() }
|
ASTNode::Program { statements: combined, span: nyash_rust::ast::Span::unknown() }
|
||||||
} else { main_ast };
|
} else { main_ast };
|
||||||
|
|
||||||
|
// Optional: dump AST statement kinds for quick diagnostics
|
||||||
|
if std::env::var("NYASH_AST_DUMP").ok().as_deref() == Some("1") {
|
||||||
|
use nyash_rust::ast::ASTNode;
|
||||||
|
eprintln!("[ast] dump start");
|
||||||
|
if let ASTNode::Program { statements, .. } = &ast {
|
||||||
|
for (i, st) in statements.iter().enumerate().take(50) {
|
||||||
|
let kind = match st {
|
||||||
|
ASTNode::BoxDeclaration { is_static, name, .. } => {
|
||||||
|
if *is_static { format!("StaticBox({})", name) } else { format!("Box({})", name) }
|
||||||
|
}
|
||||||
|
ASTNode::FunctionDeclaration { name, .. } => format!("FuncDecl({})", name),
|
||||||
|
ASTNode::FunctionCall { name, .. } => format!("FuncCall({})", name),
|
||||||
|
ASTNode::MethodCall { method, .. } => format!("MethodCall({})", method),
|
||||||
|
ASTNode::ScopeBox { .. } => "ScopeBox".to_string(),
|
||||||
|
ASTNode::ImportStatement { path, .. } => format!("Import({})", path),
|
||||||
|
ASTNode::UsingStatement { namespace_name, .. } => format!("Using({})", namespace_name),
|
||||||
|
_ => format!("{:?}", st),
|
||||||
|
};
|
||||||
|
eprintln!("[ast] {}: {}", i, kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eprintln!("[ast] dump end");
|
||||||
|
}
|
||||||
|
|
||||||
// Stage-0: import loader (top-level only) — resolve path and register in modules registry
|
// Stage-0: import loader (top-level only) — resolve path and register in modules registry
|
||||||
if let nyash_rust::ast::ASTNode::Program { statements, .. } = &ast {
|
if let nyash_rust::ast::ASTNode::Program { statements, .. } = &ast {
|
||||||
for st in statements {
|
for st in statements {
|
||||||
|
|||||||
@ -8,5 +8,4 @@ pub mod strip;
|
|||||||
pub mod seam;
|
pub mod seam;
|
||||||
|
|
||||||
// Public re-exports to preserve existing call sites
|
// Public re-exports to preserve existing call sites
|
||||||
pub use strip::{strip_using_and_register, preexpand_at_local};
|
pub use strip::{strip_using_and_register, preexpand_at_local, collect_using_and_strip, resolve_prelude_paths_profiled};
|
||||||
|
|
||||||
|
|||||||
@ -62,6 +62,10 @@ pub fn strip_using_and_register(
|
|||||||
if !crate::config::env::enable_using() {
|
if !crate::config::env::enable_using() {
|
||||||
return Ok(code.to_string());
|
return Ok(code.to_string());
|
||||||
}
|
}
|
||||||
|
// Profile guard: legacy text inlining is not allowed under prod profile
|
||||||
|
if crate::config::env::using_is_prod() {
|
||||||
|
return Err("using: text inlining is disabled in prod profile. Enable NYASH_USING_AST=1 and declare dependencies in nyash.toml [using]".to_string());
|
||||||
|
}
|
||||||
// Optional external combiner (default OFF): NYASH_USING_COMBINER=1
|
// Optional external combiner (default OFF): NYASH_USING_COMBINER=1
|
||||||
if std::env::var("NYASH_USING_COMBINER").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_USING_COMBINER").ok().as_deref() == Some("1") {
|
||||||
let fix_braces = crate::config::env::resolve_fix_braces();
|
let fix_braces = crate::config::env::resolve_fix_braces();
|
||||||
@ -435,6 +439,8 @@ pub fn collect_using_and_strip(
|
|||||||
return Ok((code.to_string(), Vec::new()));
|
return Ok((code.to_string(), Vec::new()));
|
||||||
}
|
}
|
||||||
let using_ctx = runner.init_using_context();
|
let using_ctx = runner.init_using_context();
|
||||||
|
let prod = crate::config::env::using_is_prod();
|
||||||
|
let dev = crate::config::env::using_is_dev();
|
||||||
let strict = std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1");
|
let strict = std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1");
|
||||||
let verbose = crate::config::env::cli_verbose()
|
let verbose = crate::config::env::cli_verbose()
|
||||||
|| std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1");
|
|| std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1");
|
||||||
@ -454,6 +460,12 @@ pub fn collect_using_and_strip(
|
|||||||
} else { (rest0.to_string(), None) };
|
} else { (rest0.to_string(), None) };
|
||||||
let is_path = target.starts_with('"') || target.starts_with("./") || target.starts_with('/') || target.ends_with(".nyash");
|
let is_path = target.starts_with('"') || target.starts_with("./") || target.starts_with('/') || target.ends_with(".nyash");
|
||||||
if is_path {
|
if is_path {
|
||||||
|
if prod || !crate::config::env::allow_using_file() {
|
||||||
|
return Err(format!(
|
||||||
|
"using: file paths are disallowed in this profile. Add it to nyash.toml [using] (packages/aliases) and reference by name: {}",
|
||||||
|
target
|
||||||
|
));
|
||||||
|
}
|
||||||
let path = target.trim_matches('"').to_string();
|
let path = target.trim_matches('"').to_string();
|
||||||
// Resolve relative to current file dir
|
// Resolve relative to current file dir
|
||||||
let mut p = std::path::PathBuf::from(&path);
|
let mut p = std::path::PathBuf::from(&path);
|
||||||
@ -464,29 +476,67 @@ pub fn collect_using_and_strip(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Resolve namespaces/packages
|
// Resolve namespaces/packages
|
||||||
match crate::runner::pipeline::resolve_using_target(
|
if prod {
|
||||||
&target,
|
// prod: only allow names present in aliases/packages (toml)
|
||||||
false,
|
let mut pkg_name: String = target.clone();
|
||||||
&using_ctx.pending_modules,
|
if let Some(v) = using_ctx.aliases.get(&target) {
|
||||||
&using_ctx.using_paths,
|
pkg_name = v.clone();
|
||||||
&using_ctx.aliases,
|
}
|
||||||
&using_ctx.packages,
|
if let Some(pkg) = using_ctx.packages.get(&pkg_name) {
|
||||||
ctx_dir,
|
use crate::using::spec::PackageKind;
|
||||||
strict,
|
match pkg.kind {
|
||||||
verbose,
|
PackageKind::Dylib => {
|
||||||
) {
|
// dylib: nothing to prelude-parse; runtime loader handles it.
|
||||||
Ok(value) => {
|
}
|
||||||
// Only file paths are candidates for AST prelude merge
|
PackageKind::Package => {
|
||||||
if value.ends_with(".nyash") || value.contains('/') || value.contains('\\') {
|
let base = std::path::Path::new(&pkg.path);
|
||||||
// Resolve relative
|
let out = if let Some(m) = &pkg.main {
|
||||||
let mut p = std::path::PathBuf::from(&value);
|
if base.extension().and_then(|s| s.to_str()) == Some("nyash") {
|
||||||
if p.is_relative() {
|
pkg.path.clone()
|
||||||
if let Some(dir) = ctx_dir { let cand = dir.join(&p); if cand.exists() { p = cand; } }
|
} else {
|
||||||
}
|
base.join(m).to_string_lossy().to_string()
|
||||||
prelude_paths.push(p.to_string_lossy().to_string());
|
}
|
||||||
}
|
} else if base.extension().and_then(|s| s.to_str()) == Some("nyash") {
|
||||||
|
pkg.path.clone()
|
||||||
|
} else {
|
||||||
|
let leaf = base.file_name().and_then(|s| s.to_str()).unwrap_or(&pkg_name);
|
||||||
|
base.join(format!("{}.nyash", leaf)).to_string_lossy().to_string()
|
||||||
|
};
|
||||||
|
prelude_paths.push(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(format!(
|
||||||
|
"using: '{}' not found in nyash.toml [using]. Define a package or alias and use its name (prod profile)",
|
||||||
|
target
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// dev/ci: allow broader resolution via resolver
|
||||||
|
match crate::runner::pipeline::resolve_using_target(
|
||||||
|
&target,
|
||||||
|
false,
|
||||||
|
&using_ctx.pending_modules,
|
||||||
|
&using_ctx.using_paths,
|
||||||
|
&using_ctx.aliases,
|
||||||
|
&using_ctx.packages,
|
||||||
|
ctx_dir,
|
||||||
|
strict,
|
||||||
|
verbose,
|
||||||
|
) {
|
||||||
|
Ok(value) => {
|
||||||
|
// Only file paths are candidates for AST prelude merge
|
||||||
|
if value.ends_with(".nyash") || value.contains('/') || value.contains('\\') {
|
||||||
|
// Resolve relative
|
||||||
|
let mut p = std::path::PathBuf::from(&value);
|
||||||
|
if p.is_relative() {
|
||||||
|
if let Some(dir) = ctx_dir { let cand = dir.join(&p); if cand.exists() { p = cand; } }
|
||||||
|
}
|
||||||
|
prelude_paths.push(p.to_string_lossy().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => return Err(format!("using: {}", e)),
|
||||||
}
|
}
|
||||||
Err(e) => return Err(format!("using: {}", e)),
|
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -503,6 +553,17 @@ pub fn collect_using_and_strip(
|
|||||||
Ok((out, prelude_paths))
|
Ok((out, prelude_paths))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Profile-aware prelude resolution wrapper.
|
||||||
|
/// Currently delegates to `collect_using_and_strip`, but provides a single
|
||||||
|
/// entry point for callers (common and vm_fallback) to avoid logic drift.
|
||||||
|
pub fn resolve_prelude_paths_profiled(
|
||||||
|
runner: &NyashRunner,
|
||||||
|
code: &str,
|
||||||
|
filename: &str,
|
||||||
|
) -> Result<(String, Vec<String>), String> {
|
||||||
|
collect_using_and_strip(runner, code, filename)
|
||||||
|
}
|
||||||
|
|
||||||
/// Pre-expand line-head `@name[: Type] = expr` into `local name[: Type] = expr`.
|
/// Pre-expand line-head `@name[: Type] = expr` into `local name[: Type] = expr`.
|
||||||
/// Minimal, safe, no semantics change. Applies only at line head (after spaces/tabs).
|
/// Minimal, safe, no semantics change. Applies only at line head (after spaces/tabs).
|
||||||
pub fn preexpand_at_local(src: &str) -> String {
|
pub fn preexpand_at_local(src: &str) -> String {
|
||||||
|
|||||||
@ -12,23 +12,79 @@ impl NyashRunner {
|
|||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); }
|
Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); }
|
||||||
};
|
};
|
||||||
// Using preprocessing (strip + autoload)
|
// Using preprocessing (legacy inline or AST-prelude merge when NYASH_USING_AST=1)
|
||||||
let mut code2 = code;
|
let mut code2 = code;
|
||||||
|
let use_ast_prelude = crate::config::env::enable_using()
|
||||||
|
&& std::env::var("NYASH_USING_AST").ok().as_deref() == Some("1");
|
||||||
|
let mut prelude_asts: Vec<nyash_rust::ast::ASTNode> = Vec::new();
|
||||||
if crate::config::env::enable_using() {
|
if crate::config::env::enable_using() {
|
||||||
match crate::runner::modes::common_util::resolve::strip_using_and_register(self, &code2, filename) {
|
if use_ast_prelude {
|
||||||
Ok(s) => { code2 = s; }
|
match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(self, &code2, filename) {
|
||||||
Err(e) => { eprintln!("❌ {}", e); process::exit(1); }
|
Ok((clean, paths)) => {
|
||||||
|
code2 = clean;
|
||||||
|
for p in paths {
|
||||||
|
match std::fs::read_to_string(&p) {
|
||||||
|
Ok(src) => match NyashParser::parse_from_string(&src) {
|
||||||
|
Ok(ast) => prelude_asts.push(ast),
|
||||||
|
Err(e) => { eprintln!("❌ Parse error in using prelude {}: {}", p, e); process::exit(1); }
|
||||||
|
},
|
||||||
|
Err(e) => { eprintln!("❌ Error reading using prelude {}: {}", p, e); process::exit(1); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => { eprintln!("❌ {}", e); process::exit(1); }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match crate::runner::modes::common_util::resolve::strip_using_and_register(self, &code2, filename) {
|
||||||
|
Ok(s) => { code2 = s; }
|
||||||
|
Err(e) => { eprintln!("❌ {}", e); process::exit(1); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Dev sugar pre-expand: @name = expr → local name = expr
|
// Dev sugar pre-expand: @name = expr → local name = expr
|
||||||
code2 = crate::runner::modes::common_util::resolve::preexpand_at_local(&code2);
|
code2 = crate::runner::modes::common_util::resolve::preexpand_at_local(&code2);
|
||||||
|
|
||||||
// Parse -> expand macros -> compile MIR
|
// Parse main code
|
||||||
let ast = match NyashParser::parse_from_string(&code2) {
|
let main_ast = match NyashParser::parse_from_string(&code2) {
|
||||||
Ok(ast) => ast,
|
Ok(ast) => ast,
|
||||||
Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); }
|
Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); }
|
||||||
};
|
};
|
||||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
|
// When using AST prelude mode, combine prelude ASTs + main AST into one Program before macro expansion
|
||||||
|
let ast_combined = if use_ast_prelude && !prelude_asts.is_empty() {
|
||||||
|
use nyash_rust::ast::ASTNode;
|
||||||
|
let mut combined: Vec<ASTNode> = Vec::new();
|
||||||
|
for a in prelude_asts {
|
||||||
|
if let ASTNode::Program { statements, .. } = a { combined.extend(statements); }
|
||||||
|
}
|
||||||
|
if let ASTNode::Program { statements, .. } = main_ast.clone() {
|
||||||
|
combined.extend(statements);
|
||||||
|
}
|
||||||
|
ASTNode::Program { statements: combined, span: nyash_rust::ast::Span::unknown() }
|
||||||
|
} else { main_ast };
|
||||||
|
// Optional: dump AST statement kinds for quick diagnostics
|
||||||
|
if std::env::var("NYASH_AST_DUMP").ok().as_deref() == Some("1") {
|
||||||
|
use nyash_rust::ast::ASTNode;
|
||||||
|
eprintln!("[ast] dump start (vm-fallback)");
|
||||||
|
if let ASTNode::Program { statements, .. } = &ast_combined {
|
||||||
|
for (i, st) in statements.iter().enumerate().take(50) {
|
||||||
|
let kind = match st {
|
||||||
|
ASTNode::BoxDeclaration { is_static, name, .. } => {
|
||||||
|
if *is_static { format!("StaticBox({})", name) } else { format!("Box({})", name) }
|
||||||
|
}
|
||||||
|
ASTNode::FunctionDeclaration { name, .. } => format!("FuncDecl({})", name),
|
||||||
|
ASTNode::FunctionCall { name, .. } => format!("FuncCall({})", name),
|
||||||
|
ASTNode::MethodCall { method, .. } => format!("MethodCall({})", method),
|
||||||
|
ASTNode::ScopeBox { .. } => "ScopeBox".to_string(),
|
||||||
|
ASTNode::ImportStatement { path, .. } => format!("Import({})", path),
|
||||||
|
ASTNode::UsingStatement { namespace_name, .. } => format!("Using({})", namespace_name),
|
||||||
|
_ => format!("{:?}", st),
|
||||||
|
};
|
||||||
|
eprintln!("[ast] {}: {}", i, kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eprintln!("[ast] dump end");
|
||||||
|
}
|
||||||
|
let ast = crate::r#macro::maybe_expand_and_dump(&ast_combined, false);
|
||||||
let mut compiler = MirCompiler::with_options(!self.config.no_optimize);
|
let mut compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||||||
let compile = match compiler.compile(ast) {
|
let compile = match compiler.compile(ast) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
@ -44,6 +100,12 @@ impl NyashRunner {
|
|||||||
|
|
||||||
// Execute via MIR interpreter
|
// Execute via MIR interpreter
|
||||||
let mut vm = MirInterpreter::new();
|
let mut vm = MirInterpreter::new();
|
||||||
|
if std::env::var("NYASH_DUMP_FUNCS").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[vm] functions available:");
|
||||||
|
for k in module_vm.functions.keys() {
|
||||||
|
eprintln!(" - {}", k);
|
||||||
|
}
|
||||||
|
}
|
||||||
match vm.execute_module(&module_vm) {
|
match vm.execute_module(&module_vm) {
|
||||||
Ok(_ret) => { /* interpreter already prints via println/console in program */ }
|
Ok(_ret) => { /* interpreter already prints via println/console in program */ }
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
@ -0,0 +1,63 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# using_multi_prelude_dep_ast.sh - 複数プレリュード(toml パッケージ)と依存解決(ASTマージ)
|
||||||
|
|
||||||
|
source "$(dirname "$0")/../../../lib/test_runner.sh"
|
||||||
|
|
||||||
|
require_env || exit 2
|
||||||
|
preflight_plugins || exit 2
|
||||||
|
|
||||||
|
setup_tmp_dir() {
|
||||||
|
TEST_DIR="/tmp/using_multi_prelude_$$"
|
||||||
|
mkdir -p "$TEST_DIR"
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown_tmp_dir() {
|
||||||
|
cd /
|
||||||
|
rm -rf "$TEST_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_multi_prelude_dep_ast() {
|
||||||
|
setup_tmp_dir
|
||||||
|
|
||||||
|
cat > nyash.toml << 'EOF'
|
||||||
|
[using.a]
|
||||||
|
path = "lib/a"
|
||||||
|
|
||||||
|
[using.b]
|
||||||
|
path = "lib/b"
|
||||||
|
|
||||||
|
[using]
|
||||||
|
paths = ["lib"]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mkdir -p lib/a lib/b
|
||||||
|
cat > lib/a/a.nyash << 'EOF'
|
||||||
|
static box A { x() { return "A" } }
|
||||||
|
EOF
|
||||||
|
cat > lib/b/b.nyash << 'EOF'
|
||||||
|
static box B { y() { return A.x() + "B" } }
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > main.nyash << 'EOF'
|
||||||
|
using a
|
||||||
|
using b
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
print(B.y())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
export NYASH_USING_PROFILE=prod
|
||||||
|
export NYASH_USING_AST=1
|
||||||
|
local output rc
|
||||||
|
output=$(run_nyash_vm main.nyash 2>&1)
|
||||||
|
if echo "$output" | grep -qx "AB"; then rc=0; else rc=1; fi
|
||||||
|
[ $rc -eq 0 ] || { echo "$output" >&2; }
|
||||||
|
teardown_tmp_dir
|
||||||
|
return $rc
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test "using_multi_prelude_dep_ast" test_multi_prelude_dep_ast
|
||||||
133
tools/smokes/v2/profiles/quick/core/using_profiles_ast.sh
Normal file
133
tools/smokes/v2/profiles/quick/core/using_profiles_ast.sh
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# using_profiles_ast.sh - using プロファイル(dev/prod)× AST プレリュードの基本動作チェック
|
||||||
|
|
||||||
|
source "$(dirname "$0")/../../../lib/test_runner.sh"
|
||||||
|
|
||||||
|
require_env || exit 2
|
||||||
|
preflight_plugins || exit 2
|
||||||
|
|
||||||
|
setup_tmp_dir() {
|
||||||
|
TEST_DIR="/tmp/using_profiles_ast_$$"
|
||||||
|
mkdir -p "$TEST_DIR"
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown_tmp_dir() {
|
||||||
|
cd /
|
||||||
|
rm -rf "$TEST_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test A: dev プロファイルでは `using "file"` が許可され、AST プレリュードで解決できる
|
||||||
|
test_dev_file_using_ok_ast() {
|
||||||
|
setup_tmp_dir
|
||||||
|
|
||||||
|
# nyash.toml(paths だけで十分)
|
||||||
|
cat > nyash.toml << 'EOF'
|
||||||
|
[using]
|
||||||
|
paths = ["lib"]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mkdir -p lib
|
||||||
|
cat > lib/u.nyash << 'EOF'
|
||||||
|
static box Util {
|
||||||
|
greet() { return "hi" }
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > main.nyash << 'EOF'
|
||||||
|
using "lib/u.nyash"
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
print(Util.greet())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
local output rc
|
||||||
|
# dev + AST モード(環境はexportで明示)
|
||||||
|
export NYASH_USING_PROFILE=dev
|
||||||
|
export NYASH_USING_AST=1
|
||||||
|
output=$(run_nyash_vm main.nyash 2>&1)
|
||||||
|
if echo "$output" | grep -qx "hi"; then rc=0; else rc=1; fi
|
||||||
|
[ $rc -eq 0 ] || { echo "$output" >&2; }
|
||||||
|
teardown_tmp_dir
|
||||||
|
return $rc
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test B: prod プロファイルでは `using "file"` は拒否(ガイダンス付きエラー)
|
||||||
|
test_prod_file_using_forbidden_ast() {
|
||||||
|
setup_tmp_dir
|
||||||
|
|
||||||
|
cat > nyash.toml << 'EOF'
|
||||||
|
[using]
|
||||||
|
paths = ["lib"]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mkdir -p lib
|
||||||
|
cat > lib/u.nyash << 'EOF'
|
||||||
|
static box Util { greet() { return "x" } }
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > main.nyash << 'EOF'
|
||||||
|
using "lib/u.nyash"
|
||||||
|
static box Main { main() { print(Util.greet()); return 0 } }
|
||||||
|
EOF
|
||||||
|
|
||||||
|
local output
|
||||||
|
# prod + AST モード(失敗が正)
|
||||||
|
export NYASH_USING_PROFILE=prod
|
||||||
|
export NYASH_USING_AST=1
|
||||||
|
output=$(run_nyash_vm main.nyash 2>&1 || true)
|
||||||
|
if echo "$output" | grep -qi "disallowed\|nyash.toml \[using\]"; then
|
||||||
|
test_pass "prod_file_using_forbidden_ast"
|
||||||
|
else
|
||||||
|
test_fail "prod_file_using_forbidden_ast" "expected guidance error, got: $output"
|
||||||
|
fi
|
||||||
|
|
||||||
|
teardown_tmp_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test C: prod プロファイルで alias/package は許可(AST プレリュードで読み込み)
|
||||||
|
test_prod_alias_package_ok_ast() {
|
||||||
|
setup_tmp_dir
|
||||||
|
|
||||||
|
cat > nyash.toml << 'EOF'
|
||||||
|
[using.u]
|
||||||
|
path = "lib/u"
|
||||||
|
|
||||||
|
[using]
|
||||||
|
paths = ["lib"]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mkdir -p lib/u
|
||||||
|
# main 省略 → leaf 名と同名の .nyash がデフォルト(u/u.nyash)
|
||||||
|
cat > lib/u/u.nyash << 'EOF'
|
||||||
|
static box Util {
|
||||||
|
version() { return "ok" }
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > main.nyash << 'EOF'
|
||||||
|
using u
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
print(Util.version())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
local output rc
|
||||||
|
export NYASH_USING_PROFILE=prod
|
||||||
|
export NYASH_USING_AST=1
|
||||||
|
output=$(run_nyash_vm main.nyash 2>&1)
|
||||||
|
if echo "$output" | grep -qx "ok"; then rc=0; else rc=1; fi
|
||||||
|
[ $rc -eq 0 ] || { echo "$output" >&2; }
|
||||||
|
teardown_tmp_dir
|
||||||
|
return $rc
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test "using_dev_file_ok_ast" test_dev_file_using_ok_ast
|
||||||
|
run_test "using_prod_file_forbidden_ast" test_prod_file_using_forbidden_ast
|
||||||
|
run_test "using_prod_alias_ok_ast" test_prod_alias_package_ok_ast
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# using_relative_file_ast.sh - ネストした場所からの相対パス using(AST プレリュード)
|
||||||
|
|
||||||
|
source "$(dirname "$0")/../../../lib/test_runner.sh"
|
||||||
|
|
||||||
|
require_env || exit 2
|
||||||
|
preflight_plugins || exit 2
|
||||||
|
|
||||||
|
setup_tmp_dir() {
|
||||||
|
TEST_DIR="/tmp/using_rel_file_$$"
|
||||||
|
mkdir -p "$TEST_DIR"
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown_tmp_dir() {
|
||||||
|
cd /
|
||||||
|
rm -rf "$TEST_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_relative_file_using_ast() {
|
||||||
|
setup_tmp_dir
|
||||||
|
|
||||||
|
cat > nyash.toml << 'EOF'
|
||||||
|
[using]
|
||||||
|
paths = ["lib"]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mkdir -p lib sub
|
||||||
|
cat > lib/u.nyash << 'EOF'
|
||||||
|
static box Util { greet() { return "rel" } }
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > sub/main.nyash << 'EOF'
|
||||||
|
using "../lib/u.nyash"
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
print(Util.greet())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
export NYASH_USING_PROFILE=dev
|
||||||
|
export NYASH_USING_AST=1
|
||||||
|
local output rc
|
||||||
|
output=$(run_nyash_vm sub/main.nyash 2>&1)
|
||||||
|
if echo "$output" | grep -qx "rel"; then rc=0; else rc=1; fi
|
||||||
|
[ $rc -eq 0 ] || { echo "$output" >&2; }
|
||||||
|
teardown_tmp_dir
|
||||||
|
return $rc
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test "using_relative_file_ast" test_relative_file_using_ast
|
||||||
Reference in New Issue
Block a user