selfhost/runtime: Stage 0-1 runner + MIR JSON loader (summary) with trace; compiler: scopebox/loopform prepass wiring (flags, child args); libs: add P1 standard boxes (console/string/array/map) as thin wrappers; runner: pass --box-pref via env; ops_calls dispatcher skeleton; docs: selfhost executor roadmap + scopebox/loopform notes; smokes: selfhost runner + identity prepasses; CURRENT_TASK: update plan and box lib schedule
This commit is contained in:
@ -29,6 +29,7 @@ mod jit_direct;
|
||||
mod selfhost;
|
||||
mod tasks;
|
||||
mod trace;
|
||||
mod plugins;
|
||||
|
||||
// v2 plugin system imports
|
||||
use nyash_rust::runner_plugin_init;
|
||||
@ -37,7 +38,7 @@ use nyash_rust::runtime;
|
||||
|
||||
/// Resolve a using target according to priority: modules > relative > using-paths
|
||||
/// Returns Ok(resolved_path_or_token). On strict mode, ambiguous matches cause error.
|
||||
use pipeline::resolve_using_target;
|
||||
// use pipeline::resolve_using_target; // resolved within helpers; avoid unused warning
|
||||
|
||||
/// Main execution coordinator
|
||||
pub struct NyashRunner {
|
||||
@ -47,6 +48,7 @@ pub struct NyashRunner {
|
||||
/// Minimal task runner: read nyash.toml [env] and [tasks], run the named task via shell
|
||||
use tasks::run_named_task;
|
||||
|
||||
#[cfg(not(feature = "jit-direct-only"))]
|
||||
impl NyashRunner {
|
||||
/// Create a new runner with the given configuration
|
||||
pub fn new(config: CliConfig) -> Self {
|
||||
@ -55,402 +57,8 @@ impl NyashRunner {
|
||||
|
||||
/// Run Nyash based on the configuration
|
||||
pub fn run(&self) {
|
||||
// Macro sandbox child mode: --macro-expand-child <file>
|
||||
if let Some(ref macro_file) = self.config.macro_expand_child {
|
||||
crate::runner::modes::macro_child::run_macro_child(macro_file);
|
||||
return;
|
||||
}
|
||||
// Build system (MVP): nyash --build <nyash.toml>
|
||||
let groups = self.config.as_groups();
|
||||
if let Some(cfg_path) = groups.build.path.clone() {
|
||||
if let Err(e) = self.run_build_mvp(&cfg_path) {
|
||||
eprintln!("❌ build error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Using/module overrides pre-processing
|
||||
let mut using_ctx = self.init_using_context();
|
||||
let mut pending_using: Vec<(String, Option<String>)> = Vec::new();
|
||||
// CLI --using SPEC entries (SPEC: 'ns', 'ns as Alias', '"path" as Alias')
|
||||
for spec in &groups.input.cli_usings {
|
||||
let s = spec.trim();
|
||||
if s.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let (target, alias) = if let Some(pos) = s.find(" as ") {
|
||||
(
|
||||
s[..pos].trim().to_string(),
|
||||
Some(s[pos + 4..].trim().to_string()),
|
||||
)
|
||||
} else {
|
||||
(s.to_string(), None)
|
||||
};
|
||||
// Normalize quotes for path
|
||||
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();
|
||||
let name = alias.clone().unwrap_or_else(|| {
|
||||
std::path::Path::new(&path)
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("module")
|
||||
.to_string()
|
||||
});
|
||||
pending_using.push((name, Some(path)));
|
||||
} else {
|
||||
pending_using.push((target, alias));
|
||||
}
|
||||
}
|
||||
for (ns, path) in using_ctx.pending_modules.iter() {
|
||||
let sb = crate::box_trait::StringBox::new(path.clone());
|
||||
crate::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
||||
}
|
||||
// Stage-1: Optional dependency tree bridge (log-only)
|
||||
if let Ok(dep_path) = std::env::var("NYASH_DEPS_JSON") {
|
||||
match std::fs::read_to_string(&dep_path) {
|
||||
Ok(s) => {
|
||||
let bytes = s.as_bytes().len();
|
||||
// Try to extract quick hints without failing
|
||||
let mut root_info = String::new();
|
||||
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&s) {
|
||||
if let Some(r) = v.get("root_path").and_then(|x| x.as_str()) {
|
||||
root_info = format!(" root='{}'", r);
|
||||
}
|
||||
}
|
||||
crate::cli_v!(
|
||||
"[deps] loaded {} bytes from{} {}",
|
||||
bytes,
|
||||
if root_info.is_empty() { "" } else { ":" },
|
||||
root_info
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
crate::cli_v!("[deps] read error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase-15: JSON IR v0 bridge (stdin/file)
|
||||
if self.try_run_json_v0_pipe() {
|
||||
return;
|
||||
}
|
||||
// Run named task from nyash.toml (MVP)
|
||||
if let Some(task) = groups.run_task.clone() {
|
||||
if let Err(e) = run_named_task(&task) {
|
||||
eprintln!("❌ Task error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Verbose CLI flag maps to env for downstream helpers/scripts
|
||||
if groups.debug.cli_verbose {
|
||||
std::env::set_var("NYASH_CLI_VERBOSE", "1");
|
||||
}
|
||||
// GC mode forwarding: map CLI --gc to NYASH_GC_MODE for downstream runtimes
|
||||
if let Some(ref m) = groups.gc_mode {
|
||||
if !m.trim().is_empty() {
|
||||
std::env::set_var("NYASH_GC_MODE", m);
|
||||
}
|
||||
}
|
||||
// Script-level env directives (special comments) — parse early
|
||||
// Supported:
|
||||
// // @env KEY=VALUE
|
||||
// // @jit-debug (preset: exec, threshold=1, events+trace)
|
||||
// // @plugin-builtins (NYASH_USE_PLUGIN_BUILTINS=1)
|
||||
if let Some(ref filename) = groups.input.file {
|
||||
if let Ok(code) = fs::read_to_string(filename) {
|
||||
// Apply script-level directives and lint
|
||||
let strict_fields =
|
||||
std::env::var("NYASH_FIELDS_TOP_STRICT").ok().as_deref() == Some("1");
|
||||
if let Err(e) = cli_directives::apply_cli_directives_from_source(
|
||||
&code,
|
||||
strict_fields,
|
||||
groups.debug.cli_verbose,
|
||||
) {
|
||||
eprintln!("❌ Lint/Directive error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Env overrides for using rules
|
||||
// Merge late env overrides (if any)
|
||||
if let Ok(paths) = std::env::var("NYASH_USING_PATH") {
|
||||
for p in paths.split(':') {
|
||||
let p = p.trim();
|
||||
if !p.is_empty() {
|
||||
using_ctx.using_paths.push(p.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(mods) = std::env::var("NYASH_MODULES") {
|
||||
for ent in mods.split(',') {
|
||||
if let Some((k, v)) = ent.split_once('=') {
|
||||
let k = k.trim();
|
||||
let v = v.trim();
|
||||
if !k.is_empty() && !v.is_empty() {
|
||||
using_ctx
|
||||
.pending_modules
|
||||
.push((k.to_string(), v.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply pending modules to registry as StringBox (path or ns token)
|
||||
for (ns, path) in using_ctx.pending_modules.iter() {
|
||||
let sb = nyash_rust::box_trait::StringBox::new(path.clone());
|
||||
nyash_rust::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
||||
}
|
||||
// Resolve pending using with clear precedence and ambiguity handling
|
||||
let strict = std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1");
|
||||
let verbose = crate::config::env::cli_verbose();
|
||||
let ctx = std::path::Path::new(filename).parent();
|
||||
for (ns, alias) in pending_using.iter() {
|
||||
let value = match resolve_using_target(
|
||||
ns,
|
||||
false,
|
||||
&using_ctx.pending_modules,
|
||||
&using_ctx.using_paths,
|
||||
&using_ctx.aliases,
|
||||
ctx,
|
||||
strict,
|
||||
verbose,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("❌ using: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let sb = nyash_rust::box_trait::StringBox::new(value.clone());
|
||||
nyash_rust::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
||||
if let Some(a) = alias {
|
||||
let sb2 = nyash_rust::box_trait::StringBox::new(value);
|
||||
nyash_rust::runtime::modules_registry::set(a.clone(), Box::new(sb2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If strict mode requested via env, ensure handle-only shim behavior is enabled
|
||||
if std::env::var("NYASH_JIT_STRICT").ok().as_deref() == Some("1") {
|
||||
if std::env::var("NYASH_JIT_ARGS_HANDLE_ONLY").ok().is_none() {
|
||||
std::env::set_var("NYASH_JIT_ARGS_HANDLE_ONLY", "1");
|
||||
}
|
||||
// Enforce JIT-only by default in strict mode unless explicitly overridden
|
||||
if std::env::var("NYASH_JIT_ONLY").ok().is_none() {
|
||||
std::env::set_var("NYASH_JIT_ONLY", "1");
|
||||
}
|
||||
}
|
||||
|
||||
// 🏭 Phase 9.78b: Initialize unified registry
|
||||
runtime::init_global_unified_registry();
|
||||
|
||||
// Try to initialize BID plugins from nyash.toml (best-effort)
|
||||
// Allow disabling during snapshot/CI via NYASH_DISABLE_PLUGINS=1
|
||||
if std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") {
|
||||
runner_plugin_init::init_bid_plugins();
|
||||
// Build BoxIndex after plugin host is initialized
|
||||
crate::runner::box_index::refresh_box_index();
|
||||
}
|
||||
// Allow interpreter to create plugin-backed boxes via unified registry
|
||||
// Opt-in by default for FileBox/TOMLBox which are required by ny-config and similar tools.
|
||||
if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().is_none() {
|
||||
std::env::set_var("NYASH_USE_PLUGIN_BUILTINS", "1");
|
||||
}
|
||||
// Merge FileBox,TOMLBox with defaults if present
|
||||
let mut override_types: Vec<String> =
|
||||
if let Ok(list) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES") {
|
||||
list.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect()
|
||||
} else {
|
||||
vec!["ArrayBox".into(), "MapBox".into()]
|
||||
};
|
||||
for t in ["FileBox", "TOMLBox"] {
|
||||
if !override_types.iter().any(|x| x == t) {
|
||||
override_types.push(t.into());
|
||||
}
|
||||
}
|
||||
std::env::set_var("NYASH_PLUGIN_OVERRIDE_TYPES", override_types.join(","));
|
||||
|
||||
// Opt-in: load Ny script plugins listed in nyash.toml [ny_plugins]
|
||||
if groups.load_ny_plugins
|
||||
|| std::env::var("NYASH_LOAD_NY_PLUGINS").ok().as_deref() == Some("1")
|
||||
{
|
||||
if let Ok(text) = std::fs::read_to_string("nyash.toml") {
|
||||
if let Ok(doc) = toml::from_str::<toml::Value>(&text) {
|
||||
if let Some(np) = doc.get("ny_plugins") {
|
||||
let mut list: Vec<String> = Vec::new();
|
||||
if let Some(arr) = np.as_array() {
|
||||
for v in arr {
|
||||
if let Some(s) = v.as_str() {
|
||||
list.push(s.to_string());
|
||||
}
|
||||
}
|
||||
} else if let Some(tbl) = np.as_table() {
|
||||
for (_k, v) in tbl {
|
||||
if let Some(s) = v.as_str() {
|
||||
list.push(s.to_string());
|
||||
} else if let Some(arr) = v.as_array() {
|
||||
for e in arr {
|
||||
if let Some(s) = e.as_str() {
|
||||
list.push(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !list.is_empty() {
|
||||
let list_only =
|
||||
std::env::var("NYASH_NY_PLUGINS_LIST_ONLY").ok().as_deref()
|
||||
== Some("1");
|
||||
println!("🧩 Ny script plugins ({}):", list.len());
|
||||
for p in list {
|
||||
if list_only {
|
||||
println!(" • {}", p);
|
||||
continue;
|
||||
}
|
||||
// Execute each script best-effort via interpreter
|
||||
match std::fs::read_to_string(&p) {
|
||||
Ok(code) => {
|
||||
match nyash_rust::parser::NyashParser::parse_from_string(
|
||||
&code,
|
||||
) {
|
||||
Ok(ast) => {
|
||||
let mut interpreter =
|
||||
nyash_rust::interpreter::NyashInterpreter::new(
|
||||
);
|
||||
match interpreter.execute(ast) {
|
||||
Ok(_) => println!("[ny_plugins] {}: OK", p),
|
||||
Err(e) => {
|
||||
println!(
|
||||
"[ny_plugins] {}: FAIL ({})",
|
||||
p, e
|
||||
);
|
||||
// continue to next
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("[ny_plugins] {}: FAIL (parse: {})", p, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("[ny_plugins] {}: FAIL (read: {})", p, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: enable VM stats via CLI flags
|
||||
if groups.backend.vm_stats {
|
||||
std::env::set_var("NYASH_VM_STATS", "1");
|
||||
}
|
||||
if groups.backend.vm_stats_json {
|
||||
// Prefer explicit JSON flag over any default
|
||||
std::env::set_var("NYASH_VM_STATS_JSON", "1");
|
||||
}
|
||||
// Optional: JIT controls via CLI flags (centralized)
|
||||
{
|
||||
// CLI opt-in for JSONL events
|
||||
if groups.backend.jit.events {
|
||||
std::env::set_var("NYASH_JIT_EVENTS", "1");
|
||||
}
|
||||
if groups.backend.jit.events_compile {
|
||||
std::env::set_var("NYASH_JIT_EVENTS_COMPILE", "1");
|
||||
}
|
||||
if groups.backend.jit.events_runtime {
|
||||
std::env::set_var("NYASH_JIT_EVENTS_RUNTIME", "1");
|
||||
}
|
||||
if let Some(ref p) = groups.backend.jit.events_path {
|
||||
std::env::set_var("NYASH_JIT_EVENTS_PATH", p);
|
||||
}
|
||||
let mut jc = nyash_rust::jit::config::JitConfig::from_env();
|
||||
jc.exec |= groups.backend.jit.exec;
|
||||
jc.stats |= groups.backend.jit.stats;
|
||||
jc.stats_json |= groups.backend.jit.stats_json;
|
||||
jc.dump |= groups.backend.jit.dump;
|
||||
if groups.backend.jit.threshold.is_some() {
|
||||
jc.threshold = groups.backend.jit.threshold;
|
||||
}
|
||||
jc.phi_min |= groups.backend.jit.phi_min;
|
||||
jc.hostcall |= groups.backend.jit.hostcall;
|
||||
jc.handle_debug |= groups.backend.jit.handle_debug;
|
||||
jc.native_f64 |= groups.backend.jit.native_f64;
|
||||
jc.native_bool |= groups.backend.jit.native_bool;
|
||||
// If observability is enabled and no threshold is provided, force threshold=1 so lowering runs and emits events
|
||||
let events_on = std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1")
|
||||
|| std::env::var("NYASH_JIT_EVENTS_COMPILE").ok().as_deref() == Some("1")
|
||||
|| std::env::var("NYASH_JIT_EVENTS_RUNTIME").ok().as_deref() == Some("1");
|
||||
if events_on && jc.threshold.is_none() {
|
||||
jc.threshold = Some(1);
|
||||
}
|
||||
if groups.backend.jit.only {
|
||||
std::env::set_var("NYASH_JIT_ONLY", "1");
|
||||
}
|
||||
// Apply runtime capability probe (e.g., disable b1 ABI if unsupported)
|
||||
let caps = nyash_rust::jit::config::probe_capabilities();
|
||||
jc = nyash_rust::jit::config::apply_runtime_caps(jc, caps);
|
||||
// Optional DOT emit via CLI (ensures dump is on when path specified)
|
||||
if let Some(path) = &groups.emit.emit_cfg {
|
||||
std::env::set_var("NYASH_JIT_DOT", path);
|
||||
jc.dump = true;
|
||||
}
|
||||
// Persist to env (CLI parity) and set as current
|
||||
jc.apply_env();
|
||||
nyash_rust::jit::config::set_current(jc.clone());
|
||||
}
|
||||
// Architectural pivot: JIT is compiler-only (EXE/AOT). Ensure VM runtime does not dispatch to JIT
|
||||
// unless explicitly requested via independent JIT mode, or when emitting AOT objects.
|
||||
if !groups.compile_native && !groups.backend.jit.direct {
|
||||
// When AOT object emission is requested, allow JIT to run for object generation
|
||||
let aot_obj = std::env::var("NYASH_AOT_OBJECT_OUT").ok();
|
||||
if aot_obj.is_none() || aot_obj.as_deref() == Some("") {
|
||||
// Force-disable runtime JIT execution path for VM/Interpreter flows
|
||||
std::env::set_var("NYASH_JIT_EXEC", "0");
|
||||
}
|
||||
}
|
||||
// Benchmark mode - can run without a file
|
||||
if groups.benchmark {
|
||||
println!("📊 Nyash Performance Benchmark Suite");
|
||||
println!("====================================");
|
||||
println!("Running {} iterations per test...", groups.iterations);
|
||||
println!();
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
{
|
||||
self.execute_benchmark_mode();
|
||||
return;
|
||||
}
|
||||
#[cfg(not(feature = "vm-legacy"))]
|
||||
{
|
||||
eprintln!(
|
||||
"❌ Benchmark mode requires VM backend. Rebuild with --features vm-legacy."
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref filename) = groups.input.file {
|
||||
// Independent JIT direct mode (no VM execute path)
|
||||
if groups.backend.jit.direct {
|
||||
self.run_file_jit_direct(filename);
|
||||
return;
|
||||
}
|
||||
// Delegate file-mode execution to modes::common dispatcher
|
||||
self.run_file(filename);
|
||||
} else {
|
||||
demos::run_all_demos();
|
||||
}
|
||||
// New behavior-preserving delegator
|
||||
self.run_refactored();
|
||||
}
|
||||
|
||||
// init_bid_plugins moved to runner_plugin_init.rs
|
||||
@ -466,6 +74,223 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "jit-direct-only"))]
|
||||
impl NyashRunner {
|
||||
/// New behavior-preserving refactor of run(): structured into smaller helpers
|
||||
fn run_refactored(&self) {
|
||||
// Early: macro child
|
||||
if let Some(ref macro_file) = self.config.macro_expand_child {
|
||||
crate::runner::modes::macro_child::run_macro_child(macro_file);
|
||||
return;
|
||||
}
|
||||
let groups = self.config.as_groups();
|
||||
// Early: build
|
||||
if let Some(cfg_path) = groups.build.path.clone() {
|
||||
if let Err(e) = self.run_build_mvp(&cfg_path) {
|
||||
eprintln!("❌ build error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Preprocess usings and directives (includes dep-tree log)
|
||||
self.preprocess_usings_and_directives(&groups);
|
||||
// JSON v0 bridge
|
||||
if self.try_run_json_v0_pipe() { return; }
|
||||
// Named task
|
||||
if let Some(task) = groups.run_task.clone() {
|
||||
if let Err(e) = run_named_task(&task) {
|
||||
eprintln!("❌ Task error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Common env + runtime/plugins
|
||||
self.apply_common_env(&groups);
|
||||
self.init_runtime_and_plugins(&groups);
|
||||
// Backend config + policy
|
||||
self.configure_backend(&groups);
|
||||
self.enforce_runtime_jit_policy(&groups);
|
||||
// Benchmark
|
||||
if self.maybe_run_benchmark(&groups) { return; }
|
||||
// Dispatch
|
||||
self.dispatch_entry(&groups);
|
||||
}
|
||||
|
||||
// ---- Helpers (extracted from original run) ----
|
||||
|
||||
fn preprocess_usings_and_directives(&self, groups: &crate::cli::CliGroups) {
|
||||
use pipeline::resolve_using_target;
|
||||
// Initialize UsingContext (defaults + nyash.toml + env)
|
||||
let mut using_ctx = self.init_using_context();
|
||||
// Collect CLI --using SPEC into (target, alias)
|
||||
let mut pending_using: Vec<(String, Option<String>)> = Vec::new();
|
||||
for spec in &groups.input.cli_usings {
|
||||
let s = spec.trim();
|
||||
if s.is_empty() { continue; }
|
||||
let (target, alias) = if let Some(pos) = s.find(" as ") {
|
||||
(s[..pos].trim().to_string(), Some(s[pos + 4..].trim().to_string()))
|
||||
} else { (s.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();
|
||||
let name = alias.clone().unwrap_or_else(|| {
|
||||
std::path::Path::new(&path).file_stem().and_then(|s| s.to_str()).unwrap_or("module").to_string()
|
||||
});
|
||||
pending_using.push((name, Some(path)));
|
||||
} else {
|
||||
pending_using.push((target, alias));
|
||||
}
|
||||
}
|
||||
// Apply pending modules (from context) to registry as StringBox
|
||||
for (ns, path) in using_ctx.pending_modules.iter() {
|
||||
let sb = crate::box_trait::StringBox::new(path.clone());
|
||||
crate::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
||||
}
|
||||
// Optional dependency tree bridge (log-only)
|
||||
if let Ok(dep_path) = std::env::var("NYASH_DEPS_JSON") {
|
||||
match std::fs::read_to_string(&dep_path) {
|
||||
Ok(s) => {
|
||||
let bytes = s.as_bytes().len();
|
||||
let mut root_info = String::new();
|
||||
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&s) {
|
||||
if let Some(r) = v.get("root_path").and_then(|x| x.as_str()) {
|
||||
root_info = format!(" root='{}'", r);
|
||||
}
|
||||
}
|
||||
crate::cli_v!("[deps] loaded {} bytes from{} {}", bytes, if root_info.is_empty() { "" } else { ":" }, root_info);
|
||||
}
|
||||
Err(e) => { crate::cli_v!("[deps] read error: {}", e); }
|
||||
}
|
||||
}
|
||||
// If a file is provided, apply script-level directives and late using/env merges
|
||||
if let Some(ref filename) = groups.input.file {
|
||||
if let Ok(code) = fs::read_to_string(filename) {
|
||||
// Apply directives and lint
|
||||
let strict_fields = std::env::var("NYASH_FIELDS_TOP_STRICT").ok().as_deref() == Some("1");
|
||||
if let Err(e) = cli_directives::apply_cli_directives_from_source(&code, strict_fields, groups.debug.cli_verbose) {
|
||||
eprintln!("❌ Lint/Directive error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
// Late env overrides (paths/modules)
|
||||
if let Ok(paths) = std::env::var("NYASH_USING_PATH") {
|
||||
for p in paths.split(':') { let p = p.trim(); if !p.is_empty() { using_ctx.using_paths.push(p.to_string()); } }
|
||||
}
|
||||
if let Ok(mods) = std::env::var("NYASH_MODULES") {
|
||||
for ent in mods.split(',') {
|
||||
if let Some((k, v)) = ent.split_once('=') {
|
||||
let k = k.trim(); let v = v.trim();
|
||||
if !k.is_empty() && !v.is_empty() { using_ctx.pending_modules.push((k.to_string(), v.to_string())); }
|
||||
}
|
||||
}
|
||||
}
|
||||
// Re-apply pending modules in case env added more (idempotent)
|
||||
for (ns, path) in using_ctx.pending_modules.iter() {
|
||||
let sb = nyash_rust::box_trait::StringBox::new(path.clone());
|
||||
nyash_rust::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
||||
}
|
||||
// Resolve CLI --using entries against context and register values (with aliasing)
|
||||
let strict = std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1");
|
||||
let verbose = crate::config::env::cli_verbose();
|
||||
let ctx = std::path::Path::new(filename).parent();
|
||||
for (ns, alias) in pending_using.iter() {
|
||||
let value = match resolve_using_target(ns, false, &using_ctx.pending_modules, &using_ctx.using_paths, &using_ctx.aliases, ctx, strict, verbose) {
|
||||
Ok(v) => v,
|
||||
Err(e) => { eprintln!("❌ using: {}", e); std::process::exit(1); }
|
||||
};
|
||||
let sb = nyash_rust::box_trait::StringBox::new(value.clone());
|
||||
nyash_rust::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
||||
if let Some(a) = alias {
|
||||
let sb2 = nyash_rust::box_trait::StringBox::new(value);
|
||||
nyash_rust::runtime::modules_registry::set(a.clone(), Box::new(sb2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply early environment toggles that affect CLI behavior and VM stats.
|
||||
/// Side effects: sets `NYASH_CLI_VERBOSE`, `NYASH_GC_MODE` when specified by CLI groups.
|
||||
fn apply_common_env(&self, groups: &crate::cli::CliGroups) {
|
||||
if groups.debug.cli_verbose { std::env::set_var("NYASH_CLI_VERBOSE", "1"); }
|
||||
if let Some(ref m) = groups.gc_mode { if !m.trim().is_empty() { std::env::set_var("NYASH_GC_MODE", m); } }
|
||||
}
|
||||
|
||||
// init_runtime_and_plugins moved to runner/plugins.rs
|
||||
|
||||
/// Configure backend knobs (VM/JIT) from CLI flags and env, merging with runtime capabilities.
|
||||
/// Side effects: writes environment variables for flags and applies JIT config via nyash_rust::jit::config.
|
||||
fn configure_backend(&self, groups: &crate::cli::CliGroups) {
|
||||
if groups.backend.vm_stats { std::env::set_var("NYASH_VM_STATS", "1"); }
|
||||
if groups.backend.vm_stats_json { std::env::set_var("NYASH_VM_STATS_JSON", "1"); }
|
||||
{
|
||||
if groups.backend.jit.events { std::env::set_var("NYASH_JIT_EVENTS", "1"); }
|
||||
if groups.backend.jit.events_compile { std::env::set_var("NYASH_JIT_EVENTS_COMPILE", "1"); }
|
||||
if groups.backend.jit.events_runtime { std::env::set_var("NYASH_JIT_EVENTS_RUNTIME", "1"); }
|
||||
if let Some(ref p) = groups.backend.jit.events_path { std::env::set_var("NYASH_JIT_EVENTS_PATH", p); }
|
||||
let mut jc = nyash_rust::jit::config::JitConfig::from_env();
|
||||
jc.exec |= groups.backend.jit.exec;
|
||||
jc.stats |= groups.backend.jit.stats;
|
||||
jc.stats_json |= groups.backend.jit.stats_json;
|
||||
jc.dump |= groups.backend.jit.dump;
|
||||
if groups.backend.jit.threshold.is_some() { jc.threshold = groups.backend.jit.threshold; }
|
||||
jc.phi_min |= groups.backend.jit.phi_min;
|
||||
jc.hostcall |= groups.backend.jit.hostcall;
|
||||
jc.handle_debug |= groups.backend.jit.handle_debug;
|
||||
jc.native_f64 |= groups.backend.jit.native_f64;
|
||||
jc.native_bool |= groups.backend.jit.native_bool;
|
||||
let events_on = std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1")
|
||||
|| std::env::var("NYASH_JIT_EVENTS_COMPILE").ok().as_deref() == Some("1")
|
||||
|| std::env::var("NYASH_JIT_EVENTS_RUNTIME").ok().as_deref() == Some("1");
|
||||
if events_on && jc.threshold.is_none() { jc.threshold = Some(1); }
|
||||
if groups.backend.jit.only { std::env::set_var("NYASH_JIT_ONLY", "1"); }
|
||||
let caps = nyash_rust::jit::config::probe_capabilities();
|
||||
jc = nyash_rust::jit::config::apply_runtime_caps(jc, caps);
|
||||
if let Some(path) = &groups.emit.emit_cfg { std::env::set_var("NYASH_JIT_DOT", path); jc.dump = true; }
|
||||
jc.apply_env();
|
||||
nyash_rust::jit::config::set_current(jc.clone());
|
||||
}
|
||||
if std::env::var("NYASH_JIT_STRICT").ok().as_deref() == Some("1") {
|
||||
if std::env::var("NYASH_JIT_ARGS_HANDLE_ONLY").ok().is_none() { std::env::set_var("NYASH_JIT_ARGS_HANDLE_ONLY", "1"); }
|
||||
if std::env::var("NYASH_JIT_ONLY").ok().is_none() { std::env::set_var("NYASH_JIT_ONLY", "1"); }
|
||||
}
|
||||
}
|
||||
|
||||
/// Enforce runtime policy for JIT execution when AOT object output is absent.
|
||||
/// Side effects: may set `NYASH_JIT_EXEC=0` when policy requires.
|
||||
fn enforce_runtime_jit_policy(&self, groups: &crate::cli::CliGroups) {
|
||||
if !groups.compile_native && !groups.backend.jit.direct {
|
||||
let aot_obj = std::env::var("NYASH_AOT_OBJECT_OUT").ok();
|
||||
if aot_obj.is_none() || aot_obj.as_deref() == Some("") {
|
||||
std::env::set_var("NYASH_JIT_EXEC", "0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Optionally run the benchmark suite and exit, depending on CLI flags.
|
||||
/// Returns true when a benchmark run occurred.
|
||||
fn maybe_run_benchmark(&self, groups: &crate::cli::CliGroups) -> bool {
|
||||
if groups.benchmark {
|
||||
println!("📊 Nyash Performance Benchmark Suite");
|
||||
println!("====================================");
|
||||
println!("Running {} iterations per test...", groups.iterations);
|
||||
println!();
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
{ self.execute_benchmark_mode(); return true; }
|
||||
#[cfg(not(feature = "vm-legacy"))]
|
||||
{ eprintln!("❌ Benchmark mode requires VM backend. Rebuild with --features vm-legacy."); std::process::exit(1); }
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Final dispatch to selected execution mode (file/JIT-direct or demos).
|
||||
fn dispatch_entry(&self, groups: &crate::cli::CliGroups) {
|
||||
if let Some(ref filename) = groups.input.file {
|
||||
if groups.backend.jit.direct { self.run_file_jit_direct(filename); return; }
|
||||
self.run_file(filename);
|
||||
} else { demos::run_all_demos(); }
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashRunner {
|
||||
/// Run a file through independent JIT engine (no VM execute loop)
|
||||
fn run_file_jit_direct(&self, filename: &str) {
|
||||
|
||||
@ -2,7 +2,6 @@ use super::super::NyashRunner;
|
||||
use crate::runner::json_v0_bridge;
|
||||
use nyash_rust::{parser::NyashParser, interpreter::NyashInterpreter};
|
||||
// Use the library crate's plugin init module rather than the bin crate root
|
||||
use nyash_rust::runner_plugin_init;
|
||||
use std::{fs, process};
|
||||
use std::io::Read;
|
||||
use std::process::Stdio;
|
||||
@ -15,113 +14,7 @@ use crate::cli_v;
|
||||
// (moved) suggest_in_base is now in runner/pipeline.rs
|
||||
|
||||
impl NyashRunner {
|
||||
/// File-mode dispatcher (thin wrapper around backend/mode selection)
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn run_file_legacy(&self, filename: &str) {
|
||||
// Phase-15.3: Ny compiler MVP (Ny -> JSON v0) behind env gate
|
||||
if std::env::var("NYASH_USE_NY_COMPILER").ok().as_deref() == Some("1") {
|
||||
if self.try_run_selfhost_pipeline(filename) {
|
||||
return;
|
||||
} else if crate::config::env::cli_verbose() {
|
||||
eprintln!("[ny-compiler] fallback to default path (MVP unavailable for this input)");
|
||||
}
|
||||
}
|
||||
// Direct v0 bridge when requested via CLI/env
|
||||
let groups = self.config.as_groups();
|
||||
let use_ny_parser = groups.parser.parser_ny || std::env::var("NYASH_USE_NY_PARSER").ok().as_deref() == Some("1");
|
||||
if use_ny_parser {
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(content) => content,
|
||||
Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); }
|
||||
};
|
||||
match json_v0_bridge::parse_source_v0_to_module(&code) {
|
||||
Ok(module) => {
|
||||
if crate::config::env::cli_verbose() {
|
||||
println!("🚀 Nyash MIR Interpreter - (parser=ny) Executing file: {} 🚀", filename);
|
||||
}
|
||||
self.execute_mir_module(&module);
|
||||
return;
|
||||
}
|
||||
Err(e) => { eprintln!("❌ Direct bridge parse error: {}", e); process::exit(1); }
|
||||
}
|
||||
}
|
||||
// AST dump mode
|
||||
if groups.debug.dump_ast {
|
||||
println!("🧠 Nyash AST Dump - Processing file: {}", filename);
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(content) => content,
|
||||
Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); }
|
||||
};
|
||||
let ast = match NyashParser::parse_from_string(&code) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); }
|
||||
};
|
||||
println!("{:#?}", ast);
|
||||
return;
|
||||
}
|
||||
|
||||
// MIR dump/verify
|
||||
if groups.debug.dump_mir || groups.debug.verify_mir {
|
||||
crate::cli_v!("🚀 Nyash MIR Compiler - Processing file: {} 🚀", filename);
|
||||
self.execute_mir_mode(filename);
|
||||
return;
|
||||
}
|
||||
|
||||
// WASM / AOT (feature-gated)
|
||||
if groups.compile_wasm {
|
||||
#[cfg(feature = "wasm-backend")]
|
||||
{ self.execute_wasm_mode(filename); return; }
|
||||
#[cfg(not(feature = "wasm-backend"))]
|
||||
{ eprintln!("❌ WASM backend not available. Please rebuild with: cargo build --features wasm-backend"); process::exit(1); }
|
||||
}
|
||||
if groups.compile_native {
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
{ self.execute_aot_mode(filename); return; }
|
||||
#[cfg(not(feature = "cranelift-jit"))]
|
||||
{ eprintln!("❌ Native AOT compilation requires Cranelift. Please rebuild: cargo build --features cranelift-jit"); process::exit(1); }
|
||||
}
|
||||
|
||||
// Backend selection
|
||||
match groups.backend.backend.as_str() {
|
||||
"mir" => {
|
||||
crate::cli_v!("🚀 Nyash MIR Interpreter - Executing file: {} 🚀", filename);
|
||||
self.execute_mir_interpreter_mode(filename);
|
||||
}
|
||||
"vm" => {
|
||||
crate::cli_v!("🚀 Nyash VM Backend - Executing file: {} 🚀", filename);
|
||||
self.execute_vm_mode(filename);
|
||||
}
|
||||
"cranelift" => {
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
{
|
||||
crate::cli_v!("⚙️ Nyash Cranelift JIT - Executing file: {}", filename);
|
||||
self.execute_cranelift_mode(filename);
|
||||
}
|
||||
#[cfg(not(feature = "cranelift-jit"))]
|
||||
{
|
||||
eprintln!("❌ Cranelift backend not available. Please rebuild with: cargo build --features cranelift-jit");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
"llvm" => {
|
||||
crate::cli_v!("⚡ Nyash LLVM Backend - Executing file: {} ⚡", filename);
|
||||
self.execute_llvm_mode(filename);
|
||||
}
|
||||
_ => {
|
||||
if cli_verbose() {
|
||||
println!("🦀 Nyash Rust Implementation - Executing file: {} 🦀", filename);
|
||||
let groups = self.config.as_groups();
|
||||
if let Some(fuel) = groups.debug.debug_fuel {
|
||||
println!("🔥 Debug fuel limit: {} iterations", fuel);
|
||||
} else {
|
||||
println!("🔥 Debug fuel limit: unlimited");
|
||||
}
|
||||
println!("====================================================");
|
||||
}
|
||||
self.execute_nyash_file(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
// legacy run_file_legacy removed (was commented out)
|
||||
|
||||
/// Helper: run PyVM harness over a MIR module, returning the exit code
|
||||
fn run_pyvm_harness(&self, module: &nyash_rust::mir::MirModule, tag: &str) -> Result<i32, String> {
|
||||
@ -137,356 +30,15 @@ impl NyashRunner {
|
||||
/// Phase-15.3: Attempt Ny compiler pipeline (Ny -> JSON v0 via Ny program), then execute MIR
|
||||
pub(crate) fn try_run_ny_compiler_pipeline(&self, filename: &str) -> bool {
|
||||
// Delegate to centralized selfhost pipeline to avoid drift
|
||||
return self.try_run_selfhost_pipeline(filename);
|
||||
use std::io::Write;
|
||||
// Read input source
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(c) => c,
|
||||
Err(e) => { eprintln!("[ny-compiler] read error: {}", e); return false; }
|
||||
};
|
||||
// Optional Phase-15: strip `using` lines and register modules (same policy as execute_nyash_file)
|
||||
let enable_using = crate::config::env::enable_using();
|
||||
let mut code_ref: std::borrow::Cow<'_, str> = std::borrow::Cow::Borrowed(&code);
|
||||
if enable_using {
|
||||
let mut out = String::with_capacity(code.len());
|
||||
let mut used_names: Vec<(String, Option<String>)> = Vec::new();
|
||||
for line in code.lines() {
|
||||
let t = line.trim_start();
|
||||
if t.starts_with("using ") {
|
||||
cli_v!("[using] stripped(line→selfhost): {}", line);
|
||||
let rest0 = t.strip_prefix("using ").unwrap().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();
|
||||
let name = alias.clone().unwrap_or_else(|| {
|
||||
std::path::Path::new(&path).file_stem().and_then(|s| s.to_str()).unwrap_or("module").to_string()
|
||||
});
|
||||
used_names.push((name, Some(path)));
|
||||
} else {
|
||||
used_names.push((target, alias));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
out.push_str(line);
|
||||
out.push('\n');
|
||||
}
|
||||
// Register modules into minimal registry with best-effort path resolution
|
||||
for (ns_or_alias, alias_or_path) in used_names {
|
||||
if let Some(path) = alias_or_path {
|
||||
let sb = crate::box_trait::StringBox::new(path);
|
||||
crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb));
|
||||
} else {
|
||||
let rel = format!("apps/{}.nyash", ns_or_alias.replace('.', "/"));
|
||||
let exists = std::path::Path::new(&rel).exists();
|
||||
let path_or_ns = if exists { rel } else { ns_or_alias.clone() };
|
||||
let sb = crate::box_trait::StringBox::new(path_or_ns);
|
||||
crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb));
|
||||
}
|
||||
}
|
||||
code_ref = std::borrow::Cow::Owned(out);
|
||||
}
|
||||
|
||||
// Write to tmp/ny_parser_input.ny (as expected by Ny parser v0), unless forced to reuse existing tmp
|
||||
let use_tmp_only = crate::config::env::ny_compiler_use_tmp_only();
|
||||
let tmp_dir = std::path::Path::new("tmp");
|
||||
if let Err(e) = std::fs::create_dir_all(tmp_dir) {
|
||||
eprintln!("[ny-compiler] mkdir tmp failed: {}", e);
|
||||
return false;
|
||||
}
|
||||
let tmp_path = tmp_dir.join("ny_parser_input.ny");
|
||||
if !use_tmp_only {
|
||||
match std::fs::File::create(&tmp_path) {
|
||||
Ok(mut f) => {
|
||||
if let Err(e) = f.write_all(code_ref.as_bytes()) {
|
||||
eprintln!("[ny-compiler] write tmp failed: {}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Err(e) => { eprintln!("[ny-compiler] open tmp failed: {}", e); return false; }
|
||||
}
|
||||
}
|
||||
// EXE-first: if requested, try external parser EXE (nyash_compiler)
|
||||
if crate::config::env::use_ny_compiler_exe() {
|
||||
// Resolve parser EXE path
|
||||
let exe_path = if let Some(p) = crate::config::env::ny_compiler_exe_path() {
|
||||
std::path::PathBuf::from(p)
|
||||
} else {
|
||||
let mut p = std::path::PathBuf::from("dist/nyash_compiler");
|
||||
#[cfg(windows)]
|
||||
{ p.push("nyash_compiler.exe"); }
|
||||
#[cfg(not(windows))]
|
||||
{ p.push("nyash_compiler"); }
|
||||
if !p.exists() {
|
||||
// Try PATH
|
||||
if let Ok(w) = which::which("nyash_compiler") { w } else { p }
|
||||
} else { p }
|
||||
};
|
||||
if exe_path.exists() {
|
||||
let mut cmd = std::process::Command::new(&exe_path);
|
||||
// Prefer passing the original filename directly (parser EXE accepts positional path)
|
||||
cmd.arg(filename);
|
||||
// Gates
|
||||
if crate::config::env::ny_compiler_min_json() { cmd.arg("--min-json"); }
|
||||
if crate::config::env::selfhost_read_tmp() { cmd.arg("--read-tmp"); }
|
||||
if let Some(raw) = crate::config::env::ny_compiler_child_args() { for tok in raw.split_whitespace() { cmd.arg(tok); } }
|
||||
let timeout_ms: u64 = crate::config::env::ny_compiler_timeout_ms();
|
||||
let out = match super::common_util::io::spawn_with_timeout(cmd, timeout_ms) {
|
||||
Ok(o) => o,
|
||||
Err(e) => { eprintln!("[ny-compiler] exe spawn failed: {}", e); return false; }
|
||||
};
|
||||
if out.timed_out {
|
||||
let head = String::from_utf8_lossy(&out.stdout).chars().take(200).collect::<String>();
|
||||
eprintln!("[ny-compiler] exe timeout after {} ms; stdout(head)='{}'", timeout_ms, head.replace('\n', "\\n"));
|
||||
return false;
|
||||
}
|
||||
let stdout = match String::from_utf8(out.stdout) { Ok(s) => s, Err(_) => String::new() };
|
||||
let mut json_line = String::new();
|
||||
for line in stdout.lines() { let t = line.trim(); if t.starts_with('{') && t.contains("\"version\"") && t.contains("\"kind\"") { json_line = t.to_string(); break; } }
|
||||
if json_line.is_empty() {
|
||||
if crate::config::env::cli_verbose() {
|
||||
let head: String = stdout.chars().take(200).collect();
|
||||
let errh: String = String::from_utf8_lossy(&out.stderr).chars().take(200).collect();
|
||||
eprintln!("[ny-compiler] exe produced no JSON; stdout(head)='{}' stderr(head)='{}'", head.replace('\n', "\\n"), errh.replace('\n', "\\n"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Parse JSON v0 → MIR module
|
||||
match json_v0_bridge::parse_json_v0_to_module(&json_line) {
|
||||
Ok(module) => {
|
||||
println!("🚀 Ny compiler EXE path (ny→json_v0) ON");
|
||||
json_v0_bridge::maybe_dump_mir(&module);
|
||||
let emit_only = crate::config::env::ny_compiler_emit_only();
|
||||
if emit_only {
|
||||
return false;
|
||||
} else {
|
||||
// Prefer PyVM when requested (reference semantics), regardless of BoxCall presence
|
||||
let prefer_pyvm = crate::config::env::vm_use_py();
|
||||
if prefer_pyvm {
|
||||
if let Ok(py3) = which::which("python3") {
|
||||
let runner = std::path::Path::new("tools/pyvm_runner.py");
|
||||
if runner.exists() {
|
||||
let tmp_dir = std::path::Path::new("tmp");
|
||||
let _ = std::fs::create_dir_all(tmp_dir);
|
||||
let mir_json_path = tmp_dir.join("nyash_pyvm_mir.json");
|
||||
if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(&module, &mir_json_path) {
|
||||
eprintln!("❌ PyVM MIR JSON emit error: {}", e);
|
||||
return true; // prevent double-run fallback
|
||||
}
|
||||
let code = self.run_pyvm_harness(&module, "exe").unwrap_or(1);
|
||||
println!("Result: {}", code);
|
||||
std::process::exit(code);
|
||||
} else {
|
||||
eprintln!("❌ PyVM runner not found: {}", runner.display());
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else {
|
||||
eprintln!("❌ python3 not found in PATH. Install Python 3 to use PyVM.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
// Default: execute via built-in MIR interpreter
|
||||
self.execute_mir_module(&module);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Err(e) => { eprintln!("[ny-compiler] JSON parse failed (exe): {}", e); return false; }
|
||||
}
|
||||
} else {
|
||||
if crate::config::env::cli_verbose() { eprintln!("[ny-compiler] exe not found at {}", exe_path.display()); }
|
||||
}
|
||||
}
|
||||
|
||||
// Locate current exe to invoke Ny VM for the Ny parser program
|
||||
let exe = match std::env::current_exe() {
|
||||
Ok(p) => p,
|
||||
Err(e) => { eprintln!("[ny-compiler] current_exe failed: {}", e); return false; }
|
||||
};
|
||||
// Select selfhost compiler entry
|
||||
// NYASH_NY_COMPILER_PREF=legacy|new|auto (default auto: prefer new when exists)
|
||||
let cand_new = std::path::Path::new("apps/selfhost/compiler/compiler.nyash");
|
||||
let cand_old = std::path::Path::new("apps/selfhost/parser/ny_parser_v0/main.nyash");
|
||||
let pref = std::env::var("NYASH_NY_COMPILER_PREF").ok();
|
||||
let parser_prog = match pref.as_deref() {
|
||||
Some("legacy") => cand_old,
|
||||
Some("new") => cand_new,
|
||||
_ => if cand_new.exists() { cand_new } else { cand_old },
|
||||
};
|
||||
if !parser_prog.exists() { eprintln!("[ny-compiler] compiler program not found: {}", parser_prog.display()); return false; }
|
||||
let mut cmd = std::process::Command::new(exe);
|
||||
cmd.arg("--backend").arg("vm").arg(parser_prog);
|
||||
// Gate: pass script args to child parser program
|
||||
// - NYASH_NY_COMPILER_MIN_JSON=1 → "-- --min-json"
|
||||
// - NYASH_SELFHOST_READ_TMP=1 → "-- --read-tmp"
|
||||
// - NYASH_NY_COMPILER_CHILD_ARGS: additional raw args (split by whitespace)
|
||||
let min_json = std::env::var("NYASH_NY_COMPILER_MIN_JSON").ok().unwrap_or_else(|| "0".to_string());
|
||||
let mut inserted_sep = false;
|
||||
if min_json == "1" {
|
||||
cmd.arg("--").arg("--min-json");
|
||||
inserted_sep = true;
|
||||
}
|
||||
if std::env::var("NYASH_SELFHOST_READ_TMP").ok().as_deref() == Some("1") {
|
||||
if !inserted_sep { cmd.arg("--"); inserted_sep = true; }
|
||||
cmd.arg("--read-tmp");
|
||||
}
|
||||
if let Ok(raw) = std::env::var("NYASH_NY_COMPILER_CHILD_ARGS") {
|
||||
if !inserted_sep { cmd.arg("--"); inserted_sep = true; }
|
||||
for tok in raw.split_whitespace() { cmd.arg(tok); }
|
||||
}
|
||||
// Propagate minimal env; prefer stdlib over plugins in child for stable stdout
|
||||
cmd.env_remove("NYASH_USE_NY_COMPILER");
|
||||
cmd.env_remove("NYASH_CLI_VERBOSE");
|
||||
cmd.env("NYASH_DISABLE_PLUGINS", "1");
|
||||
cmd.env_remove("NYASH_USE_PLUGIN_BUILTINS");
|
||||
// Suppress parent runner's result printing in child
|
||||
cmd.env("NYASH_JSON_ONLY", "1");
|
||||
// Prefer PyVM in child to ensure println/externcall are printed to stdout deterministically
|
||||
cmd.env("NYASH_VM_USE_PY", "1");
|
||||
// Propagate optional gates to child (if present)
|
||||
if let Ok(v) = std::env::var("NYASH_JSON_INCLUDE_USINGS") { cmd.env("NYASH_JSON_INCLUDE_USINGS", v); }
|
||||
if let Ok(v) = std::env::var("NYASH_ENABLE_USING") { cmd.env("NYASH_ENABLE_USING", v); }
|
||||
if let Ok(v) = std::env::var("NYASH_ENABLE_USING") { cmd.env("NYASH_ENABLE_USING", v); }
|
||||
// Child timeout guard (Hotfix for potential infinite loop in child Ny parser)
|
||||
// Config: NYASH_NY_COMPILER_TIMEOUT_MS (default 2000ms)
|
||||
let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS")
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<u64>().ok())
|
||||
.unwrap_or(2000);
|
||||
let out = match super::common_util::io::spawn_with_timeout(cmd, timeout_ms) {
|
||||
Ok(o) => o,
|
||||
Err(e) => { eprintln!("[ny-compiler] spawn failed: {}", e); return false; }
|
||||
};
|
||||
if out.timed_out {
|
||||
let head = String::from_utf8_lossy(&out.stdout).chars().take(200).collect::<String>();
|
||||
eprintln!("[ny-compiler] child timeout after {} ms; stdout(head)='{}'", timeout_ms, head.replace('\n', "\\n"));
|
||||
}
|
||||
let stdout = match String::from_utf8(out.stdout.clone()) { Ok(s) => s, Err(_) => String::new() };
|
||||
if timed_out {
|
||||
// Fall back path will be taken below when json_line remains empty
|
||||
} else if let Ok(s) = String::from_utf8(err_buf.clone()) {
|
||||
// If the child exited non-zero and printed stderr, surface it and fallback
|
||||
// We cannot easily access ExitStatus here after try_wait loop; rely on JSON detection path.
|
||||
if s.trim().len() > 0 && crate::config::env::cli_verbose() {
|
||||
eprintln!("[ny-compiler] parser stderr:\n{}", s);
|
||||
}
|
||||
}
|
||||
let mut json_line = String::new();
|
||||
for line in stdout.lines() {
|
||||
let t = line.trim();
|
||||
if t.starts_with('{') && t.contains("\"version\"") && t.contains("\"kind\"") { json_line = t.to_string(); break; }
|
||||
}
|
||||
if json_line.is_empty() {
|
||||
// Fallback: try Python MVP parser to produce JSON v0 from the same tmp source (unless skipped).
|
||||
if crate::config::env::cli_verbose() {
|
||||
let head: String = stdout.chars().take(200).collect();
|
||||
cli_v!("[ny-compiler] JSON not found in child stdout (head): {}", head.replace('\\n', "\\n"));
|
||||
cli_v!("[ny-compiler] falling back to tools/ny_parser_mvp.py for this input");
|
||||
}
|
||||
if std::env::var("NYASH_NY_COMPILER_SKIP_PY").ok().as_deref() != Some("1") {
|
||||
let py = which::which("python3").ok();
|
||||
if let Some(py3) = py {
|
||||
let script = std::path::Path::new("tools/ny_parser_mvp.py");
|
||||
if script.exists() {
|
||||
let out2 = std::process::Command::new(py3)
|
||||
.arg(script)
|
||||
.arg(tmp_path.as_os_str())
|
||||
.output();
|
||||
match out2 {
|
||||
Ok(o2) if o2.status.success() => {
|
||||
if let Ok(s2) = String::from_utf8(o2.stdout) {
|
||||
// pick the first JSON-ish line
|
||||
for line in s2.lines() {
|
||||
let t = line.trim();
|
||||
if t.starts_with('{') && t.contains("\"version\"") && t.contains("\"kind\"") { json_line = t.to_string(); break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(o2) => {
|
||||
let msg = String::from_utf8_lossy(&o2.stderr);
|
||||
eprintln!("[ny-compiler] python parser failed: {}", msg);
|
||||
}
|
||||
Err(e2) => {
|
||||
eprintln!("[ny-compiler] spawn python3 failed: {}", e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
} }
|
||||
if json_line.is_empty() { return false; }
|
||||
}
|
||||
// Parse JSON v0 → MIR module
|
||||
match json_v0_bridge::parse_json_v0_to_module(&json_line) {
|
||||
Ok(module) => {
|
||||
let emit_only_default = "1".to_string();
|
||||
let emit_only = if emit_only_default == "1" { true } else { crate::config::env::ny_compiler_emit_only() };
|
||||
println!("🚀 Ny compiler MVP (ny→json_v0) path ON");
|
||||
json_v0_bridge::maybe_dump_mir(&module);
|
||||
if emit_only {
|
||||
// Do not execute; fall back to default path to keep final Result unaffected (Stage‑1 policy)
|
||||
false
|
||||
} else {
|
||||
// Prefer PyVM when requested (reference semantics)
|
||||
let prefer_pyvm = crate::config::env::vm_use_py();
|
||||
if prefer_pyvm {
|
||||
if let Ok(py3) = which::which("python3") {
|
||||
let runner = std::path::Path::new("tools/pyvm_runner.py");
|
||||
if runner.exists() {
|
||||
let tmp_dir = std::path::Path::new("tmp");
|
||||
let _ = std::fs::create_dir_all(tmp_dir);
|
||||
let mir_json_path = tmp_dir.join("nyash_pyvm_mir.json");
|
||||
if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(&module, &mir_json_path) {
|
||||
eprintln!("❌ PyVM MIR JSON emit error: {}", e);
|
||||
return true; // prevent double-run fallback
|
||||
}
|
||||
if crate::config::env::cli_verbose() {
|
||||
eprintln!("[ny-compiler] using PyVM (mvp) → {}", mir_json_path.display());
|
||||
}
|
||||
// Determine entry function (prefer Main.main; top-level main only if allowed)
|
||||
let allow_top = crate::config::env::entry_allow_toplevel_main();
|
||||
let entry = if module.functions.contains_key("Main.main") {
|
||||
"Main.main"
|
||||
} else if allow_top && module.functions.contains_key("main") {
|
||||
"main"
|
||||
} else if module.functions.contains_key("main") {
|
||||
eprintln!("[entry] Warning: using top-level 'main' without explicit allow; set NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 to silence.");
|
||||
"main"
|
||||
} else {
|
||||
"Main.main"
|
||||
};
|
||||
let code = self.run_pyvm_harness(&module, "mvp").unwrap_or(1);
|
||||
println!("Result: {}", code);
|
||||
std::process::exit(code);
|
||||
} else {
|
||||
eprintln!("❌ PyVM runner not found: {}", runner.display());
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else {
|
||||
eprintln!("❌ python3 not found in PATH. Install Python 3 to use PyVM.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
// Default: execute via MIR interpreter
|
||||
self.execute_mir_module(&module);
|
||||
true
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[ny-compiler] JSON parse failed: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
self.try_run_selfhost_pipeline(filename)
|
||||
}
|
||||
|
||||
/// Execute Nyash file with interpreter (common helper)
|
||||
pub(crate) fn execute_nyash_file(&self, filename: &str) {
|
||||
let quiet_pipe = std::env::var("NYASH_JSON_ONLY").ok().as_deref() == Some("1");
|
||||
// Ensure plugin host and provider mappings are initialized (idempotent)
|
||||
if std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") {
|
||||
// Call via lib crate to avoid referring to the bin crate root
|
||||
runner_plugin_init::init_bid_plugins();
|
||||
}
|
||||
// Ensure runtime and plugins are initialized via unified helper (idempotent)
|
||||
let groups = self.config.as_groups();
|
||||
self.init_runtime_and_plugins(&groups);
|
||||
// Read the file
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(content) => content,
|
||||
|
||||
12
src/runner/modes/common_util/resolve/mod.rs
Normal file
12
src/runner/modes/common_util/resolve/mod.rs
Normal file
@ -0,0 +1,12 @@
|
||||
/*!
|
||||
* Using resolver utilities (split)
|
||||
* - strip: remove `using` lines, inline modules, register aliases/modules
|
||||
* - seam: seam logging and optional brace-fix at join points
|
||||
*/
|
||||
|
||||
pub mod strip;
|
||||
pub mod seam;
|
||||
|
||||
// Public re-exports to preserve existing call sites
|
||||
pub use strip::{strip_using_and_register, preexpand_at_local};
|
||||
|
||||
84
src/runner/modes/common_util/resolve/seam.rs
Normal file
84
src/runner/modes/common_util/resolve/seam.rs
Normal file
@ -0,0 +1,84 @@
|
||||
/// 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; }
|
||||
let tail = inlined_text
|
||||
.chars()
|
||||
.rev()
|
||||
.take(120)
|
||||
.collect::<String>()
|
||||
.chars()
|
||||
.rev()
|
||||
.collect::<String>();
|
||||
eprintln!(
|
||||
"[using][seam][inlined] {} tail=<<<{}>>>",
|
||||
path_key,
|
||||
tail.replace('\n', "\\n")
|
||||
);
|
||||
}
|
||||
|
||||
/// 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; }
|
||||
let tail = prelude_clean
|
||||
.chars()
|
||||
.rev()
|
||||
.take(160)
|
||||
.collect::<String>()
|
||||
.chars()
|
||||
.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"));
|
||||
}
|
||||
|
||||
/// Apply optional seam safety: append missing '}' for unmatched '{' in prelude
|
||||
/// When `trace` is true, emits a short note with delta count.
|
||||
pub fn fix_prelude_braces_if_enabled(prelude_clean: &str, combined: &mut String, trace: bool) {
|
||||
if std::env::var("NYASH_RESOLVE_FIX_BRACES").ok().as_deref() != Some("1") {
|
||||
return;
|
||||
}
|
||||
// compute { } delta ignoring strings and comments
|
||||
let mut delta: i32 = 0;
|
||||
let mut it = prelude_clean.chars().peekable();
|
||||
let mut in_str = false;
|
||||
let mut in_sl = false;
|
||||
let mut in_ml = false;
|
||||
while let Some(c) = it.next() {
|
||||
if in_sl {
|
||||
if c == '\n' { in_sl = false; }
|
||||
continue;
|
||||
}
|
||||
if in_ml {
|
||||
if c == '*' {
|
||||
if let Some('/') = it.peek().copied() {
|
||||
it.next();
|
||||
in_ml = false;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if in_str {
|
||||
if c == '\\' { it.next(); continue; }
|
||||
if c == '"' { in_str = false; }
|
||||
continue;
|
||||
}
|
||||
if c == '"' { in_str = true; continue; }
|
||||
if c == '/' {
|
||||
match it.peek().copied() {
|
||||
Some('/') => { in_sl = true; it.next(); continue; }
|
||||
Some('*') => { in_ml = true; it.next(); continue; }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if c == '{' { delta += 1; }
|
||||
if c == '}' { delta -= 1; }
|
||||
}
|
||||
if delta > 0 {
|
||||
if trace { eprintln!("[using][seam] fix: appending {} '}}' before body", delta); }
|
||||
for _ in 0..delta {
|
||||
combined.push('}');
|
||||
combined.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -19,8 +19,7 @@ pub fn strip_using_and_register(
|
||||
let dedup_fn = std::env::var("NYASH_RESOLVE_DEDUP_FN").ok().as_deref() == Some("1");
|
||||
let seam_dbg = std::env::var("NYASH_RESOLVE_SEAM_DEBUG").ok().as_deref() == Some("1");
|
||||
let mut cmd = std::process::Command::new("python3");
|
||||
cmd.arg("tools/using_combine.py")
|
||||
.arg("--entry").arg(filename);
|
||||
cmd.arg("tools/using_combine.py").arg("--entry").arg(filename);
|
||||
if fix_braces { cmd.arg("--fix-braces"); }
|
||||
if dedup_box { cmd.arg("--dedup-box"); }
|
||||
if dedup_fn { cmd.arg("--dedup-fn"); }
|
||||
@ -35,11 +34,10 @@ pub fn strip_using_and_register(
|
||||
return Err(format!("using combiner failed: {}", err));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(format!("using combiner spawn error: {}", e));
|
||||
}
|
||||
Err(e) => return Err(format!("using combiner spawn error: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn strip_and_inline(
|
||||
runner: &NyashRunner,
|
||||
code: &str,
|
||||
@ -57,21 +55,12 @@ pub fn strip_using_and_register(
|
||||
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");
|
||||
} 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();
|
||||
let name = alias.clone().unwrap_or_else(|| {
|
||||
std::path::Path::new(&path)
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("module")
|
||||
.to_string()
|
||||
std::path::Path::new(&path).file_stem().and_then(|s| s.to_str()).unwrap_or("module").to_string()
|
||||
});
|
||||
used.push((name, Some(path)));
|
||||
} else {
|
||||
@ -98,8 +87,8 @@ pub fn strip_using_and_register(
|
||||
}
|
||||
for (ns, alias_opt) in used {
|
||||
// Two forms:
|
||||
// - using path "..." [as Alias] → handled earlier (stored as (name, Some(path)))
|
||||
// - using namespace.with.dots [as Alias] → resolve ns → register alias → inline
|
||||
// - using path "..." [as Alias]
|
||||
// - using namespace.with.dots [as Alias]
|
||||
let resolved_path = if let Some(alias) = alias_opt {
|
||||
// alias case: resolve namespace to a concrete path
|
||||
let mut found: Option<String> = using_ctx
|
||||
@ -109,39 +98,7 @@ pub fn strip_using_and_register(
|
||||
.map(|(_, p)| p.clone());
|
||||
if trace {
|
||||
if let Some(f) = &found {
|
||||
eprintln!("[using] hit modules: {} -> {}", ns, f);
|
||||
} else {
|
||||
eprintln!("[using] miss modules: {}", ns);
|
||||
}
|
||||
}
|
||||
if found.is_none() {
|
||||
if let Ok(text) = std::fs::read_to_string("nyash.toml") {
|
||||
if let Ok(doc) = toml::from_str::<toml::Value>(&text) {
|
||||
if let Some(mut cur) = doc.get("modules").and_then(|v| v.as_table()) {
|
||||
let mut segs = ns.split('.').peekable();
|
||||
let mut hit: Option<String> = None;
|
||||
while let Some(seg) = segs.next() {
|
||||
if let Some(next) = cur.get(seg) {
|
||||
if let Some(t) = next.as_table() {
|
||||
cur = t;
|
||||
continue;
|
||||
}
|
||||
if segs.peek().is_none() {
|
||||
if let Some(s) = next.as_str() {
|
||||
hit = Some(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if hit.is_some() {
|
||||
if trace {
|
||||
eprintln!("[using] hit nyash.toml: {} -> {}", ns, hit.as_ref().unwrap());
|
||||
}
|
||||
found = hit;
|
||||
}
|
||||
}
|
||||
}
|
||||
eprintln!("[using/resolve] alias '{}' -> '{}'", ns, f);
|
||||
}
|
||||
}
|
||||
if found.is_none() {
|
||||
@ -157,11 +114,7 @@ pub fn strip_using_and_register(
|
||||
) {
|
||||
Ok(v) => {
|
||||
// Treat unchanged token (namespace) as unresolved
|
||||
if v == ns {
|
||||
found = None;
|
||||
} else {
|
||||
found = Some(v)
|
||||
}
|
||||
if v == ns { found = None; } else { found = Some(v) }
|
||||
}
|
||||
Err(e) => return Err(format!("using: {}", e)),
|
||||
}
|
||||
@ -199,50 +152,34 @@ pub fn strip_using_and_register(
|
||||
// Resolve relative to current file dir
|
||||
// Guard: skip obvious namespace tokens (ns.ns without extension)
|
||||
if (!path.contains('/') && !path.contains('\\')) && !path.ends_with(".nyash") && path.contains('.') {
|
||||
if verbose {
|
||||
eprintln!("[using] unresolved '{}' (namespace token, skip inline)", path);
|
||||
}
|
||||
if verbose { eprintln!("[using] unresolved '{}' (namespace token, skip inline)", path); }
|
||||
continue;
|
||||
}
|
||||
let mut p = std::path::PathBuf::from(&path);
|
||||
if p.is_relative() {
|
||||
// If the raw relative path exists from CWD, use it.
|
||||
// Otherwise, try relative to the current file's directory.
|
||||
if !p.exists() {
|
||||
if let Some(dir) = std::path::Path::new(filename).parent() {
|
||||
let cand = dir.join(&p);
|
||||
if cand.exists() {
|
||||
p = cand;
|
||||
}
|
||||
if cand.exists() { p = cand; }
|
||||
}
|
||||
}
|
||||
}
|
||||
// normalize to absolute to stabilize de-dup
|
||||
if let Ok(abs) = std::fs::canonicalize(&p) { p = abs; }
|
||||
let key = p.to_string_lossy().to_string();
|
||||
if visited.contains(&key) {
|
||||
continue;
|
||||
}
|
||||
if visited.contains(&key) { continue; }
|
||||
visited.insert(key.clone());
|
||||
if let Ok(text) = std::fs::read_to_string(&p) {
|
||||
let inlined = strip_and_inline(runner, &text, &key, visited)?;
|
||||
prelude.push_str(&inlined);
|
||||
prelude.push_str("\n");
|
||||
if seam_dbg {
|
||||
let tail = inlined.chars().rev().take(120).collect::<String>().chars().rev().collect::<String>();
|
||||
eprintln!("[using][seam][inlined] {} tail=<<<{}>>>", key, tail.replace('\n', "\\n"));
|
||||
}
|
||||
crate::runner::modes::common_util::resolve::seam::log_inlined_tail(&key, &inlined, seam_dbg);
|
||||
} else if verbose {
|
||||
eprintln!("[using] warn: could not read {}", p.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Prepend inlined modules so their boxes are defined before use
|
||||
// Seam guard: collapse consecutive blank lines at the join (prelude || body) to a single blank line
|
||||
if prelude.is_empty() {
|
||||
return Ok(out);
|
||||
}
|
||||
// Optionally deduplicate repeated static boxes in prelude by name (default OFF)
|
||||
if prelude.is_empty() { return Ok(out); }
|
||||
// Optional de-dup of static boxes by name
|
||||
let mut prelude_text = prelude;
|
||||
if std::env::var("NYASH_RESOLVE_DEDUP_BOX").ok().as_deref() == Some("1") {
|
||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||
@ -250,19 +187,15 @@ pub fn strip_using_and_register(
|
||||
let bytes: Vec<char> = prelude_text.chars().collect();
|
||||
let mut i = 0usize;
|
||||
while i < bytes.len() {
|
||||
// naive scan for "static box "
|
||||
if i + 12 < bytes.len() && bytes[i..].iter().take(11).collect::<String>() == "static box " {
|
||||
// read name token
|
||||
let mut j = i + 11;
|
||||
let mut name = String::new();
|
||||
while j < bytes.len() {
|
||||
let c = bytes[j];
|
||||
if c.is_alphanumeric() || c == '_' { name.push(c); j += 1; } else { break; }
|
||||
}
|
||||
// find opening brace '{'
|
||||
while j < bytes.len() && bytes[j].is_whitespace() { j += 1; }
|
||||
if j < bytes.len() && bytes[j] == '{' {
|
||||
// scan to matching closing brace for this box
|
||||
let mut k = j;
|
||||
let mut depth = 0i32;
|
||||
while k < bytes.len() {
|
||||
@ -271,210 +204,110 @@ pub fn strip_using_and_register(
|
||||
if c == '}' { depth -= 1; if depth == 0 { k += 1; break; } }
|
||||
k += 1;
|
||||
}
|
||||
// decide
|
||||
if seen.contains(&name) {
|
||||
// skip duplicate box
|
||||
i = k; // drop this block
|
||||
continue;
|
||||
} else {
|
||||
if seen.contains(&name) { i = k; continue; } else {
|
||||
seen.insert(name);
|
||||
// keep this block as-is
|
||||
out_txt.push_str(&bytes[i..k].iter().collect::<String>());
|
||||
i = k;
|
||||
continue;
|
||||
i = k; continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// default: copy one char
|
||||
out_txt.push(bytes[i]);
|
||||
i += 1;
|
||||
}
|
||||
prelude_text = out_txt;
|
||||
}
|
||||
// Optional: de-duplicate repeated function definitions inside specific boxes (default OFF)
|
||||
// Optional: function dedup (MiniVmPrints.print_prints_in_slice)
|
||||
if std::env::var("NYASH_RESOLVE_DEDUP_FN").ok().as_deref() == Some("1") {
|
||||
// Currently target MiniVmPrints.print_prints_in_slice only (low risk)
|
||||
let mut out_txt = String::with_capacity(prelude_text.len());
|
||||
let bytes: Vec<char> = prelude_text.chars().collect();
|
||||
let mut i = 0usize;
|
||||
while i < bytes.len() {
|
||||
// scan for "static box "
|
||||
let ahead: String = bytes[i..bytes.len().min(i + 12)].iter().collect();
|
||||
if ahead.starts_with("static box ") {
|
||||
// parse box name
|
||||
let mut j = i + 11; // len("static box ") == 11
|
||||
let mut j = i + 11;
|
||||
let mut name = String::new();
|
||||
while j < bytes.len() {
|
||||
let c = bytes[j];
|
||||
if c.is_ascii_alphanumeric() || c == '_' { name.push(c); j += 1; } else { break; }
|
||||
}
|
||||
// skip ws to '{'
|
||||
while j < bytes.len() { let c = bytes[j]; if c.is_ascii_alphanumeric() || c == '_' { name.push(c); j += 1; } else { break; } }
|
||||
while j < bytes.len() && bytes[j].is_whitespace() { j += 1; }
|
||||
if j < bytes.len() && bytes[j] == '{' {
|
||||
// find matching closing '}' for the box body
|
||||
let mut k = j;
|
||||
let mut depth = 0i32;
|
||||
let mut in_str = false;
|
||||
while k < bytes.len() {
|
||||
let c = bytes[k];
|
||||
if in_str {
|
||||
if c == '\\' { k += 2; continue; }
|
||||
if c == '"' { in_str = false; }
|
||||
k += 1;
|
||||
continue;
|
||||
} else {
|
||||
if c == '"' { in_str = true; k += 1; continue; }
|
||||
if c == '{' { depth += 1; }
|
||||
if c == '}' { depth -= 1; if depth == 0 { k += 1; break; } }
|
||||
k += 1;
|
||||
}
|
||||
if in_str { if c == '\\' { k += 2; continue; } if c == '"' { in_str = false; } k += 1; continue; } else { if c == '"' { in_str = true; k += 1; continue; } if c == '{' { depth += 1; } if c == '}' { depth -= 1; if depth == 0 { k += 1; break; } } k += 1; }
|
||||
}
|
||||
// write header up to body start '{'
|
||||
out_txt.push_str(&bytes[i..(j + 1)].iter().collect::<String>());
|
||||
// process body (limited dedup for MiniVmPrints.print_prints_in_slice)
|
||||
let body_end = k.saturating_sub(1);
|
||||
if name == "MiniVmPrints" {
|
||||
let mut kept = false;
|
||||
let mut p = j + 1;
|
||||
while p <= body_end {
|
||||
// find next line start
|
||||
let mut ls = p;
|
||||
if ls > j + 1 {
|
||||
while ls <= body_end && bytes[ls - 1] != '\n' { ls += 1; }
|
||||
}
|
||||
let mut ls = p; if ls > j + 1 { while ls <= body_end && bytes[ls - 1] != '\n' { ls += 1; } }
|
||||
if ls > body_end { break; }
|
||||
// skip spaces
|
||||
let mut q = ls;
|
||||
while q <= body_end && bytes[q].is_whitespace() && bytes[q] != '\n' { q += 1; }
|
||||
// check for function definition of print_prints_in_slice
|
||||
let mut q = ls; while q <= body_end && bytes[q].is_whitespace() && bytes[q] != '\n' { q += 1; }
|
||||
let rem: String = bytes[q..(body_end + 1).min(q + 64)].iter().collect();
|
||||
if rem.starts_with("print_prints_in_slice(") {
|
||||
// find ')'
|
||||
let mut r = q;
|
||||
let mut dp = 0i32;
|
||||
let mut in_s = false;
|
||||
let mut r = q; let mut dp = 0i32; let mut instr = false;
|
||||
while r <= body_end {
|
||||
let c = bytes[r];
|
||||
if in_s { if c == '\\' { r += 2; continue; } if c == '"' { in_s = false; } r += 1; continue; }
|
||||
if c == '"' { in_s = true; r += 1; continue; }
|
||||
if c == '(' { dp += 1; r += 1; continue; }
|
||||
if c == ')' { dp -= 1; r += 1; if dp <= 0 { break; } continue; }
|
||||
if instr { if c == '\\' { r += 2; continue; } if c == '"' { instr = false; } r += 1; continue; }
|
||||
if c == '"' { instr = true; r += 1; continue; }
|
||||
if c == '(' { dp += 1; }
|
||||
if c == ')' { dp -= 1; if dp == 0 { r += 1; break; } }
|
||||
if dp == 0 && c == '{' { break; }
|
||||
r += 1;
|
||||
}
|
||||
while r <= body_end && bytes[r].is_whitespace() { r += 1; }
|
||||
if r <= body_end && bytes[r] == '{' {
|
||||
// find body end
|
||||
let mut t = r;
|
||||
let mut d2 = 0i32;
|
||||
let mut in_s2 = false;
|
||||
while t <= body_end {
|
||||
let c2 = bytes[t];
|
||||
if in_s2 { if c2 == '\\' { t += 2; continue; } if c2 == '"' { in_s2 = false; } t += 1; continue; }
|
||||
if c2 == '"' { in_s2 = true; t += 1; continue; }
|
||||
if c2 == '{' { d2 += 1; }
|
||||
if c2 == '}' { d2 -= 1; if d2 == 0 { t += 1; break; } }
|
||||
t += 1;
|
||||
let mut s = r; let mut bd = 0i32; let mut is2 = false;
|
||||
while s <= body_end {
|
||||
let c = bytes[s];
|
||||
if is2 { if c == '\\' { s += 2; continue; } if c == '"' { is2 = false; } s += 1; continue; }
|
||||
if c == '"' { is2 = true; s += 1; continue; }
|
||||
if c == '{' { bd += 1; }
|
||||
if c == '}' { bd -= 1; if bd == 0 { s += 1; break; } }
|
||||
s += 1;
|
||||
}
|
||||
// start-of-line
|
||||
let mut sol = q;
|
||||
while sol > j + 1 && bytes[sol - 1] != '\n' { sol -= 1; }
|
||||
if !kept {
|
||||
out_txt.push_str(&bytes[sol..t].iter().collect::<String>());
|
||||
out_txt.push_str(&bytes[q..s].iter().collect::<String>());
|
||||
kept = true;
|
||||
}
|
||||
p = t;
|
||||
// advance outer scanner to the end of this function body
|
||||
i = s;
|
||||
let _ = i; // mark as read to satisfy unused_assignments lint
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// copy this line
|
||||
let mut eol = ls;
|
||||
while eol <= body_end && bytes[eol] != '\n' { eol += 1; }
|
||||
out_txt.push_str(&bytes[ls..(eol.min(body_end + 1))].iter().collect::<String>());
|
||||
if eol <= body_end && bytes[eol] == '\n' { out_txt.push('\n'); }
|
||||
p = eol + 1;
|
||||
out_txt.push(bytes[p]); p += 1;
|
||||
}
|
||||
} else {
|
||||
// copy body as-is
|
||||
out_txt.push_str(&bytes[(j + 1)..=body_end].iter().collect::<String>());
|
||||
}
|
||||
// write closing '}'
|
||||
out_txt.push('}');
|
||||
i = k;
|
||||
continue;
|
||||
if !kept { out_txt.push_str(&bytes[j + 1..=body_end].iter().collect::<String>()); }
|
||||
out_txt.push('}'); out_txt.push('\n'); i = k; continue;
|
||||
} else { out_txt.push_str(&bytes[j + 1..k].iter().collect::<String>()); i = k; continue; }
|
||||
}
|
||||
}
|
||||
// default: copy one char
|
||||
out_txt.push(bytes[i]);
|
||||
i += 1;
|
||||
out_txt.push(bytes[i]); i += 1;
|
||||
}
|
||||
prelude_text = out_txt;
|
||||
}
|
||||
let prelude_clean = prelude_text.trim_end_matches(['\n', '\r']);
|
||||
if seam_dbg {
|
||||
let tail = prelude_clean.chars().rev().take(160).collect::<String>().chars().rev().collect::<String>();
|
||||
let head = out.chars().take(160).collect::<String>();
|
||||
eprintln!("[using][seam] prelude_tail=<<<{}>>>", tail.replace('\n', "\\n"));
|
||||
eprintln!("[using][seam] body_head =<<<{}>>>", head.replace('\n', "\\n"));
|
||||
}
|
||||
// Seam join + optional fix
|
||||
let prelude_clean = prelude_text.trim_end_matches('\n');
|
||||
crate::runner::modes::common_util::resolve::seam::log_prelude_body_seam(prelude_clean, &out, seam_dbg);
|
||||
let mut combined = String::with_capacity(prelude_clean.len() + out.len() + 1);
|
||||
combined.push_str(prelude_clean);
|
||||
combined.push('\n');
|
||||
// Optional seam safety: append missing '}' for unmatched '{' in prelude
|
||||
if std::env::var("NYASH_RESOLVE_FIX_BRACES").ok().as_deref() == Some("1") {
|
||||
// compute { } delta ignoring strings and comments
|
||||
let mut delta: i32 = 0;
|
||||
let mut it = prelude_clean.chars().peekable();
|
||||
let mut in_str = false;
|
||||
let mut in_sl = false;
|
||||
let mut in_ml = false;
|
||||
while let Some(c) = it.next() {
|
||||
if in_sl {
|
||||
if c == '\n' { in_sl = false; }
|
||||
continue;
|
||||
}
|
||||
if in_ml {
|
||||
if c == '*' {
|
||||
if let Some('/') = it.peek().copied() {
|
||||
// consume '/'
|
||||
it.next();
|
||||
in_ml = false;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if in_str {
|
||||
if c == '\\' { it.next(); continue; }
|
||||
if c == '"' { in_str = false; }
|
||||
continue;
|
||||
}
|
||||
if c == '"' { in_str = true; continue; }
|
||||
if c == '/' {
|
||||
match it.peek().copied() {
|
||||
Some('/') => { in_sl = true; it.next(); continue; }
|
||||
Some('*') => { in_ml = true; it.next(); continue; }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if c == '{' { delta += 1; }
|
||||
if c == '}' { delta -= 1; }
|
||||
}
|
||||
if delta > 0 {
|
||||
if trace { eprintln!("[using][seam] fix: appending {} '}}' before body", delta); }
|
||||
for _ in 0..delta { combined.push('}'); combined.push('\n'); }
|
||||
}
|
||||
}
|
||||
crate::runner::modes::common_util::resolve::seam::fix_prelude_braces_if_enabled(prelude_clean, &mut combined, trace);
|
||||
combined.push_str(&out);
|
||||
Ok(combined)
|
||||
}
|
||||
|
||||
let mut visited = HashSet::new();
|
||||
let combined = strip_and_inline(runner, code, filename, &mut visited)?;
|
||||
// Dev sugar: always pre-expand @name[:T] = expr at line-head to keep sources readable
|
||||
Ok(preexpand_at_local(&combined))
|
||||
}
|
||||
|
||||
/// 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(crate) fn preexpand_at_local(src: &str) -> String {
|
||||
pub fn preexpand_at_local(src: &str) -> String {
|
||||
let mut out = String::with_capacity(src.len());
|
||||
for line in src.lines() {
|
||||
let bytes = line.as_bytes();
|
||||
@ -483,33 +316,18 @@ pub(crate) fn preexpand_at_local(src: &str) -> String {
|
||||
if i < bytes.len() && bytes[i] == b'@' {
|
||||
// parse identifier
|
||||
let mut j = i + 1;
|
||||
// first char [A-Za-z_]
|
||||
if j < bytes.len() && ((bytes[j] as char).is_ascii_alphabetic() || bytes[j] == b'_') {
|
||||
j += 1;
|
||||
while j < bytes.len() {
|
||||
let c = bytes[j] as char;
|
||||
if c.is_ascii_alphanumeric() || c == '_' { j += 1; } else { break; }
|
||||
}
|
||||
// optional type: spaces ':' spaces ident
|
||||
let mut k = j;
|
||||
while k < bytes.len() && (bytes[k] == b' ' || bytes[k] == b'\t') { k += 1; }
|
||||
while j < bytes.len() { let c = bytes[j] as char; if c.is_ascii_alphanumeric() || c == '_' { j += 1; } else { break; } }
|
||||
let mut k = j; while k < bytes.len() && (bytes[k] == b' ' || bytes[k] == b'\t') { k += 1; }
|
||||
if k < bytes.len() && bytes[k] == b':' {
|
||||
k += 1;
|
||||
while k < bytes.len() && (bytes[k] == b' ' || bytes[k] == b'\t') { k += 1; }
|
||||
// simple type ident
|
||||
k += 1; while k < bytes.len() && (bytes[k] == b' ' || bytes[k] == b'\t') { k += 1; }
|
||||
if k < bytes.len() && ((bytes[k] as char).is_ascii_alphabetic() || bytes[k] == b'_') {
|
||||
k += 1;
|
||||
while k < bytes.len() {
|
||||
let c = bytes[k] as char;
|
||||
if c.is_ascii_alphanumeric() || c == '_' { k += 1; } else { break; }
|
||||
}
|
||||
k += 1; while k < bytes.len() { let c = bytes[k] as char; if c.is_ascii_alphanumeric() || c == '_' { k += 1; } else { break; } }
|
||||
}
|
||||
}
|
||||
// consume spaces to '='
|
||||
let mut eqp = k;
|
||||
while eqp < bytes.len() && (bytes[eqp] == b' ' || bytes[eqp] == b'\t') { eqp += 1; }
|
||||
let mut eqp = k; while eqp < bytes.len() && (bytes[eqp] == b' ' || bytes[eqp] == b'\t') { eqp += 1; }
|
||||
if eqp < bytes.len() && bytes[eqp] == b'=' {
|
||||
// build transformed line: prefix + 'local ' + rest from after '@' up to '=' + ' =' + remainder
|
||||
out.push_str(&line[..i]);
|
||||
out.push_str("local ");
|
||||
out.push_str(&line[i + 1..eqp]);
|
||||
@ -98,6 +98,44 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) {
|
||||
if removed > 0 { crate::cli_v!("[PyVM] escape_elide_barriers: removed {} barriers", removed); }
|
||||
}
|
||||
|
||||
// Optional: delegate to Ny selfhost executor (Stage 0 scaffold: no-op)
|
||||
if std::env::var("NYASH_SELFHOST_EXEC").ok().as_deref() == Some("1") {
|
||||
// Emit MIR JSON to a temp file and invoke Ny runner script.
|
||||
let tmp_dir = std::path::Path::new("tmp");
|
||||
let _ = std::fs::create_dir_all(tmp_dir);
|
||||
let mir_json_path = tmp_dir.join("nyash_selfhost_mir.json");
|
||||
if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(&compile_result.module, &mir_json_path) {
|
||||
eprintln!("❌ Selfhost MIR JSON emit error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
// Resolve nyash executable and runner path
|
||||
let exe = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("target/release/nyash"));
|
||||
let runner = std::path::Path::new("apps/selfhost-runtime/runner.nyash");
|
||||
if !runner.exists() {
|
||||
eprintln!("❌ Selfhost runner missing: {}", runner.display());
|
||||
process::exit(1);
|
||||
}
|
||||
let mut cmd = std::process::Command::new(&exe);
|
||||
cmd.arg("--backend").arg("vm")
|
||||
.arg(runner)
|
||||
.arg("--")
|
||||
.arg(mir_json_path.display().to_string());
|
||||
// Optional: pass box pref to child (ny|plugin)
|
||||
if let Ok(pref) = std::env::var("NYASH_SELFHOST_BOX_PREF") {
|
||||
let p = pref.to_lowercase();
|
||||
if p == "ny" || p == "plugin" {
|
||||
cmd.arg(format!("--box-pref={}", p));
|
||||
}
|
||||
}
|
||||
let status = cmd
|
||||
// Avoid recursive selfhost delegation inside the child.
|
||||
.env_remove("NYASH_SELFHOST_EXEC")
|
||||
.status()
|
||||
.unwrap_or_else(|e| { eprintln!("❌ spawn selfhost runner failed: {}", e); std::process::exit(1); });
|
||||
let code = status.code().unwrap_or(1);
|
||||
process::exit(code);
|
||||
}
|
||||
|
||||
// Delegate to common PyVM harness
|
||||
match crate::runner::modes::common_util::pyvm::run_pyvm_harness_lib(&compile_result.module, "pyvm") {
|
||||
Ok(code) => { process::exit(code); }
|
||||
|
||||
75
src/runner/plugins.rs
Normal file
75
src/runner/plugins.rs
Normal file
@ -0,0 +1,75 @@
|
||||
/*!
|
||||
* Runner plugin/registry initialization (extracted)
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
|
||||
impl NyashRunner {
|
||||
/// Initialize global runtime registry and load configured plugins.
|
||||
///
|
||||
/// Behavior (no-op changes to defaults):
|
||||
/// - Always initializes the unified type/box registry.
|
||||
/// - Loads native BID plugins unless `NYASH_DISABLE_PLUGINS=1`.
|
||||
/// - Ensures built‑ins override list includes ArrayBox/MapBox/FileBox/TOMLBox
|
||||
/// by merging `NYASH_PLUGIN_OVERRIDE_TYPES` with required entries.
|
||||
/// - When `--load-ny-plugins` or `NYASH_LOAD_NY_PLUGINS=1` is set, best‑effort
|
||||
/// loads Nyash scripts listed under `nyash.toml`'s `ny_plugins`.
|
||||
///
|
||||
/// Side effects:
|
||||
/// - Sets `NYASH_USE_PLUGIN_BUILTINS=1` if unset, to allow interpreter side creation.
|
||||
/// - Updates `NYASH_PLUGIN_OVERRIDE_TYPES` env var (merged values).
|
||||
pub(crate) fn init_runtime_and_plugins(&self, groups: &crate::cli::CliGroups) {
|
||||
// Unified registry
|
||||
runtime::init_global_unified_registry();
|
||||
// Plugins (guarded)
|
||||
if std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") {
|
||||
runner_plugin_init::init_bid_plugins();
|
||||
crate::runner::box_index::refresh_box_index();
|
||||
}
|
||||
// Allow interpreter to create plugin-backed boxes via unified registry
|
||||
if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().is_none() {
|
||||
std::env::set_var("NYASH_USE_PLUGIN_BUILTINS", "1");
|
||||
}
|
||||
// Merge override types env (ensure FileBox/TOMLBox present)
|
||||
let mut override_types: Vec<String> = if let Ok(list) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES") {
|
||||
list.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect()
|
||||
} else { vec!["ArrayBox".into(), "MapBox".into()] };
|
||||
for t in ["FileBox", "TOMLBox"] { if !override_types.iter().any(|x| x == t) { override_types.push(t.into()); } }
|
||||
std::env::set_var("NYASH_PLUGIN_OVERRIDE_TYPES", override_types.join(","));
|
||||
|
||||
// Optional Ny script plugins loader (best-effort)
|
||||
if groups.load_ny_plugins || std::env::var("NYASH_LOAD_NY_PLUGINS").ok().as_deref() == Some("1") {
|
||||
if let Ok(text) = std::fs::read_to_string("nyash.toml") {
|
||||
if let Ok(doc) = toml::from_str::<toml::Value>(&text) {
|
||||
if let Some(np) = doc.get("ny_plugins") {
|
||||
let mut list: Vec<String> = Vec::new();
|
||||
if let Some(arr) = np.as_array() { for v in arr { if let Some(s) = v.as_str() { list.push(s.to_string()); } } }
|
||||
else if let Some(tbl) = np.as_table() { for (_k, v) in tbl { if let Some(s) = v.as_str() { list.push(s.to_string()); } else if let Some(arr) = v.as_array() { for e in arr { if let Some(s) = e.as_str() { list.push(s.to_string()); } } } } }
|
||||
if !list.is_empty() {
|
||||
let list_only = std::env::var("NYASH_NY_PLUGINS_LIST_ONLY").ok().as_deref() == Some("1");
|
||||
println!("🧩 Ny script plugins ({}):", list.len());
|
||||
for p in list {
|
||||
if list_only { println!(" • {}", p); continue; }
|
||||
match std::fs::read_to_string(&p) {
|
||||
Ok(code) => {
|
||||
match nyash_rust::parser::NyashParser::parse_from_string(&code) {
|
||||
Ok(ast) => {
|
||||
let mut interpreter = nyash_rust::interpreter::NyashInterpreter::new();
|
||||
match interpreter.execute(ast) {
|
||||
Ok(_) => println!("[ny_plugins] {}: OK", p),
|
||||
Err(e) => println!("[ny_plugins] {}: FAIL ({})", p, e),
|
||||
}
|
||||
}
|
||||
Err(e) => println!("[ny_plugins] {}: FAIL (parse: {})", p, e),
|
||||
}
|
||||
}
|
||||
Err(e) => println!("[ny_plugins] {}: FAIL (read: {})", p, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -112,14 +112,39 @@ impl NyashRunner {
|
||||
let parser_prog = std::path::Path::new("apps/selfhost/compiler/compiler.nyash");
|
||||
if parser_prog.exists() {
|
||||
// Build extra args forwarded to child program
|
||||
let mut extra: Vec<&str> = Vec::new();
|
||||
let mut extra_owned: Vec<String> = Vec::new();
|
||||
if crate::config::env::ny_compiler_min_json() {
|
||||
extra.extend(["--", "--min-json"]);
|
||||
extra_owned.push("--".to_string());
|
||||
extra_owned.push("--min-json".to_string());
|
||||
}
|
||||
extra.extend(["--", "--read-tmp"]);
|
||||
extra_owned.push("--".to_string());
|
||||
extra_owned.push("--read-tmp".to_string());
|
||||
if crate::config::env::ny_compiler_stage3() {
|
||||
extra.extend(["--", "--stage3"]);
|
||||
extra_owned.push("--".to_string());
|
||||
extra_owned.push("--stage3".to_string());
|
||||
}
|
||||
// Optional: map env toggles to child args (prepasses)
|
||||
if std::env::var("NYASH_SCOPEBOX_ENABLE").ok().as_deref() == Some("1") {
|
||||
extra_owned.push("--".to_string());
|
||||
extra_owned.push("--scopebox".to_string());
|
||||
}
|
||||
if std::env::var("NYASH_LOOPFORM_NORMALIZE").ok().as_deref() == Some("1") {
|
||||
extra_owned.push("--".to_string());
|
||||
extra_owned.push("--loopform".to_string());
|
||||
}
|
||||
// Optional: developer-provided child args passthrough (space-separated)
|
||||
if let Ok(raw) = std::env::var("NYASH_SELFHOST_CHILD_ARGS") {
|
||||
let items: Vec<String> = raw
|
||||
.split(' ')
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
if !items.is_empty() {
|
||||
extra_owned.push("--".to_string());
|
||||
for it in items { extra_owned.push(it); }
|
||||
}
|
||||
}
|
||||
let extra: Vec<&str> = extra_owned.iter().map(|s| s.as_str()).collect();
|
||||
let timeout_ms: u64 = crate::config::env::ny_compiler_timeout_ms();
|
||||
if let Some(line) = child::run_ny_program_capture_json(
|
||||
&exe,
|
||||
|
||||
Reference in New Issue
Block a user