chore: Phase 25.1 完了 - LoopForm v2/Stage1 CLI/環境変数削減 + Phase 26-D からの変更

Phase 25.1 完了成果:
-  LoopForm v2 テスト・ドキュメント・コメント完備
  - 4ケース(A/B/C/D)完全テストカバレッジ
  - 最小再現ケース作成(SSAバグ調査用)
  - SSOT文書作成(loopform_ssot.md)
  - 全ソースに [LoopForm] コメントタグ追加

-  Stage-1 CLI デバッグ環境構築
  - stage1_cli.hako 実装
  - stage1_bridge.rs ブリッジ実装
  - デバッグツール作成(stage1_debug.sh/stage1_minimal.sh)
  - アーキテクチャ改善提案文書

-  環境変数削減計画策定
  - 25変数の完全調査・分類
  - 6段階削減ロードマップ(25→5、80%削減)
  - 即時削除可能変数特定(NYASH_CONFIG/NYASH_DEBUG)

Phase 26-D からの累積変更:
- PHI実装改善(ExitPhiBuilder/HeaderPhiBuilder等)
- MIRビルダーリファクタリング
- 型伝播・最適化パス改善
- その他約300ファイルの累積変更

🎯 技術的成果:
- SSAバグ根本原因特定(条件分岐内loop変数変更)
- Region+next_iパターン適用完了(UsingCollectorBox等)
- LoopFormパターン文書化・テスト化完了
- セルフホスティング基盤強化

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: ChatGPT <noreply@openai.com>
Co-Authored-By: Task Assistant <task@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-21 06:25:17 +09:00
parent baf028a94f
commit f9d100ce01
366 changed files with 14322 additions and 5236 deletions

View File

@ -1,8 +1,19 @@
//! Resolve context — capture per-thread prelude merge context for enriched diagnostics.
use std::cell::RefCell;
/// Line span mapping for merged prelude+main sources.
/// Represents that lines [start_line, start_line + line_count) in the merged
/// text originate from `file` at local lines [1, line_count].
#[derive(Clone, Debug)]
pub struct LineSpan {
pub file: String,
pub start_line: usize,
pub line_count: usize,
}
thread_local! {
static LAST_MERGED_PRELUDES: RefCell<Vec<String>> = RefCell::new(Vec::new());
static LAST_TEXT_MERGE_LINE_SPANS: RefCell<Vec<LineSpan>> = RefCell::new(Vec::new());
}
/// Record the list of prelude file paths used for the last text merge in this thread.
@ -23,3 +34,25 @@ pub fn take_last_merged_preludes() -> Vec<String> {
LAST_MERGED_PRELUDES.with(|c| std::mem::take(&mut *c.borrow_mut()))
}
/// Record the line-span mapping for the last text merge in this thread.
pub fn set_last_text_merge_line_spans(spans: Vec<LineSpan>) {
LAST_TEXT_MERGE_LINE_SPANS.with(|c| {
*c.borrow_mut() = spans;
});
}
/// Try to map a merged (global) line number back to its origin file and local line.
pub fn map_merged_line_to_origin(line: usize) -> Option<(String, usize)> {
if line == 0 {
return None;
}
LAST_TEXT_MERGE_LINE_SPANS.with(|c| {
for span in c.borrow().iter() {
if line >= span.start_line && line < span.start_line + span.line_count {
let local = line - span.start_line + 1;
return Some((span.file.clone(), local));
}
}
None
})
}

View File

@ -1,6 +1,6 @@
/*!
* Using resolver utilities — static resolution line (SSOT + AST) 📦
*
*
* 箱化モジュール化で綺麗綺麗になったにゃ!🎉
*
* Separation of concerns:
@ -19,45 +19,29 @@
* - seam: seam logging and optional boundary markers (for diagnostics).
*/
pub mod strip;
pub mod seam;
pub mod using_resolution;
pub mod prelude_manager;
pub mod selfhost_pipeline;
pub mod path_util;
pub mod context;
pub mod path_util;
pub mod prelude_manager;
pub mod seam;
pub mod selfhost_pipeline;
pub mod strip;
pub mod using_resolution;
// 📦 箱化モジュールの公開にゃ!
pub use using_resolution::{
UsingResolutionBox,
UsingTarget,
UsingConfig,
};
pub use using_resolution::{UsingConfig, UsingResolutionBox, UsingTarget};
pub use prelude_manager::{
PreludeManagerBox,
MergeStrategy,
MergeResult,
};
pub use prelude_manager::{MergeResult, MergeStrategy, PreludeManagerBox};
pub use selfhost_pipeline::{
SelfhostPipelineBox,
CompilationResult,
PipelineConfig,
};
pub use selfhost_pipeline::{CompilationResult, PipelineConfig, SelfhostPipelineBox};
// 🔧 Legacy functions (preserved for compatibility)
pub use strip::{
preexpand_at_local,
collect_using_and_strip,
resolve_prelude_paths_profiled,
parse_preludes_to_asts,
merge_prelude_asts_with_main,
merge_prelude_text,
collect_using_and_strip, merge_prelude_asts_with_main, merge_prelude_text,
parse_preludes_to_asts, preexpand_at_local, resolve_prelude_paths_profiled,
};
// Expose context helpers for enhanced diagnostics
pub use context::{
set_last_merged_preludes,
clone_last_merged_preludes,
clone_last_merged_preludes, map_merged_line_to_origin, set_last_merged_preludes,
set_last_text_merge_line_spans, LineSpan,
};

View File

@ -17,4 +17,3 @@ pub fn is_using_target_path_unquoted(target_unquoted: &str) -> bool {
|| target_unquoted.ends_with(".hako")
|| target_unquoted.ends_with(".nyash")
}

View File

@ -1,13 +1,13 @@
//! Prelude Manager Box - 綺麗綺麗なプレリュード統合専門家!📦
//!
//!
//! テキストマージとASTマージを分離して、
//! 保守性とテスト容易性を向上させるにゃ!
use crate::runner::NyashRunner;
use crate::runner::modes::common_util::resolve::using_resolution::UsingResolutionBox;
use crate::runner::NyashRunner;
/// 📦 PreludeManagerBox - プレリュード統合の専門家!
///
///
/// テキストベースとASTベースの両方の統合を
/// 統一インターフェースで提供する箱にゃ!
pub struct PreludeManagerBox<'a> {
@ -111,11 +111,13 @@ impl<'a> PreludeManagerBox<'a> {
fn build_text_merged(
&self,
source: &str,
_filename: &str,
filename: &str,
prelude_paths: &[String],
trace: bool,
) -> Result<String, String> {
let mut merged = String::new();
let mut spans: Vec<crate::runner::modes::common_util::resolve::LineSpan> = Vec::new();
let mut current_line: usize = 1;
// プレリュードをDFS順に追加
for (idx, path) in prelude_paths.iter().enumerate() {
@ -138,16 +140,44 @@ impl<'a> PreludeManagerBox<'a> {
merged.push_str(&cleaned);
merged.push('\n');
let added = cleaned.lines().count();
if added > 0 {
spans.push(crate::runner::modes::common_util::resolve::LineSpan {
file: path.clone(),
start_line: current_line,
line_count: added,
});
current_line += added + 1; // +1 for the extra '\n'
} else {
current_line += 1;
}
}
// デバッグモードなら境界マーカーを追加
if std::env::var("NYASH_RESOLVE_SEAM_DEBUG").ok().as_deref() == Some("1") {
merged.push_str("\n/* --- using prelude/main boundary --- */\n\n");
// boundary line(s) are attributed to a synthetic "<boundary>" pseudo-file
let boundary_lines = 3usize;
spans.push(crate::runner::modes::common_util::resolve::LineSpan {
file: "<prelude/main-boundary>".to_string(),
start_line: current_line,
line_count: boundary_lines,
});
current_line += boundary_lines;
}
// メインソースを正規化して追加
let cleaned_main = self.normalize_text_for_inline(source);
merged.push_str(&cleaned_main);
let main_lines = cleaned_main.lines().count();
if main_lines > 0 {
spans.push(crate::runner::modes::common_util::resolve::LineSpan {
file: filename.to_string(),
start_line: current_line,
line_count: main_lines,
});
current_line += main_lines;
}
if trace {
crate::runner::trace::log(format!(
@ -158,6 +188,8 @@ impl<'a> PreludeManagerBox<'a> {
));
}
crate::runner::modes::common_util::resolve::set_last_text_merge_line_spans(spans);
Ok(self.normalize_text_for_inline(&merged))
}
@ -169,34 +201,35 @@ impl<'a> PreludeManagerBox<'a> {
) -> Result<(String, Vec<String>), String> {
// 既存のcollect_using_and_strip関数を呼び出す
// TODO: 将来的にはUsingResolutionBox経由に置き換える
let (cleaned, prelude_paths, _imports) = crate::runner::modes::common_util::resolve::strip::collect_using_and_strip(
&self.runner,
code,
filename,
)?;
let (cleaned, prelude_paths, _imports) =
crate::runner::modes::common_util::resolve::strip::collect_using_and_strip(
&self.runner,
code,
filename,
)?;
Ok((cleaned, prelude_paths))
}
/// 🔧 テキストを正規化するにゃ!
fn normalize_text_for_inline(&self, s: &str) -> String {
let mut out = s.replace("\r\n", "\n").replace("\r", "\n");
// `}` の前の `;` を除去(複数回パス)
for _ in 0..2 {
let mut tmp = String::with_capacity(out.len());
let bytes = out.as_bytes();
let mut i = 0usize;
while i < bytes.len() {
if bytes[i] == b';' {
// 先読みしてスペース/改行をスキップ
let mut j = i + 1;
while j < bytes.len() {
let c = bytes[j];
if c == b' ' || c == b'\t' || c == b'\n' {
j += 1;
} else {
break;
if c == b' ' || c == b'\t' || c == b'\n' {
j += 1;
} else {
break;
}
}
if j < bytes.len() && bytes[j] == b'}' {
@ -210,12 +243,12 @@ impl<'a> PreludeManagerBox<'a> {
}
out = tmp;
}
// ファイル末尾に改行を追加
if !out.ends_with('\n') {
out.push('\n');
if !out.ends_with('\n') {
out.push('\n');
}
out
}
@ -243,7 +276,7 @@ impl<'a> PreludeManagerBox<'a> {
prelude_paths: &[String],
) -> Result<MergeResult, String> {
let strategy = self.select_strategy(prelude_paths.len());
match strategy {
MergeStrategy::Text => self.merge_text(source, filename, prelude_paths),
MergeStrategy::Ast => self.merge_ast(source, filename, prelude_paths),

View File

@ -1,6 +1,8 @@
/// Log tail of inlined prelude chunk for seam inspection.
pub fn log_inlined_tail(path_key: &str, inlined_text: &str, seam_dbg: bool) {
if !seam_dbg { return; }
if !seam_dbg {
return;
}
let tail = inlined_text
.chars()
.rev()
@ -18,7 +20,9 @@ pub fn log_inlined_tail(path_key: &str, inlined_text: &str, seam_dbg: bool) {
/// Log the seam between prelude and body for quick visual diff.
pub fn log_prelude_body_seam(prelude_clean: &str, body: &str, seam_dbg: bool) {
if !seam_dbg { return; }
if !seam_dbg {
return;
}
let tail = prelude_clean
.chars()
.rev()
@ -28,8 +32,14 @@ pub fn log_prelude_body_seam(prelude_clean: &str, body: &str, seam_dbg: bool) {
.rev()
.collect::<String>();
let head = body.chars().take(160).collect::<String>();
eprintln!("[using][seam] prelude_tail=<<<{}>>>", tail.replace('\n', "\\n"));
eprintln!("[using][seam] body_head =<<<{}>>>", head.replace('\n', "\\n"));
eprintln!(
"[using][seam] prelude_tail=<<<{}>>>",
tail.replace('\n', "\\n")
);
eprintln!(
"[using][seam] body_head =<<<{}>>>",
head.replace('\n', "\\n")
);
}
// Legacy brace fix function removed (Phase 15 cleanup)

View File

@ -1,13 +1,15 @@
//! Selfhost Pipeline Box - 綺麗綺麗なセルフホストパイプライン専門家!📦
//!
//!
//! セルフホストコンパイルの複雑な処理を箱に閉じ込めて、
//! 保守性とテスト容易性を向上させるにゃ!
use crate::runner::modes::common_util::resolve::prelude_manager::{
MergeStrategy, PreludeManagerBox,
};
use crate::runner::NyashRunner;
use crate::runner::modes::common_util::resolve::prelude_manager::{PreludeManagerBox, MergeStrategy};
/// 📦 SelfhostPipelineBox - セルフホストパイプラインの専門家!
///
///
/// コンパイラーパイプライン全体を管理する箱にゃ!
pub struct SelfhostPipelineBox<'a> {
runner: &'a NyashRunner,
@ -37,7 +39,7 @@ impl<'a> SelfhostPipelineBox<'a> {
/// 🌟 新しいSelfhostPipelineBoxを作るにゃ
pub fn new(runner: &'a NyashRunner) -> Self {
let prelude_manager = PreludeManagerBox::new(runner);
Self {
runner,
prelude_manager,
@ -66,12 +68,14 @@ impl<'a> SelfhostPipelineBox<'a> {
// 第1フェーズusing文解析とプレリュードパス収集
let (cleaned_main, prelude_paths) = self.collect_and_resolve_using(code, filename)?;
// 第2フェーズプレリュード統合
let merge_result = if config.enable_ast_merge {
self.prelude_manager.merge_ast(&cleaned_main, filename, &prelude_paths)?
self.prelude_manager
.merge_ast(&cleaned_main, filename, &prelude_paths)?
} else {
self.prelude_manager.merge_text(&cleaned_main, filename, &prelude_paths)?
self.prelude_manager
.merge_text(&cleaned_main, filename, &prelude_paths)?
};
let processing_time = start_time.elapsed().as_millis() as u64;
@ -118,9 +122,7 @@ impl<'a> SelfhostPipelineBox<'a> {
eprintln!(
"[selfhost-pipeline] ✅ Completed in {}ms (strategy: {}, preludes: {})",
result.processing_time_ms,
strategy_str,
result.prelude_count
result.processing_time_ms, strategy_str, result.prelude_count
);
}
@ -146,14 +148,15 @@ impl<'a> SelfhostPipelineBox<'a> {
/// 🧪 パイプラインを検証するにゃ!(テスト用)
pub fn validate_pipeline(&self, code: &str, filename: &str) -> Result<Vec<String>, String> {
let mut issues = Vec::new();
// usingシステムの検証
if crate::config::env::enable_using() {
// using文があるかチェック
let using_count = code.lines()
let using_count = code
.lines()
.filter(|line| line.trim().starts_with("using "))
.count();
if using_count > 0 {
// プレリュード解決を試みる
match crate::runner::modes::common_util::resolve::strip::resolve_prelude_paths_profiled(
@ -177,11 +180,7 @@ impl<'a> SelfhostPipelineBox<'a> {
}
/// 📊 パフォーマンスプロファイリングするにゃ!
pub fn profile_pipeline(
&mut self,
_code: &str,
_filename: &str,
) -> Result<String, String> {
pub fn profile_pipeline(&mut self, _code: &str, _filename: &str) -> Result<String, String> {
// プロファイル機能を実装(別途)
// TODO: プロファイル機能を追加
Err("Profiling not yet implemented".to_string())

View File

@ -14,14 +14,26 @@ pub fn collect_using_and_strip(
runner: &NyashRunner,
code: &str,
filename: &str,
) -> Result<(String, Vec<String>, std::collections::HashMap<String, String>), String> {
) -> Result<
(
String,
Vec<String>,
std::collections::HashMap<String, String>,
),
String,
> {
if !crate::config::env::enable_using() {
return Ok((code.to_string(), Vec::new(), std::collections::HashMap::new()));
return Ok((
code.to_string(),
Vec::new(),
std::collections::HashMap::new(),
));
}
let using_ctx = runner.init_using_context();
let prod = crate::config::env::using_is_prod();
let strict = crate::config::env::env_bool("NYASH_USING_STRICT");
let verbose = crate::config::env::cli_verbose() || crate::config::env::env_bool("NYASH_RESOLVE_TRACE");
let verbose =
crate::config::env::cli_verbose() || crate::config::env::env_bool("NYASH_RESOLVE_TRACE");
let ctx_dir = std::path::Path::new(filename).parent();
let mut out = String::with_capacity(code.len());
@ -30,8 +42,8 @@ pub fn collect_using_and_strip(
use std::collections::HashMap;
let mut seen_paths: HashMap<String, (String, usize)> = HashMap::new(); // canon_path -> (alias/label, first_line)
let mut seen_aliases: HashMap<String, (String, usize)> = HashMap::new(); // alias -> (canon_path, first_line)
// Determine if this file is inside a declared package root; if so, allow
// internal file-using within the package even when file-using is globally disallowed.
// Determine if this file is inside a declared package root; if so, allow
// internal file-using within the package even when file-using is globally disallowed.
let filename_canon = std::fs::canonicalize(filename).ok();
let mut inside_pkg = false;
if let Some(ref fc) = filename_canon {
@ -66,7 +78,10 @@ pub fn collect_using_and_strip(
// Check if this is a known alias or module FIRST before treating as file path
let is_known_alias_or_module = using_ctx.aliases.contains_key(&target_unquoted)
|| using_ctx.pending_modules.iter().any(|(k, _)| k == &target_unquoted)
|| using_ctx
.pending_modules
.iter()
.any(|(k, _)| k == &target_unquoted)
|| using_ctx.packages.contains_key(&target_unquoted);
let is_path = if is_known_alias_or_module {
@ -74,7 +89,9 @@ pub fn collect_using_and_strip(
false
} else {
// SSOT: delegate path pattern check
crate::runner::modes::common_util::resolve::path_util::is_using_target_path_unquoted(&target_unquoted)
crate::runner::modes::common_util::resolve::path_util::is_using_target_path_unquoted(
&target_unquoted,
)
};
if is_path {
// SSOT: Disallow file-using at top-level; allow only for sources located
@ -145,7 +162,13 @@ pub fn collect_using_and_strip(
prev_line
));
} else {
seen_paths.insert(canon.clone(), (alias_name.clone().unwrap_or_else(|| "<none>".into()), line_no));
seen_paths.insert(
canon.clone(),
(
alias_name.clone().unwrap_or_else(|| "<none>".into()),
line_no,
),
);
}
if let Some(alias) = alias_name.clone() {
if let Some((prev_path, prev_line)) = seen_aliases.get(&alias) {
@ -184,7 +207,9 @@ pub fn collect_using_and_strip(
strict,
verbose,
) {
if resolved.starts_with("dylib:") { continue; }
if resolved.starts_with("dylib:") {
continue;
}
let canon = std::fs::canonicalize(&resolved)
.ok()
.map(|pb| pb.to_string_lossy().to_string())
@ -197,7 +222,10 @@ pub fn collect_using_and_strip(
} else {
seen_paths.insert(
canon.clone(),
(alias_name.clone().unwrap_or_else(|| "<none>".into()), line_no),
(
alias_name.clone().unwrap_or_else(|| "<none>".into()),
line_no,
),
);
}
if let Some(alias) = alias_name.clone() {
@ -217,10 +245,8 @@ pub fn collect_using_and_strip(
}
// 1) modules mapping (name -> path)
if let Some((_, mod_path)) = using_ctx
.pending_modules
.iter()
.find(|(n, _)| n == &name)
if let Some((_, mod_path)) =
using_ctx.pending_modules.iter().find(|(n, _)| n == &name)
{
let out_path = mod_path.clone();
// Duplicate detection (same semantics as packages below)
@ -236,7 +262,10 @@ pub fn collect_using_and_strip(
} else {
seen_paths.insert(
canon.clone(),
(alias_name.clone().unwrap_or_else(|| "<none>".into()), line_no),
(
alias_name.clone().unwrap_or_else(|| "<none>".into()),
line_no,
),
);
}
if let Some(alias) = alias_name.clone() {
@ -263,21 +292,25 @@ pub fn collect_using_and_strip(
PackageKind::Package => {
let base = std::path::Path::new(&pkg.path);
let out = if let Some(m) = &pkg.main {
if matches!(base.extension().and_then(|s| s.to_str()), Some("nyash") | Some("hako")) {
if matches!(
base.extension().and_then(|s| s.to_str()),
Some("nyash") | Some("hako")
) {
pkg.path.clone()
} else {
base.join(m).to_string_lossy().to_string()
}
} else if matches!(base.extension().and_then(|s| s.to_str()), Some("nyash") | Some("hako")) {
} else if matches!(
base.extension().and_then(|s| s.to_str()),
Some("nyash") | Some("hako")
) {
pkg.path.clone()
} else {
let leaf = base
.file_name()
.and_then(|s| s.to_str())
.unwrap_or(&name);
base.join(format!("{}.hako", leaf))
.to_string_lossy()
.to_string()
let leaf =
base.file_name().and_then(|s| s.to_str()).unwrap_or(&name);
base.join(format!("{}.hako", leaf))
.to_string_lossy()
.to_string()
};
// Duplicate detection for prod package alias resolution
let canon = std::fs::canonicalize(&out)
@ -294,7 +327,13 @@ pub fn collect_using_and_strip(
prev_line
));
} else {
seen_paths.insert(canon.clone(), (alias_name.clone().unwrap_or_else(|| "<none>".into()), line_no));
seen_paths.insert(
canon.clone(),
(
alias_name.clone().unwrap_or_else(|| "<none>".into()),
line_no,
),
);
}
if let Some(alias) = alias_name.clone() {
if let Some((prev_path, prev_line)) = seen_aliases.get(&alias) {
@ -476,7 +515,8 @@ pub fn resolve_prelude_paths_profiled(
}
let src = std::fs::read_to_string(&real_path)
.map_err(|e| format!("using: failed to read '{}': {}", real_path, e))?;
let (_cleaned, nested, _nested_imports) = collect_using_and_strip(runner, &src, &real_path)?;
let (_cleaned, nested, _nested_imports) =
collect_using_and_strip(runner, &src, &real_path)?;
for n in nested.iter() {
dfs(runner, n, out, seen)?;
}
@ -551,7 +591,10 @@ pub fn parse_preludes_to_asts(
) -> Result<Vec<nyash_rust::ast::ASTNode>, String> {
let debug = crate::config::env::env_bool("NYASH_STRIP_DEBUG");
if debug {
eprintln!("[strip-debug] parse_preludes_to_asts: {} files total", prelude_paths.len());
eprintln!(
"[strip-debug] parse_preludes_to_asts: {} files total",
prelude_paths.len()
);
for (idx, p) in prelude_paths.iter().enumerate() {
eprintln!("[strip-debug] [{}] {}", idx, p);
}
@ -559,18 +602,27 @@ pub fn parse_preludes_to_asts(
let mut out: Vec<nyash_rust::ast::ASTNode> = Vec::with_capacity(prelude_paths.len());
for (idx, prelude_path) in prelude_paths.iter().enumerate() {
if debug {
eprintln!("[strip-debug] [{}/{}] Processing: {}", idx + 1, prelude_paths.len(), prelude_path);
eprintln!(
"[strip-debug] [{}/{}] Processing: {}",
idx + 1,
prelude_paths.len(),
prelude_path
);
}
let src = std::fs::read_to_string(prelude_path)
.map_err(|e| format!("using: error reading {}: {}", prelude_path, e))?;
let (clean_src, _nested, _nested_imports) = collect_using_and_strip(runner, &src, prelude_path)?;
let (clean_src, _nested, _nested_imports) =
collect_using_and_strip(runner, &src, prelude_path)?;
// IMPORTANT: Do not attempt to AST-parse .hako preludes here.
// .hako is Hakorune surface, not Nyash AST. VM/VM-fallback paths
// will route to text-merge when any prelude is .hako.
if prelude_path.ends_with(".hako") {
if debug {
eprintln!("[strip-debug] skip AST parse for .hako prelude: {}", prelude_path);
eprintln!(
"[strip-debug] skip AST parse for .hako prelude: {}",
prelude_path
);
}
continue;
}
@ -579,22 +631,37 @@ pub fn parse_preludes_to_asts(
// Debug: dump clean_src if NYASH_STRIP_DEBUG=1
if debug {
eprintln!("[strip-debug] [{}/{}] About to parse: {}", idx + 1, prelude_paths.len(), prelude_path);
eprintln!("[strip-debug] clean_src first 500 chars:\n{}\n---",
&clean_src.chars().take(500).collect::<String>());
eprintln!(
"[strip-debug] [{}/{}] About to parse: {}",
idx + 1,
prelude_paths.len(),
prelude_path
);
eprintln!(
"[strip-debug] clean_src first 500 chars:\n{}\n---",
&clean_src.chars().take(500).collect::<String>()
);
}
match crate::parser::NyashParser::parse_from_string(&clean_src) {
Ok(ast) => {
if debug {
eprintln!("[strip-debug] [{}/{}] ✅ Parse SUCCESS: {}", idx + 1, prelude_paths.len(), prelude_path);
eprintln!(
"[strip-debug] [{}/{}] ✅ Parse SUCCESS: {}",
idx + 1,
prelude_paths.len(),
prelude_path
);
}
out.push(ast)
}
Err(e) => {
// Always output debug info on parse failure if NYASH_STRIP_DEBUG=1
let debug = crate::config::env::env_bool("NYASH_STRIP_DEBUG");
eprintln!("[strip-debug] Parse FAILED for: {} (debug={})", prelude_path, debug);
eprintln!(
"[strip-debug] Parse FAILED for: {} (debug={})",
prelude_path, debug
);
if debug {
eprintln!("[strip-debug] Error: {}", e);
let es = format!("{}", e);
@ -605,19 +672,31 @@ pub fn parse_preludes_to_asts(
if let Some(pos) = es.rfind("line ") {
let mut j = pos + 5; // after "line "
let bytes = es.as_bytes();
let mut n: usize = 0; let mut had = false;
let mut n: usize = 0;
let mut had = false;
while j < bytes.len() {
let c = bytes[j];
if c >= b'0' && c <= b'9' { n = n * 10 + (c - b'0') as usize; j += 1; had = true; } else { break; }
if c >= b'0' && c <= b'9' {
n = n * 10 + (c - b'0') as usize;
j += 1;
had = true;
} else {
break;
}
}
if had {
let ln = if n == 0 { 1 } else { n };
let from = ln.saturating_sub(3);
let to = std::cmp::min(lines.len(), ln + 3);
eprintln!("[strip-debug] Context around line {} ({}..={}):", ln, from.max(1), to);
eprintln!(
"[strip-debug] Context around line {} ({}..={}):",
ln,
from.max(1),
to
);
for i in from.max(1)..=to {
let mark = if i == ln { ">>" } else { " " };
if let Some(line) = lines.get(i-1) {
if let Some(line) = lines.get(i - 1) {
eprintln!("{} {:4}: {}", mark, i, line);
}
}
@ -640,7 +719,10 @@ pub fn parse_preludes_to_asts(
}
}
if debug {
eprintln!("[strip-debug] parse_preludes_to_asts: ✅ All {} files parsed successfully", out.len());
eprintln!(
"[strip-debug] parse_preludes_to_asts: ✅ All {} files parsed successfully",
out.len()
);
}
Ok(out)
}
@ -663,7 +745,10 @@ pub fn merge_prelude_asts_with_main(
if let ASTNode::Program { statements, .. } = main_ast.clone() {
let mut all = combined;
all.extend(statements);
ASTNode::Program { statements: all, span: Span::unknown() }
ASTNode::Program {
statements: all,
span: Span::unknown(),
}
} else {
// Defensive: unexpected shape; preserve main AST unchanged.
main_ast.clone()
@ -802,6 +887,8 @@ pub fn merge_prelude_text(
// Build merged text: preludes first, then main source
let mut merged = String::new();
let mut spans: Vec<crate::runner::modes::common_util::resolve::LineSpan> = Vec::new();
let mut current_line: usize = 1;
// Add preludes in DFS order
for (idx, path) in prelude_paths.iter().enumerate() {
@ -809,7 +896,8 @@ pub fn merge_prelude_text(
.map_err(|e| format!("using: failed to read '{}': {}", path, e))?;
// Strip using lines from prelude and normalize
let (cleaned_raw, _nested, _nested_imports) = collect_using_and_strip(runner, &content, path)?;
let (cleaned_raw, _nested, _nested_imports) =
collect_using_and_strip(runner, &content, path)?;
let mut cleaned = normalize_text_for_inline(&cleaned_raw);
// Hako-friendly normalize for preludes: always strip leading `local ` at line head
// when the prelude is a .hako (or looks like Hako code). This prevents top-level
@ -831,11 +919,30 @@ pub fn merge_prelude_text(
merged.push_str(&cleaned);
merged.push('\n');
let added = cleaned.lines().count();
if added > 0 {
spans.push(crate::runner::modes::common_util::resolve::LineSpan {
file: path.clone(),
start_line: current_line,
line_count: added,
});
current_line += added + 1; // +1 for extra '\n'
} else {
current_line += 1;
}
}
// Add boundary marker if debug mode
if crate::config::env::env_bool("NYASH_RESOLVE_SEAM_DEBUG") {
merged.push_str("\n/* --- using prelude/main boundary --- */\n\n");
let boundary_lines = 3usize;
spans.push(crate::runner::modes::common_util::resolve::LineSpan {
file: "<prelude/main-boundary>".to_string(),
start_line: current_line,
line_count: boundary_lines,
});
current_line += boundary_lines;
}
// Add main source (already cleaned of using lines) and normalize
@ -845,9 +952,19 @@ pub fn merge_prelude_text(
if filename.ends_with(".hako")
|| crate::runner::modes::common_util::hako::looks_like_hako_code(&cleaned_main_norm)
{
cleaned_main_norm = crate::runner::modes::common_util::hako::strip_local_decl(&cleaned_main_norm);
cleaned_main_norm =
crate::runner::modes::common_util::hako::strip_local_decl(&cleaned_main_norm);
}
merged.push_str(&cleaned_main_norm);
let main_lines = cleaned_main_norm.lines().count();
if main_lines > 0 {
spans.push(crate::runner::modes::common_util::resolve::LineSpan {
file: filename.to_string(),
start_line: current_line,
line_count: main_lines,
});
current_line += main_lines;
}
if trace {
crate::runner::trace::log(format!(
@ -865,6 +982,8 @@ pub fn merge_prelude_text(
}
}
crate::runner::modes::common_util::resolve::set_last_text_merge_line_spans(spans);
Ok(normalize_text_for_inline(&merged))
}
@ -887,7 +1006,11 @@ fn normalize_text_for_inline(s: &str) -> String {
let mut j = i + 1;
while j < bytes.len() {
let c = bytes[j];
if c == b' ' || c == b'\t' || c == b'\n' { j += 1; } else { break; }
if c == b' ' || c == b'\t' || c == b'\n' {
j += 1;
} else {
break;
}
}
if j < bytes.len() && bytes[j] == b'}' {
// drop ';' (do not advance j here)
@ -900,6 +1023,8 @@ fn normalize_text_for_inline(s: &str) -> String {
}
out = tmp;
}
if !out.ends_with('\n') { out.push('\n'); }
if !out.ends_with('\n') {
out.push('\n');
}
out
}

View File

@ -1,5 +1,5 @@
//! Using Resolution Box - 綺麗綺麗なusing文解決専門家📦
//!
//!
//! 巨大な `collect_using_and_strip` 関数を箱に分解して、
//! 責務を明確にしてテストしやすくするにゃ!
@ -8,7 +8,7 @@ use std::collections::HashMap;
use std::path::{Path, PathBuf};
/// 📦 UsingResolutionBox - using文解決の専門家
///
///
/// using文の解析、パス解決、重複チェックを一手に引き受ける箱にゃ
pub struct UsingResolutionBox<'a> {
runner: &'a NyashRunner,
@ -52,9 +52,9 @@ impl<'a> UsingResolutionBox<'a> {
|| crate::config::env::env_bool("NYASH_RESOLVE_TRACE"),
allow_file_using: crate::config::env::allow_using_file(),
};
let ctx_dir = Path::new(filename).parent().map(|p| p.to_path_buf());
// ファイルがパッケージ内にあるかチェック
let filename_canon = std::fs::canonicalize(filename).ok();
let mut inside_pkg = false;
@ -89,11 +89,11 @@ impl<'a> UsingResolutionBox<'a> {
}
crate::cli_v!("[using] stripped line: {}", line);
let rest0 = t.strip_prefix("using ").unwrap().trim();
let rest0 = rest0.split('#').next().unwrap_or(rest0).trim();
let rest0 = rest0.strip_suffix(';').unwrap_or(rest0).trim();
let (target, alias) = if let Some(pos) = rest0.find(" as ") {
(
rest0[..pos].trim().to_string(),
@ -105,16 +105,21 @@ impl<'a> UsingResolutionBox<'a> {
let target_unquoted = target.trim_matches('"').to_string();
let using_ctx = self.runner.init_using_context();
// 既知のエイリアスかモジュールかチェック
let is_known_alias_or_module = using_ctx.aliases.contains_key(&target_unquoted)
|| using_ctx.pending_modules.iter().any(|(k, _)| k == &target_unquoted)
|| using_ctx
.pending_modules
.iter()
.any(|(k, _)| k == &target_unquoted)
|| using_ctx.packages.contains_key(&target_unquoted);
let is_path = if is_known_alias_or_module {
false
} else {
crate::runner::modes::common_util::resolve::path_util::is_using_target_path_unquoted(&target_unquoted)
crate::runner::modes::common_util::resolve::path_util::is_using_target_path_unquoted(
&target_unquoted,
)
};
Some(UsingTarget {
@ -145,7 +150,7 @@ impl<'a> UsingResolutionBox<'a> {
let path = target.target.trim_matches('"').to_string();
let mut p = PathBuf::from(&path);
// 相対パス解決
if p.is_relative() {
if let Some(dir) = &self.ctx_dir {
@ -154,7 +159,7 @@ impl<'a> UsingResolutionBox<'a> {
p = cand;
}
}
// NYASH_ROOTも試す
if p.is_relative() {
if let Ok(root) = std::env::var("NYASH_ROOT") {
@ -172,9 +177,13 @@ impl<'a> UsingResolutionBox<'a> {
}
/// 🛡️ 重複チェックするにゃ!
pub fn check_duplicates(&mut self, target: &UsingTarget, resolved_path: &str) -> Result<(), String> {
let canon_path = std::fs::canonicalize(resolved_path)
.unwrap_or_else(|_| PathBuf::from(resolved_path));
pub fn check_duplicates(
&mut self,
target: &UsingTarget,
resolved_path: &str,
) -> Result<(), String> {
let canon_path =
std::fs::canonicalize(resolved_path).unwrap_or_else(|_| PathBuf::from(resolved_path));
let canon_str = canon_path.to_string_lossy();
// パスの重複チェック
@ -204,10 +213,14 @@ impl<'a> UsingResolutionBox<'a> {
// 記録
let alias_label = target.alias.as_ref().unwrap_or(&target.target).clone();
self.seen_paths.insert(canon_str.to_string(), (alias_label.clone(), target.line_no));
self.seen_paths
.insert(canon_str.to_string(), (alias_label.clone(), target.line_no));
if let Some(ref alias_name) = target.alias {
self.seen_aliases.insert(alias_name.clone(), (resolved_path.to_string(), target.line_no));
self.seen_aliases.insert(
alias_name.clone(),
(resolved_path.to_string(), target.line_no),
);
}
Ok(())