resolve: apply stashed using/module + deps bridge; remove conflict markers in runner/mod.rs
This commit is contained in:
@ -24,6 +24,100 @@ Ny 構文(実装時の基準)
|
|||||||
- Cranelift 側の作業は別ブランチで継続。Self‑Hosting ブランチは Ny 工具の安定化に集中。
|
- Cranelift 側の作業は別ブランチで継続。Self‑Hosting ブランチは Ny 工具の安定化に集中。
|
||||||
- 構文の最終合意後、using/module 版(配列/マップ最小 API)へ拡張。
|
- 構文の最終合意後、using/module 版(配列/マップ最小 API)へ拡張。
|
||||||
|
|
||||||
|
|
||||||
|
## Grammar Sync — Phase 12.7 合わせ(現状と方針)
|
||||||
|
|
||||||
|
参照ドキュメント(一次ソース)
|
||||||
|
- Phase 12.7 統合: `docs/development/roadmap/phases/phase-12.7/README.md`
|
||||||
|
- Grammar 最終決定: `docs/development/roadmap/phases/phase-12.7/grammar-specs/grammar-reform-final-decision.txt`
|
||||||
|
- Grammar 技術仕様: `docs/development/roadmap/phases/phase-12.7/grammar-specs/grammar-technical-spec.txt`
|
||||||
|
- 実装チェックリスト: `docs/development/roadmap/phases/phase-12.7/implementation/implementation-final-checklist.txt`
|
||||||
|
- ANCP Token Spec (参照): `docs/development/roadmap/phases/phase-12.7/ancp-specs/ANCP-Token-Specification-v1.md`
|
||||||
|
|
||||||
|
実装状況(Phase 12.7 との整合)
|
||||||
|
- OK: `peek`(else必須・ブロック可)、`continue`、`birth` 統一、`fn{}` ラムダ(P0)、糖衣 basic(`|>`, `?.`, `??`, `+=/-=/*=/=`, `..`)ゲート済。
|
||||||
|
- OK: `Parent::method` 用 `::` トークン+ `Parent::method(args)` 解析(P1相当の先行)。
|
||||||
|
- OK: フィールド宣言 `name: Type` を受理(P0: 型はパースのみ、意味付けは今後)。
|
||||||
|
- 差分(非致命):
|
||||||
|
- import 未実装(現状は `using` のみ Phase0: `nyashstd` 制限)。
|
||||||
|
- `public name: Type` の単行は未対応(`public { ... }` ブロックは対応)。
|
||||||
|
- レガシー `>>`(ARROW)トークンとASTが残存(12.7 では不要)。
|
||||||
|
- Tokenizer に `fn` の重複割り当て(`FN`/`FUNCTION`)が存在(動作は壊していないが整理対象)。
|
||||||
|
|
||||||
|
合意に向けた軽微タスク(Self‑Hosting 主線を維持したまま)
|
||||||
|
- T1: `public name: Type` 単行を Box 内で受理(`public { ... }` は後方互換維持)。
|
||||||
|
- T2: `import` を追加(現状 `using` と並行運用、Phase0は読み取りのみ/将来の解決器に接続)。
|
||||||
|
- T3: 12.7 厳格モードゲート(例: `NYASH_STRICT_12_7=1`)
|
||||||
|
- `>>` を無効化(パーサ拒否 or トークナイズ抑止)。
|
||||||
|
- 追加キーワード群(legacy拡張)の一部を“識別子扱い”へフォールバック(実験用)。
|
||||||
|
- T4: Tokenizer の `fn` 重複を解消(`FN` を正とし `FUNCTION` 二重割り当てを削除)。
|
||||||
|
|
||||||
|
受け入れ基準(Grammar Sync)
|
||||||
|
- sugar_level=none/basic の双方でスモークが通る。
|
||||||
|
- `peek` else 未指定時に適切なエラー(現状維持)。
|
||||||
|
- `public name: Type` が Box 内でフィールドとして扱われる(最小P0)。
|
||||||
|
- 厳格モードで `>>` が受理されない/互換モードでは現状維持。
|
||||||
|
|
||||||
|
検証コマンド(例)
|
||||||
|
- `NYASH_SYNTAX_SUGAR_LEVEL=none cargo test -p nyash_self_main -- tests::sugar_basic_test -- --nocapture`(none でも tokenizer/parseは通ること)
|
||||||
|
- `NYASH_SYNTAX_SUGAR_LEVEL=basic cargo test -p nyash_self_main -- tests::sugar_pipeline_test -- --nocapture`
|
||||||
|
- `NYASH_STRICT_12_7=1 ./target/release/nyash --backend vm apps/smokes/grammar/peek_basic.nyash`(`>>` を含むコードが拒否される)
|
||||||
|
|
||||||
|
## Bitwise/Shift — main 取り込みと現状
|
||||||
|
|
||||||
|
- origin/main でビット演算(&, |, ^, <<, >>)が Grammar/Tokenizer/AST/MIR に統合済み。レガシー `>>` ARROW は撤退。
|
||||||
|
- 追加テスト(main):
|
||||||
|
- `src/tests/parser_bitops_test.rs`(代表式 `1 + 2 << 3 & 7` の構文)
|
||||||
|
- `src/tests/vm_bitops_test.rs`(`(5&3)+(5|2)+(5^1)+(1<<5)+(32>>3) == 48`、`1<<100` マスク確認)
|
||||||
|
- 確認結果(ローカル):
|
||||||
|
- MIR バックエンドは 48 で合格(OK)。
|
||||||
|
- VM バックエンドは現状 `Unsupported integer operation: BitAnd`(VM 側の実装が未導入のため)
|
||||||
|
|
||||||
|
取り込み計画(selfhosting-dev に main を統合)
|
||||||
|
1) ローカル変更を一時退避(stash)し、`origin/main` をマージ。
|
||||||
|
2) コンフリクト解消:`src/jit/lower/builder/cranelift.rs` は main 側の更新(ARROW撤退/SHR採用)を優先。
|
||||||
|
3) 検証:MIR 経路で bitops スモーク(期待 48)。
|
||||||
|
4) 次段:VM の bitops(i64限定、シフトは `rhs&63` マスク)を実装→テスト有効化。
|
||||||
|
5) LLVM/E2E:grammar 解禁後に `apps/tests/ny-llvm-bitops/` を有効化(MIR直構築は現状担保)。
|
||||||
|
|
||||||
|
実行メモ(代表)
|
||||||
|
- MIR スモーク: `printf "return (5 & 3) + (5 | 2) + (5 ^ 1) + (1 << 5) + (32 >> 3)\n" > tmp/bitops_smoke.nyash && ./target/debug/nyash --backend mir tmp/bitops_smoke.nyash` → `Result: 48`
|
||||||
|
- VM は現状未対応(実装後に同式で確認)。
|
||||||
|
|
||||||
|
TODO(bitops)
|
||||||
|
- [ ] origin/main を selfhosting-dev にマージ(conflict 解消)。
|
||||||
|
- [ ] MIR 経路のスモーク確認(48)。
|
||||||
|
- [ ] VM: i64 の `& | ^ << >>` 実装(`rhs&63` マスク)。
|
||||||
|
- [ ] tests: `vm_bitops_test.rs` を有効化(VM で合格)。
|
||||||
|
- [ ] docs: ARROW(>>) 撤退と `|>` への一本化を明記。
|
||||||
|
|
||||||
|
## Self‑Host — Include‑only Dependency Tree(Phase 0)
|
||||||
|
|
||||||
|
スコープ(Phase 0 最小)
|
||||||
|
- Nyのみ(Array/Map不使用)で include 依存木を構築し、純JSONを出力。
|
||||||
|
- using/module/import は次段。Runner は `NYASH_DEPS_JSON` をログ読み込みのみ。
|
||||||
|
|
||||||
|
現状
|
||||||
|
- ツール: `apps/selfhost/tools/dep_tree_min_string.nyash`(include専用、再帰・文字列走査)。
|
||||||
|
- 出力: `make dep-tree` → `tmp/deps.json`(純JSON化、先頭ログの除去は `[tasks].dep_tree` で吸収)。
|
||||||
|
- 走査: コメント(`//`, `#`)・文字列内の `include` を無視する状態機械を導入(誤検出抑制)。
|
||||||
|
- サンプル: `apps/selfhost/smokes/dep_smoke_root.nyash`(子: `dep_smoke_child.nyash`)。
|
||||||
|
|
||||||
|
出力仕様・受け入れ基準: docs/selfhost/dep_tree_min_string.md に移設(CURRENT_TASKは要点のみ表記)。
|
||||||
|
|
||||||
|
残タスク(Phase 0 必須)
|
||||||
|
- P0-2: スモーク(循環あり/なし)と合わせて確認(追加済み)。
|
||||||
|
- P0-3: docs への移設(完了)。
|
||||||
|
|
||||||
|
任意(Phase 0.5)
|
||||||
|
- stderr固定の徹底(将来Runner側の冗長出力をenvゲート化)。
|
||||||
|
- ルートパスの正規化(`.`,`..` の整理)と最大深さ/件数の安全弁(オプション)。
|
||||||
|
|
||||||
|
検証(代表)
|
||||||
|
- `echo apps/selfhost/smokes/dep_smoke_root.nyash | ./target/release/nyash --backend vm apps/selfhost/tools/dep_tree_min_string.nyash`
|
||||||
|
- `echo apps/selfhost/smokes/dep_smoke_cycle_a.nyash | ./target/release/nyash --backend vm apps/selfhost/tools/dep_tree_min_string.nyash`
|
||||||
|
- `make dep-tree`(ENTRYは標準入力1行 or 既定パスにフォールバック)
|
||||||
|
|
||||||
# Quick Plan — Self‑Host (Restart Safe)
|
# Quick Plan — Self‑Host (Restart Safe)
|
||||||
|
|
||||||
- Goals: Ny-only dependency tree (include → later using/module), JSON out; simple file-bridge to existing MIR→VM→AOT without tight coupling.
|
- Goals: Ny-only dependency tree (include → later using/module), JSON out; simple file-bridge to existing MIR→VM→AOT without tight coupling.
|
||||||
|
|||||||
7
apps/selfhost/smokes/dep_smoke_child.nyash
Normal file
7
apps/selfhost/smokes/dep_smoke_child.nyash
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// child file
|
||||||
|
box Child {
|
||||||
|
hello() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
7
apps/selfhost/smokes/dep_smoke_cycle_a.nyash
Normal file
7
apps/selfhost/smokes/dep_smoke_cycle_a.nyash
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// cycle A -> B -> A
|
||||||
|
include "dep_smoke_cycle_b.nyash"
|
||||||
|
|
||||||
|
box A {
|
||||||
|
id() { return 1 }
|
||||||
|
}
|
||||||
|
|
||||||
7
apps/selfhost/smokes/dep_smoke_cycle_b.nyash
Normal file
7
apps/selfhost/smokes/dep_smoke_cycle_b.nyash
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// cycle B -> A
|
||||||
|
include "dep_smoke_cycle_a.nyash"
|
||||||
|
|
||||||
|
box B {
|
||||||
|
id() { return 2 }
|
||||||
|
}
|
||||||
|
|
||||||
14
apps/selfhost/smokes/dep_smoke_root.nyash
Normal file
14
apps/selfhost/smokes/dep_smoke_root.nyash
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// root smoke for include-only tree
|
||||||
|
include "dep_smoke_child.nyash"
|
||||||
|
|
||||||
|
box Root {
|
||||||
|
main() {
|
||||||
|
// a string containing include "should_not_match.nyash"
|
||||||
|
local s = "text include \"dummy.nyash\" text"
|
||||||
|
// a comment with include "ignored.nyash"
|
||||||
|
// include "ignored.nyash"
|
||||||
|
# include "ignored2.nyash"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
154
apps/selfhost/tools/dep_tree_min_string.nyash
Normal file
154
apps/selfhost/tools/dep_tree_min_string.nyash
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// dep_tree_min_string.nyash — minimal include-only dependency tree (no Array/Map plugins)
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
has_in_stack(stack, p) {
|
||||||
|
// check if stack contains "\n" + p + "\n"
|
||||||
|
local t = "\n" + p + "\n"
|
||||||
|
local n = stack.length()
|
||||||
|
local m = t.length()
|
||||||
|
if m == 0 { return 0 }
|
||||||
|
local i = 0
|
||||||
|
loop(i + m <= n) {
|
||||||
|
if stack.substring(i, i+m) == t { return 1 }
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
read_all(path) {
|
||||||
|
local fb = new FileBox()
|
||||||
|
local ok = fb.open(path, "r")
|
||||||
|
if ok == false { return null }
|
||||||
|
local s = fb.read()
|
||||||
|
fb.close()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
dirname(path) {
|
||||||
|
local pb = new PathBox()
|
||||||
|
local d = pb.dirname(path)
|
||||||
|
if d != null { return d }
|
||||||
|
local i = path.lastIndexOf("/")
|
||||||
|
if i < 0 { return "." }
|
||||||
|
return path.substring(0, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
join(base, rel) {
|
||||||
|
local pb = new PathBox()
|
||||||
|
local j = pb.join(base, rel)
|
||||||
|
if j != null { return j }
|
||||||
|
return base + "/" + rel
|
||||||
|
}
|
||||||
|
|
||||||
|
esc_json(s) {
|
||||||
|
// very small escaper: replace \ and "
|
||||||
|
local out = ""
|
||||||
|
local i = 0
|
||||||
|
local n = s.length()
|
||||||
|
loop(i < n) {
|
||||||
|
local ch = s.substring(i, i+1)
|
||||||
|
if ch == "\\" { out = out + "\\\\" } else {
|
||||||
|
if ch == "\"" { out = out + "\\\"" } else { out = out + ch }
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
node_json(path, stack, depth) {
|
||||||
|
// safety valve: max depth
|
||||||
|
if depth >= 64 {
|
||||||
|
return "{\\\"path\\\":\\\"" + me.esc_json(path) + "\\\",\\\"includes\\\":[],\\\"children\\\":[]}"
|
||||||
|
}
|
||||||
|
local src = me.read_all(path)
|
||||||
|
if src == null {
|
||||||
|
return "{\\\"path\\\":\\\"" + me.esc_json(path) + "\\\",\\\"includes\\\":[],\\\"children\\\":[]}"
|
||||||
|
}
|
||||||
|
local base = me.dirname(path)
|
||||||
|
local incs = ""
|
||||||
|
local inc_first = 1
|
||||||
|
local children = ""
|
||||||
|
local child_first = 1
|
||||||
|
local i = 0
|
||||||
|
local n = src.length()
|
||||||
|
local in_str = 0
|
||||||
|
local in_cmt = 0
|
||||||
|
loop(i < n) {
|
||||||
|
local ch = src.substring(i, i+1)
|
||||||
|
// handle line comments (// or #)
|
||||||
|
if in_cmt == 1 {
|
||||||
|
if ch == "\n" { in_cmt = 0 }
|
||||||
|
i = i + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if in_str == 1 {
|
||||||
|
if ch == "\"" {
|
||||||
|
// if previous is not backslash, close
|
||||||
|
if i == 0 { in_str = 0 } else {
|
||||||
|
local prev = src.substring(i-1, i)
|
||||||
|
if prev != "\\" { in_str = 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// not in string/comment
|
||||||
|
if ch == "/" && i + 1 < n && src.substring(i+1, i+2) == "/" { in_cmt = 1; i = i + 2; continue }
|
||||||
|
if ch == "#" { in_cmt = 1; i = i + 1; continue }
|
||||||
|
if ch == "\"" { in_str = 1; i = i + 1; continue }
|
||||||
|
// look for include "
|
||||||
|
if i + 9 <= n && src.substring(i, i+9) == "include \"" {
|
||||||
|
local j = i + 9
|
||||||
|
// find closing quote (respect escapes)
|
||||||
|
loop(j < n) {
|
||||||
|
if src.substring(j, j+1) == "\"" {
|
||||||
|
local prev = src.substring(j-1, j)
|
||||||
|
if prev != "\\" { break }
|
||||||
|
}
|
||||||
|
j = j + 1
|
||||||
|
}
|
||||||
|
if j < n {
|
||||||
|
local p = src.substring(i+9, j)
|
||||||
|
if inc_first == 1 {
|
||||||
|
incs = incs + "\"" + me.esc_json(p) + "\""
|
||||||
|
inc_first = 0
|
||||||
|
} else {
|
||||||
|
incs = incs + ",\"" + me.esc_json(p) + "\""
|
||||||
|
}
|
||||||
|
local child_path = me.join(base, p)
|
||||||
|
// cycle detection: if child_path already in stack, do not recurse
|
||||||
|
local cj = null
|
||||||
|
if me.has_in_stack(stack, child_path) == 1 {
|
||||||
|
cj = "{\\\"path\\\":\\\"" + me.esc_json(child_path) + "\\\",\\\"includes\\\":[],\\\"children\\\":[]}"
|
||||||
|
} else {
|
||||||
|
cj = me.node_json(child_path, stack + child_path + "\n", depth + 1)
|
||||||
|
}
|
||||||
|
if child_first == 1 {
|
||||||
|
children = children + cj
|
||||||
|
child_first = 0
|
||||||
|
} else {
|
||||||
|
children = children + "," + cj
|
||||||
|
}
|
||||||
|
i = j + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return "{\\\"path\\\":\\\"" + me.esc_json(path) + "\\\",\\\"includes\\\":[" + incs + "],\\\"children\\\":[" + children + "]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
main(args) {
|
||||||
|
local console = new ConsoleBox()
|
||||||
|
local entry = null
|
||||||
|
if args != null && args.length() >= 1 { entry = args.get(0) }
|
||||||
|
if entry == null || entry == "" {
|
||||||
|
// try stdin first line
|
||||||
|
local line = console.readLine()
|
||||||
|
if line != null && line != "" { entry = line } else { entry = "apps/selfhost/ny-parser-nyash/main.nyash" }
|
||||||
|
}
|
||||||
|
local tree = me.node_json(entry, "\n" + entry + "\n", 0)
|
||||||
|
local out = "{\\\"version\\\":1,\\\"root_path\\\":\\\"" + me.esc_json(entry) + "\\\",\\\"tree\\\":" + tree + "}"
|
||||||
|
console.println(out)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -37,26 +37,35 @@ static box Main {
|
|||||||
|
|
||||||
// ---- text utils ----
|
// ---- text utils ----
|
||||||
split_lines(src) {
|
split_lines(src) {
|
||||||
|
// return { arr, len }
|
||||||
|
local pair = new MapBox()
|
||||||
local out = new ArrayBox()
|
local out = new ArrayBox()
|
||||||
|
local len = 0
|
||||||
local i = 0
|
local i = 0
|
||||||
local n = src.length()
|
local n = src.length()
|
||||||
local start = 0
|
local start = 0
|
||||||
loop(true) {
|
loop(true) {
|
||||||
if i == n { out.push(src.substring(start, i)) return out }
|
if i == n { out.push(src.substring(start, i)) len = len + 1 pair.set("arr", out) pair.set("len", len) return pair }
|
||||||
local ch = src.substring(i, i+1)
|
local ch = src.substring(i, i+1)
|
||||||
if ch == "\n" { out.push(src.substring(start, i)) start = i + 1 }
|
if ch == "\n" { out.push(src.substring(start, i)) len = len + 1 start = i + 1 }
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
return out
|
pair.set("arr", out)
|
||||||
|
pair.set("len", len)
|
||||||
|
return pair
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- scanners ----
|
// ---- scanners ----
|
||||||
scan_includes(src) {
|
scan_includes(src) {
|
||||||
|
local pair = new MapBox()
|
||||||
local out = new ArrayBox()
|
local out = new ArrayBox()
|
||||||
if src == null { return out }
|
local out_len = 0
|
||||||
local lines = me.split_lines(src)
|
if src == null { pair.set("arr", out) pair.set("len", out_len) return pair }
|
||||||
|
local lp = me.split_lines(src)
|
||||||
|
local lines = lp.get("arr")
|
||||||
|
local lines_len = lp.get("len")
|
||||||
local i = 0
|
local i = 0
|
||||||
loop(i < lines.length()) {
|
loop(i < lines_len) {
|
||||||
local t = lines.get(i).trim()
|
local t = lines.get(i).trim()
|
||||||
if t.startsWith("include \"") {
|
if t.startsWith("include \"") {
|
||||||
local rest = t.substring(9, t.length())
|
local rest = t.substring(9, t.length())
|
||||||
@ -66,19 +75,26 @@ static box Main {
|
|||||||
if rest.substring(j, j+1) == "\"" { q = j j = rest.length() }
|
if rest.substring(j, j+1) == "\"" { q = j j = rest.length() }
|
||||||
j = j + 1
|
j = j + 1
|
||||||
}
|
}
|
||||||
if q >= 0 { out.push(rest.substring(0, q)) }
|
if q >= 0 { out.push(rest.substring(0, q)) out_len = out_len + 1 }
|
||||||
}
|
}
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
return out
|
pair.set("arr", out)
|
||||||
|
pair.set("len", out_len)
|
||||||
|
return pair
|
||||||
}
|
}
|
||||||
|
|
||||||
scan_usings(src) {
|
scan_usings(src) {
|
||||||
|
// return { arr, len }
|
||||||
|
local pair = new MapBox()
|
||||||
local out = new ArrayBox()
|
local out = new ArrayBox()
|
||||||
if src == null { return out }
|
local out_len = 0
|
||||||
local lines = me.split_lines(src)
|
if src == null { pair.set("arr", out) pair.set("len", out_len) return pair }
|
||||||
|
local lp = me.split_lines(src)
|
||||||
|
local lines = lp.get("arr")
|
||||||
|
local lines_len = lp.get("len")
|
||||||
local i = 0
|
local i = 0
|
||||||
loop(i < lines.length()) {
|
loop(i < lines_len) {
|
||||||
local t0 = lines.get(i).trim()
|
local t0 = lines.get(i).trim()
|
||||||
local matched = false
|
local matched = false
|
||||||
local t = t0
|
local t = t0
|
||||||
@ -95,18 +111,26 @@ static box Main {
|
|||||||
if alias != null { rec.set("alias", alias) }
|
if alias != null { rec.set("alias", alias) }
|
||||||
if target.startsWith("./") || target.startsWith("/") || target.endsWith(".nyash") { rec.set("kind", "path") } else { rec.set("kind", "namespace") }
|
if target.startsWith("./") || target.startsWith("/") || target.endsWith(".nyash") { rec.set("kind", "path") } else { rec.set("kind", "namespace") }
|
||||||
out.push(rec)
|
out.push(rec)
|
||||||
|
out_len = out_len + 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
return out
|
pair.set("arr", out)
|
||||||
|
pair.set("len", out_len)
|
||||||
|
return pair
|
||||||
}
|
}
|
||||||
|
|
||||||
scan_modules(src) {
|
scan_modules(src) {
|
||||||
|
// return { arr, len }
|
||||||
|
local pair = new MapBox()
|
||||||
local out = new ArrayBox()
|
local out = new ArrayBox()
|
||||||
if src == null { return out }
|
local out_len = 0
|
||||||
local lines = me.split_lines(src)
|
if src == null { pair.set("arr", out) pair.set("len", out_len) return pair }
|
||||||
|
local lp = me.split_lines(src)
|
||||||
|
local lines = lp.get("arr")
|
||||||
|
local lines_len = lp.get("len")
|
||||||
local i = 0
|
local i = 0
|
||||||
loop(i < lines.length()) {
|
loop(i < lines_len) {
|
||||||
local t = lines.get(i).trim()
|
local t = lines.get(i).trim()
|
||||||
if t.startsWith("// @module ") {
|
if t.startsWith("// @module ") {
|
||||||
local rest = t.substring(11, t.length())
|
local rest = t.substring(11, t.length())
|
||||||
@ -118,11 +142,14 @@ static box Main {
|
|||||||
m.set("namespace", ns)
|
m.set("namespace", ns)
|
||||||
m.set("path", path)
|
m.set("path", path)
|
||||||
out.push(m)
|
out.push(m)
|
||||||
|
out_len = out_len + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
return out
|
pair.set("arr", out)
|
||||||
|
pair.set("len", out_len)
|
||||||
|
return pair
|
||||||
}
|
}
|
||||||
|
|
||||||
default_using_paths() {
|
default_using_paths() {
|
||||||
@ -139,8 +166,9 @@ static box Main {
|
|||||||
local cand0 = me.join(base_dir, rel)
|
local cand0 = me.join(base_dir, rel)
|
||||||
if me.file_exists(cand0) { return cand0 }
|
if me.file_exists(cand0) { return cand0 }
|
||||||
local paths = me.default_using_paths()
|
local paths = me.default_using_paths()
|
||||||
|
local paths_len = 4
|
||||||
local i = 0
|
local i = 0
|
||||||
loop(i < paths.length()) {
|
loop(i < paths_len) {
|
||||||
local cand = me.join(paths.get(i), rel)
|
local cand = me.join(paths.get(i), rel)
|
||||||
if me.file_exists(cand) { return cand }
|
if me.file_exists(cand) { return cand }
|
||||||
i = i + 1
|
i = i + 1
|
||||||
@ -172,28 +200,34 @@ static box Main {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
local base = me.dirname(path)
|
local base = me.dirname(path)
|
||||||
local incs = me.scan_includes(src)
|
local incp = me.scan_includes(src)
|
||||||
local uses = me.scan_usings(src)
|
local incs = incp.get("arr")
|
||||||
local mods = me.scan_modules(src)
|
local incs_len = incp.get("len")
|
||||||
|
local usp = me.scan_usings(src)
|
||||||
|
local uses = usp.get("arr")
|
||||||
|
local uses_len = usp.get("len")
|
||||||
|
local modp = me.scan_modules(src)
|
||||||
|
local mods = modp.get("arr")
|
||||||
|
local mods_len = modp.get("len")
|
||||||
out.set("includes", incs)
|
out.set("includes", incs)
|
||||||
out.set("uses", uses)
|
out.set("uses", uses)
|
||||||
out.set("modules", mods)
|
out.set("modules", mods)
|
||||||
local mod_map = new MapBox()
|
local mod_map = new MapBox()
|
||||||
local mi = 0
|
local mi = 0
|
||||||
loop(mi < mods.length()) {
|
loop(mi < mods_len) {
|
||||||
local mm = mods.get(mi)
|
local mm = mods.get(mi)
|
||||||
mod_map.set(mm.get("namespace"), mm.get("path"))
|
mod_map.set(mm.get("namespace"), mm.get("path"))
|
||||||
mi = mi + 1
|
mi = mi + 1
|
||||||
}
|
}
|
||||||
local children = new ArrayBox()
|
local children = new ArrayBox()
|
||||||
local i = 0
|
local i = 0
|
||||||
loop(i < incs.length()) {
|
loop(i < incs_len) {
|
||||||
local child_path = me.join(base, incs.get(i))
|
local child_path = me.join(base, incs.get(i))
|
||||||
children.push(me.node_for(child_path, visited))
|
children.push(me.node_for(child_path, visited))
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
i = 0
|
i = 0
|
||||||
loop(i < uses.length()) {
|
loop(i < uses_len) {
|
||||||
local u = uses.get(i)
|
local u = uses.get(i)
|
||||||
if u.get("kind") == "path" {
|
if u.get("kind") == "path" {
|
||||||
local p = me.join(base, u.get("target"))
|
local p = me.join(base, u.get("target"))
|
||||||
|
|||||||
39
docs/selfhost/dep_tree_min_string.md
Normal file
39
docs/selfhost/dep_tree_min_string.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Include-only Dependency Tree (Phase 0)
|
||||||
|
|
||||||
|
Goal
|
||||||
|
- Build a dependency tree using only Ny (no Array/Map), scanning source text for `include "..."`, and output stable JSON.
|
||||||
|
|
||||||
|
Scope (Phase 0)
|
||||||
|
- Only `include` is supported. `using/module/import` are out of scope.
|
||||||
|
- Runner bridge: `NYASH_DEPS_JSON=<path>` is read and logged only (no behavior change).
|
||||||
|
|
||||||
|
Tool
|
||||||
|
- `apps/selfhost/tools/dep_tree_min_string.nyash`
|
||||||
|
- Recursively reads source files, scans for `include "path"` outside of strings and comments.
|
||||||
|
- Comments: `//` and `#` (line comments) are ignored.
|
||||||
|
- Strings: `"..."` with `\"` escapes are honored.
|
||||||
|
- Cycles: detected via a simple stack string; when detected, the child appears as a leaf node with empty `includes`/`children`.
|
||||||
|
|
||||||
|
Output format
|
||||||
|
```
|
||||||
|
{ "version": 1,
|
||||||
|
"root_path": "<entry>",
|
||||||
|
"tree": {
|
||||||
|
"path": "<file>", "includes": ["..."], "children": [ <node> ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Acceptance criteria
|
||||||
|
- Running `make dep-tree` produces `tmp/deps.json` whose first non-empty line starts with `{` and conforms to the format above.
|
||||||
|
- The scanner does not pick includes inside strings or comments.
|
||||||
|
- Cycles do not crash or loop; the repeated node is represented as a leaf.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
- Root: `apps/selfhost/smokes/dep_smoke_root.nyash` (includes `dep_smoke_child.nyash`)
|
||||||
|
- Cycle: `apps/selfhost/smokes/dep_smoke_cycle_a.nyash` ↔ `dep_smoke_cycle_b.nyash`
|
||||||
|
|
||||||
|
Validation (examples)
|
||||||
|
- `echo apps/selfhost/smokes/dep_smoke_root.nyash | ./target/release/nyash --backend vm apps/selfhost/tools/dep_tree_min_string.nyash`
|
||||||
|
- `make dep-tree`
|
||||||
|
|
||||||
@ -412,7 +412,7 @@ ny_plugins = [
|
|||||||
[tasks]
|
[tasks]
|
||||||
|
|
||||||
# self-host: dependency tree (Ny-only)
|
# self-host: dependency tree (Ny-only)
|
||||||
dep_tree = "NYASH_USE_PLUGIN_BUILTINS=1 NYASH_DISABLE_PLUGINS=0 {root}/target/release/nyash --backend vm {root}/apps/selfhost/tools/dep_tree_simple.nyash -- {root}/apps/selfhost/ny-parser-nyash/main.nyash > {root}/tmp/deps.json"
|
dep_tree = "NYASH_USE_PLUGIN_BUILTINS=1 NYASH_DISABLE_PLUGINS=0 {root}/target/release/nyash --backend vm {root}/apps/selfhost/tools/dep_tree_min_string.nyash | sed -n '/^{/,$p' > {root}/tmp/deps.json"
|
||||||
# LLVMビルド(nyash本体)
|
# LLVMビルド(nyash本体)
|
||||||
build_llvm = "LLVM_SYS_180_PREFIX=$(llvm-config-18 --prefix) cargo build --release --features llvm"
|
build_llvm = "LLVM_SYS_180_PREFIX=$(llvm-config-18 --prefix) cargo build --release --features llvm"
|
||||||
|
|
||||||
|
|||||||
12
src/ast.rs
12
src/ast.rs
@ -443,6 +443,12 @@ pub enum ASTNode {
|
|||||||
namespace_name: String,
|
namespace_name: String,
|
||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
|
/// import文: import "path" (as Alias)?
|
||||||
|
ImportStatement {
|
||||||
|
path: String,
|
||||||
|
alias: Option<String>,
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
|
||||||
/// nowait文: nowait variable = expression
|
/// nowait文: nowait variable = expression
|
||||||
Nowait {
|
Nowait {
|
||||||
@ -673,6 +679,7 @@ impl ASTNode {
|
|||||||
ASTNode::Break { .. } => "Break",
|
ASTNode::Break { .. } => "Break",
|
||||||
ASTNode::Continue { .. } => "Continue",
|
ASTNode::Continue { .. } => "Continue",
|
||||||
ASTNode::UsingStatement { .. } => "UsingStatement",
|
ASTNode::UsingStatement { .. } => "UsingStatement",
|
||||||
|
ASTNode::ImportStatement { .. } => "ImportStatement",
|
||||||
ASTNode::BoxDeclaration { .. } => "BoxDeclaration",
|
ASTNode::BoxDeclaration { .. } => "BoxDeclaration",
|
||||||
ASTNode::FunctionDeclaration { .. } => "FunctionDeclaration",
|
ASTNode::FunctionDeclaration { .. } => "FunctionDeclaration",
|
||||||
ASTNode::GlobalVar { .. } => "GlobalVar",
|
ASTNode::GlobalVar { .. } => "GlobalVar",
|
||||||
@ -742,6 +749,7 @@ impl ASTNode {
|
|||||||
ASTNode::Break { .. } => ASTNodeType::Statement,
|
ASTNode::Break { .. } => ASTNodeType::Statement,
|
||||||
ASTNode::Continue { .. } => ASTNodeType::Statement,
|
ASTNode::Continue { .. } => ASTNodeType::Statement,
|
||||||
ASTNode::UsingStatement { .. } => ASTNodeType::Statement,
|
ASTNode::UsingStatement { .. } => ASTNodeType::Statement,
|
||||||
|
ASTNode::ImportStatement { .. } => ASTNodeType::Statement,
|
||||||
ASTNode::GlobalVar { .. } => ASTNodeType::Statement,
|
ASTNode::GlobalVar { .. } => ASTNodeType::Statement,
|
||||||
ASTNode::Include { .. } => ASTNodeType::Statement,
|
ASTNode::Include { .. } => ASTNodeType::Statement,
|
||||||
ASTNode::Local { .. } => ASTNodeType::Statement,
|
ASTNode::Local { .. } => ASTNodeType::Statement,
|
||||||
@ -794,6 +802,9 @@ impl ASTNode {
|
|||||||
ASTNode::UsingStatement { namespace_name, .. } => {
|
ASTNode::UsingStatement { namespace_name, .. } => {
|
||||||
format!("UsingStatement({})", namespace_name)
|
format!("UsingStatement({})", namespace_name)
|
||||||
}
|
}
|
||||||
|
ASTNode::ImportStatement { path, alias, .. } => {
|
||||||
|
if let Some(a) = alias { format!("ImportStatement({}, as {})", path, a) } else { format!("ImportStatement({})", path) }
|
||||||
|
}
|
||||||
ASTNode::BoxDeclaration { name, fields, methods, constructors, is_interface, extends, implements, .. } => {
|
ASTNode::BoxDeclaration { name, fields, methods, constructors, is_interface, extends, implements, .. } => {
|
||||||
let mut desc = if *is_interface {
|
let mut desc = if *is_interface {
|
||||||
format!("InterfaceBox({}, {} methods", name, methods.len())
|
format!("InterfaceBox({}, {} methods", name, methods.len())
|
||||||
@ -907,6 +918,7 @@ impl ASTNode {
|
|||||||
ASTNode::Break { span, .. } => *span,
|
ASTNode::Break { span, .. } => *span,
|
||||||
ASTNode::Continue { span, .. } => *span,
|
ASTNode::Continue { span, .. } => *span,
|
||||||
ASTNode::UsingStatement { span, .. } => *span,
|
ASTNode::UsingStatement { span, .. } => *span,
|
||||||
|
ASTNode::ImportStatement { span, .. } => *span,
|
||||||
ASTNode::Nowait { span, .. } => *span,
|
ASTNode::Nowait { span, .. } => *span,
|
||||||
ASTNode::Arrow { span, .. } => *span,
|
ASTNode::Arrow { span, .. } => *span,
|
||||||
ASTNode::TryCatch { span, .. } => *span,
|
ASTNode::TryCatch { span, .. } => *span,
|
||||||
|
|||||||
@ -50,6 +50,7 @@ pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[
|
|||||||
"print",
|
"print",
|
||||||
"nowait",
|
"nowait",
|
||||||
"include",
|
"include",
|
||||||
|
"import",
|
||||||
"local",
|
"local",
|
||||||
"outbox",
|
"outbox",
|
||||||
"try",
|
"try",
|
||||||
|
|||||||
@ -100,6 +100,14 @@ impl NyashInterpreter {
|
|||||||
self.execute_using_statement(namespace_name)
|
self.execute_using_statement(namespace_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ASTNode::ImportStatement { path, alias, .. } => {
|
||||||
|
// Stage-0 import: no-op (record/log only)
|
||||||
|
if std::env::var("NYASH_IMPORT_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
if let Some(a) = alias { eprintln!("[import] {} as {}", path, a); } else { eprintln!("[import] {}", path); }
|
||||||
|
}
|
||||||
|
Ok(Box::new(VoidBox::new()))
|
||||||
|
}
|
||||||
|
|
||||||
ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, is_static, static_init, .. } => {
|
ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, is_static, static_init, .. } => {
|
||||||
if *is_static {
|
if *is_static {
|
||||||
// 🔥 Static Box宣言の処理
|
// 🔥 Static Box宣言の処理
|
||||||
@ -613,19 +621,25 @@ impl NyashInterpreter {
|
|||||||
pub(super) fn execute_using_statement(&mut self, namespace_name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
pub(super) fn execute_using_statement(&mut self, namespace_name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||||
idebug!("🌟 DEBUG: execute_using_statement called with namespace: {}", namespace_name);
|
idebug!("🌟 DEBUG: execute_using_statement called with namespace: {}", namespace_name);
|
||||||
|
|
||||||
// Phase 0: nyashstdのみサポート
|
// First, handle the builtin stdlib namespace
|
||||||
if namespace_name != "nyashstd" {
|
if namespace_name == "nyashstd" {
|
||||||
return Err(RuntimeError::InvalidOperation {
|
|
||||||
message: format!("Unsupported namespace '{}'. Only 'nyashstd' is supported in Phase 0.", namespace_name)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 標準ライブラリを初期化(存在しない場合)
|
|
||||||
idebug!("🌟 DEBUG: About to call ensure_stdlib_initialized");
|
idebug!("🌟 DEBUG: About to call ensure_stdlib_initialized");
|
||||||
self.ensure_stdlib_initialized()?;
|
self.ensure_stdlib_initialized()?;
|
||||||
idebug!("🌟 DEBUG: ensure_stdlib_initialized completed");
|
idebug!("🌟 DEBUG: ensure_stdlib_initialized completed");
|
||||||
|
return Ok(Box::new(VoidBox::new()));
|
||||||
// using nyashstdの場合は特に何もしない(既に標準ライブラリが初期化されている)
|
}
|
||||||
|
// Otherwise, consult the modules registry (resolved by runner/CLI/header)
|
||||||
|
if crate::runtime::modules_registry::get(namespace_name).is_some() {
|
||||||
|
// Resolved via registry; no further action at runtime stage-0
|
||||||
|
return Ok(Box::new(VoidBox::new()));
|
||||||
|
}
|
||||||
|
let strict = std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1");
|
||||||
|
if strict {
|
||||||
|
return Err(RuntimeError::InvalidOperation { message: format!("Unresolved namespace '{}' (strict)", namespace_name) });
|
||||||
|
}
|
||||||
|
if crate::interpreter::utils::debug_on() {
|
||||||
|
eprintln!("[using] unresolved '{}' (non-strict, continuing)", namespace_name);
|
||||||
|
}
|
||||||
Ok(Box::new(VoidBox::new()))
|
Ok(Box::new(VoidBox::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -407,9 +407,13 @@ impl NyashParser {
|
|||||||
let field_or_method = field_or_method.clone();
|
let field_or_method = field_or_method.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
|
|
||||||
// 可視性ブロック: public { ... } / private { ... }
|
// 可視性:
|
||||||
|
// - public { ... } / private { ... } ブロック
|
||||||
|
// - public name: Type 単行(P0: 型はパースのみ、意味付けは後段)
|
||||||
if field_or_method == "public" || field_or_method == "private" {
|
if field_or_method == "public" || field_or_method == "private" {
|
||||||
self.consume(TokenType::LBRACE)?;
|
if self.match_token(&TokenType::LBRACE) {
|
||||||
|
// ブロック形式
|
||||||
|
self.advance(); // consume '{'
|
||||||
self.skip_newlines();
|
self.skip_newlines();
|
||||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||||
if let TokenType::IDENTIFIER(fname) = &self.current_token().token_type {
|
if let TokenType::IDENTIFIER(fname) = &self.current_token().token_type {
|
||||||
@ -434,6 +438,27 @@ impl NyashParser {
|
|||||||
self.consume(TokenType::RBRACE)?;
|
self.consume(TokenType::RBRACE)?;
|
||||||
self.skip_newlines();
|
self.skip_newlines();
|
||||||
continue;
|
continue;
|
||||||
|
} else if self.match_token(&TokenType::IDENTIFIER) {
|
||||||
|
// 単行形式: public name[: Type]
|
||||||
|
let fname = if let TokenType::IDENTIFIER(n) = &self.current_token().token_type { n.clone() } else { unreachable!() };
|
||||||
|
self.advance();
|
||||||
|
if self.match_token(&TokenType::COLON) {
|
||||||
|
self.advance(); // consume ':'
|
||||||
|
// 型名(識別子)を受理して破棄(P0)
|
||||||
|
if let TokenType::IDENTIFIER(_ty) = &self.current_token().token_type {
|
||||||
|
self.advance();
|
||||||
|
} else {
|
||||||
|
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "type name".to_string(), line: self.current_token().line });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if field_or_method == "public" { public_fields.push(fname.clone()); } else { private_fields.push(fname.clone()); }
|
||||||
|
fields.push(fname);
|
||||||
|
self.skip_newlines();
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// public/private の後に '{' でも識別子でもない
|
||||||
|
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "'{' or field name".to_string(), line: self.current_token().line });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// メソッドかフィールドかを判定
|
// メソッドかフィールドかを判定
|
||||||
|
|||||||
@ -19,6 +19,9 @@ impl NyashParser {
|
|||||||
TokenType::BOX => {
|
TokenType::BOX => {
|
||||||
self.parse_box_declaration()
|
self.parse_box_declaration()
|
||||||
},
|
},
|
||||||
|
TokenType::IMPORT => {
|
||||||
|
self.parse_import()
|
||||||
|
},
|
||||||
TokenType::INTERFACE => {
|
TokenType::INTERFACE => {
|
||||||
self.parse_interface_box_declaration()
|
self.parse_interface_box_declaration()
|
||||||
},
|
},
|
||||||
@ -118,6 +121,32 @@ impl NyashParser {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// import文をパース: import "path" (as Alias)?
|
||||||
|
pub(super) fn parse_import(&mut self) -> Result<ASTNode, ParseError> {
|
||||||
|
self.advance(); // consume 'import'
|
||||||
|
let path = if let TokenType::STRING(s) = &self.current_token().token_type {
|
||||||
|
let v = s.clone();
|
||||||
|
self.advance();
|
||||||
|
v
|
||||||
|
} else {
|
||||||
|
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "string literal".to_string(), line: self.current_token().line });
|
||||||
|
};
|
||||||
|
// Optional: 'as' Alias (treat 'as' as identifier literal)
|
||||||
|
let mut alias: Option<String> = None;
|
||||||
|
if let TokenType::IDENTIFIER(w) = &self.current_token().token_type {
|
||||||
|
if w == "as" {
|
||||||
|
self.advance();
|
||||||
|
if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
|
||||||
|
alias = Some(name.clone());
|
||||||
|
self.advance();
|
||||||
|
} else {
|
||||||
|
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "alias name".to_string(), line: self.current_token().line });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ASTNode::ImportStatement { path, alias, span: Span::unknown() })
|
||||||
|
}
|
||||||
|
|
||||||
/// if文をパース: if (condition) { body } else if ... else { body }
|
/// if文をパース: if (condition) { body } else if ... else { body }
|
||||||
pub(super) fn parse_if(&mut self) -> Result<ASTNode, ParseError> {
|
pub(super) fn parse_if(&mut self) -> Result<ASTNode, ParseError> {
|
||||||
self.advance(); // consume 'if'
|
self.advance(); // consume 'if'
|
||||||
|
|||||||
@ -36,6 +36,38 @@ use nyash_rust::runtime;
|
|||||||
use nyash_rust::runner_plugin_init;
|
use nyash_rust::runner_plugin_init;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Resolve a using target according to priority: modules > relative > using-paths
|
||||||
|
/// Returns Ok(resolved_path_or_token). On strict mode, ambiguous matches cause error.
|
||||||
|
fn resolve_using_target(
|
||||||
|
tgt: &str,
|
||||||
|
is_path: bool,
|
||||||
|
modules: &[(String, String)],
|
||||||
|
using_paths: &[String],
|
||||||
|
context_dir: Option<&std::path::Path>,
|
||||||
|
strict: bool,
|
||||||
|
verbose: bool,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
if is_path { return Ok(tgt.to_string()); }
|
||||||
|
// 1) modules mapping
|
||||||
|
if let Some((_, p)) = modules.iter().find(|(n, _)| n == tgt) { return Ok(p.clone()); }
|
||||||
|
// 2) build candidate list: relative then using-paths
|
||||||
|
let rel = tgt.replace('.', "/") + ".nyash";
|
||||||
|
let mut cand: Vec<String> = Vec::new();
|
||||||
|
if let Some(dir) = context_dir { let c = dir.join(&rel); if c.exists() { cand.push(c.to_string_lossy().to_string()); } }
|
||||||
|
for base in using_paths {
|
||||||
|
let c = std::path::Path::new(base).join(&rel);
|
||||||
|
if c.exists() { cand.push(c.to_string_lossy().to_string()); }
|
||||||
|
}
|
||||||
|
if cand.is_empty() {
|
||||||
|
if verbose { eprintln!("[using] unresolved '{}' (searched: rel+paths)", tgt); }
|
||||||
|
return Ok(tgt.to_string());
|
||||||
|
}
|
||||||
|
if cand.len() > 1 && strict {
|
||||||
|
return Err(format!("ambiguous using '{}': {}", tgt, cand.join(", ")));
|
||||||
|
}
|
||||||
|
Ok(cand.remove(0))
|
||||||
|
}
|
||||||
|
|
||||||
/// Main execution coordinator
|
/// Main execution coordinator
|
||||||
pub struct NyashRunner {
|
pub struct NyashRunner {
|
||||||
config: CliConfig,
|
config: CliConfig,
|
||||||
@ -83,6 +115,66 @@ impl NyashRunner {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// CLI using/module overrides (MVP): apply early so JSON pipeline can observe them
|
||||||
|
if self.config.using.is_some() || self.config.using_path.is_some() || self.config.modules.is_some()
|
||||||
|
|| std::env::var("NYASH_USING_PATH").is_ok() || std::env::var("NYASH_MODULES").is_ok() {
|
||||||
|
let mut using_paths: Vec<String> = Vec::new();
|
||||||
|
if let Some(p) = self.config.using_path.clone() { for s in p.split(':') { let s=s.trim(); if !s.is_empty() { using_paths.push(s.to_string()); } } }
|
||||||
|
if let Ok(p) = std::env::var("NYASH_USING_PATH") { for s in p.split(':') { let s=s.trim(); if !s.is_empty() { using_paths.push(s.to_string()); } } }
|
||||||
|
if using_paths.is_empty() { using_paths.extend(["apps","lib","."].into_iter().map(|s| s.to_string())); }
|
||||||
|
|
||||||
|
// modules mapping
|
||||||
|
let mut modules: Vec<(String,String)> = Vec::new();
|
||||||
|
if let Some(m) = self.config.modules.clone() { for ent in m.split(',') { if let Some((k,v)) = ent.split_once('=') { let k=k.trim(); let v=v.trim(); if !k.is_empty() && !v.is_empty() { modules.push((k.to_string(), v.to_string())); } } } }
|
||||||
|
if let Ok(ms) = std::env::var("NYASH_MODULES") { for ent in ms.split(',') { if let Some((k,v)) = ent.split_once('=') { let k=k.trim(); let v=v.trim(); if !k.is_empty() && !v.is_empty() { modules.push((k.to_string(), v.to_string())); } } } }
|
||||||
|
for (ns, path) in &modules { let sb = crate::box_trait::StringBox::new(path.clone()); crate::runtime::modules_registry::set(ns.clone(), Box::new(sb)); }
|
||||||
|
|
||||||
|
// using specs
|
||||||
|
let mut pending_using: Vec<(String, Option<String>, bool)> = Vec::new(); // (target, alias, is_path)
|
||||||
|
if let Some(u) = self.config.using.clone() {
|
||||||
|
for ent in u.split(',') {
|
||||||
|
let s = ent.trim().trim_end_matches(';').trim(); if s.is_empty() { continue; }
|
||||||
|
let (tgt, 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) };
|
||||||
|
let is_path = tgt.starts_with('"') || tgt.starts_with("./") || tgt.starts_with('/') || tgt.ends_with(".nyash");
|
||||||
|
pending_using.push((tgt.trim_matches('"').to_string(), alias, is_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Resolve using (priority: modules > relative(file) > using-paths; ambiguous=error if strict)
|
||||||
|
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 = self.config.file.as_deref().and_then(|f| std::path::Path::new(f).parent());
|
||||||
|
for (tgt, alias, is_path) in pending_using.into_iter() {
|
||||||
|
if is_path && !std::path::Path::new(&tgt).exists() {
|
||||||
|
if strict { eprintln!("❌ using: path not found: {}", tgt); std::process::exit(1); }
|
||||||
|
if verbose { eprintln!("[using] path not found (continuing): {}", tgt); }
|
||||||
|
}
|
||||||
|
let value = match resolve_using_target(&tgt, is_path, &modules, &using_paths, ctx, strict, verbose) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => { eprintln!("❌ using: {}", e); std::process::exit(1); }
|
||||||
|
};
|
||||||
|
let sb = crate::box_trait::StringBox::new(value.clone());
|
||||||
|
crate::runtime::modules_registry::set(tgt.clone(), Box::new(sb));
|
||||||
|
if let Some(a) = alias { let sb2 = crate::box_trait::StringBox::new(value); crate::runtime::modules_registry::set(a, Box::new(sb2)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Stage-1: Optional dependency tree bridge (log-only)
|
||||||
|
if let Ok(dep_path) = std::env::var("NYASH_DEPS_JSON") {
|
||||||
|
match std::fs::read_to_string(&dep_path) {
|
||||||
|
Ok(s) => {
|
||||||
|
let bytes = s.as_bytes().len();
|
||||||
|
// Try to extract quick hints without failing
|
||||||
|
let mut root_info = String::new();
|
||||||
|
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&s) {
|
||||||
|
if let Some(r) = v.get("root_path").and_then(|x| x.as_str()) { root_info = format!(" root='{}'", r); }
|
||||||
|
}
|
||||||
|
eprintln!("[deps] loaded {} bytes from{} {}", bytes, if root_info.is_empty(){""} else {":"}, root_info);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("[deps] read error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Phase-15: JSON IR v0 bridge (stdin/file)
|
// Phase-15: JSON IR v0 bridge (stdin/file)
|
||||||
if self.config.ny_parser_pipe || self.config.json_file.is_some() {
|
if self.config.ny_parser_pipe || self.config.json_file.is_some() {
|
||||||
let json = if let Some(path) = &self.config.json_file {
|
let json = if let Some(path) = &self.config.json_file {
|
||||||
@ -160,6 +252,41 @@ impl NyashRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Env overrides for using rules
|
||||||
|
if let Ok(paths) = std::env::var("NYASH_USING_PATH") {
|
||||||
|
for p in paths.split(':') { let p = p.trim(); if !p.is_empty() { using_paths.push(p.to_string()); } }
|
||||||
|
}
|
||||||
|
if let Ok(mods) = std::env::var("NYASH_MODULES") {
|
||||||
|
for ent in mods.split(',') {
|
||||||
|
if let Some((k,v)) = ent.split_once('=') {
|
||||||
|
let k = k.trim(); let v = v.trim();
|
||||||
|
if !k.is_empty() && !v.is_empty() { pending_modules.push((k.to_string(), v.to_string())); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply pending modules to registry as StringBox (path or ns token)
|
||||||
|
for (ns, path) in pending_modules.iter() {
|
||||||
|
let sb = nyash_rust::box_trait::StringBox::new(path.clone());
|
||||||
|
nyash_rust::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
||||||
|
}
|
||||||
|
// Resolve pending using with clear precedence and ambiguity handling
|
||||||
|
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 = std::path::Path::new(filename).parent();
|
||||||
|
for (ns, alias) in pending_using.iter() {
|
||||||
|
let value = match resolve_using_target(ns, false, &pending_modules, &using_paths, ctx, strict, verbose) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => { eprintln!("❌ using: {}", e); std::process::exit(1); }
|
||||||
|
};
|
||||||
|
let sb = nyash_rust::box_trait::StringBox::new(value.clone());
|
||||||
|
nyash_rust::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
||||||
|
if let Some(a) = alias {
|
||||||
|
let sb2 = nyash_rust::box_trait::StringBox::new(value);
|
||||||
|
nyash_rust::runtime::modules_registry::set(a.clone(), Box::new(sb2));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -248,6 +248,39 @@ impl NyashRunner {
|
|||||||
Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); }
|
Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Stage-0: import loader (top-level only) — resolve path and register in modules registry
|
||||||
|
if let nyash_rust::ast::ASTNode::Program { statements, .. } = &ast {
|
||||||
|
for st in statements {
|
||||||
|
if let nyash_rust::ast::ASTNode::ImportStatement { path, alias, .. } = st {
|
||||||
|
// resolve path relative to current file if not absolute
|
||||||
|
let mut p = std::path::PathBuf::from(path);
|
||||||
|
if p.is_relative() {
|
||||||
|
if let Some(dir) = std::path::Path::new(filename).parent() {
|
||||||
|
p = dir.join(&p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let exists = p.exists();
|
||||||
|
if !exists {
|
||||||
|
if std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("❌ import: path not found: {} (from {})", p.display(), filename);
|
||||||
|
process::exit(1);
|
||||||
|
} else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") || std::env::var("NYASH_IMPORT_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[import] path not found (continuing): {}", p.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let key = if let Some(a) = alias { a.clone() } else {
|
||||||
|
std::path::Path::new(path)
|
||||||
|
.file_stem().and_then(|s| s.to_str())
|
||||||
|
.unwrap_or(path)
|
||||||
|
.to_string()
|
||||||
|
};
|
||||||
|
let value = if exists { p.to_string_lossy().to_string() } else { path.clone() };
|
||||||
|
let sb = nyash_rust::box_trait::StringBox::new(value);
|
||||||
|
nyash_rust::runtime::modules_registry::set(key, Box::new(sb));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") && !quiet_pipe {
|
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") && !quiet_pipe {
|
||||||
println!("✅ Parse successful!");
|
println!("✅ Parse successful!");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ pub fn init_bid_plugins() {
|
|||||||
|
|
||||||
if let Ok(()) = init_global_plugin_host("nyash.toml") {
|
if let Ok(()) = init_global_plugin_host("nyash.toml") {
|
||||||
if plugin_debug || cli_verbose {
|
if plugin_debug || cli_verbose {
|
||||||
println!("🔌 plugin host initialized from nyash.toml");
|
eprintln!("🔌 plugin host initialized from nyash.toml");
|
||||||
// Show which plugin loader backend compiled in (enabled/stub)
|
// Show which plugin loader backend compiled in (enabled/stub)
|
||||||
println!("[plugin-loader] backend={}", crate::runtime::plugin_loader_v2::backend_kind());
|
println!("[plugin-loader] backend={}", crate::runtime::plugin_loader_v2::backend_kind());
|
||||||
}
|
}
|
||||||
@ -29,7 +29,7 @@ pub fn init_bid_plugins() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if plugin_debug || cli_verbose {
|
if plugin_debug || cli_verbose {
|
||||||
println!("✅ plugin host fully configured");
|
eprintln!("✅ plugin host fully configured");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if plugin_debug || cli_verbose {
|
} else if plugin_debug || cli_verbose {
|
||||||
|
|||||||
@ -56,6 +56,7 @@ pub enum TokenType {
|
|||||||
FROM, // from (親メソッド呼び出し)
|
FROM, // from (親メソッド呼び出し)
|
||||||
WEAK, // weak (弱参照修飾子)
|
WEAK, // weak (弱参照修飾子)
|
||||||
USING, // using (名前空間インポート)
|
USING, // using (名前空間インポート)
|
||||||
|
IMPORT, // import (Phase 12.7)
|
||||||
|
|
||||||
// 演算子 (長いものから先に定義)
|
// 演算子 (長いものから先に定義)
|
||||||
SHIFT_LEFT, // << (bitwise shift-left)
|
SHIFT_LEFT, // << (bitwise shift-left)
|
||||||
@ -145,6 +146,10 @@ pub struct NyashTokenizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl NyashTokenizer {
|
impl NyashTokenizer {
|
||||||
|
#[inline]
|
||||||
|
fn strict_12_7() -> bool {
|
||||||
|
std::env::var("NYASH_STRICT_12_7").ok().as_deref() == Some("1")
|
||||||
|
}
|
||||||
/// 新しいトークナイザーを作成
|
/// 新しいトークナイザーを作成
|
||||||
pub fn new(input: impl Into<String>) -> Self {
|
pub fn new(input: impl Into<String>) -> Self {
|
||||||
let input_string = input.into();
|
let input_string = input.into();
|
||||||
@ -248,7 +253,7 @@ impl NyashTokenizer {
|
|||||||
self.skip_whitespace(); // コメント後の空白もスキップ
|
self.skip_whitespace(); // コメント後の空白もスキップ
|
||||||
return self.tokenize_next();
|
return self.tokenize_next();
|
||||||
}
|
}
|
||||||
Some('>') if self.peek_char() == Some('>') => {
|
Some('>') if self.peek_char() == Some('>') && !Self::strict_12_7() => {
|
||||||
self.advance();
|
self.advance();
|
||||||
self.advance();
|
self.advance();
|
||||||
Ok(Token::new(TokenType::SHIFT_RIGHT, start_line, start_column))
|
Ok(Token::new(TokenType::SHIFT_RIGHT, start_line, start_column))
|
||||||
@ -483,7 +488,7 @@ impl NyashTokenizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// キーワードチェック
|
// キーワードチェック
|
||||||
let tok = match identifier.as_str() {
|
let mut tok = match identifier.as_str() {
|
||||||
"box" => TokenType::BOX,
|
"box" => TokenType::BOX,
|
||||||
"global" => TokenType::GLOBAL,
|
"global" => TokenType::GLOBAL,
|
||||||
"singleton" => TokenType::SINGLETON,
|
"singleton" => TokenType::SINGLETON,
|
||||||
@ -497,8 +502,6 @@ impl NyashTokenizer {
|
|||||||
"return" => TokenType::RETURN,
|
"return" => TokenType::RETURN,
|
||||||
"function" => TokenType::FUNCTION,
|
"function" => TokenType::FUNCTION,
|
||||||
"fn" => TokenType::FN,
|
"fn" => TokenType::FN,
|
||||||
// Alias support: `fn` as shorthand for function
|
|
||||||
"fn" => TokenType::FUNCTION,
|
|
||||||
"print" => TokenType::PRINT,
|
"print" => TokenType::PRINT,
|
||||||
"this" => TokenType::THIS,
|
"this" => TokenType::THIS,
|
||||||
"me" => TokenType::ME,
|
"me" => TokenType::ME,
|
||||||
@ -509,6 +512,7 @@ impl NyashTokenizer {
|
|||||||
"await" => TokenType::AWAIT,
|
"await" => TokenType::AWAIT,
|
||||||
"interface" => TokenType::INTERFACE,
|
"interface" => TokenType::INTERFACE,
|
||||||
"include" => TokenType::INCLUDE,
|
"include" => TokenType::INCLUDE,
|
||||||
|
"import" => TokenType::IMPORT,
|
||||||
"try" => TokenType::TRY,
|
"try" => TokenType::TRY,
|
||||||
"catch" => TokenType::CATCH,
|
"catch" => TokenType::CATCH,
|
||||||
"finally" => TokenType::FINALLY,
|
"finally" => TokenType::FINALLY,
|
||||||
@ -529,6 +533,23 @@ impl NyashTokenizer {
|
|||||||
_ => TokenType::IDENTIFIER(identifier.clone()),
|
_ => TokenType::IDENTIFIER(identifier.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 12.7 Strict mode: fallback extended keywords to IDENTIFIER
|
||||||
|
if Self::strict_12_7() {
|
||||||
|
let is_extended = matches!(tok,
|
||||||
|
TokenType::INTERFACE
|
||||||
|
| TokenType::USING
|
||||||
|
| TokenType::INCLUDE
|
||||||
|
| TokenType::OUTBOX
|
||||||
|
| TokenType::NOWAIT
|
||||||
|
| TokenType::OVERRIDE
|
||||||
|
| TokenType::WEAK
|
||||||
|
| TokenType::PACK
|
||||||
|
);
|
||||||
|
if is_extended {
|
||||||
|
tok = TokenType::IDENTIFIER(identifier.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 統一文法エンジンとの差分チェック(動作は変更しない)
|
// 統一文法エンジンとの差分チェック(動作は変更しない)
|
||||||
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
|
||||||
// 安全に参照(初期導入のため、存在しない場合は無視)
|
// 安全に参照(初期導入のため、存在しない場合は無視)
|
||||||
|
|||||||
Reference in New Issue
Block a user