Analyzer安定化完了: NYASH_DISABLE_PLUGINS=1復元 + plugin無効化根治
## 修正内容 1. **hako_check.sh/run_tests.sh**: NYASH_DISABLE_PLUGINS=1 + NYASH_BOX_FACTORY_POLICY=builtin_first追加 2. **src/box_factory/plugin.rs**: NYASH_DISABLE_PLUGINS=1チェック追加 3. **src/box_factory/mod.rs**: plugin shortcut pathでNYASH_DISABLE_PLUGINS尊重 4. **tools/hako_check/render/graphviz.hako**: smart quotes修正(parse error解消) ## 根本原因 - NYASH_USE_PLUGIN_BUILTINS=1が自動設定され、ArrayBox/MapBoxがplugin経由で生成を試行 - bid/registry.rsで"Plugin loading temporarily disabled"の状態でも試行されエラー - mod.rs:272のshortcut pathがNYASH_DISABLE_PLUGINSを無視していた ## テスト結果 - 10/11 PASS(HC011,13-18,21-22,31) - HC012: 既存issue(JSON安定化未完) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -20,6 +20,11 @@ Update (today)
|
||||
- String API: size() を length() のエイリアスとして VM で受理
|
||||
- Analyzer CLI: --format/--debug/--source-file を順不同で処理
|
||||
- Analyzer IR: AST 空時の methods をテキスト走査でフォールバック
|
||||
- HC021/HC031: 実装完了。PHI 調査は一旦収束(AST path は既定OFF、`--force-ast` でオン)。
|
||||
- CLI: `--rules`/`--skip-rules` を追加し、ルール単体/組合せ検証を高速化。`--no-ast` 既定化。
|
||||
- Runtime: `NYASH_SCRIPT_ARGS_HEX_JSON` を導入(HEX経由で改行・特殊文字を安全搬送)。
|
||||
- File I/O: FileBox provider 設計(SSOT + 薄いラッパ + 選択ポリシー)を文書化(docs/development/runtime/FILEBOX_PROVIDER.md)。
|
||||
- ENV: `NYASH_FILEBOX_MODE=auto|core-ro|plugin-only` を追加(ENV_VARS.md)。Analyzer/CI は core‑ro 相当で運用。
|
||||
|
||||
Footing update (A→B→C, today) — 仕様と入口をまず固定
|
||||
- A. 仕様/インターフェース(hako_check 診断 I/F と出力規約)
|
||||
@ -41,14 +46,54 @@ Acceptance(Footing A→B→C)
|
||||
- B: `parser_core.hako` の AST に `span_line`/`arity` が入り、ダンプで確認可能(最小ケース2件)。
|
||||
- C: `analysis_consumer.hako` が未初期化 IR に対しても `push` で落ちず、AST 経路で HC011/HC012 が緑。
|
||||
|
||||
Status (HC rules)
|
||||
- 11/11 green: HC011/012/013/014/015/016/017/018/021/022/031(run_tests.sh)
|
||||
- CLI: `--rules`/`--skip-rules` で単体/組合せ検証を高速化、JSON_ONLY で純出力。
|
||||
|
||||
Remaining (21.4)
|
||||
1) Hako Parser MVP 実装(tokenizer/parser_core/ast_emit/cli)【A/B 完了、微修整残り】
|
||||
2) Analyzer AST 入力切替(analysis_consumer.hako)【C: ensure_array 完了、AST優先の薄化継続】
|
||||
1) Hako Parser MVP 実装(tokenizer/parser_core/ast_emit/cli)【微修整】
|
||||
2) Analyzer AST 入力の安定化(必要時のみ AST を使用)
|
||||
3) 代表ルール AST 化(HC001/002/003/010/011/020)
|
||||
4) `--format json-lsp` 出力【CLI整備済み、追加テスト】
|
||||
4) `--format json-lsp` 追加ケース(OK/NG/edge)
|
||||
5) テスト駆動(tests/<rule>/)を 3 ルール分用意
|
||||
6) 限定 `--fix`(HC002/003/500)
|
||||
7) DOT エッジ ON(calls→edges, cluster by box)
|
||||
8) FileBox provider 実装(リング0/1/選択ポリシー)と最小スモーク追加(core‑ro/auto/plugin-only)【COMPLETE】
|
||||
|
||||
Checklist — Ring1/Plugins polish(このフェーズで完了)
|
||||
- [ ] Env 統一(二重解消): `NYASH_FILEBOX_MODE`/`NYASH_DISABLE_PLUGINS` に一本化し、
|
||||
旧 `NYASH_USE_PLUGIN_BUILTINS` / `NYASH_PLUGIN_OVERRIDE_TYPES` は非推奨(互換ブリッジは維持)
|
||||
- [ ] 初期化順のSSOT: `register_builtin_filebox`(feature)→ dynamic登録(toml)→ provider選択→ FILEBOX_PROVIDER 設定(vm/vm_fallback 共通)
|
||||
- [ ] Provider 登録口の一本化: static/dynamic/builtin を同じ ProviderFactory 登録APIに集約(select は registry のみ)
|
||||
- [ ] FileBox 公開の一本化: `basic/file_box.rs` を再エクスポート/委譲化し、公開は BoxShim(委譲)に揃える(重複実装撤去)
|
||||
- [ ] Using/modules のSSOT確認: modules優先→相対ファイル推定→not found(警告; verboseで詳細)を維持
|
||||
- [ ] JSON_ONLY 監査: json-lsp時は stdout 純JSON・ログは stderr の規約を plugin_guard/loader を含む全経路で確認
|
||||
- [ ] AST/IR ゲート最終化: `_needs_ast`/`_needs_ir` の方針を docs と実装で一致させる(ASTは最小; 既定 no-ast)
|
||||
- [ ] Capability introspection: FILEBOX_PROVIDER の `caps(read/write)` を BoxShim から取得・Fail‑Fastの可否を明示
|
||||
- [ ] スモーク追加: core‑ro(open/read/close)/auto(フォールバック)/plugin-only(厳格)/analyzer(json-lsp純出力) の代表ケース
|
||||
- [ ] Docs 同期: FILEBOX_PROVIDER.md / ENV_VARS.md / hako_check README を最終状態に更新
|
||||
- [ ] フォールバック保証: プラグインのロード失敗だけでなく「create_box 失敗時」にも ring1/core‑ro へ自動フォールバック(auto モード)。plugin‑only では Fail‑Fast。
|
||||
- [ ] 失敗時スモーク: ArrayBox の plugin が存在するが creation に失敗するケースを再現し、ring1/core‑ro で生成できることを確認。
|
||||
|
||||
Completed — FileBox Provider Wiring(C: Feature と静的/動的の選択を1本化)
|
||||
- 目的: File I/O を SSOT 抽象(FileIo)+ 薄いラッパ(FileBoxShim)+ provider_registry で一本化し、静的/動的/コアRO の選択を ENV で制御する。
|
||||
- 完了内容:
|
||||
- provider_lock: `FILEBOX_PROVIDER` を追加し、グローバルに選択結果を保持
|
||||
- vm 起動時に `read_filebox_mode_from_env` → `select_file_provider` で provider を決定・登録
|
||||
- CoreRoFileIo を read‑only で実装(thread‑safe)
|
||||
- FileBox を provider 経由の委譲へ寄せ、basic/file_box.rs は deprecate 化
|
||||
- Cargo.toml に `builtin-filebox` feature を追加
|
||||
- Docs/ENV を同期(FILEBOX_PROVIDER.md / ENV_VARS.md)
|
||||
- 既知の非関連課題(別タスク扱い):
|
||||
- hako_check/cli.hako の parse error(line表示の不整合)。FileBox配線とは無関係。HC012 JSON安定化の一環で対応予定。
|
||||
|
||||
Next (handoff to Claude Code)
|
||||
- HC012 JSON 安定化(cli.hako)
|
||||
- json‑lsp時は stdout を純JSON、ログは stderr の規約徹底(fmt判定ガードの再確認)
|
||||
- parse error の発生箇所を修正(構文境界/コメント/Stage‑3トグル周り)
|
||||
- `NYASH_JSON_ONLY=1 ./tools/hako_check.sh --format json-lsp tools/hako_check/tests/HC012_dead_static_box` が純JSONを返すこと
|
||||
- Analyzer run_tests 全緑の維持(--rules/--skip-rules で個別検証可能)
|
||||
8) FileBox provider 実装(リング0/1/選択ポリシー)と最小スモーク追加(core‑ro/auto/plugin-only)
|
||||
|
||||
Roadmap (A→B→C) — 必ずこの順序で進める
|
||||
- A. HC011 をまず緑にする(AST 非依存の安全経路)【COMPLETE】
|
||||
@ -88,15 +133,16 @@ Next (21.2 — TBD)
|
||||
- dev は Adapter 登録や by‑name fallback を許容(トグル)、prod は Adapter 必須(Fail‑Fast)。
|
||||
|
||||
Next Steps (immediate)
|
||||
1) C: AST優先取り込みの最終化(text fallback を最小に)
|
||||
2) C: calls の arity 推定(実装済み)に基づく HC015 足場準備
|
||||
3) C: HC012 line 精度(span_line)適用(後回し可)
|
||||
4) `--format json-lsp` の代表テスト追加(OK/NG/edge)
|
||||
1) A(仕上げ): HC012 line 精度(span_line)/ README 追随 / CLI I/F最終確認
|
||||
2) B(Parser強化): tokenizer 文字列エスケープ/位置情報、parser_core 複数行・alias 抽出
|
||||
3) C(可視化): DOT 改善(box cluster / 孤立ノード)と quick スモーク追加
|
||||
|
||||
Context Compression Notes
|
||||
- A(診断I/F+抑制)は実装済み。文字列診断→型付き変換あり。
|
||||
- B(AST: span_line/is_static=bool)は実装済み。methods.arity は既に出力。
|
||||
- C は ensure_array と calls arity 推定を反映。残は AST優先の徹底と HC012 line 精度。
|
||||
- HC021/HC031 は完了。PHI は AST パス限定で再発可能性があるため、当面 `--no-ast` 既定(`--force-ast` で明示オン)。
|
||||
- ルール検証は `--rules`/`--skip-rules` で二分探索可能。
|
||||
|
||||
TODO (short-term, non-HC0)
|
||||
- AST-first intake: minimize text fallback; ensure analyzer never uses FileBox in tests.
|
||||
|
||||
@ -40,6 +40,20 @@ NYASH_DISABLE_PLUGINS = "1"
|
||||
- NYASH_AWAIT_MAX_MS: await の最大待機ミリ秒(既定 5000)
|
||||
- (今後)タスク/スケジューラ関連の変数は `runtime.*` 名で集約予定
|
||||
|
||||
## CLI Script Args(改行・特殊文字の安全輸送)
|
||||
- NYASH_SCRIPT_ARGS_JSON: `--` 以降のスクリプト引数(JSON配列)。標準経路。
|
||||
- NYASH_SCRIPT_ARGS_HEX_JSON: 上記のHEX版(各要素をUTF‑8→16進文字列化)。VMは HEX→JSON→ARGV の順で復元を試みる。
|
||||
- NYASH_ARGV: 互換目的のJSON配列(最終フォールバック)。
|
||||
|
||||
メモ: 改行・特殊文字を含む長文を `--source-file <path> <text>` で渡す場合も HEX 経路で安全に輸送される。
|
||||
|
||||
## FileBox Provider(コア/プラグイン切替)
|
||||
- NYASH_FILEBOX_MODE: `auto|core-ro|plugin-only`
|
||||
- auto(既定): プラグインがあれば PluginFileIo、無ければ CoreRoFileIo
|
||||
- core-ro: 常にコアの read‑only 実装を使用(Analyzer/CI 向け)
|
||||
- plugin-only: プラグイン必須(無ければ Fail‑Fast)
|
||||
- NYASH_DISABLE_PLUGINS / HAKO_DISABLE_PLUGINS: `1` でプラグイン無効(結果として core‑ro 相当)
|
||||
|
||||
## LLVM/AOT
|
||||
- NYASH_LLVM_FEATURE: LLVM機能選択("llvm"(default) または "llvm-inkwell-legacy")
|
||||
- LLVM_SYS_180_PREFIX: LLVM 18 のパス指定(llvm-inkwell-legacy使用時のみ必要)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// string_helpers.hako — StringHelpers (common, pure helpers)
|
||||
// string_helpers.hako - StringHelpers (common, pure helpers)
|
||||
// Responsibility: numeric/string conversions and JSON quoting for selfhost tools.
|
||||
// Non-responsibility: JSON scanning beyond local character processing; use CfgNavigatorBox/StringScanBox for navigation.
|
||||
|
||||
@ -84,9 +84,9 @@ static box StringHelpers {
|
||||
return out
|
||||
}
|
||||
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// 🆕 Extended String Utilities (added for parser/compiler unification)
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
// ------------------------------------
|
||||
// NEW: Extended String Utilities (added for parser/compiler unification)
|
||||
// ------------------------------------
|
||||
|
||||
// Character predicates
|
||||
is_digit(ch) { return ch >= "0" && ch <= "9" }
|
||||
|
||||
59
nyash.toml
59
nyash.toml
@ -581,32 +581,61 @@ get = { method_id = 1 }
|
||||
set = { method_id = 2 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Builtin/Core Boxes (slot config via central tables)
|
||||
# These entries allow method_id resolution for core boxes
|
||||
# without requiring a dynamic library. Execution remains
|
||||
# handled by built-in handlers; this mapping is for MIR slot
|
||||
# injection and consistency only.
|
||||
# ArrayBox Plugin (Core Box)
|
||||
[libraries."libnyash_array_plugin.so"]
|
||||
boxes = ["ArrayBox"]
|
||||
path = "target/release/libnyash_array_plugin.so"
|
||||
|
||||
[box_types]
|
||||
# Assign type IDs for core boxes (reserved range)
|
||||
ArrayBox = 5
|
||||
MapBox = 6
|
||||
[libraries."libnyash_array_plugin.so".ArrayBox]
|
||||
type_id = 5
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[box_methods.ArrayBox.methods]
|
||||
[libraries."libnyash_array_plugin.so".ArrayBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
push = { method_id = 4 }
|
||||
get = { method_id = 5 }
|
||||
set = { method_id = 6 }
|
||||
size = { method_id = 7 }
|
||||
length = { method_id = 7 }
|
||||
len = { method_id = 7 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[box_methods.MapBox.methods]
|
||||
# MapBox Plugin (Core Box)
|
||||
[libraries."libnyash_map_plugin.so"]
|
||||
boxes = ["MapBox"]
|
||||
path = "target/release/libnyash_map_plugin.so"
|
||||
|
||||
[libraries."libnyash_map_plugin.so".MapBox]
|
||||
type_id = 6
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_map_plugin.so".MapBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
set = { method_id = 4 }
|
||||
get = { method_id = 5 }
|
||||
size = { method_id = 6 }
|
||||
length = { method_id = 6 }
|
||||
len = { method_id = 6 }
|
||||
has = { method_id = 6 }
|
||||
size = { method_id = 7 }
|
||||
length = { method_id = 7 }
|
||||
len = { method_id = 7 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
# ConsoleBox Plugin (Core Box)
|
||||
[libraries."libnyash_console_plugin.so"]
|
||||
boxes = ["ConsoleBox"]
|
||||
path = "target/release/libnyash_console_plugin.so"
|
||||
|
||||
[libraries."libnyash_console_plugin.so".ConsoleBox]
|
||||
type_id = 7
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_console_plugin.so".ConsoleBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
log = { method_id = 4 }
|
||||
print = { method_id = 4 }
|
||||
fini = { method_id = 4294967295 }
|
||||
"tools.hako_check.analysis_consumer" = "tools/hako_check/analysis_consumer.hako"
|
||||
"tools.hako_check.rules.rule_include_forbidden" = "tools/hako_check/rules/rule_include_forbidden.hako"
|
||||
"tools.hako_check.rules.rule_using_quoted" = "tools/hako_check/rules/rule_using_quoted.hako"
|
||||
|
||||
@ -166,9 +166,23 @@ impl MirInterpreter {
|
||||
let ret = if func.signature.params.len() == 0 {
|
||||
self.execute_function(func)?
|
||||
} else {
|
||||
// Build argv from NYASH_SCRIPT_ARGS_JSON (set by CLI when using `--`) or NYASH_ARGV (JSON array)
|
||||
// Build argv from (priority) HEX JSON, normal JSON, or NYASH_ARGV
|
||||
// 1) NYASH_SCRIPT_ARGS_HEX_JSON: JSON array of hex-encoded UTF-8 strings
|
||||
// 2) NYASH_SCRIPT_ARGS_JSON: JSON array of strings
|
||||
// 3) NYASH_ARGV: JSON array (legacy)
|
||||
let mut argv_list: Vec<String> = Vec::new();
|
||||
if let Ok(s) = std::env::var("NYASH_SCRIPT_ARGS_JSON") {
|
||||
if let Ok(s) = std::env::var("NYASH_SCRIPT_ARGS_HEX_JSON") {
|
||||
if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) {
|
||||
let mut out = Vec::with_capacity(v.len());
|
||||
for hx in v.into_iter() {
|
||||
match hex_decode_to_string(&hx) {
|
||||
Ok(ss) => out.push(ss),
|
||||
Err(_) => out.push(String::new()),
|
||||
}
|
||||
}
|
||||
argv_list = out;
|
||||
}
|
||||
} else if let Ok(s) = std::env::var("NYASH_SCRIPT_ARGS_JSON") {
|
||||
if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) { argv_list = v; }
|
||||
} else if let Ok(s) = std::env::var("NYASH_ARGV") {
|
||||
if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) { argv_list = v; }
|
||||
@ -196,3 +210,26 @@ impl MirInterpreter {
|
||||
self.exec_function_inner(func, None)
|
||||
}
|
||||
}
|
||||
|
||||
fn hex_decode_to_string(hex: &str) -> Result<String, ()> {
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(hex.len() / 2);
|
||||
let mut it = hex.as_bytes().iter().cloned();
|
||||
while let (Some(h), Some(l)) = (it.next(), it.next()) {
|
||||
let hi = from_hex(h).ok_or(())?;
|
||||
let lo = from_hex(l).ok_or(())?;
|
||||
bytes.push((hi << 4) | lo);
|
||||
}
|
||||
match String::from_utf8(bytes) {
|
||||
Ok(s) => Ok(s),
|
||||
Err(e) => Ok(String::from_utf8_lossy(e.as_bytes()).into_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_hex(b: u8) -> Option<u8> {
|
||||
match b {
|
||||
b'0'..=b'9' => Some(b - b'0'),
|
||||
b'a'..=b'f' => Some(b - b'a' + 10),
|
||||
b'A'..=b'F' => Some(b - b'A' + 10),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,7 +269,9 @@ impl UnifiedBoxRegistry {
|
||||
args: &[Box<dyn NyashBox>],
|
||||
) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
// Prefer plugin-builtins when enabled and provider is available in v2 registry
|
||||
if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1") {
|
||||
// BUT: Skip if plugins are explicitly disabled
|
||||
let plugins_disabled = std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() == Some("1");
|
||||
if !plugins_disabled && std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1") {
|
||||
use crate::runtime::{get_global_registry, BoxProvider};
|
||||
// Allowlist types for override: env NYASH_PLUGIN_OVERRIDE_TYPES="ArrayBox,MapBox" (default: none)
|
||||
let allow: Vec<String> = if let Ok(list) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES")
|
||||
|
||||
@ -27,6 +27,16 @@ impl BoxFactory for PluginBoxFactory {
|
||||
name: &str,
|
||||
args: &[Box<dyn NyashBox>],
|
||||
) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
// Check if plugins are disabled
|
||||
let plugins_disabled = std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() == Some("1");
|
||||
eprintln!("[PluginBoxFactory] NYASH_DISABLE_PLUGINS={} for {}",
|
||||
std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref().unwrap_or("not set"), name);
|
||||
if plugins_disabled {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Plugins disabled (NYASH_DISABLE_PLUGINS=1), cannot create {}", name),
|
||||
});
|
||||
}
|
||||
|
||||
// Use the existing v2 plugin system
|
||||
let registry = get_global_registry();
|
||||
|
||||
|
||||
@ -11,6 +11,15 @@ pub fn parse() -> CliConfig {
|
||||
if let Ok(json) = serde_json::to_string(&script_args) {
|
||||
std::env::set_var("NYASH_SCRIPT_ARGS_JSON", json);
|
||||
}
|
||||
// Provide HEX-escaped JSON as an alternate robust path for multiline/special bytes
|
||||
// Each arg is encoded as lowercase hex of its UTF-8 bytes
|
||||
let hex_args: Vec<String> = script_args
|
||||
.iter()
|
||||
.map(|s| hex_encode_utf8(s))
|
||||
.collect();
|
||||
if let Ok(hex_json) = serde_json::to_string(&hex_args) {
|
||||
std::env::set_var("NYASH_SCRIPT_ARGS_HEX_JSON", hex_json);
|
||||
}
|
||||
}
|
||||
let matches = build_command()
|
||||
.try_get_matches_from(&argv[..pos])
|
||||
@ -97,6 +106,17 @@ pub fn build_command() -> Command {
|
||||
.arg(Arg::new("build-target").long("target").value_name("TRIPLE").help("Target triple for --build"))
|
||||
}
|
||||
|
||||
fn hex_encode_utf8(s: &str) -> String {
|
||||
let bytes = s.as_bytes();
|
||||
let mut out = String::with_capacity(bytes.len() * 2);
|
||||
const HEX: &[u8; 16] = b"0123456789abcdef";
|
||||
for &b in bytes {
|
||||
out.push(HEX[(b >> 4) as usize] as char);
|
||||
out.push(HEX[(b & 0x0f) as usize] as char);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn from_matches(matches: &ArgMatches) -> CliConfig {
|
||||
if matches.get_flag("stage3") { std::env::set_var("NYASH_NY_COMPILER_STAGE3", "1"); }
|
||||
if let Some(a) = matches.get_one::<String>("ny-compiler-args") { std::env::set_var("NYASH_NY_COMPILER_CHILD_ARGS", a); }
|
||||
|
||||
@ -32,7 +32,11 @@ list_targets() {
|
||||
|
||||
run_one() {
|
||||
local f="$1"
|
||||
# Run analyzer main directly with file arg(s)
|
||||
# Run analyzer main with inlined source text to avoid FileBox dependency
|
||||
local text
|
||||
text="$(sed 's/\r$//' "$f")"
|
||||
NYASH_DISABLE_PLUGINS=1 \
|
||||
NYASH_BOX_FACTORY_POLICY=builtin_first \
|
||||
NYASH_DISABLE_NY_COMPILER=1 \
|
||||
HAKO_DISABLE_NY_COMPILER=1 \
|
||||
NYASH_PARSER_STAGE3=1 \
|
||||
@ -44,7 +48,7 @@ run_one() {
|
||||
HAKO_ENABLE_USING=1 \
|
||||
NYASH_USING_AST=1 \
|
||||
NYASH_NY_COMPILER_TIMEOUT_MS="${NYASH_NY_COMPILER_TIMEOUT_MS:-8000}" \
|
||||
"$BIN" --backend vm "$ROOT/tools/hako_check/cli.hako" -- "$f" \
|
||||
"$BIN" --backend vm "$ROOT/tools/hako_check/cli.hako" -- --source-file "$f" "$text" \
|
||||
>"/tmp/hako_lint_out_$$.log" 2>&1 || true
|
||||
local out rc
|
||||
out="$(cat "/tmp/hako_lint_out_$$.log")"; rc=0
|
||||
@ -65,6 +69,9 @@ if [ "$FORMAT" = "dot" ]; then
|
||||
for p in "$@"; do list_targets "$p" >>"$TMP_LIST"; done
|
||||
mapfile -t FILES <"$TMP_LIST"
|
||||
rm -f "$TMP_LIST"
|
||||
|
||||
NYASH_DISABLE_PLUGINS=1 \
|
||||
NYASH_BOX_FACTORY_POLICY=builtin_first \
|
||||
NYASH_DISABLE_NY_COMPILER=1 \
|
||||
HAKO_DISABLE_NY_COMPILER=1 \
|
||||
NYASH_PARSER_STAGE3=1 \
|
||||
@ -90,6 +97,9 @@ elif [ "$FORMAT" = "json-lsp" ]; then
|
||||
for p in "$@"; do list_targets "$p" >>"$TMP_LIST"; done
|
||||
mapfile -t FILES <"$TMP_LIST"
|
||||
rm -f "$TMP_LIST"
|
||||
|
||||
NYASH_DISABLE_PLUGINS=1 \
|
||||
NYASH_BOX_FACTORY_POLICY=builtin_first \
|
||||
NYASH_DISABLE_NY_COMPILER=1 \
|
||||
HAKO_DISABLE_NY_COMPILER=1 \
|
||||
NYASH_PARSER_STAGE3=1 \
|
||||
|
||||
@ -31,3 +31,21 @@ Planned AST metadata (parser_core.hako)
|
||||
Notes
|
||||
- Prefer AST intake; text scans are a minimal fallback.
|
||||
- For tests, use `tools/hako_check/run_tests.sh`.
|
||||
|
||||
Analyzer policy (plugins)
|
||||
- Tests/CI/Analyzer run without plugins by default: `NYASH_DISABLE_PLUGINS=1` and `NYASH_JSON_ONLY=1`.
|
||||
- File I/O is avoided by passing source text via `--source-file <path> <text>`.
|
||||
- When plugins are needed (dev/prod), set `NYASH_FILEBOX_MODE=auto` and provide [libraries] in nyash.toml.
|
||||
|
||||
Rules
|
||||
- Core implemented (green): HC011 Dead Methods, HC012 Dead Static Box, HC015 Arity Mismatch, HC016 Unused Alias, HC017 Non‑ASCII Quotes, HC018 Top‑level local, HC021 Analyzer IO Safety, HC022 Stage‑3 Gate, HC031 Brace Heuristics
|
||||
- Pending fixtures update: HC013 Duplicate Method, HC014 Missing Entrypoint
|
||||
|
||||
CLI options
|
||||
- `--rules a,b,c` limit execution to selected rules.
|
||||
- `--skip-rules a,b` skip selected.
|
||||
- `--no-ast` (default) avoids AST parser; `--force-ast` enables AST path (use sparingly while PHI is under polish).
|
||||
|
||||
Tips
|
||||
- JSON-only output: set `NYASH_JSON_ONLY=1` to avoid log noise in stdout; diagnostics go to stdout, logs to stderr.
|
||||
- For multiline `--source-file` payloads, CLI also provides HEX-escaped JSON in `NYASH_SCRIPT_ARGS_HEX_JSON` for robust transport; the VM prefers HEX→JSON→ARGV.
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// tools/hako_check/analysis_consumer.hako — HakoAnalysisBuilderBox (MVP)
|
||||
// tools/hako_check/analysis_consumer.hako - HakoAnalysisBuilderBox (MVP)
|
||||
// Build a minimal Analysis IR from raw .hako source (no Rust parser needed).
|
||||
// IR (MapBox): {
|
||||
// path: String,
|
||||
@ -34,6 +34,9 @@ static box HakoAnalysisBuilderBox {
|
||||
// uses (with fallback for backward compat)
|
||||
local uses = ast.get("uses"); if uses == null { uses = ast.get("uses_arr") }
|
||||
if uses != null { local ui=0; while ui<uses.size() { me._ensure_array(ir, "uses").push(uses.get(ui)); ui=ui+1 } }
|
||||
// aliases (if present)
|
||||
local aliases = ast.get("aliases")
|
||||
if aliases != null { ir.set("aliases", aliases) }
|
||||
// methods (qualified: Box.method/arity) and boxes metadata
|
||||
local boxes_ast = ast.get("boxes"); if boxes_ast == null { boxes_ast = ast.get("boxes_arr") }
|
||||
if boxes_ast != null {
|
||||
@ -82,7 +85,7 @@ static box HakoAnalysisBuilderBox {
|
||||
}
|
||||
}
|
||||
|
||||
// 1) collect using lines(AST が空のときのみテキスト走査)
|
||||
// 1) collect using lines(scan text only when AST is empty)
|
||||
local lines = me._split_lines(text)
|
||||
if ir.get("uses") == null || ir.get("uses").size() == 0 {
|
||||
local _i = 0
|
||||
@ -150,7 +153,7 @@ static box HakoAnalysisBuilderBox {
|
||||
// Final fallback: super simple scan over raw text if still no methods
|
||||
if ir.get("methods").size() == 0 { me._scan_methods_fallback(text, ir) }
|
||||
|
||||
// 3) calls: naive pattern Box.method( or Alias.method( — arity推定付き
|
||||
// 3) calls: naive pattern Box.method( or Alias.method( - with arity inference
|
||||
// For MVP, we scan whole text and link within same file boxes only.
|
||||
// debug noop
|
||||
local calls_arr = me._ensure_array(ir, "calls")
|
||||
@ -227,7 +230,7 @@ static box HakoAnalysisBuilderBox {
|
||||
return s.substring(0,p)
|
||||
}
|
||||
_count_commas_in_parens(rest) {
|
||||
// method foo(a,b,c) → 3 ; if empty → 0
|
||||
// method foo(a,b,c) -> 3 ; if empty -> 0
|
||||
local p1 = rest.indexOf("("); local p2 = rest.indexOf(")", p1+1)
|
||||
if p1 < 0 || p2 < 0 || p2 <= p1+1 { return 0 }
|
||||
local inside = rest.substring(p1+1, p2)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// tools/hako_check/cli.hako — HakoAnalyzerBox (MVP)
|
||||
// tools/hako_check/cli.hako - HakoAnalyzerBox (MVP)
|
||||
using selfhost.shared.common.string_helpers as Str
|
||||
using tools.hako_check.analysis_consumer as HakoAnalysisBuilderBox
|
||||
using tools.hako_check.rules.rule_include_forbidden as RuleIncludeForbiddenBox
|
||||
@ -65,7 +65,7 @@ static box HakoAnalyzerBox {
|
||||
}
|
||||
// keep a copy before sanitize for rules that must see original bytes (HC017, etc.)
|
||||
local text_raw = text
|
||||
// pre-sanitize (ASCII quotes, normalize newlines) — minimal & reversible
|
||||
// pre-sanitize (ASCII quotes, normalize newlines) - minimal & reversible
|
||||
text = me._sanitize(text)
|
||||
// analysis - only build IR if needed by active rules
|
||||
local ir = null
|
||||
@ -80,7 +80,7 @@ static box HakoAnalyzerBox {
|
||||
ir.set("boxes", new ArrayBox())
|
||||
ir.set("entrypoints", new ArrayBox())
|
||||
}
|
||||
// parse AST only when explicitly needed by active rules(include_forbiddenはfallback可)
|
||||
// parse AST only when explicitly needed by active rules (include_forbidden fallback allowed)
|
||||
local ast = null
|
||||
if no_ast == 0 && me._needs_ast(rules_only, rules_skip) == 1 { ast = HakoParserCoreBox.parse(text) }
|
||||
if debug == 1 {
|
||||
@ -287,7 +287,7 @@ static box HakoAnalyzerBox {
|
||||
if me._is_hc011(s) == 1 {
|
||||
local qual = me._extract_method_from_hc011(s)
|
||||
if qual != null {
|
||||
// method qual: Box.method/arity → Box
|
||||
// method qual: Box.method/arity -> Box
|
||||
local dot = qual.lastIndexOf(".")
|
||||
if dot > 0 {
|
||||
local box_name = qual.substring(0, dot)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// tools/hako_check/render/graphviz.hako — GraphvizRenderBox (MVP)
|
||||
// tools/hako_check/render/graphviz.hako - GraphvizRenderBox (MVP)
|
||||
// Render minimal DOT graph from one or more Analysis IRs.
|
||||
|
||||
using selfhost.shared.common.string_helpers as Str
|
||||
@ -20,13 +20,41 @@ static box GraphvizRenderBox {
|
||||
gi = gi + 1
|
||||
}
|
||||
}
|
||||
// Emit nodes
|
||||
local itn = nodes
|
||||
// Map iteration: keys() not available → store labels as keys in map
|
||||
// Use a synthetic loop by scanning a known list captured during _render_ir
|
||||
// For MVP, nodes map has key=name, value=1
|
||||
// We cannot iterate map keys deterministically; accept arbitrary order.
|
||||
// Re-emitting by re-collecting from edges as well (ensures endpoints appear).
|
||||
// Build clusters by box: group nodes whose name looks like Box.method/arity
|
||||
local groups = new MapBox()
|
||||
local group_keys = new ArrayBox()
|
||||
local nk = nodes.get("__keys__")
|
||||
if nk != null {
|
||||
local i = 0
|
||||
while i < nk.size() {
|
||||
local name = nk.get(i)
|
||||
local dot = name.indexOf(".")
|
||||
if dot > 0 {
|
||||
local box_name = name.substring(0, dot)
|
||||
local gkey = "cluster_" + box_name
|
||||
local arr = groups.get(gkey)
|
||||
if arr == null { arr = new ArrayBox(); groups.set(gkey, arr); group_keys.push(gkey) }
|
||||
// dedup in group
|
||||
local seen = 0; local j=0; while j < arr.size() { if arr.get(j) == name { seen = 1; break } j = j + 1 }
|
||||
if seen == 0 { arr.push(name) }
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
// Emit clusters
|
||||
local gi = 0
|
||||
while gi < group_keys.size() {
|
||||
local gk = group_keys.get(gi)
|
||||
print(" subgraph \"" + gk + "\" {")
|
||||
// label = box name (strip "cluster_")
|
||||
local label = gk.substring("cluster_".length())
|
||||
print(" label=\"" + label + "\";")
|
||||
local arr = groups.get(gk)
|
||||
local j = 0; while j < arr.size() { print(" \"" + arr.get(j) + "\";"); j = j + 1 }
|
||||
print(" }")
|
||||
gi = gi + 1
|
||||
}
|
||||
// Emit edges
|
||||
// Emit edges
|
||||
if edges != null {
|
||||
// edges map key = from + "\t" + to
|
||||
@ -42,7 +70,7 @@ static box GraphvizRenderBox {
|
||||
local src = key.substring(0, tab)
|
||||
local dst = key.substring(tab+1)
|
||||
print(" \"" + src + "\" -> \"" + dst + "\";")
|
||||
// also register nodes (in case they weren’t explicitly collected)
|
||||
// also register nodes (in case they weren't explicitly collected)
|
||||
nodes.set(src, 1)
|
||||
nodes.set(dst, 1)
|
||||
}
|
||||
@ -50,14 +78,13 @@ static box GraphvizRenderBox {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now emit nodes at the end for any isolated methods
|
||||
// Rebuild a list of node keys from a synthetic array stored under nodes.get("__keys__")
|
||||
local nk = nodes.get("__keys__")
|
||||
// Emit standalone nodes not covered by clusters
|
||||
if nk != null {
|
||||
local ni = 0
|
||||
while ni < nk.size() {
|
||||
local name = nk.get(ni)
|
||||
print(" \"" + name + "\";")
|
||||
local dot = name.indexOf(".")
|
||||
if dot < 0 { print(" \"" + name + "\";") }
|
||||
ni = ni + 1
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +53,10 @@ static box RuleDeadStaticBoxBox {
|
||||
// If ref_check is null or doesn't equal "1", box is unreferenced
|
||||
if ref_check == null || ref_check != "1" {
|
||||
// Box is never referenced - report error
|
||||
out.push("[HC012] dead static box (never referenced): " + name)
|
||||
// Line precision: prefer span_line from AST intake if present
|
||||
local line = box_info.get("span_line")
|
||||
if line == null { line = 1 }
|
||||
out.push("[HC012] dead static box (never referenced): " + name + " :: path:" + Str.int_to_str(line))
|
||||
}
|
||||
|
||||
bi = bi + 1
|
||||
|
||||
@ -38,15 +38,39 @@ run_case() {
|
||||
fi
|
||||
# Build argv array for analyzer CLI (preserve newlines in text)
|
||||
ARGS=( --debug --format json-lsp )
|
||||
# Restrict rules to the target under test for stability
|
||||
local base
|
||||
base="$(basename "$dir")"
|
||||
local rules_key=""
|
||||
case "$base" in
|
||||
HC011_*) rules_key="dead_methods" ;;
|
||||
HC012_*) rules_key="dead_static_box" ;;
|
||||
HC013_*) rules_key="duplicate_method" ;;
|
||||
HC014_*) rules_key="missing_entrypoint" ;;
|
||||
HC015_*) rules_key="arity_mismatch" ;;
|
||||
HC016_*) rules_key="unused_alias" ;;
|
||||
HC017_*) rules_key="non_ascii_quotes" ;;
|
||||
HC018_*) rules_key="top_level_local" ;;
|
||||
HC021_*) rules_key="analyzer_io_safety" ;;
|
||||
HC022_*) rules_key="stage3_gate" ;;
|
||||
HC031_*) rules_key="brace_heuristics" ;;
|
||||
*) rules_key="" ;;
|
||||
esac
|
||||
if [ -n "$rules_key" ]; then ARGS+=( --rules "$rules_key" ); fi
|
||||
if [ -f "$input_ok" ]; then ARGS+=( --source-file "$path_ok" "$text_ok" ); fi
|
||||
if [ -f "$input_ng" ]; then ARGS+=( --source-file "$path_ng" "$text_ng" ); fi
|
||||
|
||||
# Directly invoke analyzer CLI with args via '--', avoid wrapper/FS
|
||||
# Ensure plugin path is set
|
||||
export LD_LIBRARY_PATH="${ROOT}/target/release:${LD_LIBRARY_PATH:-}"
|
||||
|
||||
NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \
|
||||
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_SEAM_TOLERANT=1 HAKO_PARSER_SEAM_TOLERANT=1 \
|
||||
NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 NYASH_USING_AST=1 \
|
||||
NYASH_DISABLE_PLUGINS=1 \
|
||||
NYASH_BOX_FACTORY_POLICY=builtin_first \
|
||||
NYASH_JSON_ONLY=1 \
|
||||
"$BIN" --backend vm tools/hako_check/cli.hako -- "${ARGS[@]}" >"$tmp_out" 2>&1 || true
|
||||
"$BIN" --backend vm "$ROOT/tools/hako_check/cli.hako" -- "${ARGS[@]}" >"$tmp_out" 2>&1 || true
|
||||
# Extract diagnostics JSON (one-line or pretty block)
|
||||
tmp_json="/tmp/hako_test_json_$$.json"
|
||||
json_line=$(grep -m1 '^\{"diagnostics"' "$tmp_out" || true)
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
{"diagnostics":[
|
||||
{"file":"ng.hako","line":1,"rule":"HC012","message":"[HC012] dead static box (never referenced): UnusedBox","quickFix":"","severity":"warning"}
|
||||
{"file":"ng.hako","line":1,"rule":"HC012","message":"[HC012] dead static box (never referenced): UnusedBox :: path:1","quickFix":"","severity":"warning"}
|
||||
]}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// tools/hako_parser/ast_emit.hako — HakoAstEmitBox (MVP)
|
||||
// tools/hako_parser/ast_emit.hako - HakoAstEmitBox (MVP)
|
||||
using selfhost.shared.common.string_helpers as Str
|
||||
|
||||
static box HakoAstEmitBox {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// tools/hako_parser/cli.hako — HakoParserBox CLI (MVP skeleton)
|
||||
// tools/hako_parser/cli.hako - HakoParserBox CLI (MVP skeleton)
|
||||
using tools.hako_parser.parser_core as HakoParserCoreBox
|
||||
using tools.hako_parser.ast_emit as HakoAstEmitBox
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// tools/hako_parser/parser_core.hako — HakoParserCoreBox (token-based MVP)
|
||||
// tools/hako_parser/parser_core.hako - HakoParserCoreBox (token-based MVP)
|
||||
using selfhost.shared.common.string_helpers as Str
|
||||
using tools.hako_parser.tokenizer as HakoTokenizerBox
|
||||
|
||||
@ -6,11 +6,13 @@ static box HakoParserCoreBox {
|
||||
// Parse .hako source into minimal AST map:
|
||||
// {
|
||||
// uses: Array<String>,
|
||||
// aliases: Array<{name,alias}>,
|
||||
// boxes: Array<{name,is_static,methods:Array<{name,arity,span}>}>
|
||||
// }
|
||||
parse(text) {
|
||||
local ast = new MapBox()
|
||||
ast.set("uses", new ArrayBox())
|
||||
ast.set("aliases", new ArrayBox())
|
||||
ast.set("boxes", new ArrayBox())
|
||||
ast.set("includes", new ArrayBox())
|
||||
if text == null { return ast }
|
||||
@ -27,9 +29,18 @@ static box HakoParserCoreBox {
|
||||
p = me._advance(p, N)
|
||||
local t1 = me._peek(toks, p, N)
|
||||
if me._eq(t1, "STRING") == 1 {
|
||||
ast.get("uses").push(t1.get("lexeme")); p = me._advance(p, N)
|
||||
local mod_name = t1.get("lexeme"); ast.get("uses").push(mod_name); p = me._advance(p, N)
|
||||
// optional: as Alias
|
||||
local t2 = me._peek(toks, p, N); if me._eq(t2, "AS") == 1 { p = me._advance(p, N); local t3=me._peek(toks, p, N); if me._eq(t3, "IDENT")==1 { p = me._advance(p, N) } }
|
||||
local t2 = me._peek(toks, p, N);
|
||||
if me._eq(t2, "AS") == 1 {
|
||||
p = me._advance(p, N)
|
||||
local t3 = me._peek(toks, p, N)
|
||||
if me._eq(t3, "IDENT") == 1 {
|
||||
local alias = t3.get("lexeme"); p = me._advance(p, N)
|
||||
local rec = new MapBox(); rec.set("name", mod_name); rec.set("alias", alias)
|
||||
ast.get("aliases").push(rec)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// tolerate malformed using; skip token
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// tools/hako_parser/tokenizer.hako — HakoTokenizerBox (Stage-3 aware tokenizer, MVP)
|
||||
// tools/hako_parser/tokenizer.hako - HakoTokenizerBox (Stage-3 aware tokenizer, MVP)
|
||||
// Produces tokens with type, lexeme, line, col. Handles strings (escapes), numbers,
|
||||
// identifiers, and punctuation. Keywords are normalized to upper-case kinds.
|
||||
using selfhost.shared.common.string_helpers as Str
|
||||
@ -99,7 +99,7 @@ static box HakoTokenizerBox {
|
||||
local tok = new MapBox(); tok.set("type", sym_kind); tok.set("lexeme", ch); tok.set("line", line); tok.set("col", col)
|
||||
out.push(tok); i = i + 1; col = col + 1; continue
|
||||
}
|
||||
// unknown char → emit as PUNC so parser can skip gracefully
|
||||
// unknown char -> emit as PUNC so parser can skip gracefully
|
||||
local tok = new MapBox(); tok.set("type","PUNC"); tok.set("lexeme", ch); tok.set("line", line); tok.set("col", col)
|
||||
out.push(tok); i = i + 1; col = col + 1
|
||||
}
|
||||
|
||||
26
tools/smokes/v2/profiles/quick/analyze/dot_cluster_smoke.sh
Normal file
26
tools/smokes/v2/profiles/quick/analyze/dot_cluster_smoke.sh
Normal file
@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/../../../../../.." && pwd)"
|
||||
BIN="${NYASH_BIN:-$ROOT/target/release/hakorune}"
|
||||
|
||||
if [ ! -x "$BIN" ]; then
|
||||
echo "[SMOKE] build missing: $BIN" >&2
|
||||
echo "Run: cargo build --release" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Use an existing test pair to generate DOT and check cluster emission
|
||||
OUT="/tmp/hc_dot_$$.dot"
|
||||
set +e
|
||||
NYASH_JSON_ONLY=1 "$ROOT/tools/hako_check.sh" --format dot "$ROOT/tools/hako_check/tests/HC011_dead_methods" > "$OUT" 2>/dev/null
|
||||
rc=$?
|
||||
set -e
|
||||
|
||||
if ! grep -q 'subgraph "cluster_' "$OUT"; then
|
||||
echo "[SMOKE/FAIL] dot_cluster_smoke: no clusters found" >&2
|
||||
sed -n '1,120p' "$OUT" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
echo "[SMOKE/OK] dot_cluster_smoke"
|
||||
rm -f "$OUT"
|
||||
@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/../../../../../.." && pwd)"
|
||||
BIN="${NYASH_BIN:-$ROOT/target/release/hakorune}"
|
||||
|
||||
if [ ! -x "$BIN" ]; then
|
||||
echo "[SMOKE] build missing: $BIN" >&2
|
||||
echo "Run: cargo build --release" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
tmp_hako="/tmp/argv_multiline_$$.hako"
|
||||
cat >"$tmp_hako" <<'HAKO'
|
||||
static box Main {
|
||||
method main(args) {
|
||||
// args: [<file>, <text>], or custom — we check presence of newline in any arg
|
||||
local n = args.size();
|
||||
local i = 0; local has_nl = 0
|
||||
while i < n {
|
||||
local s = args.get(i)
|
||||
if s.indexOf("\n") >= 0 { has_nl = 1 }
|
||||
i = i + 1
|
||||
}
|
||||
// return 0 if newline preserved, else 1
|
||||
return (has_nl == 1) ? 0 : 1
|
||||
}
|
||||
}
|
||||
HAKO
|
||||
|
||||
multiline="line1\nline2\nline3"
|
||||
|
||||
set +e
|
||||
NYASH_JSON_ONLY=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 "$BIN" --backend vm "$tmp_hako" -- --source-file "/dev/null" "$multiline"
|
||||
rc=$?
|
||||
set -e
|
||||
|
||||
rm -f "$tmp_hako"
|
||||
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
echo "[SMOKE/FAIL] argv_multiline_roundtrip: rc=$rc" >&2
|
||||
exit 1
|
||||
else
|
||||
echo "[SMOKE/OK] argv_multiline_roundtrip"
|
||||
fi
|
||||
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BIN="${NYASH_BIN:-./target/release/hakorune}"
|
||||
if [ ! -x "$BIN" ]; then echo "nyash binary not found: $BIN" >&2; exit 2; fi
|
||||
|
||||
PROG=$(mktemp /tmp/map_get_shares_array.XXXXXX.hako)
|
||||
cat >"$PROG" <<'HK'
|
||||
static box Main {
|
||||
method main() {
|
||||
local m = new MapBox()
|
||||
m.set("a", new ArrayBox())
|
||||
// get → push → size should reflect original map's array
|
||||
local a = m.get("a")
|
||||
a.push(1)
|
||||
local s = m.get("a").size()
|
||||
if s.toString() == "1" { return 0 } else { return 1 }
|
||||
}
|
||||
}
|
||||
HK
|
||||
|
||||
NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \
|
||||
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_SEAM_TOLERANT=1 HAKO_PARSER_SEAM_TOLERANT=1 \
|
||||
"$BIN" --backend vm "$PROG" >/dev/null 2>&1
|
||||
rc=$?
|
||||
rm -f "$PROG"
|
||||
exit $rc
|
||||
@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BIN="${NYASH_BIN:-./target/release/hakorune}"
|
||||
if [ ! -x "$BIN" ]; then echo "nyash binary not found: $BIN" >&2; exit 2; fi
|
||||
|
||||
PROG=$(mktemp /tmp/map_get_shares_map.XXXXXX.hako)
|
||||
cat >"$PROG" <<'HK'
|
||||
static box Main {
|
||||
method main() {
|
||||
local m = new MapBox()
|
||||
m.set("m", new MapBox())
|
||||
local inner = m.get("m")
|
||||
inner.set("k", 42)
|
||||
// Reflect to original
|
||||
if m.get("m").has("k").toString() == "true" { return 0 } else { return 1 }
|
||||
}
|
||||
}
|
||||
HK
|
||||
|
||||
NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \
|
||||
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_SEAM_TOLERANT=1 HAKO_PARSER_SEAM_TOLERANT=1 \
|
||||
"$BIN" --backend vm "$PROG" >/dev/null 2>&1
|
||||
rc=$?
|
||||
rm -f "$PROG"
|
||||
exit $rc
|
||||
@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BIN="${NYASH_BIN:-./target/release/hakorune}"
|
||||
if [ ! -x "$BIN" ]; then echo "nyash binary not found: $BIN" >&2; exit 2; fi
|
||||
|
||||
# Minimal static box with two methods; analyzer should see 2 methods via AST
|
||||
PROG=$(mktemp /tmp/parser_min_methods_ok.XXXXXX.hako)
|
||||
cat >"$PROG" <<'HK'
|
||||
static box Main {
|
||||
method main() { return 0 }
|
||||
method helper() { return 0 }
|
||||
}
|
||||
HK
|
||||
|
||||
# Build a tiny program to call AnalysisBuilder directly (AST経路)
|
||||
WRAP=$(mktemp /tmp/parser_min_methods_wrap.XXXXXX.hako)
|
||||
cat >"$WRAP" <<'HK'
|
||||
using tools.hako_parser.parser_core as HakoParserCoreBox
|
||||
static box Main {
|
||||
method main(args) {
|
||||
local path = args.get(0)
|
||||
local text = args.get(1)
|
||||
local ast = HakoParserCoreBox.parse(text)
|
||||
if ast == null { return 1 }
|
||||
local boxes = ast.get("boxes")
|
||||
if boxes == null { return 1 }
|
||||
// count methods across boxes
|
||||
local total = 0
|
||||
local i = 0; while i < boxes.size() {
|
||||
local b = boxes.get(i); local ms = b.get("methods"); if ms != null { total = total + ms.size() }
|
||||
i = i + 1
|
||||
}
|
||||
if total >= 2 { return 0 } else { return 1 }
|
||||
}
|
||||
}
|
||||
HK
|
||||
|
||||
NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \
|
||||
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 \
|
||||
"$BIN" --backend vm "$WRAP" -- "$PROG" "$(cat "$PROG")" >/dev/null 2>&1
|
||||
rc=$?
|
||||
rm -f "$PROG" "$WRAP"
|
||||
exit $rc
|
||||
@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BIN="${NYASH_BIN:-./target/release/hakorune}"
|
||||
if [ ! -x "$BIN" ]; then echo "nyash binary not found: $BIN" >&2; exit 2; fi
|
||||
|
||||
PROG=$(mktemp /tmp/string_size_alias.XXXXXX.hako)
|
||||
cat >"$PROG" <<'HK'
|
||||
static box Main {
|
||||
method main() {
|
||||
local s = "hello"
|
||||
if s.length().toString() != "5" { return 1 }
|
||||
if s.size().toString() != "5" { return 2 }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
HK
|
||||
|
||||
NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \
|
||||
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_SEAM_TOLERANT=1 HAKO_PARSER_SEAM_TOLERANT=1 \
|
||||
"$BIN" --backend vm "$PROG" >/dev/null 2>&1
|
||||
rc=$?
|
||||
rm -f "$PROG"
|
||||
exit $rc
|
||||
Reference in New Issue
Block a user