vm(resolve): add text-based prelude merge for selfhost compilation
- Add merge_prelude_text() function for fast using system support - Implement normalize_text_for_inline() for parser robustness - Update selfhost.rs to use text-based merge when NYASH_USING_AST=1 - Add merge_prelude_text export to mod.rs - Improves Phase 15 selfhost compilation speed and reliability Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
@ -26,4 +26,5 @@ pub use strip::{
|
|||||||
resolve_prelude_paths_profiled,
|
resolve_prelude_paths_profiled,
|
||||||
parse_preludes_to_asts,
|
parse_preludes_to_asts,
|
||||||
merge_prelude_asts_with_main,
|
merge_prelude_asts_with_main,
|
||||||
|
merge_prelude_text,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -635,3 +635,112 @@ pub fn preexpand_at_local(src: &str) -> String {
|
|||||||
}
|
}
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Text-based prelude merge: simpler and faster than AST merge.
|
||||||
|
/// Recursively resolves using dependencies, strips using lines from each file,
|
||||||
|
/// and concatenates prelude text followed by main source text.
|
||||||
|
/// Returns merged source text ready for compilation.
|
||||||
|
pub fn merge_prelude_text(
|
||||||
|
runner: &NyashRunner,
|
||||||
|
source: &str,
|
||||||
|
filename: &str,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let trace = std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1");
|
||||||
|
|
||||||
|
// First pass: collect and resolve prelude paths
|
||||||
|
let (cleaned_main, prelude_paths) = resolve_prelude_paths_profiled(runner, source, filename)?;
|
||||||
|
|
||||||
|
if prelude_paths.is_empty() {
|
||||||
|
// No using statements, return original
|
||||||
|
return Ok(source.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if trace {
|
||||||
|
crate::runner::trace::log(format!(
|
||||||
|
"[using/text-merge] {} prelude files for '{}'",
|
||||||
|
prelude_paths.len(),
|
||||||
|
filename
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build merged text: preludes first, then main source
|
||||||
|
let mut merged = String::new();
|
||||||
|
|
||||||
|
// Add preludes in DFS order
|
||||||
|
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))?;
|
||||||
|
|
||||||
|
// Strip using lines from prelude and normalize
|
||||||
|
let (cleaned_raw, _nested) = collect_using_and_strip(runner, &content, path)?;
|
||||||
|
let cleaned = normalize_text_for_inline(&cleaned_raw);
|
||||||
|
|
||||||
|
if trace {
|
||||||
|
crate::runner::trace::log(format!(
|
||||||
|
"[using/text-merge] [{}] '{}' ({} bytes)",
|
||||||
|
idx + 1,
|
||||||
|
path,
|
||||||
|
cleaned.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
merged.push_str(&cleaned);
|
||||||
|
merged.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add boundary marker if debug mode
|
||||||
|
if std::env::var("NYASH_RESOLVE_SEAM_DEBUG").ok().as_deref() == Some("1") {
|
||||||
|
merged.push_str("\n/* --- using prelude/main boundary --- */\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add main source (already cleaned of using lines) and normalize
|
||||||
|
let cleaned_main_norm = normalize_text_for_inline(&cleaned_main);
|
||||||
|
merged.push_str(&cleaned_main_norm);
|
||||||
|
|
||||||
|
if trace {
|
||||||
|
crate::runner::trace::log(format!(
|
||||||
|
"[using/text-merge] final merged: {} bytes ({} prelude + {} main)",
|
||||||
|
merged.len(),
|
||||||
|
merged.len() - cleaned_main.len(),
|
||||||
|
cleaned_main.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(normalize_text_for_inline(&merged))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Minimal normalization to improve inline parser robustness.
|
||||||
|
/// - Normalize CRLF to LF
|
||||||
|
/// - Remove redundant semicolons before closing braces (`; }` → `}`)
|
||||||
|
/// - Ensure file ends with a newline
|
||||||
|
fn normalize_text_for_inline(s: &str) -> String {
|
||||||
|
let mut out = s.replace("\r\n", "\n").replace("\r", "\n");
|
||||||
|
// Remove `;` before `}` across line boundaries conservatively
|
||||||
|
// pattern: `;` followed by optional spaces/newlines then `}`
|
||||||
|
// Do a few passes to cover nested cases without regex
|
||||||
|
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';' {
|
||||||
|
// peek ahead skipping spaces/newlines
|
||||||
|
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'}' {
|
||||||
|
// drop ';' (do not advance j here)
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tmp.push(bytes[i] as char);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
out = tmp;
|
||||||
|
}
|
||||||
|
if !out.ends_with('\n') { out.push('\n'); }
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|||||||
@ -22,19 +22,33 @@ impl NyashRunner {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Optional Phase-15: strip `using` lines and register modules (same policy as execute_nyash_file)
|
// Optional Phase-15: using prelude merge (text-based for speed)
|
||||||
let mut code_ref: std::borrow::Cow<'_, str> = std::borrow::Cow::Borrowed(&code);
|
let mut code_ref: std::borrow::Cow<'_, str> = std::borrow::Cow::Borrowed(&code);
|
||||||
if crate::config::env::enable_using() {
|
if crate::config::env::enable_using() {
|
||||||
match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(self, &code, filename) {
|
let using_ast = crate::config::env::using_ast_enabled();
|
||||||
Ok((clean, paths)) => {
|
if using_ast {
|
||||||
if !paths.is_empty() && !crate::config::env::using_ast_enabled() {
|
// Text-based merge: faster for inline/selfhost execution
|
||||||
eprintln!("[ny-compiler] using: AST prelude merge is disabled in this profile. Enable NYASH_USING_AST=1 or remove 'using' lines.");
|
match crate::runner::modes::common_util::resolve::merge_prelude_text(self, &code, filename) {
|
||||||
|
Ok(merged) => {
|
||||||
|
code_ref = std::borrow::Cow::Owned(merged);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("[ny-compiler] using text merge error: {}", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
code_ref = std::borrow::Cow::Owned(clean);
|
|
||||||
// Selfhost compile path does not need to parse prelude ASTs here.
|
|
||||||
}
|
}
|
||||||
Err(e) => { eprintln!("[ny-compiler] {}", e); return false; }
|
} else {
|
||||||
|
// Legacy: strip only (no prelude merge)
|
||||||
|
match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(self, &code, filename) {
|
||||||
|
Ok((clean, paths)) => {
|
||||||
|
if !paths.is_empty() {
|
||||||
|
eprintln!("[ny-compiler] using: AST prelude merge is disabled in this profile. Enable NYASH_USING_AST=1 or remove 'using' lines.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
code_ref = std::borrow::Cow::Owned(clean);
|
||||||
|
}
|
||||||
|
Err(e) => { eprintln!("[ny-compiler] {}", e); return false; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user