chore: Phase 25.1 完了 - LoopForm v2/Stage1 CLI/環境変数削減 + Phase 26-D からの変更

Phase 25.1 完了成果:
-  LoopForm v2 テスト・ドキュメント・コメント完備
  - 4ケース(A/B/C/D)完全テストカバレッジ
  - 最小再現ケース作成(SSAバグ調査用)
  - SSOT文書作成(loopform_ssot.md)
  - 全ソースに [LoopForm] コメントタグ追加

-  Stage-1 CLI デバッグ環境構築
  - stage1_cli.hako 実装
  - stage1_bridge.rs ブリッジ実装
  - デバッグツール作成(stage1_debug.sh/stage1_minimal.sh)
  - アーキテクチャ改善提案文書

-  環境変数削減計画策定
  - 25変数の完全調査・分類
  - 6段階削減ロードマップ(25→5、80%削減)
  - 即時削除可能変数特定(NYASH_CONFIG/NYASH_DEBUG)

Phase 26-D からの累積変更:
- PHI実装改善(ExitPhiBuilder/HeaderPhiBuilder等)
- MIRビルダーリファクタリング
- 型伝播・最適化パス改善
- その他約300ファイルの累積変更

🎯 技術的成果:
- SSAバグ根本原因特定(条件分岐内loop変数変更)
- Region+next_iパターン適用完了(UsingCollectorBox等)
- LoopFormパターン文書化・テスト化完了
- セルフホスティング基盤強化

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: ChatGPT <noreply@openai.com>
Co-Authored-By: Task Assistant <task@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-21 06:25:17 +09:00
parent baf028a94f
commit f9d100ce01
366 changed files with 14322 additions and 5236 deletions

View File

@ -2,16 +2,22 @@
// Kept tiny and isolated. Linkage names match include/nyrt.h.
#[no_mangle]
pub extern "C" fn nyrt_init() -> i32 { 0 }
pub extern "C" fn nyrt_init() -> i32 {
0
}
#[no_mangle]
pub extern "C" fn nyrt_teardown() { }
pub extern "C" fn nyrt_teardown() {}
#[no_mangle]
pub extern "C" fn nyrt_load_mir_json(_json_text: *const ::std::os::raw::c_char) -> u64 { 1 }
pub extern "C" fn nyrt_load_mir_json(_json_text: *const ::std::os::raw::c_char) -> u64 {
1
}
#[no_mangle]
pub extern "C" fn nyrt_exec_main(_module_handle: u64) -> i32 { 0 }
pub extern "C" fn nyrt_exec_main(_module_handle: u64) -> i32 {
0
}
#[no_mangle]
pub extern "C" fn nyrt_hostcall(
@ -20,7 +26,9 @@ pub extern "C" fn nyrt_hostcall(
_payload_json: *const ::std::os::raw::c_char,
_out_buf: *mut ::std::os::raw::c_char,
_out_buf_len: u32,
) -> i32 { 0 }
) -> i32 {
0
}
#[cfg(test)]
mod tests {

View File

@ -10,8 +10,8 @@ use std::collections::HashMap;
use std::fmt;
mod span;
pub use span::Span;
mod utils;
mod nodes;
mod utils;
pub use nodes::*;
// Span は src/ast/span.rs へ分離re-export で後方互換維持)
@ -573,7 +573,6 @@ pub enum ASTNode {
/// meフィールドアクセス: me.field
MeField { field: String, span: Span },
/// ローカル変数宣言: local x, y, z
Local {
variables: Vec<String>,
@ -584,10 +583,7 @@ pub enum ASTNode {
/// ScopeBoxオプション: 診断/マクロ可視性のためのno-opスコープ。
/// 正規化で注入され、MIRビルダがブロックとして処理意味不変
ScopeBox {
body: Vec<ASTNode>,
span: Span,
},
ScopeBox { body: Vec<ASTNode>, span: Span },
/// Outbox変数宣言: outbox x, y, z (static関数内専用)
Outbox {

View File

@ -22,7 +22,15 @@ impl TryFrom<ASTNode> for AssignStmt {
type Error = ASTNode;
fn try_from(node: ASTNode) -> Result<Self, Self::Error> {
match node {
ASTNode::Assignment { target, value, span } => Ok(AssignStmt { target, value, span }),
ASTNode::Assignment {
target,
value,
span,
} => Ok(AssignStmt {
target,
value,
span,
}),
other => Err(other),
}
}
@ -30,7 +38,11 @@ impl TryFrom<ASTNode> for AssignStmt {
impl From<AssignStmt> for ASTNode {
fn from(s: AssignStmt) -> Self {
ASTNode::Assignment { target: s.target, value: s.value, span: s.span }
ASTNode::Assignment {
target: s.target,
value: s.value,
span: s.span,
}
}
}
@ -52,7 +64,10 @@ impl TryFrom<ASTNode> for ReturnStmt {
impl From<ReturnStmt> for ASTNode {
fn from(s: ReturnStmt) -> Self {
ASTNode::Return { value: s.value, span: s.span }
ASTNode::Return {
value: s.value,
span: s.span,
}
}
}
@ -68,7 +83,17 @@ impl TryFrom<ASTNode> for IfStmt {
type Error = ASTNode;
fn try_from(node: ASTNode) -> Result<Self, Self::Error> {
match node {
ASTNode::If { condition, then_body, else_body, span } => Ok(IfStmt { condition, then_body, else_body, span }),
ASTNode::If {
condition,
then_body,
else_body,
span,
} => Ok(IfStmt {
condition,
then_body,
else_body,
span,
}),
other => Err(other),
}
}
@ -76,7 +101,12 @@ impl TryFrom<ASTNode> for IfStmt {
impl From<IfStmt> for ASTNode {
fn from(s: IfStmt) -> Self {
ASTNode::If { condition: s.condition, then_body: s.then_body, else_body: s.else_body, span: s.span }
ASTNode::If {
condition: s.condition,
then_body: s.then_body,
else_body: s.else_body,
span: s.span,
}
}
}
@ -96,8 +126,17 @@ impl TryFrom<ASTNode> for BinaryExpr {
type Error = ASTNode;
fn try_from(node: ASTNode) -> Result<Self, Self::Error> {
match node {
ASTNode::BinaryOp { operator, left, right, span } =>
Ok(BinaryExpr { operator, left, right, span }),
ASTNode::BinaryOp {
operator,
left,
right,
span,
} => Ok(BinaryExpr {
operator,
left,
right,
span,
}),
other => Err(other),
}
}
@ -105,7 +144,12 @@ impl TryFrom<ASTNode> for BinaryExpr {
impl From<BinaryExpr> for ASTNode {
fn from(e: BinaryExpr) -> Self {
ASTNode::BinaryOp { operator: e.operator, left: e.left, right: e.right, span: e.span }
ASTNode::BinaryOp {
operator: e.operator,
left: e.left,
right: e.right,
span: e.span,
}
}
}
@ -120,7 +164,15 @@ impl TryFrom<ASTNode> for CallExpr {
type Error = ASTNode;
fn try_from(node: ASTNode) -> Result<Self, Self::Error> {
match node {
ASTNode::FunctionCall { name, arguments, span } => Ok(CallExpr { name, arguments, span }),
ASTNode::FunctionCall {
name,
arguments,
span,
} => Ok(CallExpr {
name,
arguments,
span,
}),
other => Err(other),
}
}
@ -128,7 +180,11 @@ impl TryFrom<ASTNode> for CallExpr {
impl From<CallExpr> for ASTNode {
fn from(c: CallExpr) -> Self {
ASTNode::FunctionCall { name: c.name, arguments: c.arguments, span: c.span }
ASTNode::FunctionCall {
name: c.name,
arguments: c.arguments,
span: c.span,
}
}
}
@ -144,8 +200,17 @@ impl TryFrom<ASTNode> for MethodCallExpr {
type Error = ASTNode;
fn try_from(node: ASTNode) -> Result<Self, Self::Error> {
match node {
ASTNode::MethodCall { object, method, arguments, span } =>
Ok(MethodCallExpr { object, method, arguments, span }),
ASTNode::MethodCall {
object,
method,
arguments,
span,
} => Ok(MethodCallExpr {
object,
method,
arguments,
span,
}),
other => Err(other),
}
}
@ -153,7 +218,12 @@ impl TryFrom<ASTNode> for MethodCallExpr {
impl From<MethodCallExpr> for ASTNode {
fn from(m: MethodCallExpr) -> Self {
ASTNode::MethodCall { object: m.object, method: m.method, arguments: m.arguments, span: m.span }
ASTNode::MethodCall {
object: m.object,
method: m.method,
arguments: m.arguments,
span: m.span,
}
}
}
@ -168,8 +238,15 @@ impl TryFrom<ASTNode> for FieldAccessExpr {
type Error = ASTNode;
fn try_from(node: ASTNode) -> Result<Self, Self::Error> {
match node {
ASTNode::FieldAccess { object, field, span } =>
Ok(FieldAccessExpr { object, field, span }),
ASTNode::FieldAccess {
object,
field,
span,
} => Ok(FieldAccessExpr {
object,
field,
span,
}),
other => Err(other),
}
}
@ -177,6 +254,10 @@ impl TryFrom<ASTNode> for FieldAccessExpr {
impl From<FieldAccessExpr> for ASTNode {
fn from(f: FieldAccessExpr) -> Self {
ASTNode::FieldAccess { object: f.object, field: f.field, span: f.span }
ASTNode::FieldAccess {
object: f.object,
field: f.field,
span: f.span,
}
}
}

View File

@ -35,7 +35,7 @@ impl ASTNode {
ASTNode::FromCall { .. } => "FromCall",
ASTNode::ThisField { .. } => "ThisField",
ASTNode::MeField { .. } => "MeField",
ASTNode::Local { .. } => "Local",
ASTNode::Outbox { .. } => "Outbox",
ASTNode::FunctionCall { .. } => "FunctionCall",
@ -277,7 +277,7 @@ impl ASTNode {
ASTNode::MeField { field, .. } => {
format!("MeField({})", field)
}
ASTNode::Local { variables, .. } => {
format!("Local({})", variables.join(", "))
}
@ -368,7 +368,7 @@ impl ASTNode {
ASTNode::FromCall { span, .. } => *span,
ASTNode::ThisField { span, .. } => *span,
ASTNode::MeField { span, .. } => *span,
ASTNode::Local { span, .. } => *span,
ASTNode::Outbox { span, .. } => *span,
ASTNode::FunctionCall { span, .. } => *span,

View File

@ -54,10 +54,20 @@ pub fn eq_vm(a: &VMValue, b: &VMValue) -> bool {
(Float(x), Integer(y)) => *x == (*y as f64),
(BoxRef(ax), BoxRef(by)) => Arc::ptr_eq(ax, by),
// Treat BoxRef(VoidBox/MissingBox) as equal to Void (null) for backward compatibility
(BoxRef(bx), Void) => bx.as_any().downcast_ref::<VoidBox>().is_some()
|| bx.as_any().downcast_ref::<crate::boxes::missing_box::MissingBox>().is_some(),
(Void, BoxRef(bx)) => bx.as_any().downcast_ref::<VoidBox>().is_some()
|| bx.as_any().downcast_ref::<crate::boxes::missing_box::MissingBox>().is_some(),
(BoxRef(bx), Void) => {
bx.as_any().downcast_ref::<VoidBox>().is_some()
|| bx
.as_any()
.downcast_ref::<crate::boxes::missing_box::MissingBox>()
.is_some()
}
(Void, BoxRef(bx)) => {
bx.as_any().downcast_ref::<VoidBox>().is_some()
|| bx
.as_any()
.downcast_ref::<crate::boxes::missing_box::MissingBox>()
.is_some()
}
_ => false,
}
}

View File

@ -20,9 +20,7 @@ impl MirInterpreter {
if self.call_depth > MAX_CALL_DEPTH {
eprintln!(
"[vm-call-depth] exceeded {} in fn={} (depth={})",
MAX_CALL_DEPTH,
func.signature.name,
self.call_depth
MAX_CALL_DEPTH, func.signature.name, self.call_depth
);
self.call_depth = self.call_depth.saturating_sub(1);
return Err(VMError::InvalidInstruction(format!(
@ -31,7 +29,9 @@ impl MirInterpreter {
)));
}
// Phase 1: delegate cross-class reroute / narrow fallbacks to method_router
if let Some(r) = super::method_router::pre_exec_reroute(self, func, arg_vals) { return r; }
if let Some(r) = super::method_router::pre_exec_reroute(self, func, arg_vals) {
return r;
}
let saved_regs = mem::take(&mut self.regs);
let saved_fn = self.cur_fn.clone();
self.cur_fn = Some(func.signature.name.clone());
@ -59,7 +59,11 @@ impl MirInterpreter {
let max_steps: u64 = std::env::var("HAKO_VM_MAX_STEPS")
.ok()
.and_then(|v| v.parse::<u64>().ok())
.or_else(|| std::env::var("NYASH_VM_MAX_STEPS").ok().and_then(|v| v.parse::<u64>().ok()))
.or_else(|| {
std::env::var("NYASH_VM_MAX_STEPS")
.ok()
.and_then(|v| v.parse::<u64>().ok())
})
.unwrap_or(1_000_000);
let mut steps: u64 = 0;
@ -133,9 +137,7 @@ impl MirInterpreter {
if trace_phi {
eprintln!(
"[vm-trace-phi] enter bb={:?} last_pred={:?} preds={:?}",
block.id,
last_pred,
block.predecessors
block.id, last_pred, block.predecessors
);
}
for inst in block.phi_instructions() {
@ -154,7 +156,11 @@ impl MirInterpreter {
Ok(v) => v,
Err(e) => {
// Dev safety valve: tolerate undefined phi inputs by substituting Void
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") {
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED")
.ok()
.as_deref()
== Some("1")
{
if Self::trace_enabled() {
eprintln!("[vm-trace] phi tolerate undefined input {:?} -> Void (err={:?})", val, e);
}
@ -179,17 +185,41 @@ impl MirInterpreter {
// Strict PHI (default ON). Can be disabled with HAKO_VM_PHI_STRICT=0 (or NYASH_VM_PHI_STRICT=0).
let strict = {
let on = |s: &str| {
matches!(s, "1"|"true"|"on"|"yes"|"y"|"t"|"enabled"|"enable")
matches!(
s,
"1" | "true" | "on" | "yes" | "y" | "t" | "enabled" | "enable"
)
};
let off = |s: &str| {
matches!(s, "0"|"false"|"off"|"no"|"n"|"f"|"disabled"|"disable")
matches!(
s,
"0" | "false"
| "off"
| "no"
| "n"
| "f"
| "disabled"
| "disable"
)
};
if let Ok(v) = std::env::var("HAKO_VM_PHI_STRICT") {
let v = v.to_ascii_lowercase();
if off(&v) { false } else if on(&v) { true } else { true }
if off(&v) {
false
} else if on(&v) {
true
} else {
true
}
} else if let Ok(v) = std::env::var("NYASH_VM_PHI_STRICT") {
let v = v.to_ascii_lowercase();
if off(&v) { false } else if on(&v) { true } else { true }
if off(&v) {
false
} else if on(&v) {
true
} else {
true
}
} else {
true
}
@ -213,7 +243,11 @@ impl MirInterpreter {
let v = match self.reg_load(*val0) {
Ok(v) => v,
Err(e) => {
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") {
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED")
.ok()
.as_deref()
== Some("1")
{
if Self::trace_enabled() {
eprintln!("[vm-trace] phi missing pred match {:?}; fallback first input {:?} -> Void (err={:?})", pred, val0, e);
}
@ -243,7 +277,11 @@ impl MirInterpreter {
let v = match self.reg_load(*val) {
Ok(v) => v,
Err(e) => {
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") {
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED")
.ok()
.as_deref()
== Some("1")
{
if Self::trace_enabled() {
eprintln!("[vm-trace] phi tolerate undefined default input {:?} -> Void (err={:?})", val, e);
}
@ -255,10 +293,7 @@ impl MirInterpreter {
};
self.regs.insert(dst_id, v);
if Self::trace_enabled() {
eprintln!(
"[vm-trace] phi dst={:?} take default val={:?}",
dst_id, val
);
eprintln!("[vm-trace] phi dst={:?} take default val={:?}", dst_id, val);
}
}
}

View File

@ -32,7 +32,8 @@ impl MirInterpreter {
if let Some(op_fn) = self.functions.get("AddOperator.apply/2").cloned() {
if !in_guard {
if crate::config::env::operator_box_add_adopt() {
let out = self.exec_function_inner(&op_fn, Some(&[a.clone(), b.clone()]))?;
let out =
self.exec_function_inner(&op_fn, Some(&[a.clone(), b.clone()]))?;
self.regs.insert(dst, out);
return Ok(());
} else {

View File

@ -20,7 +20,9 @@ impl MirInterpreter {
let s_opt: Option<String> = match v0.clone() {
VMValue::String(s) => Some(s),
VMValue::BoxRef(b) => {
if let Some(sb) = b.as_any().downcast_ref::<crate::boxes::basic::StringBox>() {
if let Some(sb) =
b.as_any().downcast_ref::<crate::boxes::basic::StringBox>()
{
Some(sb.value.clone())
} else {
None
@ -29,10 +31,13 @@ impl MirInterpreter {
_ => None,
};
if let Some(s) = s_opt {
let boxed: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::boxes::basic::StringBox::new(s));
let boxed: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::boxes::basic::StringBox::new(s));
let created_vm = VMValue::from_nyash_box(boxed);
self.regs.insert(dst, created_vm);
if Self::box_trace_enabled() { self.box_trace_emit_new(box_type, args.len()); }
if Self::box_trace_enabled() {
self.box_trace_emit_new(box_type, args.len());
}
return Ok(());
}
}
@ -88,7 +93,7 @@ impl MirInterpreter {
Err(e) => {
return Err(self.err_with_context(
&format!("PluginInvoke {}.{}", p.box_type, method),
&format!("{:?}", e)
&format!("{:?}", e),
))
}
}
@ -115,7 +120,10 @@ impl MirInterpreter {
return Ok(());
}
if let VMValue::BoxRef(b) = self.reg_load(box_val)? {
if b.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
if b.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
self.write_string(dst, "null".to_string());
return Ok(());
}
@ -125,7 +133,8 @@ impl MirInterpreter {
if Self::box_trace_enabled() {
let cls = match self.reg_load(box_val).unwrap_or(VMValue::Void) {
VMValue::BoxRef(b) => {
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>()
{
inst.class_name.clone()
} else {
b.type_name().to_string()
@ -167,7 +176,10 @@ impl MirInterpreter {
}
}
VMValue::BoxRef(ref b) => {
if b.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
if b.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
if let Some(val) = super::boxes_void_guards::handle_void_method(method) {
self.write_result(dst, val);
return Ok(());
@ -252,9 +264,12 @@ impl MirInterpreter {
// Determine receiver class name when possible
let recv_cls: Option<String> = match self.reg_load(box_val).ok() {
Some(VMValue::BoxRef(b)) => {
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>()
{
Some(inst.class_name.clone())
} else { None }
} else {
None
}
}
_ => None,
};
@ -262,13 +277,19 @@ impl MirInterpreter {
let prefix = format!("{}.", want);
cands.retain(|k| k.starts_with(&prefix));
}
if cands.len() == 1 { self.functions.get(&cands[0]).cloned() } else { None }
if cands.len() == 1 {
self.functions.get(&cands[0]).cloned()
} else {
None
}
} {
// Build argv: pass receiver as first arg (me)
let recv_vm = self.reg_load(box_val)?;
let mut argv: Vec<VMValue> = Vec::with_capacity(1 + args.len());
argv.push(recv_vm);
for a in args { argv.push(self.reg_load(*a)?); }
for a in args {
argv.push(self.reg_load(*a)?);
}
let ret = self.exec_function_inner(&func, Some(&argv))?;
self.write_result(dst, ret);
return Ok(());
@ -312,5 +333,4 @@ impl MirInterpreter {
) -> Result<bool, VMError> {
super::boxes_array::try_handle_array_box(self, dst, box_val, method, args)
}
}

View File

@ -8,56 +8,58 @@ pub(super) fn try_handle_array_box(
method: &str,
args: &[ValueId],
) -> Result<bool, VMError> {
let recv = this.reg_load(box_val)?;
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
VMValue::BoxRef(b) => b.share_box(),
other => other.to_nyash_box(),
};
if let Some(ab) = recv_box_any
.as_any()
.downcast_ref::<crate::boxes::array::ArrayBox>()
{
match method {
"birth" => {
// No-op constructor init
this.write_void(dst);
return Ok(true);
}
"push" => {
this.validate_args_exact("push", args, 1)?;
let val = this.load_as_box(args[0])?;
let _ = ab.push(val);
this.write_void(dst);
return Ok(true);
}
"pop" => {
if !args.is_empty() { return Err(this.err_invalid("pop expects 0 args")); }
let ret = ab.pop();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"len" | "length" | "size" => {
let ret = ab.length();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"get" => {
this.validate_args_exact("get", args, 1)?;
let idx = this.load_as_box(args[0])?;
let ret = ab.get(idx);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"set" => {
this.validate_args_exact("set", args, 2)?;
let idx = this.load_as_box(args[0])?;
let val = this.load_as_box(args[1])?;
let _ = ab.set(idx, val);
this.write_void(dst);
return Ok(true);
}
_ => {}
let recv = this.reg_load(box_val)?;
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
VMValue::BoxRef(b) => b.share_box(),
other => other.to_nyash_box(),
};
if let Some(ab) = recv_box_any
.as_any()
.downcast_ref::<crate::boxes::array::ArrayBox>()
{
match method {
"birth" => {
// No-op constructor init
this.write_void(dst);
return Ok(true);
}
"push" => {
this.validate_args_exact("push", args, 1)?;
let val = this.load_as_box(args[0])?;
let _ = ab.push(val);
this.write_void(dst);
return Ok(true);
}
"pop" => {
if !args.is_empty() {
return Err(this.err_invalid("pop expects 0 args"));
}
let ret = ab.pop();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"len" | "length" | "size" => {
let ret = ab.length();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"get" => {
this.validate_args_exact("get", args, 1)?;
let idx = this.load_as_box(args[0])?;
let ret = ab.get(idx);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"set" => {
this.validate_args_exact("set", args, 2)?;
let idx = this.load_as_box(args[0])?;
let val = this.load_as_box(args[1])?;
let _ = ab.set(idx, val);
this.write_void(dst);
return Ok(true);
}
_ => {}
}
Ok(false)
}
Ok(false)
}

View File

@ -14,26 +14,39 @@ pub(super) fn try_handle_instance_box(
other => other.to_nyash_box(),
};
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "toString" {
eprintln!("[vm-trace] instance-check recv_box_any.type={} args_len={}", recv_box_any.type_name(), args.len());
eprintln!(
"[vm-trace] instance-check recv_box_any.type={} args_len={}",
recv_box_any.type_name(),
args.len()
);
}
if let Some(inst) = recv_box_any.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = recv_box_any
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
// Development guard: ensure JsonScanner core fields have sensible defaults
if inst.class_name == "JsonScanner" {
// populate missing fields to avoid Void in comparisons inside is_eof/advance
if inst.get_field_ng("position").is_none() {
let _ = inst.set_field_ng("position".to_string(), crate::value::NyashValue::Integer(0));
let _ =
inst.set_field_ng("position".to_string(), crate::value::NyashValue::Integer(0));
}
if inst.get_field_ng("length").is_none() {
let _ = inst.set_field_ng("length".to_string(), crate::value::NyashValue::Integer(0));
let _ =
inst.set_field_ng("length".to_string(), crate::value::NyashValue::Integer(0));
}
if inst.get_field_ng("line").is_none() {
let _ = inst.set_field_ng("line".to_string(), crate::value::NyashValue::Integer(1));
}
if inst.get_field_ng("column").is_none() {
let _ = inst.set_field_ng("column".to_string(), crate::value::NyashValue::Integer(1));
let _ =
inst.set_field_ng("column".to_string(), crate::value::NyashValue::Integer(1));
}
if inst.get_field_ng("text").is_none() {
let _ = inst.set_field_ng("text".to_string(), crate::value::NyashValue::String(String::new()));
let _ = inst.set_field_ng(
"text".to_string(),
crate::value::NyashValue::String(String::new()),
);
}
}
// JsonNodeInstance narrow bridges removed: rely on builder rewrite and instance dispatch
@ -47,11 +60,26 @@ pub(super) fn try_handle_instance_box(
);
}
// Resolve lowered method function: "Class.method/arity"
let primary = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len()));
let primary = format!(
"{}.{}{}",
inst.class_name,
method,
format!("/{}", args.len())
);
// Alternate naming: "ClassInstance.method/arity"
let alt = format!("{}Instance.{}{}", inst.class_name, method, format!("/{}", args.len()));
let alt = format!(
"{}Instance.{}{}",
inst.class_name,
method,
format!("/{}", args.len())
);
// Static method variant that takes 'me' explicitly as first arg: "Class.method/(arity+1)"
let static_variant = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len() + 1));
let static_variant = format!(
"{}.{}{}",
inst.class_name,
method,
format!("/{}", args.len() + 1)
);
// Special-case: toString() → stringify/0 if present
// Prefer base class (strip trailing "Instance") stringify when available.
let (stringify_base, stringify_inst) = if method == "toString" && args.is_empty() {
@ -64,27 +92,43 @@ pub(super) fn try_handle_instance_box(
Some(format!("{}.stringify/0", base_name)),
Some(format!("{}.stringify/0", inst.class_name)),
)
} else { (None, None) };
} else {
(None, None)
};
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!(
"[vm-trace] instance-dispatch class={} method={} arity={} candidates=[{}, {}, {}]",
inst.class_name, method, args.len(), primary, alt, static_variant
inst.class_name,
method,
args.len(),
primary,
alt,
static_variant
);
}
// Prefer stringify for toString() if present (semantic alias). Try instance first, then base.
let func_opt = if let Some(ref sname) = stringify_inst {
this.functions.get(sname).cloned()
} else { None }
.or_else(|| stringify_base.as_ref().and_then(|n| this.functions.get(n).cloned()))
} else {
None
}
.or_else(|| {
stringify_base
.as_ref()
.and_then(|n| this.functions.get(n).cloned())
})
.or_else(|| this.functions.get(&primary).cloned())
.or_else(|| this.functions.get(&alt).cloned())
.or_else(|| this.functions.get(&static_variant).cloned());
if let Some(func) = func_opt {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] instance-dispatch hit -> {}", func.signature.name);
eprintln!(
"[vm-trace] instance-dispatch hit -> {}",
func.signature.name
);
}
// Build argv: me + args (works for both instance and static(me, ...))
let mut argv: Vec<VMValue> = Vec::with_capacity(1 + args.len());
@ -95,7 +139,9 @@ pub(super) fn try_handle_instance_box(
}
}
argv.push(recv_vm.clone());
for a in args { argv.push(this.reg_load(*a)?); }
for a in args {
argv.push(this.reg_load(*a)?);
}
let ret = this.exec_function_inner(&func, Some(&argv))?;
this.write_result(dst, ret);
return Ok(true);
@ -120,19 +166,28 @@ pub(super) fn try_handle_instance_box(
if filtered.len() == 1 {
let fname = &filtered[0];
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] instance-dispatch fallback (scoped) -> {}", fname);
eprintln!(
"[vm-trace] instance-dispatch fallback (scoped) -> {}",
fname
);
}
if let Some(func) = this.functions.get(fname).cloned() {
let mut argv: Vec<VMValue> = Vec::with_capacity(1 + args.len());
if method == "birth" && crate::config::env::using_is_dev() {
if matches!(recv_vm, VMValue::Void) {
return Err(this.err_invalid("Dev assert: birth(me==Void) is forbidden"));
return Err(
this.err_invalid("Dev assert: birth(me==Void) is forbidden")
);
}
}
argv.push(recv_vm.clone());
for a in args { argv.push(this.reg_load(*a)?); }
for a in args {
argv.push(this.reg_load(*a)?);
}
let ret = this.exec_function_inner(&func, Some(&argv))?;
if let Some(d) = dst { this.regs.insert(d, ret); }
if let Some(d) = dst {
this.regs.insert(d, ret);
}
return Ok(true);
}
} else if filtered.len() > 1 {
@ -143,7 +198,11 @@ pub(super) fn try_handle_instance_box(
} else {
// No same-class candidate: do not dispatch cross-class
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] instance-dispatch no same-class candidate for tail .{}{}", method, format!("/{}", args.len()));
eprintln!(
"[vm-trace] instance-dispatch no same-class candidate for tail .{}{}",
method,
format!("/{}", args.len())
);
}
}
}

View File

@ -8,115 +8,130 @@ pub(super) fn try_handle_map_box(
method: &str,
args: &[ValueId],
) -> Result<bool, VMError> {
let recv = this.reg_load(box_val)?;
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
VMValue::BoxRef(b) => b.share_box(),
other => other.to_nyash_box(),
};
if let Some(mb) = recv_box_any
.as_any()
.downcast_ref::<crate::boxes::map_box::MapBox>()
{
match method {
"birth" => {
// No-op constructor init for MapBox
this.write_void(dst);
return Ok(true);
}
// Field bridge: treat getField/setField as get/set with string key
"getField" => {
this.validate_args_exact("MapBox.getField", args, 1)?;
let k_vm = this.reg_load(args[0])?;
// Field access expects a String key; otherwise return a stable tag.
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(dst, VMValue::String("[map/bad-key] field name must be string".to_string()));
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let ret = mb.get(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"setField" => {
this.validate_args_exact("MapBox.setField", args, 2)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(dst, VMValue::String("[map/bad-key] field name must be string".to_string()));
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let v = this.load_as_box(args[1])?;
let ret = mb.set(k, v);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"set" => {
this.validate_args_exact("MapBox.set", args, 2)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(dst, VMValue::String("[map/bad-key] key must be string".to_string()));
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let v = this.load_as_box(args[1])?;
let ret = mb.set(k, v);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"get" => {
this.validate_args_exact("MapBox.get", args, 1)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(dst, VMValue::String("[map/bad-key] key must be string".to_string()));
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let ret = mb.get(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"has" => {
this.validate_args_exact("MapBox.has", args, 1)?;
let k = this.load_as_box(args[0])?;
let ret = mb.has(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"delete" => {
this.validate_args_exact("MapBox.delete", args, 1)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(dst, VMValue::String("[map/bad-key] key must be string".to_string()));
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let ret = mb.delete(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"len" | "length" | "size" => {
let ret = mb.size();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"keys" => {
let ret = mb.keys();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"values" => {
let ret = mb.values();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"clear" => {
// Reset map to empty; return a neutral value
let ret = mb.clear();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
_ => {}
let recv = this.reg_load(box_val)?;
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
VMValue::BoxRef(b) => b.share_box(),
other => other.to_nyash_box(),
};
if let Some(mb) = recv_box_any
.as_any()
.downcast_ref::<crate::boxes::map_box::MapBox>()
{
match method {
"birth" => {
// No-op constructor init for MapBox
this.write_void(dst);
return Ok(true);
}
// Field bridge: treat getField/setField as get/set with string key
"getField" => {
this.validate_args_exact("MapBox.getField", args, 1)?;
let k_vm = this.reg_load(args[0])?;
// Field access expects a String key; otherwise return a stable tag.
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(
dst,
VMValue::String("[map/bad-key] field name must be string".to_string()),
);
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let ret = mb.get(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"setField" => {
this.validate_args_exact("MapBox.setField", args, 2)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(
dst,
VMValue::String("[map/bad-key] field name must be string".to_string()),
);
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let v = this.load_as_box(args[1])?;
let ret = mb.set(k, v);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"set" => {
this.validate_args_exact("MapBox.set", args, 2)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(
dst,
VMValue::String("[map/bad-key] key must be string".to_string()),
);
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let v = this.load_as_box(args[1])?;
let ret = mb.set(k, v);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"get" => {
this.validate_args_exact("MapBox.get", args, 1)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(
dst,
VMValue::String("[map/bad-key] key must be string".to_string()),
);
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let ret = mb.get(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"has" => {
this.validate_args_exact("MapBox.has", args, 1)?;
let k = this.load_as_box(args[0])?;
let ret = mb.has(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"delete" => {
this.validate_args_exact("MapBox.delete", args, 1)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(
dst,
VMValue::String("[map/bad-key] key must be string".to_string()),
);
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let ret = mb.delete(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"len" | "length" | "size" => {
let ret = mb.size();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"keys" => {
let ret = mb.keys();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"values" => {
let ret = mb.values();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"clear" => {
// Reset map to empty; return a neutral value
let ret = mb.clear();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
_ => {}
}
Ok(false)
}
Ok(false)
}

View File

@ -9,8 +9,8 @@ pub(super) fn try_handle_object_fields(
) -> Result<bool, VMError> {
// Local helpers to bridge NyashValue <-> VMValue for InstanceBox fields
fn vm_to_nv(v: &VMValue) -> crate::value::NyashValue {
use crate::value::NyashValue as NV;
use super::VMValue as VV;
use crate::value::NyashValue as NV;
match v {
VV::Integer(i) => NV::Integer(*i),
VV::Float(f) => NV::Float(*f),
@ -18,12 +18,12 @@ pub(super) fn try_handle_object_fields(
VV::String(s) => NV::String(s.clone()),
VV::Void => NV::Void,
VV::Future(_) => NV::Void, // not expected in fields
VV::BoxRef(_) => NV::Void, // store minimal; complex object fields are not required here
VV::BoxRef(_) => NV::Void, // store minimal; complex object fields are not required here
}
}
fn nv_to_vm(v: &crate::value::NyashValue) -> VMValue {
use crate::value::NyashValue as NV;
use super::VMValue as VV;
use crate::value::NyashValue as NV;
match v {
NV::Integer(i) => VV::Integer(*i),
NV::Float(f) => VV::Float(*f),
@ -48,7 +48,8 @@ pub(super) fn try_handle_object_fields(
// Create a temporary value to hold the singleton
let temp_id = ValueId(999999999); // Temporary ID for singleton
this.regs.insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone)));
this.regs
.insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone)));
temp_id
} else {
box_val
@ -59,18 +60,27 @@ pub(super) fn try_handle_object_fields(
// MapBox special-case: bridge to MapBox.get, with string-only key
if let Ok(VMValue::BoxRef(bref)) = this.reg_load(actual_box_val) {
if bref.as_any().downcast_ref::<crate::boxes::map_box::MapBox>().is_some() {
if bref
.as_any()
.downcast_ref::<crate::boxes::map_box::MapBox>()
.is_some()
{
let key_vm = this.reg_load(args[0])?;
if let VMValue::String(_) = key_vm {
let k = key_vm.to_nyash_box();
let map = bref.share_box();
if let Some(mb) = map.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
if let Some(mb) =
map.as_any().downcast_ref::<crate::boxes::map_box::MapBox>()
{
let ret = mb.get(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
} else {
this.write_string(dst, "[map/bad-key] field name must be string".to_string());
this.write_string(
dst,
"[map/bad-key] field name must be string".to_string(),
);
return Ok(true);
}
}
@ -94,15 +104,22 @@ pub(super) fn try_handle_object_fields(
};
// Prefer InstanceBox internal storage (structural correctness)
if let VMValue::BoxRef(bref) = this.reg_load(actual_box_val)? {
if let Some(inst) = bref.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = bref
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] getField instance class={}", inst.class_name);
}
// Special-case bridge: JsonParser.length -> tokens.length()
if inst.class_name == "JsonParser" && fname == "length" {
if let Some(tokens_shared) = inst.get_field("tokens") {
let tokens_box: Box<dyn crate::box_trait::NyashBox> = tokens_shared.share_box();
if let Some(arr) = tokens_box.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let tokens_box: Box<dyn crate::box_trait::NyashBox> =
tokens_shared.share_box();
if let Some(arr) = tokens_box
.as_any()
.downcast_ref::<crate::boxes::array::ArrayBox>()
{
let len_box = arr.length();
this.write_result(dst, VMValue::from_nyash_box(len_box));
return Ok(true);
@ -112,7 +129,9 @@ pub(super) fn try_handle_object_fields(
// First: prefer fields_ng (NyashValue) when present
if let Some(nv) = inst.get_field_ng(&fname) {
// Dev trace: JsonToken field get
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && inst.class_name == "JsonToken" {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
&& inst.class_name == "JsonToken"
{
eprintln!("[vm-trace] JsonToken.getField name={} nv={:?}", fname, nv);
}
// Treat complex Box-like values as "missing" for internal storage so that
@ -129,13 +148,18 @@ pub(super) fn try_handle_object_fields(
);
if !is_missing {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] getField internal {}.{} -> {:?}", inst.class_name, fname, nv);
eprintln!(
"[vm-trace] getField internal {}.{} -> {:?}",
inst.class_name, fname, nv
);
}
// Special-case: NV::Box should surface as VMValue::BoxRef
if let crate::value::NyashValue::Box(ref arc_m) = nv {
if let Ok(guard) = arc_m.lock() {
let cloned: Box<dyn crate::box_trait::NyashBox> = guard.clone_box();
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(cloned);
let cloned: Box<dyn crate::box_trait::NyashBox> =
guard.clone_box();
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> =
std::sync::Arc::from(cloned);
this.write_result(dst, VMValue::BoxRef(arc));
} else {
this.write_void(dst);
@ -170,8 +194,12 @@ pub(super) fn try_handle_object_fields(
_ => None,
};
if let Some(v) = def {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] getField default JsonScanner.{} -> {:?}", fname, v);
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
{
eprintln!(
"[vm-trace] getField default JsonScanner.{} -> {:?}",
fname, v
);
}
this.write_result(dst, v);
return Ok(true);
@ -229,10 +257,14 @@ pub(super) fn try_handle_object_fields(
// JsonScanner fields inside JsonScanner.{is_eof,current,advance}, provide
// pragmatic defaults to avoid Void comparisons during bring-up.
if let VMValue::Void = v {
let guard_on = std::env::var("NYASH_VM_SCANNER_DEFAULTS").ok().as_deref() == Some("1");
let guard_on =
std::env::var("NYASH_VM_SCANNER_DEFAULTS").ok().as_deref() == Some("1");
let fn_ctx = this.cur_fn.as_deref().unwrap_or("");
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] getField guard_check ctx={} guard_on={} name={}", fn_ctx, guard_on, fname);
eprintln!(
"[vm-trace] getField guard_check ctx={} guard_on={} name={}",
fn_ctx, guard_on, fname
);
}
if guard_on {
let fn_ctx = this.cur_fn.as_deref().unwrap_or("");
@ -243,7 +275,10 @@ pub(super) fn try_handle_object_fields(
if is_scanner_ctx {
// Try class-aware default first
if let Ok(VMValue::BoxRef(bref2)) = this.reg_load(actual_box_val) {
if let Some(inst2) = bref2.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst2) = bref2
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
if inst2.class_name == "JsonScanner" {
let fallback = match fname.as_str() {
"position" | "length" => Some(VMValue::Integer(0)),
@ -252,8 +287,13 @@ pub(super) fn try_handle_object_fields(
_ => None,
};
if let Some(val) = fallback {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] getField final_default {} -> {:?}", fname, val);
if std::env::var("NYASH_VM_TRACE").ok().as_deref()
== Some("1")
{
eprintln!(
"[vm-trace] getField final_default {} -> {:?}",
fname, val
);
}
v = val;
}
@ -280,7 +320,11 @@ pub(super) fn try_handle_object_fields(
}
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
if let VMValue::BoxRef(b) = &v {
eprintln!("[vm-trace] getField legacy {} -> BoxRef({})", fname, b.type_name());
eprintln!(
"[vm-trace] getField legacy {} -> BoxRef({})",
fname,
b.type_name()
);
} else {
eprintln!("[vm-trace] getField legacy {} -> {:?}", fname, v);
}
@ -299,9 +343,13 @@ pub(super) fn try_handle_object_fields(
// class name unknown here; use receiver type name if possible
let cls = match this.reg_load(actual_box_val).unwrap_or(VMValue::Void) {
VMValue::BoxRef(b) => {
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) =
b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>()
{
inst.class_name.clone()
} else { b.type_name().to_string() }
} else {
b.type_name().to_string()
}
}
_ => "<unknown>".to_string(),
};
@ -322,7 +370,8 @@ pub(super) fn try_handle_object_fields(
// Create a temporary value to hold the singleton
let temp_id = ValueId(999999998); // Temporary ID for singleton (different from getField)
this.regs.insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone)));
this.regs
.insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone)));
temp_id
} else {
box_val
@ -333,19 +382,28 @@ pub(super) fn try_handle_object_fields(
// MapBox special-case: bridge to MapBox.set, with string-only key
if let Ok(VMValue::BoxRef(bref)) = this.reg_load(actual_box_val) {
if bref.as_any().downcast_ref::<crate::boxes::map_box::MapBox>().is_some() {
if bref
.as_any()
.downcast_ref::<crate::boxes::map_box::MapBox>()
.is_some()
{
let key_vm = this.reg_load(args[0])?;
if let VMValue::String(_) = key_vm {
let k = key_vm.to_nyash_box();
let v = this.reg_load(args[1])?.to_nyash_box();
let map = bref.share_box();
if let Some(mb) = map.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
if let Some(mb) =
map.as_any().downcast_ref::<crate::boxes::map_box::MapBox>()
{
let _ = mb.set(k, v);
this.write_void(dst);
return Ok(true);
}
} else {
this.write_string(dst, "[map/bad-key] field name must be string".to_string());
this.write_string(
dst,
"[map/bad-key] field name must be string".to_string(),
);
return Ok(true);
}
}
@ -358,9 +416,15 @@ pub(super) fn try_handle_object_fields(
// Dev trace: JsonToken field set
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
if let VMValue::BoxRef(bref) = this.reg_load(actual_box_val)? {
if let Some(inst) = bref.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = bref
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
if inst.class_name == "JsonToken" {
eprintln!("[vm-trace] JsonToken.setField name={} vmval={:?}", fname, valv);
eprintln!(
"[vm-trace] JsonToken.setField name={} vmval={:?}",
fname, valv
);
}
}
}
@ -377,9 +441,13 @@ pub(super) fn try_handle_object_fields(
};
let cls = match this.reg_load(actual_box_val).unwrap_or(VMValue::Void) {
VMValue::BoxRef(b) => {
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) =
b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>()
{
inst.class_name.clone()
} else { b.type_name().to_string() }
} else {
b.type_name().to_string()
}
}
_ => "<unknown>".to_string(),
};
@ -387,28 +455,52 @@ pub(super) fn try_handle_object_fields(
}
// Prefer InstanceBox internal storage
if let VMValue::BoxRef(bref) = this.reg_load(actual_box_val)? {
if let Some(inst) = bref.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = bref
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
// Primitives → 内部保存
if matches!(valv, VMValue::Integer(_) | VMValue::Float(_) | VMValue::Bool(_) | VMValue::String(_) | VMValue::Void) {
if matches!(
valv,
VMValue::Integer(_)
| VMValue::Float(_)
| VMValue::Bool(_)
| VMValue::String(_)
| VMValue::Void
) {
let _ = inst.set_field_ng(fname.clone(), vm_to_nv(&valv));
return Ok(true);
}
// BoxRef のうち、Integer/Float/Bool/String はプリミティブに剥がして内部保存
if let VMValue::BoxRef(bx) = &valv {
if let Some(ib) = bx.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Integer(ib.value));
if let Some(ib) = bx.as_any().downcast_ref::<crate::box_trait::IntegerBox>()
{
let _ = inst.set_field_ng(
fname.clone(),
crate::value::NyashValue::Integer(ib.value),
);
return Ok(true);
}
if let Some(fb) = bx.as_any().downcast_ref::<crate::boxes::FloatBox>() {
let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Float(fb.value));
let _ = inst.set_field_ng(
fname.clone(),
crate::value::NyashValue::Float(fb.value),
);
return Ok(true);
}
if let Some(bb) = bx.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Bool(bb.value));
let _ = inst.set_field_ng(
fname.clone(),
crate::value::NyashValue::Bool(bb.value),
);
return Ok(true);
}
if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::String(sb.value.clone()));
if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
{
let _ = inst.set_field_ng(
fname.clone(),
crate::value::NyashValue::String(sb.value.clone()),
);
return Ok(true);
}
// For complex Box values (InstanceBox/MapBox/ArrayBox...), store into
@ -419,10 +511,7 @@ pub(super) fn try_handle_object_fields(
}
}
let key = this.object_key_for(actual_box_val);
this.obj_fields
.entry(key)
.or_default()
.insert(fname, valv);
this.obj_fields.entry(key).or_default().insert(fname, valv);
Ok(true)
}
_ => Ok(false),

View File

@ -52,7 +52,7 @@ pub(super) fn invoke_plugin_box(
}
Err(e) => Err(this.err_with_context(
&format!("BoxCall {}.{}", p.box_type, method),
&format!("{:?}", e)
&format!("{:?}", e),
)),
}
} else if recv_box.type_name() == "StringBox" {
@ -99,10 +99,7 @@ pub(super) fn invoke_plugin_box(
if let Some(arg_id) = args.get(0) {
let ch = this.reg_load(*arg_id)?.to_string();
let c = ch.chars().next().unwrap_or('\0');
let is_alpha =
('A'..='Z').contains(&c) ||
('a'..='z').contains(&c) ||
c == '_';
let is_alpha = ('A'..='Z').contains(&c) || ('a'..='z').contains(&c) || c == '_';
this.write_result(dst, VMValue::Bool(is_alpha));
Ok(())
} else {
@ -115,14 +112,21 @@ pub(super) fn invoke_plugin_box(
// Special-case: minimal runtime fallback for common InstanceBox methods when
// lowered functions are not available (dev robustness). Keeps behavior stable
// without changing semantics in the normal path.
if let Some(inst) = recv_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = recv_box
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
// Generic current() fallback: if object has integer 'position' and string 'text',
// return one character at that position (or empty at EOF). This covers JsonScanner
// and compatible scanners without relying on class name.
if method == "current" && args.is_empty() {
if let Some(crate::value::NyashValue::Integer(pos)) = inst.get_field_ng("position") {
if let Some(crate::value::NyashValue::String(text)) = inst.get_field_ng("text") {
let s = if pos < 0 || (pos as usize) >= text.len() { String::new() } else {
if let Some(crate::value::NyashValue::Integer(pos)) = inst.get_field_ng("position")
{
if let Some(crate::value::NyashValue::String(text)) = inst.get_field_ng("text")
{
let s = if pos < 0 || (pos as usize) >= text.len() {
String::new()
} else {
let bytes = text.as_bytes();
let i = pos as usize;
let j = (i + 1).min(bytes.len());
@ -137,7 +141,11 @@ pub(super) fn invoke_plugin_box(
// Generic toString fallback for any non-plugin box
if method == "toString" {
// Map VoidBox.toString → "null" for JSON-friendly semantics
let s = if recv_box.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
let s = if recv_box
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
"null".to_string()
} else {
recv_box.to_string_box().value
@ -148,7 +156,10 @@ pub(super) fn invoke_plugin_box(
// Minimal runtime fallback for common InstanceBox.is_eof when lowered function is not present.
// This avoids cross-class leaks and hard errors in union-like flows.
if method == "is_eof" && args.is_empty() {
if let Some(inst) = recv_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = recv_box
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
if inst.class_name == "JsonToken" {
let is = match inst.get_field_ng("type") {
Some(crate::value::NyashValue::String(ref s)) => s == "EOF",
@ -158,8 +169,14 @@ pub(super) fn invoke_plugin_box(
return Ok(());
}
if inst.class_name == "JsonScanner" {
let pos = match inst.get_field_ng("position") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 };
let len = match inst.get_field_ng("length") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 };
let pos = match inst.get_field_ng("position") {
Some(crate::value::NyashValue::Integer(i)) => i,
_ => 0,
};
let len = match inst.get_field_ng("length") {
Some(crate::value::NyashValue::Integer(i)) => i,
_ => 0,
};
let is = pos >= len;
this.write_result(dst, VMValue::Bool(is));
return Ok(());
@ -167,7 +184,10 @@ pub(super) fn invoke_plugin_box(
}
}
// Dynamic fallback for user-defined InstanceBox: dispatch to lowered function "Class.method/Arity"
if let Some(inst) = recv_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = recv_box
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
let class_name = inst.class_name.clone();
let arity = args.len(); // function name arity excludes 'me'
let fname = format!("{}.{}{}", class_name, method, format!("/{}", arity));
@ -190,10 +210,10 @@ pub(super) fn invoke_plugin_box(
this.write_string(dst, String::new());
return Ok(());
}
// VoidBox graceful handling for common container-like methods
// Treat null.receiver.* as safe no-ops that return null/0 where appropriate
if recv_box.type_name() == "VoidBox" {
match method {
// VoidBox graceful handling for common container-like methods
// Treat null.receiver.* as safe no-ops that return null/0 where appropriate
if recv_box.type_name() == "VoidBox" {
match method {
"object_get" | "array_get" | "toString" => {
this.write_void(dst);
return Ok(());

View File

@ -7,207 +7,231 @@ pub(super) fn try_handle_string_box(
method: &str,
args: &[ValueId],
) -> Result<bool, VMError> {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] try_handle_string_box(method={})", method);
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] try_handle_string_box(method={})", method);
}
let recv = this.reg_load(box_val)?;
// Ultra-fast path: raw VM string receiver for length/size (no boxing at all)
if (method == "length" || method == "size")
&& std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1")
{
if let VMValue::String(ref raw) = recv {
let use_cp = std::env::var("NYASH_STR_CP").ok().as_deref() == Some("1");
let n = if use_cp {
raw.chars().count() as i64
} else {
raw.len() as i64
};
this.write_result(dst, VMValue::Integer(n));
return Ok(true);
}
let recv = this.reg_load(box_val)?;
// Ultra-fast path: raw VM string receiver for length/size (no boxing at all)
if (method == "length" || method == "size")
&& std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1")
{
if let VMValue::String(ref raw) = recv {
}
// Handle ONLY when the receiver is actually a string.
// Do NOT coerce arbitrary boxes to StringBox (e.g., ArrayBox.length()).
let sb_norm_opt: Option<crate::box_trait::StringBox> = match recv.clone() {
VMValue::String(s) => Some(crate::box_trait::StringBox::new(s)),
VMValue::BoxRef(b) => {
if b.as_any()
.downcast_ref::<crate::box_trait::StringBox>()
.is_some()
{
Some(b.to_string_box())
} else {
None
}
}
_ => None,
};
let Some(sb_norm) = sb_norm_opt else {
return Ok(false);
};
// Only handle known string methods here (receiver is confirmed string)
match method {
"length" | "size" => {
// Bench/profile fast path: return VMValue::Integer directly (avoid boxing overhead)
if std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1") {
let use_cp = std::env::var("NYASH_STR_CP").ok().as_deref() == Some("1");
let n = if use_cp { raw.chars().count() as i64 } else { raw.len() as i64 };
let n = if use_cp {
sb_norm.value.chars().count() as i64
} else {
sb_norm.value.len() as i64
};
this.write_result(dst, VMValue::Integer(n));
return Ok(true);
}
let ret = sb_norm.length();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
// Handle ONLY when the receiver is actually a string.
// Do NOT coerce arbitrary boxes to StringBox (e.g., ArrayBox.length()).
let sb_norm_opt: Option<crate::box_trait::StringBox> = match recv.clone() {
VMValue::String(s) => Some(crate::box_trait::StringBox::new(s)),
VMValue::BoxRef(b) => {
if b.as_any().downcast_ref::<crate::box_trait::StringBox>().is_some() {
Some(b.to_string_box())
} else {
None
}
}
_ => None,
};
let Some(sb_norm) = sb_norm_opt else { return Ok(false) };
// Only handle known string methods here (receiver is confirmed string)
match method {
"length" | "size" => {
// Bench/profile fast path: return VMValue::Integer directly (avoid boxing overhead)
if std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1") {
let use_cp = std::env::var("NYASH_STR_CP").ok().as_deref() == Some("1");
let n = if use_cp { sb_norm.value.chars().count() as i64 } else { sb_norm.value.len() as i64 };
this.write_result(dst, VMValue::Integer(n));
return Ok(true);
}
let ret = sb_norm.length();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"replace" => {
// replace(old, new) -> string with first occurrence replaced (Rust replace = all; match Core minimal
this.validate_args_exact("replace", args, 2)?;
let old_s = this.reg_load(args[0])?.to_string();
let new_s = this.reg_load(args[1])?.to_string();
// Core policy: replace only the first occurrence
let out = if let Some(pos) = sb_norm.value.find(&old_s) {
let mut s = String::with_capacity(sb_norm.value.len() + new_s.len());
s.push_str(&sb_norm.value[..pos]);
s.push_str(&new_s);
s.push_str(&sb_norm.value[pos + old_s.len()..]);
s
} else {
sb_norm.value.clone()
};
this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(out))));
return Ok(true);
}
"trim" => {
let ret = sb_norm.trim();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"indexOf" => {
// Support both 1-arg indexOf(search) and 2-arg indexOf(search, fromIndex)
let (needle, from_index) = match args.len() {
1 => {
// indexOf(search) - search from beginning
let n = this.reg_load(args[0])?.to_string();
(n, 0)
}
2 => {
// indexOf(search, fromIndex) - search from specified position
let n = this.reg_load(args[0])?.to_string();
let from = this.reg_load(args[1])?.as_integer().unwrap_or(0);
(n, from.max(0) as usize)
}
_ => {
return Err(this.err_invalid(
"indexOf expects 1 or 2 args (search [, fromIndex])"
));
}
};
// Search for needle starting from from_index
let search_str = if from_index >= sb_norm.value.len() {
""
} else {
&sb_norm.value[from_index..]
};
let idx = search_str.find(&needle)
.map(|i| (from_index + i) as i64)
.unwrap_or(-1);
this.write_result(dst, VMValue::Integer(idx));
return Ok(true);
}
"contains" => {
// contains(search) -> boolean (true if found, false otherwise)
// Implemented as indexOf(search) >= 0
this.validate_args_exact("contains", args, 1)?;
let needle = this.reg_load(args[0])?.to_string();
let found = sb_norm.value.contains(&needle);
this.write_result(dst, VMValue::Bool(found));
return Ok(true);
}
"lastIndexOf" => {
// lastIndexOf(substr) -> last index or -1
this.validate_args_exact("lastIndexOf", args, 1)?;
let needle = this.reg_load(args[0])?.to_string();
let idx = sb_norm.value.rfind(&needle).map(|i| i as i64).unwrap_or(-1);
this.write_result(dst, VMValue::Integer(idx));
return Ok(true);
}
"stringify" => {
// JSON-style stringify for strings: quote and escape common characters
let mut quoted = String::with_capacity(sb_norm.value.len() + 2);
quoted.push('"');
for ch in sb_norm.value.chars() {
match ch {
'"' => quoted.push_str("\\\""),
'\\' => quoted.push_str("\\\\"),
'\n' => quoted.push_str("\\n"),
'\r' => quoted.push_str("\\r"),
'\t' => quoted.push_str("\\t"),
c if c.is_control() => quoted.push(' '),
c => quoted.push(c),
}
}
quoted.push('"');
this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(quoted))));
return Ok(true);
}
"substring" => {
// Support both 1-arg (start to end) and 2-arg (start, end) forms
let (s_idx, e_idx) = match args.len() {
1 => {
// substring(start) - from start to end of string
let s = this.reg_load(args[0])?.as_integer().unwrap_or(0);
let len = sb_norm.value.chars().count() as i64;
(s, len)
}
2 => {
// substring(start, end) - half-open interval [start, end)
let s = this.reg_load(args[0])?.as_integer().unwrap_or(0);
let e = this.reg_load(args[1])?.as_integer().unwrap_or(0);
(s, e)
}
_ => {
return Err(this.err_invalid(
"substring expects 1 or 2 args (start [, end])"
));
}
};
let len = sb_norm.value.chars().count() as i64;
let start = s_idx.max(0).min(len) as usize;
let end = e_idx.max(start as i64).min(len) as usize;
let chars: Vec<char> = sb_norm.value.chars().collect();
let sub: String = chars[start..end].iter().collect();
this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(sub))));
return Ok(true);
}
"concat" => {
this.validate_args_exact("concat", args, 1)?;
let rhs = this.reg_load(args[0])?;
let new_s = format!("{}{}", sb_norm.value, rhs.to_string());
this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(new_s))));
return Ok(true);
}
"is_digit_char" => {
// Accept either 0-arg (use first char of receiver) or 1-arg (string/char to test)
let ch_opt = if args.is_empty() {
sb_norm.value.chars().next()
} else if args.len() == 1 {
let s = this.reg_load(args[0])?.to_string();
s.chars().next()
} else {
return Err(this.err_invalid("is_digit_char expects 0 or 1 arg"));
};
let is_digit = ch_opt.map(|c| c.is_ascii_digit()).unwrap_or(false);
this.write_result(dst, VMValue::Bool(is_digit));
return Ok(true);
}
"is_hex_digit_char" => {
let ch_opt = if args.is_empty() {
sb_norm.value.chars().next()
} else if args.len() == 1 {
let s = this.reg_load(args[0])?.to_string();
s.chars().next()
} else {
return Err(this.err_invalid("is_hex_digit_char expects 0 or 1 arg"));
};
let is_hex = ch_opt.map(|c| c.is_ascii_hexdigit()).unwrap_or(false);
this.write_result(dst, VMValue::Bool(is_hex));
return Ok(true);
}
_ => {}
"replace" => {
// replace(old, new) -> string with first occurrence replaced (Rust replace = all; match Core minimal
this.validate_args_exact("replace", args, 2)?;
let old_s = this.reg_load(args[0])?.to_string();
let new_s = this.reg_load(args[1])?.to_string();
// Core policy: replace only the first occurrence
let out = if let Some(pos) = sb_norm.value.find(&old_s) {
let mut s = String::with_capacity(sb_norm.value.len() + new_s.len());
s.push_str(&sb_norm.value[..pos]);
s.push_str(&new_s);
s.push_str(&sb_norm.value[pos + old_s.len()..]);
s
} else {
sb_norm.value.clone()
};
this.write_result(
dst,
VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(out))),
);
return Ok(true);
}
Ok(false)
"trim" => {
let ret = sb_norm.trim();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"indexOf" => {
// Support both 1-arg indexOf(search) and 2-arg indexOf(search, fromIndex)
let (needle, from_index) = match args.len() {
1 => {
// indexOf(search) - search from beginning
let n = this.reg_load(args[0])?.to_string();
(n, 0)
}
2 => {
// indexOf(search, fromIndex) - search from specified position
let n = this.reg_load(args[0])?.to_string();
let from = this.reg_load(args[1])?.as_integer().unwrap_or(0);
(n, from.max(0) as usize)
}
_ => {
return Err(
this.err_invalid("indexOf expects 1 or 2 args (search [, fromIndex])")
);
}
};
// Search for needle starting from from_index
let search_str = if from_index >= sb_norm.value.len() {
""
} else {
&sb_norm.value[from_index..]
};
let idx = search_str
.find(&needle)
.map(|i| (from_index + i) as i64)
.unwrap_or(-1);
this.write_result(dst, VMValue::Integer(idx));
return Ok(true);
}
"contains" => {
// contains(search) -> boolean (true if found, false otherwise)
// Implemented as indexOf(search) >= 0
this.validate_args_exact("contains", args, 1)?;
let needle = this.reg_load(args[0])?.to_string();
let found = sb_norm.value.contains(&needle);
this.write_result(dst, VMValue::Bool(found));
return Ok(true);
}
"lastIndexOf" => {
// lastIndexOf(substr) -> last index or -1
this.validate_args_exact("lastIndexOf", args, 1)?;
let needle = this.reg_load(args[0])?.to_string();
let idx = sb_norm.value.rfind(&needle).map(|i| i as i64).unwrap_or(-1);
this.write_result(dst, VMValue::Integer(idx));
return Ok(true);
}
"stringify" => {
// JSON-style stringify for strings: quote and escape common characters
let mut quoted = String::with_capacity(sb_norm.value.len() + 2);
quoted.push('"');
for ch in sb_norm.value.chars() {
match ch {
'"' => quoted.push_str("\\\""),
'\\' => quoted.push_str("\\\\"),
'\n' => quoted.push_str("\\n"),
'\r' => quoted.push_str("\\r"),
'\t' => quoted.push_str("\\t"),
c if c.is_control() => quoted.push(' '),
c => quoted.push(c),
}
}
quoted.push('"');
this.write_result(
dst,
VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(quoted))),
);
return Ok(true);
}
"substring" => {
// Support both 1-arg (start to end) and 2-arg (start, end) forms
let (s_idx, e_idx) = match args.len() {
1 => {
// substring(start) - from start to end of string
let s = this.reg_load(args[0])?.as_integer().unwrap_or(0);
let len = sb_norm.value.chars().count() as i64;
(s, len)
}
2 => {
// substring(start, end) - half-open interval [start, end)
let s = this.reg_load(args[0])?.as_integer().unwrap_or(0);
let e = this.reg_load(args[1])?.as_integer().unwrap_or(0);
(s, e)
}
_ => {
return Err(this.err_invalid("substring expects 1 or 2 args (start [, end])"));
}
};
let len = sb_norm.value.chars().count() as i64;
let start = s_idx.max(0).min(len) as usize;
let end = e_idx.max(start as i64).min(len) as usize;
let chars: Vec<char> = sb_norm.value.chars().collect();
let sub: String = chars[start..end].iter().collect();
this.write_result(
dst,
VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(sub))),
);
return Ok(true);
}
"concat" => {
this.validate_args_exact("concat", args, 1)?;
let rhs = this.reg_load(args[0])?;
let new_s = format!("{}{}", sb_norm.value, rhs.to_string());
this.write_result(
dst,
VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(new_s))),
);
return Ok(true);
}
"is_digit_char" => {
// Accept either 0-arg (use first char of receiver) or 1-arg (string/char to test)
let ch_opt = if args.is_empty() {
sb_norm.value.chars().next()
} else if args.len() == 1 {
let s = this.reg_load(args[0])?.to_string();
s.chars().next()
} else {
return Err(this.err_invalid("is_digit_char expects 0 or 1 arg"));
};
let is_digit = ch_opt.map(|c| c.is_ascii_digit()).unwrap_or(false);
this.write_result(dst, VMValue::Bool(is_digit));
return Ok(true);
}
"is_hex_digit_char" => {
let ch_opt = if args.is_empty() {
sb_norm.value.chars().next()
} else if args.len() == 1 {
let s = this.reg_load(args[0])?.to_string();
s.chars().next()
} else {
return Err(this.err_invalid("is_hex_digit_char expects 0 or 1 arg"));
};
let is_hex = ch_opt.map(|c| c.is_ascii_hexdigit()).unwrap_or(false);
this.write_result(dst, VMValue::Bool(is_hex));
return Ok(true);
}
_ => {}
}
Ok(false)
}

View File

@ -22,9 +22,15 @@ impl MirInterpreter {
match &v {
VMValue::Void => println!("null"),
VMValue::BoxRef(bx) => {
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
if bx
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
println!("null");
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
} else if let Some(sb) =
bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
{
println!("{}", sb.value);
} else {
println!("{}", v.to_string());
@ -56,8 +62,12 @@ impl MirInterpreter {
};
panic!("{}", msg);
}
"hostbridge.extern_invoke" => Err(self.err_invalid("hostbridge.extern_invoke should be routed via extern_provider_dispatch")),
_ => Err(self.err_with_context("extern function", &format!("Unknown: {}", extern_name))),
"hostbridge.extern_invoke" => Err(self.err_invalid(
"hostbridge.extern_invoke should be routed via extern_provider_dispatch",
)),
_ => {
Err(self.err_with_context("extern function", &format!("Unknown: {}", extern_name)))
}
}
}
}

View File

@ -12,7 +12,9 @@ impl MirInterpreter {
// Module-local/global function: execute by function table if present (use original name)
if let Some(func) = self.functions.get(func_name).cloned() {
let mut argv: Vec<VMValue> = Vec::with_capacity(args.len());
for a in args { argv.push(self.reg_load(*a)?); }
for a in args {
argv.push(self.reg_load(*a)?);
}
return self.exec_function_inner(&func, Some(&argv));
}
@ -60,13 +62,19 @@ impl MirInterpreter {
let s = self.reg_load(*a0)?.to_string();
let opts = crate::host_providers::llvm_codegen::Opts {
out: None,
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from),
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok().or(Some("0".to_string())),
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT")
.ok()
.map(std::path::PathBuf::from),
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL")
.ok()
.or(Some("0".to_string())),
timeout_ms: None,
};
match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts) {
Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())),
Err(e) => Err(self.err_with_context("env.codegen.emit_object", &e.to_string())),
Err(e) => {
Err(self.err_with_context("env.codegen.emit_object", &e.to_string()))
}
}
} else {
Err(self.err_invalid("env.codegen.emit_object expects 1 arg"))
@ -74,32 +82,51 @@ impl MirInterpreter {
}
"env.codegen.link_object" | "env.codegen.link_object/3" => {
// C-API route only; args[2] is expected to be an ArrayBox [obj_path, exe_out?]
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
.ok()
.as_deref()
!= Some("1")
{
return Err(self.err_invalid("env.codegen.link_object: C-API route disabled"));
}
if args.len() < 3 { return Err(self.err_arg_count("env.codegen.link_object", 3, args.len())); }
if args.len() < 3 {
return Err(self.err_arg_count("env.codegen.link_object", 3, args.len()));
}
let v = self.reg_load(args[2])?;
let (obj_path, exe_out) = match v {
VMValue::BoxRef(b) => {
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
{
let idx0: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(0));
let elem0 = ab.get(idx0).to_string_box().value;
let mut exe: Option<String> = None;
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
let idx1: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(1));
let e1 = ab.get(idx1).to_string_box().value;
if !e1.is_empty() { exe = Some(e1); }
if !e1.is_empty() {
exe = Some(e1);
}
(elem0, exe)
} else { (b.to_string_box().value, None) }
} else {
(b.to_string_box().value, None)
}
}
_ => (v.to_string(), None),
};
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
let obj = std::path::PathBuf::from(obj_path);
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
let exe = exe_out
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(
&obj,
&exe,
extra.as_deref(),
) {
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string()))
Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string())),
}
}
"nyash.builtin.error" => {

View File

@ -22,18 +22,40 @@ impl MirInterpreter {
if let Some(block) = func.blocks.get(&bb) {
let mut last_recv: Option<ValueId> = None;
for inst in &block.instructions {
if let crate::mir::MirInstruction::NewBox { dst, box_type, .. } = inst {
if box_type == box_name { last_recv = Some(*dst); }
if let crate::mir::MirInstruction::NewBox {
dst,
box_type,
..
} = inst
{
if box_type == box_name {
last_recv = Some(*dst);
}
}
}
if let Some(rid) = last_recv {
if let Ok(v) = self.reg_load(rid) { v } else { return Err(e); }
if let Ok(v) = self.reg_load(rid) {
v
} else {
return Err(e);
}
} else {
// Dev fallback (guarded): use args[0] as surrogate receiver if explicitly allowed
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref() == Some("1")
|| std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1");
if tolerate { if let Some(a0) = args.get(0) { self.reg_load(*a0)? } else { return Err(e); } }
else { return Err(e); }
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK")
.ok()
.as_deref()
== Some("1")
|| std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref()
== Some("1");
if tolerate {
if let Some(a0) = args.get(0) {
self.reg_load(*a0)?
} else {
return Err(e);
}
} else {
return Err(e);
}
}
} else {
return Err(e);
@ -43,10 +65,18 @@ impl MirInterpreter {
}
} else {
// Dev fallback (guarded): use args[0] as surrogate receiver if explicitly allowed
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref() == Some("1")
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref()
== Some("1")
|| std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1");
if tolerate { if let Some(a0) = args.get(0) { self.reg_load(*a0)? } else { return Err(e); } }
else { return Err(e); }
if tolerate {
if let Some(a0) = args.get(0) {
self.reg_load(*a0)?
} else {
return Err(e);
}
} else {
return Err(e);
}
}
}
};
@ -57,7 +87,9 @@ impl MirInterpreter {
// ArrayBox bridge
if let Some(arr) = bx.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
match method {
"birth" => { return Ok(VMValue::Void); }
"birth" => {
return Ok(VMValue::Void);
}
"push" => {
if let Some(a0) = args.get(0) {
let v = self.load_as_box(*a0)?;
@ -162,14 +194,20 @@ impl MirInterpreter {
"substring" => {
let start = if let Some(a0) = args.get(0) {
self.reg_load(*a0)?.as_integer().unwrap_or(0)
} else { 0 };
} else {
0
};
let end = if let Some(a1) = args.get(1) {
self.reg_load(*a1)?.as_integer().unwrap_or(s.len() as i64)
} else { s.len() as i64 };
} else {
s.len() as i64
};
let len = s.len() as i64;
let i0 = start.max(0).min(len) as usize;
let i1 = end.max(0).min(len) as usize;
if i0 > i1 { return Ok(VMValue::String(String::new())); }
if i0 > i1 {
return Ok(VMValue::String(String::new()));
}
// Note: operating on bytes; Nyash strings are UTF8, but tests are ASCII only here
let bytes = s.as_bytes();
let sub = String::from_utf8(bytes[i0..i1].to_vec()).unwrap_or_default();
@ -209,8 +247,7 @@ impl MirInterpreter {
"is_space" => {
if let Some(arg_id) = args.get(0) {
let ch = self.reg_load(*arg_id)?.to_string();
let is_ws =
ch == " " || ch == "\t" || ch == "\n" || ch == "\r";
let is_ws = ch == " " || ch == "\t" || ch == "\n" || ch == "\r";
Ok(VMValue::Bool(is_ws))
} else {
Err(self.err_invalid("is_space requires 1 argument"))
@ -221,10 +258,9 @@ impl MirInterpreter {
if let Some(arg_id) = args.get(0) {
let ch = self.reg_load(*arg_id)?.to_string();
let c = ch.chars().next().unwrap_or('\0');
let is_alpha =
('A'..='Z').contains(&c) ||
('a'..='z').contains(&c) ||
c == '_';
let is_alpha = ('A'..='Z').contains(&c)
|| ('a'..='z').contains(&c)
|| c == '_';
Ok(VMValue::Bool(is_alpha))
} else {
Err(self.err_invalid("is_alpha requires 1 argument"))
@ -234,8 +270,8 @@ impl MirInterpreter {
}
} else if let Some(p) = box_ref
.as_any()
.downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>()
{
.downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>(
) {
let host = crate::runtime::plugin_loader_unified::get_global_plugin_host();
let host = host.read().unwrap();
let argv = self.load_args_as_boxes(args)?;
@ -249,14 +285,17 @@ impl MirInterpreter {
Ok(None) => Ok(VMValue::Void),
Err(e) => Err(self.err_with_context(
&format!("Plugin method {}.{}", p.box_type, method),
&format!("{:?}", e)
&format!("{:?}", e),
)),
}
} else {
Err(self.err_method_not_found(&box_ref.type_name(), method))
}
}
_ => Err(self.err_with_context("method call", &format!("{} not supported on {:?}", method, receiver))),
_ => Err(self.err_with_context(
"method call",
&format!("{} not supported on {:?}", method, receiver),
)),
}
}
}

View File

@ -4,9 +4,9 @@
use super::*;
mod externs;
mod global;
mod method;
mod externs;
// legacy by-name resolver has been removed (Phase 2 complete)
impl MirInterpreter {
@ -19,18 +19,43 @@ impl MirInterpreter {
) -> Result<(), VMError> {
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") {
match callee {
Some(Callee::Global(n)) => eprintln!("[hb:path] call Callee::Global {} argc={}", n, args.len()),
Some(Callee::Method{ box_name, method, ..}) => eprintln!("[hb:path] call Callee::Method {}.{} argc={}", box_name, method, args.len()),
Some(Callee::Constructor{ box_type }) => eprintln!("[hb:path] call Callee::Constructor {} argc={}", box_type, args.len()),
Some(Callee::Closure{ .. }) => eprintln!("[hb:path] call Callee::Closure argc={}", args.len()),
Some(Callee::Value(_)) => eprintln!("[hb:path] call Callee::Value argc={}", args.len()),
Some(Callee::Extern(n)) => eprintln!("[hb:path] call Callee::Extern {} argc={}", n, args.len()),
None => eprintln!("[hb:path] call Legacy func_id={:?} argc={}", func, args.len()),
Some(Callee::Global(n)) => {
eprintln!("[hb:path] call Callee::Global {} argc={}", n, args.len())
}
Some(Callee::Method {
box_name, method, ..
}) => eprintln!(
"[hb:path] call Callee::Method {}.{} argc={}",
box_name,
method,
args.len()
),
Some(Callee::Constructor { box_type }) => eprintln!(
"[hb:path] call Callee::Constructor {} argc={}",
box_type,
args.len()
),
Some(Callee::Closure { .. }) => {
eprintln!("[hb:path] call Callee::Closure argc={}", args.len())
}
Some(Callee::Value(_)) => {
eprintln!("[hb:path] call Callee::Value argc={}", args.len())
}
Some(Callee::Extern(n)) => {
eprintln!("[hb:path] call Callee::Extern {} argc={}", n, args.len())
}
None => eprintln!(
"[hb:path] call Legacy func_id={:?} argc={}",
func,
args.len()
),
}
}
// SSOT fast-path: route hostbridge.extern_invoke to extern dispatcher regardless of resolution form
if let Some(Callee::Global(func_name)) = callee {
if func_name == "hostbridge.extern_invoke" || func_name.starts_with("hostbridge.extern_invoke/") {
if func_name == "hostbridge.extern_invoke"
|| func_name.starts_with("hostbridge.extern_invoke/")
{
let v = self.execute_extern_function("hostbridge.extern_invoke", args)?;
self.write_result(dst, v);
return Ok(());
@ -44,7 +69,9 @@ impl MirInterpreter {
if let VMValue::String(ref s) = name_val {
if let Some(f) = self.functions.get(s).cloned() {
let mut argv: Vec<VMValue> = Vec::with_capacity(args.len());
for a in args { argv.push(self.reg_load(*a)?); }
for a in args {
argv.push(self.reg_load(*a)?);
}
self.exec_function_inner(&f, Some(&argv))?
} else {
return Err(self.err_with_context("call", &format!(
@ -53,7 +80,10 @@ impl MirInterpreter {
)));
}
} else {
return Err(self.err_with_context("call", "by-name calls unsupported without Callee attachment"));
return Err(self.err_with_context(
"call",
"by-name calls unsupported without Callee attachment",
));
}
};
self.write_result(dst, call_result);
@ -67,10 +97,15 @@ impl MirInterpreter {
) -> Result<VMValue, VMError> {
match callee {
Callee::Global(func_name) => self.execute_global_function(func_name, args),
Callee::Method { box_name, method, receiver, .. } => {
self.execute_method_callee(box_name, method, receiver, args)
Callee::Method {
box_name,
method,
receiver,
..
} => self.execute_method_callee(box_name, method, receiver, args),
Callee::Constructor { box_type } => {
Err(self.err_unsupported(&format!("Constructor calls for {}", box_type)))
}
Callee::Constructor { box_type } => Err(self.err_unsupported(&format!("Constructor calls for {}", box_type))),
Callee::Closure { .. } => Err(self.err_unsupported("Closure creation in VM")),
Callee::Value(func_val_id) => {
let _ = self.reg_load(*func_val_id)?;
@ -79,5 +114,4 @@ impl MirInterpreter {
Callee::Extern(extern_name) => self.execute_extern_function(extern_name, args),
}
}
}

View File

@ -10,8 +10,12 @@ impl MirInterpreter {
let key = format!("{}.{}", target, method);
for pat in flt.split(',') {
let p = pat.trim();
if p.is_empty() { continue; }
if p == method || p == key { return true; }
if p.is_empty() {
continue;
}
if p == method || p == key {
return true;
}
}
return false;
}
@ -23,7 +27,9 @@ impl MirInterpreter {
if let JsonValue::Object(ref mut m) = v {
if !m.contains_key("version") {
m.insert("version".to_string(), JsonValue::from(0));
if let Ok(out) = serde_json::to_string(&v) { return out; }
if let Ok(out) = serde_json::to_string(&v) {
return out;
}
}
}
s.to_string()
@ -64,9 +70,15 @@ impl MirInterpreter {
VMValue::Void => println!("null"),
VMValue::String(s) => println!("{}", s),
VMValue::BoxRef(bx) => {
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
if bx
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
println!("null");
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
} else if let Some(sb) =
bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
{
println!("{}", sb.value);
} else {
println!("{}", v.to_string());
@ -90,9 +102,15 @@ impl MirInterpreter {
VMValue::Void => eprintln!("[warn] null"),
VMValue::String(s) => eprintln!("[warn] {}", s),
VMValue::BoxRef(bx) => {
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
if bx
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
eprintln!("[warn] null");
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
} else if let Some(sb) =
bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
{
eprintln!("[warn] {}", sb.value);
} else {
eprintln!("[warn] {}", v.to_string());
@ -116,9 +134,15 @@ impl MirInterpreter {
VMValue::Void => eprintln!("[error] null"),
VMValue::String(s) => eprintln!("[error] {}", s),
VMValue::BoxRef(bx) => {
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
if bx
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
eprintln!("[error] null");
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
} else if let Some(sb) =
bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
{
eprintln!("[error] {}", sb.value);
} else {
eprintln!("[error] {}", v.to_string());
@ -140,15 +164,29 @@ impl MirInterpreter {
if std::env::var("HAKO_V1_EXTERN_PROVIDER").ok().as_deref() == Some("1") {
return Some(Ok(VMValue::String(String::new())));
}
if args.is_empty() { return Some(Err(ErrorBuilder::arg_count_mismatch("env.mirbuilder.emit", 1, args.len()))); }
let program_json = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
if args.is_empty() {
return Some(Err(ErrorBuilder::arg_count_mismatch(
"env.mirbuilder.emit",
1,
args.len(),
)));
}
let program_json = match self.reg_load(args[0]) {
Ok(v) => v.to_string(),
Err(e) => return Some(Err(e)),
};
// Phase 21.8: Read imports from environment variable if present
let imports = if let Ok(imports_json) = std::env::var("HAKO_MIRBUILDER_IMPORTS") {
match serde_json::from_str::<std::collections::HashMap<String, String>>(&imports_json) {
match serde_json::from_str::<std::collections::HashMap<String, String>>(
&imports_json,
) {
Ok(map) => map,
Err(e) => {
eprintln!("[mirbuilder/imports] Failed to parse HAKO_MIRBUILDER_IMPORTS: {}", e);
eprintln!(
"[mirbuilder/imports] Failed to parse HAKO_MIRBUILDER_IMPORTS: {}",
e
);
std::collections::HashMap::new()
}
}
@ -156,10 +194,17 @@ impl MirInterpreter {
std::collections::HashMap::new()
};
let res = match crate::host_providers::mir_builder::program_json_to_mir_json_with_imports(&program_json, imports) {
Ok(s) => Ok(VMValue::String(Self::patch_mir_json_version(&s))),
Err(e) => Err(ErrorBuilder::with_context("env.mirbuilder.emit", &e.to_string())),
};
let res =
match crate::host_providers::mir_builder::program_json_to_mir_json_with_imports(
&program_json,
imports,
) {
Ok(s) => Ok(VMValue::String(Self::patch_mir_json_version(&s))),
Err(e) => Err(ErrorBuilder::with_context(
"env.mirbuilder.emit",
&e.to_string(),
)),
};
Some(res)
}
"env.codegen.emit_object" => {
@ -167,55 +212,114 @@ impl MirInterpreter {
if std::env::var("HAKO_V1_EXTERN_PROVIDER").ok().as_deref() == Some("1") {
return Some(Ok(VMValue::String(String::new())));
}
if args.is_empty() { return Some(Err(ErrorBuilder::arg_count_mismatch("env.codegen.emit_object", 1, args.len()))); }
let mir_json_raw = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
if args.is_empty() {
return Some(Err(ErrorBuilder::arg_count_mismatch(
"env.codegen.emit_object",
1,
args.len(),
)));
}
let mir_json_raw = match self.reg_load(args[0]) {
Ok(v) => v.to_string(),
Err(e) => return Some(Err(e)),
};
// Normalize to v1 shape if missing/legacy (prevents harness NoneType errors)
let mir_json = Self::patch_mir_json_version(&mir_json_raw);
let opts = crate::host_providers::llvm_codegen::Opts {
out: None,
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from),
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok().or(Some("0".to_string())),
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT")
.ok()
.map(std::path::PathBuf::from),
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL")
.ok()
.or(Some("0".to_string())),
timeout_ms: None,
};
let res = match crate::host_providers::llvm_codegen::mir_json_to_object(&mir_json, opts) {
let res = match crate::host_providers::llvm_codegen::mir_json_to_object(
&mir_json, opts,
) {
Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())),
Err(e) => Err(ErrorBuilder::with_context("env.codegen.emit_object", &e.to_string())),
Err(e) => Err(ErrorBuilder::with_context(
"env.codegen.emit_object",
&e.to_string(),
)),
};
Some(res)
}
"env.codegen.link_object" => {
// Only supported on C-API route; expect 1 or 2 args: obj_path [, exe_out]
let obj_path = match args.get(0) {
Some(v) => match self.reg_load(*v) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) },
None => return Some(Err(self.err_invalid("env.codegen.link_object expects 1+ args"))),
Some(v) => match self.reg_load(*v) {
Ok(v) => v.to_string(),
Err(e) => return Some(Err(e)),
},
None => {
return Some(Err(
self.err_invalid("env.codegen.link_object expects 1+ args")
))
}
};
let exe_out = match args.get(1) {
Some(v) => Some(match self.reg_load(*v) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }),
Some(v) => Some(match self.reg_load(*v) {
Ok(v) => v.to_string(),
Err(e) => return Some(Err(e)),
}),
None => None,
};
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
// Require C-API toggles
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
return Some(Err(ErrorBuilder::invalid_instruction("env.codegen.link_object: C-API route disabled")));
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
.ok()
.as_deref()
!= Some("1")
{
return Some(Err(ErrorBuilder::invalid_instruction(
"env.codegen.link_object: C-API route disabled",
)));
}
let obj = std::path::PathBuf::from(obj_path);
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
let exe = exe_out
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(
&obj,
&exe,
extra.as_deref(),
) {
Ok(()) => Some(Ok(VMValue::String(exe.to_string_lossy().into_owned()))),
Err(e) => Some(Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string()))),
Err(e) => Some(Err(ErrorBuilder::with_context(
"env.codegen.link_object",
&e.to_string(),
))),
}
}
// Environment
"env.get" => {
if args.is_empty() { return Some(Err(ErrorBuilder::arg_count_mismatch("env.get", 1, args.len()))); }
let key = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
if args.is_empty() {
return Some(Err(ErrorBuilder::arg_count_mismatch(
"env.get",
1,
args.len(),
)));
}
let key = match self.reg_load(args[0]) {
Ok(v) => v.to_string(),
Err(e) => return Some(Err(e)),
};
let val = std::env::var(&key).ok();
Some(Ok(match val { Some(s) => VMValue::String(s), None => VMValue::Void }))
Some(Ok(match val {
Some(s) => VMValue::String(s),
None => VMValue::Void,
}))
}
"env.set" => {
if args.len() < 2 {
return Some(Err(ErrorBuilder::arg_count_mismatch("env.set", 2, args.len())));
return Some(Err(ErrorBuilder::arg_count_mismatch(
"env.set",
2,
args.len(),
)));
}
let key = match self.reg_load(args[0]) {
Ok(v) => v.to_string(),
@ -247,24 +351,24 @@ impl MirInterpreter {
}
}
} else {
return Some(Err(self.err_invalid(
"env.box_introspect.kind expects 1 arg",
)));
return Some(Err(
self.err_invalid("env.box_introspect.kind expects 1 arg")
));
}
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
let result = plugin_loader_v2::handle_box_introspect("kind", &collected);
#[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))]
let result: crate::bid::BidResult<Option<Box<dyn crate::box_trait::NyashBox>>> =
Err(crate::bid::BidError::PluginError);
let result: crate::bid::BidResult<
Option<Box<dyn crate::box_trait::NyashBox>>,
> = Err(crate::bid::BidError::PluginError);
match result {
Ok(Some(b)) => Some(Ok(VMValue::BoxRef(Arc::from(b)))),
Ok(None) => Some(Ok(VMValue::Void)),
Err(e) => Some(Err(self.err_with_context(
"env.box_introspect.kind",
&format!("{:?}", e),
))),
Err(e) => Some(Err(
self.err_with_context("env.box_introspect.kind", &format!("{:?}", e))
)),
}
}
"hostbridge.extern_invoke" => {
@ -272,17 +376,30 @@ impl MirInterpreter {
eprintln!("[hb:entry:provider] hostbridge.extern_invoke");
}
if args.len() < 2 {
return Some(Err(self.err_invalid("extern_invoke expects at least 2 args")));
return Some(Err(
self.err_invalid("extern_invoke expects at least 2 args")
));
}
let name = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
let method = match self.reg_load(args[1]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
let name = match self.reg_load(args[0]) {
Ok(v) => v.to_string(),
Err(e) => return Some(Err(e)),
};
let method = match self.reg_load(args[1]) {
Ok(v) => v.to_string(),
Err(e) => return Some(Err(e)),
};
// Extract first payload arg (optional)
let mut first_arg_str: Option<String> = None;
if let Some(a2) = args.get(2) {
let v = match self.reg_load(*a2) { Ok(v) => v, Err(e) => return Some(Err(e)) };
let v = match self.reg_load(*a2) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
match v {
VMValue::BoxRef(b) => {
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
if let Some(ab) =
b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
{
let idx: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(0));
let elem = ab.get(idx);
@ -299,16 +416,27 @@ impl MirInterpreter {
eprintln!("[hb:dispatch:provider] {} {}", name, method);
}
let out = match (name.as_str(), method.as_str()) {
("env.codegen", "link_object") if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") => {
("env.codegen", "link_object")
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") =>
{
// Trace payload shape before actual handling
if let Some(a2) = args.get(2) {
let v = match self.reg_load(*a2) { Ok(v) => v, Err(_) => VMValue::Void };
let v = match self.reg_load(*a2) {
Ok(v) => v,
Err(_) => VMValue::Void,
};
match &v {
VMValue::BoxRef(b) => {
if b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>().is_some() {
if b.as_any()
.downcast_ref::<crate::boxes::array::ArrayBox>()
.is_some()
{
eprintln!("[hb:provider:args] link_object third=ArrayBox");
} else {
eprintln!("[hb:provider:args] link_object third=BoxRef({})", b.type_name());
eprintln!(
"[hb:provider:args] link_object third=BoxRef({})",
b.type_name()
);
}
}
other => {
@ -327,13 +455,19 @@ impl MirInterpreter {
};
match v {
VMValue::BoxRef(b) => {
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
if let Some(ab) =
b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
{
let idx0: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(0));
let elem0 = ab.get(idx0).to_string_box().value;
let mut exe: Option<String> = None;
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
let idx1: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(1));
let e1 = ab.get(idx1).to_string_box().value;
if !e1.is_empty() { exe = Some(e1); }
if !e1.is_empty() {
exe = Some(e1);
}
(elem0, exe)
} else {
(b.to_string_box().value, None)
@ -342,25 +476,47 @@ impl MirInterpreter {
_ => (v.to_string(), None),
}
} else {
return Some(Err(self.err_invalid("extern_invoke env.codegen.link_object expects args array")));
return Some(Err(self.err_invalid(
"extern_invoke env.codegen.link_object expects args array",
)));
};
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
return Some(Err(ErrorBuilder::invalid_instruction("env.codegen.link_object: C-API route disabled")));
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
.ok()
.as_deref()
!= Some("1")
{
return Some(Err(ErrorBuilder::invalid_instruction(
"env.codegen.link_object: C-API route disabled",
)));
}
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
let obj = std::path::PathBuf::from(objs);
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
let exe = exe_out
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(
&obj,
&exe,
extra.as_deref(),
) {
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
Err(e) => Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string()))
Err(e) => Err(ErrorBuilder::with_context(
"env.codegen.link_object",
&e.to_string(),
)),
}
}
("env.mirbuilder", "emit") => {
if let Some(s) = first_arg_str {
// Phase 21.8: Read imports from environment variable if present
let imports = if let Ok(imports_json) = std::env::var("HAKO_MIRBUILDER_IMPORTS") {
match serde_json::from_str::<std::collections::HashMap<String, String>>(&imports_json) {
let imports = if let Ok(imports_json) =
std::env::var("HAKO_MIRBUILDER_IMPORTS")
{
match serde_json::from_str::<
std::collections::HashMap<String, String>,
>(&imports_json)
{
Ok(map) => map,
Err(e) => {
eprintln!("[mirbuilder/imports] Failed to parse HAKO_MIRBUILDER_IMPORTS: {}", e);
@ -383,16 +539,21 @@ impl MirInterpreter {
if let Some(s) = first_arg_str {
let opts = crate::host_providers::llvm_codegen::Opts {
out: None,
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from),
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT")
.ok()
.map(std::path::PathBuf::from),
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok(),
timeout_ms: None,
};
match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts) {
match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts)
{
Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())),
Err(e) => Err(self.err_with_context("env.codegen.emit_object", &e.to_string())),
Err(e) => Err(self
.err_with_context("env.codegen.emit_object", &e.to_string())),
}
} else {
Err(self.err_invalid("extern_invoke env.codegen.emit_object expects 1 arg"))
Err(self
.err_invalid("extern_invoke env.codegen.emit_object expects 1 arg"))
}
}
("env.codegen", "link_object") => {
@ -408,12 +569,18 @@ impl MirInterpreter {
};
match v {
VMValue::BoxRef(b) => {
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
if let Some(ab) =
b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
{
let idx0: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(0));
obj_s = Some(ab.get(idx0).to_string_box().value);
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
let idx1: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(1));
let s1 = ab.get(idx1).to_string_box().value;
if !s1.is_empty() { exe_s = Some(s1); }
if !s1.is_empty() {
exe_s = Some(s1);
}
} else {
obj_s = Some(b.to_string_box().value);
}
@ -426,18 +593,37 @@ impl MirInterpreter {
}
let objs = match obj_s {
Some(s) => s,
None => return Some(Err(self.err_invalid("extern_invoke env.codegen.link_object expects args"))),
None => {
return Some(Err(self.err_invalid(
"extern_invoke env.codegen.link_object expects args",
)))
}
};
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
return Some(Err(ErrorBuilder::invalid_instruction("env.codegen.link_object: C-API route disabled")));
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
.ok()
.as_deref()
!= Some("1")
{
return Some(Err(ErrorBuilder::invalid_instruction(
"env.codegen.link_object: C-API route disabled",
)));
}
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
let obj = std::path::PathBuf::from(objs);
let exe = exe_s.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
let exe = exe_s
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(
&obj,
&exe,
extra.as_deref(),
) {
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
Err(e) => Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string()))
Err(e) => Err(ErrorBuilder::with_context(
"env.codegen.link_object",
&e.to_string(),
)),
}
}
("env.box_introspect", "kind") => {
@ -487,9 +673,7 @@ impl MirInterpreter {
}
}
other => {
if std::env::var("NYASH_BOX_INTROSPECT_TRACE")
.ok()
.as_deref()
if std::env::var("NYASH_BOX_INTROSPECT_TRACE").ok().as_deref()
== Some("1")
{
eprintln!(
@ -509,16 +693,15 @@ impl MirInterpreter {
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
let result = plugin_loader_v2::handle_box_introspect("kind", &collected);
#[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))]
let result: crate::bid::BidResult<Option<Box<dyn NyashBox>>> =
Err(crate::bid::BidError::PluginError);
let result: crate::bid::BidResult<
Option<Box<dyn NyashBox>>,
> = Err(crate::bid::BidError::PluginError);
match result {
Ok(Some(b)) => Ok(VMValue::BoxRef(Arc::from(b))),
Ok(None) => Ok(VMValue::Void),
Err(e) => Err(self.err_with_context(
"env.box_introspect.kind",
&format!("{:?}", e),
)),
Err(e) => Err(self
.err_with_context("env.box_introspect.kind", &format!("{:?}", e))),
}
}
_ => {
@ -529,7 +712,7 @@ impl MirInterpreter {
"hostbridge.extern_invoke unsupported for {}.{} [provider] (check extern_provider_dispatch / env.* handlers)",
name, method
)))
},
}
};
Some(out)
}

View File

@ -10,7 +10,9 @@ impl MirInterpreter {
if let JsonValue::Object(ref mut m) = v {
if !m.contains_key("version") {
m.insert("version".to_string(), JsonValue::from(0));
if let Ok(out) = serde_json::to_string(&v) { return out; }
if let Ok(out) = serde_json::to_string(&v) {
return out;
}
}
}
s.to_string()
@ -52,23 +54,47 @@ impl MirInterpreter {
if let Some(a0) = args.get(0) {
let v = self.reg_load(*a0)?;
// Dev-only: mirror print-trace for extern console.log
if Self::print_trace_enabled() { self.print_trace_emit(&v); }
if Self::print_trace_enabled() {
self.print_trace_emit(&v);
}
// Treat VM Void and BoxRef(VoidBox) as JSON null for dev ergonomics
match &v {
VMValue::Void => { println!("null"); self.write_void(dst); return Ok(()); }
VMValue::Void => {
println!("null");
self.write_void(dst);
return Ok(());
}
VMValue::BoxRef(bx) => {
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
println!("null"); self.write_void(dst); return Ok(());
if bx
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
println!("null");
self.write_void(dst);
return Ok(());
}
if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
println!("{}", sb.value); self.write_void(dst); return Ok(());
if let Some(sb) =
bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
{
println!("{}", sb.value);
self.write_void(dst);
return Ok(());
}
}
VMValue::String(s) => { println!("{}", s); self.write_void(dst); return Ok(()); }
VMValue::String(s) => {
println!("{}", s);
self.write_void(dst);
return Ok(());
}
_ => {}
}
// Operator Box (Stringify) dev flag gated
if std::env::var("NYASH_OPERATOR_BOX_STRINGIFY").ok().as_deref() == Some("1") {
if std::env::var("NYASH_OPERATOR_BOX_STRINGIFY")
.ok()
.as_deref()
== Some("1")
{
if let Some(op) = self.functions.get("StringifyOperator.apply/1").cloned() {
let out = self.exec_function_inner(&op, Some(&[v.clone()]))?;
println!("{}", out.to_string());
@ -151,20 +177,28 @@ impl MirInterpreter {
Ok(())
}
("env.mirbuilder", "emit") => {
let ret = self.extern_provider_dispatch("env.mirbuilder.emit", args).unwrap_or(Ok(VMValue::Void))?;
let ret = self
.extern_provider_dispatch("env.mirbuilder.emit", args)
.unwrap_or(Ok(VMValue::Void))?;
self.write_result(dst, ret);
Ok(())
}
("env.codegen", "emit_object") => {
let ret = self.extern_provider_dispatch("env.codegen.emit_object", args).unwrap_or(Ok(VMValue::Void))?;
let ret = self
.extern_provider_dispatch("env.codegen.emit_object", args)
.unwrap_or(Ok(VMValue::Void))?;
self.write_result(dst, ret);
Ok(())
}
("env.codegen", "link_object") => {
// Args in third param (ArrayBox): [obj_path, exe_out?]
// Note: This branch is used for ExternCall form; provider toggles must be ON.
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
.ok()
.as_deref()
!= Some("1")
{
return Err(self.err_invalid("env.codegen.link_object: C-API route disabled"));
}
// Extract array payload
@ -172,13 +206,19 @@ impl MirInterpreter {
let v = self.reg_load(*a2)?;
match v {
VMValue::BoxRef(b) => {
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
if let Some(ab) =
b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
{
let idx0: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(0));
let elem0 = ab.get(idx0).to_string_box().value;
let mut exe: Option<String> = None;
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
let idx1: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(1));
let e1 = ab.get(idx1).to_string_box().value;
if !e1.is_empty() { exe = Some(e1); }
if !e1.is_empty() {
exe = Some(e1);
}
(elem0, exe)
} else {
(b.to_string_box().value, None)
@ -187,13 +227,18 @@ impl MirInterpreter {
_ => (v.to_string(), None),
}
} else {
return Err(self.err_invalid("extern_invoke env.codegen.link_object expects args array"));
return Err(self
.err_invalid("extern_invoke env.codegen.link_object expects args array"));
};
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
let obj = std::path::PathBuf::from(obj_path);
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
let exe = exe_out
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref())
.map_err(|e| self.err_with_context("env.codegen.link_object", &e.to_string()))?;
.map_err(|e| {
self.err_with_context("env.codegen.link_object", &e.to_string())
})?;
self.write_result(dst, VMValue::String(exe.to_string_lossy().into_owned()));
Ok(())
}
@ -208,17 +253,18 @@ impl MirInterpreter {
("hostbridge", "extern_invoke") => {
if let Some(res) = self.extern_provider_dispatch("hostbridge.extern_invoke", args) {
match res {
Ok(v) => { self.write_result(dst, v); }
Err(e) => { return Err(e); }
Ok(v) => {
self.write_result(dst, v);
}
Err(e) => {
return Err(e);
}
}
return Ok(());
}
return Err(self.err_invalid("hostbridge.extern_invoke unsupported [externals]"));
}
_ => Err(self.err_invalid(format!(
"ExternCall {}.{} not supported",
iface, method
))),
_ => Err(self.err_invalid(format!("ExternCall {}.{} not supported", iface, method))),
}
}
}

View File

@ -13,16 +13,28 @@ impl MirInterpreter {
let v = self.reg_load(value)?;
// Align with calls.rs behavior: Void/BoxRef(VoidBox) prints as null; raw String/StringBox unquoted
match &v {
VMValue::Void => { println!("null"); return Ok(()); }
VMValue::Void => {
println!("null");
return Ok(());
}
VMValue::BoxRef(bx) => {
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
println!("null"); return Ok(());
if bx
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
println!("null");
return Ok(());
}
if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
println!("{}", sb.value); return Ok(());
println!("{}", sb.value);
return Ok(());
}
}
VMValue::String(s) => { println!("{}", s); return Ok(()); }
VMValue::String(s) => {
println!("{}", s);
return Ok(());
}
_ => {}
}
println!("{}", v.to_string());

View File

@ -3,9 +3,7 @@ use super::*;
// VM dispatch trace macro (used across handlers)
macro_rules! trace_dispatch {
($method:expr, $handler:expr) => {
if $method == "length"
&& std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
{
if $method == "length" && std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] length dispatch handler={}", $handler);
}
};
@ -14,15 +12,15 @@ macro_rules! trace_dispatch {
mod arithmetic;
mod boxes;
mod boxes_array;
mod boxes_string;
mod boxes_instance;
mod boxes_map;
mod boxes_object_fields;
mod boxes_instance;
mod boxes_plugin;
mod boxes_string;
mod boxes_void_guards;
mod calls;
mod externals;
mod extern_provider;
mod externals;
mod memory;
mod misc;
@ -90,10 +88,7 @@ impl MirInterpreter {
}
MirInstruction::DebugLog { message, values } => {
// Dev-only: MIR-level debug logging (no new values defined).
if std::env::var("NYASH_MIR_DEBUG_LOG")
.ok()
.as_deref() == Some("1")
{
if std::env::var("NYASH_MIR_DEBUG_LOG").ok().as_deref() == Some("1") {
eprint!("[MIR-LOG] {}:", message);
for vid in values {
let v = self.reg_load(*vid).unwrap_or(VMValue::Void);

View File

@ -8,10 +8,26 @@ impl MirInterpreter {
match v {
VMValue::Void => "void",
VMValue::BoxRef(b) => {
if b.as_any().downcast_ref::<crate::boxes::null_box::NullBox>().is_some() { "null" }
else if b.as_any().downcast_ref::<crate::boxes::missing_box::MissingBox>().is_some() { "missing" }
else if b.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() { "void" }
else { "" }
if b.as_any()
.downcast_ref::<crate::boxes::null_box::NullBox>()
.is_some()
{
"null"
} else if b
.as_any()
.downcast_ref::<crate::boxes::missing_box::MissingBox>()
.is_some()
{
"missing"
} else if b
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
"void"
} else {
""
}
}
_ => "",
}
@ -23,11 +39,7 @@ impl MirInterpreter {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
|| std::env::var("NYASH_VM_TRACE_EXEC").ok().as_deref() == Some("1")
{
let keys: Vec<String> = self
.regs
.keys()
.map(|k| format!("{:?}", k))
.collect();
let keys: Vec<String> = self.regs.keys().map(|k| format!("{:?}", k)).collect();
eprintln!(
"[vm-trace] reg_load undefined id={:?} fn={} last_block={:?} last_inst={:?} regs={}",
id,
@ -39,8 +51,16 @@ impl MirInterpreter {
}
// Dev-time safety valve: tolerate undefined registers as Void when enabled
let tolerate = std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1")
|| std::env::var("HAKO_PHI_VERIFY").ok().map(|v| v.to_ascii_lowercase()).map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false)
|| std::env::var("NYASH_PHI_VERIFY").ok().map(|v| v.to_ascii_lowercase()).map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false);
|| std::env::var("HAKO_PHI_VERIFY")
.ok()
.map(|v| v.to_ascii_lowercase())
.map(|v| v == "1" || v == "true" || v == "on")
.unwrap_or(false)
|| std::env::var("NYASH_PHI_VERIFY")
.ok()
.map(|v| v.to_ascii_lowercase())
.map(|v| v == "1" || v == "true" || v == "on")
.unwrap_or(false);
if tolerate {
return Ok(VMValue::Void);
}
@ -87,21 +107,43 @@ impl MirInterpreter {
v
};
(norm(a), norm(b))
} else { (a, b) };
} else {
(a, b)
};
// Dev: nullish trace for binop
if crate::config::env::null_missing_box_enabled() && Self::box_trace_enabled() {
let (ak, bk) = (crate::backend::abi_util::tag_of_vm(&a), crate::backend::abi_util::tag_of_vm(&b));
let (ak, bk) = (
crate::backend::abi_util::tag_of_vm(&a),
crate::backend::abi_util::tag_of_vm(&b),
);
let (an, bn) = (Self::tag_nullish(&a), Self::tag_nullish(&b));
let op_s = match op { BinaryOp::Add=>"Add", BinaryOp::Sub=>"Sub", BinaryOp::Mul=>"Mul", BinaryOp::Div=>"Div", BinaryOp::Mod=>"Mod", BinaryOp::BitAnd=>"BitAnd", BinaryOp::BitOr=>"BitOr", BinaryOp::BitXor=>"BitXor", BinaryOp::And=>"And", BinaryOp::Or=>"Or", BinaryOp::Shl=>"Shl", BinaryOp::Shr=>"Shr" };
let op_s = match op {
BinaryOp::Add => "Add",
BinaryOp::Sub => "Sub",
BinaryOp::Mul => "Mul",
BinaryOp::Div => "Div",
BinaryOp::Mod => "Mod",
BinaryOp::BitAnd => "BitAnd",
BinaryOp::BitOr => "BitOr",
BinaryOp::BitXor => "BitXor",
BinaryOp::And => "And",
BinaryOp::Or => "Or",
BinaryOp::Shl => "Shl",
BinaryOp::Shr => "Shr",
};
eprintln!("{{\"ev\":\"binop\",\"op\":\"{}\",\"a_k\":\"{}\",\"b_k\":\"{}\",\"a_n\":\"{}\",\"b_n\":\"{}\"}}", op_s, ak, bk, an, bn);
}
Ok(match (op, a, b) {
// Dev-only safety valves for Add (guarded by tolerance or --dev):
// - Treat Void as 0 for numeric +
// - Treat Void as empty string for string +
(Add, VMValue::Void, Integer(y)) | (Add, Integer(y), VMValue::Void) if tolerate => Integer(y),
(Add, VMValue::Void, Integer(y)) | (Add, Integer(y), VMValue::Void) if tolerate => {
Integer(y)
}
(Add, VMValue::Void, Float(y)) | (Add, Float(y), VMValue::Void) if tolerate => Float(y),
(Add, String(s), VMValue::Void) | (Add, VMValue::Void, String(s)) if tolerate => String(s),
(Add, String(s), VMValue::Void) | (Add, VMValue::Void, String(s)) if tolerate => {
String(s)
}
// Dev-only safety valve for Sub (guarded): treat Void as 0
(Sub, Integer(x), VMValue::Void) if tolerate => Integer(x),
(Sub, VMValue::Void, Integer(y)) if tolerate => Integer(0 - y),
@ -129,7 +171,7 @@ impl MirInterpreter {
(BitOr, Integer(x), Integer(y)) => Integer(x | y),
(BitXor, Integer(x), Integer(y)) => Integer(x ^ y),
(And, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x && y),
(Or, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x || y),
(Or, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x || y),
(Shl, Integer(x), Integer(y)) => Integer(x.wrapping_shl(y as u32)),
(Shr, Integer(x), Integer(y)) => Integer(x.wrapping_shr(y as u32)),
(opk, va, vb) => {
@ -142,7 +184,7 @@ impl MirInterpreter {
return Err(VMError::TypeError(format!(
"unsupported binop {:?} on {:?} and {:?}",
opk, va, vb
)))
)));
}
})
}
@ -162,7 +204,9 @@ impl MirInterpreter {
v
};
(norm(a), norm(b))
} else { (a, b) };
} else {
(a, b)
};
// Dev-only safety valve: tolerate Void in comparisons when enabled or in --dev
// → treat Void as 0 for numeric, empty for string
let (a2, b2) = if tolerate {
@ -195,9 +239,19 @@ impl MirInterpreter {
};
// Dev: nullish trace for compare
if crate::config::env::null_missing_box_enabled() && Self::box_trace_enabled() {
let (ak, bk) = (crate::backend::abi_util::tag_of_vm(&a2), crate::backend::abi_util::tag_of_vm(&b2));
let (ak, bk) = (
crate::backend::abi_util::tag_of_vm(&a2),
crate::backend::abi_util::tag_of_vm(&b2),
);
let (an, bn) = (Self::tag_nullish(&a2), Self::tag_nullish(&b2));
let op_s = match op { CompareOp::Eq=>"Eq", CompareOp::Ne=>"Ne", CompareOp::Lt=>"Lt", CompareOp::Le=>"Le", CompareOp::Gt=>"Gt", CompareOp::Ge=>"Ge" };
let op_s = match op {
CompareOp::Eq => "Eq",
CompareOp::Ne => "Ne",
CompareOp::Lt => "Lt",
CompareOp::Le => "Le",
CompareOp::Gt => "Gt",
CompareOp::Ge => "Ge",
};
eprintln!("{{\"ev\":\"cmp\",\"op\":\"{}\",\"a_k\":\"{}\",\"b_k\":\"{}\",\"a_n\":\"{}\",\"b_n\":\"{}\"}}", op_s, ak, bk, an, bn);
}
let result = match (op, &a3, &b3) {
@ -230,7 +284,6 @@ impl MirInterpreter {
};
Ok(result)
}
}
// ---- Box trace (dev-only observer) ----
@ -243,11 +296,15 @@ impl MirInterpreter {
fn box_trace_filter_match(class_name: &str) -> bool {
if let Ok(filt) = std::env::var("NYASH_BOX_TRACE_FILTER") {
let want = filt.trim();
if want.is_empty() { return true; }
if want.is_empty() {
return true;
}
// comma/space separated tokens; match if any token is contained in class
for tok in want.split(|c: char| c == ',' || c.is_whitespace()) {
let t = tok.trim();
if !t.is_empty() && class_name.contains(t) { return true; }
if !t.is_empty() && class_name.contains(t) {
return true;
}
}
false
} else {
@ -272,34 +329,49 @@ impl MirInterpreter {
}
pub(super) fn box_trace_emit_new(&self, class_name: &str, argc: usize) {
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; }
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) {
return;
}
eprintln!(
"{{\"ev\":\"new\",\"class\":\"{}\",\"argc\":{}}}",
Self::json_escape(class_name), argc
Self::json_escape(class_name),
argc
);
}
pub(super) fn box_trace_emit_call(&self, class_name: &str, method: &str, argc: usize) {
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; }
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) {
return;
}
eprintln!(
"{{\"ev\":\"call\",\"class\":\"{}\",\"method\":\"{}\",\"argc\":{}}}",
Self::json_escape(class_name), Self::json_escape(method), argc
Self::json_escape(class_name),
Self::json_escape(method),
argc
);
}
pub(super) fn box_trace_emit_get(&self, class_name: &str, field: &str, val_kind: &str) {
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; }
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) {
return;
}
eprintln!(
"{{\"ev\":\"get\",\"class\":\"{}\",\"field\":\"{}\",\"val\":\"{}\"}}",
Self::json_escape(class_name), Self::json_escape(field), Self::json_escape(val_kind)
Self::json_escape(class_name),
Self::json_escape(field),
Self::json_escape(val_kind)
);
}
pub(super) fn box_trace_emit_set(&self, class_name: &str, field: &str, val_kind: &str) {
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; }
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) {
return;
}
eprintln!(
"{{\"ev\":\"set\",\"class\":\"{}\",\"field\":\"{}\",\"val\":\"{}\"}}",
Self::json_escape(class_name), Self::json_escape(field), Self::json_escape(val_kind)
Self::json_escape(class_name),
Self::json_escape(field),
Self::json_escape(val_kind)
);
}
}
@ -312,7 +384,9 @@ impl MirInterpreter {
}
pub(super) fn print_trace_emit(&self, val: &VMValue) {
if !Self::print_trace_enabled() { return; }
if !Self::print_trace_enabled() {
return;
}
let (kind, class, nullish) = match val {
VMValue::Integer(_) => ("Integer", "".to_string(), None),
VMValue::Float(_) => ("Float", "".to_string(), None),
@ -324,17 +398,43 @@ impl MirInterpreter {
// Prefer InstanceBox.class_name when available
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
let tag = if crate::config::env::null_missing_box_enabled() {
if b.as_any().downcast_ref::<crate::boxes::null_box::NullBox>().is_some() { Some("null") }
else if b.as_any().downcast_ref::<crate::boxes::missing_box::MissingBox>().is_some() { Some("missing") }
else { None }
} else { None };
if b.as_any()
.downcast_ref::<crate::boxes::null_box::NullBox>()
.is_some()
{
Some("null")
} else if b
.as_any()
.downcast_ref::<crate::boxes::missing_box::MissingBox>()
.is_some()
{
Some("missing")
} else {
None
}
} else {
None
};
("BoxRef", inst.class_name.clone(), tag)
} else {
let tag = if crate::config::env::null_missing_box_enabled() {
if b.as_any().downcast_ref::<crate::boxes::null_box::NullBox>().is_some() { Some("null") }
else if b.as_any().downcast_ref::<crate::boxes::missing_box::MissingBox>().is_some() { Some("missing") }
else { None }
} else { None };
if b.as_any()
.downcast_ref::<crate::boxes::null_box::NullBox>()
.is_some()
{
Some("null")
} else if b
.as_any()
.downcast_ref::<crate::boxes::missing_box::MissingBox>()
.is_some()
{
Some("missing")
} else {
None
}
} else {
None
};
("BoxRef", b.type_name().to_string(), tag)
}
}

View File

@ -12,8 +12,8 @@
*/
use super::{MirFunction, MirInterpreter};
use serde_json::json;
use crate::backend::vm::{VMError, VMValue};
use serde_json::json;
#[derive(Debug, Clone)]
struct ParsedSig<'a> {
@ -25,16 +25,25 @@ struct ParsedSig<'a> {
fn parse_method_signature(name: &str) -> Option<ParsedSig<'_>> {
let dot = name.find('.')?;
let slash = name.rfind('/')?;
if dot >= slash { return None; }
if dot >= slash {
return None;
}
let class = &name[..dot];
let method = &name[dot + 1..slash];
let arity_str = &name[slash + 1..];
Some(ParsedSig { class, method, arity_str })
Some(ParsedSig {
class,
method,
arity_str,
})
}
fn extract_instance_box_class(arg0: &VMValue) -> Option<String> {
if let VMValue::BoxRef(bx) = arg0 {
if let Some(inst) = bx.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = bx
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
return Some(inst.class_name.clone());
}
}
@ -47,7 +56,12 @@ fn reroute_to_correct_method(
parsed: &ParsedSig<'_>,
arg_vals: Option<&[VMValue]>,
) -> Option<Result<VMValue, VMError>> {
let target = format!("{}.{}{}", recv_cls, parsed.method, format!("/{}", parsed.arity_str));
let target = format!(
"{}.{}{}",
recv_cls,
parsed.method,
format!("/{}", parsed.arity_str)
);
if let Some(f) = interp.functions.get(&target).cloned() {
// Debug: emit class-reroute event (dev-only)
crate::debug::hub::emit(
@ -149,7 +163,10 @@ fn try_special_method(
if parsed.method == "is_eof" && parsed.arity_str == "0" {
if let Some(args) = arg_vals {
if let VMValue::BoxRef(bx) = &args[0] {
if let Some(inst) = bx.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = bx
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
if recv_cls == "JsonToken" {
let is = match inst.get_field_ng("type") {
Some(crate::value::NyashValue::String(ref s)) => s == "EOF",
@ -158,8 +175,14 @@ fn try_special_method(
return Some(Ok(VMValue::Bool(is)));
}
if recv_cls == "JsonScanner" {
let pos = match inst.get_field_ng("position") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 };
let len = match inst.get_field_ng("length") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 };
let pos = match inst.get_field_ng("position") {
Some(crate::value::NyashValue::Integer(i)) => i,
_ => 0,
};
let len = match inst.get_field_ng("length") {
Some(crate::value::NyashValue::Integer(i)) => i,
_ => 0,
};
return Some(Ok(VMValue::Bool(pos >= len)));
}
}
@ -183,16 +206,35 @@ pub(super) fn pre_exec_reroute(
func: &MirFunction,
arg_vals: Option<&[VMValue]>,
) -> Option<Result<VMValue, VMError>> {
let args = match arg_vals { Some(a) => a, None => return None };
if args.is_empty() { return None; }
let parsed = match parse_method_signature(func.signature.name.as_str()) { Some(p) => p, None => return None };
let recv_cls = match extract_instance_box_class(&args[0]) { Some(c) => c, None => return None };
let args = match arg_vals {
Some(a) => a,
None => return None,
};
if args.is_empty() {
return None;
}
let parsed = match parse_method_signature(func.signature.name.as_str()) {
Some(p) => p,
None => return None,
};
let recv_cls = match extract_instance_box_class(&args[0]) {
Some(c) => c,
None => return None,
};
// Always consider special re-routes (e.g., toString→stringify) even when class matches
if let Some(r) = try_special_reroute(interp, &recv_cls, &parsed, arg_vals) { return Some(r); }
if recv_cls == parsed.class { return None; }
if let Some(r) = try_special_reroute(interp, &recv_cls, &parsed, arg_vals) {
return Some(r);
}
if recv_cls == parsed.class {
return None;
}
// Class mismatch: reroute to same method on the receiver's class
if let Some(r) = reroute_to_correct_method(interp, &recv_cls, &parsed, arg_vals) { return Some(r); }
if let Some(r) = reroute_to_correct_method(interp, &recv_cls, &parsed, arg_vals) {
return Some(r);
}
// Narrow special fallback (e.g., is_eof)
if let Some(r) = try_special_method(&recv_cls, &parsed, arg_vals) { return Some(r); }
if let Some(r) = try_special_method(&recv_cls, &parsed, arg_vals) {
return Some(r);
}
None
}

View File

@ -71,20 +71,32 @@ impl MirInterpreter {
}
/// Register static box declarations (called from vm.rs during setup)
pub fn register_static_box_decl(&mut self, name: String, decl: crate::core::model::BoxDeclaration) {
pub fn register_static_box_decl(
&mut self,
name: String,
decl: crate::core::model::BoxDeclaration,
) {
self.static_box_decls.insert(name, decl);
}
/// Ensure static box singleton instance exists, create if not
/// Returns mutable reference to the singleton instance
fn ensure_static_box_instance(&mut self, box_name: &str) -> Result<&mut crate::instance_v2::InstanceBox, VMError> {
fn ensure_static_box_instance(
&mut self,
box_name: &str,
) -> Result<&mut crate::instance_v2::InstanceBox, VMError> {
// Check if instance already exists
if !self.static_boxes.contains_key(box_name) {
// Get declaration
let decl = self.static_box_decls.get(box_name)
.ok_or_else(|| VMError::InvalidInstruction(
format!("static box declaration not found: {}", box_name)
))?
let decl = self
.static_box_decls
.get(box_name)
.ok_or_else(|| {
VMError::InvalidInstruction(format!(
"static box declaration not found: {}",
box_name
))
})?
.clone();
// Create instance from declaration
@ -97,15 +109,20 @@ impl MirInterpreter {
self.static_boxes.insert(box_name.to_string(), instance);
if std::env::var("NYASH_VM_STATIC_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-static] created singleton instance for static box: {}", box_name);
eprintln!(
"[vm-static] created singleton instance for static box: {}",
box_name
);
}
}
// Return mutable reference
self.static_boxes.get_mut(box_name)
.ok_or_else(|| VMError::InvalidInstruction(
format!("static box instance not found after creation: {}", box_name)
self.static_boxes.get_mut(box_name).ok_or_else(|| {
VMError::InvalidInstruction(format!(
"static box instance not found after creation: {}",
box_name
))
})
}
/// Check if a function name represents a static box method
@ -169,7 +186,12 @@ impl MirInterpreter {
// Build helpful error message
let mut names: Vec<&String> = module.functions.keys().collect();
names.sort();
let avail = names.into_iter().take(12).cloned().collect::<Vec<_>>().join(", ");
let avail = names
.into_iter()
.take(12)
.cloned()
.collect::<Vec<_>>()
.join(", ");
let tried = candidates.join(", ");
let msg = format!(
"entry function not found. searched: [{}]. available: [{}]. hint: define 'static box Main {{ method main(args){{ ... }} }}' or set NYASH_ENTRY=Name",
@ -200,9 +222,13 @@ impl MirInterpreter {
argv_list = out;
}
} else if let Ok(s) = std::env::var("NYASH_SCRIPT_ARGS_JSON") {
if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) { argv_list = v; }
if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) {
argv_list = v;
}
} else if let Ok(s) = std::env::var("NYASH_ARGV") {
if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) { argv_list = v; }
if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) {
argv_list = v;
}
}
// Construct ArrayBox of StringBox
let array = crate::boxes::array::ArrayBox::new();

View File

@ -3,8 +3,8 @@
//! レジスタ値の読み込み+型変換チェーンを統一します。
use super::super::*;
use crate::mir::ValueId;
use crate::box_trait::NyashBox;
use crate::mir::ValueId;
impl MirInterpreter {
/// レジスタ値をBox<dyn NyashBox>として読み込む
@ -53,7 +53,13 @@ impl MirInterpreter {
VMValue::Bool(_) => "Bool",
VMValue::String(_) => "String",
VMValue::Void => "Void",
VMValue::BoxRef(b) => return Err(self.err_type_mismatch("load_as_int", "Integer", &b.type_name())),
VMValue::BoxRef(b) => {
return Err(self.err_type_mismatch(
"load_as_int",
"Integer",
&b.type_name(),
))
}
VMValue::Future(_) => "Future",
};
Err(self.err_type_mismatch("load_as_int", "Integer", type_name))
@ -83,7 +89,9 @@ impl MirInterpreter {
VMValue::Bool(_) => "Bool",
VMValue::String(_) => "String",
VMValue::Void => "Void",
VMValue::BoxRef(b) => return Err(self.err_type_mismatch("load_as_bool", "Bool", &b.type_name())),
VMValue::BoxRef(b) => {
return Err(self.err_type_mismatch("load_as_bool", "Bool", &b.type_name()))
}
VMValue::Future(_) => "Future",
};
Err(self.err_type_mismatch("load_as_bool", "Bool", type_name))

View File

@ -42,7 +42,10 @@ impl ErrorBuilder {
#[inline]
#[allow(dead_code)]
pub fn type_mismatch(method: &str, expected: &str, actual: &str) -> VMError {
VMError::InvalidInstruction(format!("{} expects {} type, got {}", method, expected, actual))
VMError::InvalidInstruction(format!(
"{} expects {} type, got {}",
method, expected, actual
))
}
/// Index out of bounds error
@ -60,7 +63,10 @@ impl ErrorBuilder {
#[inline]
#[allow(dead_code)]
pub fn out_of_bounds(method: &str, index: usize, len: usize) -> VMError {
VMError::InvalidInstruction(format!("{} index out of bounds: {} >= {}", method, index, len))
VMError::InvalidInstruction(format!(
"{} index out of bounds: {} >= {}",
method, index, len
))
}
/// Unsupported operation error

View File

@ -1,11 +1,11 @@
//! MIR Interpreter共通ユーティリティ
pub mod destination_helpers;
pub mod arg_validation;
pub mod receiver_helpers;
pub mod error_helpers;
pub mod conversion_helpers;
pub mod destination_helpers;
pub mod error_helpers;
pub mod naming;
pub mod receiver_helpers;
// Phase 21.2: adapter_dev removed - all adapter functions now in .hako implementation
// Selective re-export (only naming is widely used via utils::normalize_arity_suffix)

View File

@ -12,4 +12,3 @@ pub fn normalize_arity_suffix(name: &str) -> &str {
None => name,
}
}

View File

@ -23,9 +23,7 @@ impl MirInterpreter {
let receiver_value = self.reg_load(receiver)?;
match receiver_value {
VMValue::BoxRef(b) => Ok(b),
_ => Err(VMError::InvalidInstruction(
"receiver must be Box".into(),
)),
_ => Err(VMError::InvalidInstruction("receiver must be Box".into())),
}
}
}

View File

@ -7,5 +7,5 @@
// Re-export all arithmetic operations from the dedicated arithmetic module
pub use crate::boxes::arithmetic::{
AddBox, SubtractBox, MultiplyBox, DivideBox, ModuloBox, CompareBox,
AddBox, CompareBox, DivideBox, ModuloBox, MultiplyBox, SubtractBox,
};

View File

@ -5,8 +5,8 @@
* 🎯 Phase 2.4: Delete this file to remove builtin ArrayBox support
*/
use crate::box_trait::NyashBox;
use crate::box_factory::RuntimeError;
use crate::box_trait::NyashBox;
/// Create builtin ArrayBox instance
///
@ -31,4 +31,4 @@ mod tests {
let result = create(&[]).unwrap();
assert!(result.as_any().downcast_ref::<ArrayBox>().is_some());
}
}
}

View File

@ -5,8 +5,8 @@
* 🎯 Phase 2.3: Delete this file to remove builtin BoolBox support
*/
use crate::box_trait::{NyashBox, BoolBox};
use crate::box_factory::RuntimeError;
use crate::box_trait::{BoolBox, NyashBox};
/// Create builtin BoolBox instance
///
@ -35,4 +35,4 @@ mod tests {
let result = create(&[]).unwrap();
assert!(result.as_any().downcast_ref::<BoolBox>().is_some());
}
}
}

View File

@ -5,8 +5,8 @@
* 🎯 Phase 2.6: Delete this file to remove builtin ConsoleBox support (LAST)
*/
use crate::box_trait::NyashBox;
use crate::box_factory::RuntimeError;
use crate::box_trait::NyashBox;
/// Create builtin ConsoleBox instance
///
@ -32,4 +32,4 @@ mod tests {
let result = create(&[]).unwrap();
assert!(result.as_any().downcast_ref::<ConsoleBox>().is_some());
}
}
}

View File

@ -7,8 +7,8 @@
* 🎯 Core-ro mode: This is used directly
*/
use crate::box_trait::NyashBox;
use crate::box_factory::RuntimeError;
use crate::box_trait::NyashBox;
use crate::boxes::file::core_ro::CoreRoFileIo;
use crate::boxes::file::FileBox;
use std::sync::Arc;
@ -28,9 +28,7 @@ pub fn create(_args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeE
});
}
eprintln!(
"[FileBox] Using builtin core-ro fallback implementation"
);
eprintln!("[FileBox] Using builtin core-ro fallback implementation");
// Create FileBox with core-ro provider directly
// Don't rely on global provider_lock which may not be initialized

View File

@ -5,8 +5,8 @@
* 🎯 Phase 2.2: Delete this file to remove builtin IntegerBox support
*/
use crate::box_trait::{NyashBox, IntegerBox};
use crate::box_factory::RuntimeError;
use crate::box_trait::{IntegerBox, NyashBox};
/// Create builtin IntegerBox instance
///
@ -35,4 +35,4 @@ mod tests {
let result = create(&[]).unwrap();
assert!(result.as_any().downcast_ref::<IntegerBox>().is_some());
}
}
}

View File

@ -5,8 +5,8 @@
* 🎯 Phase 2.5: Delete this file to remove builtin MapBox support
*/
use crate::box_trait::NyashBox;
use crate::box_factory::RuntimeError;
use crate::box_trait::NyashBox;
/// Create builtin MapBox instance
///
@ -31,4 +31,4 @@ mod tests {
let result = create(&[]).unwrap();
assert!(result.as_any().downcast_ref::<MapBox>().is_some());
}
}
}

View File

@ -15,15 +15,15 @@
*/
// Phase 2.1-2.6: Delete these modules one by one
pub mod string_box; // DELETE: Phase 2.1 (plugin ready)
pub mod integer_box; // DELETE: Phase 2.2 (plugin ready)
pub mod bool_box; // DELETE: Phase 2.3 (plugin needed)
pub mod array_box; // DELETE: Phase 2.4 (plugin check)
pub mod map_box; // DELETE: Phase 2.5 (plugin check)
pub mod console_box; // DELETE: Phase 2.6 (LAST - critical for logging)
pub mod array_box; // DELETE: Phase 2.4 (plugin check)
pub mod bool_box; // DELETE: Phase 2.3 (plugin needed)
pub mod console_box;
pub mod integer_box; // DELETE: Phase 2.2 (plugin ready)
pub mod map_box; // DELETE: Phase 2.5 (plugin check)
pub mod string_box; // DELETE: Phase 2.1 (plugin ready) // DELETE: Phase 2.6 (LAST - critical for logging)
// Fallback support (Phase 15.5: Fallback Guarantee)
pub mod file_box; // FALLBACK: Core-ro FileBox for auto/core-ro modes
pub mod file_box; // FALLBACK: Core-ro FileBox for auto/core-ro modes
// Special consideration
pub mod null_box; // DISCUSS: Keep as primitive?
pub mod null_box; // DISCUSS: Keep as primitive?

View File

@ -5,8 +5,8 @@
* 📋 Discussion needed: Is null a language primitive or plugin concern?
*/
use crate::box_trait::NyashBox;
use crate::box_factory::RuntimeError;
use crate::box_trait::NyashBox;
/// Create builtin NullBox instance
///
@ -26,4 +26,4 @@ mod tests {
let result = create(&[]).unwrap();
assert!(result.as_any().downcast_ref::<NullBox>().is_some());
}
}
}

View File

@ -5,8 +5,8 @@
* 🎯 Phase 2.1: Delete this file to remove builtin StringBox support
*/
use crate::box_trait::{NyashBox, StringBox};
use crate::box_factory::RuntimeError;
use crate::box_trait::{NyashBox, StringBox};
/// Create builtin StringBox instance
///
@ -35,4 +35,4 @@ mod tests {
let result = create(&[]).unwrap();
assert!(result.as_any().downcast_ref::<StringBox>().is_some());
}
}
}

View File

@ -138,7 +138,10 @@ impl UnifiedBoxRegistry {
Some("strict_plugin_first") | _ => FactoryPolicy::StrictPluginFirst, // Phase 15.5: Plugin First DEFAULT!
};
eprintln!("[UnifiedBoxRegistry] 🎯 Factory Policy: {:?} (Phase 15.5: Everything is Plugin!)", policy);
eprintln!(
"[UnifiedBoxRegistry] 🎯 Factory Policy: {:?} (Phase 15.5: Everything is Plugin!)",
policy
);
Self::with_policy(policy)
}
@ -175,7 +178,7 @@ impl UnifiedBoxRegistry {
if std::env::var("NYASH_USE_PLUGIN_BUILTINS").is_ok() {
if let Ok(types) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES") {
if types.split(',').any(|t| t.trim() == name) {
return false; // 予約型として扱わない
return false; // 予約型として扱わない
}
}
}
@ -229,19 +232,19 @@ impl UnifiedBoxRegistry {
match self.policy {
FactoryPolicy::StrictPluginFirst => match factory_type {
FactoryType::Plugin => 0, // Highest priority
FactoryType::User => 1, // Medium priority
FactoryType::Builtin => 2, // Lowest priority
FactoryType::Plugin => 0, // Highest priority
FactoryType::User => 1, // Medium priority
FactoryType::Builtin => 2, // Lowest priority
},
FactoryPolicy::CompatPluginFirst => match factory_type {
FactoryType::Plugin => 0, // Highest priority
FactoryType::Builtin => 1, // Medium priority
FactoryType::User => 2, // Lowest priority
FactoryType::Plugin => 0, // Highest priority
FactoryType::Builtin => 1, // Medium priority
FactoryType::User => 2, // Lowest priority
},
FactoryPolicy::BuiltinFirst => match factory_type {
FactoryType::Builtin => 0, // Highest priority (current default)
FactoryType::User => 1, // Medium priority
FactoryType::Plugin => 2, // Lowest priority
FactoryType::Builtin => 0, // Highest priority (current default)
FactoryType::User => 1, // Medium priority
FactoryType::Plugin => 2, // Lowest priority
},
}
} else {
@ -271,7 +274,9 @@ impl UnifiedBoxRegistry {
// Prefer plugin-builtins when enabled and provider is available in v2 registry
// BUT: Skip if plugins are explicitly disabled
let plugins_disabled = std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() == Some("1");
if !plugins_disabled && std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1") {
if !plugins_disabled
&& std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1")
{
use crate::runtime::{get_global_registry, BoxProvider};
// Allowlist types for override: env NYASH_PLUGIN_OVERRIDE_TYPES="ArrayBox,MapBox" (default: none)
let allow: Vec<String> = if let Ok(list) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES")
@ -338,7 +343,8 @@ impl UnifiedBoxRegistry {
Err(e) => {
// FileBox special case: handle fallback based on mode
if name == "FileBox" {
if let Some(fallback_result) = self.try_filebox_fallback(name, args, &e) {
if let Some(fallback_result) = self.try_filebox_fallback(name, args, &e)
{
return fallback_result;
}
}
@ -386,14 +392,23 @@ impl UnifiedBoxRegistry {
match mode {
provider_registry::FileBoxMode::PluginOnly => {
// Fail-Fast: return the original error immediately
eprintln!("[FileBox] Plugin creation failed in plugin-only mode: {}", original_error);
eprintln!(
"[FileBox] Plugin creation failed in plugin-only mode: {}",
original_error
);
Some(Err(RuntimeError::InvalidOperation {
message: format!("FileBox plugin creation failed (plugin-only mode): {}", original_error),
message: format!(
"FileBox plugin creation failed (plugin-only mode): {}",
original_error
),
}))
}
provider_registry::FileBoxMode::Auto => {
// Auto mode: try fallback to builtin/core-ro
eprintln!("[FileBox] Plugin creation failed, falling back to builtin/core-ro: {}", original_error);
eprintln!(
"[FileBox] Plugin creation failed, falling back to builtin/core-ro: {}",
original_error
);
// Try builtin factory if available
for factory in &self.factories {

View File

@ -6,8 +6,8 @@
*/
use super::BoxFactory;
use crate::box_trait::NyashBox;
use super::RuntimeError;
use crate::box_trait::NyashBox;
use crate::runtime::get_global_registry;
/// Factory for plugin-based Box types
@ -29,11 +29,20 @@ impl BoxFactory for PluginBoxFactory {
) -> Result<Box<dyn NyashBox>, RuntimeError> {
// Check if plugins are disabled
let plugins_disabled = std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() == Some("1");
eprintln!("[PluginBoxFactory] NYASH_DISABLE_PLUGINS={} for {}",
std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref().unwrap_or("not set"), name);
eprintln!(
"[PluginBoxFactory] NYASH_DISABLE_PLUGINS={} for {}",
std::env::var("NYASH_DISABLE_PLUGINS")
.ok()
.as_deref()
.unwrap_or("not set"),
name
);
if plugins_disabled {
return Err(RuntimeError::InvalidOperation {
message: format!("Plugins disabled (NYASH_DISABLE_PLUGINS=1), cannot create {}", name),
message: format!(
"Plugins disabled (NYASH_DISABLE_PLUGINS=1), cannot create {}",
name
),
});
}

View File

@ -6,9 +6,9 @@
*/
use super::BoxFactory;
use super::{RuntimeError, SharedState};
use crate::box_trait::NyashBox;
use crate::instance_v2::InstanceBox;
use super::{RuntimeError, SharedState};
/// Factory for user-defined Box types
pub struct UserDefinedBoxFactory {

View File

@ -16,16 +16,14 @@
use crate::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox};
use crate::boxes::FloatBox;
use crate::operator_traits::{
DynamicAdd, DynamicDiv, DynamicMul, DynamicSub, OperatorError,
};
use crate::operator_traits::{DynamicAdd, DynamicDiv, DynamicMul, DynamicSub, OperatorError};
// Phase 1-2: Import macros, helpers, and static implementations from separate modules
mod macros;
mod helpers;
mod macros;
mod static_ops;
pub use helpers::{concat_result, can_repeat};
pub use helpers::{can_repeat, concat_result};
pub use macros::impl_static_numeric_ops;
// Phase 2: Static implementations are now in static_ops.rs
@ -392,16 +390,27 @@ impl OperatorResolver {
#[inline]
fn try_dyn_left_add(left: &dyn NyashBox, right: &dyn NyashBox) -> Option<Box<dyn NyashBox>> {
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
if let Some(result) = int_box.try_add(right) { return Some(result); }
if let Some(result) = int_box.try_add(right) {
return Some(result);
}
}
if let Some(str_box) = left.as_any().downcast_ref::<crate::box_trait::StringBox>() {
if let Some(result) = str_box.try_add(right) { return Some(result); }
if let Some(result) = str_box.try_add(right) {
return Some(result);
}
}
if let Some(float_box) = left.as_any().downcast_ref::<crate::boxes::math_box::FloatBox>() {
if let Some(result) = float_box.try_add(right) { return Some(result); }
if let Some(float_box) = left
.as_any()
.downcast_ref::<crate::boxes::math_box::FloatBox>()
{
if let Some(result) = float_box.try_add(right) {
return Some(result);
}
}
if let Some(bool_box) = left.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
if let Some(result) = bool_box.try_add(right) { return Some(result); }
if let Some(result) = bool_box.try_add(right) {
return Some(result);
}
}
None
}
@ -409,13 +418,22 @@ impl OperatorResolver {
#[inline]
fn try_dyn_left_sub(left: &dyn NyashBox, right: &dyn NyashBox) -> Option<Box<dyn NyashBox>> {
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
if let Some(result) = int_box.try_sub(right) { return Some(result); }
if let Some(result) = int_box.try_sub(right) {
return Some(result);
}
}
if let Some(float_box) = left.as_any().downcast_ref::<crate::boxes::math_box::FloatBox>() {
if let Some(result) = float_box.try_sub(right) { return Some(result); }
if let Some(float_box) = left
.as_any()
.downcast_ref::<crate::boxes::math_box::FloatBox>()
{
if let Some(result) = float_box.try_sub(right) {
return Some(result);
}
}
if let Some(bool_box) = left.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
if let Some(result) = bool_box.try_sub(right) { return Some(result); }
if let Some(result) = bool_box.try_sub(right) {
return Some(result);
}
}
None
}
@ -423,16 +441,27 @@ impl OperatorResolver {
#[inline]
fn try_dyn_left_mul(left: &dyn NyashBox, right: &dyn NyashBox) -> Option<Box<dyn NyashBox>> {
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
if let Some(result) = int_box.try_mul(right) { return Some(result); }
if let Some(result) = int_box.try_mul(right) {
return Some(result);
}
}
if let Some(str_box) = left.as_any().downcast_ref::<crate::box_trait::StringBox>() {
if let Some(result) = str_box.try_mul(right) { return Some(result); }
if let Some(result) = str_box.try_mul(right) {
return Some(result);
}
}
if let Some(float_box) = left.as_any().downcast_ref::<crate::boxes::math_box::FloatBox>() {
if let Some(result) = float_box.try_mul(right) { return Some(result); }
if let Some(float_box) = left
.as_any()
.downcast_ref::<crate::boxes::math_box::FloatBox>()
{
if let Some(result) = float_box.try_mul(right) {
return Some(result);
}
}
if let Some(bool_box) = left.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
if let Some(result) = bool_box.try_mul(right) { return Some(result); }
if let Some(result) = bool_box.try_mul(right) {
return Some(result);
}
}
None
}
@ -442,7 +471,10 @@ impl OperatorResolver {
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
return int_box.try_div(right);
}
if let Some(float_box) = left.as_any().downcast_ref::<crate::boxes::math_box::FloatBox>() {
if let Some(float_box) = left
.as_any()
.downcast_ref::<crate::boxes::math_box::FloatBox>()
{
return float_box.try_div(right);
}
if let Some(bool_box) = left.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
@ -458,7 +490,9 @@ impl OperatorResolver {
// Try to cast to concrete types first and use their DynamicAdd implementation
// This approach uses the concrete types rather than trait objects
if let Some(result) = Self::try_dyn_left_add(left, right) { return Ok(result); }
if let Some(result) = Self::try_dyn_left_add(left, right) {
return Ok(result);
}
Err(OperatorError::UnsupportedOperation {
operator: "+".to_string(),
@ -472,7 +506,9 @@ impl OperatorResolver {
left: &dyn NyashBox,
right: &dyn NyashBox,
) -> Result<Box<dyn NyashBox>, OperatorError> {
if let Some(result) = Self::try_dyn_left_sub(left, right) { return Ok(result); }
if let Some(result) = Self::try_dyn_left_sub(left, right) {
return Ok(result);
}
Err(OperatorError::UnsupportedOperation {
operator: "-".to_string(),
@ -486,7 +522,9 @@ impl OperatorResolver {
left: &dyn NyashBox,
right: &dyn NyashBox,
) -> Result<Box<dyn NyashBox>, OperatorError> {
if let Some(result) = Self::try_dyn_left_mul(left, right) { return Ok(result); }
if let Some(result) = Self::try_dyn_left_mul(left, right) {
return Ok(result);
}
Err(OperatorError::UnsupportedOperation {
operator: "*".to_string(),
@ -500,7 +538,9 @@ impl OperatorResolver {
left: &dyn NyashBox,
right: &dyn NyashBox,
) -> Result<Box<dyn NyashBox>, OperatorError> {
if let Some(result) = Self::try_dyn_left_div(left, right) { return Ok(result); }
if let Some(result) = Self::try_dyn_left_div(left, right) {
return Ok(result);
}
Err(OperatorError::UnsupportedOperation {
operator: "/".to_string(),

View File

@ -60,4 +60,4 @@ pub fn concat_result(left: &dyn NyashBox, right: &dyn NyashBox) -> Box<dyn Nyash
#[inline]
pub fn can_repeat(times: i64) -> bool {
(0..=10_000).contains(&times)
}
}

View File

@ -28,21 +28,21 @@ macro_rules! impl_static_numeric_ops {
impl crate::operator_traits::NyashAdd<$ty> for $ty {
type Output = $ty;
fn add(self, rhs: $ty) -> Self::Output {
< $ty >::new(self.value + rhs.value)
<$ty>::new(self.value + rhs.value)
}
}
impl crate::operator_traits::NyashSub<$ty> for $ty {
type Output = $ty;
fn sub(self, rhs: $ty) -> Self::Output {
< $ty >::new(self.value - rhs.value)
<$ty>::new(self.value - rhs.value)
}
}
impl crate::operator_traits::NyashMul<$ty> for $ty {
type Output = $ty;
fn mul(self, rhs: $ty) -> Self::Output {
< $ty >::new(self.value * rhs.value)
<$ty>::new(self.value * rhs.value)
}
}
@ -52,7 +52,7 @@ macro_rules! impl_static_numeric_ops {
if rhs.value == $zero {
Err(crate::operator_traits::OperatorError::DivisionByZero)
} else {
Ok(< $ty >::new(self.value / rhs.value))
Ok(<$ty>::new(self.value / rhs.value))
}
}
}
@ -60,4 +60,4 @@ macro_rules! impl_static_numeric_ops {
}
// Re-export the macro for external use
pub use impl_static_numeric_ops;
pub use impl_static_numeric_ops;

View File

@ -6,8 +6,8 @@
use crate::box_trait::{BoolBox, IntegerBox, StringBox};
use crate::boxes::FloatBox;
use crate::operator_traits::{NyashAdd, NyashMul};
use crate::impl_static_numeric_ops;
use crate::operator_traits::{NyashAdd, NyashMul};
// ===== Macro-generated static implementations =====

View File

@ -160,15 +160,7 @@ pub trait NyashBox: BoxCore + Debug {
// ===== Basic Box Types (Re-exported from basic module) =====
// Re-export all basic box types from the dedicated basic module
pub use crate::boxes::basic::{
BoolBox, ErrorBox, FileBox, IntegerBox, StringBox, VoidBox,
};
pub use crate::boxes::basic::{BoolBox, ErrorBox, FileBox, IntegerBox, StringBox, VoidBox};
// Old Box implementations have been moved to separate files
// ArrayBox is now defined in boxes::array module

View File

@ -137,4 +137,4 @@ impl Display for AddBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -76,4 +76,4 @@ impl CompareBox {
let right_str = right.to_string_box();
BoolBox::new(left_str.value >= right_str.value)
}
}
}

View File

@ -124,4 +124,4 @@ impl Display for DivideBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -13,19 +13,19 @@
// Individual arithmetic operation implementations
mod add_box;
mod subtract_box;
mod multiply_box;
mod compare_box;
mod divide_box;
mod modulo_box;
mod compare_box;
mod multiply_box;
mod subtract_box;
// Re-export all arithmetic box types
pub use add_box::AddBox;
pub use subtract_box::SubtractBox;
pub use multiply_box::MultiplyBox;
pub use compare_box::CompareBox;
pub use divide_box::DivideBox;
pub use modulo_box::ModuloBox;
pub use compare_box::CompareBox;
pub use multiply_box::MultiplyBox;
pub use subtract_box::SubtractBox;
// Re-export for convenience - common pattern in arithmetic operations
pub use crate::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox};
@ -125,4 +125,4 @@ mod tests {
assert_eq!(CompareBox::greater(&left, &right).value, false);
assert_eq!(CompareBox::equals(&left, &right).value, false);
}
}
}

View File

@ -115,4 +115,4 @@ impl Display for MultiplyBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -116,4 +116,4 @@ impl Display for SubtractBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -42,11 +42,11 @@ impl ArrayBox {
None => {
let strict = std::env::var("HAKO_OOB_STRICT")
.ok()
.map(|v| matches!(v.as_str(), "1"|"true"|"on"))
.map(|v| matches!(v.as_str(), "1" | "true" | "on"))
.unwrap_or(false)
|| std::env::var("NYASH_OOB_STRICT")
.ok()
.map(|v| matches!(v.as_str(), "1"|"true"|"on"))
.map(|v| matches!(v.as_str(), "1" | "true" | "on"))
.unwrap_or(false);
if strict {
Box::new(StringBox::new("[array/empty/pop] empty array"))
@ -92,11 +92,11 @@ impl ArrayBox {
None => {
let strict = std::env::var("HAKO_OOB_STRICT")
.ok()
.map(|v| matches!(v.as_str(), "1"|"true"|"on"))
.map(|v| matches!(v.as_str(), "1" | "true" | "on"))
.unwrap_or(false)
|| std::env::var("NYASH_OOB_STRICT")
.ok()
.map(|v| matches!(v.as_str(), "1"|"true"|"on"))
.map(|v| matches!(v.as_str(), "1" | "true" | "on"))
.unwrap_or(false);
if strict {
// Mark OOB occurrence for runner policies (GateC strict fail, etc.)
@ -127,11 +127,11 @@ impl ArrayBox {
} else {
let strict = std::env::var("HAKO_OOB_STRICT")
.ok()
.map(|v| matches!(v.as_str(), "1"|"true"|"on"))
.map(|v| matches!(v.as_str(), "1" | "true" | "on"))
.unwrap_or(false)
|| std::env::var("NYASH_OOB_STRICT")
.ok()
.map(|v| matches!(v.as_str(), "1"|"true"|"on"))
.map(|v| matches!(v.as_str(), "1" | "true" | "on"))
.unwrap_or(false);
if strict {
crate::runtime::observe::mark_oob();

View File

@ -2,7 +2,7 @@
//!
//! Implements the core BoolBox type for true/false values.
use crate::box_trait::{NyashBox, BoxCore, BoxBase, StringBox};
use crate::box_trait::{BoxBase, BoxCore, NyashBox, StringBox};
use std::any::Any;
use std::fmt::{Debug, Display};
@ -83,4 +83,4 @@ impl Display for BoolBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -2,7 +2,7 @@
//!
//! Implements the ErrorBox type for representing error information.
use crate::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use std::any::Any;
use std::fmt::{Debug, Display};
@ -79,4 +79,4 @@ impl Display for ErrorBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -9,4 +9,4 @@
)]
// Re-export the new FileBox implementation for backward compatibility
pub use crate::boxes::file::FileBox;
pub use crate::boxes::file::FileBox;

View File

@ -2,7 +2,7 @@
//!
//! Implements the core IntegerBox type for 64-bit signed integers.
use crate::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use std::any::Any;
use std::fmt::{Debug, Display};
@ -79,4 +79,4 @@ impl Display for IntegerBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -4,17 +4,17 @@
//! fundamental data types in Nyash: String, Integer, Boolean, Void, File, and Error.
// Individual basic box implementations
mod string_box;
mod integer_box;
mod bool_box;
mod void_box;
mod file_box;
mod error_box;
mod file_box;
mod integer_box;
mod string_box;
mod void_box;
// Re-export all basic box types
pub use string_box::StringBox;
pub use integer_box::IntegerBox;
pub use bool_box::BoolBox;
pub use void_box::VoidBox;
pub use error_box::ErrorBox;
pub use file_box::FileBox;
pub use error_box::ErrorBox;
pub use integer_box::IntegerBox;
pub use string_box::StringBox;
pub use void_box::VoidBox;

View File

@ -2,7 +2,7 @@
//!
//! Implements the core StringBox type with all string manipulation methods.
use crate::box_trait::{NyashBox, BoxCore, BoxBase};
use crate::box_trait::{BoxBase, BoxCore, NyashBox};
use crate::boxes::ArrayBox;
use std::any::Any;
use std::fmt::{Debug, Display};

View File

@ -2,7 +2,7 @@
//!
//! Implements the core VoidBox type representing empty or null results.
use crate::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use std::any::Any;
use std::fmt::{Debug, Display};
@ -75,4 +75,4 @@ impl Display for VoidBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -99,9 +99,9 @@
* - call stackは直近100件まで自動保持
*/
use crate::box_factory::RuntimeError;
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox, VoidBox};
use crate::instance_v2::InstanceBox;
use crate::box_factory::RuntimeError;
use chrono::Local;
use std::any::Any;
use std::collections::HashMap;

View File

@ -33,8 +33,8 @@
* - `run()`はブロッキング動作(アプリ終了まで制御を返さない)
*/
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use crate::box_factory::RuntimeError;
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use eframe::{self, egui, epaint::Vec2};
use std::any::Any;
use std::sync::{Arc, RwLock};

View File

@ -1,8 +1,8 @@
//! Thin FileBox shim that delegates to a selected provider.
//! Not wired into the registry yet (safe placeholder).
use super::provider::{FileCaps, FileIo, FileResult};
use std::sync::Arc;
use super::provider::{FileIo, FileCaps, FileResult};
#[allow(dead_code)]
pub struct FileBoxShim {
@ -16,9 +16,16 @@ impl FileBoxShim {
let caps = provider.caps();
Self { provider, caps }
}
pub fn open(&self, path: &str) -> FileResult<()> { self.provider.open(path) }
pub fn read(&self) -> FileResult<String> { self.provider.read() }
pub fn close(&self) -> FileResult<()> { self.provider.close() }
pub fn caps(&self) -> FileCaps { self.caps }
pub fn open(&self, path: &str) -> FileResult<()> {
self.provider.open(path)
}
pub fn read(&self) -> FileResult<String> {
self.provider.read()
}
pub fn close(&self) -> FileResult<()> {
self.provider.close()
}
pub fn caps(&self) -> FileCaps {
self.caps
}
}

View File

@ -3,10 +3,12 @@
//! Provides ProviderFactory implementation for the builtin FileBox (core-ro).
//! This is auto-registered when feature "builtin-filebox" is enabled.
use std::sync::Arc;
use crate::boxes::file::provider::FileIo;
use crate::boxes::file::core_ro::CoreRoFileIo;
use crate::runner::modes::common_util::provider_registry::{ProviderFactory, register_provider_factory};
use crate::boxes::file::provider::FileIo;
use crate::runner::modes::common_util::provider_registry::{
register_provider_factory, ProviderFactory,
};
use std::sync::Arc;
/// Builtin FileBox factory (static registration)
pub struct BuiltinFileBoxFactory;

View File

@ -3,10 +3,10 @@
// 参考: 既存Boxの設計思想
// SSOT provider design (ring0/1) — modules are currently placeholders
pub mod provider; // trait FileIo / FileCaps / FileError
pub mod core_ro; // Core readonly provider
pub mod box_shim; // Thin delegating shim
pub mod builtin_factory; // Builtin FileBox ProviderFactory
pub mod box_shim; // Thin delegating shim
pub mod builtin_factory;
pub mod core_ro; // Core readonly provider
pub mod provider; // trait FileIo / FileCaps / FileError // Builtin FileBox ProviderFactory
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use crate::runtime::provider_lock;
@ -64,7 +64,8 @@ impl FileBox {
.ok_or("FileBox provider not initialized")?
.clone();
provider.open(path)
provider
.open(path)
.map_err(|e| format!("Failed to open: {}", e))?;
Ok(FileBox {
@ -76,8 +77,7 @@ impl FileBox {
pub fn read_to_string(&self) -> Result<String, String> {
if let Some(ref provider) = self.provider {
provider.read()
.map_err(|e| format!("Read failed: {}", e))
provider.read().map_err(|e| format!("Read failed: {}", e))
} else {
Err("No provider available".to_string())
}
@ -85,7 +85,8 @@ impl FileBox {
pub fn write_all(&self, _buf: &[u8]) -> Result<(), String> {
// Fail-Fast by capability: consult provider caps
let caps = self.provider
let caps = self
.provider
.as_ref()
.map(|p| p.caps())
.or_else(|| provider_lock::get_filebox_caps())
@ -107,15 +108,20 @@ impl FileBox {
/// ファイルに内容を書き込む
pub fn write(&self, _content: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
let caps = self.provider
let caps = self
.provider
.as_ref()
.map(|p| p.caps())
.or_else(|| provider_lock::get_filebox_caps())
.unwrap_or_else(|| provider::FileCaps::read_only());
if !caps.write {
return Box::new(StringBox::new("Error: write unsupported by provider (read-only)"));
return Box::new(StringBox::new(
"Error: write unsupported by provider (read-only)",
));
}
Box::new(StringBox::new("Error: write supported but not implemented in this build"))
Box::new(StringBox::new(
"Error: write supported but not implemented in this build",
))
}
/// ファイルが存在するかチェック
@ -126,28 +132,38 @@ impl FileBox {
/// ファイルを削除
pub fn delete(&self) -> Box<dyn NyashBox> {
let caps = self.provider
let caps = self
.provider
.as_ref()
.map(|p| p.caps())
.or_else(|| provider_lock::get_filebox_caps())
.unwrap_or_else(|| provider::FileCaps::read_only());
if !caps.write {
return Box::new(StringBox::new("Error: delete unsupported by provider (read-only)"));
return Box::new(StringBox::new(
"Error: delete unsupported by provider (read-only)",
));
}
Box::new(StringBox::new("Error: delete supported but not implemented in this build"))
Box::new(StringBox::new(
"Error: delete supported but not implemented in this build",
))
}
/// ファイルをコピー
pub fn copy(&self, _dest: &str) -> Box<dyn NyashBox> {
let caps = self.provider
let caps = self
.provider
.as_ref()
.map(|p| p.caps())
.or_else(|| provider_lock::get_filebox_caps())
.unwrap_or_else(|| provider::FileCaps::read_only());
if !caps.write {
return Box::new(StringBox::new("Error: copy unsupported by provider (read-only)"));
return Box::new(StringBox::new(
"Error: copy unsupported by provider (read-only)",
));
}
Box::new(StringBox::new("Error: copy supported but not implemented in this build"))
Box::new(StringBox::new(
"Error: copy supported but not implemented in this build",
))
}
}

View File

@ -12,7 +12,12 @@ pub struct FileCaps {
}
impl FileCaps {
pub const fn read_only() -> Self { Self { read: true, write: false } }
pub const fn read_only() -> Self {
Self {
read: true,
write: false,
}
}
}
/// Unified error type (thin placeholder for now)
@ -40,9 +45,10 @@ pub trait FileIo: Send + Sync {
pub fn normalize_newlines(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for b in s.as_bytes() {
if *b == b'\r' { continue; }
if *b == b'\r' {
continue;
}
out.push(*b as char);
}
out
}

View File

@ -279,7 +279,11 @@ impl HTTPResponseBox {
}
/// ステータスコード・メッセージ設定
pub fn set_status(&self, code: Box<dyn NyashBox>, message: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
pub fn set_status(
&self,
code: Box<dyn NyashBox>,
message: Box<dyn NyashBox>,
) -> Box<dyn NyashBox> {
let code_val = code.to_string_box().value.parse::<i32>().unwrap_or(200);
let message_val = message.to_string_box().value;
*self.status_code.lock().unwrap() = code_val;
@ -288,7 +292,11 @@ impl HTTPResponseBox {
}
/// ヘッダー設定
pub fn set_header(&self, name: Box<dyn NyashBox>, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
pub fn set_header(
&self,
name: Box<dyn NyashBox>,
value: Box<dyn NyashBox>,
) -> Box<dyn NyashBox> {
let name_str = name.to_string_box().value;
let value_str = value.to_string_box().value;
self.headers.lock().unwrap().insert(name_str, value_str);
@ -326,7 +334,10 @@ impl HTTPResponseBox {
let version = self.http_version.lock().unwrap().clone();
let status_code = *self.status_code.lock().unwrap();
let status_message = self.status_message.lock().unwrap().clone();
response.push_str(&format!("{} {} {}\r\n", version, status_code, status_message));
response.push_str(&format!(
"{} {} {}\r\n",
version, status_code, status_message
));
// Headers
for (name, value) in self.headers.lock().unwrap().iter() {
@ -335,7 +346,8 @@ impl HTTPResponseBox {
// Content-Length if not already set
let body_len = { self.body.lock().unwrap().len() };
let need_len = { !self.headers.lock().unwrap().contains_key("content-length") && body_len > 0 };
let need_len =
{ !self.headers.lock().unwrap().contains_key("content-length") && body_len > 0 };
if need_len {
response.push_str(&format!("Content-Length: {}\r\n", body_len));
}
@ -367,10 +379,11 @@ impl HTTPResponseBox {
let response = HTTPResponseBox::new();
*response.status_code.lock().unwrap() = 200;
*response.status_message.lock().unwrap() = "OK".to_string();
response.headers.lock().unwrap().insert(
"Content-Type".to_string(),
"application/json".to_string(),
);
response
.headers
.lock()
.unwrap()
.insert("Content-Type".to_string(), "application/json".to_string());
*response.body.lock().unwrap() = content.to_string_box().value;
response
}
@ -384,7 +397,8 @@ impl HTTPResponseBox {
"Content-Type".to_string(),
"text/html; charset=utf-8".to_string(),
);
*response.body.lock().unwrap() = "<html><body><h1>404 - Not Found</h1></body></html>".to_string();
*response.body.lock().unwrap() =
"<html><body><h1>404 - Not Found</h1></body></html>".to_string();
response
}
}

View File

@ -161,7 +161,10 @@ impl MapBox {
}
value.clone_box()
}
None => Box::new(StringBox::new(&format!("[map/missing] Key not found: {}", key_str))),
None => Box::new(StringBox::new(&format!(
"[map/missing] Key not found: {}",
key_str
))),
}
}
@ -202,7 +205,10 @@ impl MapBox {
.unwrap()
.values()
.map(|v| {
if v.as_any().downcast_ref::<crate::instance_v2::InstanceBox>().is_some() {
if v.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
.is_some()
{
v.share_box()
} else {
v.clone_box()

View File

@ -16,28 +16,48 @@ pub struct MissingBox {
impl MissingBox {
pub fn new() -> Self {
Self { base: BoxBase::new() }
Self {
base: BoxBase::new(),
}
}
pub fn is_missing(&self) -> bool { true }
pub fn is_missing(&self) -> bool {
true
}
}
impl BoxCore for MissingBox {
fn box_id(&self) -> u64 { self.base.id }
fn parent_type_id(&self) -> Option<std::any::TypeId> { self.base.parent_type_id }
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
self.base.parent_type_id
}
fn fmt_box(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
// 開発時の可視性向上のための文字列表現。prod では基本的に表面化させない想定。
write!(f, "(missing)")
}
fn as_any(&self) -> &dyn Any { self }
fn as_any_mut(&mut self) -> &mut dyn Any { self }
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl NyashBox for MissingBox {
fn type_name(&self) -> &'static str { "MissingBox" }
fn to_string_box(&self) -> StringBox { StringBox::new("(missing)") }
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
fn type_name(&self) -> &'static str {
"MissingBox"
}
fn to_string_box(&self) -> StringBox {
StringBox::new("(missing)")
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
fn share_box(&self) -> Box<dyn NyashBox> {
self.clone_box()
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
// 欠損どうしは論理同値とみなすが、通常の等価比較は境界で禁止される想定。
BoolBox::new(other.as_any().downcast_ref::<MissingBox>().is_some())
@ -45,6 +65,7 @@ impl NyashBox for MissingBox {
}
impl Display for MissingBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) }
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}

View File

@ -145,8 +145,8 @@ pub use egui_box::EguiBox;
#[cfg(target_arch = "wasm32")]
pub use web::{WebCanvasBox, WebConsoleBox, WebDisplayBox};
pub mod null_box;
pub mod missing_box;
pub mod null_box;
// High-priority Box types
pub mod array;
@ -168,8 +168,8 @@ pub mod intent_box;
pub mod p2p_box;
// null関数も再エクスポート
pub use null_box::{null, NullBox};
pub use missing_box::MissingBox;
pub use null_box::{null, NullBox};
// High-priority Box types re-export
pub use array::ArrayBox;

View File

@ -1,7 +1,7 @@
use super::utils::parse_debug_fuel;
use super::CliConfig;
use clap::{Arg, ArgMatches, Command};
use serde_json;
use super::CliConfig;
use super::utils::parse_debug_fuel;
pub fn parse() -> CliConfig {
let argv: Vec<String> = std::env::args().collect();
@ -13,10 +13,7 @@ pub fn parse() -> CliConfig {
}
// Provide HEX-escaped JSON as an alternate robust path for multiline/special bytes
// Each arg is encoded as lowercase hex of its UTF-8 bytes
let hex_args: Vec<String> = script_args
.iter()
.map(|s| hex_encode_utf8(s))
.collect();
let hex_args: Vec<String> = script_args.iter().map(|s| hex_encode_utf8(s)).collect();
if let Ok(hex_json) = serde_json::to_string(&hex_args) {
std::env::set_var("NYASH_SCRIPT_ARGS_HEX_JSON", hex_json);
}
@ -118,8 +115,12 @@ fn hex_encode_utf8(s: &str) -> String {
}
pub fn from_matches(matches: &ArgMatches) -> CliConfig {
if matches.get_flag("stage3") { std::env::set_var("NYASH_NY_COMPILER_STAGE3", "1"); }
if let Some(a) = matches.get_one::<String>("ny-compiler-args") { std::env::set_var("NYASH_NY_COMPILER_CHILD_ARGS", a); }
if matches.get_flag("stage3") {
std::env::set_var("NYASH_NY_COMPILER_STAGE3", "1");
}
if let Some(a) = matches.get_one::<String>("ny-compiler-args") {
std::env::set_var("NYASH_NY_COMPILER_CHILD_ARGS", a);
}
let cfg = CliConfig {
file: matches.get_one::<String>("file").cloned(),
debug_fuel: parse_debug_fuel(matches.get_one::<String>("debug-fuel").unwrap()),
@ -134,7 +135,11 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig {
compile_native: matches.get_flag("compile-native") || matches.get_flag("aot"),
output_file: matches.get_one::<String>("output").cloned(),
benchmark: matches.get_flag("benchmark"),
iterations: matches.get_one::<String>("iterations").unwrap().parse().unwrap_or(10),
iterations: matches
.get_one::<String>("iterations")
.unwrap()
.parse()
.unwrap_or(10),
vm_stats: matches.get_flag("vm-stats"),
vm_stats_json: matches.get_flag("vm-stats-json"),
jit_exec: matches.get_flag("jit-exec"),
@ -145,7 +150,9 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig {
jit_events_compile: matches.get_flag("jit-events-compile"),
jit_events_runtime: matches.get_flag("jit-events-runtime"),
jit_events_path: matches.get_one::<String>("jit-events-path").cloned(),
jit_threshold: matches.get_one::<String>("jit-threshold").and_then(|s| s.parse::<u32>().ok()),
jit_threshold: matches
.get_one::<String>("jit-threshold")
.and_then(|s| s.parse::<u32>().ok()),
jit_phi_min: matches.get_flag("jit-phi-min"),
jit_hostcall: matches.get_flag("jit-hostcall"),
jit_handle_debug: matches.get_flag("jit-handle-debug"),
@ -158,7 +165,10 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig {
run_task: matches.get_one::<String>("run-task").cloned(),
load_ny_plugins: matches.get_flag("load-ny-plugins"),
gc_mode: matches.get_one::<String>("gc").cloned(),
parser_ny: matches.get_one::<String>("parser").map(|s| s == "ny").unwrap_or(false),
parser_ny: matches
.get_one::<String>("parser")
.map(|s| s == "ny")
.unwrap_or(false),
ny_parser_pipe: matches.get_flag("ny-parser-pipe"),
json_file: matches.get_one::<String>("json-file").cloned(),
mir_json_file: matches.get_one::<String>("mir-json-file").cloned(),
@ -168,7 +178,10 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig {
build_aot: matches.get_one::<String>("build-aot").cloned(),
build_profile: matches.get_one::<String>("build-profile").cloned(),
build_target: matches.get_one::<String>("build-target").cloned(),
cli_usings: matches.get_many::<String>("using").map(|v| v.cloned().collect()).unwrap_or_else(|| Vec::new()),
cli_usings: matches
.get_many::<String>("using")
.map(|v| v.cloned().collect())
.unwrap_or_else(|| Vec::new()),
emit_mir_json: matches.get_one::<String>("emit-mir-json").cloned(),
program_json_to_mir: matches.get_one::<String>("program-json-to-mir").cloned(),
emit_exe: matches.get_one::<String>("emit-exe").cloned(),
@ -179,42 +192,94 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig {
macro_ctx_json: matches.get_one::<String>("macro-ctx-json").cloned(),
};
if cfg.cli_verbose { std::env::set_var("NYASH_CLI_VERBOSE", "1"); }
if cfg.vm_stats { std::env::set_var("NYASH_VM_STATS", "1"); }
if cfg.vm_stats_json { std::env::set_var("NYASH_VM_STATS_JSON", "1"); }
if cfg.jit_exec { std::env::set_var("NYASH_JIT_EXEC", "1"); }
if cfg.jit_stats { std::env::set_var("NYASH_JIT_STATS", "1"); }
if cfg.jit_stats_json { std::env::set_var("NYASH_JIT_STATS_JSON", "1"); }
if cfg.jit_dump { std::env::set_var("NYASH_JIT_DUMP", "1"); }
if cfg.jit_events { std::env::set_var("NYASH_JIT_EVENTS", "1"); }
if cfg.jit_events_compile { std::env::set_var("NYASH_JIT_EVENTS_COMPILE", "1"); }
if cfg.jit_events_runtime { std::env::set_var("NYASH_JIT_EVENTS_RUNTIME", "1"); }
if let Some(p) = &cfg.jit_events_path { std::env::set_var("NYASH_JIT_EVENTS_PATH", p); }
if let Some(t) = cfg.jit_threshold { std::env::set_var("NYASH_JIT_THRESHOLD", t.to_string()); }
if cfg.jit_phi_min { std::env::set_var("NYASH_JIT_PHI_MIN", "1"); }
if cfg.jit_hostcall { std::env::set_var("NYASH_JIT_HOSTCALL", "1"); }
if cfg.jit_handle_debug { std::env::set_var("NYASH_JIT_HANDLE_DEBUG", "1"); }
if cfg.jit_native_f64 { std::env::set_var("NYASH_JIT_NATIVE_F64", "1"); }
if cfg.jit_native_bool { std::env::set_var("NYASH_JIT_NATIVE_BOOL", "1"); }
if cfg.jit_only { std::env::set_var("NYASH_JIT_ONLY", "1"); }
if cfg.jit_direct { std::env::set_var("NYASH_JIT_DIRECT", "1"); }
if let Some(gc) = &cfg.gc_mode { std::env::set_var("NYASH_GC_MODE", gc); }
if cfg.cli_verbose {
std::env::set_var("NYASH_CLI_VERBOSE", "1");
}
if cfg.vm_stats {
std::env::set_var("NYASH_VM_STATS", "1");
}
if cfg.vm_stats_json {
std::env::set_var("NYASH_VM_STATS_JSON", "1");
}
if cfg.jit_exec {
std::env::set_var("NYASH_JIT_EXEC", "1");
}
if cfg.jit_stats {
std::env::set_var("NYASH_JIT_STATS", "1");
}
if cfg.jit_stats_json {
std::env::set_var("NYASH_JIT_STATS_JSON", "1");
}
if cfg.jit_dump {
std::env::set_var("NYASH_JIT_DUMP", "1");
}
if cfg.jit_events {
std::env::set_var("NYASH_JIT_EVENTS", "1");
}
if cfg.jit_events_compile {
std::env::set_var("NYASH_JIT_EVENTS_COMPILE", "1");
}
if cfg.jit_events_runtime {
std::env::set_var("NYASH_JIT_EVENTS_RUNTIME", "1");
}
if let Some(p) = &cfg.jit_events_path {
std::env::set_var("NYASH_JIT_EVENTS_PATH", p);
}
if let Some(t) = cfg.jit_threshold {
std::env::set_var("NYASH_JIT_THRESHOLD", t.to_string());
}
if cfg.jit_phi_min {
std::env::set_var("NYASH_JIT_PHI_MIN", "1");
}
if cfg.jit_hostcall {
std::env::set_var("NYASH_JIT_HOSTCALL", "1");
}
if cfg.jit_handle_debug {
std::env::set_var("NYASH_JIT_HANDLE_DEBUG", "1");
}
if cfg.jit_native_f64 {
std::env::set_var("NYASH_JIT_NATIVE_F64", "1");
}
if cfg.jit_native_bool {
std::env::set_var("NYASH_JIT_NATIVE_BOOL", "1");
}
if cfg.jit_only {
std::env::set_var("NYASH_JIT_ONLY", "1");
}
if cfg.jit_direct {
std::env::set_var("NYASH_JIT_DIRECT", "1");
}
if let Some(gc) = &cfg.gc_mode {
std::env::set_var("NYASH_GC_MODE", gc);
}
if matches.get_flag("run-tests") {
std::env::set_var("NYASH_RUN_TESTS", "1");
if let Some(filter) = matches.get_one::<String>("test-filter") { std::env::set_var("NYASH_TEST_FILTER", filter); }
if let Some(filter) = matches.get_one::<String>("test-filter") {
std::env::set_var("NYASH_TEST_FILTER", filter);
}
if let Some(entry) = matches.get_one::<String>("test-entry") {
let v = entry.as_str();
if v == "wrap" || v == "override" { std::env::set_var("NYASH_TEST_ENTRY", v); }
if v == "wrap" || v == "override" {
std::env::set_var("NYASH_TEST_ENTRY", v);
}
}
if let Some(ret) = matches.get_one::<String>("test-return") {
let v = ret.as_str();
if v == "tests" || v == "original" { std::env::set_var("NYASH_TEST_RETURN", v); }
if v == "tests" || v == "original" {
std::env::set_var("NYASH_TEST_RETURN", v);
}
}
}
if matches.get_flag("macro-preexpand") { std::env::set_var("NYASH_MACRO_SELFHOST_PRE_EXPAND", "1"); }
if matches.get_flag("macro-preexpand-auto") { std::env::set_var("NYASH_MACRO_SELFHOST_PRE_EXPAND", "auto"); }
if matches.get_flag("macro-top-level-allow") { std::env::set_var("NYASH_MACRO_TOPLEVEL_ALLOW", "1"); }
if matches.get_flag("macro-preexpand") {
std::env::set_var("NYASH_MACRO_SELFHOST_PRE_EXPAND", "1");
}
if matches.get_flag("macro-preexpand-auto") {
std::env::set_var("NYASH_MACRO_SELFHOST_PRE_EXPAND", "auto");
}
if matches.get_flag("macro-top-level-allow") {
std::env::set_var("NYASH_MACRO_TOPLEVEL_ALLOW", "1");
}
if let Some(p) = matches.get_one::<String>("macro-profile") {
match p.as_str() {
"dev" | "ci-fast" | "strict" => {

View File

@ -6,7 +6,6 @@ mod args;
mod groups;
mod utils;
/// Command-line configuration structure
#[derive(Debug, Clone)]
pub struct CliConfig {
@ -69,14 +68,22 @@ pub struct CliConfig {
pub macro_ctx_json: Option<String>,
}
pub use groups::{BackendConfig, BuildConfig, CliGroups, DebugConfig, EmitConfig, InputConfig, JitConfig, ParserPipeConfig};
pub use groups::{
BackendConfig, BuildConfig, CliGroups, DebugConfig, EmitConfig, InputConfig, JitConfig,
ParserPipeConfig,
};
impl CliConfig {
pub fn parse() -> Self { args::parse() }
pub fn parse() -> Self {
args::parse()
}
pub fn as_groups(&self) -> CliGroups {
CliGroups {
input: InputConfig { file: self.file.clone(), cli_usings: self.cli_usings.clone() },
input: InputConfig {
file: self.file.clone(),
cli_usings: self.cli_usings.clone(),
},
debug: DebugConfig {
debug_fuel: self.debug_fuel,
dump_ast: self.dump_ast,

View File

@ -6,4 +6,3 @@ pub fn parse_debug_fuel(value: &str) -> Option<usize> {
value.parse::<usize>().ok()
}
}

View File

@ -121,7 +121,9 @@ pub fn await_max_ms() -> u64 {
/// Enable MIR PHI non-generation for Bridge compatibility mode only.
/// フェーズM.2: MirBuilder/LoopBuilderでPHI統一済み、Bridge層の互換性制御のみ
/// Default: PHI-ON (Phase 15 direction), override with NYASH_MIR_NO_PHI=1
pub fn mir_no_phi() -> bool { env_bool("NYASH_MIR_NO_PHI") }
pub fn mir_no_phi() -> bool {
env_bool("NYASH_MIR_NO_PHI")
}
/// Allow verifier to skip SSA/dominance/merge checks for PHI-less MIR.
pub fn verify_allow_no_phi() -> bool {
@ -131,11 +133,15 @@ pub fn verify_allow_no_phi() -> bool {
/// Enable strict edge-copy policy verification in PHI-off mode.
/// When enabled, merge blocks must receive merged values via predecessor copies only,
/// and the merge block itself must not introduce a self-copy to the merged destination.
pub fn verify_edge_copy_strict() -> bool { env_bool("NYASH_VERIFY_EDGE_COPY_STRICT") }
pub fn verify_edge_copy_strict() -> bool {
env_bool("NYASH_VERIFY_EDGE_COPY_STRICT")
}
/// Enforce purity of return blocks: no side-effecting instructions allowed before Return
/// Default: OFF. Enable with NYASH_VERIFY_RET_PURITY=1 in dev/profiling sessions.
pub fn verify_ret_purity() -> bool { env_bool("NYASH_VERIFY_RET_PURITY") }
pub fn verify_ret_purity() -> bool {
env_bool("NYASH_VERIFY_RET_PURITY")
}
// ---- LLVM harness toggle (llvmlite) ----
pub fn llvm_use_harness() -> bool {
@ -172,7 +178,9 @@ pub fn env_bool_default(key: &str, default: bool) -> bool {
/// Global fail-fast policy for runtime fallbacks.
/// Default: ON (true) to prohibit silent/different-route fallbacks in Rust layer.
/// Set NYASH_FAIL_FAST=0 to temporarily allow legacy fallbacks during bring-up.
pub fn fail_fast() -> bool { env_bool_default("NYASH_FAIL_FAST", true) }
pub fn fail_fast() -> bool {
env_bool_default("NYASH_FAIL_FAST", true)
}
// VM legacy by-name call fallback was removed (Phase 2 complete).
@ -204,7 +212,9 @@ pub fn plugin_only() -> bool {
/// Core-13 "pure" mode: after normalization, only the 13 canonical ops are allowed.
/// If enabled, the optimizer will try lightweight rewrites for Load/Store/NewBox/Unary,
/// and the final verifier will reject any remaining non-Core-13 ops.
pub fn mir_core13_pure() -> bool { env_bool("NYASH_MIR_CORE13_PURE") }
pub fn mir_core13_pure() -> bool {
env_bool("NYASH_MIR_CORE13_PURE")
}
/// Enable heuristic pre-pin of comparison operands in if/loop headers.
/// Default: OFF (0). Set NYASH_MIR_PREPIN=1 to enable.
@ -235,14 +245,26 @@ pub fn opt_diag_fail() -> bool {
// ---- Legacy compatibility (dev-only) ----
/// Enable legacy InstanceBox fields (SharedNyashBox map) for compatibility.
/// Default: OFF. Set NYASH_LEGACY_FIELDS_ENABLE=1 to materialize and use legacy fields.
pub fn legacy_fields_enable() -> bool { env_bool("NYASH_LEGACY_FIELDS_ENABLE") }
pub fn legacy_fields_enable() -> bool {
env_bool("NYASH_LEGACY_FIELDS_ENABLE")
}
// ---- GC/Runtime tracing (execution-affecting visibility) ----
pub fn gc_trace() -> bool { env_bool("NYASH_GC_TRACE") }
pub fn gc_barrier_trace() -> bool { env_bool("NYASH_GC_BARRIER_TRACE") }
pub fn runtime_checkpoint_trace() -> bool { env_bool("NYASH_RUNTIME_CHECKPOINT_TRACE") }
pub fn vm_pic_stats() -> bool { env_bool("NYASH_VM_PIC_STATS") }
pub fn vm_vt_trace() -> bool { env_bool("NYASH_VM_VT_TRACE") }
pub fn gc_trace() -> bool {
env_bool("NYASH_GC_TRACE")
}
pub fn gc_barrier_trace() -> bool {
env_bool("NYASH_GC_BARRIER_TRACE")
}
pub fn runtime_checkpoint_trace() -> bool {
env_bool("NYASH_RUNTIME_CHECKPOINT_TRACE")
}
pub fn vm_pic_stats() -> bool {
env_bool("NYASH_VM_PIC_STATS")
}
pub fn vm_vt_trace() -> bool {
env_bool("NYASH_VM_VT_TRACE")
}
pub fn vm_pic_trace() -> bool {
std::env::var("NYASH_VM_PIC_TRACE").ok().as_deref() == Some("1")
}
@ -349,7 +371,11 @@ pub fn extern_trace() -> bool {
// ---- Operator Boxes adopt defaults ----
/// CompareOperator.apply adopt: default ON (prod/devともに採用)
pub fn operator_box_compare_adopt() -> bool {
match std::env::var("NYASH_OPERATOR_BOX_COMPARE_ADOPT").ok().as_deref().map(|v| v.to_ascii_lowercase()) {
match std::env::var("NYASH_OPERATOR_BOX_COMPARE_ADOPT")
.ok()
.as_deref()
.map(|v| v.to_ascii_lowercase())
{
Some(ref s) if s == "0" || s == "false" || s == "off" => false,
Some(ref s) if s == "1" || s == "true" || s == "on" => true,
_ => true, // default ON
@ -357,7 +383,11 @@ pub fn operator_box_compare_adopt() -> bool {
}
/// AddOperator.apply adopt: default OFF順次昇格のため
pub fn operator_box_add_adopt() -> bool {
match std::env::var("NYASH_OPERATOR_BOX_ADD_ADOPT").ok().as_deref().map(|v| v.to_ascii_lowercase()) {
match std::env::var("NYASH_OPERATOR_BOX_ADD_ADOPT")
.ok()
.as_deref()
.map(|v| v.to_ascii_lowercase())
{
Some(ref s) if s == "0" || s == "false" || s == "off" => false,
_ => true, // default ON (promoted after validation)
}
@ -415,9 +445,15 @@ pub fn enable_using() -> bool {
pub fn using_profile() -> String {
std::env::var("NYASH_USING_PROFILE").unwrap_or_else(|_| "dev".to_string())
}
pub fn using_is_prod() -> bool { using_profile().eq_ignore_ascii_case("prod") }
pub fn using_is_ci() -> bool { using_profile().eq_ignore_ascii_case("ci") }
pub fn using_is_dev() -> bool { using_profile().eq_ignore_ascii_case("dev") }
pub fn using_is_prod() -> bool {
using_profile().eq_ignore_ascii_case("prod")
}
pub fn using_is_ci() -> bool {
using_profile().eq_ignore_ascii_case("ci")
}
pub fn using_is_dev() -> bool {
using_profile().eq_ignore_ascii_case("dev")
}
/// Allow `using "path"` statements in source (dev-only by default).
pub fn allow_using_file() -> bool {
// SSOT 徹底: 全プロファイルで既定禁止nyash.toml を唯一の真実に)
@ -432,7 +468,11 @@ pub fn allow_using_file() -> bool {
/// 1) Explicit env `NYASH_USING_AST` = 1/true/on → enabled, = 0/false/off → disabled
/// 2) Default by profile: dev/ci → ON, prod → OFF
pub fn using_ast_enabled() -> bool {
match std::env::var("NYASH_USING_AST").ok().as_deref().map(|v| v.to_ascii_lowercase()) {
match std::env::var("NYASH_USING_AST")
.ok()
.as_deref()
.map(|v| v.to_ascii_lowercase())
{
Some(ref s) if s == "1" || s == "true" || s == "on" => true,
Some(ref s) if s == "0" || s == "false" || s == "off" => false,
_ => !using_is_prod(), // dev/ci → true, prod → false
@ -443,7 +483,11 @@ pub fn using_ast_enabled() -> bool {
/// - dev/ci: default true (allow, with WARN)
/// Override with NYASH_VM_USER_INSTANCE_BOXCALL={0|1}
pub fn vm_allow_user_instance_boxcall() -> bool {
match std::env::var("NYASH_VM_USER_INSTANCE_BOXCALL").ok().as_deref().map(|v| v.to_ascii_lowercase()) {
match std::env::var("NYASH_VM_USER_INSTANCE_BOXCALL")
.ok()
.as_deref()
.map(|v| v.to_ascii_lowercase())
{
Some(ref s) if s == "0" || s == "false" || s == "off" => false,
Some(ref s) if s == "1" || s == "true" || s == "on" => true,
_ => !using_is_prod(),
@ -630,7 +674,10 @@ fn warn_alias_once(alias: &str, primary: &str) {
let set = WARNED_ALIASES.get_or_init(|| Mutex::new(HashSet::new()));
if let Ok(mut s) = set.lock() {
if !s.contains(alias) {
eprintln!("[deprecate/env] '{}' is deprecated; use '{}'", alias, primary);
eprintln!(
"[deprecate/env] '{}' is deprecated; use '{}'",
alias, primary
);
s.insert(alias.to_string());
}
}
@ -652,7 +699,9 @@ pub fn llvm_opt_level() -> String {
/// GateC(Core) route request (primary: NYASH_GATE_C_CORE; alias: HAKO_GATE_C_CORE)
pub fn gate_c_core() -> bool {
if env_bool("NYASH_GATE_C_CORE") { return true; }
if env_bool("NYASH_GATE_C_CORE") {
return true;
}
if env_bool("HAKO_GATE_C_CORE") {
warn_alias_once("HAKO_GATE_C_CORE", "NYASH_GATE_C_CORE");
return true;

View File

@ -14,11 +14,18 @@ pub enum ProviderPolicy {
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FileBoxMode { Auto, CoreRo, PluginOnly }
pub enum FileBoxMode {
Auto,
CoreRo,
PluginOnly,
}
/// Read global provider policy (affects Auto mode only)
pub fn provider_policy_from_env() -> ProviderPolicy {
match std::env::var("HAKO_PROVIDER_POLICY").unwrap_or_else(|_| "strict-plugin-first".to_string()).as_str() {
match std::env::var("HAKO_PROVIDER_POLICY")
.unwrap_or_else(|_| "strict-plugin-first".to_string())
.as_str()
{
"safe-core-first" => ProviderPolicy::SafeCoreFirst,
"static-preferred" => ProviderPolicy::StaticPreferred,
_ => ProviderPolicy::StrictPluginFirst,
@ -27,13 +34,18 @@ pub fn provider_policy_from_env() -> ProviderPolicy {
/// Read FileBox mode from environment variables
pub fn filebox_mode_from_env() -> FileBoxMode {
match std::env::var("NYASH_FILEBOX_MODE").unwrap_or_else(|_| "auto".to_string()).as_str() {
match std::env::var("NYASH_FILEBOX_MODE")
.unwrap_or_else(|_| "auto".to_string())
.as_str()
{
"core-ro" => FileBoxMode::CoreRo,
"plugin-only" => FileBoxMode::PluginOnly,
_ => {
if std::env::var("NYASH_DISABLE_PLUGINS").as_deref() == Ok("1") {
FileBoxMode::CoreRo
} else { FileBoxMode::Auto }
} else {
FileBoxMode::Auto
}
}
}
}
@ -44,4 +56,3 @@ pub fn filebox_mode_from_env() -> FileBoxMode {
pub fn allow_filebox_fallback_override(quiet_pipe: bool) -> bool {
quiet_pipe || crate::config::env::env_bool("NYASH_FILEBOX_ALLOW_FALLBACK")
}

View File

@ -9,7 +9,13 @@ static EMIT_COUNTER: AtomicU64 = AtomicU64::new(0);
/// - NYASH_DEBUG_ENABLE=1 master gate
/// - NYASH_DEBUG_KINDS=resolve,ssa allowed cats (comma-separated)
/// - NYASH_DEBUG_SINK=path file to append JSONL events
pub fn emit(cat: &str, kind: &str, fn_name: Option<&str>, region_id: Option<&str>, meta: serde_json::Value) {
pub fn emit(
cat: &str,
kind: &str,
fn_name: Option<&str>,
region_id: Option<&str>,
meta: serde_json::Value,
) {
if std::env::var("NYASH_DEBUG_ENABLE").ok().as_deref() != Some("1") {
return;
}
@ -25,7 +31,9 @@ pub fn emit(cat: &str, kind: &str, fn_name: Option<&str>, region_id: Option<&str
.unwrap_or(1);
if sample_every > 1 {
let n = EMIT_COUNTER.fetch_add(1, Ordering::Relaxed) + 1;
if n % sample_every != 0 { return; }
if n % sample_every != 0 {
return;
}
}
let sink = match std::env::var("NYASH_DEBUG_SINK") {
Ok(s) if !s.is_empty() => s,
@ -41,7 +49,11 @@ pub fn emit(cat: &str, kind: &str, fn_name: Option<&str>, region_id: Option<&str
"kind": kind,
"meta": meta,
});
if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&sink) {
if let Ok(mut f) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&sink)
{
let _ = writeln!(f, "{}", obj.to_string());
}
}

View File

@ -1,2 +1,2 @@
pub mod log;
pub mod hub;
pub mod log;

View File

@ -36,37 +36,15 @@ pub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[
];
pub fn lookup_keyword(word: &str) -> Option<&'static str> {
for (k, t) in KEYWORDS {
if *k == word { return Some(*t); }
if *k == word {
return Some(*t);
}
}
None
}
pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[
"box",
"global",
"function",
"static",
"if",
"loop",
"break",
"return",
"print",
"nowait",
"include",
"local",
"outbox",
"try",
"throw",
"using",
"from",
"box", "global", "function", "static", "if", "loop", "break", "return", "print", "nowait",
"include", "local", "outbox", "try", "throw", "using", "from",
];
pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[
"add",
"sub",
"mul",
"div",
"and",
"or",
"eq",
"ne",
];
pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &["add", "sub", "mul", "div", "and", "or", "eq", "ne"];

View File

@ -1,8 +1,8 @@
use std::ffi::{CStr, CString};
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::ffi::{CString, CStr};
pub struct Opts {
pub out: Option<PathBuf>,
@ -13,9 +13,13 @@ pub struct Opts {
fn resolve_ny_llvmc() -> PathBuf {
if let Ok(s) = std::env::var("NYASH_NY_LLVM_COMPILER") {
if !s.is_empty() { return PathBuf::from(s); }
if !s.is_empty() {
return PathBuf::from(s);
}
}
if let Ok(p) = which::which("ny-llvmc") {
return p;
}
if let Ok(p) = which::which("ny-llvmc") { return p; }
PathBuf::from("target/release/ny-llvmc")
}
@ -25,7 +29,10 @@ pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result<PathBuf, String>
// Optional provider selection (C-API) — guarded by env flags
// NYASH_LLVM_USE_CAPI=1 and HAKO_V1_EXTERN_PROVIDER_C_ABI=1
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() == Some("1")
&& std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() == Some("1")
&& std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
.ok()
.as_deref()
== Some("1")
{
// Basic shape check first
if !mir_json.contains("\"functions\"") || !mir_json.contains("\"blocks\"") {
@ -37,11 +44,19 @@ pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result<PathBuf, String>
let tmp_dir = std::env::temp_dir();
let in_path = tmp_dir.join("hako_llvm_in.json");
{
let mut f = fs::File::create(&in_path).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
f.write_all(mir_json.as_bytes()).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
let mut f = fs::File::create(&in_path)
.map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
f.write_all(mir_json.as_bytes())
.map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
}
let out_path = if let Some(p) = opts.out.clone() {
p
} else {
tmp_dir.join("hako_llvm_out.o")
};
if let Some(parent) = out_path.parent() {
let _ = fs::create_dir_all(parent);
}
let out_path = if let Some(p) = opts.out.clone() { p } else { tmp_dir.join("hako_llvm_out.o") };
if let Some(parent) = out_path.parent() { let _ = fs::create_dir_all(parent); }
match compile_via_capi(&in_path, &out_path) {
Ok(()) => return Ok(out_path),
Err(e) => {
@ -74,26 +89,41 @@ pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result<PathBuf, String>
let tmp_dir = std::env::temp_dir();
let in_path = tmp_dir.join("hako_llvm_in.json");
{
let mut f = fs::File::create(&in_path).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
f.write_all(mir_json.as_bytes()).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
let mut f =
fs::File::create(&in_path).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
f.write_all(mir_json.as_bytes())
.map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
}
// Output path
let out_path = if let Some(p) = opts.out.clone() { p } else { tmp_dir.join("hako_llvm_out.o") };
if let Some(parent) = out_path.parent() { let _ = fs::create_dir_all(parent); }
let out_path = if let Some(p) = opts.out.clone() {
p
} else {
tmp_dir.join("hako_llvm_out.o")
};
if let Some(parent) = out_path.parent() {
let _ = fs::create_dir_all(parent);
}
// Build command: ny-llvmc --in <json> --emit obj --out <out>
let mut cmd = Command::new(&ny_llvmc);
cmd.arg("--in").arg(&in_path)
.arg("--emit").arg("obj")
.arg("--out").arg(&out_path);
if let Some(nyrt) = opts.nyrt.as_ref() { cmd.arg("--nyrt").arg(nyrt); }
cmd.arg("--in")
.arg(&in_path)
.arg("--emit")
.arg("obj")
.arg("--out")
.arg(&out_path);
if let Some(nyrt) = opts.nyrt.as_ref() {
cmd.arg("--nyrt").arg(nyrt);
}
if let Some(level) = opts.opt_level.as_ref() {
cmd.env("HAKO_LLVM_OPT_LEVEL", level);
cmd.env("NYASH_LLVM_OPT_LEVEL", level);
}
let status = cmd.status().map_err(|e| format!("[llvmemit/spawn/error] {}", e))?;
let status = cmd
.status()
.map_err(|e| format!("[llvmemit/spawn/error] {}", e))?;
if !status.success() {
let code = status.code().unwrap_or(1);
let tag = format!("[llvmemit/ny-llvmc/failed status={}]", code);
@ -121,19 +151,28 @@ fn compile_via_capi(json_in: &Path, obj_out: &Path) -> Result<(), String> {
unsafe {
// Resolve library path
let mut candidates: Vec<PathBuf> = Vec::new();
if let Ok(p) = std::env::var("HAKO_AOT_FFI_LIB") { if !p.is_empty() { candidates.push(PathBuf::from(p)); } }
if let Ok(p) = std::env::var("HAKO_AOT_FFI_LIB") {
if !p.is_empty() {
candidates.push(PathBuf::from(p));
}
}
candidates.push(PathBuf::from("target/release/libhako_llvmc_ffi.so"));
candidates.push(PathBuf::from("lib/libhako_llvmc_ffi.so"));
let lib_path = candidates.into_iter().find(|p| p.exists())
let lib_path = candidates
.into_iter()
.find(|p| p.exists())
.ok_or_else(|| "FFI library not found (set HAKO_AOT_FFI_LIB)".to_string())?;
let lib = Library::new(lib_path).map_err(|e| format!("dlopen failed: {}", e))?;
// Symbol: int hako_llvmc_compile_json(const char*, const char*, char**)
type CompileFn = unsafe extern "C" fn(*const c_char, *const c_char, *mut *mut c_char) -> c_int;
type CompileFn =
unsafe extern "C" fn(*const c_char, *const c_char, *mut *mut c_char) -> c_int;
let func: libloading::Symbol<CompileFn> = lib
.get(b"hako_llvmc_compile_json\0")
.map_err(|e| format!("dlsym failed: {}", e))?;
let cin = CString::new(json_in.to_string_lossy().as_bytes()).map_err(|_| "invalid json path".to_string())?;
let cout = CString::new(obj_out.to_string_lossy().as_bytes()).map_err(|_| "invalid out path".to_string())?;
let cin = CString::new(json_in.to_string_lossy().as_bytes())
.map_err(|_| "invalid json path".to_string())?;
let cout = CString::new(obj_out.to_string_lossy().as_bytes())
.map_err(|_| "invalid out path".to_string())?;
let mut err_ptr: *mut c_char = std::ptr::null_mut();
// Avoid recursive FFI-in-FFI: force inner AOT to use CLI path
let prev = std::env::var("HAKO_AOT_USE_FFI").ok();
@ -156,17 +195,31 @@ fn compile_via_capi(json_in: &Path, obj_out: &Path) -> Result<(), String> {
);
}
let rc = func(cin.as_ptr(), cout.as_ptr(), &mut err_ptr as *mut *mut c_char);
if let Some(v) = prev { std::env::set_var("HAKO_AOT_USE_FFI", v); } else { std::env::remove_var("HAKO_AOT_USE_FFI"); }
let rc = func(
cin.as_ptr(),
cout.as_ptr(),
&mut err_ptr as *mut *mut c_char,
);
if let Some(v) = prev {
std::env::set_var("HAKO_AOT_USE_FFI", v);
} else {
std::env::remove_var("HAKO_AOT_USE_FFI");
}
if rc != 0 {
let msg = if !err_ptr.is_null() { CStr::from_ptr(err_ptr).to_string_lossy().to_string() } else { "compile failed".to_string() };
let msg = if !err_ptr.is_null() {
CStr::from_ptr(err_ptr).to_string_lossy().to_string()
} else {
"compile failed".to_string()
};
// Free error string (allocated by C side)
if !err_ptr.is_null() {
free(err_ptr as *mut c_void);
}
return Err(msg);
}
if !obj_out.exists() { return Err("object not produced".into()); }
if !obj_out.exists() {
return Err("object not produced".into());
}
Ok(())
}
}
@ -177,13 +230,19 @@ fn compile_via_capi(_json_in: &Path, _obj_out: &Path) -> Result<(), String> {
}
/// Link an object to an executable via C-API FFI bundle.
pub fn link_object_capi(obj_in: &Path, exe_out: &Path, extra_ldflags: Option<&str>) -> Result<(), String> {
pub fn link_object_capi(
obj_in: &Path,
exe_out: &Path,
extra_ldflags: Option<&str>,
) -> Result<(), String> {
// Compute effective ldflags
let mut eff: Option<String> = extra_ldflags.map(|s| s.to_string());
let empty = eff.as_deref().map(|s| s.trim().is_empty()).unwrap_or(true);
if empty {
if let Ok(s) = std::env::var("HAKO_AOT_LDFLAGS") {
if !s.trim().is_empty() { eff = Some(s); }
if !s.trim().is_empty() {
eff = Some(s);
}
}
}
if eff.is_none() {
@ -224,35 +283,68 @@ fn link_via_capi(obj_in: &Path, exe_out: &Path, extra_ldflags: Option<&str>) ->
unsafe {
let mut candidates: Vec<PathBuf> = Vec::new();
if let Ok(p) = std::env::var("HAKO_AOT_FFI_LIB") { if !p.is_empty() { candidates.push(PathBuf::from(p)); } }
if let Ok(p) = std::env::var("HAKO_AOT_FFI_LIB") {
if !p.is_empty() {
candidates.push(PathBuf::from(p));
}
}
candidates.push(PathBuf::from("target/release/libhako_llvmc_ffi.so"));
candidates.push(PathBuf::from("lib/libhako_llvmc_ffi.so"));
let lib_path = candidates.into_iter().find(|p| p.exists())
let lib_path = candidates
.into_iter()
.find(|p| p.exists())
.ok_or_else(|| "FFI library not found (set HAKO_AOT_FFI_LIB)".to_string())?;
let lib = Library::new(lib_path).map_err(|e| format!("dlopen failed: {}", e))?;
// int hako_llvmc_link_obj(const char*, const char*, const char*, char**)
type LinkFn = unsafe extern "C" fn(*const c_char, *const c_char, *const c_char, *mut *mut c_char) -> c_int;
type LinkFn = unsafe extern "C" fn(
*const c_char,
*const c_char,
*const c_char,
*mut *mut c_char,
) -> c_int;
let func: libloading::Symbol<LinkFn> = lib
.get(b"hako_llvmc_link_obj\0")
.map_err(|e| format!("dlsym failed: {}", e))?;
let cobj = CString::new(obj_in.to_string_lossy().as_bytes()).map_err(|_| "invalid obj path".to_string())?;
let cexe = CString::new(exe_out.to_string_lossy().as_bytes()).map_err(|_| "invalid exe path".to_string())?;
let cobj = CString::new(obj_in.to_string_lossy().as_bytes())
.map_err(|_| "invalid obj path".to_string())?;
let cexe = CString::new(exe_out.to_string_lossy().as_bytes())
.map_err(|_| "invalid exe path".to_string())?;
let ldflags_owned;
let cflags_ptr = if let Some(s) = extra_ldflags { ldflags_owned = CString::new(s).map_err(|_| "invalid ldflags".to_string())?; ldflags_owned.as_ptr() } else { std::ptr::null() };
let cflags_ptr = if let Some(s) = extra_ldflags {
ldflags_owned = CString::new(s).map_err(|_| "invalid ldflags".to_string())?;
ldflags_owned.as_ptr()
} else {
std::ptr::null()
};
let mut err_ptr: *mut c_char = std::ptr::null_mut();
// Avoid recursive FFI-in-FFI
let prev = std::env::var("HAKO_AOT_USE_FFI").ok();
std::env::set_var("HAKO_AOT_USE_FFI", "0");
let rc = func(cobj.as_ptr(), cexe.as_ptr(), cflags_ptr, &mut err_ptr as *mut *mut c_char);
if let Some(v) = prev { std::env::set_var("HAKO_AOT_USE_FFI", v); } else { std::env::remove_var("HAKO_AOT_USE_FFI"); }
let rc = func(
cobj.as_ptr(),
cexe.as_ptr(),
cflags_ptr,
&mut err_ptr as *mut *mut c_char,
);
if let Some(v) = prev {
std::env::set_var("HAKO_AOT_USE_FFI", v);
} else {
std::env::remove_var("HAKO_AOT_USE_FFI");
}
if rc != 0 {
let msg = if !err_ptr.is_null() { CStr::from_ptr(err_ptr).to_string_lossy().to_string() } else { "link failed".to_string() };
let msg = if !err_ptr.is_null() {
CStr::from_ptr(err_ptr).to_string_lossy().to_string()
} else {
"link failed".to_string()
};
if !err_ptr.is_null() {
free(err_ptr as *mut c_void);
}
return Err(msg);
}
if !exe_out.exists() { return Err("exe not produced".into()); }
if !exe_out.exists() {
return Err("exe not produced".into());
}
Ok(())
}
}
@ -263,21 +355,31 @@ fn link_via_capi(_obj_in: &Path, _exe_out: &Path, _extra: Option<&str>) -> Resul
}
fn resolve_python3() -> Option<PathBuf> {
if let Ok(p) = which::which("python3") { return Some(p); }
if let Ok(p) = which::which("python") { return Some(p); }
if let Ok(p) = which::which("python3") {
return Some(p);
}
if let Ok(p) = which::which("python") {
return Some(p);
}
None
}
fn resolve_llvmlite_harness() -> Option<PathBuf> {
if let Ok(root) = std::env::var("NYASH_ROOT") {
let p = PathBuf::from(root).join("tools/llvmlite_harness.py");
if p.exists() { return Some(p); }
if p.exists() {
return Some(p);
}
}
let p = PathBuf::from("tools/llvmlite_harness.py");
if p.exists() { return Some(p); }
if p.exists() {
return Some(p);
}
// Also try repo-relative (target may run elsewhere)
let p2 = PathBuf::from("../tools/llvmlite_harness.py");
if p2.exists() { return Some(p2); }
if p2.exists() {
return Some(p2);
}
None
}
@ -303,17 +405,27 @@ fn mir_json_to_object_llvmlite(mir_json: &str, opts: &Opts) -> Result<PathBuf, S
let tmp_dir = std::env::temp_dir();
let in_path = tmp_dir.join("hako_llvm_in.json");
{
let mut f = fs::File::create(&in_path).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
f.write_all(mir_json.as_bytes()).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
let mut f =
fs::File::create(&in_path).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
f.write_all(mir_json.as_bytes())
.map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
}
let out_path = if let Some(p) = opts.out.clone() {
p
} else {
tmp_dir.join("hako_llvm_out.o")
};
if let Some(parent) = out_path.parent() {
let _ = fs::create_dir_all(parent);
}
let out_path = if let Some(p) = opts.out.clone() { p } else { tmp_dir.join("hako_llvm_out.o") };
if let Some(parent) = out_path.parent() { let _ = fs::create_dir_all(parent); }
// Run: python3 tools/llvmlite_harness.py --in <json> --out <out>
let status = Command::new(&py)
.arg(&harness)
.arg("--in").arg(&in_path)
.arg("--out").arg(&out_path)
.arg("--in")
.arg(&in_path)
.arg("--out")
.arg(&out_path)
.status()
.map_err(|e| format!("[llvmemit/llvmlite/spawn/error] {}", e))?;
if !status.success() {

View File

@ -11,7 +11,10 @@ pub fn program_json_to_mir_json(program_json: &str) -> Result<String, String> {
}
/// Convert Program(JSON v0) to MIR(JSON v0) with using imports support.
pub fn program_json_to_mir_json_with_imports(program_json: &str, imports: HashMap<String, String>) -> Result<String, String> {
pub fn program_json_to_mir_json_with_imports(
program_json: &str,
imports: HashMap<String, String>,
) -> Result<String, String> {
// Basic header check
if !program_json.contains("\"version\"") || !program_json.contains("\"kind\"") {
let tag = "[mirbuilder/input/invalid] missing version/kind keys";
@ -20,14 +23,15 @@ pub fn program_json_to_mir_json_with_imports(program_json: &str, imports: HashMa
}
// Parse Program(JSON v0) into a MIR Module with imports
let module = match runner::json_v0_bridge::parse_json_v0_to_module_with_imports(program_json, imports) {
Ok(m) => m,
Err(e) => {
let tag = format!("[mirbuilder/parse/error] {}", e);
eprintln!("{}", tag);
return Err(tag);
}
};
let module =
match runner::json_v0_bridge::parse_json_v0_to_module_with_imports(program_json, imports) {
Ok(m) => m,
Err(e) => {
let tag = format!("[mirbuilder/parse/error] {}", e);
eprintln!("{}", tag);
return Err(tag);
}
};
// Emit MIR(JSON) to a temporary file (reuse existing emitter), then read back
let tmp_dir = std::env::temp_dir();
@ -57,8 +61,12 @@ pub fn program_json_to_mir_json_with_imports(program_json: &str, imports: HashMa
if let Some(funcs) = m.get("functions").cloned() {
let v1 = serde_json::json!({"schema_version":"1.0","functions": funcs});
serde_json::to_string(&v1).unwrap_or(s0)
} else { s0 }
} else { s0 }
} else {
s0
}
} else {
s0
}
}
_ => s0,
};
@ -128,7 +136,10 @@ mod tests {
let mir_json = result.unwrap();
// MIR JSON should contain functions
assert!(mir_json.contains("functions"), "MIR JSON should contain functions");
assert!(
mir_json.contains("functions"),
"MIR JSON should contain functions"
);
eprintln!("[test] MIR JSON generated successfully with MatI64 imports");
}
}

View File

@ -1,3 +1,2 @@
pub mod mir_builder;
pub mod llvm_codegen;
pub mod mir_builder;

View File

@ -50,7 +50,7 @@ impl Clone for InstanceBox {
class_name: self.class_name.clone(),
fields_ng: Arc::clone(&self.fields_ng), // Shared reference
methods: Arc::clone(&self.methods),
inner_content: None, // inner_content cannot be cloned (Box<dyn>)
inner_content: None, // inner_content cannot be cloned (Box<dyn>)
base: BoxBase::new(), // Fresh base for clone
finalized: Arc::clone(&self.finalized),
fields: self.fields.as_ref().map(Arc::clone),
@ -108,7 +108,11 @@ impl InstanceBox {
base: BoxBase::new(),
finalized: Arc::new(Mutex::new(false)),
// レガシー互換フィールド既定OFF
fields: if legacy_enabled { Some(Arc::new(Mutex::new(legacy_field_map))) } else { None },
fields: if legacy_enabled {
Some(Arc::new(Mutex::new(legacy_field_map)))
} else {
None
},
init_field_order: fields,
weak_fields_union: std::collections::HashSet::new(),
in_finalization: Arc::new(Mutex::new(false)),
@ -240,10 +244,7 @@ impl InstanceBox {
}
/// レガシー互換weak field取得
pub fn get_weak_field(
&self,
field_name: &str,
) -> Option<NyashValue> {
pub fn get_weak_field(&self, field_name: &str) -> Option<NyashValue> {
self.get_field_ng(field_name)
}

View File

@ -65,7 +65,7 @@ pub mod runtime;
// Unified Grammar scaffolding
pub mod grammar;
pub mod syntax; // syntax sugar config and helpers
// Execution runner (CLI coordinator)
// Execution runner (CLI coordinator)
pub mod runner;
pub mod runner_hv1_inline_guard {}
pub mod using; // using resolver scaffolding (Phase 15)
@ -76,7 +76,9 @@ pub mod host_providers;
pub mod providers;
// CABI PoC shim (20.36/20.37)
pub mod abi { pub mod nyrt_shim; }
pub mod abi {
pub mod nyrt_shim;
}
// Expose the macro engine module under a raw identifier; the source lives under `src/macro/`.
#[path = "macro/mod.rs"]
@ -91,12 +93,8 @@ pub mod tests;
// Re-export main types for easy access
pub use ast::{ASTNode, BinaryOperator, LiteralValue};
pub use box_arithmetic::{AddBox, CompareBox, DivideBox, ModuloBox, MultiplyBox, SubtractBox};
pub use box_trait::{BoolBox, IntegerBox, NyashBox, StringBox, VoidBox};
pub use environment::{Environment, PythonCompatEnvironment};
pub use box_factory::RuntimeError;
pub use parser::{NyashParser, ParseError};
pub use tokenizer::{NyashTokenizer, Token, TokenType};
pub use type_box::{MethodSignature, TypeBox, TypeRegistry}; // 🌟 TypeBox exports
pub use box_trait::{BoolBox, IntegerBox, NyashBox, StringBox, VoidBox};
pub use boxes::console_box::ConsoleBox;
pub use boxes::debug_box::DebugBox;
pub use boxes::map_box::MapBox;
@ -106,8 +104,12 @@ pub use boxes::random_box::RandomBox;
pub use boxes::sound_box::SoundBox;
pub use boxes::time_box::{DateTimeBox, TimeBox, TimerBox};
pub use channel_box::{ChannelBox, MessageBox};
pub use environment::{Environment, PythonCompatEnvironment};
pub use instance_v2::InstanceBox; // 🎯 新実装テストnyash_rustパス使用
pub use method_box::{BoxType, EphemeralInstance, FunctionDefinition, MethodBox};
pub use parser::{NyashParser, ParseError};
pub use tokenizer::{NyashTokenizer, Token, TokenType};
pub use type_box::{MethodSignature, TypeBox, TypeRegistry}; // 🌟 TypeBox exports
pub use value::NyashValue;

View File

@ -1,5 +1,5 @@
use nyash_rust::ast::{ASTNode, BinaryOperator, LiteralValue, Span, UnaryOperator};
use serde_json::{json, Value};
use nyash_rust::ast::{ASTNode, LiteralValue, BinaryOperator, UnaryOperator, Span};
pub fn ast_to_json(ast: &ASTNode) -> Value {
match ast.clone() {
@ -7,7 +7,9 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
"kind": "Program",
"statements": statements.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>()
}),
ASTNode::Loop { condition, body, .. } => json!({
ASTNode::Loop {
condition, body, ..
} => json!({
"kind": "Loop",
"condition": ast_to_json(&condition),
"body": body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>()
@ -27,18 +29,32 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
"target": ast_to_json(&target),
"value": ast_to_json(&value),
}),
ASTNode::Local { variables, initial_values, .. } => json!({
ASTNode::Local {
variables,
initial_values,
..
} => json!({
"kind": "Local",
"variables": variables,
"inits": initial_values.into_iter().map(|opt| opt.map(|v| ast_to_json(&v))).collect::<Vec<_>>()
}),
ASTNode::If { condition, then_body, else_body, .. } => json!({
ASTNode::If {
condition,
then_body,
else_body,
..
} => json!({
"kind": "If",
"condition": ast_to_json(&condition),
"then": then_body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>(),
"else": else_body.map(|v| v.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>()),
}),
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => json!({
ASTNode::TryCatch {
try_body,
catch_clauses,
finally_body,
..
} => json!({
"kind": "TryCatch",
"try": try_body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>(),
"catch": catch_clauses.into_iter().map(|cc| json!({
@ -48,7 +64,14 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
})).collect::<Vec<_>>(),
"cleanup": finally_body.map(|v| v.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>())
}),
ASTNode::FunctionDeclaration { name, params, body, is_static, is_override, .. } => json!({
ASTNode::FunctionDeclaration {
name,
params,
body,
is_static,
is_override,
..
} => json!({
"kind": "FunctionDeclaration",
"name": name,
"params": params,
@ -58,24 +81,38 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
}),
ASTNode::Variable { name, .. } => json!({"kind":"Variable","name":name}),
ASTNode::Literal { value, .. } => json!({"kind":"Literal","value": lit_to_json(&value)}),
ASTNode::BinaryOp { operator, left, right, .. } => json!({
ASTNode::BinaryOp {
operator,
left,
right,
..
} => json!({
"kind":"BinaryOp",
"op": bin_to_str(&operator),
"left": ast_to_json(&left),
"right": ast_to_json(&right),
}),
ASTNode::UnaryOp { operator, operand, .. } => json!({
ASTNode::UnaryOp {
operator, operand, ..
} => json!({
"kind":"UnaryOp",
"op": un_to_str(&operator),
"operand": ast_to_json(&operand),
}),
ASTNode::MethodCall { object, method, arguments, .. } => json!({
ASTNode::MethodCall {
object,
method,
arguments,
..
} => json!({
"kind":"MethodCall",
"object": ast_to_json(&object),
"method": method,
"arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::<Vec<_>>()
}),
ASTNode::FunctionCall { name, arguments, .. } => json!({
ASTNode::FunctionCall {
name, arguments, ..
} => json!({
"kind":"FunctionCall",
"name": name,
"arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::<Vec<_>>()
@ -88,7 +125,12 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
"kind":"Map",
"entries": entries.into_iter().map(|(k,v)| json!({"k":k,"v":ast_to_json(&v)})).collect::<Vec<_>>()
}),
ASTNode::MatchExpr { scrutinee, arms, else_expr, .. } => json!({
ASTNode::MatchExpr {
scrutinee,
arms,
else_expr,
..
} => json!({
"kind":"MatchExpr",
"scrutinee": ast_to_json(&scrutinee),
"arms": arms.into_iter().map(|(lit, body)| json!({
@ -108,45 +150,163 @@ pub fn json_to_ast(v: &Value) -> Option<ASTNode> {
let k = v.get("kind")?.as_str()?;
Some(match k {
"Program" => {
let stmts = v.get("statements")?.as_array()?.iter().filter_map(json_to_ast).collect::<Vec<_>>();
ASTNode::Program { statements: stmts, span: Span::unknown() }
let stmts = v
.get("statements")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect::<Vec<_>>();
ASTNode::Program {
statements: stmts,
span: Span::unknown(),
}
}
"Loop" => ASTNode::Loop {
condition: Box::new(json_to_ast(v.get("condition")?)?),
body: v.get("body")?.as_array()?.iter().filter_map(json_to_ast).collect::<Vec<_>>(),
body: v
.get("body")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect::<Vec<_>>(),
span: Span::unknown(),
},
"Print" => ASTNode::Print { expression: Box::new(json_to_ast(v.get("expression")?)?), span: Span::unknown() },
"Return" => ASTNode::Return { value: v.get("value").and_then(json_to_ast).map(Box::new), span: Span::unknown() },
"Break" => ASTNode::Break { span: Span::unknown() },
"Continue" => ASTNode::Continue { span: Span::unknown() },
"Assignment" => ASTNode::Assignment { target: Box::new(json_to_ast(v.get("target")?)?), value: Box::new(json_to_ast(v.get("value")?)?), span: Span::unknown() },
"Local" => {
let vars = v.get("variables")?.as_array()?.iter().filter_map(|s| s.as_str().map(|x| x.to_string())).collect();
let inits = v.get("inits")?.as_array()?.iter().map(|initv| {
if initv.is_null() { None } else { json_to_ast(initv).map(Box::new) }
}).collect();
ASTNode::Local { variables: vars, initial_values: inits, span: Span::unknown() }
"Print" => ASTNode::Print {
expression: Box::new(json_to_ast(v.get("expression")?)?),
span: Span::unknown(),
},
"Return" => ASTNode::Return {
value: v.get("value").and_then(json_to_ast).map(Box::new),
span: Span::unknown(),
},
"Break" => ASTNode::Break {
span: Span::unknown(),
},
"Continue" => ASTNode::Continue {
span: Span::unknown(),
},
"Assignment" => ASTNode::Assignment {
target: Box::new(json_to_ast(v.get("target")?)?),
value: Box::new(json_to_ast(v.get("value")?)?),
span: Span::unknown(),
},
"Local" => {
let vars = v
.get("variables")?
.as_array()?
.iter()
.filter_map(|s| s.as_str().map(|x| x.to_string()))
.collect();
let inits = v
.get("inits")?
.as_array()?
.iter()
.map(|initv| {
if initv.is_null() {
None
} else {
json_to_ast(initv).map(Box::new)
}
})
.collect();
ASTNode::Local {
variables: vars,
initial_values: inits,
span: Span::unknown(),
}
}
"If" => ASTNode::If {
condition: Box::new(json_to_ast(v.get("condition")?)?),
then_body: v
.get("then")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect::<Vec<_>>(),
else_body: v.get("else").and_then(|a| {
a.as_array()
.map(|arr| arr.iter().filter_map(json_to_ast).collect::<Vec<_>>())
}),
span: Span::unknown(),
},
"If" => ASTNode::If { condition: Box::new(json_to_ast(v.get("condition")?)?), then_body: v.get("then")?.as_array()?.iter().filter_map(json_to_ast).collect::<Vec<_>>(), else_body: v.get("else").and_then(|a| a.as_array().map(|arr| arr.iter().filter_map(json_to_ast).collect::<Vec<_>>())), span: Span::unknown() },
"FunctionDeclaration" => ASTNode::FunctionDeclaration {
name: v.get("name")?.as_str()?.to_string(),
params: v.get("params")?.as_array()?.iter().filter_map(|s| s.as_str().map(|x| x.to_string())).collect(),
body: v.get("body")?.as_array()?.iter().filter_map(json_to_ast).collect(),
params: v
.get("params")?
.as_array()?
.iter()
.filter_map(|s| s.as_str().map(|x| x.to_string()))
.collect(),
body: v
.get("body")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect(),
is_static: v.get("static").and_then(|b| b.as_bool()).unwrap_or(false),
is_override: v.get("override").and_then(|b| b.as_bool()).unwrap_or(false),
span: Span::unknown(),
},
"Variable" => ASTNode::Variable { name: v.get("name")?.as_str()?.to_string(), span: Span::unknown() },
"Literal" => ASTNode::Literal { value: json_to_lit(v.get("value")?)?, span: Span::unknown() },
"BinaryOp" => ASTNode::BinaryOp { operator: str_to_bin(v.get("op")?.as_str()?)?, left: Box::new(json_to_ast(v.get("left")?)?), right: Box::new(json_to_ast(v.get("right")?)?), span: Span::unknown() },
"UnaryOp" => ASTNode::UnaryOp { operator: str_to_un(v.get("op")?.as_str()?)?, operand: Box::new(json_to_ast(v.get("operand")?)?), span: Span::unknown() },
"MethodCall" => ASTNode::MethodCall { object: Box::new(json_to_ast(v.get("object")?)?), method: v.get("method")?.as_str()?.to_string(), arguments: v.get("arguments")?.as_array()?.iter().filter_map(json_to_ast).collect(), span: Span::unknown() },
"FunctionCall" => ASTNode::FunctionCall { name: v.get("name")?.as_str()?.to_string(), arguments: v.get("arguments")?.as_array()?.iter().filter_map(json_to_ast).collect(), span: Span::unknown() },
"Array" => ASTNode::ArrayLiteral { elements: v.get("elements")?.as_array()?.iter().filter_map(json_to_ast).collect(), span: Span::unknown() },
"Map" => ASTNode::MapLiteral { entries: v.get("entries")?.as_array()?.iter().filter_map(|e| {
Some((e.get("k")?.as_str()?.to_string(), json_to_ast(e.get("v")?)?))
}).collect(), span: Span::unknown() },
"Variable" => ASTNode::Variable {
name: v.get("name")?.as_str()?.to_string(),
span: Span::unknown(),
},
"Literal" => ASTNode::Literal {
value: json_to_lit(v.get("value")?)?,
span: Span::unknown(),
},
"BinaryOp" => ASTNode::BinaryOp {
operator: str_to_bin(v.get("op")?.as_str()?)?,
left: Box::new(json_to_ast(v.get("left")?)?),
right: Box::new(json_to_ast(v.get("right")?)?),
span: Span::unknown(),
},
"UnaryOp" => ASTNode::UnaryOp {
operator: str_to_un(v.get("op")?.as_str()?)?,
operand: Box::new(json_to_ast(v.get("operand")?)?),
span: Span::unknown(),
},
"MethodCall" => ASTNode::MethodCall {
object: Box::new(json_to_ast(v.get("object")?)?),
method: v.get("method")?.as_str()?.to_string(),
arguments: v
.get("arguments")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect(),
span: Span::unknown(),
},
"FunctionCall" => ASTNode::FunctionCall {
name: v.get("name")?.as_str()?.to_string(),
arguments: v
.get("arguments")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect(),
span: Span::unknown(),
},
"Array" => ASTNode::ArrayLiteral {
elements: v
.get("elements")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect(),
span: Span::unknown(),
},
"Map" => ASTNode::MapLiteral {
entries: v
.get("entries")?
.as_array()?
.iter()
.filter_map(|e| {
Some((e.get("k")?.as_str()?.to_string(), json_to_ast(e.get("v")?)?))
})
.collect(),
span: Span::unknown(),
},
"MatchExpr" => {
let scr = json_to_ast(v.get("scrutinee")?)?;
let arms_json = v.get("arms")?.as_array()?.iter();
@ -166,18 +326,47 @@ pub fn json_to_ast(v: &Value) -> Option<ASTNode> {
}
}
"TryCatch" => {
let try_b = v.get("try")?.as_array()?.iter().filter_map(json_to_ast).collect::<Vec<_>>();
let try_b = v
.get("try")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect::<Vec<_>>();
let mut catches = Vec::new();
if let Some(arr) = v.get("catch").and_then(|x| x.as_array()) {
for c in arr.iter() {
let exc_t = match c.get("type") { Some(t) if !t.is_null() => t.as_str().map(|s| s.to_string()), _ => None };
let var = match c.get("var") { Some(vv) if !vv.is_null() => vv.as_str().map(|s| s.to_string()), _ => None };
let body = c.get("body")?.as_array()?.iter().filter_map(json_to_ast).collect::<Vec<_>>();
catches.push(nyash_rust::ast::CatchClause { exception_type: exc_t, variable_name: var, body, span: Span::unknown() });
let exc_t = match c.get("type") {
Some(t) if !t.is_null() => t.as_str().map(|s| s.to_string()),
_ => None,
};
let var = match c.get("var") {
Some(vv) if !vv.is_null() => vv.as_str().map(|s| s.to_string()),
_ => None,
};
let body = c
.get("body")?
.as_array()?
.iter()
.filter_map(json_to_ast)
.collect::<Vec<_>>();
catches.push(nyash_rust::ast::CatchClause {
exception_type: exc_t,
variable_name: var,
body,
span: Span::unknown(),
});
}
}
let cleanup = v.get("cleanup").and_then(|cl| cl.as_array().map(|arr| arr.iter().filter_map(json_to_ast).collect::<Vec<_>>()));
ASTNode::TryCatch { try_body: try_b, catch_clauses: catches, finally_body: cleanup, span: Span::unknown() }
let cleanup = v.get("cleanup").and_then(|cl| {
cl.as_array()
.map(|arr| arr.iter().filter_map(json_to_ast).collect::<Vec<_>>())
});
ASTNode::TryCatch {
try_body: try_b,
catch_clauses: catches,
finally_body: cleanup,
span: Span::unknown(),
}
}
_ => return None,
})

View File

@ -32,14 +32,18 @@ impl MacroCtx {
}
}
pub fn gensym(&self, prefix: &str) -> String { gensym(prefix) }
pub fn gensym(&self, prefix: &str) -> String {
gensym(prefix)
}
pub fn report(&self, level: &str, message: &str) {
eprintln!("[macro][{}] {}", level, message);
}
pub fn get_env(&self, key: &str) -> Option<String> {
if !self.caps.env { return None; }
if !self.caps.env {
return None;
}
std::env::var(key).ok()
}
}

View File

@ -1,5 +1,5 @@
use nyash_rust::ast::Span;
use nyash_rust::{ASTNode, ast::LiteralValue, ast::BinaryOperator};
use nyash_rust::{ast::BinaryOperator, ast::LiteralValue, ASTNode};
use std::time::Instant;
/// HIR Patch description (MVP placeholder)
@ -16,10 +16,20 @@ pub struct MacroEngine {
impl MacroEngine {
pub fn new() -> Self {
let max_passes = std::env::var("NYASH_MACRO_MAX_PASSES").ok().and_then(|v| v.parse().ok()).unwrap_or(32);
let cycle_window = std::env::var("NYASH_MACRO_CYCLE_WINDOW").ok().and_then(|v| v.parse().ok()).unwrap_or(8);
let max_passes = std::env::var("NYASH_MACRO_MAX_PASSES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(32);
let cycle_window = std::env::var("NYASH_MACRO_CYCLE_WINDOW")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(8);
let trace = std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1");
Self { max_passes, cycle_window, trace }
Self {
max_passes,
cycle_window,
trace,
}
}
/// Expand all macros with depth/cycle guards and return patched AST.
@ -29,25 +39,48 @@ impl MacroEngine {
let mut history: std::collections::VecDeque<ASTNode> = std::collections::VecDeque::new();
for pass in 0..self.max_passes {
let t0 = Instant::now();
let before_len = crate::r#macro::ast_json::ast_to_json(&cur).to_string().len();
let before_len = crate::r#macro::ast_json::ast_to_json(&cur)
.to_string()
.len();
let next0 = self.expand_node(&cur);
// Apply user MacroBoxes once per pass (if enabled)
let next = crate::r#macro::macro_box::expand_all_once(&next0);
let after_len = crate::r#macro::ast_json::ast_to_json(&next).to_string().len();
let after_len = crate::r#macro::ast_json::ast_to_json(&next)
.to_string()
.len();
let dt = t0.elapsed();
if self.trace { eprintln!("[macro][engine] pass={} changed={} bytes:{}=>{} dt={:?}", pass, (next != cur), before_len, after_len, dt); }
if self.trace {
eprintln!(
"[macro][engine] pass={} changed={} bytes:{}=>{} dt={:?}",
pass,
(next != cur),
before_len,
after_len,
dt
);
}
jsonl_trace(pass, before_len, after_len, next != cur, dt);
if next == cur { return (cur, patches); }
if next == cur {
return (cur, patches);
}
// cycle detection in small window
if history.iter().any(|h| *h == next) {
eprintln!("[macro][engine] cycle detected at pass {} — stopping expansion", pass);
eprintln!(
"[macro][engine] cycle detected at pass {} — stopping expansion",
pass
);
return (cur, patches);
}
history.push_back(cur);
if history.len() > self.cycle_window { let _ = history.pop_front(); }
if history.len() > self.cycle_window {
let _ = history.pop_front();
}
cur = next;
}
eprintln!("[macro][engine] max passes ({}) exceeded — stopping expansion", self.max_passes);
eprintln!(
"[macro][engine] max passes ({}) exceeded — stopping expansion",
self.max_passes
);
(cur, patches)
}
@ -57,23 +90,55 @@ impl MacroEngine {
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
eprintln!("[macro][visit] Program: statements={}", statements.len());
}
let new_stmts = statements.into_iter().map(|n| {
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
eprintln!("[macro][visit] child kind...",);
}
self.expand_node(&n)
}).collect();
ASTNode::Program { statements: new_stmts, span }
let new_stmts = statements
.into_iter()
.map(|n| {
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
eprintln!("[macro][visit] child kind...",);
}
self.expand_node(&n)
})
.collect();
ASTNode::Program {
statements: new_stmts,
span,
}
}
ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, mut methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, is_static, static_init, span } => {
ASTNode::BoxDeclaration {
name,
fields,
public_fields,
private_fields,
mut methods,
constructors,
init_fields,
weak_fields,
is_interface,
extends,
implements,
type_parameters,
is_static,
static_init,
span,
} => {
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
eprintln!("[macro][visit] BoxDeclaration: {} (fields={})", name, fields.len());
eprintln!(
"[macro][visit] BoxDeclaration: {} (fields={})",
name,
fields.len()
);
}
// Derive set: default Equals+ToString when macro is enabled
let derive_all = std::env::var("NYASH_MACRO_DERIVE_ALL").ok().as_deref() == Some("1");
let derive_set = std::env::var("NYASH_MACRO_DERIVE").ok().unwrap_or_else(|| "Equals,ToString".to_string());
let derive_all =
std::env::var("NYASH_MACRO_DERIVE_ALL").ok().as_deref() == Some("1");
let derive_set = std::env::var("NYASH_MACRO_DERIVE")
.ok()
.unwrap_or_else(|| "Equals,ToString".to_string());
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
eprintln!("[macro][derive] box={} derive_all={} set={}", name, derive_all, derive_set);
eprintln!(
"[macro][derive] box={} derive_all={} set={}",
name, derive_all, derive_set
);
}
let want_equals = derive_all || derive_set.contains("Equals");
let want_tostring = derive_all || derive_set.contains("ToString");
@ -81,19 +146,43 @@ impl MacroEngine {
let field_view: &Vec<String> = &public_fields;
if want_equals && !methods.contains_key("equals") {
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
eprintln!("[macro][derive] equals for {} (public fields: {})", name, field_view.len());
eprintln!(
"[macro][derive] equals for {} (public fields: {})",
name,
field_view.len()
);
}
let m = build_equals_method(&name, field_view);
methods.insert("equals".to_string(), m);
}
if want_tostring && !methods.contains_key("toString") {
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
eprintln!("[macro][derive] toString for {} (public fields: {})", name, field_view.len());
eprintln!(
"[macro][derive] toString for {} (public fields: {})",
name,
field_view.len()
);
}
let m = build_tostring_method(&name, field_view);
methods.insert("toString".to_string(), m);
}
ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, is_static, static_init, span }
ASTNode::BoxDeclaration {
name,
fields,
public_fields,
private_fields,
methods,
constructors,
init_fields,
weak_fields,
is_interface,
extends,
implements,
type_parameters,
is_static,
static_init,
span,
}
}
other => other,
}
@ -102,7 +191,9 @@ impl MacroEngine {
fn jsonl_trace(pass: usize, before: usize, after: usize, changed: bool, dt: std::time::Duration) {
if let Ok(path) = std::env::var("NYASH_MACRO_TRACE_JSONL") {
if path.is_empty() { return; }
if path.is_empty() {
return;
}
let rec = serde_json::json!({
"event": "macro_pass",
"pass": pass,
@ -110,17 +201,24 @@ fn jsonl_trace(pass: usize, before: usize, after: usize, changed: bool, dt: std:
"before_bytes": before,
"after_bytes": after,
"dt_us": dt.as_micros() as u64,
}).to_string();
let _ = std::fs::OpenOptions::new().create(true).append(true).open(path).and_then(|mut f| {
use std::io::Write;
writeln!(f, "{}", rec)
});
})
.to_string();
let _ = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)
.and_then(|mut f| {
use std::io::Write;
writeln!(f, "{}", rec)
});
}
}
fn me_field(name: &str) -> ASTNode {
ASTNode::FieldAccess {
object: Box::new(ASTNode::Me { span: Span::unknown() }),
object: Box::new(ASTNode::Me {
span: Span::unknown(),
}),
field: name.to_string(),
span: Span::unknown(),
}
@ -128,30 +226,56 @@ fn me_field(name: &str) -> ASTNode {
fn var_field(var: &str, field: &str) -> ASTNode {
ASTNode::FieldAccess {
object: Box::new(ASTNode::Variable { name: var.to_string(), span: Span::unknown() }),
object: Box::new(ASTNode::Variable {
name: var.to_string(),
span: Span::unknown(),
}),
field: field.to_string(),
span: Span::unknown(),
}
}
fn bin_add(lhs: ASTNode, rhs: ASTNode) -> ASTNode {
ASTNode::BinaryOp { operator: BinaryOperator::Add, left: Box::new(lhs), right: Box::new(rhs), span: Span::unknown() }
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(lhs),
right: Box::new(rhs),
span: Span::unknown(),
}
}
fn bin_and(lhs: ASTNode, rhs: ASTNode) -> ASTNode {
ASTNode::BinaryOp { operator: BinaryOperator::And, left: Box::new(lhs), right: Box::new(rhs), span: Span::unknown() }
ASTNode::BinaryOp {
operator: BinaryOperator::And,
left: Box::new(lhs),
right: Box::new(rhs),
span: Span::unknown(),
}
}
fn bin_eq(lhs: ASTNode, rhs: ASTNode) -> ASTNode {
ASTNode::BinaryOp { operator: BinaryOperator::Equal, left: Box::new(lhs), right: Box::new(rhs), span: Span::unknown() }
ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(lhs),
right: Box::new(rhs),
span: Span::unknown(),
}
}
fn lit_str(s: &str) -> ASTNode { ASTNode::Literal { value: LiteralValue::String(s.to_string()), span: Span::unknown() } }
fn lit_str(s: &str) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::String(s.to_string()),
span: Span::unknown(),
}
}
fn build_equals_method(_box_name: &str, fields: &Vec<String>) -> ASTNode {
// equals(other) { return me.f1 == other.f1 && ...; }
let cond = if fields.is_empty() {
ASTNode::Literal { value: LiteralValue::Bool(true), span: Span::unknown() }
ASTNode::Literal {
value: LiteralValue::Bool(true),
span: Span::unknown(),
}
} else {
let mut it = fields.iter();
let first = it.next().unwrap();
@ -166,7 +290,10 @@ fn build_equals_method(_box_name: &str, fields: &Vec<String>) -> ASTNode {
ASTNode::FunctionDeclaration {
name: "equals".to_string(),
params: vec![param_name.clone()],
body: vec![ASTNode::Return { value: Some(Box::new(cond)), span: Span::unknown() }],
body: vec![ASTNode::Return {
value: Some(Box::new(cond)),
span: Span::unknown(),
}],
is_static: false,
is_override: false,
span: Span::unknown(),
@ -178,7 +305,9 @@ fn build_tostring_method(box_name: &str, fields: &Vec<String>) -> ASTNode {
let mut expr = lit_str(&format!("{}(", box_name));
let mut first = true;
for f in fields {
if !first { expr = bin_add(expr, lit_str(",")); }
if !first {
expr = bin_add(expr, lit_str(","));
}
first = false;
expr = bin_add(expr, me_field(f));
}
@ -186,7 +315,10 @@ fn build_tostring_method(box_name: &str, fields: &Vec<String>) -> ASTNode {
ASTNode::FunctionDeclaration {
name: "toString".to_string(),
params: vec![],
body: vec![ASTNode::Return { value: Some(Box::new(expr)), span: Span::unknown() }],
body: vec![ASTNode::Return {
value: Some(Box::new(expr)),
span: Span::unknown(),
}],
is_static: false,
is_override: false,
span: Span::unknown(),

View File

@ -1,5 +1,5 @@
use std::sync::{Mutex, OnceLock};
use nyash_rust::ASTNode;
use std::sync::{Mutex, OnceLock};
/// MacroBox API — user-extensible macro expansion units (experimental)
///
@ -33,13 +33,17 @@ pub fn register(m: &'static dyn MacroBox) {
/// Legacy env `NYASH_MACRO_BOX=1` still forces ON, but by default we
/// synchronize with the macro system gate so user macros run when macros are enabled.
pub fn enabled() -> bool {
if std::env::var("NYASH_MACRO_BOX").ok().as_deref() == Some("1") { return true; }
if std::env::var("NYASH_MACRO_BOX").ok().as_deref() == Some("1") {
return true;
}
super::enabled()
}
/// Expand AST by applying all registered MacroBoxes in order once.
pub fn expand_all_once(ast: &ASTNode) -> ASTNode {
if !enabled() { return ast.clone(); }
if !enabled() {
return ast.clone();
}
let reg = registry();
let guard = reg.lock().expect("macro registry poisoned");
let mut cur = ast.clone();
@ -55,39 +59,127 @@ pub fn expand_all_once(ast: &ASTNode) -> ASTNode {
pub struct UppercasePrintMacro;
impl MacroBox for UppercasePrintMacro {
fn name(&self) -> &'static str { "UppercasePrintMacro" }
fn name(&self) -> &'static str {
"UppercasePrintMacro"
}
fn expand(&self, ast: &ASTNode) -> ASTNode {
use nyash_rust::ast::{ASTNode as A, LiteralValue, Span};
fn go(n: &A) -> A {
match n.clone() {
A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|c| go(&c)).collect(), span },
A::Program { statements, span } => A::Program {
statements: statements.into_iter().map(|c| go(&c)).collect(),
span,
},
A::Print { expression, span } => {
match &*expression {
A::Literal { value: LiteralValue::String(s), .. } => {
A::Literal {
value: LiteralValue::String(s),
..
} => {
// Demo: if string starts with "UPPER:", uppercase the rest.
if let Some(rest) = s.strip_prefix("UPPER:") {
let up = rest.to_uppercase();
A::Print { expression: Box::new(A::Literal { value: LiteralValue::String(up), span: Span::unknown() }), span }
} else { A::Print { expression: Box::new(go(&*expression)), span } }
A::Print {
expression: Box::new(A::Literal {
value: LiteralValue::String(up),
span: Span::unknown(),
}),
span,
}
} else {
A::Print {
expression: Box::new(go(&*expression)),
span,
}
}
}
other => A::Print { expression: Box::new(go(other)), span }
other => A::Print {
expression: Box::new(go(other)),
span,
},
}
}
A::Assignment { target, value, span } => A::Assignment { target: Box::new(go(&*target)), value: Box::new(go(&*value)), span },
A::If { condition, then_body, else_body, span } => A::If {
A::Assignment {
target,
value,
span,
} => A::Assignment {
target: Box::new(go(&*target)),
value: Box::new(go(&*value)),
span,
},
A::If {
condition,
then_body,
else_body,
span,
} => A::If {
condition: Box::new(go(&*condition)),
then_body: then_body.into_iter().map(|c| go(&c)).collect(),
else_body: else_body.map(|v| v.into_iter().map(|c| go(&c)).collect()),
span,
},
A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(go(v))), span },
A::FieldAccess { object, field, span } => A::FieldAccess { object: Box::new(go(&*object)), field, span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(go(&*object)), method, arguments: arguments.into_iter().map(|c| go(&c)).collect(), span },
A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.into_iter().map(|c| go(&c)).collect(), span },
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(go(&*left)), right: Box::new(go(&*right)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(go(&*operand)), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|c| go(&c)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k,v)| (k, go(&v))).collect(), span },
A::Return { value, span } => A::Return {
value: value.as_ref().map(|v| Box::new(go(v))),
span,
},
A::FieldAccess {
object,
field,
span,
} => A::FieldAccess {
object: Box::new(go(&*object)),
field,
span,
},
A::MethodCall {
object,
method,
arguments,
span,
} => A::MethodCall {
object: Box::new(go(&*object)),
method,
arguments: arguments.into_iter().map(|c| go(&c)).collect(),
span,
},
A::FunctionCall {
name,
arguments,
span,
} => A::FunctionCall {
name,
arguments: arguments.into_iter().map(|c| go(&c)).collect(),
span,
},
A::BinaryOp {
operator,
left,
right,
span,
} => A::BinaryOp {
operator,
left: Box::new(go(&*left)),
right: Box::new(go(&*right)),
span,
},
A::UnaryOp {
operator,
operand,
span,
} => A::UnaryOp {
operator,
operand: Box::new(go(&*operand)),
span,
},
A::ArrayLiteral { elements, span } => A::ArrayLiteral {
elements: elements.into_iter().map(|c| go(&c)).collect(),
span,
},
A::MapLiteral { entries, span } => A::MapLiteral {
entries: entries.into_iter().map(|(k, v)| (k, go(&v))).collect(),
span,
},
other => other,
}
}
@ -101,13 +193,17 @@ static INIT_FLAG: OnceLock<()> = OnceLock::new();
pub fn init_builtin() {
INIT_FLAG.get_or_init(|| {
// Explicit example toggle
if std::env::var("NYASH_MACRO_BOX_EXAMPLE").ok().as_deref() == Some("1") { register(&UppercasePrintMacro); }
if std::env::var("NYASH_MACRO_BOX_EXAMPLE").ok().as_deref() == Some("1") {
register(&UppercasePrintMacro);
}
// Comma-separated names: NYASH_MACRO_BOX_ENABLE="UppercasePrintMacro,Other"
if let Ok(list) = std::env::var("NYASH_MACRO_BOX_ENABLE") {
for name in list.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) {
match name {
"UppercasePrintMacro" => register(&UppercasePrintMacro),
_ => { eprintln!("[macro][box] unknown MacroBox '{}', ignoring", name); }
_ => {
eprintln!("[macro][box] unknown MacroBox '{}', ignoring", name);
}
}
}
}

View File

@ -28,7 +28,9 @@ pub fn init_from_env() {
if std::env::var("NYASH_MACRO_BOX_CHILD_RUNNER").ok().is_some() {
eprintln!("[macro][compat] NYASH_MACRO_BOX_CHILD_RUNNER is deprecated; runner mode is managed automatically");
}
let Some(paths) = paths else { return; };
let Some(paths) = paths else {
return;
};
for p in paths.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) {
if let Err(e) = try_load_one(p) {
// Quiet by default; print only when tracing is enabled to reduce noise in normal runs
@ -47,35 +49,72 @@ fn try_load_one(path: &str) -> Result<(), String> {
let prev_sugar = std::env::var("NYASH_SYNTAX_SUGAR_LEVEL").ok();
std::env::set_var("NYASH_SYNTAX_SUGAR_LEVEL", "basic");
let ast_res = nyash_rust::parser::NyashParser::parse_from_string(&src);
if let Some(v) = prev_sugar { std::env::set_var("NYASH_SYNTAX_SUGAR_LEVEL", v); } else { std::env::remove_var("NYASH_SYNTAX_SUGAR_LEVEL"); }
if let Some(v) = prev_sugar {
std::env::set_var("NYASH_SYNTAX_SUGAR_LEVEL", v);
} else {
std::env::remove_var("NYASH_SYNTAX_SUGAR_LEVEL");
}
let ast = ast_res.map_err(|e| format!("parse error: {:?}", e))?;
// Find a BoxDeclaration with static function expand(...)
if let ASTNode::Program { statements, .. } = ast {
// Capabilities: conservative scan before registration
if let Err(msg) = caps_allow_macro_source(&ASTNode::Program { statements: statements.clone(), span: nyash_rust::ast::Span::unknown() }) {
if let Err(msg) = caps_allow_macro_source(&ASTNode::Program {
statements: statements.clone(),
span: nyash_rust::ast::Span::unknown(),
}) {
eprintln!("[macro][box_ny][caps] {} (in '{}')", msg, path);
if strict_enabled() { return Err(msg); }
if strict_enabled() {
return Err(msg);
}
return Ok(());
}
for st in &statements {
if let ASTNode::BoxDeclaration { name: box_name, methods, .. } = st {
if let Some(ASTNode::FunctionDeclaration { name: mname, body: exp_body, params, .. }) = methods.get("expand") {
if let ASTNode::BoxDeclaration {
name: box_name,
methods,
..
} = st
{
if let Some(ASTNode::FunctionDeclaration {
name: mname,
body: exp_body,
params,
..
}) = methods.get("expand")
{
if mname == "expand" {
let reg_name = derive_box_name(&box_name, methods.get("name"));
// Prefer Nyash runner route by default (self-hosting). Child-proxy only when explicitly enabled.
let use_child = std::env::var("NYASH_MACRO_BOX_CHILD").ok().map(|v| v != "0" && v != "false" && v != "off").unwrap_or(true);
let use_child = std::env::var("NYASH_MACRO_BOX_CHILD")
.ok()
.map(|v| v != "0" && v != "false" && v != "off")
.unwrap_or(true);
if use_child {
let nm = reg_name;
let file_static: &'static str = Box::leak(path.to_string().into_boxed_str());
crate::r#macro::macro_box::register(Box::leak(Box::new(NyChildMacroBox { nm, file: file_static })));
eprintln!("[macro][box_ny] registered child-proxy MacroBox '{}' for {}", nm, path);
let file_static: &'static str =
Box::leak(path.to_string().into_boxed_str());
crate::r#macro::macro_box::register(Box::leak(Box::new(
NyChildMacroBox {
nm,
file: file_static,
},
)));
eprintln!(
"[macro][box_ny] registered child-proxy MacroBox '{}' for {}",
nm, path
);
} else {
// Heuristic mapping by name first, otherwise inspect body pattern.
let mut mapped = false;
match reg_name {
"UppercasePrintMacro" => {
crate::r#macro::macro_box::register(&crate::r#macro::macro_box::UppercasePrintMacro);
eprintln!("[macro][box_ny] registered built-in '{}' from {}", reg_name, path);
crate::r#macro::macro_box::register(
&crate::r#macro::macro_box::UppercasePrintMacro,
);
eprintln!(
"[macro][box_ny] registered built-in '{}' from {}",
reg_name, path
);
mapped = true;
}
_ => {}
@ -83,14 +122,20 @@ fn try_load_one(path: &str) -> Result<(), String> {
if !mapped {
if expand_is_identity(exp_body, params) {
let nm = reg_name;
crate::r#macro::macro_box::register(Box::leak(Box::new(NyIdentityMacroBox { nm })));
crate::r#macro::macro_box::register(Box::leak(Box::new(
NyIdentityMacroBox { nm },
)));
eprintln!("[macro][box_ny] registered Ny MacroBox '{}' (identity by body) from {}", nm, path);
} else if expand_indicates_uppercase(exp_body, params) {
crate::r#macro::macro_box::register(&crate::r#macro::macro_box::UppercasePrintMacro);
crate::r#macro::macro_box::register(
&crate::r#macro::macro_box::UppercasePrintMacro,
);
eprintln!("[macro][box_ny] registered built-in 'UppercasePrintMacro' by body pattern from {}", path);
} else {
let nm = reg_name;
crate::r#macro::macro_box::register(Box::leak(Box::new(NyIdentityMacroBox { nm })));
crate::r#macro::macro_box::register(Box::leak(Box::new(
NyIdentityMacroBox { nm },
)));
eprintln!("[macro][box_ny] registered Ny MacroBox '{}' (identity: unknown body) from {}", nm, path);
}
}
@ -102,19 +147,38 @@ fn try_load_one(path: &str) -> Result<(), String> {
}
// Fallback: accept top-level `static function MacroBoxSpec.expand(json)` without a BoxDeclaration
// Default OFF for safety; can be enabled via CLI/env
let allow_top = std::env::var("NYASH_MACRO_TOPLEVEL_ALLOW").ok().map(|v| v != "0" && v != "false" && v != "off").unwrap_or(false);
let allow_top = std::env::var("NYASH_MACRO_TOPLEVEL_ALLOW")
.ok()
.map(|v| v != "0" && v != "false" && v != "off")
.unwrap_or(false);
for st in &statements {
if let ASTNode::FunctionDeclaration { is_static: true, name, .. } = st {
if let ASTNode::FunctionDeclaration {
is_static: true,
name,
..
} = st
{
if let Some((box_name, method)) = name.split_once('.') {
if method == "expand" {
let nm: &'static str = Box::leak(box_name.to_string().into_boxed_str());
let file_static: &'static str = Box::leak(path.to_string().into_boxed_str());
let use_child = std::env::var("NYASH_MACRO_BOX_CHILD").ok().map(|v| v != "0" && v != "false" && v != "off").unwrap_or(true);
let file_static: &'static str =
Box::leak(path.to_string().into_boxed_str());
let use_child = std::env::var("NYASH_MACRO_BOX_CHILD")
.ok()
.map(|v| v != "0" && v != "false" && v != "off")
.unwrap_or(true);
if use_child && allow_top {
crate::r#macro::macro_box::register(Box::leak(Box::new(NyChildMacroBox { nm, file: file_static })));
crate::r#macro::macro_box::register(Box::leak(Box::new(
NyChildMacroBox {
nm,
file: file_static,
},
)));
eprintln!("[macro][box_ny] registered child-proxy MacroBox '{}' (top-level static) for {}", nm, path);
} else {
crate::r#macro::macro_box::register(Box::leak(Box::new(NyIdentityMacroBox { nm })));
crate::r#macro::macro_box::register(Box::leak(Box::new(
NyIdentityMacroBox { nm },
)));
eprintln!("[macro][box_ny] registered identity MacroBox '{}' (top-level static) for {}", nm, path);
}
return Ok(());
@ -131,7 +195,11 @@ fn derive_box_name(default: &str, name_fn: Option<&ASTNode>) -> &'static str {
if let Some(ASTNode::FunctionDeclaration { body, .. }) = name_fn {
if body.len() == 1 {
if let ASTNode::Return { value: Some(v), .. } = &body[0] {
if let ASTNode::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } = &**v {
if let ASTNode::Literal {
value: nyash_rust::ast::LiteralValue::String(s),
..
} = &**v
{
let owned = s.clone();
return Box::leak(owned.into_boxed_str());
}
@ -141,21 +209,33 @@ fn derive_box_name(default: &str, name_fn: Option<&ASTNode>) -> &'static str {
Box::leak(default.to_string().into_boxed_str())
}
pub(crate) struct NyIdentityMacroBox { nm: &'static str }
pub(crate) struct NyIdentityMacroBox {
nm: &'static str,
}
impl super::macro_box::MacroBox for NyIdentityMacroBox {
fn name(&self) -> &'static str { self.nm }
fn name(&self) -> &'static str {
self.nm
}
fn expand(&self, ast: &ASTNode) -> ASTNode {
if std::env::var("NYASH_MACRO_BOX_NY_IDENTITY_ROUNDTRIP").ok().as_deref() == Some("1") {
if std::env::var("NYASH_MACRO_BOX_NY_IDENTITY_ROUNDTRIP")
.ok()
.as_deref()
== Some("1")
{
let j = crate::r#macro::ast_json::ast_to_json(ast);
if let Some(a2) = crate::r#macro::ast_json::json_to_ast(&j) { return a2; }
if let Some(a2) = crate::r#macro::ast_json::json_to_ast(&j) {
return a2;
}
}
ast.clone()
}
}
fn expand_is_identity(body: &Vec<ASTNode>, params: &Vec<String>) -> bool {
if body.len() != 1 { return false; }
if body.len() != 1 {
return false;
}
if let ASTNode::Return { value: Some(v), .. } = &body[0] {
if let ASTNode::Variable { name, .. } = &**v {
return params.get(0).map(|p| p == name).unwrap_or(false);
@ -165,11 +245,15 @@ fn expand_is_identity(body: &Vec<ASTNode>, params: &Vec<String>) -> bool {
}
fn expand_indicates_uppercase(body: &Vec<ASTNode>, params: &Vec<String>) -> bool {
if body.len() != 1 { return false; }
if body.len() != 1 {
return false;
}
let p0 = params.get(0).cloned().unwrap_or_else(|| "ast".to_string());
match &body[0] {
ASTNode::Return { value: Some(v), .. } => match &**v {
ASTNode::FunctionCall { name, arguments, .. } => {
ASTNode::FunctionCall {
name, arguments, ..
} => {
if (name == "uppercase_print" || name == "upper_print") && arguments.len() == 1 {
if let ASTNode::Variable { name: an, .. } = &arguments[0] {
return an == &p0;
@ -184,32 +268,80 @@ fn expand_indicates_uppercase(body: &Vec<ASTNode>, params: &Vec<String>) -> bool
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MacroBehavior { Identity, Uppercase, ArrayPrependZero, MapInsertTag, LoopNormalize, IfMatchNormalize, ForForeachNormalize, EnvTagString }
pub enum MacroBehavior {
Identity,
Uppercase,
ArrayPrependZero,
MapInsertTag,
LoopNormalize,
IfMatchNormalize,
ForForeachNormalize,
EnvTagString,
}
pub fn analyze_macro_file(path: &str) -> MacroBehavior {
let src = match std::fs::read_to_string(path) { Ok(s) => s, Err(_) => return MacroBehavior::Identity };
let ast = match nyash_rust::parser::NyashParser::parse_from_string(&src) { Ok(a) => a, Err(_) => return MacroBehavior::Identity };
let src = match std::fs::read_to_string(path) {
Ok(s) => s,
Err(_) => return MacroBehavior::Identity,
};
let ast = match nyash_rust::parser::NyashParser::parse_from_string(&src) {
Ok(a) => a,
Err(_) => return MacroBehavior::Identity,
};
// Quick heuristics based on literals present in file
fn ast_has_literal_string(a: &ASTNode, needle: &str) -> bool {
use nyash_rust::ast::ASTNode as A;
match a {
A::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } => s.contains(needle),
A::Program { statements, .. } => statements.iter().any(|n| ast_has_literal_string(n, needle)),
A::Literal {
value: nyash_rust::ast::LiteralValue::String(s),
..
} => s.contains(needle),
A::Program { statements, .. } => {
statements.iter().any(|n| ast_has_literal_string(n, needle))
}
A::Print { expression, .. } => ast_has_literal_string(expression, needle),
A::Return { value, .. } => value.as_ref().map(|v| ast_has_literal_string(v, needle)).unwrap_or(false),
A::Assignment { target, value, .. } => ast_has_literal_string(target, needle) || ast_has_literal_string(value, needle),
A::If { condition, then_body, else_body, .. } => {
A::Return { value, .. } => value
.as_ref()
.map(|v| ast_has_literal_string(v, needle))
.unwrap_or(false),
A::Assignment { target, value, .. } => {
ast_has_literal_string(target, needle) || ast_has_literal_string(value, needle)
}
A::If {
condition,
then_body,
else_body,
..
} => {
ast_has_literal_string(condition, needle)
|| then_body.iter().any(|n| ast_has_literal_string(n, needle))
|| else_body.as_ref().map(|v| v.iter().any(|n| ast_has_literal_string(n, needle))).unwrap_or(false)
|| else_body
.as_ref()
.map(|v| v.iter().any(|n| ast_has_literal_string(n, needle)))
.unwrap_or(false)
}
A::FunctionDeclaration { body, .. } => {
body.iter().any(|n| ast_has_literal_string(n, needle))
}
A::BinaryOp { left, right, .. } => {
ast_has_literal_string(left, needle) || ast_has_literal_string(right, needle)
}
A::FunctionDeclaration { body, .. } => body.iter().any(|n| ast_has_literal_string(n, needle)),
A::BinaryOp { left, right, .. } => ast_has_literal_string(left, needle) || ast_has_literal_string(right, needle),
A::UnaryOp { operand, .. } => ast_has_literal_string(operand, needle),
A::MethodCall { object, arguments, .. } => ast_has_literal_string(object, needle) || arguments.iter().any(|n| ast_has_literal_string(n, needle)),
A::FunctionCall { arguments, .. } => arguments.iter().any(|n| ast_has_literal_string(n, needle)),
A::ArrayLiteral { elements, .. } => elements.iter().any(|n| ast_has_literal_string(n, needle)),
A::MapLiteral { entries, .. } => entries.iter().any(|(_, v)| ast_has_literal_string(v, needle)),
A::MethodCall {
object, arguments, ..
} => {
ast_has_literal_string(object, needle)
|| arguments.iter().any(|n| ast_has_literal_string(n, needle))
}
A::FunctionCall { arguments, .. } => {
arguments.iter().any(|n| ast_has_literal_string(n, needle))
}
A::ArrayLiteral { elements, .. } => {
elements.iter().any(|n| ast_has_literal_string(n, needle))
}
A::MapLiteral { entries, .. } => entries
.iter()
.any(|(_, v)| ast_has_literal_string(v, needle)),
_ => false,
}
}
@ -218,29 +350,59 @@ pub fn analyze_macro_file(path: &str) -> MacroBehavior {
match a {
A::Program { statements, .. } => statements.iter().any(|n| ast_has_method(n, method)),
A::Print { expression, .. } => ast_has_method(expression, method),
A::Return { value, .. } => value.as_ref().map(|v| ast_has_method(v, method)).unwrap_or(false),
A::Assignment { target, value, .. } => ast_has_method(target, method) || ast_has_method(value, method),
A::If { condition, then_body, else_body, .. } => ast_has_method(condition, method)
|| then_body.iter().any(|n| ast_has_method(n, method))
|| else_body.as_ref().map(|v| v.iter().any(|n| ast_has_method(n, method))).unwrap_or(false),
A::Return { value, .. } => value
.as_ref()
.map(|v| ast_has_method(v, method))
.unwrap_or(false),
A::Assignment { target, value, .. } => {
ast_has_method(target, method) || ast_has_method(value, method)
}
A::If {
condition,
then_body,
else_body,
..
} => {
ast_has_method(condition, method)
|| then_body.iter().any(|n| ast_has_method(n, method))
|| else_body
.as_ref()
.map(|v| v.iter().any(|n| ast_has_method(n, method)))
.unwrap_or(false)
}
A::FunctionDeclaration { body, .. } => body.iter().any(|n| ast_has_method(n, method)),
A::BinaryOp { left, right, .. } => ast_has_method(left, method) || ast_has_method(right, method),
A::BinaryOp { left, right, .. } => {
ast_has_method(left, method) || ast_has_method(right, method)
}
A::UnaryOp { operand, .. } => ast_has_method(operand, method),
A::MethodCall { object, method: m, arguments, .. } => m == method
|| ast_has_method(object, method)
|| arguments.iter().any(|n| ast_has_method(n, method)),
A::FunctionCall { arguments, .. } => arguments.iter().any(|n| ast_has_method(n, method)),
A::MethodCall {
object,
method: m,
arguments,
..
} => {
m == method
|| ast_has_method(object, method)
|| arguments.iter().any(|n| ast_has_method(n, method))
}
A::FunctionCall { arguments, .. } => {
arguments.iter().any(|n| ast_has_method(n, method))
}
A::ArrayLiteral { elements, .. } => elements.iter().any(|n| ast_has_method(n, method)),
A::MapLiteral { entries, .. } => entries.iter().any(|(_, v)| ast_has_method(v, method)),
_ => false,
}
}
// Detect array prepend-zero macro by pattern strings present in macro source
if ast_has_literal_string(&ast, "\"kind\":\"Array\",\"elements\":[") || ast_has_literal_string(&ast, "\"elements\":[") {
if ast_has_literal_string(&ast, "\"kind\":\"Array\",\"elements\":[")
|| ast_has_literal_string(&ast, "\"elements\":[")
{
return MacroBehavior::ArrayPrependZero;
}
// Detect map insert-tag macro by pattern strings
if ast_has_literal_string(&ast, "\"kind\":\"Map\",\"entries\":[") || ast_has_literal_string(&ast, "\"entries\":[") {
if ast_has_literal_string(&ast, "\"kind\":\"Map\",\"entries\":[")
|| ast_has_literal_string(&ast, "\"entries\":[")
{
return MacroBehavior::MapInsertTag;
}
// Detect upper-string macro by pattern or toUpperCase usage
@ -253,23 +415,47 @@ pub fn analyze_macro_file(path: &str) -> MacroBehavior {
}
if let ASTNode::Program { statements, .. } = ast {
for st in statements {
if let ASTNode::BoxDeclaration { name: _, methods, .. } = st {
if let ASTNode::BoxDeclaration {
name: _, methods, ..
} = st
{
// Detect LoopNormalize/IfMatchNormalize by name() returning a specific string
if let Some(ASTNode::FunctionDeclaration { name: mname, body, .. }) = methods.get("name") {
if let Some(ASTNode::FunctionDeclaration {
name: mname, body, ..
}) = methods.get("name")
{
if mname == "name" {
if body.len() == 1 {
if let ASTNode::Return { value: Some(v), .. } = &body[0] {
if let ASTNode::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } = &**v {
if s == "LoopNormalize" { return MacroBehavior::LoopNormalize; }
if s == "IfMatchNormalize" { return MacroBehavior::IfMatchNormalize; }
if s == "ForForeach" { return MacroBehavior::ForForeachNormalize; }
if s == "EnvTagString" { return MacroBehavior::EnvTagString; }
if let ASTNode::Literal {
value: nyash_rust::ast::LiteralValue::String(s),
..
} = &**v
{
if s == "LoopNormalize" {
return MacroBehavior::LoopNormalize;
}
if s == "IfMatchNormalize" {
return MacroBehavior::IfMatchNormalize;
}
if s == "ForForeach" {
return MacroBehavior::ForForeachNormalize;
}
if s == "EnvTagString" {
return MacroBehavior::EnvTagString;
}
}
}
}
}
}
if let Some(ASTNode::FunctionDeclaration { name: mname, body, params, .. }) = methods.get("expand") {
if let Some(ASTNode::FunctionDeclaration {
name: mname,
body,
params,
..
}) = methods.get("expand")
{
if mname == "expand" {
if expand_indicates_uppercase(body, params) {
return MacroBehavior::Uppercase;
@ -282,7 +468,10 @@ pub fn analyze_macro_file(path: &str) -> MacroBehavior {
MacroBehavior::Identity
}
struct NyChildMacroBox { nm: &'static str, file: &'static str }
struct NyChildMacroBox {
nm: &'static str,
file: &'static str,
}
fn cap_enabled(name: &str) -> bool {
match std::env::var(name).ok() {
@ -301,67 +490,148 @@ fn caps_allow_macro_source(ast: &ASTNode) -> Result<(), String> {
fn scan(n: &A, seen: &mut Vec<String>) {
match n {
A::New { class, .. } => seen.push(class.clone()),
A::Program { statements, .. } => for s in statements { scan(s, seen); },
A::FunctionDeclaration { body, .. } => for s in body { scan(s, seen); },
A::Assignment { target, value, .. } => { scan(target, seen); scan(value, seen); },
A::Return { value, .. } => if let Some(v) = value { scan(v, seen); },
A::If { condition, then_body, else_body, .. } => {
scan(condition, seen);
for s in then_body { scan(s, seen); }
if let Some(b) = else_body { for s in b { scan(s, seen); } }
A::Program { statements, .. } => {
for s in statements {
scan(s, seen);
}
}
A::FunctionDeclaration { body, .. } => {
for s in body {
scan(s, seen);
}
}
A::Assignment { target, value, .. } => {
scan(target, seen);
scan(value, seen);
}
A::Return { value, .. } => {
if let Some(v) = value {
scan(v, seen);
}
}
A::If {
condition,
then_body,
else_body,
..
} => {
scan(condition, seen);
for s in then_body {
scan(s, seen);
}
if let Some(b) = else_body {
for s in b {
scan(s, seen);
}
}
}
A::BinaryOp { left, right, .. } => {
scan(left, seen);
scan(right, seen);
}
A::BinaryOp { left, right, .. } => { scan(left, seen); scan(right, seen); }
A::UnaryOp { operand, .. } => scan(operand, seen),
A::MethodCall { object, arguments, .. } => { scan(object, seen); for a in arguments { scan(a, seen); } }
A::FunctionCall { arguments, .. } => for a in arguments { scan(a, seen); },
A::ArrayLiteral { elements, .. } => for e in elements { scan(e, seen); },
A::MapLiteral { entries, .. } => for (_, v) in entries { scan(v, seen); },
A::MethodCall {
object, arguments, ..
} => {
scan(object, seen);
for a in arguments {
scan(a, seen);
}
}
A::FunctionCall { arguments, .. } => {
for a in arguments {
scan(a, seen);
}
}
A::ArrayLiteral { elements, .. } => {
for e in elements {
scan(e, seen);
}
}
A::MapLiteral { entries, .. } => {
for (_, v) in entries {
scan(v, seen);
}
}
_ => {}
}
}
let mut boxes = Vec::new();
scan(ast, &mut boxes);
if !allow_io && boxes.iter().any(|c| c == "FileBox" || c == "PathBox" || c == "DirBox") {
if !allow_io
&& boxes
.iter()
.any(|c| c == "FileBox" || c == "PathBox" || c == "DirBox")
{
return Err("macro capability violation: IO (File/Path/Dir) denied".into());
}
if !allow_net && boxes.iter().any(|c| c.contains("HTTP") || c.contains("Http") || c == "SocketBox") {
if !allow_net
&& boxes
.iter()
.any(|c| c.contains("HTTP") || c.contains("Http") || c == "SocketBox")
{
return Err("macro capability violation: NET (HTTP/Socket) denied".into());
}
Ok(())
}
impl super::macro_box::MacroBox for NyChildMacroBox {
fn name(&self) -> &'static str { self.nm }
fn name(&self) -> &'static str {
self.nm
}
fn expand(&self, ast: &ASTNode) -> ASTNode {
// Parent-side proxy: prefer runner script (PyVM) when enabled; otherwise fallback to internal child mode.
let exe = match std::env::current_exe() {
Ok(p) => p,
Err(e) => { eprintln!("[macro-proxy] current_exe failed: {}", e); return ast.clone(); }
Err(e) => {
eprintln!("[macro-proxy] current_exe failed: {}", e);
return ast.clone();
}
};
// Prefer Nyash runner route by default for self-hosting; legacy env can force internal child with 0.
let use_runner = std::env::var("NYASH_MACRO_BOX_CHILD_RUNNER").ok().map(|v| v != "0" && v != "false" && v != "off").unwrap_or(false);
let use_runner = std::env::var("NYASH_MACRO_BOX_CHILD_RUNNER")
.ok()
.map(|v| v != "0" && v != "false" && v != "off")
.unwrap_or(false);
if std::env::var("NYASH_MACRO_BOX_CHILD_RUNNER").ok().is_some() {
eprintln!("[macro][compat] NYASH_MACRO_BOX_CHILD_RUNNER is deprecated; prefer defaults");
eprintln!(
"[macro][compat] NYASH_MACRO_BOX_CHILD_RUNNER is deprecated; prefer defaults"
);
}
let mut cmd = std::process::Command::new(exe.clone());
// Build MacroCtx JSON once (caps only, MVP)
let mctx = crate::r#macro::ctx::MacroCtx::from_env();
let ctx_json = format!("{{\"caps\":{{\"io\":{},\"net\":{},\"env\":{}}}}}", mctx.caps.io, mctx.caps.net, mctx.caps.env);
let ctx_json = format!(
"{{\"caps\":{{\"io\":{},\"net\":{},\"env\":{}}}}}",
mctx.caps.io, mctx.caps.net, mctx.caps.env
);
if use_runner {
// Synthesize a tiny runner that inlines the macro file and calls MacroBoxSpec.expand
use std::io::Write as _;
let tmp_dir = std::path::Path::new("tmp");
let _ = std::fs::create_dir_all(tmp_dir);
let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_millis();
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis();
let tmp_path = tmp_dir.join(format!("macro_expand_runner_{}.hako", ts));
let mut f = match std::fs::File::create(&tmp_path) { Ok(x) => x, Err(e) => { eprintln!("[macro-proxy] create tmp runner failed: {}", e); return ast.clone(); } };
let mut f = match std::fs::File::create(&tmp_path) {
Ok(x) => x,
Err(e) => {
eprintln!("[macro-proxy] create tmp runner failed: {}", e);
return ast.clone();
}
};
let macro_src = std::fs::read_to_string(self.file)
.unwrap_or_else(|_| String::from("// failed to read macro file\n"));
let script = format!(
"{}\n\nfunction main(args) {{\n if args.length() == 0 {{\n print(\"{{}}\")\n return 0\n }}\n local j, r, ctx\n j = args.get(0)\n if args.length() > 1 {{ ctx = args.get(1) }} else {{ ctx = \"{{}}\" }}\n r = MacroBoxSpec.expand(j, ctx)\n print(r)\n return 0\n}}\n",
macro_src
);
if let Err(e) = f.write_all(script.as_bytes()) { eprintln!("[macro-proxy] write tmp runner failed: {}", e); return ast.clone(); }
if let Err(e) = f.write_all(script.as_bytes()) {
eprintln!("[macro-proxy] write tmp runner failed: {}", e);
return ast.clone();
}
// Run Nyash runner script under PyVM: nyash --backend vm <tmp_runner> -- <json>
cmd.arg("--backend").arg("vm").arg(tmp_path);
// Append script args after '--'
@ -372,7 +642,8 @@ impl super::macro_box::MacroBox for NyChildMacroBox {
cmd.stdin(std::process::Stdio::null());
} else {
// Internal child mode: --macro-expand-child <macro file> with stdin JSON
cmd.arg("--macro-expand-child").arg(self.file)
cmd.arg("--macro-expand-child")
.arg(self.file)
.stdin(std::process::Stdio::piped());
// Provide MacroCtx via env for internal child
cmd.env("NYASH_MACRO_CTX_JSON", ctx_json.clone());
@ -393,13 +664,21 @@ impl super::macro_box::MacroBox for NyChildMacroBox {
cmd.env_remove("NYASH_MACRO_BOX_CHILD");
cmd.env_remove("NYASH_MACRO_BOX_CHILD_RUNNER");
// Timeout
let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(2000);
let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(2000);
// Spawn
let mut child = match cmd.spawn() { Ok(c) => c, Err(e) => {
eprintln!("[macro-proxy] spawn failed: {}", e);
if strict_enabled() { std::process::exit(2); }
return ast.clone();
} };
let mut child = match cmd.spawn() {
Ok(c) => c,
Err(e) => {
eprintln!("[macro-proxy] spawn failed: {}", e);
if strict_enabled() {
std::process::exit(2);
}
return ast.clone();
}
};
// Write stdin only in internal child mode
if !use_runner {
if let Some(mut sin) = child.stdin.take() {
@ -415,34 +694,60 @@ impl super::macro_box::MacroBox for NyChildMacroBox {
loop {
match child.try_wait() {
Ok(Some(_status)) => {
if let Some(mut so) = child.stdout.take() { use std::io::Read; let _ = so.read_to_string(&mut out); }
if let Some(mut so) = child.stdout.take() {
use std::io::Read;
let _ = so.read_to_string(&mut out);
}
break;
}
Ok(None) => {
if start.elapsed() >= Duration::from_millis(timeout_ms) {
let _ = child.kill(); let _ = child.wait();
let _ = child.kill();
let _ = child.wait();
eprintln!("[macro-proxy] timeout {} ms", timeout_ms);
if strict_enabled() { std::process::exit(124); }
if strict_enabled() {
std::process::exit(124);
}
return ast.clone();
}
std::thread::sleep(Duration::from_millis(5));
}
Err(e) => { eprintln!("[macro-proxy] wait error: {}", e); if strict_enabled() { std::process::exit(2); } return ast.clone(); }
Err(e) => {
eprintln!("[macro-proxy] wait error: {}", e);
if strict_enabled() {
std::process::exit(2);
}
return ast.clone();
}
}
}
// capture stderr for diagnostics and continue
// Capture stderr for diagnostics
let mut err = String::new();
if let Some(mut se) = child.stderr.take() { use std::io::Read; let _ = se.read_to_string(&mut err); }
if let Some(mut se) = child.stderr.take() {
use std::io::Read;
let _ = se.read_to_string(&mut err);
}
// Parse output JSON
match serde_json::from_str::<serde_json::Value>(&out) {
Ok(v) => match crate::r#macro::ast_json::json_to_ast(&v) {
Some(a) => a,
None => { eprintln!("[macro-proxy] child JSON did not map to AST. stderr=\n{}", err); if strict_enabled() { std::process::exit(2); } ast.clone() }
None => {
eprintln!(
"[macro-proxy] child JSON did not map to AST. stderr=\n{}",
err
);
if strict_enabled() {
std::process::exit(2);
}
ast.clone()
}
},
Err(e) => {
eprintln!("[macro-proxy] invalid JSON from child: {}\n-- child stderr --\n{}\n-- end stderr --", e, err);
if strict_enabled() { std::process::exit(2); }
if strict_enabled() {
std::process::exit(2);
}
ast.clone()
}
}

View File

@ -3,22 +3,28 @@
//! Goal: Provide minimal, typed interfaces for AST pattern matching and
//! HIR patch based expansion. Backends (MIR/JIT/LLVM) remain unchanged.
pub mod pattern;
pub mod ast_json;
pub mod ctx;
pub mod engine;
pub mod macro_box;
pub mod macro_box_ny;
pub mod ast_json;
pub mod ctx;
pub mod pattern;
use nyash_rust::ASTNode;
/// Enable/disable macro system via env gate.
pub fn enabled() -> bool {
// Default ON. Disable with NYASH_MACRO_DISABLE=1 or NYASH_MACRO_ENABLE=0/false/off.
if let Ok(v) = std::env::var("NYASH_MACRO_DISABLE") { if v == "1" { return false; } }
if let Ok(v) = std::env::var("NYASH_MACRO_DISABLE") {
if v == "1" {
return false;
}
}
if let Ok(v) = std::env::var("NYASH_MACRO_ENABLE") {
let v = v.to_ascii_lowercase();
if v == "0" || v == "false" || v == "off" { return false; }
if v == "0" || v == "false" || v == "off" {
return false;
}
return true;
}
true
@ -26,15 +32,21 @@ pub fn enabled() -> bool {
/// A hook to dump AST for `--expand` (pre/post). Expansion is no-op for now.
pub fn maybe_expand_and_dump(ast: &ASTNode, _dump_only: bool) -> ASTNode {
if !enabled() { return ast.clone(); }
if !enabled() {
return ast.clone();
}
// Initialize user macro boxes (if any, behind env gates)
self::macro_box::init_builtin();
self::macro_box_ny::init_from_env();
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { eprintln!("[macro] input AST: {:?}", ast); }
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
eprintln!("[macro] input AST: {:?}", ast);
}
let mut eng = self::engine::MacroEngine::new();
let (out, _patches) = eng.expand(ast);
let out2 = maybe_inject_test_harness(&out);
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { eprintln!("[macro] output AST: {:?}", out2); }
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
eprintln!("[macro] output AST: {:?}", out2);
}
out2
}
@ -44,7 +56,11 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode {
}
// Test call plan
#[derive(Clone)]
struct TestPlan { label: String, setup: Option<nyash_rust::ASTNode>, call: nyash_rust::ASTNode }
struct TestPlan {
label: String,
setup: Option<nyash_rust::ASTNode>,
call: nyash_rust::ASTNode,
}
// Collect tests (top-level and Box)
let mut tests: Vec<TestPlan> = Vec::new();
@ -56,9 +72,16 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode {
// - Detailed per-test: { "Box.method": { "args": [...], "instance": {"ctor":"new|birth","args":[...] } } }
// - Typed values inside args supported via objects (see json_to_ast below)
#[derive(Clone, Default)]
struct InstanceSpec { ctor: String, args: Vec<nyash_rust::ASTNode>, type_args: Vec<String> }
struct InstanceSpec {
ctor: String,
args: Vec<nyash_rust::ASTNode>,
type_args: Vec<String>,
}
#[derive(Clone, Default)]
struct TestArgSpec { args: Vec<nyash_rust::ASTNode>, instance: Option<InstanceSpec> }
struct TestArgSpec {
args: Vec<nyash_rust::ASTNode>,
instance: Option<InstanceSpec>,
}
fn json_err(msg: &str) {
eprintln!("[macro][test][args] {}", msg);
@ -67,76 +90,163 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode {
fn json_to_ast(v: &serde_json::Value) -> Result<nyash_rust::ASTNode, String> {
use nyash_rust::ast::{ASTNode as A, LiteralValue, Span};
match v {
serde_json::Value::String(st) => Ok(A::Literal { value: LiteralValue::String(st.clone()), span: Span::unknown() }),
serde_json::Value::Bool(b) => Ok(A::Literal { value: LiteralValue::Bool(*b), span: Span::unknown() }),
serde_json::Value::String(st) => Ok(A::Literal {
value: LiteralValue::String(st.clone()),
span: Span::unknown(),
}),
serde_json::Value::Bool(b) => Ok(A::Literal {
value: LiteralValue::Bool(*b),
span: Span::unknown(),
}),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
Ok(A::Literal { value: LiteralValue::Integer(i), span: Span::unknown() })
Ok(A::Literal {
value: LiteralValue::Integer(i),
span: Span::unknown(),
})
} else if let Some(f) = n.as_f64() {
Ok(A::Literal { value: LiteralValue::Float(f), span: Span::unknown() })
Ok(A::Literal {
value: LiteralValue::Float(f),
span: Span::unknown(),
})
} else {
Err("unsupported number literal".into())
}
}
serde_json::Value::Null => Ok(A::Literal { value: LiteralValue::Null, span: Span::unknown() }),
serde_json::Value::Null => Ok(A::Literal {
value: LiteralValue::Null,
span: Span::unknown(),
}),
serde_json::Value::Array(elems) => {
// Treat nested arrays as ArrayLiteral by default
let mut out = Vec::with_capacity(elems.len());
for x in elems { out.push(json_to_ast(x)?); }
Ok(A::ArrayLiteral { elements: out, span: Span::unknown() })
for x in elems {
out.push(json_to_ast(x)?);
}
Ok(A::ArrayLiteral {
elements: out,
span: Span::unknown(),
})
}
serde_json::Value::Object(obj) => {
// Typed shorthands accepted: {i:1}|{int:1}, {f:1.2}|{float:1.2}, {s:"x"}|{string:"x"}, {b:true}|{bool:true}
if let Some(v) = obj.get("i").or_else(|| obj.get("int")) { return json_to_ast(v); }
if let Some(v) = obj.get("f").or_else(|| obj.get("float")) { return json_to_ast(v); }
if let Some(v) = obj.get("s").or_else(|| obj.get("string")) { return json_to_ast(v); }
if let Some(v) = obj.get("b").or_else(|| obj.get("bool")) { return json_to_ast(v); }
if let Some(v) = obj.get("i").or_else(|| obj.get("int")) {
return json_to_ast(v);
}
if let Some(v) = obj.get("f").or_else(|| obj.get("float")) {
return json_to_ast(v);
}
if let Some(v) = obj.get("s").or_else(|| obj.get("string")) {
return json_to_ast(v);
}
if let Some(v) = obj.get("b").or_else(|| obj.get("bool")) {
return json_to_ast(v);
}
if let Some(map) = obj.get("map") {
if let Some(mo) = map.as_object() {
let mut ents: Vec<(String, nyash_rust::ASTNode)> = Vec::with_capacity(mo.len());
for (k, vv) in mo { ents.push((k.clone(), json_to_ast(vv)?)); }
return Ok(A::MapLiteral { entries: ents, span: Span::unknown() });
} else { return Err("map must be an object".into()); }
let mut ents: Vec<(String, nyash_rust::ASTNode)> =
Vec::with_capacity(mo.len());
for (k, vv) in mo {
ents.push((k.clone(), json_to_ast(vv)?));
}
return Ok(A::MapLiteral {
entries: ents,
span: Span::unknown(),
});
} else {
return Err("map must be an object".into());
}
}
if let Some(arr) = obj.get("array") {
if let Some(va) = arr.as_array() {
let mut out = Vec::with_capacity(va.len());
for x in va { out.push(json_to_ast(x)?); }
return Ok(A::ArrayLiteral { elements: out, span: Span::unknown() });
} else { return Err("array must be an array".into()); }
for x in va {
out.push(json_to_ast(x)?);
}
return Ok(A::ArrayLiteral {
elements: out,
span: Span::unknown(),
});
} else {
return Err("array must be an array".into());
}
}
if let Some(name) = obj.get("var").and_then(|v| v.as_str()) {
return Ok(A::Variable { name: name.to_string(), span: Span::unknown() });
return Ok(A::Variable {
name: name.to_string(),
span: Span::unknown(),
});
}
if let Some(name) = obj.get("call").and_then(|v| v.as_str()) {
let mut args: Vec<A> = Vec::new();
if let Some(va) = obj.get("args").and_then(|v| v.as_array()) {
for x in va { args.push(json_to_ast(x)?); }
for x in va {
args.push(json_to_ast(x)?);
}
}
return Ok(A::FunctionCall { name: name.to_string(), arguments: args, span: Span::unknown() });
return Ok(A::FunctionCall {
name: name.to_string(),
arguments: args,
span: Span::unknown(),
});
}
if let Some(method) = obj.get("method").and_then(|v| v.as_str()) {
let objv = obj.get("object").ok_or_else(|| "method requires 'object'".to_string())?;
let objv = obj
.get("object")
.ok_or_else(|| "method requires 'object'".to_string())?;
let object = json_to_ast(objv)?;
let mut args: Vec<A> = Vec::new();
if let Some(va) = obj.get("args").and_then(|v| v.as_array()) {
for x in va { args.push(json_to_ast(x)?); }
for x in va {
args.push(json_to_ast(x)?);
}
}
return Ok(A::MethodCall { object: Box::new(object), method: method.to_string(), arguments: args, span: Span::unknown() });
return Ok(A::MethodCall {
object: Box::new(object),
method: method.to_string(),
arguments: args,
span: Span::unknown(),
});
}
if let Some(bx) = obj.get("box").and_then(|v| v.as_str()) {
let mut args: Vec<A> = Vec::new();
if let Some(va) = obj.get("args").and_then(|v| v.as_array()) {
for x in va { args.push(json_to_ast(x)?); }
for x in va {
args.push(json_to_ast(x)?);
}
}
let type_args: Vec<String> = obj.get("type_args").and_then(|v| v.as_array()).map(|arr| arr.iter().filter_map(|x| x.as_str().map(|s| s.to_string())).collect()).unwrap_or_default();
let type_args: Vec<String> = obj
.get("type_args")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|x| x.as_str().map(|s| s.to_string()))
.collect()
})
.unwrap_or_default();
let ctor = obj.get("ctor").and_then(|v| v.as_str()).unwrap_or("new");
if ctor == "new" {
return Ok(A::New { class: bx.to_string(), arguments: args, type_arguments: type_args, span: Span::unknown() });
return Ok(A::New {
class: bx.to_string(),
arguments: args,
type_arguments: type_args,
span: Span::unknown(),
});
} else if ctor == "birth" {
return Ok(A::MethodCall { object: Box::new(A::Variable { name: bx.to_string(), span: Span::unknown() }), method: "birth".into(), arguments: args, span: Span::unknown() });
return Ok(A::MethodCall {
object: Box::new(A::Variable {
name: bx.to_string(),
span: Span::unknown(),
}),
method: "birth".into(),
arguments: args,
span: Span::unknown(),
});
} else {
return Err(format!("unknown ctor '{}', expected 'new' or 'birth'", ctor));
return Err(format!(
"unknown ctor '{}', expected 'new' or 'birth'",
ctor
));
}
}
Err("unknown object mapping for AST".into())
@ -148,38 +258,90 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode {
match v {
serde_json::Value::Array(arr) => {
let mut out: Vec<nyash_rust::ASTNode> = Vec::new();
for a in arr { match json_to_ast(a) { Ok(n) => out.push(n), Err(e) => { json_err(&format!("args element error: {}", e)); return None; } } }
Some(TestArgSpec { args: out, instance: None })
for a in arr {
match json_to_ast(a) {
Ok(n) => out.push(n),
Err(e) => {
json_err(&format!("args element error: {}", e));
return None;
}
}
}
Some(TestArgSpec {
args: out,
instance: None,
})
}
serde_json::Value::Object(obj) => {
let mut spec = TestArgSpec::default();
if let Some(a) = obj.get("args").and_then(|v| v.as_array()) {
let mut out: Vec<nyash_rust::ASTNode> = Vec::new();
for x in a { match json_to_ast(x) { Ok(n) => out.push(n), Err(e) => { json_err(&format!("args element error: {}", e)); return None; } } }
for x in a {
match json_to_ast(x) {
Ok(n) => out.push(n),
Err(e) => {
json_err(&format!("args element error: {}", e));
return None;
}
}
}
spec.args = out;
}
if let Some(inst) = obj.get("instance").and_then(|v| v.as_object()) {
let ctor = inst.get("ctor").and_then(|v| v.as_str()).unwrap_or("new").to_string();
let type_args: Vec<String> = inst.get("type_args").and_then(|v| v.as_array()).map(|arr| arr.iter().filter_map(|x| x.as_str().map(|s| s.to_string())).collect()).unwrap_or_default();
let ctor = inst
.get("ctor")
.and_then(|v| v.as_str())
.unwrap_or("new")
.to_string();
let type_args: Vec<String> = inst
.get("type_args")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|x| x.as_str().map(|s| s.to_string()))
.collect()
})
.unwrap_or_default();
let mut args: Vec<nyash_rust::ASTNode> = Vec::new();
if let Some(va) = inst.get("args").and_then(|v| v.as_array()) {
for x in va { match json_to_ast(x) { Ok(n) => args.push(n), Err(e) => { json_err(&format!("instance.args element error: {}", e)); return None; } } }
for x in va {
match json_to_ast(x) {
Ok(n) => args.push(n),
Err(e) => {
json_err(&format!("instance.args element error: {}", e));
return None;
}
}
}
}
spec.instance = Some(InstanceSpec { ctor, args, type_args });
spec.instance = Some(InstanceSpec {
ctor,
args,
type_args,
});
}
Some(spec)
}
_ => { json_err("test value must be array or object"); None }
_ => {
json_err("test value must be array or object");
None
}
}
}
let args_map: Option<std::collections::HashMap<String, TestArgSpec>> = (|| {
if let Ok(s) = std::env::var("NYASH_TEST_ARGS_JSON") {
if s.trim().is_empty() { return None; }
if s.trim().is_empty() {
return None;
}
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&s) {
let mut map = std::collections::HashMap::new();
if let Some(obj) = v.as_object() {
for (k, vv) in obj { if let Some(spec) = parse_test_arg_spec(vv) { map.insert(k.clone(), spec); } }
for (k, vv) in obj {
if let Some(spec) = parse_test_arg_spec(vv) {
map.insert(k.clone(), spec);
}
}
return Some(map);
}
}
@ -190,21 +352,48 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode {
for st in statements {
match st {
nyash_rust::ASTNode::FunctionDeclaration { name, params, .. } => {
if name == "main" { has_main_fn = true; _main_params_len = params.len(); }
if name == "main" {
has_main_fn = true;
_main_params_len = params.len();
}
if name.starts_with("test_") {
let label = name.clone();
// select args: JSON map > defaults > skip
let mut maybe_args: Option<Vec<nyash_rust::ASTNode>> = None;
if let Some(m) = &args_map { if let Some(v) = m.get(&label) { maybe_args = Some(v.args.clone()); } }
let args = if let Some(a) = maybe_args { a }
else if !params.is_empty() && std::env::var("NYASH_TEST_ARGS_DEFAULTS").ok().as_deref() == Some("1") {
let mut a: Vec<nyash_rust::ASTNode> = Vec::new(); for _ in params { a.push(nyash_rust::ASTNode::Literal{ value: nyash_rust::ast::LiteralValue::Integer(0), span: nyash_rust::ast::Span::unknown() }); } a
} else if params.is_empty() { Vec::new() }
else {
eprintln!("[macro][test][args] missing args for {} (need {}), skipping (set NYASH_TEST_ARGS_DEFAULTS=1 for zero defaults)", label, params.len());
continue
};
tests.push(TestPlan { label, setup: None, call: nyash_rust::ASTNode::FunctionCall { name: name.clone(), arguments: args, span: nyash_rust::ast::Span::unknown() } });
if let Some(m) = &args_map {
if let Some(v) = m.get(&label) {
maybe_args = Some(v.args.clone());
}
}
let args = if let Some(a) = maybe_args {
a
} else if !params.is_empty()
&& std::env::var("NYASH_TEST_ARGS_DEFAULTS").ok().as_deref()
== Some("1")
{
let mut a: Vec<nyash_rust::ASTNode> = Vec::new();
for _ in params {
a.push(nyash_rust::ASTNode::Literal {
value: nyash_rust::ast::LiteralValue::Integer(0),
span: nyash_rust::ast::Span::unknown(),
});
}
a
} else if params.is_empty() {
Vec::new()
} else {
eprintln!("[macro][test][args] missing args for {} (need {}), skipping (set NYASH_TEST_ARGS_DEFAULTS=1 for zero defaults)", label, params.len());
continue;
};
tests.push(TestPlan {
label,
setup: None,
call: nyash_rust::ASTNode::FunctionCall {
name: name.clone(),
arguments: args,
span: nyash_rust::ast::Span::unknown(),
},
});
}
}
_ => {}
@ -214,45 +403,101 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode {
// Collect Box tests: static and instance (no-arg only for instance)
if let nyash_rust::ASTNode::Program { statements, .. } = ast {
for st in statements {
if let nyash_rust::ASTNode::BoxDeclaration { name: box_name, methods, .. } = st {
if let nyash_rust::ASTNode::BoxDeclaration {
name: box_name,
methods,
..
} = st
{
for (mname, mnode) in methods {
if !mname.starts_with("test_") { continue; }
if let nyash_rust::ASTNode::FunctionDeclaration { is_static, params, .. } = mnode {
if !mname.starts_with("test_") {
continue;
}
if let nyash_rust::ASTNode::FunctionDeclaration {
is_static, params, ..
} = mnode
{
if *is_static {
// Static: BoxName.test_*()
let mut args: Vec<nyash_rust::ASTNode> = Vec::new();
if let Some(m) = &args_map { if let Some(v) = m.get(&format!("{}.{}", box_name, mname)) { args = v.args.clone(); } }
if let Some(m) = &args_map {
if let Some(v) = m.get(&format!("{}.{}", box_name, mname)) {
args = v.args.clone();
}
}
if args.is_empty() && !params.is_empty() {
if std::env::var("NYASH_TEST_ARGS_DEFAULTS").ok().as_deref() == Some("1") { for _ in params { args.push(nyash_rust::ASTNode::Literal { value: nyash_rust::ast::LiteralValue::Integer(0), span: nyash_rust::ast::Span::unknown() }); } }
else {
if std::env::var("NYASH_TEST_ARGS_DEFAULTS").ok().as_deref()
== Some("1")
{
for _ in params {
args.push(nyash_rust::ASTNode::Literal {
value: nyash_rust::ast::LiteralValue::Integer(0),
span: nyash_rust::ast::Span::unknown(),
});
}
} else {
eprintln!("[macro][test][args] missing args for {}.{} (need {}), skipping", box_name, mname, params.len());
continue;
}
}
let call = nyash_rust::ASTNode::MethodCall {
object: Box::new(nyash_rust::ASTNode::Variable { name: box_name.clone(), span: nyash_rust::ast::Span::unknown() }),
object: Box::new(nyash_rust::ASTNode::Variable {
name: box_name.clone(),
span: nyash_rust::ast::Span::unknown(),
}),
method: mname.clone(),
arguments: args,
span: nyash_rust::ast::Span::unknown(),
};
tests.push(TestPlan { label: format!("{}.{}", box_name, mname), setup: None, call });
tests.push(TestPlan {
label: format!("{}.{}", box_name, mname),
setup: None,
call,
});
} else {
// Instance: try new BoxName() then .test_*()
let inst_var = format!("__t_{}", box_name.to_lowercase());
// Instance override via JSON
let mut inst_ctor: Option<InstanceSpec> = None;
if let Some(m) = &args_map { if let Some(v) = m.get(&format!("{}.{}", box_name, mname)) { inst_ctor = v.instance.clone(); } }
if let Some(m) = &args_map {
if let Some(v) = m.get(&format!("{}.{}", box_name, mname)) {
inst_ctor = v.instance.clone();
}
}
let inst_init: nyash_rust::ASTNode = if let Some(spec) = inst_ctor {
match spec.ctor.as_str() {
"new" => nyash_rust::ASTNode::New { class: box_name.clone(), arguments: spec.args, type_arguments: spec.type_args, span: nyash_rust::ast::Span::unknown() },
"birth" => nyash_rust::ASTNode::MethodCall { object: Box::new(nyash_rust::ASTNode::Variable { name: box_name.clone(), span: nyash_rust::ast::Span::unknown() }), method: "birth".into(), arguments: spec.args, span: nyash_rust::ast::Span::unknown() },
"new" => nyash_rust::ASTNode::New {
class: box_name.clone(),
arguments: spec.args,
type_arguments: spec.type_args,
span: nyash_rust::ast::Span::unknown(),
},
"birth" => nyash_rust::ASTNode::MethodCall {
object: Box::new(nyash_rust::ASTNode::Variable {
name: box_name.clone(),
span: nyash_rust::ast::Span::unknown(),
}),
method: "birth".into(),
arguments: spec.args,
span: nyash_rust::ast::Span::unknown(),
},
other => {
eprintln!("[macro][test][args] unknown ctor '{}' for {}.{}, using new()", other, box_name, mname);
nyash_rust::ASTNode::New { class: box_name.clone(), arguments: vec![], type_arguments: vec![], span: nyash_rust::ast::Span::unknown() }
nyash_rust::ASTNode::New {
class: box_name.clone(),
arguments: vec![],
type_arguments: vec![],
span: nyash_rust::ast::Span::unknown(),
}
}
}
} else {
nyash_rust::ASTNode::New { class: box_name.clone(), arguments: vec![], type_arguments: vec![], span: nyash_rust::ast::Span::unknown() }
nyash_rust::ASTNode::New {
class: box_name.clone(),
arguments: vec![],
type_arguments: vec![],
span: nyash_rust::ast::Span::unknown(),
}
};
let setup = nyash_rust::ASTNode::Local {
variables: vec![inst_var.clone()],
@ -260,21 +505,40 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode {
span: nyash_rust::ast::Span::unknown(),
};
let mut args: Vec<nyash_rust::ASTNode> = Vec::new();
if let Some(m) = &args_map { if let Some(v) = m.get(&format!("{}.{}", box_name, mname)) { args = v.args.clone(); } }
if let Some(m) = &args_map {
if let Some(v) = m.get(&format!("{}.{}", box_name, mname)) {
args = v.args.clone();
}
}
if args.is_empty() && !params.is_empty() {
if std::env::var("NYASH_TEST_ARGS_DEFAULTS").ok().as_deref() == Some("1") { for _ in params { args.push(nyash_rust::ASTNode::Literal { value: nyash_rust::ast::LiteralValue::Integer(0), span: nyash_rust::ast::Span::unknown() }); } }
else {
if std::env::var("NYASH_TEST_ARGS_DEFAULTS").ok().as_deref()
== Some("1")
{
for _ in params {
args.push(nyash_rust::ASTNode::Literal {
value: nyash_rust::ast::LiteralValue::Integer(0),
span: nyash_rust::ast::Span::unknown(),
});
}
} else {
eprintln!("[macro][test][args] missing args for {}.{} (need {}), skipping", box_name, mname, params.len());
continue;
}
}
let call = nyash_rust::ASTNode::MethodCall {
object: Box::new(nyash_rust::ASTNode::Variable { name: inst_var.clone(), span: nyash_rust::ast::Span::unknown() }),
object: Box::new(nyash_rust::ASTNode::Variable {
name: inst_var.clone(),
span: nyash_rust::ast::Span::unknown(),
}),
method: mname.clone(),
arguments: args,
span: nyash_rust::ast::Span::unknown(),
};
tests.push(TestPlan { label: format!("{}.{}", box_name, mname), setup: Some(setup), call });
tests.push(TestPlan {
label: format!("{}.{}", box_name, mname),
setup: Some(setup),
call,
});
}
}
}
@ -288,7 +552,9 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode {
}
}
if tests.is_empty() {
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { eprintln!("[macro][test] no tests found (functions starting with 'test_')"); }
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
eprintln!("[macro][test] no tests found (functions starting with 'test_')");
}
return ast.clone();
}
// Decide entry policy when main exists
@ -297,34 +563,128 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode {
let ret_policy = std::env::var("NYASH_TEST_RETURN").ok(); // Some("tests"|"original") default tests
// Build harness: top-level function main(args) { ... }
use nyash_rust::ast::{ASTNode as A, Span, LiteralValue, BinaryOperator};
use nyash_rust::ast::{ASTNode as A, BinaryOperator, LiteralValue, Span};
let mut body: Vec<A> = Vec::new();
// locals: pass=0, fail=0
body.push(A::Local { variables: vec!["pass".into(), "fail".into()], initial_values: vec![Some(Box::new(A::Literal{ value: LiteralValue::Integer(0), span: Span::unknown()})), Some(Box::new(A::Literal{ value: LiteralValue::Integer(0), span: Span::unknown()}))], span: Span::unknown() });
body.push(A::Local {
variables: vec!["pass".into(), "fail".into()],
initial_values: vec![
Some(Box::new(A::Literal {
value: LiteralValue::Integer(0),
span: Span::unknown(),
})),
Some(Box::new(A::Literal {
value: LiteralValue::Integer(0),
span: Span::unknown(),
})),
],
span: Span::unknown(),
});
for tp in &tests {
// optional setup
if let Some(set) = tp.setup.clone() { body.push(set); }
if let Some(set) = tp.setup.clone() {
body.push(set);
}
// local r = CALL
body.push(A::Local { variables: vec!["r".into()], initial_values: vec![Some(Box::new(tp.call.clone()))], span: Span::unknown() });
body.push(A::Local {
variables: vec!["r".into()],
initial_values: vec![Some(Box::new(tp.call.clone()))],
span: Span::unknown(),
});
// if r { print("PASS t"); pass = pass + 1 } else { print("FAIL t"); fail = fail + 1 }
let pass_msg = A::Literal { value: LiteralValue::String(format!("PASS {}", tp.label)), span: Span::unknown() };
let fail_msg = A::Literal { value: LiteralValue::String(format!("FAIL {}", tp.label)), span: Span::unknown() };
let pass_msg = A::Literal {
value: LiteralValue::String(format!("PASS {}", tp.label)),
span: Span::unknown(),
};
let fail_msg = A::Literal {
value: LiteralValue::String(format!("FAIL {}", tp.label)),
span: Span::unknown(),
};
let then_body = vec![
A::Print { expression: Box::new(pass_msg), span: Span::unknown() },
A::Assignment { target: Box::new(A::Variable{ name: "pass".into(), span: Span::unknown() }), value: Box::new(A::BinaryOp{ operator: BinaryOperator::Add, left: Box::new(A::Variable{ name: "pass".into(), span: Span::unknown() }), right: Box::new(A::Literal{ value: LiteralValue::Integer(1), span: Span::unknown() }), span: Span::unknown() }), span: Span::unknown() },
A::Print {
expression: Box::new(pass_msg),
span: Span::unknown(),
},
A::Assignment {
target: Box::new(A::Variable {
name: "pass".into(),
span: Span::unknown(),
}),
value: Box::new(A::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(A::Variable {
name: "pass".into(),
span: Span::unknown(),
}),
right: Box::new(A::Literal {
value: LiteralValue::Integer(1),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
},
];
let else_body = vec![
A::Print { expression: Box::new(fail_msg), span: Span::unknown() },
A::Assignment { target: Box::new(A::Variable{ name: "fail".into(), span: Span::unknown() }), value: Box::new(A::BinaryOp{ operator: BinaryOperator::Add, left: Box::new(A::Variable{ name: "fail".into(), span: Span::unknown() }), right: Box::new(A::Literal{ value: LiteralValue::Integer(1), span: Span::unknown() }), span: Span::unknown() }), span: Span::unknown() },
A::Print {
expression: Box::new(fail_msg),
span: Span::unknown(),
},
A::Assignment {
target: Box::new(A::Variable {
name: "fail".into(),
span: Span::unknown(),
}),
value: Box::new(A::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(A::Variable {
name: "fail".into(),
span: Span::unknown(),
}),
right: Box::new(A::Literal {
value: LiteralValue::Integer(1),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
},
];
body.push(A::If { condition: Box::new(A::Variable { name: "r".into(), span: Span::unknown() }), then_body, else_body: Some(else_body), span: Span::unknown() });
body.push(A::If {
condition: Box::new(A::Variable {
name: "r".into(),
span: Span::unknown(),
}),
then_body,
else_body: Some(else_body),
span: Span::unknown(),
});
}
// print summary and return fail
body.push(A::Print { expression: Box::new(A::Literal{ value: LiteralValue::String(format!("Summary: {} tests", tests.len())), span: Span::unknown() }), span: Span::unknown() });
body.push(A::Return { value: Some(Box::new(A::Variable{ name: "fail".into(), span: Span::unknown() })), span: Span::unknown() });
body.push(A::Print {
expression: Box::new(A::Literal {
value: LiteralValue::String(format!("Summary: {} tests", tests.len())),
span: Span::unknown(),
}),
span: Span::unknown(),
});
body.push(A::Return {
value: Some(Box::new(A::Variable {
name: "fail".into(),
span: Span::unknown(),
})),
span: Span::unknown(),
});
// Build harness main body as above
let make_harness_main = |body: Vec<A>| -> A {
A::FunctionDeclaration { name: "main".into(), params: vec!["args".into()], body, is_static: false, is_override: false, span: Span::unknown() }
A::FunctionDeclaration {
name: "main".into(),
params: vec!["args".into()],
body,
is_static: false,
is_override: false,
span: Span::unknown(),
}
};
// Transform AST according to policy
@ -334,27 +694,64 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode {
let mut orig_call_fn: Option<A> = None;
for st in statements {
match st {
A::FunctionDeclaration { name, params, body: orig_body, is_static, is_override, span: fspan } if name == "main" => {
A::FunctionDeclaration {
name,
params,
body: orig_body,
is_static,
is_override,
span: fspan,
} if name == "main" => {
if has_main_fn && (force || entry_mode.is_some()) {
// rename original main
let new_name = "__ny_orig_main".to_string();
out_stmts.push(A::FunctionDeclaration { name: new_name.clone(), params: params.clone(), body: orig_body.clone(), is_static, is_override, span: fspan });
out_stmts.push(A::FunctionDeclaration {
name: new_name.clone(),
params: params.clone(),
body: orig_body.clone(),
is_static,
is_override,
span: fspan,
});
_renamed_main = true;
if entry_mode.as_deref() == Some("wrap") {
let args_exprs = if params.len() >= 1 { vec![A::Variable { name: "args".into(), span: nyash_rust::ast::Span::unknown() }] } else { vec![] };
orig_call_fn = Some(A::FunctionCall { name: new_name, arguments: args_exprs, span: nyash_rust::ast::Span::unknown() });
let args_exprs = if params.len() >= 1 {
vec![A::Variable {
name: "args".into(),
span: nyash_rust::ast::Span::unknown(),
}]
} else {
vec![]
};
orig_call_fn = Some(A::FunctionCall {
name: new_name,
arguments: args_exprs,
span: nyash_rust::ast::Span::unknown(),
});
}
} else {
// keep as-is (no injection)
out_stmts.push(A::FunctionDeclaration { name, params, body: orig_body, is_static, is_override, span: fspan });
out_stmts.push(A::FunctionDeclaration {
name,
params,
body: orig_body,
is_static,
is_override,
span: fspan,
});
}
}
other => out_stmts.push(other),
}
}
if has_main_fn && !(force || entry_mode.is_some()) {
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") { eprintln!("[macro][test] existing main detected; skip harness (set --test-entry or NYASH_TEST_FORCE=1)"); }
return nyash_rust::ASTNode::Program { statements: out_stmts, span };
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
eprintln!("[macro][test] existing main detected; skip harness (set --test-entry or NYASH_TEST_FORCE=1)");
}
return nyash_rust::ASTNode::Program {
statements: out_stmts,
span,
};
}
// Compose harness main now
let mut body2 = body;
@ -362,9 +759,19 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode {
if let Some(call) = orig_call_fn.take() {
if ret_policy.as_deref() == Some("original") {
// local __ny_orig_ret = __ny_orig_main(args)
body2.push(A::Local { variables: vec!["__ny_orig_ret".into()], initial_values: vec![Some(Box::new(call))], span: nyash_rust::ast::Span::unknown() });
body2.push(A::Local {
variables: vec!["__ny_orig_ret".into()],
initial_values: vec![Some(Box::new(call))],
span: nyash_rust::ast::Span::unknown(),
});
// return __ny_orig_ret
body2.push(A::Return { value: Some(Box::new(A::Variable { name: "__ny_orig_ret".into(), span: nyash_rust::ast::Span::unknown() })), span: nyash_rust::ast::Span::unknown() });
body2.push(A::Return {
value: Some(Box::new(A::Variable {
name: "__ny_orig_ret".into(),
span: nyash_rust::ast::Span::unknown(),
})),
span: nyash_rust::ast::Span::unknown(),
});
} else {
// default: tests policy; still call original but ignore result
body2.push(call);
@ -373,7 +780,10 @@ fn maybe_inject_test_harness(ast: &ASTNode) -> ASTNode {
}
let harness_fn = make_harness_main(body2);
out_stmts.push(harness_fn);
return nyash_rust::ASTNode::Program { statements: out_stmts, span };
return nyash_rust::ASTNode::Program {
statements: out_stmts,
span,
};
}
ast.clone()
}

View File

@ -1,5 +1,5 @@
use std::collections::HashMap;
use nyash_rust::ASTNode;
use std::collections::HashMap;
/// Minimal pattern trait — MVP
pub trait MacroPattern {
@ -10,35 +10,71 @@ pub trait MacroPattern {
pub struct AstBuilder;
impl AstBuilder {
pub fn new() -> Self { Self }
pub fn new() -> Self {
Self
}
pub fn quote(&self, code: &str) -> ASTNode {
// MVP: parse string into AST using existing parser
match nyash_rust::parser::NyashParser::parse_from_string(code) {
Ok(ast) => ast,
Err(_) => ASTNode::Program { statements: vec![], span: nyash_rust::ast::Span::unknown() },
Err(_) => ASTNode::Program {
statements: vec![],
span: nyash_rust::ast::Span::unknown(),
},
}
}
pub fn unquote(&self, template: &ASTNode, _bindings: &HashMap<String, ASTNode>) -> ASTNode {
// Replace Variables named like "$name" with corresponding bound AST
fn is_placeholder(name: &str) -> Option<&str> {
if name.starts_with('$') && name.len() > 1 { Some(&name[1..]) } else { None }
if name.starts_with('$') && name.len() > 1 {
Some(&name[1..])
} else {
None
}
}
fn is_variadic(name: &str) -> Option<&str> {
if name.starts_with("$...") && name.len() > 4 { Some(&name[4..]) } else { None }
if name.starts_with("$...") && name.len() > 4 {
Some(&name[4..])
} else {
None
}
}
fn subst(node: &ASTNode, binds: &HashMap<String, ASTNode>) -> ASTNode {
match node.clone() {
ASTNode::Variable { name, .. } => {
if let Some(k) = is_placeholder(&name) {
if let Some(v) = binds.get(k) { return v.clone(); }
if let Some(v) = binds.get(k) {
return v.clone();
}
}
node.clone()
}
ASTNode::BinaryOp { operator, left, right, span } => ASTNode::BinaryOp {
operator, left: Box::new(subst(&left, binds)), right: Box::new(subst(&right, binds)), span
ASTNode::BinaryOp {
operator,
left,
right,
span,
} => ASTNode::BinaryOp {
operator,
left: Box::new(subst(&left, binds)),
right: Box::new(subst(&right, binds)),
span,
},
ASTNode::UnaryOp { operator, operand, span } => ASTNode::UnaryOp { operator, operand: Box::new(subst(&operand, binds)), span },
ASTNode::MethodCall { object, method, arguments, span } => {
ASTNode::UnaryOp {
operator,
operand,
span,
} => ASTNode::UnaryOp {
operator,
operand: Box::new(subst(&operand, binds)),
span,
},
ASTNode::MethodCall {
object,
method,
arguments,
span,
} => {
let mut out_args: Vec<ASTNode> = Vec::new();
let mut i = 0usize;
while i < arguments.len() {
@ -54,9 +90,18 @@ impl AstBuilder {
out_args.push(subst(&arguments[i], binds));
i += 1;
}
ASTNode::MethodCall { object: Box::new(subst(&object, binds)), method, arguments: out_args, span }
ASTNode::MethodCall {
object: Box::new(subst(&object, binds)),
method,
arguments: out_args,
span,
}
}
ASTNode::FunctionCall { name, arguments, span } => {
ASTNode::FunctionCall {
name,
arguments,
span,
} => {
let mut out_args: Vec<ASTNode> = Vec::new();
let mut i = 0usize;
while i < arguments.len() {
@ -72,7 +117,11 @@ impl AstBuilder {
out_args.push(subst(&arguments[i], binds));
i += 1;
}
ASTNode::FunctionCall { name, arguments: out_args, span }
ASTNode::FunctionCall {
name,
arguments: out_args,
span,
}
}
ASTNode::ArrayLiteral { elements, span } => {
// Splice variadic placeholder inside arrays
@ -91,21 +140,55 @@ impl AstBuilder {
out_elems.push(subst(&elements[i], binds));
i += 1;
}
ASTNode::ArrayLiteral { elements: out_elems, span }
ASTNode::ArrayLiteral {
elements: out_elems,
span,
}
}
ASTNode::MapLiteral { entries, span } => {
ASTNode::MapLiteral { entries: entries.into_iter().map(|(k, v)| (k, subst(&v, binds))).collect(), span }
}
ASTNode::FieldAccess { object, field, span } => ASTNode::FieldAccess { object: Box::new(subst(&object, binds)), field, span },
ASTNode::Assignment { target, value, span } => ASTNode::Assignment { target: Box::new(subst(&target, binds)), value: Box::new(subst(&value, binds)), span },
ASTNode::Return { value, span } => ASTNode::Return { value: value.as_ref().map(|v| Box::new(subst(v, binds))), span },
ASTNode::If { condition, then_body, else_body, span } => ASTNode::If {
ASTNode::MapLiteral { entries, span } => ASTNode::MapLiteral {
entries: entries
.into_iter()
.map(|(k, v)| (k, subst(&v, binds)))
.collect(),
span,
},
ASTNode::FieldAccess {
object,
field,
span,
} => ASTNode::FieldAccess {
object: Box::new(subst(&object, binds)),
field,
span,
},
ASTNode::Assignment {
target,
value,
span,
} => ASTNode::Assignment {
target: Box::new(subst(&target, binds)),
value: Box::new(subst(&value, binds)),
span,
},
ASTNode::Return { value, span } => ASTNode::Return {
value: value.as_ref().map(|v| Box::new(subst(v, binds))),
span,
},
ASTNode::If {
condition,
then_body,
else_body,
span,
} => ASTNode::If {
condition: Box::new(subst(&condition, binds)),
then_body: then_body.into_iter().map(|n| subst(&n, binds)).collect(),
else_body: else_body.map(|v| v.into_iter().map(|n| subst(&n, binds)).collect()),
span,
},
ASTNode::Program { statements, span } => ASTNode::Program { statements: statements.into_iter().map(|n| subst(&n, binds)).collect(), span },
ASTNode::Program { statements, span } => ASTNode::Program {
statements: statements.into_iter().map(|n| subst(&n, binds)).collect(),
span,
},
other => other,
}
}
@ -114,115 +197,326 @@ impl AstBuilder {
}
/// Simple template-based pattern that uses Variables named "$name" as bind points.
pub struct TemplatePattern { pub template: ASTNode }
pub struct TemplatePattern {
pub template: ASTNode,
}
impl TemplatePattern { pub fn new(template: ASTNode) -> Self { Self { template } } }
impl TemplatePattern {
pub fn new(template: ASTNode) -> Self {
Self { template }
}
}
impl MacroPattern for TemplatePattern {
fn match_ast(&self, node: &ASTNode) -> Option<HashMap<String, ASTNode>> {
fn is_placeholder(name: &str) -> Option<&str> {
if name.starts_with('$') && name.len() > 1 { Some(&name[1..]) } else { None }
if name.starts_with('$') && name.len() > 1 {
Some(&name[1..])
} else {
None
}
}
fn is_variadic(name: &str) -> Option<&str> {
if name.starts_with("$...") && name.len() > 4 { Some(&name[4..]) } else { None }
if name.starts_with("$...") && name.len() > 4 {
Some(&name[4..])
} else {
None
}
}
fn go(tpl: &ASTNode, tgt: &ASTNode, out: &mut HashMap<String, ASTNode>) -> bool {
match (tpl, tgt) {
(ASTNode::Variable { name, .. }, v) => {
if let Some(k) = is_placeholder(name) { out.insert(k.to_string(), v.clone()); true } else { tpl == tgt }
if let Some(k) = is_placeholder(name) {
out.insert(k.to_string(), v.clone());
true
} else {
tpl == tgt
}
}
(ASTNode::Literal { .. }, _) | (ASTNode::Me { .. }, _) | (ASTNode::This { .. }, _) => tpl == tgt,
(ASTNode::BinaryOp { operator: op1, left: l1, right: r1, .. }, ASTNode::BinaryOp { operator: op2, left: l2, right: r2, .. }) => {
op1 == op2 && go(l1, l2, out) && go(r1, r2, out)
}
(ASTNode::UnaryOp { operator: o1, operand: a1, .. }, ASTNode::UnaryOp { operator: o2, operand: a2, .. }) => {
o1 == o2 && go(a1, a2, out)
}
(ASTNode::MethodCall { object: o1, method: m1, arguments: a1, .. }, ASTNode::MethodCall { object: o2, method: m2, arguments: a2, .. }) => {
if m1 != m2 { return false; }
if !go(o1, o2, out) { return false; }
(ASTNode::Literal { .. }, _)
| (ASTNode::Me { .. }, _)
| (ASTNode::This { .. }, _) => tpl == tgt,
(
ASTNode::BinaryOp {
operator: op1,
left: l1,
right: r1,
..
},
ASTNode::BinaryOp {
operator: op2,
left: l2,
right: r2,
..
},
) => op1 == op2 && go(l1, l2, out) && go(r1, r2, out),
(
ASTNode::UnaryOp {
operator: o1,
operand: a1,
..
},
ASTNode::UnaryOp {
operator: o2,
operand: a2,
..
},
) => o1 == o2 && go(a1, a2, out),
(
ASTNode::MethodCall {
object: o1,
method: m1,
arguments: a1,
..
},
ASTNode::MethodCall {
object: o2,
method: m2,
arguments: a2,
..
},
) => {
if m1 != m2 {
return false;
}
if !go(o1, o2, out) {
return false;
}
// Support variadic anywhere in a1
let mut varpos: Option<(usize, String)> = None;
for (i, arg) in a1.iter().enumerate() {
if let ASTNode::Variable { name, .. } = arg { if let Some(vn) = is_variadic(name) { varpos = Some((i, vn.to_string())); break; } }
if let ASTNode::Variable { name, .. } = arg {
if let Some(vn) = is_variadic(name) {
varpos = Some((i, vn.to_string()));
break;
}
}
}
if let Some((k, vn)) = varpos {
let suffix_len = a1.len() - k - 1;
if a2.len() < k + suffix_len { return false; }
for (x, y) in a1[..k].iter().zip(a2.iter()) { if !go(x, y, out) { return false; } }
for (i, x) in a1[a1.len()-suffix_len..].iter().enumerate() {
let y = &a2[a2.len()-suffix_len + i];
if !go(x, y, out) { return false; }
if a2.len() < k + suffix_len {
return false;
}
let tail: Vec<ASTNode> = a2[k..a2.len()-suffix_len].to_vec();
out.insert(vn, ASTNode::Program { statements: tail, span: nyash_rust::ast::Span::unknown() });
for (x, y) in a1[..k].iter().zip(a2.iter()) {
if !go(x, y, out) {
return false;
}
}
for (i, x) in a1[a1.len() - suffix_len..].iter().enumerate() {
let y = &a2[a2.len() - suffix_len + i];
if !go(x, y, out) {
return false;
}
}
let tail: Vec<ASTNode> = a2[k..a2.len() - suffix_len].to_vec();
out.insert(
vn,
ASTNode::Program {
statements: tail,
span: nyash_rust::ast::Span::unknown(),
},
);
return true;
}
if a1.len() != a2.len() { return false; }
for (x, y) in a1.iter().zip(a2.iter()) { if !go(x, y, out) { return false; } }
if a1.len() != a2.len() {
return false;
}
for (x, y) in a1.iter().zip(a2.iter()) {
if !go(x, y, out) {
return false;
}
}
true
}
(ASTNode::FunctionCall { name: n1, arguments: a1, .. }, ASTNode::FunctionCall { name: n2, arguments: a2, .. }) => {
if n1 != n2 { return false; }
(
ASTNode::FunctionCall {
name: n1,
arguments: a1,
..
},
ASTNode::FunctionCall {
name: n2,
arguments: a2,
..
},
) => {
if n1 != n2 {
return false;
}
let mut varpos: Option<(usize, String)> = None;
for (i, arg) in a1.iter().enumerate() {
if let ASTNode::Variable { name, .. } = arg { if let Some(vn) = is_variadic(name) { varpos = Some((i, vn.to_string())); break; } }
if let ASTNode::Variable { name, .. } = arg {
if let Some(vn) = is_variadic(name) {
varpos = Some((i, vn.to_string()));
break;
}
}
}
if let Some((k, vn)) = varpos {
let suffix_len = a1.len() - k - 1;
if a2.len() < k + suffix_len { return false; }
for (x, y) in a1[..k].iter().zip(a2.iter()) { if !go(x, y, out) { return false; } }
for (i, x) in a1[a1.len()-suffix_len..].iter().enumerate() {
let y = &a2[a2.len()-suffix_len + i];
if !go(x, y, out) { return false; }
if a2.len() < k + suffix_len {
return false;
}
let tail: Vec<ASTNode> = a2[k..a2.len()-suffix_len].to_vec();
out.insert(vn, ASTNode::Program { statements: tail, span: nyash_rust::ast::Span::unknown() });
for (x, y) in a1[..k].iter().zip(a2.iter()) {
if !go(x, y, out) {
return false;
}
}
for (i, x) in a1[a1.len() - suffix_len..].iter().enumerate() {
let y = &a2[a2.len() - suffix_len + i];
if !go(x, y, out) {
return false;
}
}
let tail: Vec<ASTNode> = a2[k..a2.len() - suffix_len].to_vec();
out.insert(
vn,
ASTNode::Program {
statements: tail,
span: nyash_rust::ast::Span::unknown(),
},
);
return true;
}
if a1.len() != a2.len() { return false; }
for (x, y) in a1.iter().zip(a2.iter()) { if !go(x, y, out) { return false; } }
if a1.len() != a2.len() {
return false;
}
for (x, y) in a1.iter().zip(a2.iter()) {
if !go(x, y, out) {
return false;
}
}
true
}
(ASTNode::ArrayLiteral { elements: e1, .. }, ASTNode::ArrayLiteral { elements: e2, .. }) => {
(
ASTNode::ArrayLiteral { elements: e1, .. },
ASTNode::ArrayLiteral { elements: e2, .. },
) => {
let mut varpos: Option<(usize, String)> = None;
for (i, el) in e1.iter().enumerate() {
if let ASTNode::Variable { name, .. } = el { if let Some(vn) = is_variadic(name) { varpos = Some((i, vn.to_string())); break; } }
if let ASTNode::Variable { name, .. } = el {
if let Some(vn) = is_variadic(name) {
varpos = Some((i, vn.to_string()));
break;
}
}
}
if let Some((k, vn)) = varpos {
let suffix_len = e1.len() - k - 1;
if e2.len() < k + suffix_len { return false; }
for (x, y) in e1[..k].iter().zip(e2.iter()) { if !go(x, y, out) { return false; } }
for (i, x) in e1[e1.len()-suffix_len..].iter().enumerate() {
let y = &e2[e2.len()-suffix_len + i];
if !go(x, y, out) { return false; }
if e2.len() < k + suffix_len {
return false;
}
let tail: Vec<ASTNode> = e2[k..e2.len()-suffix_len].to_vec();
out.insert(vn, ASTNode::Program { statements: tail, span: nyash_rust::ast::Span::unknown() });
for (x, y) in e1[..k].iter().zip(e2.iter()) {
if !go(x, y, out) {
return false;
}
}
for (i, x) in e1[e1.len() - suffix_len..].iter().enumerate() {
let y = &e2[e2.len() - suffix_len + i];
if !go(x, y, out) {
return false;
}
}
let tail: Vec<ASTNode> = e2[k..e2.len() - suffix_len].to_vec();
out.insert(
vn,
ASTNode::Program {
statements: tail,
span: nyash_rust::ast::Span::unknown(),
},
);
return true;
}
if e1.len() != e2.len() { return false; }
for (x, y) in e1.iter().zip(e2.iter()) { if !go(x, y, out) { return false; } }
true
}
(ASTNode::MapLiteral { entries: m1, .. }, ASTNode::MapLiteral { entries: m2, .. }) => {
if m1.len() != m2.len() { return false; }
for ((k1, v1), (k2, v2)) in m1.iter().zip(m2.iter()) {
if k1 != k2 { return false; }
if !go(v1, v2, out) { return false; }
if e1.len() != e2.len() {
return false;
}
for (x, y) in e1.iter().zip(e2.iter()) {
if !go(x, y, out) {
return false;
}
}
true
}
(ASTNode::FieldAccess { object: o1, field: f1, .. }, ASTNode::FieldAccess { object: o2, field: f2, .. }) => f1 == f2 && go(o1, o2, out),
(ASTNode::Assignment { target: t1, value: v1, .. }, ASTNode::Assignment { target: t2, value: v2, .. }) => go(t1, t2, out) && go(v1, v2, out),
(ASTNode::Return { value: v1, .. }, ASTNode::Return { value: v2, .. }) => match (v1, v2) { (Some(a), Some(b)) => go(a, b, out), (None, None) => true, _ => false },
(ASTNode::If { condition: c1, then_body: t1, else_body: e1, .. }, ASTNode::If { condition: c2, then_body: t2, else_body: e2, .. }) => {
if !go(c1, c2, out) || t1.len() != t2.len() { return false; }
for (x, y) in t1.iter().zip(t2.iter()) { if !go(x, y, out) { return false; } }
(
ASTNode::MapLiteral { entries: m1, .. },
ASTNode::MapLiteral { entries: m2, .. },
) => {
if m1.len() != m2.len() {
return false;
}
for ((k1, v1), (k2, v2)) in m1.iter().zip(m2.iter()) {
if k1 != k2 {
return false;
}
if !go(v1, v2, out) {
return false;
}
}
true
}
(
ASTNode::FieldAccess {
object: o1,
field: f1,
..
},
ASTNode::FieldAccess {
object: o2,
field: f2,
..
},
) => f1 == f2 && go(o1, o2, out),
(
ASTNode::Assignment {
target: t1,
value: v1,
..
},
ASTNode::Assignment {
target: t2,
value: v2,
..
},
) => go(t1, t2, out) && go(v1, v2, out),
(ASTNode::Return { value: v1, .. }, ASTNode::Return { value: v2, .. }) => {
match (v1, v2) {
(Some(a), Some(b)) => go(a, b, out),
(None, None) => true,
_ => false,
}
}
(
ASTNode::If {
condition: c1,
then_body: t1,
else_body: e1,
..
},
ASTNode::If {
condition: c2,
then_body: t2,
else_body: e2,
..
},
) => {
if !go(c1, c2, out) || t1.len() != t2.len() {
return false;
}
for (x, y) in t1.iter().zip(t2.iter()) {
if !go(x, y, out) {
return false;
}
}
match (e1, e2) {
(Some(a), Some(b)) => {
if a.len() != b.len() { return false; }
for (x, y) in a.iter().zip(b.iter()) { if !go(x, y, out) { return false; } }
if a.len() != b.len() {
return false;
}
for (x, y) in a.iter().zip(b.iter()) {
if !go(x, y, out) {
return false;
}
}
true
}
(None, None) => true,
@ -233,19 +527,31 @@ impl MacroPattern for TemplatePattern {
}
}
let mut out = HashMap::new();
if go(&self.template, node, &mut out) { Some(out) } else { None }
if go(&self.template, node, &mut out) {
Some(out)
} else {
None
}
}
}
/// ORパターン: いずれかのテンプレートにマッチすれば成功
pub struct OrPattern { pub alts: Vec<TemplatePattern> }
pub struct OrPattern {
pub alts: Vec<TemplatePattern>,
}
impl OrPattern { pub fn new(alts: Vec<TemplatePattern>) -> Self { Self { alts } } }
impl OrPattern {
pub fn new(alts: Vec<TemplatePattern>) -> Self {
Self { alts }
}
}
impl MacroPattern for OrPattern {
fn match_ast(&self, node: &ASTNode) -> Option<HashMap<String, ASTNode>> {
for tp in &self.alts {
if let Some(b) = tp.match_ast(node) { return Some(b); }
if let Some(b) = tp.match_ast(node) {
return Some(b);
}
}
None
}

View File

@ -11,11 +11,7 @@ use nyash_rust::runner::NyashRunner;
fn main() {
// Optional: enable backtrace on stack overflow for deep debug runs.
// Guarded by env to keep default behavior unchanged.
if std::env::var("NYASH_DEBUG_STACK_OVERFLOW")
.ok()
.as_deref()
== Some("1")
{
if std::env::var("NYASH_DEBUG_STACK_OVERFLOW").ok().as_deref() == Some("1") {
unsafe {
let _ = backtrace_on_stack_overflow::enable();
}

View File

@ -1,10 +1,10 @@
//! AOT-Plan v1 → MIR13 importer (Phase 15.1)
//! Feature-gated behind `aot-plan-import`.
use crate::mir::function_emission as femit;
use crate::mir::{
BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction, MirModule, MirType,
};
use crate::mir::function_emission as femit;
#[derive(Debug, serde::Deserialize)]
struct PlanV1 {
@ -98,7 +98,10 @@ pub fn import_from_str(plan_json: &str) -> Result<MirModule, String> {
// Fall back to direct const emission inline here to avoid adding a new helper unnecessarily.
let d = mf.next_value_id();
if let Some(block) = mf.get_block_mut(bb) {
block.add_instruction(crate::mir::MirInstruction::Const { dst: d, value: ConstValue::Float(fl) });
block.add_instruction(crate::mir::MirInstruction::Const {
dst: d,
value: ConstValue::Float(fl),
});
}
d
}
@ -107,7 +110,10 @@ pub fn import_from_str(plan_json: &str) -> Result<MirModule, String> {
// Null/Void are not expected in PlanBody::ConstReturn; still handle gracefully.
let d = mf.next_value_id();
if let Some(block) = mf.get_block_mut(bb) {
block.add_instruction(crate::mir::MirInstruction::Const { dst: d, value: other });
block.add_instruction(crate::mir::MirInstruction::Const {
dst: d,
value: other,
});
}
d
}

View File

@ -240,7 +240,10 @@ impl BasicBlock {
}
}
}
Err(format!("PHI instruction with dst {:?} not found in block {:?}", phi_dst, self.id))
Err(format!(
"PHI instruction with dst {:?} not found in block {:?}",
phi_dst, self.id
))
}
/// Replace terminator instruction

View File

@ -9,50 +9,50 @@ use super::{
BasicBlock, BasicBlockId, BasicBlockIdGenerator, CompareOp, ConstValue, Effect, EffectMask,
FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId, ValueIdGenerator,
};
use crate::mir::region::function_slot_registry::FunctionSlotRegistry;
use crate::mir::region::RegionId;
use crate::ast::{ASTNode, LiteralValue};
use crate::mir::builder::builder_calls::CallTarget;
use crate::mir::region::function_slot_registry::FunctionSlotRegistry;
use crate::mir::region::RegionId;
use std::collections::HashMap;
use std::collections::HashSet;
mod calls; // Call system modules (refactored from builder_calls)
mod builder_calls;
mod call_resolution; // ChatGPT5 Pro: Type-safe call resolution utilities
mod calls; // Call system modules (refactored from builder_calls)
mod context; // BoxCompilationContext - 箱理論による静的Boxコンパイル時のコンテキスト分離
mod method_call_handlers; // Method call handler separation (Phase 3)
mod decls; // declarations lowering split
mod exprs; // expression lowering split
mod exprs_call; // call(expr)
// include lowering removed (using is handled in runner)
mod exprs_call;
mod method_call_handlers; // Method call handler separation (Phase 3) // call(expr)
// include lowering removed (using is handled in runner)
mod control_flow; // thin wrappers to centralize control-flow entrypoints
mod exprs_lambda; // lambda lowering
mod exprs_peek; // peek expression
mod exprs_qmark; // ?-propagate
mod fields; // field access/assignment lowering split
mod if_form;
mod lifecycle;
pub(crate) mod loops;
mod ops;
mod phi;
mod phi_merge; // Phase 25.1q: Unified PHI merge helper
mod if_form;
mod control_flow; // thin wrappers to centralize control-flow entrypoints
mod lifecycle; // prepare/lower_root/finalize split
// legacy large-match remains inline for now (planned extraction)
mod plugin_sigs; // plugin signature loader
mod stmts;
mod utils;
mod vars; // variables/scope helpers // small loop helpers (header/exit context)
mod origin; // P0: origin inferenceme/Knownと PHI 伝播(軽量)
mod observe; // P0: dev-only observability helpersssa/resolve
mod rewrite; // P1: Known rewrite & special consolidation
mod ssa; // LocalSSA helpers (in-block materialization)
mod schedule; // BlockScheduleBox物理順序: PHI→materialize→body
mod receiver; // ReceiverMaterializationBoxMethod recv の pin+LocalSSA 集約)
mod metadata; // MetadataPropagationBoxtype/originの伝播
mod emission; // emission::*Const/Compare/Branch の薄い発行箱)
mod types; // types::annotation / inference型注釈/推論の箱: 推論は後段)
mod router; // RouterPolicyBoxUnified vs BoxCall
mod phi_merge; // Phase 25.1q: Unified PHI merge helper // prepare/lower_root/finalize split
// legacy large-match remains inline for now (planned extraction)
mod emission; // emission::*Const/Compare/Branch の薄い発行箱)
mod emit_guard; // EmitGuardBoxemit直前の最終関所
mod metadata; // MetadataPropagationBoxtype/originの伝播
mod name_const; // NameConstBox関数名Const生成
pub(crate) mod type_registry; // TypeRegistryBox型情報管理の一元化
mod observe; // P0: dev-only observability helpersssa/resolve
mod origin; // P0: origin inferenceme/Knownと PHI 伝播(軽量)
mod plugin_sigs; // plugin signature loader
mod receiver; // ReceiverMaterializationBoxMethod recv の pin+LocalSSA 集約)
mod rewrite; // P1: Known rewrite & special consolidation
mod router; // RouterPolicyBoxUnified vs BoxCall
mod schedule; // BlockScheduleBox物理順序: PHI→materialize→body
mod ssa; // LocalSSA helpers (in-block materialization)
mod stmts;
pub(crate) mod type_registry;
mod types; // types::annotation / inference型注釈/推論の箱: 推論は後段)
mod utils;
mod vars; // variables/scope helpers // small loop helpers (header/exit context) // TypeRegistryBox型情報管理の一元化
// Unified member property kinds for computed/once/birth_once
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -62,7 +62,6 @@ pub(crate) enum PropertyKind {
BirthOnce,
}
/// MIR builder for converting AST to SSA form
pub struct MirBuilder {
/// Current module being built
@ -152,7 +151,6 @@ pub struct MirBuilder {
pub(super) current_region_stack: Vec<RegionId>,
// include guards removed
/// Loop context stacks for lowering break/continue inside nested control flow
/// Top of stack corresponds to the innermost active loop
pub(super) loop_header_stack: Vec<BasicBlockId>,
@ -288,19 +286,31 @@ impl MirBuilder {
}
/// Push/pop helpers for If merge context (best-effort; optional usage)
pub(super) fn push_if_merge(&mut self, bb: BasicBlockId) { self.if_merge_stack.push(bb); }
pub(super) fn pop_if_merge(&mut self) { let _ = self.if_merge_stack.pop(); }
pub(super) fn push_if_merge(&mut self, bb: BasicBlockId) {
self.if_merge_stack.push(bb);
}
pub(super) fn pop_if_merge(&mut self) {
let _ = self.if_merge_stack.pop();
}
/// Suppress entry pin copy for the next start_new_block (used for merge blocks).
pub(super) fn suppress_next_entry_pin_copy(&mut self) { self.suppress_pin_entry_copy_next = true; }
pub(super) fn suppress_next_entry_pin_copy(&mut self) {
self.suppress_pin_entry_copy_next = true;
}
// ---- Hint helpers (no-op by default) ----
#[inline]
pub(crate) fn hint_scope_enter(&mut self, id: u32) { self.hint_sink.scope_enter(id); }
pub(crate) fn hint_scope_enter(&mut self, id: u32) {
self.hint_sink.scope_enter(id);
}
#[inline]
pub(crate) fn hint_scope_leave(&mut self, id: u32) { self.hint_sink.scope_leave(id); }
pub(crate) fn hint_scope_leave(&mut self, id: u32) {
self.hint_sink.scope_leave(id);
}
#[inline]
pub(crate) fn hint_join_result<S: Into<String>>(&mut self, var: S) { self.hint_sink.join_result(var.into()); }
pub(crate) fn hint_join_result<S: Into<String>>(&mut self, var: S) {
self.hint_sink.join_result(var.into());
}
// ----------------------
// Debug scope helpers (region_id for DebugHub events)
@ -332,10 +342,7 @@ impl MirBuilder {
// ----------------------
#[inline]
pub(super) fn compile_trace_enabled() -> bool {
std::env::var("NYASH_MIR_COMPILE_TRACE")
.ok()
.as_deref()
== Some("1")
std::env::var("NYASH_MIR_COMPILE_TRACE").ok().as_deref() == Some("1")
}
#[inline]
@ -395,7 +402,6 @@ impl MirBuilder {
.unwrap_or_default()
}
/// Build a complete MIR module from AST
pub fn build_module(&mut self, ast: ASTNode) -> Result<MirModule, String> {
self.prepare_module()?;
@ -403,7 +409,6 @@ impl MirBuilder {
self.finalize_module(result_value)
}
/// Build an expression and return its value ID
pub(super) fn build_expression(&mut self, ast: ASTNode) -> Result<ValueId, String> {
// Delegated to exprs.rs to keep this file lean
@ -412,11 +417,17 @@ impl MirBuilder {
self.recursion_depth += 1;
if self.recursion_depth > MAX_RECURSION_DEPTH {
eprintln!("\n[FATAL] ============================================");
eprintln!("[FATAL] Recursion depth exceeded {} in build_expression", MAX_RECURSION_DEPTH);
eprintln!(
"[FATAL] Recursion depth exceeded {} in build_expression",
MAX_RECURSION_DEPTH
);
eprintln!("[FATAL] Current depth: {}", self.recursion_depth);
eprintln!("[FATAL] AST node type: {:?}", std::mem::discriminant(&ast));
eprintln!("[FATAL] ============================================\n");
return Err(format!("Recursion depth exceeded: {} (possible infinite loop)", self.recursion_depth));
return Err(format!(
"Recursion depth exceeded: {} (possible infinite loop)",
self.recursion_depth
));
}
let result = self.build_expression_impl(ast);
@ -424,7 +435,6 @@ impl MirBuilder {
result
}
/// Build a literal value
pub(super) fn build_literal(&mut self, literal: LiteralValue) -> Result<ValueId, String> {
// Determine type without moving literal
@ -438,9 +448,13 @@ impl MirBuilder {
// Emit via ConstantEmissionBox仕様不変の統一ルート
let dst = match literal {
LiteralValue::Integer(n) => crate::mir::builder::emission::constant::emit_integer(self, n),
LiteralValue::Integer(n) => {
crate::mir::builder::emission::constant::emit_integer(self, n)
}
LiteralValue::Float(f) => crate::mir::builder::emission::constant::emit_float(self, f),
LiteralValue::String(s) => crate::mir::builder::emission::constant::emit_string(self, s),
LiteralValue::String(s) => {
crate::mir::builder::emission::constant::emit_string(self, s)
}
LiteralValue::Bool(b) => crate::mir::builder::emission::constant::emit_bool(self, b),
LiteralValue::Null => crate::mir::builder::emission::constant::emit_null(self),
LiteralValue::Void => crate::mir::builder::emission::constant::emit_void(self),
@ -453,7 +467,6 @@ impl MirBuilder {
Ok(dst)
}
/// Build variable access
pub(super) fn build_variable_access(&mut self, name: String) -> Result<ValueId, String> {
// Step 5-5-G: __pin$ variables should NEVER be accessed from variable_map
@ -478,7 +491,8 @@ impl MirBuilder {
msg.push_str("\nHint: 'local' is a Stage-3 keyword. Enable NYASH_PARSER_STAGE3=1 (and HAKO_PARSER_STAGE3=1 for Stage-B).");
msg.push_str("\nFor AotPrep verification, use tools/hakorune_emit_mir.sh which sets these automatically.");
} else if (name == "flow" || name == "try" || name == "catch" || name == "throw")
&& !crate::config::env::parser_stage3() {
&& !crate::config::env::parser_stage3()
{
msg.push_str(&format!("\nHint: '{}' is a Stage-3 keyword. Enable NYASH_PARSER_STAGE3=1 (and HAKO_PARSER_STAGE3=1 for Stage-B).", name));
}
@ -486,7 +500,9 @@ impl MirBuilder {
if !suggest.is_empty() {
msg.push_str("\nHint: symbol appears in using module(s): ");
msg.push_str(&suggest.join(", "));
msg.push_str("\nConsider adding 'using <module> [as Alias]' or check nyash.toml [using].");
msg.push_str(
"\nConsider adding 'using <module> [as Alias]' or check nyash.toml [using].",
);
}
Err(msg)
}
@ -527,8 +543,6 @@ impl MirBuilder {
Ok(value_id)
}
/// Emit an instruction to the current basic block
pub(super) fn emit_instruction(&mut self, instruction: MirInstruction) -> Result<(), String> {
let block_id = self.current_block.ok_or("No current basic block")?;
@ -551,9 +565,23 @@ impl MirBuilder {
// CRITICAL: Final receiver materialization for MethodCall
// This ensures the receiver has an in-block definition in the same block as the Call.
// Must happen BEFORE function mutable borrow to avoid borrowck conflicts.
if let MirInstruction::Call { callee: Some(callee), dst, args, effects, .. } = &instruction {
if let MirInstruction::Call {
callee: Some(callee),
dst,
args,
effects,
..
} = &instruction
{
use crate::mir::definitions::call_unified::Callee;
if let Callee::Method { box_name, method, receiver: Some(r), certainty, box_kind } = callee.clone() {
if let Callee::Method {
box_name,
method,
receiver: Some(r),
certainty,
box_kind,
} = callee.clone()
{
// LocalSSA: ensure receiver has a Copy in current_block
let r_local = crate::mir::builder::ssa::local::recv(self, r);
@ -579,7 +607,9 @@ impl MirBuilder {
// Pre-capture branch/jump targets for predecessor update after we finish
// mutably borrowing the current block.
let (then_t, else_t, jump_t) = match &instruction {
MirInstruction::Branch { then_bb, else_bb, .. } => (Some(*then_bb), Some(*else_bb), None),
MirInstruction::Branch {
then_bb, else_bb, ..
} => (Some(*then_bb), Some(*else_bb), None),
MirInstruction::Jump { target } => (None, None, Some(*target)),
_ => (None, None, None),
};
@ -607,7 +637,13 @@ impl MirBuilder {
return Err("builder invariant violated: MirInstruction::Call.callee must be Some (unified call)".into());
} else if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
use crate::mir::definitions::call_unified::Callee;
if let Some(Callee::Method { box_name, method, receiver: Some(r), .. }) = callee {
if let Some(Callee::Method {
box_name,
method,
receiver: Some(r),
..
}) = callee
{
eprintln!(
"[emit-inst] fn={} bb={:?} Call {}.{} recv=%{}",
current_fn_name,
@ -617,9 +653,16 @@ impl MirBuilder {
r.0
);
}
} else if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1") {
} else if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1")
{
use crate::mir::definitions::call_unified::Callee;
if let Some(Callee::Method { box_name, method, receiver: Some(r), .. }) = callee {
if let Some(Callee::Method {
box_name,
method,
receiver: Some(r),
..
}) = callee
{
let names: Vec<String> = self
.variable_map
.iter()
@ -688,18 +731,24 @@ impl MirBuilder {
}
block.add_instruction(instruction);
// Drop the mutable borrow of `block` before updating other blocks
}
}
// Update predecessor sets for branch/jump immediately so that
// debug_verify_phi_inputs can observe a consistent CFG without
// requiring a full function.update_cfg() pass.
if let Some(t) = then_t {
if let Some(succ) = function.get_block_mut(t) { succ.add_predecessor(block_id); }
if let Some(succ) = function.get_block_mut(t) {
succ.add_predecessor(block_id);
}
}
if let Some(t) = else_t {
if let Some(succ) = function.get_block_mut(t) { succ.add_predecessor(block_id); }
if let Some(succ) = function.get_block_mut(t) {
succ.add_predecessor(block_id);
}
}
if let Some(t) = jump_t {
if let Some(succ) = function.get_block_mut(t) { succ.add_predecessor(block_id); }
if let Some(succ) = function.get_block_mut(t) {
succ.add_predecessor(block_id);
}
}
Ok(())
} else {
@ -726,7 +775,10 @@ impl MirBuilder {
}
}
}
Err(format!("PHI instruction {} not found in block {}", phi_id, block))
Err(format!(
"PHI instruction {} not found in block {}",
phi_id, block
))
} else {
Err(format!("Block {} not found", block))
}
@ -739,7 +791,6 @@ impl MirBuilder {
// フェーズM: insert_edge_copy()メソッド削除no_phi_mode撤廃により不要
/// Build new expression: new ClassName(arguments)
pub(super) fn build_new_expression(
&mut self,
@ -825,14 +876,15 @@ impl MirBuilder {
// 代替: 既存互換として BoxCall("birth")(プラグイン/ビルトインの初期化に対応)
if class != "StringBox" {
let arity = arg_values.len();
let lowered = crate::mir::builder::calls::function_lowering::generate_method_function_name(
&class,
"birth",
arity,
);
let lowered =
crate::mir::builder::calls::function_lowering::generate_method_function_name(
&class, "birth", arity,
);
let use_lowered = if let Some(ref module) = self.current_module {
module.functions.contains_key(&lowered)
} else { false };
} else {
false
};
if use_lowered {
// Call Global("Class.birth/Arity") with argv = [me, args...]
let mut argv: Vec<ValueId> = Vec::with_capacity(1 + arity);
@ -847,7 +899,10 @@ impl MirBuilder {
let is_user_box = self.user_defined_boxes.contains(&class);
// Dev safety: allow disabling birth() injection for builtins to avoid
// unified-call method dispatch issues while migrating. Off by default unless explicitly enabled.
let allow_builtin_birth = std::env::var("NYASH_DEV_BIRTH_INJECT_BUILTINS").ok().as_deref() == Some("1");
let allow_builtin_birth = std::env::var("NYASH_DEV_BIRTH_INJECT_BUILTINS")
.ok()
.as_deref()
== Some("1");
if !is_user_box && allow_builtin_birth {
let birt_mid = resolve_slot_by_type_name(&class, "birth");
self.emit_box_or_plugin_call(
@ -865,7 +920,6 @@ impl MirBuilder {
Ok(dst)
}
/// Check if the current basic block is terminated
fn is_current_block_terminated(&self) -> bool {
if let (Some(block_id), Some(ref function)) = (self.current_block, &self.current_function) {
@ -919,7 +973,6 @@ impl MirBuilder {
}
}
impl Default for MirBuilder {
fn default() -> Self {
Self::new()

View File

@ -39,7 +39,10 @@ pub fn suggest_resolution(name: &str) -> String {
"Consider using ArrayBox methods or array.* functions".to_string()
}
_ => {
format!("Function '{}' not found. Check spelling or add explicit scope qualifier", name)
format!(
"Function '{}' not found. Check spelling or add explicit scope qualifier",
name
)
}
}
}
@ -53,7 +56,7 @@ pub fn is_commonly_shadowed_method(method: &str) -> bool {
"print" | "error" | "log" | "panic" | // Console methods
"length" | "size" | "count" | // Container methods
"toString" | "valueOf" | // Conversion methods
"equals" | "compare" // Comparison methods
"equals" | "compare" // Comparison methods
)
}

View File

@ -36,9 +36,18 @@ pub(in super::super) fn annotate_call_result_from_func_name<S: AsRef<str>>(
builder.value_types.insert(dst, ret.clone());
if let MirType::Box(bx) = ret {
builder.value_origin_newbox.insert(dst, bx);
if super::super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
let bx = builder.value_origin_newbox.get(&dst).cloned().unwrap_or_default();
super::super::utils::builder_debug_log(&format!("annotate call dst={} from {} -> Box({})", dst.0, name, bx));
if super::super::utils::builder_debug_enabled()
|| std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1")
{
let bx = builder
.value_origin_newbox
.get(&dst)
.cloned()
.unwrap_or_default();
super::super::utils::builder_debug_log(&format!(
"annotate call dst={} from {} -> Box({})",
dst.0, name, bx
));
}
}
return;
@ -48,32 +57,60 @@ pub(in super::super) fn annotate_call_result_from_func_name<S: AsRef<str>>(
if name == "JsonParser.parse/1" {
let ret = MirType::Box("JsonNode".into());
builder.value_types.insert(dst, ret.clone());
if let MirType::Box(bx) = ret { builder.value_origin_newbox.insert(dst, bx); }
if super::super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
super::super::utils::builder_debug_log(&format!("annotate call (fallback) dst={} from {} -> Box(JsonNode)", dst.0, name));
if let MirType::Box(bx) = ret {
builder.value_origin_newbox.insert(dst, bx);
}
if super::super::utils::builder_debug_enabled()
|| std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1")
{
super::super::utils::builder_debug_log(&format!(
"annotate call (fallback) dst={} from {} -> Box(JsonNode)",
dst.0, name
));
}
} else if name == "JsonParser.current_token/0" {
let ret = MirType::Box("JsonToken".into());
builder.value_types.insert(dst, ret.clone());
if let MirType::Box(bx) = ret { builder.value_origin_newbox.insert(dst, bx); }
if super::super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
super::super::utils::builder_debug_log(&format!("annotate call (fallback) dst={} from {} -> Box(JsonToken)", dst.0, name));
if let MirType::Box(bx) = ret {
builder.value_origin_newbox.insert(dst, bx);
}
if super::super::utils::builder_debug_enabled()
|| std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1")
{
super::super::utils::builder_debug_log(&format!(
"annotate call (fallback) dst={} from {} -> Box(JsonToken)",
dst.0, name
));
}
} else if name == "JsonTokenizer.tokenize/0" {
// Tokenize returns an ArrayBox of tokens
let ret = MirType::Box("ArrayBox".into());
builder.value_types.insert(dst, ret.clone());
if let MirType::Box(bx) = ret { builder.value_origin_newbox.insert(dst, bx); }
if super::super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
super::super::utils::builder_debug_log(&format!("annotate call (fallback) dst={} from {} -> Box(ArrayBox)", dst.0, name));
if let MirType::Box(bx) = ret {
builder.value_origin_newbox.insert(dst, bx);
}
if super::super::utils::builder_debug_enabled()
|| std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1")
{
super::super::utils::builder_debug_log(&format!(
"annotate call (fallback) dst={} from {} -> Box(ArrayBox)",
dst.0, name
));
}
} else if name == "JsonParserModule.create_parser/0" {
// Fallback path for parser factory
let ret = MirType::Box("JsonParser".into());
builder.value_types.insert(dst, ret.clone());
if let MirType::Box(bx) = ret { builder.value_origin_newbox.insert(dst, bx); }
if super::super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
super::super::utils::builder_debug_log(&format!("annotate call (fallback) dst={} from {} -> Box(JsonParser)", dst.0, name));
if let MirType::Box(bx) = ret {
builder.value_origin_newbox.insert(dst, bx);
}
if super::super::utils::builder_debug_enabled()
|| std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1")
{
super::super::utils::builder_debug_log(&format!(
"annotate call (fallback) dst={} from {} -> Box(JsonParser)",
dst.0, name
));
}
} else {
// Generic tiny whitelist for known primitive-like utilities (spec unchanged)

View File

@ -5,11 +5,11 @@
//! - build_method_call: メソッド呼び出し構築
//! - build_from_expression: from式構築
use crate::ast::{ASTNode, LiteralValue};
use super::super::{Effect, EffectMask, MirBuilder, MirInstruction, MirType, ValueId};
use crate::mir::TypeOpKind;
use super::CallTarget;
use super::special_handlers;
use super::CallTarget;
use crate::ast::{ASTNode, LiteralValue};
use crate::mir::TypeOpKind;
impl MirBuilder {
// Build function call: name(args)
@ -20,7 +20,11 @@ impl MirBuilder {
) -> Result<ValueId, String> {
// Dev trace
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
let cur_fun = self.current_function.as_ref().map(|f| f.signature.name.clone()).unwrap_or_else(|| "<none>".to_string());
let cur_fun = self
.current_function
.as_ref()
.map(|f| f.signature.name.clone())
.unwrap_or_else(|| "<none>".to_string());
eprintln!(
"[builder] function-call name={} static_ctx={} in_fn={}",
name,
@ -71,10 +75,16 @@ impl MirBuilder {
const MAX_METHOD_DEPTH: usize = 100;
self.recursion_depth += 1;
if self.recursion_depth > MAX_METHOD_DEPTH {
eprintln!("[FATAL] build_method_call recursion depth exceeded {}", MAX_METHOD_DEPTH);
eprintln!(
"[FATAL] build_method_call recursion depth exceeded {}",
MAX_METHOD_DEPTH
);
eprintln!("[FATAL] Current depth: {}", self.recursion_depth);
eprintln!("[FATAL] Method: {}", method);
return Err(format!("build_method_call recursion depth exceeded: {}", self.recursion_depth));
return Err(format!(
"build_method_call recursion depth exceeded: {}",
self.recursion_depth
));
}
let result = self.build_method_call_impl(object, method, arguments);
@ -96,7 +106,10 @@ impl MirBuilder {
ASTNode::Me { .. } => "Me",
_ => "Other",
};
eprintln!("[builder] method-call object kind={} method={}", kind, method);
eprintln!(
"[builder] method-call object kind={} method={}",
kind, method
);
}
// 0. Dev-only: __mir__.log / __mir__.mark → MirInstruction::DebugLog へ直接 lowering
@ -110,7 +123,9 @@ impl MirBuilder {
// 1. Static box method call: BoxName.method(args)
if let ASTNode::Variable { name: obj_name, .. } = &object {
if let Some(result) = self.try_build_static_method_call(obj_name, &method, &arguments)? {
if let Some(result) =
self.try_build_static_method_call(obj_name, &method, &arguments)?
{
return Ok(result);
}
}
@ -211,40 +226,43 @@ impl MirBuilder {
let mut math_args: Vec<ValueId> = Vec::new();
for a in raw_args.into_iter() {
match a {
ASTNode::New { class, arguments, .. } if class == "FloatBox" && arguments.len() == 1 => {
ASTNode::New {
class, arguments, ..
} if class == "FloatBox" && arguments.len() == 1 => {
match self.build_expression(arguments[0].clone()) {
v @ Ok(_) => math_args.push(v.unwrap()),
err @ Err(_) => return Some(err),
}
}
ASTNode::New { class, arguments, .. } if class == "IntegerBox" && arguments.len() == 1 => {
ASTNode::New {
class, arguments, ..
} if class == "IntegerBox" && arguments.len() == 1 => {
let iv = match self.build_expression(arguments[0].clone()) {
Ok(v) => v,
Err(e) => return Some(Err(e))
Err(e) => return Some(Err(e)),
};
let fv = self.next_value_id();
if let Err(e) = self.emit_instruction(MirInstruction::TypeOp {
dst: fv,
op: TypeOpKind::Cast,
value: iv,
ty: MirType::Float
ty: MirType::Float,
}) {
return Some(Err(e));
}
math_args.push(fv);
}
ASTNode::Literal { value: LiteralValue::Float(_), .. } => {
match self.build_expression(a) {
v @ Ok(_) => math_args.push(v.unwrap()),
err @ Err(_) => return Some(err),
}
}
other => {
match self.build_expression(other) {
v @ Ok(_) => math_args.push(v.unwrap()),
err @ Err(_) => return Some(err),
}
}
ASTNode::Literal {
value: LiteralValue::Float(_),
..
} => match self.build_expression(a) {
v @ Ok(_) => math_args.push(v.unwrap()),
err @ Err(_) => return Some(err),
},
other => match self.build_expression(other) {
v @ Ok(_) => math_args.push(v.unwrap()),
err @ Err(_) => return Some(err),
},
}
}
// new MathBox()
@ -252,7 +270,8 @@ impl MirBuilder {
if let Err(e) = self.emit_constructor_call(math_recv, "MathBox".to_string(), vec![]) {
return Some(Err(e));
}
self.value_origin_newbox.insert(math_recv, "MathBox".to_string());
self.value_origin_newbox
.insert(math_recv, "MathBox".to_string());
// birth()
if let Err(e) = self.emit_method_call(None, math_recv, "birth".to_string(), vec![]) {
return Some(Err(e));
@ -272,29 +291,40 @@ impl MirBuilder {
method: &str,
arguments: &Vec<ASTNode>,
) -> Option<Result<ValueId, String>> {
let ASTNode::FieldAccess { object: env_obj, field: env_field, .. } = object else {
let ASTNode::FieldAccess {
object: env_obj,
field: env_field,
..
} = object
else {
return None;
};
if let ASTNode::Variable { name: env_name, .. } = env_obj.as_ref() {
if env_name != "env" { return None; }
if env_name != "env" {
return None;
}
// Build arguments once
let mut arg_values = Vec::new();
for arg in arguments {
match self.build_expression(arg.clone()) {
Ok(v) => arg_values.push(v),
Err(e) => return Some(Err(e))
Err(e) => return Some(Err(e)),
}
}
let iface = env_field.as_str();
let m = method;
let mut extern_call = |iface_name: &str, method_name: &str, effects: EffectMask, returns: bool| -> Result<ValueId, String> {
let mut extern_call = |iface_name: &str,
method_name: &str,
effects: EffectMask,
returns: bool|
-> Result<ValueId, String> {
let result_id = self.next_value_id();
self.emit_instruction(MirInstruction::ExternCall {
dst: if returns { Some(result_id) } else { None },
iface_name: iface_name.to_string(),
method_name: method_name.to_string(),
args: arg_values.clone(),
effects
effects,
})?;
if returns {
Ok(result_id)
@ -351,7 +381,8 @@ impl MirBuilder {
if let Some(result) = self.try_tail_based_fallback(&name, &arg_values)? {
return Ok(result);
}
return Err(format!("Unresolved function: '{}'. {}",
return Err(format!(
"Unresolved function: '{}'. {}",
name,
super::super::call_resolution::suggest_resolution(&name)
));
@ -379,11 +410,7 @@ impl MirBuilder {
arg_values: Vec<ValueId>,
) -> Result<ValueId, String> {
let dst = self.next_value_id();
self.emit_unified_call(
Some(dst),
CallTarget::Global(name),
arg_values,
)?;
self.emit_unified_call(Some(dst), CallTarget::Global(name), arg_values)?;
Ok(dst)
}
@ -398,11 +425,17 @@ impl MirBuilder {
// Debug trace
if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
eprintln!("[DEBUG] try_build_static_method_call: obj_name={}, method={}", obj_name, method);
eprintln!(
"[DEBUG] try_build_static_method_call: obj_name={}, method={}",
obj_name, method
);
eprintln!("[DEBUG] is_local_var={}", is_local_var);
if is_local_var {
eprintln!("[DEBUG] variable_map contains '{}' - treating as local variable, will use method call", obj_name);
eprintln!("[DEBUG] variable_map keys: {:?}", self.variable_map.keys().collect::<Vec<_>>());
eprintln!(
"[DEBUG] variable_map keys: {:?}",
self.variable_map.keys().collect::<Vec<_>>()
);
} else {
eprintln!("[DEBUG] '{}' not in variable_map - treating as static box, will use global call", obj_name);
}
@ -488,7 +521,7 @@ impl MirBuilder {
self.emit_unified_call(
Some(dst),
CallTarget::Global(func_name),
arg_values.to_vec()
arg_values.to_vec(),
)?;
return Ok(Some(dst));
}
@ -517,7 +550,7 @@ impl MirBuilder {
self.emit_legacy_call(
Some(dst),
CallTarget::Global(func_name),
arg_values.to_vec()
arg_values.to_vec(),
)?;
return Ok(Some(dst));
}
@ -530,20 +563,31 @@ impl MirBuilder {
fn trace_receiver_if_enabled(&self, object: &ASTNode, object_value: ValueId) {
if std::env::var("NYASH_DEBUG_PARAM_RECEIVER").ok().as_deref() == Some("1") {
if let ASTNode::Variable { name, .. } = object {
eprintln!("[DEBUG/param-recv] build_method_call receiver '{}' → ValueId({})",
name, object_value.0);
eprintln!(
"[DEBUG/param-recv] build_method_call receiver '{}' → ValueId({})",
name, object_value.0
);
if let Some(origin) = self.value_origin_newbox.get(&object_value) {
eprintln!("[DEBUG/param-recv] origin: {}", origin);
}
if let Some(&mapped_id) = self.variable_map.get(name) {
eprintln!("[DEBUG/param-recv] variable_map['{}'] = ValueId({})", name, mapped_id.0);
eprintln!(
"[DEBUG/param-recv] variable_map['{}'] = ValueId({})",
name, mapped_id.0
);
if mapped_id != object_value {
eprintln!("[DEBUG/param-recv] ⚠️ MISMATCH! build_expression returned different ValueId!");
}
} else {
eprintln!("[DEBUG/param-recv] ⚠️ '{}' NOT FOUND in variable_map!", name);
eprintln!(
"[DEBUG/param-recv] ⚠️ '{}' NOT FOUND in variable_map!",
name
);
}
eprintln!("[DEBUG/param-recv] current_block: {:?}", self.current_block);
eprintln!(
"[DEBUG/param-recv] current_block: {:?}",
self.current_block
);
}
}
}

View File

@ -16,7 +16,7 @@ pub enum CallTarget {
/// Method call (box.method)
Method {
box_type: Option<String>, // None = infer from value
box_type: Option<String>, // None = infer from value
method: String,
receiver: ValueId,
},
@ -55,4 +55,4 @@ impl CallTarget {
CallTarget::Closure { .. } => "<closure>".to_string(),
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More