Phase 12.7: Nyash文法革命とANCP 90%圧縮技法の発見 - 文法改革完了とFunctionBox実装

This commit is contained in:
Moe Charm
2025-09-03 20:03:45 +09:00
parent 6d79d7d3ac
commit 7455c9ec97
69 changed files with 3817 additions and 62 deletions

View File

@ -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
View 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() }
}
}

View File

@ -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")]

View File

@ -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
View 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) }
}
}

View File

@ -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")))]

View File

@ -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(&params, body)?;
Ok(Box::new(crate::boxes::function_box::FunctionBox::with_env(params.clone(), body.clone(), env)))
}
ASTNode::Include { filename, .. } => {
// include式: 最初のstatic boxを返す

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -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())

View File

@ -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 })

View 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);
}

View 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);
}

View 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));
}

View 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));
}

View 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));
}

View 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");
}

View 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);
}

View File

@ -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,