diff --git a/src/cli.rs b/src/cli.rs index b4ff247b..69875566 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -6,6 +6,7 @@ */ use clap::{Arg, Command, ArgMatches}; +use serde_json; /// Command-line configuration structure #[derive(Debug, Clone)] @@ -71,8 +72,23 @@ pub struct CliConfig { impl CliConfig { /// Parse command-line arguments and return configuration pub fn parse() -> Self { - let matches = Self::build_command().get_matches(); - Self::from_matches(&matches) + // Pre-process raw argv to capture trailing script args after '--' + let argv: Vec = std::env::args().collect(); + if let Some(pos) = argv.iter().position(|s| s == "--") { + // Everything after '--' is script args + let script_args: Vec = argv.iter().skip(pos + 1).cloned().collect(); + if !script_args.is_empty() { + if let Ok(json) = serde_json::to_string(&script_args) { + std::env::set_var("NYASH_SCRIPT_ARGS_JSON", json); + } + } + // Only parse CLI args up to '--' + let matches = Self::build_command().try_get_matches_from(&argv[..pos]).unwrap_or_else(|e| e.exit()); + Self::from_matches(&matches) + } else { + let matches = Self::build_command().get_matches(); + Self::from_matches(&matches) + } } /// Build the clap Command structure diff --git a/src/interpreter/eval.rs b/src/interpreter/eval.rs index 910ef146..e66ce504 100644 --- a/src/interpreter/eval.rs +++ b/src/interpreter/eval.rs @@ -64,14 +64,32 @@ impl NyashInterpreter { // Main static boxを初期化 self.ensure_static_box_initialized("Main")?; - // Main.main(args?) を呼び出し(引数が1つなら空配列をデフォルト注入) + // Main.main(args?) を呼び出し(引数が1つならデフォルトで args を注入) let mut default_args: Vec = Vec::new(); if let Ok(defs) = self.shared.static_box_definitions.read() { if let Some(main_def) = defs.get("Main") { if let Some(m) = main_def.methods.get("main") { if let ASTNode::FunctionDeclaration { params, .. } = m { if params.len() == 1 { - default_args.push(ASTNode::New { class: "ArrayBox".to_string(), arguments: vec![], type_arguments: vec![], span: crate::ast::Span::unknown() }); + // Try to read script args from env (JSON array); fallback to empty ArrayBox + if let Ok(json) = std::env::var("NYASH_SCRIPT_ARGS_JSON") { + if let Ok(vals) = serde_json::from_str::>(&json) { + let mut str_nodes: Vec = Vec::with_capacity(vals.len()); + for s in vals { + str_nodes.push(ASTNode::Literal { value: crate::ast::LiteralValue::String(s), span: crate::ast::Span::unknown() }); + } + default_args.push(ASTNode::MethodCall { + object: Box::new(ASTNode::Variable { name: "ArrayBox".to_string(), span: crate::ast::Span::unknown() }), + method: "of".to_string(), + arguments: str_nodes, + span: crate::ast::Span::unknown(), + }); + } else { + default_args.push(ASTNode::New { class: "ArrayBox".to_string(), arguments: vec![], type_arguments: vec![], span: crate::ast::Span::unknown() }); + } + } else { + default_args.push(ASTNode::New { class: "ArrayBox".to_string(), arguments: vec![], type_arguments: vec![], span: crate::ast::Span::unknown() }); + } } } } diff --git a/src/interpreter/methods/collection_methods.rs b/src/interpreter/methods/collection_methods.rs index 0858cf19..627aa92f 100644 --- a/src/interpreter/methods/collection_methods.rs +++ b/src/interpreter/methods/collection_methods.rs @@ -16,6 +16,16 @@ impl NyashInterpreter { pub(in crate::interpreter) fn execute_array_method(&mut self, array_box: &ArrayBox, method: &str, arguments: &[ASTNode]) -> Result, RuntimeError> { match method { + "of" => { + // Build a new ArrayBox from provided arguments + let mut elems: Vec> = Vec::with_capacity(arguments.len()); + for arg in arguments { + let v = self.execute_expression(arg)?; + elems.push(v); + } + let arr = ArrayBox::new_with_elements(elems); + return Ok(Box::new(arr)); + } "push" => { if arguments.len() != 1 { return Err(RuntimeError::InvalidOperation { @@ -300,4 +310,4 @@ impl NyashInterpreter { } } } -} \ No newline at end of file +} diff --git a/src/runner/modes/common.rs b/src/runner/modes/common.rs index f1c68676..59e2eaeb 100644 --- a/src/runner/modes/common.rs +++ b/src/runner/modes/common.rs @@ -429,40 +429,41 @@ impl NyashRunner { 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. - if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { - let head: String = stdout.chars().take(200).collect(); - eprintln!("[ny-compiler] JSON not found in child stdout (head): {}", head.replace('\n', "\\n")); - eprintln!("[ny-compiler] falling back to tools/ny_parser_mvp.py for this input"); - } - 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; } - } + // Fallback: try Python MVP parser to produce JSON v0 from the same tmp source (unless skipped). + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + let head: String = stdout.chars().take(200).collect(); + eprintln!("[ny-compiler] JSON not found in child stdout (head): {}", head.replace('\n', "\\n")); + eprintln!("[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); - } + } + 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 diff --git a/src/runner/selfhost.rs b/src/runner/selfhost.rs index 8572295f..3657a534 100644 --- a/src/runner/selfhost.rs +++ b/src/runner/selfhost.rs @@ -91,7 +91,97 @@ impl NyashRunner { Err(e) => { eprintln!("[ny-compiler] open tmp failed: {}", e); return false; } } } - // Python MVP-first: prefer the lightweight harness to produce JSON v0 + // Preferred: run Ny selfhost compiler program (apps/selfhost-compiler/compiler.nyash) + // This avoids inline embedding pitfalls and supports Stage-3 gating via args. + { + let exe = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("target/release/nyash")); + let parser_prog = std::path::Path::new("apps/selfhost-compiler/compiler.nyash"); + if parser_prog.exists() { + let mut cmd = std::process::Command::new(&exe); + cmd.arg("--backend").arg("vm").arg(parser_prog); + // Forward minimal args to child parser program + if std::env::var("NYASH_NY_COMPILER_MIN_JSON").ok().as_deref() == Some("1") { + cmd.arg("--").arg("--min-json"); + } + // Always feed input via tmp file written by the parent pipeline + cmd.arg("--").arg("--read-tmp"); + if std::env::var("NYASH_NY_COMPILER_STAGE3").ok().as_deref() == Some("1") { + cmd.arg("--").arg("--stage3"); + } + // Suppress parent noise and keep only JSON from child + cmd.env_remove("NYASH_USE_NY_COMPILER"); + cmd.env_remove("NYASH_CLI_VERBOSE"); + cmd.env("NYASH_JSON_ONLY", "1"); + let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(2000); + let mut cmd = cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::piped()); + if let Ok(mut child) = cmd.spawn() { + let mut ch_stdout = child.stdout.take(); + let mut ch_stderr = child.stderr.take(); + let start = std::time::Instant::now(); + let mut timed_out = false; + loop { + match child.try_wait() { + Ok(Some(_)) => break, + Ok(None) => { + if start.elapsed() >= std::time::Duration::from_millis(timeout_ms) { + let _ = child.kill(); let _ = child.wait(); timed_out = true; break; + } + std::thread::sleep(std::time::Duration::from_millis(10)); + } + Err(_) => break, + } + } + let mut out_buf = Vec::new(); + let mut err_buf = Vec::new(); + if let Some(mut s) = ch_stdout { let _ = s.read_to_end(&mut out_buf); } + if let Some(mut s) = ch_stderr { let _ = s.read_to_end(&mut err_buf); } + if timed_out { + let head = String::from_utf8_lossy(&out_buf).chars().take(200).collect::(); + eprintln!("[ny-compiler] child timeout after {} ms; stdout(head)='{}'", timeout_ms, head.replace('\n', "\\n")); + } + let stdout = String::from_utf8_lossy(&out_buf).to_string(); + 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() { + match super::json_v0_bridge::parse_json_v0_to_module(&json_line) { + Ok(module) => { + super::json_v0_bridge::maybe_dump_mir(&module); + let emit_only = std::env::var("NYASH_NY_COMPILER_EMIT_ONLY").unwrap_or_else(|_| "1".to_string()) == "1"; + if emit_only { return false; } + // Prefer PyVM path when requested + if std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1") { + 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); + std::process::exit(1); + } + let entry = if module.functions.contains_key("Main.main") { "Main.main" } else if module.functions.contains_key("main") { "main" } else { "Main.main" }; + let status = std::process::Command::new(py3) + .args(["tools/pyvm_runner.py", "--in", &mir_json_path.display().to_string(), "--entry", entry]) + .status().map_err(|e| format!("spawn pyvm: {}", e)).unwrap(); + let code = status.code().unwrap_or(1); + println!("Result: {}", code); + std::process::exit(code); + } + } + } + self.execute_mir_module(&module); + return true; + } + Err(e) => { eprintln!("[ny-compiler] json parse error (child): {}", e); } + } + } + } + } + } + + // Python MVP-first: prefer the lightweight harness to produce JSON v0 (unless skipped) + if std::env::var("NYASH_NY_COMPILER_SKIP_PY").ok().as_deref() != Some("1") { if let Ok(py3) = which::which("python3") { let py = std::path::Path::new("tools/ny_parser_mvp.py"); if py.exists() { @@ -141,7 +231,7 @@ impl NyashRunner { } } } - } + } } // EXE-first: if requested, try external parser EXE (nyash_compiler) if std::env::var("NYASH_USE_NY_COMPILER_EXE").ok().as_deref() == Some("1") { // Resolve parser EXE path diff --git a/tools/selfhost_stage3_accept_smoke.sh b/tools/selfhost_stage3_accept_smoke.sh index ac0cc7f3..a6dfc154 100644 --- a/tools/selfhost_stage3_accept_smoke.sh +++ b/tools/selfhost_stage3_accept_smoke.sh @@ -15,56 +15,33 @@ mkdir -p "$TMP" pass() { echo "✅ $1" >&2; } fail() { echo "❌ $1" >&2; echo "$2" >&2; exit 1; } -compile_json_stage3() { - local src_text="$1" - local inline="$TMP/inline_selfhost_emit_stage3.nyash" - # Embed source (escape quotes and backslashes; preserve newlines) - local esc - esc=$(printf '%s' "$src_text" | sed -e 's/\\/\\\\/g' -e 's/\"/\\\"/g') - cat > "$inline" << NY -include "apps/selfhost-compiler/boxes/parser_box.nyash" -include "apps/selfhost-compiler/boxes/emitter_box.nyash" -static box Main { - main(args) { - local source_text = "$esc" - local p = new ParserBox() - local json = p.parse_program2(source_text) - local e = new EmitterBox() - json = e.emit_program(json, "[]") - print(json) - return 0 - } -} -NY - local raw - raw=$("$BIN" --backend vm "$inline" 2>/dev/null || true) - # Extract the first JSON-looking line (contains version/kind) - printf '%s\n' "$raw" | awk 'BEGIN{found=0} /^[ \t]*\{/{ if ($0 ~ /"version"/ && $0 ~ /"kind"/) { print; found=1; exit } } END{ if(found==0){} }' -} - run_case_stage3() { local name="$1"; shift local src="$1"; shift local expect_code="$1"; shift + local file="$TMP/selfhost_stage3_${name// /_}.nyash" + printf "%s\n" "$src" > "$file" + # 1) Produce JSON v0 via selfhost compiler program set +e - JSON=$(compile_json_stage3 "$src") - OUT=$(printf '%s\n' "$JSON" | NYASH_PIPE_USE_PYVM=1 "$BIN" --ny-parser-pipe --backend vm 2>&1) + JSON=$(NYASH_JSON_ONLY=1 "$BIN" --backend vm "$ROOT_DIR/apps/selfhost-compiler/compiler.nyash" -- --stage3 "$file" 2>/dev/null | awk 'BEGIN{found=0} /^[ \t]*\{/{ if ($0 ~ /"version"/ && $0 ~ /"kind"/) { print; found=1; exit } } END{ if(found==0){} }') + # 2) Execute JSON v0 via Bridge (prefer PyVM harness if requested) + OUT=$(printf '%s\n' "$JSON" | NYASH_PIPE_USE_PYVM=${NYASH_PIPE_USE_PYVM:-1} "$BIN" --ny-parser-pipe --backend vm 2>&1) CODE=$? set -e if [[ "$CODE" == "$expect_code" ]]; then pass "$name"; else fail "$name" "$OUT"; fi } # A) try/catch/finally acceptance; final return 0 -run_case_stage3 "try/catch/finally (accept)" $'try { local x = 1 } catch (Error e) { local y = 2 } finally { local z = 3 }\nreturn 0' 0 +run_case_stage3 "try_finally" $'try { local x = 1 } catch (Error e) { local y = 2 } finally { local z = 3 }\nreturn 0' 0 # B) break acceptance under dead branch -run_case_stage3 "break in dead branch (accept)" $'if false { break } else { }\nreturn 0' 0 +run_case_stage3 "break_dead" $'if false { break } else { }\nreturn 0' 0 # C) continue acceptance under dead branch -run_case_stage3 "continue in dead branch (accept)" $'if false { continue } else { }\nreturn 0' 0 +run_case_stage3 "continue_dead" $'if false { continue } else { }\nreturn 0' 0 # D) throw acceptance (degrade); final return 0 -run_case_stage3 "throw (accept)" $'try { throw 123 } finally { }\nreturn 0' 0 +run_case_stage3 "throw_accept" $'try { throw 123 } finally { }\nreturn 0' 0 echo "All selfhost Stage-3 acceptance smokes PASS" >&2 exit 0