From 2192d1829d5bb35f320981dc83c2668e8e41e653 Mon Sep 17 00:00:00 2001 From: Selfhosting Dev Date: Thu, 25 Sep 2025 10:38:06 +0900 Subject: [PATCH] using(ast): add AST prelude merge mode (NYASH_USING_AST=1); strip-only collector; combine Program ASTs; prep for parser fix. Also keep legacy inlining default. --- src/runner/modes/common.rs | 45 +++++++++-- src/runner/modes/common_util/resolve/strip.rs | 80 +++++++++++++++++++ 2 files changed, 120 insertions(+), 5 deletions(-) diff --git a/src/runner/modes/common.rs b/src/runner/modes/common.rs index 004fba5d..ee24513f 100644 --- a/src/runner/modes/common.rs +++ b/src/runner/modes/common.rs @@ -49,13 +49,36 @@ impl NyashRunner { println!("\nšŸš€ Parsing and executing...\n"); } - // Optional Phase-15: strip `using` lines (gate) for minimal acceptance + // Using handling: either strip+inline (legacy) or AST-based prelude merge (when NYASH_USING_AST=1) + let use_ast = std::env::var("NYASH_USING_AST").ok().as_deref() == Some("1"); let mut code_ref: &str = &code; let cleaned_code_owned; + let mut prelude_asts: Vec = Vec::new(); if crate::config::env::enable_using() { - match crate::runner::modes::common_util::resolve::strip_using_and_register(self, &code, filename) { - Ok(s) => { cleaned_code_owned = s; code_ref = &cleaned_code_owned; } - Err(e) => { eprintln!("āŒ {}", e); std::process::exit(1); } + if use_ast { + match crate::runner::modes::common_util::resolve::collect_using_and_strip(self, &code, filename) { + Ok((clean, paths)) => { + cleaned_code_owned = clean; code_ref = &cleaned_code_owned; + // Parse each prelude file into AST and store + for p in paths { + match std::fs::read_to_string(&p) { + Ok(src) => { + match NyashParser::parse_from_string(&src) { + Ok(ast) => prelude_asts.push(ast), + Err(e) => { eprintln!("āŒ Parse error in using prelude {}: {}", p, e); std::process::exit(1); } + } + } + Err(e) => { eprintln!("āŒ Error reading using prelude {}: {}", p, e); std::process::exit(1); } + } + } + } + Err(e) => { eprintln!("āŒ {}", e); std::process::exit(1); } + } + } else { + match crate::runner::modes::common_util::resolve::strip_using_and_register(self, &code, filename) { + Ok(s) => { cleaned_code_owned = s; code_ref = &cleaned_code_owned; } + Err(e) => { eprintln!("āŒ {}", e); std::process::exit(1); } + } } } // Optional dev sugar: @name[:T] = expr → local name[:T] = expr (line-head only) @@ -69,10 +92,22 @@ impl NyashRunner { // Parse the code with debug fuel limit let groups = self.config.as_groups(); eprintln!("šŸ” DEBUG: Starting parse with fuel: {:?}...", groups.debug.debug_fuel); - let ast = match NyashParser::parse_from_string_with_fuel(code_ref, groups.debug.debug_fuel) { + let main_ast = match NyashParser::parse_from_string_with_fuel(code_ref, groups.debug.debug_fuel) { Ok(ast) => { eprintln!("šŸ” DEBUG: Parse completed, AST created"); ast }, Err(e) => { eprintln!("āŒ Parse error: {}", e); process::exit(1); } }; + // When using AST prelude mode, combine prelude ASTs + main AST into one Program + let ast = if use_ast && !prelude_asts.is_empty() { + use nyash_rust::ast::ASTNode; + let mut combined: Vec = Vec::new(); + for a in prelude_asts { + if let ASTNode::Program { statements, .. } = a { combined.extend(statements); } + } + if let ASTNode::Program { statements, .. } = main_ast.clone() { + combined.extend(statements); + } + ASTNode::Program { statements: combined, span: nyash_rust::ast::Span::unknown() } + } else { main_ast }; // Stage-0: import loader (top-level only) — resolve path and register in modules registry if let nyash_rust::ast::ASTNode::Program { statements, .. } = &ast { diff --git a/src/runner/modes/common_util/resolve/strip.rs b/src/runner/modes/common_util/resolve/strip.rs index 1ce2de21..53ba7025 100644 --- a/src/runner/modes/common_util/resolve/strip.rs +++ b/src/runner/modes/common_util/resolve/strip.rs @@ -423,6 +423,86 @@ pub fn strip_using_and_register( Ok(preexpand_at_local(&combined)) } +/// Collect using targets and strip using lines, without inlining. +/// Returns (cleaned_source, prelude_paths) where prelude_paths are resolved file paths +/// to be parsed separately and AST-merged (when NYASH_USING_AST=1). +pub fn collect_using_and_strip( + runner: &NyashRunner, + code: &str, + filename: &str, +) -> Result<(String, Vec), String> { + if !crate::config::env::enable_using() { + return Ok((code.to_string(), Vec::new())); + } + let using_ctx = runner.init_using_context(); + let strict = std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1"); + let verbose = crate::config::env::cli_verbose() + || std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1"); + let ctx_dir = std::path::Path::new(filename).parent(); + + let mut out = String::with_capacity(code.len()); + let mut prelude_paths: Vec = Vec::new(); + for line in code.lines() { + let t = line.trim_start(); + if t.starts_with("using ") { + 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 is_path = target.starts_with('"') || target.starts_with("./") || target.starts_with('/') || target.ends_with(".nyash"); + if is_path { + let path = target.trim_matches('"').to_string(); + // Resolve relative to current file dir + let mut p = std::path::PathBuf::from(&path); + if p.is_relative() { + if let Some(dir) = ctx_dir { let cand = dir.join(&p); if cand.exists() { p = cand; } } + } + prelude_paths.push(p.to_string_lossy().to_string()); + continue; + } + // Resolve namespaces/packages + match crate::runner::pipeline::resolve_using_target( + &target, + false, + &using_ctx.pending_modules, + &using_ctx.using_paths, + &using_ctx.aliases, + &using_ctx.packages, + ctx_dir, + strict, + verbose, + ) { + Ok(value) => { + // Only file paths are candidates for AST prelude merge + if value.ends_with(".nyash") || value.contains('/') || value.contains('\\') { + // Resolve relative + let mut p = std::path::PathBuf::from(&value); + if p.is_relative() { + if let Some(dir) = ctx_dir { let cand = dir.join(&p); if cand.exists() { p = cand; } } + } + prelude_paths.push(p.to_string_lossy().to_string()); + } + } + Err(e) => return Err(format!("using: {}", e)), + } + continue; + } + out.push_str(line); + out.push('\n'); + } + // Optional prelude boundary comment (helps manual inspection; parser ignores comments) + if std::env::var("NYASH_RESOLVE_SEAM_DEBUG").ok().as_deref() == Some("1") { + let mut with_marker = String::with_capacity(out.len() + 64); + with_marker.push_str("\n/* --- using boundary (AST) --- */\n"); + with_marker.push_str(&out); + out = with_marker; + } + Ok((out, prelude_paths)) +} + /// Pre-expand line-head `@name[: Type] = expr` into `local name[: Type] = expr`. /// Minimal, safe, no semantics change. Applies only at line head (after spaces/tabs). pub fn preexpand_at_local(src: &str) -> String {