Phase 12.7: Nyash文法革命とANCP 90%圧縮技法の発見 - 文法改革完了とFunctionBox実装
This commit is contained in:
34
src/ast.rs
34
src/ast.rs
@ -447,6 +447,12 @@ pub enum ASTNode {
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// result伝播: expr? (ResultBoxなら isOk/getValue or 早期return)
|
||||
QMarkPropagate {
|
||||
expression: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// peek式: peek <expr> { lit => expr, ... else => expr }
|
||||
PeekExpr {
|
||||
scrutinee: Box<ASTNode>,
|
||||
@ -455,6 +461,13 @@ pub enum ASTNode {
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// 無名関数(最小P1: 値としてのみ。呼び出しは未対応)
|
||||
Lambda {
|
||||
params: Vec<String>,
|
||||
body: Vec<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// arrow文: (sender >> receiver).method(args)
|
||||
Arrow {
|
||||
sender: Box<ASTNode>,
|
||||
@ -628,6 +641,13 @@ pub enum ASTNode {
|
||||
arguments: Vec<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// 一般式呼び出し: (callee)(arguments)
|
||||
Call {
|
||||
callee: Box<ASTNode>,
|
||||
arguments: Vec<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
}
|
||||
|
||||
impl ASTNode {
|
||||
@ -662,12 +682,15 @@ impl ASTNode {
|
||||
ASTNode::Local { .. } => "Local",
|
||||
ASTNode::Outbox { .. } => "Outbox",
|
||||
ASTNode::FunctionCall { .. } => "FunctionCall",
|
||||
ASTNode::Call { .. } => "Call",
|
||||
ASTNode::Nowait { .. } => "Nowait",
|
||||
ASTNode::Arrow { .. } => "Arrow",
|
||||
ASTNode::TryCatch { .. } => "TryCatch",
|
||||
ASTNode::Throw { .. } => "Throw",
|
||||
ASTNode::AwaitExpression { .. } => "AwaitExpression",
|
||||
ASTNode::QMarkPropagate { .. } => "QMarkPropagate",
|
||||
ASTNode::PeekExpr { .. } => "PeekExpr",
|
||||
ASTNode::Lambda { .. } => "Lambda",
|
||||
}
|
||||
}
|
||||
|
||||
@ -688,6 +711,7 @@ impl ASTNode {
|
||||
ASTNode::BinaryOp { .. } => ASTNodeType::Expression,
|
||||
ASTNode::UnaryOp { .. } => ASTNodeType::Expression,
|
||||
ASTNode::FunctionCall { .. } => ASTNodeType::Expression,
|
||||
ASTNode::Call { .. } => ASTNodeType::Expression,
|
||||
ASTNode::MethodCall { .. } => ASTNodeType::Expression,
|
||||
ASTNode::FieldAccess { .. } => ASTNodeType::Expression,
|
||||
ASTNode::New { .. } => ASTNodeType::Expression,
|
||||
@ -697,6 +721,8 @@ impl ASTNode {
|
||||
ASTNode::ThisField { .. } => ASTNodeType::Expression,
|
||||
ASTNode::MeField { .. } => ASTNodeType::Expression,
|
||||
ASTNode::PeekExpr { .. } => ASTNodeType::Expression,
|
||||
ASTNode::QMarkPropagate { .. } => ASTNodeType::Expression,
|
||||
ASTNode::Lambda { .. } => ASTNodeType::Expression,
|
||||
|
||||
// Statement nodes - 実行可能なアクション
|
||||
ASTNode::Program { .. } => ASTNodeType::Statement, // プログラム全体
|
||||
@ -831,6 +857,7 @@ impl ASTNode {
|
||||
ASTNode::FunctionCall { name, arguments, .. } => {
|
||||
format!("FunctionCall({}, {} args)", name, arguments.len())
|
||||
}
|
||||
ASTNode::Call { .. } => "Call".to_string(),
|
||||
ASTNode::Nowait { variable, .. } => {
|
||||
format!("Nowait({})", variable)
|
||||
}
|
||||
@ -851,6 +878,10 @@ impl ASTNode {
|
||||
format!("Await({:?})", expression)
|
||||
}
|
||||
ASTNode::PeekExpr { .. } => "PeekExpr".to_string(),
|
||||
ASTNode::QMarkPropagate { .. } => "QMarkPropagate".to_string(),
|
||||
ASTNode::Lambda { params, body, .. } => {
|
||||
format!("Lambda({} params, {} statements)", params.len(), body.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -889,8 +920,11 @@ impl ASTNode {
|
||||
ASTNode::Local { span, .. } => *span,
|
||||
ASTNode::Outbox { span, .. } => *span,
|
||||
ASTNode::FunctionCall { span, .. } => *span,
|
||||
ASTNode::Call { span, .. } => *span,
|
||||
ASTNode::AwaitExpression { span, .. } => *span,
|
||||
ASTNode::PeekExpr { span, .. } => *span,
|
||||
ASTNode::QMarkPropagate { span, .. } => *span,
|
||||
ASTNode::Lambda { span, .. } => *span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
69
src/boxes/function_box.rs
Normal file
69
src/boxes/function_box.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox, BoxCore, BoxBase};
|
||||
use crate::ast::ASTNode;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::any::Any;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClosureEnv {
|
||||
pub me_value: Option<Weak<dyn NyashBox>>, // Weak me (upgrade at call)
|
||||
pub captures: HashMap<String, Box<dyn NyashBox>>, // P1: by-value captures
|
||||
}
|
||||
|
||||
impl ClosureEnv {
|
||||
pub fn new() -> Self { Self { me_value: None, captures: HashMap::new() } }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionBox {
|
||||
pub params: Vec<String>,
|
||||
pub body: Vec<ASTNode>,
|
||||
pub env: ClosureEnv,
|
||||
base: BoxBase,
|
||||
}
|
||||
|
||||
impl FunctionBox {
|
||||
pub fn new(params: Vec<String>, body: Vec<ASTNode>) -> Self {
|
||||
Self { params, body, env: ClosureEnv::new(), base: BoxBase::new() }
|
||||
}
|
||||
pub fn with_env(params: Vec<String>, body: Vec<ASTNode>, env: ClosureEnv) -> Self {
|
||||
Self { params, body, env, base: BoxBase::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxCore for FunctionBox {
|
||||
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 {
|
||||
write!(f, "FunctionBox(params={}, body={})", self.params.len(), self.body.len())
|
||||
}
|
||||
fn as_any(&self) -> &dyn Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any { self }
|
||||
}
|
||||
|
||||
impl NyashBox for FunctionBox {
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
||||
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
|
||||
fn to_string_box(&self) -> StringBox { StringBox::new(format!("FunctionBox(params={}, captures={}, body={})", self.params.len(), self.env.captures.len(), self.body.len())) }
|
||||
fn type_name(&self) -> &'static str { "FunctionBox" }
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(o) = other.as_any().downcast_ref::<FunctionBox>() {
|
||||
BoolBox::new(self.box_id() == o.box_id())
|
||||
} else { BoolBox::new(false) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ClosureEnv {
|
||||
fn clone(&self) -> Self {
|
||||
let me_value = self.me_value.as_ref().map(|w| Weak::clone(w));
|
||||
let mut captures: HashMap<String, Box<dyn NyashBox>> = HashMap::new();
|
||||
for (k, v) in &self.captures { captures.insert(k.clone(), v.clone_box()); }
|
||||
Self { me_value, captures }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for FunctionBox {
|
||||
fn clone(&self) -> Self {
|
||||
Self { params: self.params.clone(), body: self.body.clone(), env: self.env.clone(), base: BoxBase::new() }
|
||||
}
|
||||
}
|
||||
@ -86,6 +86,8 @@ pub mod aot_config_box;
|
||||
pub mod aot_compiler_box;
|
||||
pub mod task_group_box;
|
||||
pub mod token_box;
|
||||
pub mod function_box;
|
||||
pub mod ref_cell_box;
|
||||
|
||||
// Web専用Box群(ブラウザ環境でのみ利用可能)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
||||
@ -246,6 +246,7 @@ impl P2PBox {
|
||||
|
||||
// 可能ならTransportにハンドラ登録(InProcessなど)
|
||||
if let Ok(mut t) = self.transport.write() {
|
||||
// MethodBox ハンドラー
|
||||
if let Some(method_box) = handler.as_any().downcast_ref::<MethodBox>() {
|
||||
let method_clone = method_box.clone();
|
||||
let intent_name = intent_str.to_string();
|
||||
@ -253,18 +254,54 @@ impl P2PBox {
|
||||
let last_from = Arc::clone(&self.last_from);
|
||||
let last_intent = Arc::clone(&self.last_intent_name);
|
||||
t.register_intent_handler(&intent_name, Box::new(move |env| {
|
||||
// flagがtrueのときのみ実行
|
||||
if flag.load(Ordering::SeqCst) {
|
||||
// Update receive-side traces for E2E visibility
|
||||
if let Ok(mut lf) = last_from.write() { *lf = Some(env.from.clone()); }
|
||||
if let Ok(mut li) = last_intent.write() { *li = Some(env.intent.get_name().to_string_box().value); }
|
||||
let _ = method_clone.invoke(vec![
|
||||
Box::new(env.intent.clone()),
|
||||
Box::new(StringBox::new(env.from.clone())),
|
||||
]);
|
||||
if once {
|
||||
flag.store(false, Ordering::SeqCst);
|
||||
if once { flag.store(false, Ordering::SeqCst); }
|
||||
}
|
||||
}));
|
||||
// FunctionBox ハンドラー(関数値)
|
||||
} else if let Some(func_box) = handler.as_any().downcast_ref::<crate::boxes::function_box::FunctionBox>() {
|
||||
let func_clone = func_box.clone();
|
||||
let intent_name = intent_str.to_string();
|
||||
let last_from = Arc::clone(&self.last_from);
|
||||
let last_intent = Arc::clone(&self.last_intent_name);
|
||||
t.register_intent_handler(&intent_name, Box::new(move |env| {
|
||||
if flag.load(Ordering::SeqCst) {
|
||||
if let Ok(mut lf) = last_from.write() { *lf = Some(env.from.clone()); }
|
||||
if let Ok(mut li) = last_intent.write() { *li = Some(env.intent.get_name().to_string_box().value); }
|
||||
// 最小インタープリタで FunctionBox を実行
|
||||
let mut interp = crate::interpreter::NyashInterpreter::new();
|
||||
// キャプチャ注入
|
||||
for (k, v) in func_clone.env.captures.iter() {
|
||||
interp.declare_local_variable(k, v.clone_or_share());
|
||||
}
|
||||
if let Some(me_w) = &func_clone.env.me_value {
|
||||
if let Some(me_arc) = me_w.upgrade() {
|
||||
interp.declare_local_variable("me", (*me_arc).clone_or_share());
|
||||
}
|
||||
}
|
||||
// 引数束縛: intent, from(必要数だけ)
|
||||
let args: Vec<Box<dyn NyashBox>> = vec![
|
||||
Box::new(env.intent.clone()),
|
||||
Box::new(StringBox::new(env.from.clone())),
|
||||
];
|
||||
for (i, p) in func_clone.params.iter().enumerate() {
|
||||
if let Some(av) = args.get(i) {
|
||||
interp.declare_local_variable(p, av.clone_or_share());
|
||||
}
|
||||
}
|
||||
// 本体実行
|
||||
crate::runtime::global_hooks::push_task_scope();
|
||||
for st in &func_clone.body {
|
||||
let _ = interp.execute_statement(st);
|
||||
}
|
||||
crate::runtime::global_hooks::pop_task_scope();
|
||||
if once { flag.store(false, Ordering::SeqCst); }
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
52
src/boxes/ref_cell_box.rs
Normal file
52
src/boxes/ref_cell_box.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox, BoxCore, BoxBase};
|
||||
use std::any::Any;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RefCellBox {
|
||||
inner: Arc<Mutex<Box<dyn NyashBox>>>,
|
||||
base: BoxBase,
|
||||
}
|
||||
|
||||
impl RefCellBox {
|
||||
pub fn new(initial: Box<dyn NyashBox>) -> Self {
|
||||
Self { inner: Arc::new(Mutex::new(initial)), base: BoxBase::new() }
|
||||
}
|
||||
pub fn with_inner(inner: Arc<Mutex<Box<dyn NyashBox>>>) -> Self {
|
||||
Self { inner, base: BoxBase::new() }
|
||||
}
|
||||
pub fn borrow(&self) -> Box<dyn NyashBox> {
|
||||
self.inner.lock().unwrap().clone_box()
|
||||
}
|
||||
pub fn replace(&self, value: Box<dyn NyashBox>) {
|
||||
let mut guard = self.inner.lock().unwrap();
|
||||
*guard = value;
|
||||
}
|
||||
pub fn inner_arc(&self) -> Arc<Mutex<Box<dyn NyashBox>>> { Arc::clone(&self.inner) }
|
||||
}
|
||||
|
||||
impl BoxCore for RefCellBox {
|
||||
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 {
|
||||
write!(f, "RefCellBox(..)")
|
||||
}
|
||||
fn as_any(&self) -> &dyn Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any { self }
|
||||
}
|
||||
|
||||
impl NyashBox for RefCellBox {
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(Self::with_inner(self.inner_arc())) }
|
||||
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
StringBox::new(format!("RefCell({})", inner.to_string_box().value))
|
||||
}
|
||||
fn type_name(&self) -> &'static str { "RefCellBox" }
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(o) = other.as_any().downcast_ref::<RefCellBox>() {
|
||||
BoolBox::new(Arc::ptr_eq(&self.inner, &o.inner))
|
||||
} else { BoolBox::new(false) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -243,7 +243,7 @@ impl NyashInterpreter {
|
||||
}
|
||||
|
||||
/// 🔥 厳密変数設定: 明示的宣言のみ許可 - Everything is Box哲学
|
||||
pub(super) fn set_variable(&mut self, name: &str, value: Box<dyn NyashBox>) -> Result<(), RuntimeError> {
|
||||
pub(crate) fn set_variable(&mut self, name: &str, value: Box<dyn NyashBox>) -> Result<(), RuntimeError> {
|
||||
let shared_value = Arc::from(value); // Convert Box to Arc
|
||||
|
||||
// 1. outbox変数が存在する場合は更新
|
||||
@ -278,7 +278,7 @@ impl NyashInterpreter {
|
||||
}
|
||||
|
||||
/// local変数を宣言(関数内でのみ有効)
|
||||
pub(super) fn declare_local_variable(&mut self, name: &str, value: Box<dyn NyashBox>) {
|
||||
pub(crate) fn declare_local_variable(&mut self, name: &str, value: Box<dyn NyashBox>) {
|
||||
// Pass-by-share for plugin handle types; by-value (clone) semantics can be applied at call sites
|
||||
#[allow(unused_mut)]
|
||||
let mut store_value = value;
|
||||
@ -292,7 +292,7 @@ impl NyashInterpreter {
|
||||
}
|
||||
|
||||
/// outbox変数を宣言(static関数内で所有権移転)
|
||||
pub(super) fn declare_outbox_variable(&mut self, name: &str, value: Box<dyn NyashBox>) {
|
||||
pub(crate) fn declare_outbox_variable(&mut self, name: &str, value: Box<dyn NyashBox>) {
|
||||
#[allow(unused_mut)]
|
||||
let mut store_value = value;
|
||||
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
|
||||
@ -13,15 +13,113 @@ mod access;
|
||||
mod builtins;
|
||||
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
// Direct implementation approach to avoid import issues
|
||||
|
||||
// TODO: Fix NullBox import issue later
|
||||
// use crate::NullBox;
|
||||
|
||||
impl NyashInterpreter {
|
||||
/// Build closure environment by capturing 'me' and free variables by value (P1)
|
||||
fn build_closure_env(&mut self, params: &Vec<String>, body: &Vec<ASTNode>) -> Result<crate::boxes::function_box::ClosureEnv, RuntimeError> {
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
let mut env = crate::boxes::function_box::ClosureEnv::new();
|
||||
// Capture 'me' if bound
|
||||
if let Ok(mev) = self.resolve_variable("me") { env.me_value = Some(Arc::downgrade(&mev)); }
|
||||
|
||||
// Collect free variables
|
||||
let mut used: HashSet<String> = HashSet::new();
|
||||
let mut locals: HashSet<String> = HashSet::new();
|
||||
// params are considered local
|
||||
for p in params { locals.insert(p.clone()); }
|
||||
// BFS walk statements
|
||||
fn collect(node: &ASTNode, used: &mut HashSet<String>, locals: &mut HashSet<String>) {
|
||||
match node {
|
||||
ASTNode::Variable { name, .. } => {
|
||||
if !locals.contains(name) && name != "me" && name != "this" { used.insert(name.clone()); }
|
||||
}
|
||||
ASTNode::Local { variables, .. } => { for v in variables { locals.insert(v.clone()); } }
|
||||
ASTNode::Assignment { target, value, .. } => { collect(target, used, locals); collect(value, used, locals); }
|
||||
ASTNode::BinaryOp { left, right, .. } => { collect(left, used, locals); collect(right, used, locals); }
|
||||
ASTNode::UnaryOp { operand, .. } => { collect(operand, used, locals); }
|
||||
ASTNode::MethodCall { object, arguments, .. } => { collect(object, used, locals); for a in arguments { collect(a, used, locals);} }
|
||||
ASTNode::FunctionCall { arguments, .. } => { for a in arguments { collect(a, used, locals);} }
|
||||
ASTNode::Call { callee, arguments, .. } => { collect(callee, used, locals); for a in arguments { collect(a, used, locals);} }
|
||||
ASTNode::FieldAccess { object, .. } => { collect(object, used, locals); }
|
||||
ASTNode::New { arguments, .. } => { for a in arguments { collect(a, used, locals);} }
|
||||
ASTNode::If { condition, then_body, else_body, .. } => {
|
||||
collect(condition, used, locals);
|
||||
for st in then_body { collect(st, used, locals); }
|
||||
if let Some(eb) = else_body { for st in eb { collect(st, used, locals); } }
|
||||
}
|
||||
ASTNode::Loop { condition, body, .. } => { collect(condition, used, locals); for st in body { collect(st, used, locals);} }
|
||||
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
|
||||
for st in try_body { collect(st, used, locals); }
|
||||
for c in catch_clauses { for st in &c.body { collect(st, used, locals); } }
|
||||
if let Some(fb) = finally_body { for st in fb { collect(st, used, locals); } }
|
||||
}
|
||||
ASTNode::Throw { expression, .. } => { collect(expression, used, locals); }
|
||||
ASTNode::Print { expression, .. } => { collect(expression, used, locals); }
|
||||
ASTNode::Return { value, .. } => { if let Some(v) = value { collect(v, used, locals); } }
|
||||
ASTNode::AwaitExpression { expression, .. } => { collect(expression, used, locals); }
|
||||
ASTNode::PeekExpr { scrutinee, arms, else_expr, .. } => {
|
||||
collect(scrutinee, used, locals);
|
||||
for (_, e) in arms { collect(e, used, locals); }
|
||||
collect(else_expr, used, locals);
|
||||
}
|
||||
ASTNode::Program { statements, .. } => { for st in statements { collect(st, used, locals); } }
|
||||
ASTNode::FunctionDeclaration { params, body, .. } => {
|
||||
let mut inner = locals.clone();
|
||||
for p in params { inner.insert(p.clone()); }
|
||||
for st in body { collect(st, used, &mut inner); }
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
for st in body { collect(st, &mut used, &mut locals); }
|
||||
|
||||
// Materialize captures: local by-ref via RefCellBox, others by-value
|
||||
for name in used.into_iter() {
|
||||
if let Some(local_arc) = self.local_vars.get(&name) {
|
||||
let lb: &dyn NyashBox = &**local_arc;
|
||||
// If already RefCellBox, reuse inner; else wrap and replace local binding
|
||||
if let Some(rc) = lb.as_any().downcast_ref::<crate::boxes::ref_cell_box::RefCellBox>() {
|
||||
env.captures.insert(name.clone(), rc.share_box());
|
||||
} else {
|
||||
// wrap existing into RefCell and replace local binding
|
||||
let wrapped = crate::boxes::ref_cell_box::RefCellBox::new(lb.clone_box());
|
||||
self.local_vars.insert(name.clone(), wrapped.clone_arc());
|
||||
env.captures.insert(name, wrapped.share_box());
|
||||
}
|
||||
} else {
|
||||
// non-local (global/static): by-value capture
|
||||
if let Ok(v) = self.resolve_variable(&name) { env.captures.insert(name, v.clone_or_share()); }
|
||||
}
|
||||
}
|
||||
Ok(env)
|
||||
}
|
||||
/// 式を実行 - Expression evaluation engine
|
||||
pub(super) fn execute_expression(&mut self, expression: &ASTNode) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
match expression {
|
||||
// P1: allow block (Program) as expression; value = last statement's value
|
||||
ASTNode::Program { statements, .. } => {
|
||||
let mut result: Box<dyn NyashBox> = Box::new(VoidBox::new());
|
||||
let last = statements.len().saturating_sub(1);
|
||||
for (i, st) in statements.iter().enumerate() {
|
||||
let prev = self.discard_context;
|
||||
self.discard_context = i != last;
|
||||
result = self.execute_statement(st)?;
|
||||
self.discard_context = prev;
|
||||
match &self.control_flow {
|
||||
ControlFlow::Break => { return Err(RuntimeError::BreakOutsideLoop); }
|
||||
ControlFlow::Continue => { return Err(RuntimeError::BreakOutsideLoop); }
|
||||
ControlFlow::Return(_) => { return Err(RuntimeError::ReturnOutsideFunction); }
|
||||
ControlFlow::Throw(_) => { return Err(RuntimeError::UncaughtException); }
|
||||
ControlFlow::None => {}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
ASTNode::Literal { value, .. } => {
|
||||
Ok(value.to_nyash_box())
|
||||
}
|
||||
@ -125,10 +223,84 @@ impl NyashInterpreter {
|
||||
ASTNode::FunctionCall { name, arguments, .. } => {
|
||||
self.execute_function_call(name, arguments)
|
||||
}
|
||||
ASTNode::Call { callee, arguments, .. } => {
|
||||
// callee を評価して FunctionBox なら本体を実行
|
||||
let callee_val = self.execute_expression(callee)?;
|
||||
if let Some(fun) = callee_val.as_any().downcast_ref::<crate::boxes::function_box::FunctionBox>() {
|
||||
// 引数評価
|
||||
let mut arg_values: Vec<Box<dyn NyashBox>> = Vec::new();
|
||||
for a in arguments { arg_values.push(self.execute_expression(a)?); }
|
||||
if arg_values.len() != fun.params.len() {
|
||||
return Err(RuntimeError::InvalidOperation { message: format!("Function expects {} args, got {}", fun.params.len(), arg_values.len()) });
|
||||
}
|
||||
// スコープ保存
|
||||
let saved_locals = self.save_local_vars();
|
||||
self.local_vars.clear();
|
||||
// キャプチャ注入(by-value)
|
||||
for (k, v) in fun.env.captures.iter() { self.declare_local_variable(k, v.clone_or_share()); }
|
||||
if let Some(me_w) = &fun.env.me_value {
|
||||
if let Some(me_arc) = me_w.upgrade() {
|
||||
self.declare_local_variable("me", (*me_arc).clone_or_share());
|
||||
} else {
|
||||
self.declare_local_variable("me", Box::new(crate::boxes::null_box::NullBox::new()));
|
||||
}
|
||||
}
|
||||
for (p, v) in fun.params.iter().zip(arg_values.iter()) {
|
||||
self.declare_local_variable(p, v.clone_or_share());
|
||||
}
|
||||
// 実行
|
||||
crate::runtime::global_hooks::push_task_scope();
|
||||
let mut result: Box<dyn NyashBox> = Box::new(VoidBox::new());
|
||||
for st in &fun.body {
|
||||
result = self.execute_statement(st)?;
|
||||
if let super::ControlFlow::Return(rv) = &self.control_flow {
|
||||
result = rv.clone_box();
|
||||
self.control_flow = super::ControlFlow::None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
crate::runtime::global_hooks::pop_task_scope();
|
||||
self.restore_local_vars(saved_locals);
|
||||
Ok(result)
|
||||
} else if let ASTNode::Lambda { params, body, .. } = callee.as_ref() {
|
||||
// 直書きLambdaは従来通り実行(後方互換)
|
||||
let mut arg_values: Vec<Box<dyn NyashBox>> = Vec::new();
|
||||
for a in arguments { arg_values.push(self.execute_expression(a)?); }
|
||||
if arg_values.len() != params.len() {
|
||||
return Err(RuntimeError::InvalidOperation { message: format!("Lambda expects {} args, got {}", params.len(), arg_values.len()) });
|
||||
}
|
||||
let saved_locals = self.save_local_vars();
|
||||
self.local_vars.clear();
|
||||
for (p, v) in params.iter().zip(arg_values.iter()) { self.declare_local_variable(p, v.clone_or_share()); }
|
||||
crate::runtime::global_hooks::push_task_scope();
|
||||
let mut result: Box<dyn NyashBox> = Box::new(VoidBox::new());
|
||||
for st in body { result = self.execute_statement(st)?; if let super::ControlFlow::Return(rv) = &self.control_flow { result = rv.clone_box(); self.control_flow = super::ControlFlow::None; break; } }
|
||||
crate::runtime::global_hooks::pop_task_scope();
|
||||
self.restore_local_vars(saved_locals);
|
||||
Ok(result)
|
||||
} else {
|
||||
Err(RuntimeError::InvalidOperation { message: "Callee is not callable".to_string() })
|
||||
}
|
||||
}
|
||||
|
||||
ASTNode::Arrow { sender, receiver, .. } => {
|
||||
self.execute_arrow(sender, receiver)
|
||||
}
|
||||
ASTNode::QMarkPropagate { expression, .. } => {
|
||||
let v = self.execute_expression(expression)?;
|
||||
if let Some(res) = v.as_any().downcast_ref::<crate::boxes::result::NyashResultBox>() {
|
||||
// ok -> unwrap, err -> early return (propagate)
|
||||
if matches!(res, crate::boxes::result::NyashResultBox::Ok(_)) {
|
||||
return Ok(res.get_value());
|
||||
} else {
|
||||
// Early return the Result itself
|
||||
self.control_flow = super::ControlFlow::Return(v.clone_box());
|
||||
return Ok(Box::new(crate::box_trait::VoidBox::new()));
|
||||
}
|
||||
}
|
||||
// Not a Result: pass-through
|
||||
Ok(v)
|
||||
}
|
||||
ASTNode::PeekExpr { scrutinee, arms, else_expr, .. } => {
|
||||
let val = self.execute_expression(scrutinee)?;
|
||||
let sval = val.to_string_box().value;
|
||||
@ -147,6 +319,11 @@ impl NyashInterpreter {
|
||||
}
|
||||
self.execute_expression(else_expr)
|
||||
}
|
||||
ASTNode::Lambda { params, body, .. } => {
|
||||
// 値としての関数ボックスを生成(ClosureEnv: me/by-val captures)
|
||||
let env = self.build_closure_env(¶ms, body)?;
|
||||
Ok(Box::new(crate::boxes::function_box::FunctionBox::with_env(params.clone(), body.clone(), env)))
|
||||
}
|
||||
|
||||
ASTNode::Include { filename, .. } => {
|
||||
// include式: 最初のstatic boxを返す
|
||||
|
||||
@ -11,6 +11,7 @@ use super::super::*;
|
||||
use crate::boxes::ResultBox;
|
||||
use crate::box_trait::{StringBox, NyashBox};
|
||||
use crate::boxes::FileBox;
|
||||
use crate::boxes::ref_cell_box::RefCellBox;
|
||||
// use crate::bid::plugin_box::PluginFileBox; // legacy - FileBox専用
|
||||
|
||||
impl NyashInterpreter {
|
||||
@ -108,6 +109,26 @@ impl NyashInterpreter {
|
||||
}
|
||||
}
|
||||
|
||||
/// RefCellBox のメソッド: get()/set(value)
|
||||
pub(in crate::interpreter) fn execute_refcell_method(&mut self, cell: &RefCellBox, method: &str, arguments: &[ASTNode])
|
||||
-> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
match method {
|
||||
"get" => {
|
||||
if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation { message: format!("get() expects 0 arguments, got {}", arguments.len()) });
|
||||
}
|
||||
Ok(cell.borrow())
|
||||
}
|
||||
"set" => {
|
||||
if arguments.len() != 1 { return Err(RuntimeError::InvalidOperation { message: format!("set() expects 1 argument, got {}", arguments.len()) }); }
|
||||
let v = self.execute_expression(&arguments[0])?;
|
||||
cell.replace(v);
|
||||
Ok(Box::new(crate::box_trait::VoidBox::new()))
|
||||
}
|
||||
_ => Err(RuntimeError::InvalidOperation { message: format!("Unknown method '{}' for RefCellBox", method) })
|
||||
}
|
||||
}
|
||||
|
||||
/* legacy - PluginFileBox専用
|
||||
/// 汎用プラグインメソッド呼び出し実行 (BID-FFI system)
|
||||
/// Handles generic plugin method calls via dynamic method discovery
|
||||
|
||||
@ -5,6 +5,7 @@ use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, BoxCore};
|
||||
use crate::boxes::{ArrayBox, FloatBox, BufferBox, ResultBox, FutureBox, JSONBox, HttpClientBox, StreamBox, RegexBox, MathBox};
|
||||
use crate::boxes::{null_box, time_box, map_box, random_box, sound_box, debug_box, console_box};
|
||||
use crate::boxes::{gc_config_box::GcConfigBox, debug_config_box::DebugConfigBox};
|
||||
use crate::boxes::ref_cell_box::RefCellBox as RcCell;
|
||||
use crate::boxes::file;
|
||||
use crate::channel_box::ChannelBox;
|
||||
use super::{NyashInterpreter, RuntimeError};
|
||||
@ -118,6 +119,10 @@ impl NyashInterpreter {
|
||||
if let Some(b) = obj.as_any().downcast_ref::<DebugConfigBox>() {
|
||||
return Some(self.execute_debug_config_method(b, method, arguments));
|
||||
}
|
||||
// RefCellBox (by-ref proxy)
|
||||
if let Some(b) = obj.as_any().downcast_ref::<RcCell>() {
|
||||
return Some(self.execute_refcell_method(b, method, arguments));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
use crate::boxes::ref_cell_box::RefCellBox;
|
||||
use super::BuiltinStdlib;
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -46,7 +47,7 @@ impl NyashInterpreter {
|
||||
}
|
||||
}
|
||||
/// 文を実行 - Core statement execution engine
|
||||
pub(super) fn execute_statement(&mut self, statement: &ASTNode) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
pub(crate) fn execute_statement(&mut self, statement: &ASTNode) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
match statement {
|
||||
ASTNode::Assignment { target, value, .. } => {
|
||||
self.execute_assignment(target, value)
|
||||
@ -340,6 +341,13 @@ impl NyashInterpreter {
|
||||
val.clone_box()
|
||||
}
|
||||
};
|
||||
// セル反映: 既存が RefCellBox なら中身のみ置換
|
||||
if let Ok(existing) = self.resolve_variable(name) {
|
||||
if let Some(rc) = existing.as_any().downcast_ref::<RefCellBox>() {
|
||||
rc.replace(assigned);
|
||||
return Ok(val);
|
||||
}
|
||||
}
|
||||
self.set_variable(name, assigned)?;
|
||||
Ok(val)
|
||||
}
|
||||
@ -399,6 +407,13 @@ impl NyashInterpreter {
|
||||
#[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))]
|
||||
{ val.clone_box() }
|
||||
};
|
||||
// セル反映: 既存フィールドが RefCellBox なら中身を置換
|
||||
if let Some(cur) = instance.get_field(field) {
|
||||
if let Some(rc) = cur.as_any().downcast_ref::<RefCellBox>() {
|
||||
rc.replace(stored);
|
||||
return Ok(val);
|
||||
}
|
||||
}
|
||||
instance.set_field(field, Arc::from(stored))
|
||||
.map_err(|e| RuntimeError::InvalidOperation { message: e })?;
|
||||
Ok(val)
|
||||
@ -433,6 +448,13 @@ impl NyashInterpreter {
|
||||
#[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))]
|
||||
{ val.clone_box() }
|
||||
};
|
||||
// セル反映: 既存フィールドが RefCellBox なら中身を置換
|
||||
if let Some(cur) = instance.get_field(field) {
|
||||
if let Some(rc) = cur.as_any().downcast_ref::<RefCellBox>() {
|
||||
rc.replace(stored);
|
||||
return Ok(val);
|
||||
}
|
||||
}
|
||||
instance.set_field(field, Arc::from(stored))
|
||||
.map_err(|e| RuntimeError::InvalidOperation { message: e })?;
|
||||
Ok(val)
|
||||
|
||||
@ -300,6 +300,52 @@ impl MirBuilder {
|
||||
}
|
||||
self.build_function_call(name.clone(), arguments.clone())
|
||||
},
|
||||
ASTNode::Call { callee, arguments, .. } => {
|
||||
// 最小P1: callee が Lambda の場合のみ対応
|
||||
if let ASTNode::Lambda { params, body, .. } = callee.as_ref() {
|
||||
if params.len() != arguments.len() {
|
||||
return Err(format!("Lambda expects {} args, got {}", params.len(), arguments.len()));
|
||||
}
|
||||
// 引数を評価
|
||||
let mut arg_vals: Vec<ValueId> = Vec::new();
|
||||
for a in arguments { arg_vals.push(self.build_expression(a)?); }
|
||||
// スコープ保存
|
||||
let saved_vars = self.variable_map.clone();
|
||||
// パラメータ束縛
|
||||
for (p, v) in params.iter().zip(arg_vals.iter()) {
|
||||
self.variable_map.insert(p.clone(), *v);
|
||||
}
|
||||
// 本体を Program として Lower
|
||||
let prog = ASTNode::Program { statements: body.clone(), span: crate::ast::Span::unknown() };
|
||||
let out = self.build_expression(prog)?;
|
||||
// 復元
|
||||
self.variable_map = saved_vars;
|
||||
Ok(out)
|
||||
} else {
|
||||
Err("Callee is not callable (lambda required)".to_string())
|
||||
}
|
||||
},
|
||||
|
||||
ASTNode::QMarkPropagate { expression, .. } => {
|
||||
// Lower: ok = expr.isOk(); br ok then else; else => return expr; then => expr.getValue()
|
||||
let res_val = self.build_expression(*expression.clone())?;
|
||||
let ok_id = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::PluginInvoke { dst: Some(ok_id), box_val: res_val, method: "isOk".to_string(), args: vec![], effects: EffectMask::PURE })?;
|
||||
let then_block = self.block_gen.next();
|
||||
let else_block = self.block_gen.next();
|
||||
self.emit_instruction(MirInstruction::Branch { condition: ok_id, then_bb: then_block, else_bb: else_block })?;
|
||||
// else: return res_val
|
||||
self.current_block = Some(else_block);
|
||||
self.ensure_block_exists(else_block)?;
|
||||
self.emit_instruction(MirInstruction::Return { value: Some(res_val) })?;
|
||||
// then: getValue()
|
||||
self.current_block = Some(then_block);
|
||||
self.ensure_block_exists(then_block)?;
|
||||
let val_id = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::PluginInvoke { dst: Some(val_id), box_val: res_val, method: "getValue".to_string(), args: vec![], effects: EffectMask::PURE })?;
|
||||
self.value_types.insert(val_id, super::MirType::Unknown);
|
||||
Ok(val_id)
|
||||
},
|
||||
|
||||
ASTNode::Print { expression, .. } => {
|
||||
self.build_print_statement(*expression.clone())
|
||||
@ -340,6 +386,67 @@ impl MirBuilder {
|
||||
ASTNode::Throw { expression, .. } => {
|
||||
self.build_throw_statement(*expression.clone())
|
||||
},
|
||||
|
||||
// P1: Lower peek expression into if-else chain with phi
|
||||
ASTNode::PeekExpr { scrutinee, arms, else_expr, .. } => {
|
||||
// Evaluate scrutinee once
|
||||
let scr_val = self.build_expression(*scrutinee.clone())?;
|
||||
|
||||
// Prepare a merge block and collect phi inputs
|
||||
let merge_block = self.block_gen.next();
|
||||
let mut phi_inputs: Vec<(super::BasicBlockId, super::ValueId)> = Vec::new();
|
||||
|
||||
// Start chaining from the current block
|
||||
for (lit, arm_expr) in arms.into_iter() {
|
||||
// Build condition: scr_val == lit
|
||||
let lit_id = self.build_literal(lit)?;
|
||||
let cond_id = self.value_gen.next();
|
||||
self.emit_instruction(super::MirInstruction::Compare { dst: cond_id, op: super::CompareOp::Eq, lhs: scr_val, rhs: lit_id })?;
|
||||
|
||||
// Create then and next blocks
|
||||
let then_block = self.block_gen.next();
|
||||
let next_block = self.block_gen.next();
|
||||
self.emit_instruction(super::MirInstruction::Branch { condition: cond_id, then_bb: then_block, else_bb: next_block })?;
|
||||
|
||||
// then: evaluate arm expr, jump to merge
|
||||
self.current_block = Some(then_block);
|
||||
self.ensure_block_exists(then_block)?;
|
||||
let then_val = self.build_expression(arm_expr)?;
|
||||
if !self.is_current_block_terminated() {
|
||||
self.emit_instruction(super::MirInstruction::Jump { target: merge_block })?;
|
||||
}
|
||||
phi_inputs.push((then_block, then_val));
|
||||
|
||||
// else path continues chaining
|
||||
self.current_block = Some(next_block);
|
||||
self.ensure_block_exists(next_block)?;
|
||||
// Loop continues from next_block
|
||||
}
|
||||
|
||||
// Final else branch
|
||||
let cur_block = self.current_block.ok_or("No current basic block")?;
|
||||
let else_val = self.build_expression(*else_expr.clone())?;
|
||||
if !self.is_current_block_terminated() {
|
||||
self.emit_instruction(super::MirInstruction::Jump { target: merge_block })?;
|
||||
}
|
||||
phi_inputs.push((cur_block, else_val));
|
||||
|
||||
// Merge and phi
|
||||
self.current_block = Some(merge_block);
|
||||
self.ensure_block_exists(merge_block)?;
|
||||
let result_val = self.value_gen.next();
|
||||
self.emit_instruction(super::MirInstruction::Phi { dst: result_val, inputs: phi_inputs })?;
|
||||
Ok(result_val)
|
||||
},
|
||||
|
||||
ASTNode::Lambda { params, body, .. } => {
|
||||
// Minimal P1: represent as constant string for now
|
||||
let dst = self.value_gen.next();
|
||||
let s = format!("Lambda(params={}, body={})", params.len(), body.len());
|
||||
self.emit_instruction(MirInstruction::Const { dst, value: ConstValue::String(s) })?;
|
||||
self.value_types.insert(dst, super::MirType::String);
|
||||
Ok(dst)
|
||||
},
|
||||
|
||||
ASTNode::Return { value, .. } => {
|
||||
self.build_return_statement(value.clone())
|
||||
|
||||
@ -228,7 +228,8 @@ impl NyashParser {
|
||||
self.parse_call()
|
||||
}
|
||||
|
||||
/// peek式: peek <expr> { lit => expr ... else => expr }
|
||||
/// peek式: peek <expr> { lit => arm ... else => arm }
|
||||
/// P1: arm は 式 または ブロック({ ... } 最後の式が値)
|
||||
fn parse_peek_expr(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.advance(); // consume 'peek'
|
||||
let scrutinee = self.parse_expression()?;
|
||||
@ -250,13 +251,42 @@ impl NyashParser {
|
||||
if is_else {
|
||||
self.advance(); // consume 'else'
|
||||
self.consume(TokenType::FAT_ARROW)?;
|
||||
let expr = self.parse_expression()?;
|
||||
// else アーム: ブロック or 式
|
||||
let expr = if self.match_token(&TokenType::LBRACE) {
|
||||
// ブロックを式として扱う(最後の文の値が返る)
|
||||
self.advance(); // consume '{'
|
||||
let mut stmts: Vec<ASTNode> = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
stmts.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
ASTNode::Program { statements: stmts, span: Span::unknown() }
|
||||
} else {
|
||||
self.parse_expression()?
|
||||
};
|
||||
else_expr = Some(expr);
|
||||
} else {
|
||||
// リテラルのみ許可(P0)
|
||||
let lit = self.parse_literal_only()?;
|
||||
self.consume(TokenType::FAT_ARROW)?;
|
||||
let expr = self.parse_expression()?;
|
||||
// アーム: ブロック or 式
|
||||
let expr = if self.match_token(&TokenType::LBRACE) {
|
||||
self.advance(); // consume '{'
|
||||
let mut stmts: Vec<ASTNode> = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
stmts.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
ASTNode::Program { statements: stmts, span: Span::unknown() }
|
||||
} else {
|
||||
self.parse_expression()?
|
||||
};
|
||||
arms.push((lit, expr));
|
||||
}
|
||||
|
||||
@ -350,26 +380,25 @@ impl NyashParser {
|
||||
});
|
||||
}
|
||||
} else if self.match_token(&TokenType::LPAREN) {
|
||||
// 関数呼び出し: function(args)
|
||||
if let ASTNode::Variable { name, .. } = expr {
|
||||
self.advance(); // consume '('
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "function call argument parsing");
|
||||
|
||||
arguments.push(self.parse_expression()?);
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
|
||||
// 関数呼び出し: function(args) または 一般式呼び出し: (callee)(args)
|
||||
self.advance(); // consume '('
|
||||
let mut arguments = Vec::new();
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "function call argument parsing");
|
||||
arguments.push(self.parse_expression()?);
|
||||
if self.match_token(&TokenType::COMMA) { self.advance(); }
|
||||
}
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
|
||||
if let ASTNode::Variable { name, .. } = expr.clone() {
|
||||
expr = ASTNode::FunctionCall { name, arguments, span: Span::unknown() };
|
||||
} else {
|
||||
break;
|
||||
expr = ASTNode::Call { callee: Box::new(expr), arguments, span: Span::unknown() };
|
||||
}
|
||||
} else if self.match_token(&TokenType::QUESTION) {
|
||||
// 後置 ?(Result伝播)
|
||||
self.advance();
|
||||
expr = ASTNode::QMarkPropagate { expression: Box::new(expr), span: Span::unknown() };
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@ -423,8 +452,12 @@ impl NyashParser {
|
||||
}
|
||||
|
||||
TokenType::THIS => {
|
||||
// Deprecation: normalize 'this' to 'me'
|
||||
if std::env::var("NYASH_DEPRECATE_THIS").ok().as_deref() == Some("1") {
|
||||
eprintln!("[deprecate:this] 'this' is deprecated; use 'me' instead (line {})", self.current_token().line);
|
||||
}
|
||||
self.advance();
|
||||
Ok(ASTNode::This { span: Span::unknown() })
|
||||
Ok(ASTNode::Me { span: Span::unknown() })
|
||||
}
|
||||
|
||||
TokenType::ME => {
|
||||
@ -506,9 +539,33 @@ impl NyashParser {
|
||||
}
|
||||
|
||||
TokenType::IDENTIFIER(name) => {
|
||||
let name = name.clone();
|
||||
let parent = name.clone();
|
||||
self.advance();
|
||||
Ok(ASTNode::Variable { name, span: Span::unknown() })
|
||||
if self.match_token(&TokenType::DOUBLE_COLON) {
|
||||
// Parent::method(args)
|
||||
self.advance(); // consume '::'
|
||||
let method = match &self.current_token().token_type {
|
||||
TokenType::IDENTIFIER(m) => { let s=m.clone(); self.advance(); s }
|
||||
TokenType::INIT => { self.advance(); "init".to_string() }
|
||||
TokenType::PACK => { self.advance(); "pack".to_string() }
|
||||
TokenType::BIRTH => { self.advance(); "birth".to_string() }
|
||||
_ => {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "method name".to_string(), line });
|
||||
}
|
||||
};
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let mut arguments = Vec::new();
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "Parent::method call argument parsing");
|
||||
arguments.push(self.parse_expression()?);
|
||||
if self.match_token(&TokenType::COMMA) { self.advance(); }
|
||||
}
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
Ok(ASTNode::FromCall { parent, method, arguments, span: Span::unknown() })
|
||||
} else {
|
||||
Ok(ASTNode::Variable { name: parent, span: Span::unknown() })
|
||||
}
|
||||
}
|
||||
|
||||
TokenType::LPAREN => {
|
||||
@ -518,6 +575,36 @@ impl NyashParser {
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
TokenType::FN => {
|
||||
// 無名関数: fn (params?) { body }
|
||||
self.advance(); // consume 'fn'
|
||||
let mut params: Vec<String> = Vec::new();
|
||||
if self.match_token(&TokenType::LPAREN) {
|
||||
self.advance();
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
if let TokenType::IDENTIFIER(p) = &self.current_token().token_type {
|
||||
params.push(p.clone());
|
||||
self.advance();
|
||||
if self.match_token(&TokenType::COMMA) { self.advance(); }
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "parameter name".to_string(), line });
|
||||
}
|
||||
}
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
}
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
let mut body: Vec<ASTNode> = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
body.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
Ok(ASTNode::Lambda { params, body, span: Span::unknown() })
|
||||
}
|
||||
|
||||
_ => {
|
||||
let line = self.current_token().line;
|
||||
Err(ParseError::InvalidExpression { line })
|
||||
|
||||
23
src/tests/mir_peek_lower.rs
Normal file
23
src/tests/mir_peek_lower.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use crate::mir::{MirCompiler, MirPrinter};
|
||||
use crate::ast::{ASTNode, LiteralValue, Span};
|
||||
|
||||
#[test]
|
||||
fn mir_lowering_of_peek_expr() {
|
||||
// Build AST: peek 2 { 1 => 10, 2 => 20, else => 30 }
|
||||
let ast = ASTNode::PeekExpr {
|
||||
scrutinee: Box::new(ASTNode::Literal { value: LiteralValue::Integer(2), span: Span::unknown() }),
|
||||
arms: vec![
|
||||
(LiteralValue::Integer(1), ASTNode::Literal { value: LiteralValue::Integer(10), span: Span::unknown() }),
|
||||
(LiteralValue::Integer(2), ASTNode::Literal { value: LiteralValue::Integer(20), span: Span::unknown() }),
|
||||
],
|
||||
else_expr: Box::new(ASTNode::Literal { value: LiteralValue::Integer(30), span: Span::unknown() }),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let mut compiler = MirCompiler::new();
|
||||
let res = compiler.compile(ast).expect("compile ok");
|
||||
let dump = MirPrinter::new().print_module(&res.module);
|
||||
assert!(dump.contains("br "), "expected branches in MIR:\n{}", dump);
|
||||
assert!(dump.contains("phi"), "expected phi merge in MIR:\n{}", dump);
|
||||
}
|
||||
|
||||
20
src/tests/mir_qmark_lower.rs
Normal file
20
src/tests/mir_qmark_lower.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use crate::mir::{MirCompiler, MirPrinter};
|
||||
use crate::ast::{ASTNode, Span};
|
||||
|
||||
#[test]
|
||||
fn mir_lowering_of_qmark_propagate() {
|
||||
// Build AST: (new StringBox("ok"))?
|
||||
let ast = ASTNode::QMarkPropagate {
|
||||
expression: Box::new(ASTNode::New { class: "StringBox".to_string(), arguments: vec![
|
||||
ASTNode::Literal { value: crate::ast::LiteralValue::String("ok".to_string()), span: Span::unknown() }
|
||||
], type_arguments: vec![], span: Span::unknown() }),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let mut c = MirCompiler::new();
|
||||
let out = c.compile(ast).expect("compile ok");
|
||||
let dump = MirPrinter::new().print_module(&out.module);
|
||||
assert!(dump.contains("plugin_invoke"), "expected plugin_invoke isOk/getValue in MIR:\n{}", dump);
|
||||
assert!(dump.contains("br "), "expected branch in MIR:\n{}", dump);
|
||||
assert!(dump.contains("ret "), "expected return on error path in MIR:\n{}", dump);
|
||||
}
|
||||
|
||||
17
src/tests/parser_lambda.rs
Normal file
17
src/tests/parser_lambda.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use crate::parser::NyashParser;
|
||||
use crate::ast::ASTNode;
|
||||
|
||||
#[test]
|
||||
fn parse_lambda_fn_block() {
|
||||
let src = "local f = fn() { return 1 }";
|
||||
let ast = NyashParser::parse_from_string(src).expect("parse ok");
|
||||
fn has_lambda(n: &ASTNode) -> bool {
|
||||
match n {
|
||||
ASTNode::Lambda { .. } => true,
|
||||
ASTNode::Program { statements, .. } => statements.iter().any(has_lambda),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
assert!(has_lambda(&ast));
|
||||
}
|
||||
|
||||
17
src/tests/parser_lambda_call.rs
Normal file
17
src/tests/parser_lambda_call.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use crate::parser::NyashParser;
|
||||
use crate::ast::ASTNode;
|
||||
|
||||
#[test]
|
||||
fn parse_immediate_lambda_call() {
|
||||
let src = "(fn(x){ x }) (1)";
|
||||
let ast = NyashParser::parse_from_string(src).expect("parse ok");
|
||||
fn has_call(n: &ASTNode) -> bool {
|
||||
match n {
|
||||
ASTNode::Call { .. } => true,
|
||||
ASTNode::Program { statements, .. } => statements.iter().any(has_call),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
assert!(has_call(&ast));
|
||||
}
|
||||
|
||||
17
src/tests/parser_parent_colon.rs
Normal file
17
src/tests/parser_parent_colon.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use crate::parser::NyashParser;
|
||||
use crate::ast::ASTNode;
|
||||
|
||||
#[test]
|
||||
fn parse_parent_colon_syntax() {
|
||||
let src = "Parent::birth()";
|
||||
let ast = NyashParser::parse_from_string(src).expect("parse ok");
|
||||
fn is_fromcall(n: &ASTNode) -> bool {
|
||||
match n {
|
||||
ASTNode::FromCall { parent, method, .. } => parent == "Parent" && method == "birth",
|
||||
ASTNode::Program { statements, .. } => statements.iter().any(is_fromcall),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
assert!(is_fromcall(&ast));
|
||||
}
|
||||
|
||||
29
src/tests/parser_peek_block.rs
Normal file
29
src/tests/parser_peek_block.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use crate::parser::NyashParser;
|
||||
|
||||
#[test]
|
||||
fn parse_peek_with_block_arm() {
|
||||
let src = r#"
|
||||
local x = 2
|
||||
local y = peek x {
|
||||
1 => { local a = 10 a }
|
||||
2 => { 20 }
|
||||
else => { 30 }
|
||||
}
|
||||
"#;
|
||||
let ast = NyashParser::parse_from_string(src).expect("parse ok");
|
||||
// Quick structural check: ensure AST contains PeekExpr and Program nodes inside arms
|
||||
fn find_peek(ast: &crate::ast::ASTNode) -> bool {
|
||||
match ast {
|
||||
crate::ast::ASTNode::PeekExpr { arms, else_expr, .. } => {
|
||||
// Expect at least one Program arm
|
||||
let has_block = arms.iter().any(|(_, e)| matches!(e, crate::ast::ASTNode::Program { .. }));
|
||||
let else_is_block = matches!(**else_expr, crate::ast::ASTNode::Program { .. });
|
||||
has_block && else_is_block
|
||||
}
|
||||
crate::ast::ASTNode::Program { statements, .. } => statements.iter().any(find_peek),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
assert!(find_peek(&ast), "expected peek with block arms in AST");
|
||||
}
|
||||
|
||||
68
src/tests/refcell_assignment_test.rs
Normal file
68
src/tests/refcell_assignment_test.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use crate::interpreter::NyashInterpreter;
|
||||
use crate::ast::{ASTNode, LiteralValue};
|
||||
use crate::box_trait::{NyashBox, IntegerBox};
|
||||
|
||||
#[test]
|
||||
fn assign_updates_refcell_variable_inner() {
|
||||
let mut interp = NyashInterpreter::new();
|
||||
// x = RefCell(1)
|
||||
let rc = crate::boxes::ref_cell_box::RefCellBox::new(Box::new(IntegerBox::new(1)));
|
||||
interp.declare_local_variable("x", Box::new(rc));
|
||||
|
||||
// Execute: x = 42
|
||||
let target = ASTNode::Variable { name: "x".to_string(), span: crate::ast::Span::unknown() };
|
||||
let value = ASTNode::Literal { value: LiteralValue::Integer(42), span: crate::ast::Span::unknown() };
|
||||
let _ = interp.execute_assignment(&target, &value).expect("assign ok");
|
||||
|
||||
// Verify x is still RefCell and inner == 42
|
||||
let xv = interp.resolve_variable("x").expect("x exists");
|
||||
let rc = xv.as_any().downcast_ref::<crate::boxes::ref_cell_box::RefCellBox>().expect("x is RefCellBox");
|
||||
let inner = rc.borrow();
|
||||
let ib = inner.as_any().downcast_ref::<IntegerBox>().expect("inner integer");
|
||||
assert_eq!(ib.value, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assign_updates_refcell_field_inner() {
|
||||
let mut interp = NyashInterpreter::new();
|
||||
// obj with field v = RefCell(5)
|
||||
let inst = crate::instance_v2::InstanceBox::from_declaration("Test".to_string(), vec!["v".to_string()], std::collections::HashMap::new());
|
||||
let rc = crate::boxes::ref_cell_box::RefCellBox::new(Box::new(IntegerBox::new(5)));
|
||||
let _ = inst.set_field("v", std::sync::Arc::from(Box::new(rc) as Box<dyn NyashBox>));
|
||||
// bind obj into local
|
||||
interp.declare_local_variable("obj", Box::new(inst.clone()));
|
||||
|
||||
// Execute: obj.v = 7
|
||||
let target = ASTNode::FieldAccess { object: Box::new(ASTNode::Variable { name: "obj".to_string(), span: crate::ast::Span::unknown() }), field: "v".to_string(), span: crate::ast::Span::unknown() };
|
||||
let value = ASTNode::Literal { value: LiteralValue::Integer(7), span: crate::ast::Span::unknown() };
|
||||
let _ = interp.execute_assignment(&target, &value).expect("assign ok");
|
||||
|
||||
// Verify obj.v inner == 7
|
||||
let sv = inst.get_field("v").expect("field exists");
|
||||
let rc = sv.as_any().downcast_ref::<crate::boxes::ref_cell_box::RefCellBox>().expect("v is RefCellBox");
|
||||
let inner = rc.borrow();
|
||||
let ib = inner.as_any().downcast_ref::<IntegerBox>().expect("inner integer");
|
||||
assert_eq!(ib.value, 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closure_reads_updated_refcell_capture() {
|
||||
let mut interp = NyashInterpreter::new();
|
||||
// local x = 10
|
||||
interp.declare_local_variable("x", Box::new(IntegerBox::new(10)));
|
||||
// Build lambda: () { x }
|
||||
let lam = ASTNode::Lambda { params: vec![], body: vec![
|
||||
ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: "x".to_string(), span: crate::ast::Span::unknown() })), span: crate::ast::Span::unknown() }
|
||||
], span: crate::ast::Span::unknown() };
|
||||
// Evaluate lambda to FunctionBox
|
||||
let f = interp.execute_expression(&lam).expect("lambda eval");
|
||||
// x = 20 (should update RefCell capture)
|
||||
let target = ASTNode::Variable { name: "x".to_string(), span: crate::ast::Span::unknown() };
|
||||
let value = ASTNode::Literal { value: LiteralValue::Integer(20), span: crate::ast::Span::unknown() };
|
||||
let _ = interp.execute_assignment(&target, &value).expect("assign ok");
|
||||
// Call f()
|
||||
let call = ASTNode::Call { callee: Box::new(lam.clone()), arguments: vec![], span: crate::ast::Span::unknown() };
|
||||
let out = interp.execute_expression(&call).expect("call ok");
|
||||
let ib = out.as_any().downcast_ref::<IntegerBox>().expect("integer ret");
|
||||
assert_eq!(ib.value, 20);
|
||||
}
|
||||
@ -32,6 +32,7 @@ pub enum TokenType {
|
||||
CONTINUE,
|
||||
RETURN,
|
||||
FUNCTION,
|
||||
FN,
|
||||
PRINT,
|
||||
THIS,
|
||||
ME,
|
||||
@ -82,6 +83,7 @@ pub enum TokenType {
|
||||
LBRACE, // {
|
||||
RBRACE, // }
|
||||
COMMA, // ,
|
||||
QUESTION, // ? (postfix result propagation)
|
||||
NEWLINE, // \n
|
||||
|
||||
// 識別子
|
||||
@ -293,6 +295,10 @@ impl NyashTokenizer {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::COMMA, start_line, start_column))
|
||||
}
|
||||
Some('?') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::QUESTION, start_line, start_column))
|
||||
}
|
||||
Some(':') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::COLON, start_line, start_column))
|
||||
@ -413,6 +419,7 @@ impl NyashTokenizer {
|
||||
"continue" => TokenType::CONTINUE,
|
||||
"return" => TokenType::RETURN,
|
||||
"function" => TokenType::FUNCTION,
|
||||
"fn" => TokenType::FN,
|
||||
// Alias support: `fn` as shorthand for function
|
||||
"fn" => TokenType::FUNCTION,
|
||||
"print" => TokenType::PRINT,
|
||||
|
||||
Reference in New Issue
Block a user