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:
@ -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,
|
||||
|
||||
251
src/runner/modes/common_util/resolve/prelude_manager.rs
Normal file
251
src/runner/modes/common_util/resolve/prelude_manager.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
189
src/runner/modes/common_util/resolve/selfhost_pipeline.rs
Normal file
189
src/runner/modes/common_util/resolve/selfhost_pipeline.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
222
src/runner/modes/common_util/resolve/using_resolution.rs
Normal file
222
src/runner/modes/common_util/resolve/using_resolution.rs
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user