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:
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
113
src/ast/nodes.rs
113
src/ast/nodes.rs
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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(());
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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" => {
|
||||
|
||||
@ -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 UTF‑8, 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),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -12,4 +12,3 @@ pub fn normalize_arity_suffix(name: &str) -> &str {
|
||||
None => name,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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?
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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(×)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 =====
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -137,4 +137,4 @@ impl Display for AddBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.fmt_box(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,4 +76,4 @@ impl CompareBox {
|
||||
let right_str = right.to_string_box();
|
||||
BoolBox::new(left_str.value >= right_str.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,4 +124,4 @@ impl Display for DivideBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.fmt_box(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,4 +115,4 @@ impl Display for MultiplyBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.fmt_box(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,4 +116,4 @@ impl Display for SubtractBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.fmt_box(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 (Gate‑C 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();
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -3,10 +3,10 @@
|
||||
// 参考: 既存Boxの設計思想
|
||||
|
||||
// SSOT provider design (ring‑0/1) — modules are currently placeholders
|
||||
pub mod provider; // trait FileIo / FileCaps / FileError
|
||||
pub mod core_ro; // Core read‑only 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 read‑only 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",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
141
src/cli/args.rs
141
src/cli/args.rs
@ -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" => {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -6,4 +6,3 @@ pub fn parse_debug_fuel(value: &str) -> Option<usize> {
|
||||
value.parse::<usize>().ok()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
/// Gate‑C(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;
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
pub mod log;
|
||||
pub mod hub;
|
||||
pub mod log;
|
||||
|
||||
@ -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"];
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
pub mod mir_builder;
|
||||
pub mod llvm_codegen;
|
||||
|
||||
pub mod mir_builder;
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
16
src/lib.rs
16
src/lib.rs
@ -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;
|
||||
|
||||
// C‑ABI 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;
|
||||
|
||||
|
||||
@ -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,
|
||||
})
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
618
src/macro/mod.rs
618
src/macro/mod.rs
@ -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()
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 inference(me/Known)と PHI 伝播(軽量)
|
||||
mod observe; // P0: dev-only observability helpers(ssa/resolve)
|
||||
mod rewrite; // P1: Known rewrite & special consolidation
|
||||
mod ssa; // LocalSSA helpers (in-block materialization)
|
||||
mod schedule; // BlockScheduleBox(物理順序: PHI→materialize→body)
|
||||
mod receiver; // ReceiverMaterializationBox(Method recv の pin+LocalSSA 集約)
|
||||
mod metadata; // MetadataPropagationBox(type/originの伝播)
|
||||
mod emission; // emission::*(Const/Compare/Branch の薄い発行箱)
|
||||
mod types; // types::annotation / inference(型注釈/推論の箱: 推論は後段)
|
||||
mod router; // RouterPolicyBox(Unified 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; // EmitGuardBox(emit直前の最終関所)
|
||||
mod metadata; // MetadataPropagationBox(type/originの伝播)
|
||||
mod name_const; // NameConstBox(関数名Const生成)
|
||||
pub(crate) mod type_registry; // TypeRegistryBox(型情報管理の一元化)
|
||||
mod observe; // P0: dev-only observability helpers(ssa/resolve)
|
||||
mod origin; // P0: origin inference(me/Known)と PHI 伝播(軽量)
|
||||
mod plugin_sigs; // plugin signature loader
|
||||
mod receiver; // ReceiverMaterializationBox(Method recv の pin+LocalSSA 集約)
|
||||
mod rewrite; // P1: Known rewrite & special consolidation
|
||||
mod router; // RouterPolicyBox(Unified 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()
|
||||
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
Reference in New Issue
Block a user