hv1 verify: add direct route (env JSON) and clean inline path; fix v1 phi incoming order; make test_runner use hv1 direct; add phase2037 phi canaries; load modules.workspace exports for alias; update docs (phase-20.38, source extensions) and CURRENT_TASK

This commit is contained in:
nyash-codex
2025-11-04 16:33:04 +09:00
parent 5a1bb549a7
commit 30aa39f50b
270 changed files with 4320 additions and 758 deletions

View File

@ -1,5 +1,7 @@
/*!
* Using resolver utilities — static resolution line (SSOT + AST)
* Using resolver utilities — static resolution line (SSOT + AST) 📦
*
* 箱化モジュール化で綺麗綺麗になったにゃ!🎉
*
* Separation of concerns:
* - Static (using-time): Resolve packages/aliases from nyash.toml (SSOT),
@ -9,17 +11,40 @@
* fallback is disallowed in prod; builder must rewrite obj.method() to
* a function call.
*
* Modules:
* - strip: profile-aware resolution (`collect_using_and_strip`,
* `resolve_prelude_paths_profiled`) — single entrypoints used by all
* runner modes to avoid drift.
* 📦 箱化モジュール構造 (Box-First Architecture):
* - strip: Legacy functions (preserved for compatibility)
* - using_resolution: 🎯 UsingResolutionBox - using文解析専門家
* - prelude_manager: 📚 PreludeManagerBox - プレリュード統合専門家!
* - selfhost_pipeline: 🚀 SelfhostPipelineBox - パイプライン管理専門家!
* - 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;
// Public re-exports to preserve existing call sites
// 📦 箱化モジュールの公開にゃ!
pub use using_resolution::{
UsingResolutionBox,
UsingTarget,
UsingConfig,
};
pub use prelude_manager::{
PreludeManagerBox,
MergeStrategy,
MergeResult,
};
pub use selfhost_pipeline::{
SelfhostPipelineBox,
CompilationResult,
PipelineConfig,
};
// 🔧 Legacy functions (preserved for compatibility)
pub use strip::{
preexpand_at_local,
collect_using_and_strip,

View File

@ -0,0 +1,251 @@
//! Prelude Manager Box - 綺麗綺麗なプレリュード統合専門家!📦
//!
//! テキストマージとASTマージを分離して、
//! 保守性とテスト容易性を向上させるにゃ!
use crate::runner::NyashRunner;
use crate::runner::modes::common_util::resolve::using_resolution::UsingResolutionBox;
/// 📦 PreludeManagerBox - プレリュード統合の専門家!
///
/// テキストベースとASTベースの両方の統合を
/// 統一インターフェースで提供する箱にゃ!
pub struct PreludeManagerBox<'a> {
runner: &'a NyashRunner,
}
/// 🎯 MergeStrategy - 統合戦略!
#[derive(Debug, Clone)]
pub enum MergeStrategy {
/// 🚀 テキストベース統合(高速)
Text,
/// 🧠 ASTベース統合高機能
Ast,
}
/// 📊 MergeResult - 統合結果!
#[derive(Debug)]
pub struct MergeResult {
pub merged_content: String,
pub strategy: MergeStrategy,
pub prelude_count: usize,
pub total_bytes: usize,
}
impl<'a> PreludeManagerBox<'a> {
/// 🌟 新しいPreludeManagerBoxを作るにゃ
pub fn new(runner: &'a NyashRunner) -> Self {
Self { runner }
}
/// 🚀 テキストベース統合を実行するにゃ!
pub fn merge_text(
&self,
source: &str,
filename: &str,
prelude_paths: &[String],
) -> Result<MergeResult, String> {
let trace = std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1");
if prelude_paths.is_empty() {
return Ok(MergeResult {
merged_content: source.to_string(),
strategy: MergeStrategy::Text,
prelude_count: 0,
total_bytes: source.len(),
});
}
if trace {
crate::runner::trace::log(format!(
"[prelude/text] {} prelude files for '{}'",
prelude_paths.len(),
filename
));
}
// テキスト統合ロジック
let merged = self.build_text_merged(source, filename, prelude_paths, trace)?;
let total_bytes = merged.len();
Ok(MergeResult {
merged_content: merged,
strategy: MergeStrategy::Text,
prelude_count: prelude_paths.len(),
total_bytes,
})
}
/// 🧠 ASTベース統合を実行するにゃ
pub fn merge_ast(
&self,
source: &str,
filename: &str,
prelude_paths: &[String],
) -> Result<MergeResult, String> {
let trace = std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1");
if prelude_paths.is_empty() {
return Ok(MergeResult {
merged_content: source.to_string(),
strategy: MergeStrategy::Ast,
prelude_count: 0,
total_bytes: source.len(),
});
}
if trace {
crate::runner::trace::log(format!(
"[prelude/ast] {} prelude files for '{}'",
prelude_paths.len(),
filename
));
}
// TODO: AST統合ロジックをここに実装
// 今はテキスト統合にフォールバック
self.merge_text(source, filename, prelude_paths)
}
/// 🏗️ テキスト統合を組み立てるにゃ!
fn build_text_merged(
&self,
source: &str,
filename: &str,
prelude_paths: &[String],
trace: bool,
) -> Result<String, String> {
let mut merged = String::new();
// プレリュードをDFS順に追加
for (idx, path) in prelude_paths.iter().enumerate() {
let content = std::fs::read_to_string(path)
.map_err(|e| format!("using: failed to read '{}': {}", path, e))?;
// using行を除去して正規化
let using_resolver = UsingResolutionBox::new(&self.runner, path)?;
let (cleaned_raw, _nested) = self.collect_using_and_strip_internal(&content, path)?;
let cleaned = self.normalize_text_for_inline(&cleaned_raw);
if trace {
crate::runner::trace::log(format!(
"[prelude/text] [{}] '{}' ({} bytes)",
idx + 1,
path,
cleaned.len()
));
}
merged.push_str(&cleaned);
merged.push('\n');
}
// デバッグモードなら境界マーカーを追加
if std::env::var("NYASH_RESOLVE_SEAM_DEBUG").ok().as_deref() == Some("1") {
merged.push_str("\n/* --- using prelude/main boundary --- */\n\n");
}
// メインソースを正規化して追加
let cleaned_main = self.normalize_text_for_inline(source);
merged.push_str(&cleaned_main);
if trace {
crate::runner::trace::log(format!(
"[prelude/text] final merged: {} bytes ({} prelude + {} main)",
merged.len(),
merged.len() - cleaned_main.len(),
cleaned_main.len()
));
}
Ok(self.normalize_text_for_inline(&merged))
}
/// 🧹 using行を収集して除去するにゃ内部実装
fn collect_using_and_strip_internal(
&self,
code: &str,
filename: &str,
) -> Result<(String, Vec<String>), String> {
// 既存のcollect_using_and_strip関数を呼び出す
// TODO: 将来的にはUsingResolutionBox経由に置き換える
crate::runner::modes::common_util::resolve::strip::collect_using_and_strip(
&self.runner,
code,
filename,
)
}
/// 🔧 テキストを正規化するにゃ!
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 j < bytes.len() && bytes[j] == b'}' {
// `;` をドロップ
i += 1;
continue;
}
}
tmp.push(bytes[i] as char);
i += 1;
}
out = tmp;
}
// ファイル末尾に改行を追加
if !out.ends_with('\n') {
out.push('\n');
}
out
}
/// 📊 最適な統合戦略を選択するにゃ!
pub fn select_strategy(&self, prelude_count: usize) -> MergeStrategy {
// 環境変数でAST統合が強制されている場合はASTを選択
if crate::config::env::using_ast_enabled() {
return MergeStrategy::Ast;
}
// プレリュード数が多い場合はテキスト統合を選択(高速)
if prelude_count > 5 {
return MergeStrategy::Text;
}
// デフォルトはテキスト統合
MergeStrategy::Text
}
/// 🚀 自動戦略選択で統合を実行するにゃ!
pub fn merge_auto(
&self,
source: &str,
filename: &str,
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

@ -0,0 +1,189 @@
//! Selfhost Pipeline Box - 綺麗綺麗なセルフホストパイプライン専門家!📦
//!
//! セルフホストコンパイルの複雑な処理を箱に閉じ込めて、
//! 保守性とテスト容易性を向上させるにゃ!
use crate::runner::NyashRunner;
use crate::runner::modes::common_util::resolve::prelude_manager::{PreludeManagerBox, MergeStrategy};
/// 📦 SelfhostPipelineBox - セルフホストパイプラインの専門家!
///
/// コンパイラーパイプライン全体を管理する箱にゃ!
pub struct SelfhostPipelineBox<'a> {
runner: &'a NyashRunner,
prelude_manager: PreludeManagerBox<'a>,
}
/// 🎯 CompilationResult - コンパイル結果!
#[derive(Debug)]
pub struct CompilationResult {
pub success: bool,
pub final_code: String,
pub merge_strategy: MergeStrategy,
pub prelude_count: usize,
pub processing_time_ms: u64,
}
/// ⚙️ PipelineConfig - パイプライン設定!
#[derive(Debug, Clone)]
pub struct PipelineConfig {
pub enable_using: bool,
pub enable_ast_merge: bool,
pub trace_execution: bool,
pub debug_mode: bool,
}
impl<'a> SelfhostPipelineBox<'a> {
/// 🌟 新しいSelfhostPipelineBoxを作るにゃ
pub fn new(runner: &'a NyashRunner) -> Self {
let prelude_manager = PreludeManagerBox::new(runner);
Self {
runner,
prelude_manager,
}
}
/// 🚀 セルフホストパイプラインを実行するにゃ!
pub fn execute_pipeline(
&mut self,
code: &str,
filename: &str,
) -> Result<CompilationResult, String> {
let start_time = std::time::Instant::now();
let config = self.build_config();
// usingが無効ならそのまま返す
if !config.enable_using {
return Ok(CompilationResult {
success: true,
final_code: code.to_string(),
merge_strategy: MergeStrategy::Text, // デフォルト
prelude_count: 0,
processing_time_ms: start_time.elapsed().as_millis() as u64,
});
}
// 第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)?
} else {
self.prelude_manager.merge_text(&cleaned_main, filename, &prelude_paths)?
};
let processing_time = start_time.elapsed().as_millis() as u64;
Ok(CompilationResult {
success: true,
final_code: merge_result.merged_content,
merge_strategy: merge_result.strategy,
prelude_count: merge_result.prelude_count,
processing_time_ms: processing_time,
})
}
/// 📋 パイプライン設定を構築するにゃ!
fn build_config(&self) -> PipelineConfig {
PipelineConfig {
enable_using: crate::config::env::enable_using(),
enable_ast_merge: crate::config::env::using_ast_enabled(),
trace_execution: std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1"),
debug_mode: std::env::var("NYASH_RESOLVE_SEAM_DEBUG").ok().as_deref() == Some("1"),
}
}
/// 🔍 using文を収集して解決するにゃ
fn collect_and_resolve_using(
&mut self,
code: &str,
filename: &str,
) -> Result<(String, Vec<String>), String> {
// 既存のresolve_prelude_paths_profiledを使用
crate::runner::modes::common_util::resolve::strip::resolve_prelude_paths_profiled(
&self.runner,
code,
filename,
)
}
/// 📊 パイプライン統計を表示するにゃ!
pub fn print_pipeline_stats(&self, result: &CompilationResult) {
let strategy_str = match result.merge_strategy {
MergeStrategy::Text => "text",
MergeStrategy::Ast => "ast",
};
eprintln!(
"[selfhost-pipeline] ✅ Completed in {}ms (strategy: {}, preludes: {})",
result.processing_time_ms,
strategy_str,
result.prelude_count
);
}
/// 🚨 エラーハンドリングとフォールバックするにゃ!
pub fn handle_fallback(
&self,
error: &str,
original_code: &str,
filename: &str,
) -> CompilationResult {
eprintln!("[selfhost-pipeline] ⚠️ Error: {}", error);
eprintln!("[selfhost-pipeline] 🔄 Falling back to original code");
CompilationResult {
success: false,
final_code: original_code.to_string(),
merge_strategy: MergeStrategy::Text, // フォールバックはテキスト
prelude_count: 0,
processing_time_ms: 0,
}
}
/// 🧪 パイプラインを検証するにゃ!(テスト用)
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()
.filter(|line| line.trim().starts_with("using "))
.count();
if using_count > 0 {
// プレリュード解決を試みる
match crate::runner::modes::common_util::resolve::strip::resolve_prelude_paths_profiled(
&self.runner,
code,
filename,
) {
Ok((_, paths)) => {
if paths.is_empty() {
issues.push("using statements found but no preludes resolved".to_string());
}
}
Err(e) => {
issues.push(format!("using resolution failed: {}", e));
}
}
}
}
Ok(issues)
}
/// 📊 パフォーマンスプロファイリングするにゃ!
pub fn profile_pipeline(
&mut self,
code: &str,
filename: &str,
) -> Result<String, String> {
// プロファイル機能を実装(別途)
// TODO: プロファイル機能を追加
Err("Profiling not yet implemented".to_string())
}
}

View File

@ -230,6 +230,7 @@ pub fn collect_using_and_strip(
seen_aliases.insert(alias, (canon, line_no));
}
}
// push resolved file path for text-prelude merge
prelude_paths.push(out);
}
}
@ -476,6 +477,15 @@ pub fn parse_preludes_to_asts(
.map_err(|e| format!("using: error reading {}: {}", prelude_path, e))?;
let (clean_src, _nested) = collect_using_and_strip(runner, &src, prelude_path)?;
// Safety valve: do not attempt to parse .hako preludes as Nyash AST.
// Hako は別言語系のため、プレリュード統合はテキスト統合に一本化する。
if prelude_path.ends_with(".hako") {
if debug {
eprintln!("[strip-debug] Skipping AST parse for Hako prelude: {} (use text merge)", prelude_path);
}
continue;
}
// Debug: dump clean_src if NYASH_STRIP_DEBUG=1
if debug {
eprintln!("[strip-debug] [{}/{}] About to parse: {}", idx + 1, prelude_paths.len(), prelude_path);

View File

@ -0,0 +1,222 @@
//! Using Resolution Box - 綺麗綺麗なusing文解決専門家📦
//!
//! 巨大な `collect_using_and_strip` 関数を箱に分解して、
//! 責務を明確にしてテストしやすくするにゃ!
use crate::runner::NyashRunner;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
/// 📦 UsingResolutionBox - using文解決の専門家
///
/// using文の解析、パス解決、重複チェックを一手に引き受ける箱にゃ
pub struct UsingResolutionBox<'a> {
runner: &'a NyashRunner,
config: UsingConfig,
ctx_dir: Option<PathBuf>,
filename_canon: Option<PathBuf>,
inside_pkg: bool,
seen_paths: HashMap<String, (String, usize)>, // canon_path -> (alias/label, first_line)
seen_aliases: HashMap<String, (String, usize)>, // alias -> (canon_path, first_line)
}
/// 🎯 UsingTarget - 解析済みusing文の構造体にゃ
#[derive(Debug, Clone)]
pub struct UsingTarget {
pub original: String,
pub target: String,
pub target_unquoted: String,
pub alias: Option<String>,
pub line_no: usize,
pub is_path: bool,
}
/// ⚙️ UsingConfig - using解決の設定
#[derive(Debug)]
pub struct UsingConfig {
pub prod: bool,
pub strict: bool,
pub verbose: bool,
pub allow_file_using: bool,
}
impl<'a> UsingResolutionBox<'a> {
/// 🌟 新しいUsingResolutionBoxを作るにゃ
pub fn new(runner: &'a NyashRunner, filename: &str) -> Result<Self, String> {
let using_ctx = runner.init_using_context();
let config = UsingConfig {
prod: crate::config::env::using_is_prod(),
strict: std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1"),
verbose: crate::config::env::cli_verbose()
|| std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1"),
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;
if let Some(ref fc) = filename_canon {
for (_name, pkg) in &using_ctx.packages {
let base = Path::new(&pkg.path);
if let Ok(root) = std::fs::canonicalize(base) {
if fc.starts_with(&root) {
inside_pkg = true;
break;
}
}
}
}
Ok(Self {
runner,
config,
ctx_dir,
filename_canon,
inside_pkg,
seen_paths: HashMap::new(),
seen_aliases: HashMap::new(),
})
}
/// 🔍 using文を解析するにゃ
pub fn parse_using_line(&self, line: &str, line_no: usize) -> Option<UsingTarget> {
let t = line.trim_start();
if !t.starts_with("using ") {
return None;
}
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(),
Some(rest0[pos + 4..].trim().to_string()),
)
} else {
(rest0.to_string(), None)
};
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.packages.contains_key(&target_unquoted);
let is_path = if is_known_alias_or_module {
false
} else {
target.starts_with("./")
|| target.starts_with('/')
|| target.ends_with(".nyash")
|| target.ends_with(".hako")
};
Some(UsingTarget {
original: line.to_string(),
target,
target_unquoted,
alias,
line_no,
is_path,
})
}
/// 🚀 パスを解決するにゃ!
pub fn resolve_path(&self, target: &UsingTarget) -> Result<String, String> {
if !target.is_path {
return Err("Not a file path".to_string());
}
// ファイルusingチェック
if (self.config.prod || !self.config.allow_file_using) && !self.inside_pkg {
return Err(format!(
"{}:{}: using: file paths are disallowed in this profile. Add it to nyash.toml [using]/[modules] and reference by name: {}\n suggestions: using \"alias.name\" as Name | dev/test: set NYASH_PREINCLUDE=1 to expand includes ahead of VM\n docs: see docs/reference/using.md",
"filename", // TODO: 実際のファイル名を渡す
target.line_no,
target.target
));
}
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 {
let cand = dir.join(&p);
if cand.exists() {
p = cand;
}
}
// NYASH_ROOTも試す
if p.is_relative() {
if let Ok(root) = std::env::var("NYASH_ROOT") {
let cand = Path::new(&root).join(&p);
if cand.exists() {
p = cand;
}
}
}
}
p.to_str()
.ok_or_else(|| "Invalid path".to_string())
.map(|s| s.to_string())
}
/// 🛡️ 重複チェックするにゃ!
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();
// パスの重複チェック
if let Some((prev_alias, prev_line)) = self.seen_paths.get(&canon_str.to_string()) {
return Err(format!(
"{}:{}: using: duplicate target (first imported at {}:{})",
"filename", // TODO: 実際のファイル名を渡す
target.line_no,
prev_alias,
prev_line
));
}
// エイリアスの重複チェック
if let Some(ref alias_name) = target.alias {
if let Some((prev_path, prev_line)) = self.seen_aliases.get(alias_name) {
return Err(format!(
"{}:{}: using: duplicate alias '{}' (first used for {} at {})",
"filename", // TODO: 実際のファイル名を渡す
target.line_no,
alias_name,
prev_path,
prev_line
));
}
}
// 記録
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));
if let Some(ref alias_name) = target.alias {
self.seen_aliases.insert(alias_name.clone(), (resolved_path.to_string(), target.line_no));
}
Ok(())
}
/// 📊 設定を取得するにゃ!
pub fn config(&self) -> &UsingConfig {
&self.config
}
}