public: publish selfhost snapshot to public repo (SSOT using + AST merge + JSON VM fixes)

- SSOT using profiles (aliases/packages via nyash.toml), AST prelude merge
- Parser/member guards; Builder pin/PHI and instance→function rewrite (dev on)
- VM refactors (handlers split) and JSON roundtrip/nested stabilization
- CURRENT_TASK.md updated with scope and acceptance criteria

Notes: dev-only guards remain togglable via env; no default behavior changes for prod.
This commit is contained in:
nyash-codex
2025-09-26 14:34:42 +09:00
parent ecd46161b3
commit cdf826cbe7
44 changed files with 6264 additions and 576 deletions

View File

@ -1,15 +1,15 @@
use super::super::NyashRunner;
use crate::runner::json_v0_bridge;
use nyash_rust::{parser::NyashParser, interpreter::NyashInterpreter};
use nyash_rust::{interpreter::NyashInterpreter, parser::NyashParser};
// Use the library crate's plugin init module rather than the bin crate root
use std::{fs, process};
use crate::cli_v;
use crate::runner::pipeline::{resolve_using_target, suggest_in_base};
use crate::runner::trace::cli_verbose;
use std::io::Read;
use std::process::Stdio;
use std::time::{Duration, Instant};
use std::thread::sleep;
use crate::runner::pipeline::{suggest_in_base, resolve_using_target};
use crate::runner::trace::cli_verbose;
use crate::cli_v;
use std::time::{Duration, Instant};
use std::{fs, process};
// (moved) suggest_in_base is now in runner/pipeline.rs
@ -17,13 +17,21 @@ impl NyashRunner {
// 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> {
fn run_pyvm_harness(
&self,
module: &nyash_rust::mir::MirModule,
tag: &str,
) -> Result<i32, String> {
super::common_util::pyvm::run_pyvm_harness(module, tag)
}
/// Helper: try external selfhost compiler EXE to parse Ny -> JSON v0 and return MIR module
/// Returns Some(module) on success, None on failure (timeout/invalid output/missing exe)
fn exe_try_parse_json_v0(&self, filename: &str, timeout_ms: u64) -> Option<nyash_rust::mir::MirModule> {
fn exe_try_parse_json_v0(
&self,
filename: &str,
timeout_ms: u64,
) -> Option<nyash_rust::mir::MirModule> {
super::common_util::selfhost_exe::exe_try_parse_json_v0(filename, timeout_ms)
}
@ -42,7 +50,10 @@ impl NyashRunner {
// Read the file
let code = match fs::read_to_string(filename) {
Ok(content) => content,
Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); }
Err(e) => {
eprintln!("❌ Error reading file {}: {}", filename, e);
process::exit(1);
}
};
if crate::config::env::cli_verbose() && !quiet_pipe {
println!("📝 File contents:\n{}", code);
@ -55,100 +66,49 @@ impl NyashRunner {
let cleaned_code_owned;
let mut prelude_asts: Vec<nyash_rust::ast::ASTNode> = Vec::new();
if crate::config::env::enable_using() {
match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(self, &code, filename) {
match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(
self, &code, filename,
) {
Ok((clean, paths)) => {
cleaned_code_owned = clean; code_ref = &cleaned_code_owned;
cleaned_code_owned = clean;
code_ref = &cleaned_code_owned;
if !paths.is_empty() && !use_ast {
eprintln!("❌ using: AST prelude merge is disabled in this profile. Enable NYASH_USING_AST=1 or remove 'using' lines.");
std::process::exit(1);
}
if use_ast {
// Parse each prelude file into AST after stripping its own using-lines.
// Recursively process nested preludes to avoid parse errors.
let mut visited = std::collections::HashSet::<String>::new();
// Normalize initial paths relative to filename or $NYASH_ROOT
let mut stack: Vec<String> = Vec::new();
for raw in paths {
let mut pb = std::path::PathBuf::from(&raw);
if pb.is_relative() {
if let Some(dir) = std::path::Path::new(filename).parent() {
let cand = dir.join(&pb);
if cand.exists() { pb = cand; }
}
if pb.is_relative() {
if let Ok(root) = std::env::var("NYASH_ROOT") {
let cand = std::path::Path::new(&root).join(&pb);
if cand.exists() { pb = cand; }
} else {
// Fallback: resolve relative to project root guessed from the nyash binary path
if let Ok(exe) = std::env::current_exe() {
if let Some(root) = exe.parent().and_then(|p| p.parent()).and_then(|p| p.parent()) {
let cand = root.join(&pb);
if cand.exists() { pb = cand; }
}
}
}
}
}
stack.push(pb.to_string_lossy().to_string());
}
while let Some(mut p) = stack.pop() {
// Normalize relative path against $NYASH_ROOT as a last resort
if std::path::Path::new(&p).is_relative() {
if let Ok(root) = std::env::var("NYASH_ROOT") {
let cand = std::path::Path::new(&root).join(&p);
p = cand.to_string_lossy().to_string();
} else if let Ok(exe) = std::env::current_exe() {
if let Some(root) = exe.parent().and_then(|p| p.parent()).and_then(|p| p.parent()) {
let cand = root.join(&p);
p = cand.to_string_lossy().to_string();
}
}
}
if !visited.insert(p.clone()) { continue; }
match std::fs::read_to_string(&p) {
for prelude_path in paths {
match std::fs::read_to_string(&prelude_path) {
Ok(src) => {
match crate::runner::modes::common_util::resolve::collect_using_and_strip(self, &src, &p) {
match crate::runner::modes::common_util::resolve::collect_using_and_strip(self, &src, &prelude_path) {
Ok((clean_src, nested)) => {
// Normalize and push nested first so they are parsed before the current file (DFS)
for np in nested {
let mut npp = std::path::PathBuf::from(&np);
if npp.is_relative() {
if let Some(dir) = std::path::Path::new(&p).parent() {
let cand = dir.join(&npp);
if cand.exists() { npp = cand; }
}
if npp.is_relative() {
if let Ok(root) = std::env::var("NYASH_ROOT") {
let cand = std::path::Path::new(&root).join(&npp);
if cand.exists() { npp = cand; }
} else {
if let Ok(exe) = std::env::current_exe() {
if let Some(root) = exe.parent().and_then(|p| p.parent()).and_then(|p| p.parent()) {
let cand = root.join(&npp);
if cand.exists() { npp = cand; }
}
}
}
}
}
let nps = npp.to_string_lossy().to_string();
if !visited.contains(&nps) { stack.push(nps); }
}
// Nested entries have already been expanded by DFS; ignore `nested` here.
match NyashParser::parse_from_string(&clean_src) {
Ok(ast) => prelude_asts.push(ast),
Err(e) => { eprintln!("❌ Parse error in using prelude {}: {}", p, e); std::process::exit(1); }
Err(e) => {
eprintln!("❌ Parse error in using prelude {}: {}", prelude_path, e);
std::process::exit(1);
}
}
}
Err(e) => { eprintln!("{}", e); std::process::exit(1); }
Err(e) => {
eprintln!("{}", e);
std::process::exit(1);
}
}
}
Err(e) => { eprintln!("❌ Error reading using prelude {}: {}", p, e); std::process::exit(1); }
Err(e) => {
eprintln!("❌ Error reading using prelude {}: {}", prelude_path, e);
std::process::exit(1);
}
}
}
}
}
Err(e) => { eprintln!("{}", e); std::process::exit(1); }
Err(e) => {
eprintln!("{}", e);
std::process::exit(1);
}
}
}
// Optional dev sugar: @name[:T] = expr → local name[:T] = expr (line-head only)
@ -161,23 +121,40 @@ 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 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); }
};
eprintln!(
"🔍 DEBUG: Starting parse with fuel: {:?}...",
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<ASTNode> = Vec::new();
for a in prelude_asts {
if let ASTNode::Program { statements, .. } = a { combined.extend(statements); }
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 };
ASTNode::Program {
statements: combined,
span: nyash_rust::ast::Span::unknown(),
}
} else {
main_ast
};
// Optional: dump AST statement kinds for quick diagnostics
if std::env::var("NYASH_AST_DUMP").ok().as_deref() == Some("1") {
@ -186,15 +163,23 @@ impl NyashRunner {
if let ASTNode::Program { statements, .. } = &ast {
for (i, st) in statements.iter().enumerate().take(50) {
let kind = match st {
ASTNode::BoxDeclaration { is_static, name, .. } => {
if *is_static { format!("StaticBox({})", name) } else { format!("Box({})", name) }
ASTNode::BoxDeclaration {
is_static, name, ..
} => {
if *is_static {
format!("StaticBox({})", name)
} else {
format!("Box({})", name)
}
}
ASTNode::FunctionDeclaration { name, .. } => format!("FuncDecl({})", name),
ASTNode::FunctionCall { name, .. } => format!("FuncCall({})", name),
ASTNode::MethodCall { method, .. } => format!("MethodCall({})", method),
ASTNode::ScopeBox { .. } => "ScopeBox".to_string(),
ASTNode::ImportStatement { path, .. } => format!("Import({})", path),
ASTNode::UsingStatement { namespace_name, .. } => format!("Using({})", namespace_name),
ASTNode::UsingStatement { namespace_name, .. } => {
format!("Using({})", namespace_name)
}
_ => format!("{:?}", st),
};
eprintln!("[ast] {}: {}", i, kind);
@ -217,19 +202,32 @@ impl NyashRunner {
let exists = p.exists();
if !exists {
if std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1") {
eprintln!("❌ import: path not found: {} (from {})", p.display(), filename);
eprintln!(
"❌ import: path not found: {} (from {})",
p.display(),
filename
);
process::exit(1);
} else if crate::config::env::cli_verbose() || std::env::var("NYASH_IMPORT_TRACE").ok().as_deref() == Some("1") {
} else if crate::config::env::cli_verbose()
|| std::env::var("NYASH_IMPORT_TRACE").ok().as_deref() == Some("1")
{
eprintln!("[import] path not found (continuing): {}", p.display());
}
}
let key = if let Some(a) = alias { a.clone() } else {
let key = if let Some(a) = alias {
a.clone()
} else {
std::path::Path::new(path)
.file_stem().and_then(|s| s.to_str())
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or(path)
.to_string()
};
let value = if exists { p.to_string_lossy().to_string() } else { path.clone() };
let value = if exists {
p.to_string_lossy().to_string()
} else {
path.clone()
};
let sb = nyash_rust::box_trait::StringBox::new(value);
nyash_rust::runtime::modules_registry::set(key, Box::new(sb));
}
@ -237,7 +235,9 @@ impl NyashRunner {
}
if crate::config::env::cli_verbose() && !quiet_pipe {
if crate::config::env::cli_verbose() { println!("✅ Parse successful!"); }
if crate::config::env::cli_verbose() {
println!("✅ Parse successful!");
}
}
// Execute the AST
@ -246,48 +246,79 @@ impl NyashRunner {
match interpreter.execute(ast) {
Ok(result) => {
if crate::config::env::cli_verbose() && !quiet_pipe {
if crate::config::env::cli_verbose() { println!("✅ Execution completed successfully!"); }
if crate::config::env::cli_verbose() {
println!("✅ Execution completed successfully!");
}
}
// Normalize display via semantics: prefer numeric, then string, then fallback
let disp = {
// Special-case: plugin IntegerBox → call .get to fetch numeric value
if let Some(p) = result.as_any().downcast_ref::<nyash_rust::runtime::plugin_loader_v2::PluginBoxV2>() {
if let Some(p) = result
.as_any()
.downcast_ref::<nyash_rust::runtime::plugin_loader_v2::PluginBoxV2>(
) {
if p.box_type == "IntegerBox" {
// Scope the lock strictly to this block
let fetched = {
let host = nyash_rust::runtime::get_global_plugin_host();
let res = if let Ok(ro) = host.read() {
if let Ok(Some(vb)) = ro.invoke_instance_method("IntegerBox", "get", p.instance_id(), &[]) {
if let Some(ib) = vb.as_any().downcast_ref::<nyash_rust::box_trait::IntegerBox>() {
if let Ok(Some(vb)) = ro.invoke_instance_method(
"IntegerBox",
"get",
p.instance_id(),
&[],
) {
if let Some(ib) =
vb.as_any()
.downcast_ref::<nyash_rust::box_trait::IntegerBox>()
{
Some(ib.value.to_string())
} else {
Some(vb.to_string_box().value)
}
} else { None }
} else { None };
} else {
None
}
} else {
None
};
res
};
if let Some(s) = fetched { s } else {
if let Some(s) = fetched {
s
} else {
nyash_rust::runtime::semantics::coerce_to_i64(result.as_ref())
.map(|i| i.to_string())
.or_else(|| nyash_rust::runtime::semantics::coerce_to_string(result.as_ref()))
.or_else(|| {
nyash_rust::runtime::semantics::coerce_to_string(
result.as_ref(),
)
})
.unwrap_or_else(|| result.to_string_box().value)
}
} else {
nyash_rust::runtime::semantics::coerce_to_i64(result.as_ref())
.map(|i| i.to_string())
.or_else(|| nyash_rust::runtime::semantics::coerce_to_string(result.as_ref()))
.or_else(|| {
nyash_rust::runtime::semantics::coerce_to_string(
result.as_ref(),
)
})
.unwrap_or_else(|| result.to_string_box().value)
}
} else {
nyash_rust::runtime::semantics::coerce_to_i64(result.as_ref())
.map(|i| i.to_string())
.or_else(|| nyash_rust::runtime::semantics::coerce_to_string(result.as_ref()))
.or_else(|| {
nyash_rust::runtime::semantics::coerce_to_string(result.as_ref())
})
.unwrap_or_else(|| result.to_string_box().value)
}
};
if !quiet_pipe { println!("Result: {}", disp); }
},
if !quiet_pipe {
println!("Result: {}", disp);
}
}
Err(e) => {
eprintln!("❌ Runtime error:\n{}", e.detailed_message(Some(&code)));
process::exit(1);