fix(dx): Quick Win 1-3 for better error messages and API simplification
Quick Win 1: Show available boxes on "Unknown Box type" error - vm.rs, vm_fallback.rs: Display sorted list of available user-defined boxes - Before: "Unknown Box type: Foo" - After: "Unknown Box type: Foo. Available: Bar, Baz, Main" Quick Win 2: Show stderr on child process timeout - child.rs, selfhost_exe.rs: Capture and display stderr (up to 500 chars) - Helps diagnose what went wrong in selfhost compiler child process Quick Win 3: Simplify Stage-B compiler API (SSOT) - compiler_stageb.hako: Add StageBDriverBox.compile() as single entry point - compiler_stageb.hako: Remove StageBMain compatibility wrapper - compiler.hako: Change from `using ... as StageBMain` to direct import - compiler.hako: Call StageBDriverBox.compile() directly Also includes child_env.rs NYASH_MODULES env var for module mapping. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -2,7 +2,9 @@
|
|||||||
// - When invoked with --min-json, emit minimal Program JSON v0 to stdout
|
// - When invoked with --min-json, emit minimal Program JSON v0 to stdout
|
||||||
// - Otherwise, act as a silent placeholder (return 0)
|
// - Otherwise, act as a silent placeholder (return 0)
|
||||||
|
|
||||||
using lang.compiler.entry.compiler_stageb as StageBMain
|
// Phase 28.2 Quick Win 3: Direct import without alias
|
||||||
|
// StageBDriverBox.compile() is the SSOT entry point for Stage-B compilation
|
||||||
|
using lang.compiler.entry.compiler_stageb
|
||||||
|
|
||||||
static box Main {
|
static box Main {
|
||||||
_parse_signed_int(raw) {
|
_parse_signed_int(raw) {
|
||||||
@ -478,7 +480,8 @@ static box Main {
|
|||||||
main(args) {
|
main(args) {
|
||||||
local flags = me._collect_flags(args)
|
local flags = me._collect_flags(args)
|
||||||
if flags.stage_b == 1 {
|
if flags.stage_b == 1 {
|
||||||
local json = StageBMain._do_compile_stage_b(flags.source, flags.prefer_cfg, flags.stage3, flags.v1_compat)
|
// Phase 28.2 Quick Win 3: Direct call to SSOT
|
||||||
|
local json = StageBDriverBox.compile(flags.source, flags.prefer_cfg, flags.stage3, flags.v1_compat)
|
||||||
print(json)
|
print(json)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1139,6 +1139,89 @@ static box StageBHelperBox {
|
|||||||
|
|
||||||
// Phase 25.1c: Main driver logic
|
// Phase 25.1c: Main driver logic
|
||||||
static box StageBDriverBox {
|
static box StageBDriverBox {
|
||||||
|
// Phase 28.2 Quick Win 3: SSOT compile API for compiler.hako direct call
|
||||||
|
// Returns Program(JSON v0) instead of printing it.
|
||||||
|
compile(source, prefer_cfg, stage3, v1_compat) {
|
||||||
|
// 1) Set env vars for internal functions
|
||||||
|
if stage3 != null && stage3 != 0 {
|
||||||
|
env.set("NYASH_PARSER_STAGE3", "1")
|
||||||
|
env.set("HAKO_PARSER_STAGE3", "1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Create parser
|
||||||
|
local p = new ParserBox()
|
||||||
|
if stage3 != null && stage3 != 0 {
|
||||||
|
p.stage3_enable(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Extract body and parse
|
||||||
|
local body_src = StageBBodyExtractorBox.build_body_src(source, null)
|
||||||
|
|
||||||
|
// 4) Parse to JSON
|
||||||
|
local ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":[]}"
|
||||||
|
if body_src != null {
|
||||||
|
local block_src = "{" + body_src + "}"
|
||||||
|
local block_res = p.parse_block2(block_src, 0)
|
||||||
|
local at = block_res.lastIndexOf("@")
|
||||||
|
if at >= 0 {
|
||||||
|
local body_json = block_res.substring(0, at)
|
||||||
|
if body_json != null && body_json != "" {
|
||||||
|
ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":" + body_json + "}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5) Apply SSA transformations
|
||||||
|
ast_json = CompilerBuilder.apply_all(ast_json)
|
||||||
|
|
||||||
|
// 6) Scan for function definitions
|
||||||
|
local defs_json = ""
|
||||||
|
local methods = StageBFuncScannerBox.scan_all_boxes(source)
|
||||||
|
if methods != null && methods.length() > 0 {
|
||||||
|
defs_json = ",\"defs\":["
|
||||||
|
local mi = 0
|
||||||
|
local mn = methods.length()
|
||||||
|
loop(mi < mn) {
|
||||||
|
local def = methods.get(mi)
|
||||||
|
local mname = "" + def.get("name")
|
||||||
|
local mparams = def.get("params")
|
||||||
|
local mbody = "" + def.get("body_json")
|
||||||
|
local mbox = "" + def.get("box")
|
||||||
|
local wrapped_body = "{\"type\":\"Block\",\"body\":" + mbody + "}"
|
||||||
|
local params_arr = "["
|
||||||
|
local pi = 0
|
||||||
|
local pn = mparams.length()
|
||||||
|
loop(pi < pn) {
|
||||||
|
if pi > 0 { params_arr = params_arr + "," }
|
||||||
|
params_arr = params_arr + "\"" + ("" + mparams.get(pi)) + "\""
|
||||||
|
pi = pi + 1
|
||||||
|
}
|
||||||
|
params_arr = params_arr + "]"
|
||||||
|
if mi > 0 { defs_json = defs_json + "," }
|
||||||
|
defs_json = defs_json + "{\"name\":\"" + mname + "\",\"params\":" + params_arr + ",\"body\":" + wrapped_body + ",\"box\":\"" + mbox + "\"}"
|
||||||
|
mi = mi + 1
|
||||||
|
}
|
||||||
|
defs_json = defs_json + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7) Inject defs into Program JSON
|
||||||
|
if defs_json != "" && defs_json.length() > 0 {
|
||||||
|
local ajson = "" + ast_json
|
||||||
|
local close_pos = -1
|
||||||
|
local j = ajson.length() - 1
|
||||||
|
loop(j >= 0) {
|
||||||
|
if ajson.substring(j, j + 1) == "}" { close_pos = j break }
|
||||||
|
j = j - 1
|
||||||
|
}
|
||||||
|
if close_pos >= 0 {
|
||||||
|
ast_json = ajson.substring(0, close_pos) + defs_json + ajson.substring(close_pos, ajson.length())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8) Return JSON (SSOT - single return point)
|
||||||
|
return ast_json
|
||||||
|
}
|
||||||
|
|
||||||
main(args) {
|
main(args) {
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Phase 25.1c: Guaranteed marker for entry point confirmation (dev-only)
|
// Phase 25.1c: Guaranteed marker for entry point confirmation (dev-only)
|
||||||
|
|||||||
@ -72,5 +72,15 @@ pub fn apply_selfhost_compiler_env(cmd: &mut std::process::Command) {
|
|||||||
// Without plugins, the [plugin/missing] warning fires and file I/O fails.
|
// Without plugins, the [plugin/missing] warning fires and file I/O fails.
|
||||||
cmd.env("NYASH_DISABLE_PLUGINS", "0");
|
cmd.env("NYASH_DISABLE_PLUGINS", "0");
|
||||||
|
|
||||||
|
// Phase 28.2b fix: Pass module mappings explicitly to child process.
|
||||||
|
// compiler.hako uses `using lang.compiler.entry.compiler_stageb as StageBMain`.
|
||||||
|
// Child process may not have access to nyash.toml or CWD context, so we pass
|
||||||
|
// the required mappings via NYASH_MODULES environment variable.
|
||||||
|
cmd.env(
|
||||||
|
"NYASH_MODULES",
|
||||||
|
"lang.compiler.entry.compiler_stageb=lang/src/compiler/entry/compiler_stageb.hako,\
|
||||||
|
lang.compiler.entry.compiler=lang/src/compiler/entry/compiler.hako",
|
||||||
|
);
|
||||||
|
|
||||||
// Note: NYASH_USING_AST stays 0 (AST-based using not needed for basic module resolution)
|
// Note: NYASH_USING_AST stays 0 (AST-based using not needed for basic module resolution)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use std::path::Path;
|
|||||||
|
|
||||||
/// Run a Nyash program as a child (`nyash --backend vm <program>`) and capture the first JSON v0 line.
|
/// Run a Nyash program as a child (`nyash --backend vm <program>`) and capture the first JSON v0 line.
|
||||||
/// - `exe`: path to nyash executable
|
/// - `exe`: path to nyash executable
|
||||||
/// - `program`: path to the Nyash script to run (e.g., apps/selfhost/compiler/compiler.hako)
|
/// - `program`: path to the Nyash script to run (e.g., lang/src/compiler/entry/compiler.hako)
|
||||||
/// - `timeout_ms`: kill child after this duration
|
/// - `timeout_ms`: kill child after this duration
|
||||||
/// - `extra_args`: additional args to pass after program (e.g., "--", "--read-tmp")
|
/// - `extra_args`: additional args to pass after program (e.g., "--", "--read-tmp")
|
||||||
/// - `env_remove`: environment variable names to remove for the child
|
/// - `env_remove`: environment variable names to remove for the child
|
||||||
@ -41,11 +41,22 @@ pub fn run_ny_program_capture_json(
|
|||||||
.chars()
|
.chars()
|
||||||
.take(200)
|
.take(200)
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
// Quick Win 2: Show stderr for easier debugging
|
||||||
|
let err_head = String::from_utf8_lossy(&out.stderr)
|
||||||
|
.chars()
|
||||||
|
.take(500)
|
||||||
|
.collect::<String>();
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[selfhost-child] timeout after {} ms; stdout(head)='{}'",
|
"[selfhost-child] timeout after {} ms; stdout(head)='{}'",
|
||||||
timeout_ms,
|
timeout_ms,
|
||||||
head.replace('\n', "\\n")
|
head.replace('\n', "\\n")
|
||||||
);
|
);
|
||||||
|
if !err_head.is_empty() {
|
||||||
|
eprintln!(
|
||||||
|
"[selfhost-child] stderr(head)='{}'",
|
||||||
|
err_head.replace('\n', "\\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let stdout = match String::from_utf8(out.stdout) {
|
let stdout = match String::from_utf8(out.stdout) {
|
||||||
|
|||||||
@ -90,11 +90,22 @@ pub fn exe_try_parse_json_v0(filename: &str, timeout_ms: u64) -> Option<crate::m
|
|||||||
.chars()
|
.chars()
|
||||||
.take(200)
|
.take(200)
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
// Quick Win 2: Show stderr for easier debugging
|
||||||
|
let err_head = String::from_utf8_lossy(&err_buf)
|
||||||
|
.chars()
|
||||||
|
.take(500)
|
||||||
|
.collect::<String>();
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[ny-compiler] exe timeout after {} ms; stdout(head)='{}'",
|
"[ny-compiler] exe timeout after {} ms; stdout(head)='{}'",
|
||||||
timeout_ms,
|
timeout_ms,
|
||||||
head.replace('\n', "\\n")
|
head.replace('\n', "\\n")
|
||||||
);
|
);
|
||||||
|
if !err_head.is_empty() {
|
||||||
|
eprintln!(
|
||||||
|
"[ny-compiler] stderr(head)='{}'",
|
||||||
|
err_head.replace('\n', "\\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let stdout = match String::from_utf8(out_buf) {
|
let stdout = match String::from_utf8(out_buf) {
|
||||||
|
|||||||
@ -363,13 +363,32 @@ impl NyashRunner {
|
|||||||
args: &[Box<dyn crate::box_trait::NyashBox>],
|
args: &[Box<dyn crate::box_trait::NyashBox>],
|
||||||
) -> Result<Box<dyn crate::box_trait::NyashBox>, RuntimeError>
|
) -> Result<Box<dyn crate::box_trait::NyashBox>, RuntimeError>
|
||||||
{
|
{
|
||||||
let opt = { self.decls.read().unwrap().get(name).cloned() };
|
let guard = self.decls.read().unwrap();
|
||||||
|
let opt = guard.get(name).cloned();
|
||||||
let decl = match opt {
|
let decl = match opt {
|
||||||
Some(d) => d,
|
Some(d) => {
|
||||||
|
drop(guard);
|
||||||
|
d
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
|
// Quick Win 1: Show available boxes for easier debugging
|
||||||
|
let mut available: Vec<_> = guard.keys().cloned().collect();
|
||||||
|
available.sort();
|
||||||
|
drop(guard);
|
||||||
|
let hint = if available.is_empty() {
|
||||||
|
"No user-defined boxes available".to_string()
|
||||||
|
} else if available.len() <= 10 {
|
||||||
|
format!("Available: {}", available.join(", "))
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"Available ({} boxes): {}, ...",
|
||||||
|
available.len(),
|
||||||
|
available[..10].join(", ")
|
||||||
|
)
|
||||||
|
};
|
||||||
return Err(RuntimeError::InvalidOperation {
|
return Err(RuntimeError::InvalidOperation {
|
||||||
message: format!("Unknown Box type: {}", name),
|
message: format!("Unknown Box type: {}. {}", name, hint),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut inst = InstanceBox::from_declaration(
|
let mut inst = InstanceBox::from_declaration(
|
||||||
|
|||||||
@ -232,13 +232,32 @@ impl NyashRunner {
|
|||||||
args: &[Box<dyn crate::box_trait::NyashBox>],
|
args: &[Box<dyn crate::box_trait::NyashBox>],
|
||||||
) -> Result<Box<dyn crate::box_trait::NyashBox>, RuntimeError>
|
) -> Result<Box<dyn crate::box_trait::NyashBox>, RuntimeError>
|
||||||
{
|
{
|
||||||
let opt = { self.decls.read().unwrap().get(name).cloned() };
|
let guard = self.decls.read().unwrap();
|
||||||
|
let opt = guard.get(name).cloned();
|
||||||
let decl = match opt {
|
let decl = match opt {
|
||||||
Some(d) => d,
|
Some(d) => {
|
||||||
|
drop(guard);
|
||||||
|
d
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
|
// Quick Win 1: Show available boxes for easier debugging
|
||||||
|
let mut available: Vec<_> = guard.keys().cloned().collect();
|
||||||
|
available.sort();
|
||||||
|
drop(guard);
|
||||||
|
let hint = if available.is_empty() {
|
||||||
|
"No user-defined boxes available".to_string()
|
||||||
|
} else if available.len() <= 10 {
|
||||||
|
format!("Available: {}", available.join(", "))
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"Available ({} boxes): {}, ...",
|
||||||
|
available.len(),
|
||||||
|
available[..10].join(", ")
|
||||||
|
)
|
||||||
|
};
|
||||||
return Err(RuntimeError::InvalidOperation {
|
return Err(RuntimeError::InvalidOperation {
|
||||||
message: format!("Unknown Box type: {}", name),
|
message: format!("Unknown Box type: {}. {}", name, hint),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut inst = InstanceBox::from_declaration(
|
let mut inst = InstanceBox::from_declaration(
|
||||||
@ -446,13 +465,32 @@ impl NyashRunner {
|
|||||||
args: &[Box<dyn crate::box_trait::NyashBox>],
|
args: &[Box<dyn crate::box_trait::NyashBox>],
|
||||||
) -> Result<Box<dyn crate::box_trait::NyashBox>, RuntimeError>
|
) -> Result<Box<dyn crate::box_trait::NyashBox>, RuntimeError>
|
||||||
{
|
{
|
||||||
let opt = { self.decls.read().unwrap().get(name).cloned() };
|
let guard = self.decls.read().unwrap();
|
||||||
|
let opt = guard.get(name).cloned();
|
||||||
let decl = match opt {
|
let decl = match opt {
|
||||||
Some(d) => d,
|
Some(d) => {
|
||||||
|
drop(guard);
|
||||||
|
d
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
|
// Quick Win 1: Show available boxes for easier debugging
|
||||||
|
let mut available: Vec<_> = guard.keys().cloned().collect();
|
||||||
|
available.sort();
|
||||||
|
drop(guard);
|
||||||
|
let hint = if available.is_empty() {
|
||||||
|
"No user-defined boxes available".to_string()
|
||||||
|
} else if available.len() <= 10 {
|
||||||
|
format!("Available: {}", available.join(", "))
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"Available ({} boxes): {}, ...",
|
||||||
|
available.len(),
|
||||||
|
available[..10].join(", ")
|
||||||
|
)
|
||||||
|
};
|
||||||
return Err(RuntimeError::InvalidOperation {
|
return Err(RuntimeError::InvalidOperation {
|
||||||
message: format!("Unknown Box type: {}", name),
|
message: format!("Unknown Box type: {}. {}", name, hint),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut inst = InstanceBox::from_declaration(
|
let mut inst = InstanceBox::from_declaration(
|
||||||
|
|||||||
Reference in New Issue
Block a user