🎉 initial commit: Nyash Programming Language完成版
🚀 主要機能: • Everything is Box哲学による革新的アーキテクチャ • WebAssemblyブラウザー対応プレイグラウンド • アーティスト協同制作デモ - 複数Boxインスタンス実証 • 視覚的デバッグシステム - DebugBox完全統合 • static box Mainパターン - メモリ安全設計 ⚡ 言語機能: • NOT/AND/OR/除算演算子完全実装 • ジェネリクス/テンプレートシステム • 非同期処理(nowait/await) • try/catchエラーハンドリング • Canvas統合グラフィックス 🎨 ブラウザー体験: • 9種類のインタラクティブデモ • リアルタイムコード実行 • WebCanvas/WebConsole/WebDisplay • モバイル対応完了 🤖 Built with Claude Code collaboration Ready for public release!
This commit is contained in:
963
src/ast.rs
Normal file
963
src/ast.rs
Normal file
@ -0,0 +1,963 @@
|
||||
/*!
|
||||
* Nyash AST (Abstract Syntax Tree) - Rust Implementation
|
||||
*
|
||||
* Python版nyashc_v4.pyのAST構造をRustで完全再実装
|
||||
* Everything is Box哲学に基づく型安全なAST設計
|
||||
*/
|
||||
|
||||
use crate::box_trait::NyashBox;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
||||
/// ソースコード位置情報 - エラー報告とデバッグの革命
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Span {
|
||||
pub start: usize, // 開始位置(バイトオフセット)
|
||||
pub end: usize, // 終了位置(バイトオフセット)
|
||||
pub line: usize, // 行番号(1から開始)
|
||||
pub column: usize, // 列番号(1から開始)
|
||||
}
|
||||
|
||||
impl Span {
|
||||
/// 新しいSpanを作成
|
||||
pub fn new(start: usize, end: usize, line: usize, column: usize) -> Self {
|
||||
Self { start, end, line, column }
|
||||
}
|
||||
|
||||
/// デフォルトのSpan(不明な位置)
|
||||
pub fn unknown() -> Self {
|
||||
Self { start: 0, end: 0, line: 1, column: 1 }
|
||||
}
|
||||
|
||||
/// 2つのSpanを結合(開始位置から終了位置まで)
|
||||
pub fn merge(&self, other: Span) -> Span {
|
||||
Span {
|
||||
start: self.start.min(other.start),
|
||||
end: self.end.max(other.end),
|
||||
line: self.line,
|
||||
column: self.column,
|
||||
}
|
||||
}
|
||||
|
||||
/// ソースコードから該当箇所を抽出してエラー表示用文字列を生成
|
||||
pub fn error_context(&self, source: &str) -> String {
|
||||
let lines: Vec<&str> = source.lines().collect();
|
||||
if self.line == 0 || self.line > lines.len() {
|
||||
return format!("line {}, column {}", self.line, self.column);
|
||||
}
|
||||
|
||||
let line_content = lines[self.line - 1];
|
||||
let mut context = String::new();
|
||||
|
||||
// 行番号とソース行を表示
|
||||
context.push_str(&format!(" |\n{:3} | {}\n", self.line, line_content));
|
||||
|
||||
// カーソル位置を表示(簡易版)
|
||||
if self.column > 0 && self.column <= line_content.len() + 1 {
|
||||
context.push_str(" | ");
|
||||
for _ in 1..self.column {
|
||||
context.push(' ');
|
||||
}
|
||||
let span_length = if self.end > self.start {
|
||||
(self.end - self.start).min(line_content.len() - self.column + 1)
|
||||
} else {
|
||||
1
|
||||
};
|
||||
for _ in 0..span_length.max(1) {
|
||||
context.push('^');
|
||||
}
|
||||
context.push('\n');
|
||||
}
|
||||
|
||||
context
|
||||
}
|
||||
|
||||
/// 位置情報の文字列表現
|
||||
pub fn location_string(&self) -> String {
|
||||
format!("line {}, column {}", self.line, self.column)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Span {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "line {}, column {}", self.line, self.column)
|
||||
}
|
||||
}
|
||||
|
||||
/// 🌟 AST分類システム - ChatGPTアドバイス統合による3層アーキテクチャ
|
||||
/// Structure/Expression/Statement の明確な分離による型安全性向上
|
||||
|
||||
/// ASTノードの種類分類
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ASTNodeType {
|
||||
Structure, // 構造定義: box, function, if, loop, try/catch
|
||||
Expression, // 式: リテラル, 変数, 演算, 呼び出し
|
||||
Statement, // 文: 代入, return, break, include
|
||||
}
|
||||
|
||||
/// 構造ノード - 言語の基本構造を定義
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StructureNode {
|
||||
BoxDeclaration {
|
||||
name: String,
|
||||
fields: Vec<String>,
|
||||
methods: Vec<ASTNode>,
|
||||
constructors: Vec<ASTNode>,
|
||||
init_fields: Vec<String>,
|
||||
is_interface: bool,
|
||||
extends: Option<String>,
|
||||
implements: Vec<String>,
|
||||
/// 🔥 ジェネリクス型パラメータ (例: ["T", "U"])
|
||||
type_parameters: Vec<String>,
|
||||
/// 🔥 Static boxかどうかのフラグ
|
||||
is_static: bool,
|
||||
/// 🔥 Static初期化ブロック (static { ... })
|
||||
static_init: Option<Vec<ASTNode>>,
|
||||
span: Span,
|
||||
},
|
||||
FunctionDeclaration {
|
||||
name: String,
|
||||
params: Vec<String>,
|
||||
body: Vec<ASTNode>,
|
||||
is_static: bool, // 🔥 静的メソッドフラグ
|
||||
span: Span,
|
||||
},
|
||||
IfStructure {
|
||||
condition: Box<ASTNode>,
|
||||
then_body: Vec<ASTNode>,
|
||||
else_body: Option<Vec<ASTNode>>,
|
||||
span: Span,
|
||||
},
|
||||
LoopStructure {
|
||||
condition: Box<ASTNode>,
|
||||
body: Vec<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
TryCatchStructure {
|
||||
try_body: Vec<ASTNode>,
|
||||
catch_clauses: Vec<CatchClause>,
|
||||
finally_body: Option<Vec<ASTNode>>,
|
||||
span: Span,
|
||||
},
|
||||
}
|
||||
|
||||
/// 式ノード - 値を生成する表現
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ExpressionNode {
|
||||
Literal {
|
||||
value: LiteralValue,
|
||||
span: Span,
|
||||
},
|
||||
Variable {
|
||||
name: String,
|
||||
span: Span,
|
||||
},
|
||||
BinaryOperation {
|
||||
operator: BinaryOperator,
|
||||
left: Box<ASTNode>,
|
||||
right: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
UnaryOperation {
|
||||
operator: UnaryOperator,
|
||||
operand: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
FunctionCall {
|
||||
name: String,
|
||||
arguments: Vec<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
MethodCall {
|
||||
object: Box<ASTNode>,
|
||||
method: String,
|
||||
arguments: Vec<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
FieldAccess {
|
||||
object: Box<ASTNode>,
|
||||
field: String,
|
||||
span: Span,
|
||||
},
|
||||
NewExpression {
|
||||
class: String,
|
||||
arguments: Vec<ASTNode>,
|
||||
/// 🔥 ジェネリクス型引数 (例: ["IntegerBox", "StringBox"])
|
||||
type_arguments: Vec<String>,
|
||||
span: Span,
|
||||
},
|
||||
ThisExpression {
|
||||
span: Span,
|
||||
},
|
||||
MeExpression {
|
||||
span: Span,
|
||||
},
|
||||
}
|
||||
|
||||
/// 文ノード - 実行可能なアクション
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StatementNode {
|
||||
Assignment {
|
||||
target: Box<ASTNode>,
|
||||
value: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
Print {
|
||||
expression: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
Return {
|
||||
value: Option<Box<ASTNode>>,
|
||||
span: Span,
|
||||
},
|
||||
Break {
|
||||
span: Span,
|
||||
},
|
||||
Include {
|
||||
filename: String,
|
||||
span: Span,
|
||||
},
|
||||
Local {
|
||||
variables: Vec<String>,
|
||||
span: Span,
|
||||
},
|
||||
Throw {
|
||||
exception_type: String,
|
||||
message: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
Expression {
|
||||
expr: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
}
|
||||
|
||||
/// Catch節の構造体
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CatchClause {
|
||||
pub exception_type: Option<String>, // None = catch-all
|
||||
pub variable_name: Option<String>, // 例外を受け取る変数名
|
||||
pub body: Vec<ASTNode>, // catch本体
|
||||
pub span: Span, // ソースコード位置
|
||||
}
|
||||
|
||||
/// リテラル値の型 (Clone可能)
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LiteralValue {
|
||||
String(String),
|
||||
Integer(i64),
|
||||
Float(f64), // 浮動小数点数サポート追加
|
||||
Bool(bool),
|
||||
Void,
|
||||
}
|
||||
|
||||
impl LiteralValue {
|
||||
/// LiteralValueをNyashBoxに変換
|
||||
pub fn to_nyash_box(&self) -> Box<dyn NyashBox> {
|
||||
use crate::box_trait::{StringBox, IntegerBox, BoolBox, VoidBox};
|
||||
use crate::boxes::math_box::FloatBox;
|
||||
|
||||
match self {
|
||||
LiteralValue::String(s) => Box::new(StringBox::new(s)),
|
||||
LiteralValue::Integer(i) => Box::new(IntegerBox::new(*i)),
|
||||
LiteralValue::Float(f) => Box::new(FloatBox::new(*f)),
|
||||
LiteralValue::Bool(b) => Box::new(BoolBox::new(*b)),
|
||||
LiteralValue::Void => Box::new(VoidBox::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// NyashBoxからLiteralValueに変換
|
||||
pub fn from_nyash_box(box_val: &dyn NyashBox) -> Option<LiteralValue> {
|
||||
#[allow(unused_imports)]
|
||||
use std::any::Any;
|
||||
use crate::box_trait::{StringBox, IntegerBox, BoolBox, VoidBox};
|
||||
use crate::boxes::math_box::FloatBox;
|
||||
|
||||
if let Some(string_box) = box_val.as_any().downcast_ref::<StringBox>() {
|
||||
Some(LiteralValue::String(string_box.value.clone()))
|
||||
} else if let Some(int_box) = box_val.as_any().downcast_ref::<IntegerBox>() {
|
||||
Some(LiteralValue::Integer(int_box.value))
|
||||
} else if let Some(float_box) = box_val.as_any().downcast_ref::<FloatBox>() {
|
||||
Some(LiteralValue::Float(float_box.value))
|
||||
} else if let Some(bool_box) = box_val.as_any().downcast_ref::<BoolBox>() {
|
||||
Some(LiteralValue::Bool(bool_box.value))
|
||||
} else if box_val.as_any().downcast_ref::<VoidBox>().is_some() {
|
||||
Some(LiteralValue::Void)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LiteralValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
LiteralValue::String(s) => write!(f, "\"{}\"", s),
|
||||
LiteralValue::Integer(i) => write!(f, "{}", i),
|
||||
LiteralValue::Float(fl) => write!(f, "{}", fl),
|
||||
LiteralValue::Bool(b) => write!(f, "{}", b),
|
||||
LiteralValue::Void => write!(f, "void"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 単項演算子の種類
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum UnaryOperator {
|
||||
Minus, // -x
|
||||
Not, // not x
|
||||
}
|
||||
|
||||
/// 二項演算子の種類
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum BinaryOperator {
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Equal,
|
||||
NotEqual,
|
||||
Less,
|
||||
Greater,
|
||||
LessEqual,
|
||||
GreaterEqual,
|
||||
And,
|
||||
Or,
|
||||
}
|
||||
|
||||
impl fmt::Display for UnaryOperator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let symbol = match self {
|
||||
UnaryOperator::Minus => "-",
|
||||
UnaryOperator::Not => "not",
|
||||
};
|
||||
write!(f, "{}", symbol)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BinaryOperator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let symbol = match self {
|
||||
BinaryOperator::Add => "+",
|
||||
BinaryOperator::Subtract => "-",
|
||||
BinaryOperator::Multiply => "*",
|
||||
BinaryOperator::Divide => "/",
|
||||
BinaryOperator::Equal => "==",
|
||||
BinaryOperator::NotEqual => "!=",
|
||||
BinaryOperator::Less => "<",
|
||||
BinaryOperator::Greater => ">",
|
||||
BinaryOperator::LessEqual => "<=",
|
||||
BinaryOperator::GreaterEqual => ">=",
|
||||
BinaryOperator::And => "&&",
|
||||
BinaryOperator::Or => "||",
|
||||
};
|
||||
write!(f, "{}", symbol)
|
||||
}
|
||||
}
|
||||
|
||||
/// AST Node - Everything is Box哲学に基づく統一構造
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ASTNode {
|
||||
/// プログラム全体 - 文のリスト
|
||||
Program {
|
||||
statements: Vec<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
// ===== 文 (Statements) =====
|
||||
|
||||
/// 代入文: target = value
|
||||
Assignment {
|
||||
target: Box<ASTNode>,
|
||||
value: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// print文: print(expression)
|
||||
Print {
|
||||
expression: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// if文: if condition { then_body } else { else_body }
|
||||
If {
|
||||
condition: Box<ASTNode>,
|
||||
then_body: Vec<ASTNode>,
|
||||
else_body: Option<Vec<ASTNode>>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// loop文: loop(condition) { body } のみ
|
||||
Loop {
|
||||
condition: Box<ASTNode>,
|
||||
body: Vec<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// return文: return value
|
||||
Return {
|
||||
value: Option<Box<ASTNode>>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// break文
|
||||
Break {
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// nowait文: nowait variable = expression
|
||||
Nowait {
|
||||
variable: String,
|
||||
expression: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// await式: await expression
|
||||
AwaitExpression {
|
||||
expression: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// arrow文: (sender >> receiver).method(args)
|
||||
Arrow {
|
||||
sender: Box<ASTNode>,
|
||||
receiver: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// try/catch/finally文: try { ... } catch (Type e) { ... } finally { ... }
|
||||
TryCatch {
|
||||
try_body: Vec<ASTNode>,
|
||||
catch_clauses: Vec<CatchClause>,
|
||||
finally_body: Option<Vec<ASTNode>>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// throw文: throw expression
|
||||
Throw {
|
||||
expression: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
// ===== 宣言 (Declarations) =====
|
||||
|
||||
/// box宣言: box Name { fields... methods... }
|
||||
BoxDeclaration {
|
||||
name: String,
|
||||
fields: Vec<String>,
|
||||
methods: HashMap<String, ASTNode>, // method_name -> FunctionDeclaration
|
||||
constructors: HashMap<String, ASTNode>, // constructor_key -> FunctionDeclaration
|
||||
init_fields: Vec<String>, // initブロック内のフィールド定義
|
||||
is_interface: bool, // interface box かどうか
|
||||
extends: Option<String>, // 継承元のBox名
|
||||
implements: Vec<String>, // 実装するinterface名のリスト
|
||||
type_parameters: Vec<String>, // 🔥 ジェネリクス型パラメータ (例: ["T", "U"])
|
||||
/// 🔥 Static boxかどうかのフラグ
|
||||
is_static: bool,
|
||||
/// 🔥 Static初期化ブロック (static { ... })
|
||||
static_init: Option<Vec<ASTNode>>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// 関数宣言: functionName(params) { body }
|
||||
FunctionDeclaration {
|
||||
name: String,
|
||||
params: Vec<String>,
|
||||
body: Vec<ASTNode>,
|
||||
is_static: bool, // 🔥 静的メソッドフラグ
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// グローバル変数: global name = value
|
||||
GlobalVar {
|
||||
name: String,
|
||||
value: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
// ===== 式 (Expressions) =====
|
||||
|
||||
/// リテラル値: "string", 42, true, etc
|
||||
Literal {
|
||||
value: LiteralValue,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// 変数参照: variableName
|
||||
Variable {
|
||||
name: String,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// 単項演算: operator operand
|
||||
UnaryOp {
|
||||
operator: UnaryOperator,
|
||||
operand: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// 二項演算: left operator right
|
||||
BinaryOp {
|
||||
operator: BinaryOperator,
|
||||
left: Box<ASTNode>,
|
||||
right: Box<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// メソッド呼び出し: object.method(arguments)
|
||||
MethodCall {
|
||||
object: Box<ASTNode>,
|
||||
method: String,
|
||||
arguments: Vec<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// フィールドアクセス: object.field
|
||||
FieldAccess {
|
||||
object: Box<ASTNode>,
|
||||
field: String,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// コンストラクタ呼び出し: new ClassName(arguments)
|
||||
New {
|
||||
class: String,
|
||||
arguments: Vec<ASTNode>,
|
||||
type_arguments: Vec<String>, // 🔥 ジェネリクス型引数 (例: ["IntegerBox", "StringBox"])
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// this参照
|
||||
This {
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// me参照
|
||||
Me {
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// thisフィールドアクセス: this.field
|
||||
ThisField {
|
||||
field: String,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// meフィールドアクセス: me.field
|
||||
MeField {
|
||||
field: String,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// ファイル読み込み: include "filename.nyash"
|
||||
Include {
|
||||
filename: String,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// ローカル変数宣言: local x, y, z
|
||||
Local {
|
||||
variables: Vec<String>,
|
||||
/// 初期化値(変数と同じ順序、Noneは初期化なし)
|
||||
initial_values: Vec<Option<Box<ASTNode>>>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// Outbox変数宣言: outbox x, y, z (static関数内専用)
|
||||
Outbox {
|
||||
variables: Vec<String>,
|
||||
/// 初期化値(変数と同じ順序、Noneは初期化なし)
|
||||
initial_values: Vec<Option<Box<ASTNode>>>,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// 関数呼び出し: functionName(arguments)
|
||||
FunctionCall {
|
||||
name: String,
|
||||
arguments: Vec<ASTNode>,
|
||||
span: Span,
|
||||
},
|
||||
}
|
||||
|
||||
impl ASTNode {
|
||||
/// AST nodeの種類を文字列で取得 (デバッグ用)
|
||||
pub fn node_type(&self) -> &'static str {
|
||||
match self {
|
||||
ASTNode::Program { .. } => "Program",
|
||||
ASTNode::Assignment { .. } => "Assignment",
|
||||
ASTNode::Print { .. } => "Print",
|
||||
ASTNode::If { .. } => "If",
|
||||
ASTNode::Loop { .. } => "Loop",
|
||||
ASTNode::Return { .. } => "Return",
|
||||
ASTNode::Break { .. } => "Break",
|
||||
ASTNode::BoxDeclaration { .. } => "BoxDeclaration",
|
||||
ASTNode::FunctionDeclaration { .. } => "FunctionDeclaration",
|
||||
ASTNode::GlobalVar { .. } => "GlobalVar",
|
||||
ASTNode::Literal { .. } => "Literal",
|
||||
ASTNode::Variable { .. } => "Variable",
|
||||
ASTNode::UnaryOp { .. } => "UnaryOp",
|
||||
ASTNode::BinaryOp { .. } => "BinaryOp",
|
||||
ASTNode::MethodCall { .. } => "MethodCall",
|
||||
ASTNode::FieldAccess { .. } => "FieldAccess",
|
||||
ASTNode::New { .. } => "New",
|
||||
ASTNode::This { .. } => "This",
|
||||
ASTNode::Me { .. } => "Me",
|
||||
ASTNode::ThisField { .. } => "ThisField",
|
||||
ASTNode::MeField { .. } => "MeField",
|
||||
ASTNode::Include { .. } => "Include",
|
||||
ASTNode::Local { .. } => "Local",
|
||||
ASTNode::Outbox { .. } => "Outbox",
|
||||
ASTNode::FunctionCall { .. } => "FunctionCall",
|
||||
ASTNode::Nowait { .. } => "Nowait",
|
||||
ASTNode::Arrow { .. } => "Arrow",
|
||||
ASTNode::TryCatch { .. } => "TryCatch",
|
||||
ASTNode::Throw { .. } => "Throw",
|
||||
ASTNode::AwaitExpression { .. } => "AwaitExpression",
|
||||
}
|
||||
}
|
||||
|
||||
/// 🌟 AST分類 - ChatGPTアドバイス統合による革新的分類システム
|
||||
/// Structure/Expression/Statement の明確な分離
|
||||
pub fn classify(&self) -> ASTNodeType {
|
||||
match self {
|
||||
// Structure nodes - 言語の基本構造
|
||||
ASTNode::BoxDeclaration { .. } => ASTNodeType::Structure,
|
||||
ASTNode::FunctionDeclaration { .. } => ASTNodeType::Structure,
|
||||
ASTNode::If { .. } => ASTNodeType::Structure,
|
||||
ASTNode::Loop { .. } => ASTNodeType::Structure,
|
||||
ASTNode::TryCatch { .. } => ASTNodeType::Structure,
|
||||
|
||||
// Expression nodes - 値を生成する表現
|
||||
ASTNode::Literal { .. } => ASTNodeType::Expression,
|
||||
ASTNode::Variable { .. } => ASTNodeType::Expression,
|
||||
ASTNode::BinaryOp { .. } => ASTNodeType::Expression,
|
||||
ASTNode::UnaryOp { .. } => ASTNodeType::Expression,
|
||||
ASTNode::FunctionCall { .. } => ASTNodeType::Expression,
|
||||
ASTNode::MethodCall { .. } => ASTNodeType::Expression,
|
||||
ASTNode::FieldAccess { .. } => ASTNodeType::Expression,
|
||||
ASTNode::New { .. } => ASTNodeType::Expression,
|
||||
ASTNode::This { .. } => ASTNodeType::Expression,
|
||||
ASTNode::Me { .. } => ASTNodeType::Expression,
|
||||
ASTNode::ThisField { .. } => ASTNodeType::Expression,
|
||||
ASTNode::MeField { .. } => ASTNodeType::Expression,
|
||||
|
||||
// Statement nodes - 実行可能なアクション
|
||||
ASTNode::Program { .. } => ASTNodeType::Statement, // プログラム全体
|
||||
ASTNode::Assignment { .. } => ASTNodeType::Statement,
|
||||
ASTNode::Print { .. } => ASTNodeType::Statement,
|
||||
ASTNode::Return { .. } => ASTNodeType::Statement,
|
||||
ASTNode::Break { .. } => ASTNodeType::Statement,
|
||||
ASTNode::GlobalVar { .. } => ASTNodeType::Statement,
|
||||
ASTNode::Include { .. } => ASTNodeType::Statement,
|
||||
ASTNode::Local { .. } => ASTNodeType::Statement,
|
||||
ASTNode::Outbox { .. } => ASTNodeType::Statement,
|
||||
ASTNode::Nowait { .. } => ASTNodeType::Statement,
|
||||
ASTNode::Arrow { .. } => ASTNodeType::Statement,
|
||||
ASTNode::Throw { .. } => ASTNodeType::Statement,
|
||||
ASTNode::AwaitExpression { .. } => ASTNodeType::Expression,
|
||||
}
|
||||
}
|
||||
|
||||
/// 🎯 構造パターンチェック - 2段階パーサー用
|
||||
pub fn is_structure(&self) -> bool {
|
||||
matches!(self.classify(), ASTNodeType::Structure)
|
||||
}
|
||||
|
||||
/// ⚡ 式パターンチェック - 評価エンジン用
|
||||
pub fn is_expression(&self) -> bool {
|
||||
matches!(self.classify(), ASTNodeType::Expression)
|
||||
}
|
||||
|
||||
/// 📝 文パターンチェック - 実行エンジン用
|
||||
pub fn is_statement(&self) -> bool {
|
||||
matches!(self.classify(), ASTNodeType::Statement)
|
||||
}
|
||||
|
||||
/// AST nodeの詳細情報を取得 (デバッグ用)
|
||||
pub fn info(&self) -> String {
|
||||
match self {
|
||||
ASTNode::Program { statements, .. } => {
|
||||
format!("Program({} statements)", statements.len())
|
||||
}
|
||||
ASTNode::Assignment { target, .. } => {
|
||||
format!("Assignment(target: {})", target.info())
|
||||
}
|
||||
ASTNode::Print { .. } => "Print".to_string(),
|
||||
ASTNode::If { .. } => "If".to_string(),
|
||||
ASTNode::Loop { condition: _, body, .. } => {
|
||||
format!("Loop({} statements)", body.len())
|
||||
}
|
||||
ASTNode::Return { value, .. } => {
|
||||
if value.is_some() {
|
||||
"Return(with value)".to_string()
|
||||
} else {
|
||||
"Return(void)".to_string()
|
||||
}
|
||||
}
|
||||
ASTNode::Break { .. } => "Break".to_string(),
|
||||
ASTNode::BoxDeclaration { name, fields, methods, constructors, is_interface, extends, implements, .. } => {
|
||||
let mut desc = if *is_interface {
|
||||
format!("InterfaceBox({}, {} methods", name, methods.len())
|
||||
} else {
|
||||
format!("BoxDeclaration({}, {} fields, {} methods, {} constructors", name, fields.len(), methods.len(), constructors.len())
|
||||
};
|
||||
|
||||
if let Some(parent) = extends {
|
||||
desc.push_str(&format!(", extends {}", parent));
|
||||
}
|
||||
|
||||
if !implements.is_empty() {
|
||||
desc.push_str(&format!(", implements [{}]", implements.join(", ")));
|
||||
}
|
||||
|
||||
desc.push(')');
|
||||
desc
|
||||
}
|
||||
ASTNode::FunctionDeclaration { name, params, body, is_static, .. } => {
|
||||
let static_str = if *is_static { "static " } else { "" };
|
||||
format!("FunctionDeclaration({}{}({}), {} statements)",
|
||||
static_str, name, params.join(", "), body.len())
|
||||
}
|
||||
ASTNode::GlobalVar { name, .. } => {
|
||||
format!("GlobalVar({})", name)
|
||||
}
|
||||
ASTNode::Literal { .. } => "Literal".to_string(),
|
||||
ASTNode::Variable { name, .. } => {
|
||||
format!("Variable({})", name)
|
||||
}
|
||||
ASTNode::UnaryOp { operator, .. } => {
|
||||
format!("UnaryOp({})", operator)
|
||||
}
|
||||
ASTNode::BinaryOp { operator, .. } => {
|
||||
format!("BinaryOp({})", operator)
|
||||
}
|
||||
ASTNode::MethodCall { method, arguments, .. } => {
|
||||
format!("MethodCall({}, {} args)", method, arguments.len())
|
||||
}
|
||||
ASTNode::FieldAccess { field, .. } => {
|
||||
format!("FieldAccess({})", field)
|
||||
}
|
||||
ASTNode::New { class, arguments, type_arguments, .. } => {
|
||||
if type_arguments.is_empty() {
|
||||
format!("New({}, {} args)", class, arguments.len())
|
||||
} else {
|
||||
format!("New({}<{}>, {} args)", class, type_arguments.join(", "), arguments.len())
|
||||
}
|
||||
}
|
||||
ASTNode::This { .. } => "This".to_string(),
|
||||
ASTNode::Me { .. } => "Me".to_string(),
|
||||
ASTNode::ThisField { field, .. } => {
|
||||
format!("ThisField({})", field)
|
||||
}
|
||||
ASTNode::MeField { field, .. } => {
|
||||
format!("MeField({})", field)
|
||||
}
|
||||
ASTNode::Include { filename, .. } => {
|
||||
format!("Include({})", filename)
|
||||
}
|
||||
ASTNode::Local { variables, .. } => {
|
||||
format!("Local({})", variables.join(", "))
|
||||
}
|
||||
ASTNode::Outbox { variables, .. } => {
|
||||
format!("Outbox({})", variables.join(", "))
|
||||
}
|
||||
ASTNode::FunctionCall { name, arguments, .. } => {
|
||||
format!("FunctionCall({}, {} args)", name, arguments.len())
|
||||
}
|
||||
ASTNode::Nowait { variable, .. } => {
|
||||
format!("Nowait({})", variable)
|
||||
}
|
||||
ASTNode::Arrow { .. } => {
|
||||
"Arrow(>>)".to_string()
|
||||
}
|
||||
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
|
||||
let mut desc = format!("TryCatch({} try statements, {} catch clauses",
|
||||
try_body.len(), catch_clauses.len());
|
||||
if finally_body.is_some() {
|
||||
desc.push_str(", has finally");
|
||||
}
|
||||
desc.push(')');
|
||||
desc
|
||||
}
|
||||
ASTNode::Throw { .. } => "Throw".to_string(),
|
||||
ASTNode::AwaitExpression { expression, .. } => {
|
||||
format!("Await({:?})", expression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ASTノードからSpan情報を取得
|
||||
pub fn span(&self) -> Span {
|
||||
match self {
|
||||
ASTNode::Program { span, .. } => *span,
|
||||
ASTNode::Assignment { span, .. } => *span,
|
||||
ASTNode::Print { span, .. } => *span,
|
||||
ASTNode::If { span, .. } => *span,
|
||||
ASTNode::Loop { span, .. } => *span,
|
||||
ASTNode::Return { span, .. } => *span,
|
||||
ASTNode::Break { span, .. } => *span,
|
||||
ASTNode::Nowait { span, .. } => *span,
|
||||
ASTNode::Arrow { span, .. } => *span,
|
||||
ASTNode::TryCatch { span, .. } => *span,
|
||||
ASTNode::Throw { span, .. } => *span,
|
||||
ASTNode::BoxDeclaration { span, .. } => *span,
|
||||
ASTNode::FunctionDeclaration { span, .. } => *span,
|
||||
ASTNode::GlobalVar { span, .. } => *span,
|
||||
ASTNode::Literal { span, .. } => *span,
|
||||
ASTNode::Variable { span, .. } => *span,
|
||||
ASTNode::UnaryOp { span, .. } => *span,
|
||||
ASTNode::BinaryOp { span, .. } => *span,
|
||||
ASTNode::MethodCall { span, .. } => *span,
|
||||
ASTNode::FieldAccess { span, .. } => *span,
|
||||
ASTNode::New { span, .. } => *span,
|
||||
ASTNode::This { span, .. } => *span,
|
||||
ASTNode::Me { span, .. } => *span,
|
||||
ASTNode::ThisField { span, .. } => *span,
|
||||
ASTNode::MeField { span, .. } => *span,
|
||||
ASTNode::Include { span, .. } => *span,
|
||||
ASTNode::Local { span, .. } => *span,
|
||||
ASTNode::Outbox { span, .. } => *span,
|
||||
ASTNode::FunctionCall { span, .. } => *span,
|
||||
ASTNode::AwaitExpression { span, .. } => *span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ASTNode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.info())
|
||||
}
|
||||
}
|
||||
|
||||
impl ASTNode {
|
||||
/// FunctionDeclarationのパラメータ数を取得
|
||||
pub fn get_param_count(&self) -> usize {
|
||||
match self {
|
||||
ASTNode::FunctionDeclaration { params, .. } => params.len(),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Tests =====
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::box_trait::{StringBox, IntegerBox, BoolBox};
|
||||
|
||||
#[test]
|
||||
fn test_ast_node_creation() {
|
||||
// Program node
|
||||
let program = ASTNode::Program {
|
||||
statements: vec![],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert_eq!(program.node_type(), "Program");
|
||||
assert!(program.info().contains("Program(0 statements)"));
|
||||
|
||||
// Variable node
|
||||
let variable = ASTNode::Variable {
|
||||
name: "test_var".to_string(),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert_eq!(variable.node_type(), "Variable");
|
||||
assert!(variable.is_expression());
|
||||
assert!(!variable.is_statement());
|
||||
|
||||
// Assignment node
|
||||
let assignment = ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable { name: "x".to_string(), span: Span::unknown() }),
|
||||
value: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(42),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert_eq!(assignment.node_type(), "Assignment");
|
||||
assert!(!assignment.is_expression());
|
||||
assert!(assignment.is_statement());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_operator() {
|
||||
let add_op = BinaryOperator::Add;
|
||||
assert_eq!(format!("{}", add_op), "+");
|
||||
|
||||
let equals_op = BinaryOperator::Equal;
|
||||
assert_eq!(format!("{}", equals_op), "==");
|
||||
|
||||
let less_equals_op = BinaryOperator::LessEqual;
|
||||
assert_eq!(format!("{}", less_equals_op), "<=");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_ast() {
|
||||
// box TestBox { value }のAST
|
||||
let mut methods = HashMap::new();
|
||||
methods.insert("getValue".to_string(), ASTNode::FunctionDeclaration {
|
||||
name: "getValue".to_string(),
|
||||
params: vec![],
|
||||
body: vec![
|
||||
ASTNode::Return {
|
||||
value: Some(Box::new(ASTNode::FieldAccess {
|
||||
object: Box::new(ASTNode::This { span: Span::unknown() }),
|
||||
field: "value".to_string(),
|
||||
span: Span::unknown(),
|
||||
})),
|
||||
span: Span::unknown(),
|
||||
}
|
||||
],
|
||||
is_static: false, // 通常のメソッド
|
||||
span: Span::unknown(),
|
||||
});
|
||||
|
||||
let box_decl = ASTNode::BoxDeclaration {
|
||||
name: "TestBox".to_string(),
|
||||
fields: vec!["value".to_string()],
|
||||
methods,
|
||||
constructors: HashMap::new(),
|
||||
init_fields: vec![],
|
||||
is_interface: false,
|
||||
extends: None,
|
||||
implements: vec![],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
assert_eq!(box_decl.node_type(), "BoxDeclaration");
|
||||
assert!(box_decl.info().contains("TestBox"));
|
||||
assert!(box_decl.info().contains("1 fields"));
|
||||
assert!(box_decl.info().contains("1 methods"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_method_call() {
|
||||
// obj.getValue()のAST
|
||||
let method_call = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable { name: "obj".to_string(), span: Span::unknown() }),
|
||||
method: "getValue".to_string(),
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
assert_eq!(method_call.node_type(), "MethodCall");
|
||||
assert!(method_call.is_expression());
|
||||
assert!(method_call.info().contains("getValue"));
|
||||
assert!(method_call.info().contains("0 args"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_operation() {
|
||||
// x + y のAST
|
||||
let binary_op = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable { name: "x".to_string(), span: Span::unknown() }),
|
||||
right: Box::new(ASTNode::Variable { name: "y".to_string(), span: Span::unknown() }),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
assert_eq!(binary_op.node_type(), "BinaryOp");
|
||||
assert!(binary_op.is_expression());
|
||||
assert!(binary_op.info().contains("+"));
|
||||
}
|
||||
}
|
||||
1444
src/box_trait.rs
Normal file
1444
src/box_trait.rs
Normal file
File diff suppressed because it is too large
Load Diff
67
src/boxes/bool_box.rs
Normal file
67
src/boxes/bool_box.rs
Normal file
@ -0,0 +1,67 @@
|
||||
// BoolBox implementation - Boolean values in Nyash
|
||||
use crate::box_trait::NyashBox;
|
||||
use std::any::Any;
|
||||
use std::fmt::Display;
|
||||
|
||||
/// Boolean values in Nyash - true/false
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct BoolBox {
|
||||
pub value: bool,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl BoolBox {
|
||||
pub fn new(value: bool) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { value, id }
|
||||
}
|
||||
|
||||
pub fn true_box() -> Self {
|
||||
Self::new(true)
|
||||
}
|
||||
|
||||
pub fn false_box() -> Self {
|
||||
Self::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for BoolBox {
|
||||
fn to_string_box(&self) -> crate::box_trait::StringBox {
|
||||
crate::box_trait::StringBox::new(if self.value { "true" } else { "false" })
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> crate::box_trait::BoolBox {
|
||||
if let Some(other_bool) = other.as_any().downcast_ref::<BoolBox>() {
|
||||
crate::box_trait::BoolBox::new(self.value == other_bool.value)
|
||||
} else {
|
||||
crate::box_trait::BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"BoolBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BoolBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", if self.value { "true" } else { "false" })
|
||||
}
|
||||
}
|
||||
154
src/boxes/console_box.rs
Normal file
154
src/boxes/console_box.rs
Normal file
@ -0,0 +1,154 @@
|
||||
/*!
|
||||
* ConsoleBox - ブラウザコンソール制御Box
|
||||
*
|
||||
* WebAssembly環境でブラウザのconsole APIにアクセス
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox};
|
||||
use std::any::Any;
|
||||
use std::fmt::Display;
|
||||
|
||||
// 🌐 Browser console access Box
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConsoleBox {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl ConsoleBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
Self { id }
|
||||
}
|
||||
|
||||
/// Log messages to browser console
|
||||
pub fn log(&self, message: &str) {
|
||||
web_sys::console::log_1(&message.into());
|
||||
}
|
||||
|
||||
/// Log warning to browser console
|
||||
pub fn warn(&self, message: &str) {
|
||||
web_sys::console::warn_1(&message.into());
|
||||
}
|
||||
|
||||
/// Log error to browser console
|
||||
pub fn error(&self, message: &str) {
|
||||
web_sys::console::error_1(&message.into());
|
||||
}
|
||||
|
||||
/// Clear browser console
|
||||
pub fn clear(&self) {
|
||||
web_sys::console::clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl NyashBox for ConsoleBox {
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("[ConsoleBox - Browser Console Interface]")
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
BoolBox::new(other.as_any().is::<ConsoleBox>())
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"ConsoleBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
// Non-WASM版 - モックアップ実装
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConsoleBox {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl ConsoleBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
Self { id }
|
||||
}
|
||||
|
||||
/// Mock log method for non-WASM environments
|
||||
pub fn log(&self, message: &str) {
|
||||
println!("[Console LOG] {}", message);
|
||||
}
|
||||
|
||||
pub fn warn(&self, message: &str) {
|
||||
println!("[Console WARN] {}", message);
|
||||
}
|
||||
|
||||
pub fn error(&self, message: &str) {
|
||||
println!("[Console ERROR] {}", message);
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
println!("[Console CLEAR]");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl NyashBox for ConsoleBox {
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("[ConsoleBox - Mock Implementation]")
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
BoolBox::new(other.as_any().is::<ConsoleBox>())
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"ConsoleBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Display implementations for both WASM and non-WASM versions
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl Display for ConsoleBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "[ConsoleBox - Browser Console Interface]")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Display for ConsoleBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "[ConsoleBox - Mock Implementation]")
|
||||
}
|
||||
}
|
||||
246
src/boxes/debug_box.rs
Normal file
246
src/boxes/debug_box.rs
Normal file
@ -0,0 +1,246 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use chrono::Local;
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox, VoidBox};
|
||||
use crate::interpreter::RuntimeError;
|
||||
use crate::instance::InstanceBox;
|
||||
use std::any::Any;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DebugBox {
|
||||
tracking_enabled: Arc<Mutex<bool>>,
|
||||
tracked_boxes: Arc<Mutex<HashMap<String, TrackedBoxInfo>>>,
|
||||
breakpoints: Arc<Mutex<Vec<String>>>,
|
||||
call_stack: Arc<Mutex<Vec<CallInfo>>>,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TrackedBoxInfo {
|
||||
box_type: String,
|
||||
created_at: String,
|
||||
fields: String,
|
||||
value_repr: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct CallInfo {
|
||||
function_name: String,
|
||||
args: Vec<String>,
|
||||
timestamp: String,
|
||||
}
|
||||
|
||||
impl DebugBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
DebugBox {
|
||||
tracking_enabled: Arc::new(Mutex::new(false)),
|
||||
tracked_boxes: Arc::new(Mutex::new(HashMap::new())),
|
||||
breakpoints: Arc::new(Mutex::new(Vec::new())),
|
||||
call_stack: Arc::new(Mutex::new(Vec::new())),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_tracking(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let mut enabled = self.tracking_enabled.lock().unwrap();
|
||||
*enabled = true;
|
||||
println!("[DEBUG] Tracking started");
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
pub fn stop_tracking(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let mut enabled = self.tracking_enabled.lock().unwrap();
|
||||
*enabled = false;
|
||||
println!("[DEBUG] Tracking stopped");
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
pub fn track_box(&self, box_value: &dyn NyashBox, name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let enabled = self.tracking_enabled.lock().unwrap();
|
||||
if !*enabled {
|
||||
return Ok(Box::new(VoidBox::new()));
|
||||
}
|
||||
|
||||
let mut tracked = self.tracked_boxes.lock().unwrap();
|
||||
|
||||
let info = TrackedBoxInfo {
|
||||
box_type: box_value.type_name().to_string(),
|
||||
created_at: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
|
||||
fields: self.get_box_fields(box_value),
|
||||
value_repr: box_value.to_string_box().value,
|
||||
};
|
||||
|
||||
tracked.insert(name.to_string(), info);
|
||||
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
fn get_box_fields(&self, box_value: &dyn NyashBox) -> String {
|
||||
// Try to downcast to InstanceBox to get fields
|
||||
if let Some(instance) = box_value.as_any().downcast_ref::<InstanceBox>() {
|
||||
let fields = instance.fields.lock().unwrap();
|
||||
let field_names: Vec<String> = fields.keys().cloned().collect();
|
||||
field_names.join(", ")
|
||||
} else {
|
||||
"N/A".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump_all(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let tracked = self.tracked_boxes.lock().unwrap();
|
||||
let mut output = String::from("=== Box State Dump ===\n");
|
||||
output.push_str(&format!("Time: {}\n", Local::now().format("%Y-%m-%d %H:%M:%S")));
|
||||
output.push_str(&format!("Total tracked boxes: {}\n\n", tracked.len()));
|
||||
|
||||
for (name, info) in tracked.iter() {
|
||||
output.push_str(&format!("Box: {}\n", name));
|
||||
output.push_str(&format!(" Type: {}\n", info.box_type));
|
||||
output.push_str(&format!(" Created: {}\n", info.created_at));
|
||||
output.push_str(&format!(" Fields: {}\n", info.fields));
|
||||
output.push_str(&format!(" Value: {}\n", info.value_repr));
|
||||
output.push_str("\n");
|
||||
}
|
||||
|
||||
Ok(Box::new(StringBox::new(output)))
|
||||
}
|
||||
|
||||
pub fn save_to_file(&self, filename: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let dump_result = self.dump_all()?;
|
||||
let content = dump_result.to_string_box().value;
|
||||
|
||||
// Write to file using std::fs
|
||||
std::fs::write(filename, content)
|
||||
.map_err(|e| RuntimeError::InvalidOperation {
|
||||
message: format!("Failed to write debug file: {}", e),
|
||||
})?;
|
||||
|
||||
println!("[DEBUG] Saved debug info to {}", filename);
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
pub fn watch(&self, box_value: &dyn NyashBox, name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let value_str = box_value.to_string_box().value;
|
||||
let type_name = box_value.type_name();
|
||||
|
||||
println!("[DEBUG] Watching {} ({}): {}", name, type_name, value_str);
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
pub fn memory_report(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let tracked = self.tracked_boxes.lock().unwrap();
|
||||
let mut report = String::from("=== Memory Report ===\n");
|
||||
report.push_str(&format!("Tracked boxes: {}\n", tracked.len()));
|
||||
|
||||
// Count by type
|
||||
let mut type_counts: HashMap<String, usize> = HashMap::new();
|
||||
for info in tracked.values() {
|
||||
*type_counts.entry(info.box_type.clone()).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
report.push_str("\nBoxes by type:\n");
|
||||
for (box_type, count) in type_counts.iter() {
|
||||
report.push_str(&format!(" {}: {}\n", box_type, count));
|
||||
}
|
||||
|
||||
Ok(Box::new(StringBox::new(report)))
|
||||
}
|
||||
|
||||
// Advanced features
|
||||
pub fn set_breakpoint(&self, function_name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let mut breakpoints = self.breakpoints.lock().unwrap();
|
||||
breakpoints.push(function_name.to_string());
|
||||
println!("[DEBUG] Breakpoint set at function: {}", function_name);
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
pub fn trace_call(&self, function_name: &str, args: Vec<String>) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let mut stack = self.call_stack.lock().unwrap();
|
||||
stack.push(CallInfo {
|
||||
function_name: function_name.to_string(),
|
||||
args,
|
||||
timestamp: Local::now().format("%H:%M:%S.%3f").to_string(),
|
||||
});
|
||||
|
||||
// Keep only last 100 calls to prevent memory issues
|
||||
if stack.len() > 100 {
|
||||
stack.remove(0);
|
||||
}
|
||||
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
pub fn show_call_stack(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let stack = self.call_stack.lock().unwrap();
|
||||
let mut output = String::from("=== Call Stack ===\n");
|
||||
|
||||
for (i, call) in stack.iter().enumerate() {
|
||||
output.push_str(&format!("{}: [{}] {}({})\n",
|
||||
i,
|
||||
call.timestamp,
|
||||
call.function_name,
|
||||
call.args.join(", ")
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Box::new(StringBox::new(output)))
|
||||
}
|
||||
|
||||
pub fn clear(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let mut tracked = self.tracked_boxes.lock().unwrap();
|
||||
tracked.clear();
|
||||
|
||||
let mut stack = self.call_stack.lock().unwrap();
|
||||
stack.clear();
|
||||
|
||||
println!("[DEBUG] Cleared all debug information");
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
pub fn is_tracking(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let enabled = self.tracking_enabled.lock().unwrap();
|
||||
Ok(Box::new(BoolBox::new(*enabled)))
|
||||
}
|
||||
|
||||
pub fn get_tracked_count(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let tracked = self.tracked_boxes.lock().unwrap();
|
||||
Ok(Box::new(crate::box_trait::IntegerBox::new(tracked.len() as i64)))
|
||||
}
|
||||
}
|
||||
|
||||
// Implement NyashBox trait for DebugBox
|
||||
impl NyashBox for DebugBox {
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
let tracked = self.tracked_boxes.lock().unwrap();
|
||||
StringBox::new(format!("DebugBox[{} tracked]", tracked.len()))
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_debug) = other.as_any().downcast_ref::<DebugBox>() {
|
||||
BoolBox::new(self.id == other_debug.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"DebugBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
64
src/boxes/integer_box.rs
Normal file
64
src/boxes/integer_box.rs
Normal file
@ -0,0 +1,64 @@
|
||||
// IntegerBox implementation - Integer values in Nyash
|
||||
use crate::box_trait::NyashBox;
|
||||
use std::any::Any;
|
||||
use std::fmt::Display;
|
||||
|
||||
/// Integer values in Nyash - 64-bit signed integers
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct IntegerBox {
|
||||
pub value: i64,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl IntegerBox {
|
||||
pub fn new(value: i64) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { value, id }
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self::new(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for IntegerBox {
|
||||
fn to_string_box(&self) -> crate::box_trait::StringBox {
|
||||
crate::box_trait::StringBox::new(self.value.to_string())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> crate::box_trait::BoolBox {
|
||||
use crate::box_trait::BoolBox;
|
||||
if let Some(other_int) = other.as_any().downcast_ref::<IntegerBox>() {
|
||||
BoolBox::new(self.value == other_int.value)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"IntegerBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IntegerBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
175
src/boxes/map_box.rs
Normal file
175
src/boxes/map_box.rs
Normal file
@ -0,0 +1,175 @@
|
||||
/*!
|
||||
* Nyash Map Box - Key-Value store implementation
|
||||
*
|
||||
* キーバリューストアを提供するBox型
|
||||
* Everything is Box哲学に基づくマップデータ構造
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, ArrayBox};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// キーバリューストアを表すBox
|
||||
#[derive(Clone)]
|
||||
pub struct MapBox {
|
||||
data: Arc<Mutex<HashMap<String, Box<dyn NyashBox>>>>,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl MapBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self {
|
||||
data: Arc::new(Mutex::new(HashMap::new())),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// 値を設定
|
||||
pub fn set(&self, key: Box<dyn NyashBox>, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
let key_str = key.to_string_box().value;
|
||||
self.data.lock().unwrap().insert(key_str.clone(), value);
|
||||
Box::new(StringBox::new(&format!("Set key: {}", key_str)))
|
||||
}
|
||||
|
||||
/// 値を取得
|
||||
pub fn get(&self, key: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
let key_str = key.to_string_box().value;
|
||||
match self.data.lock().unwrap().get(&key_str) {
|
||||
Some(value) => value.clone_box(),
|
||||
None => Box::new(StringBox::new(&format!("Key not found: {}", key_str))),
|
||||
}
|
||||
}
|
||||
|
||||
/// キーが存在するかチェック
|
||||
pub fn has(&self, key: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
let key_str = key.to_string_box().value;
|
||||
Box::new(BoolBox::new(self.data.lock().unwrap().contains_key(&key_str)))
|
||||
}
|
||||
|
||||
/// キーを削除
|
||||
pub fn delete(&self, key: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
let key_str = key.to_string_box().value;
|
||||
match self.data.lock().unwrap().remove(&key_str) {
|
||||
Some(_) => Box::new(StringBox::new(&format!("Deleted key: {}", key_str))),
|
||||
None => Box::new(StringBox::new(&format!("Key not found: {}", key_str))),
|
||||
}
|
||||
}
|
||||
|
||||
/// 全てのキーを取得
|
||||
pub fn keys(&self) -> Box<dyn NyashBox> {
|
||||
let keys: Vec<String> = self.data.lock().unwrap().keys().cloned().collect();
|
||||
let array = ArrayBox::new();
|
||||
for key in keys {
|
||||
array.push(Box::new(StringBox::new(&key)));
|
||||
}
|
||||
Box::new(array)
|
||||
}
|
||||
|
||||
/// 全ての値を取得
|
||||
pub fn values(&self) -> Box<dyn NyashBox> {
|
||||
let values: Vec<Box<dyn NyashBox>> = self.data.lock().unwrap()
|
||||
.values()
|
||||
.map(|v| v.clone_box())
|
||||
.collect();
|
||||
let array = ArrayBox::new();
|
||||
for value in values {
|
||||
array.push(value);
|
||||
}
|
||||
Box::new(array)
|
||||
}
|
||||
|
||||
/// サイズを取得
|
||||
pub fn size(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.data.lock().unwrap().len() as i64))
|
||||
}
|
||||
|
||||
/// 全てクリア
|
||||
pub fn clear(&self) -> Box<dyn NyashBox> {
|
||||
self.data.lock().unwrap().clear();
|
||||
Box::new(StringBox::new("Map cleared"))
|
||||
}
|
||||
|
||||
/// 各要素に対して関数を実行
|
||||
pub fn forEach(&self, _callback: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
// 簡易実装:callbackの実行はスキップ
|
||||
let count = self.data.lock().unwrap().len();
|
||||
Box::new(StringBox::new(&format!("Iterated over {} items", count)))
|
||||
}
|
||||
|
||||
/// JSON文字列に変換
|
||||
pub fn toJSON(&self) -> Box<dyn NyashBox> {
|
||||
let data = self.data.lock().unwrap();
|
||||
let mut json_parts = Vec::new();
|
||||
|
||||
for (key, value) in data.iter() {
|
||||
let value_str = value.to_string_box().value;
|
||||
// 値が数値の場合はそのまま、文字列の場合は引用符で囲む
|
||||
let formatted_value = if value.as_any().downcast_ref::<IntegerBox>().is_some()
|
||||
|| value.as_any().downcast_ref::<BoolBox>().is_some() {
|
||||
value_str
|
||||
} else {
|
||||
format!("\"{}\"", value_str.replace("\"", "\\\""))
|
||||
};
|
||||
json_parts.push(format!("\"{}\":{}", key, formatted_value));
|
||||
}
|
||||
|
||||
Box::new(StringBox::new(&format!("{{{}}}", json_parts.join(","))))
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for MapBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"MapBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
let size = self.data.lock().unwrap().len();
|
||||
StringBox::new(&format!("MapBox(size={})", size))
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_map) = other.as_any().downcast_ref::<MapBox>() {
|
||||
// 同じインスタンスかチェック(データの共有を考慮)
|
||||
BoolBox::new(self.id == other_map.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MapBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_string_box().value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for MapBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let data = self.data.lock().unwrap();
|
||||
f.debug_struct("MapBox")
|
||||
.field("id", &self.id)
|
||||
.field("size", &data.len())
|
||||
.field("keys", &data.keys().collect::<Vec<_>>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
410
src/boxes/math_box.rs
Normal file
410
src/boxes/math_box.rs
Normal file
@ -0,0 +1,410 @@
|
||||
/*!
|
||||
* Nyash Math Box - Mathematical operations
|
||||
*
|
||||
* 数学演算を提供するBox型
|
||||
* Everything is Box哲学に基づく数学ライブラリ
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
|
||||
/// 数学演算を提供するBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MathBox {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl MathBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { id }
|
||||
}
|
||||
|
||||
/// 絶対値を計算
|
||||
pub fn abs(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(IntegerBox::new(int_box.value.abs()))
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(FloatBox::new(float_box.value.abs()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: abs() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 最大値を返す
|
||||
pub fn max(&self, a: Box<dyn NyashBox>, b: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let (Some(a_int), Some(b_int)) = (
|
||||
a.as_any().downcast_ref::<IntegerBox>(),
|
||||
b.as_any().downcast_ref::<IntegerBox>()
|
||||
) {
|
||||
Box::new(IntegerBox::new(a_int.value.max(b_int.value)))
|
||||
} else if let (Some(a_float), Some(b_float)) = (
|
||||
a.as_any().downcast_ref::<FloatBox>(),
|
||||
b.as_any().downcast_ref::<FloatBox>()
|
||||
) {
|
||||
Box::new(FloatBox::new(a_float.value.max(b_float.value)))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: max() requires numeric inputs"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 最小値を返す
|
||||
pub fn min(&self, a: Box<dyn NyashBox>, b: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let (Some(a_int), Some(b_int)) = (
|
||||
a.as_any().downcast_ref::<IntegerBox>(),
|
||||
b.as_any().downcast_ref::<IntegerBox>()
|
||||
) {
|
||||
Box::new(IntegerBox::new(a_int.value.min(b_int.value)))
|
||||
} else if let (Some(a_float), Some(b_float)) = (
|
||||
a.as_any().downcast_ref::<FloatBox>(),
|
||||
b.as_any().downcast_ref::<FloatBox>()
|
||||
) {
|
||||
Box::new(FloatBox::new(a_float.value.min(b_float.value)))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: min() requires numeric inputs"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 累乗を計算
|
||||
pub fn pow(&self, base: Box<dyn NyashBox>, exp: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let (Some(base_int), Some(exp_int)) = (
|
||||
base.as_any().downcast_ref::<IntegerBox>(),
|
||||
exp.as_any().downcast_ref::<IntegerBox>()
|
||||
) {
|
||||
if exp_int.value >= 0 {
|
||||
let result = (base_int.value as f64).powi(exp_int.value as i32);
|
||||
Box::new(FloatBox::new(result))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: negative exponent"))
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: pow() requires numeric inputs"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 平方根を計算
|
||||
pub fn sqrt(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
if int_box.value >= 0 {
|
||||
Box::new(FloatBox::new((int_box.value as f64).sqrt()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: sqrt() of negative number"))
|
||||
}
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
if float_box.value >= 0.0 {
|
||||
Box::new(FloatBox::new(float_box.value.sqrt()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: sqrt() of negative number"))
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: sqrt() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 円周率πを返す
|
||||
#[allow(non_snake_case)]
|
||||
pub fn getPi(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(FloatBox::new(std::f64::consts::PI))
|
||||
}
|
||||
|
||||
/// 自然対数の底eを返す
|
||||
#[allow(non_snake_case)]
|
||||
pub fn getE(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(FloatBox::new(std::f64::consts::E))
|
||||
}
|
||||
|
||||
/// サイン(正弦)
|
||||
pub fn sin(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(FloatBox::new((int_box.value as f64).sin()))
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(FloatBox::new(float_box.value.sin()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: sin() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// コサイン(余弦)
|
||||
pub fn cos(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(FloatBox::new((int_box.value as f64).cos()))
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(FloatBox::new(float_box.value.cos()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: cos() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// タンジェント(正接)
|
||||
pub fn tan(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(FloatBox::new((int_box.value as f64).tan()))
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(FloatBox::new(float_box.value.tan()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: tan() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 自然対数
|
||||
pub fn log(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
if int_box.value > 0 {
|
||||
Box::new(FloatBox::new((int_box.value as f64).ln()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: log() of non-positive number"))
|
||||
}
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
if float_box.value > 0.0 {
|
||||
Box::new(FloatBox::new(float_box.value.ln()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: log() of non-positive number"))
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: log() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 常用対数(底10)
|
||||
pub fn log10(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
if int_box.value > 0 {
|
||||
Box::new(FloatBox::new((int_box.value as f64).log10()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: log10() of non-positive number"))
|
||||
}
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
if float_box.value > 0.0 {
|
||||
Box::new(FloatBox::new(float_box.value.log10()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: log10() of non-positive number"))
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: log10() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 指数関数(e^x)
|
||||
pub fn exp(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(FloatBox::new((int_box.value as f64).exp()))
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(FloatBox::new(float_box.value.exp()))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: exp() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 床関数(切り下げ)
|
||||
pub fn floor(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(IntegerBox::new(int_box.value)) // 整数はそのまま
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(IntegerBox::new(float_box.value.floor() as i64))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: floor() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 天井関数(切り上げ)
|
||||
pub fn ceil(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(IntegerBox::new(int_box.value)) // 整数はそのまま
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(IntegerBox::new(float_box.value.ceil() as i64))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: ceil() requires numeric input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 四捨五入
|
||||
pub fn round(&self, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(IntegerBox::new(int_box.value)) // 整数はそのまま
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
Box::new(IntegerBox::new(float_box.value.round() as i64))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: round() requires numeric input"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for MathBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"MathBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("MathBox()")
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_math) = other.as_any().downcast_ref::<MathBox>() {
|
||||
BoolBox::new(self.id == other_math.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MathBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "MathBox()")
|
||||
}
|
||||
}
|
||||
|
||||
/// 浮動小数点数Box
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FloatBox {
|
||||
pub value: f64,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl FloatBox {
|
||||
pub fn new(value: f64) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { value, id }
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for FloatBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"FloatBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(&self.value.to_string())
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_float) = other.as_any().downcast_ref::<FloatBox>() {
|
||||
BoolBox::new((self.value - other_float.value).abs() < f64::EPSILON)
|
||||
} else if let Some(other_int) = other.as_any().downcast_ref::<IntegerBox>() {
|
||||
BoolBox::new((self.value - other_int.value as f64).abs() < f64::EPSILON)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FloatBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
/// 範囲を表すBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RangeBox {
|
||||
pub start: i64,
|
||||
pub end: i64,
|
||||
pub step: i64,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl RangeBox {
|
||||
pub fn new(start: i64, end: i64, step: i64) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { start, end, step, id }
|
||||
}
|
||||
|
||||
/// イテレータとして値を生成
|
||||
pub fn iter(&self) -> Vec<i64> {
|
||||
let mut result = Vec::new();
|
||||
let mut current = self.start;
|
||||
|
||||
if self.step > 0 {
|
||||
while current < self.end {
|
||||
result.push(current);
|
||||
current += self.step;
|
||||
}
|
||||
} else if self.step < 0 {
|
||||
while current > self.end {
|
||||
result.push(current);
|
||||
current += self.step;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for RangeBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"RangeBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(&format!("Range({}, {}, {})", self.start, self.end, self.step))
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_range) = other.as_any().downcast_ref::<RangeBox>() {
|
||||
BoolBox::new(
|
||||
self.start == other_range.start &&
|
||||
self.end == other_range.end &&
|
||||
self.step == other_range.step
|
||||
)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RangeBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Range({}, {}, {})", self.start, self.end, self.step)
|
||||
}
|
||||
}
|
||||
46
src/boxes/mod.rs
Normal file
46
src/boxes/mod.rs
Normal file
@ -0,0 +1,46 @@
|
||||
// Nyash Box Implementations Module
|
||||
// Everything is Box哲学に基づく各Box型の実装
|
||||
|
||||
// Nyashは意図的にJavaScript/TypeScriptスタイルのcamelCase命名規約を採用
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
// 各Boxモジュールを宣言
|
||||
pub mod string_box;
|
||||
pub mod integer_box;
|
||||
pub mod bool_box;
|
||||
pub mod math_box;
|
||||
pub mod time_box;
|
||||
pub mod debug_box;
|
||||
pub mod random_box;
|
||||
pub mod sound_box;
|
||||
pub mod map_box;
|
||||
pub mod console_box;
|
||||
|
||||
// Web専用Box群(ブラウザ環境でのみ利用可能)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod web;
|
||||
|
||||
// 共通で使う型とトレイトを再エクスポート
|
||||
pub use string_box::StringBox;
|
||||
pub use integer_box::IntegerBox;
|
||||
pub use bool_box::BoolBox;
|
||||
pub use math_box::MathBox;
|
||||
pub use time_box::TimeBox;
|
||||
pub use debug_box::DebugBox;
|
||||
pub use random_box::RandomBox;
|
||||
pub use sound_box::SoundBox;
|
||||
pub use map_box::MapBox;
|
||||
pub use console_box::ConsoleBox;
|
||||
|
||||
// Web Box群の再エクスポート(WASM環境のみ)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use web::{WebDisplayBox, WebConsoleBox, WebCanvasBox};
|
||||
|
||||
pub mod null_box;
|
||||
|
||||
// 今後追加予定のBox型(コメントアウト)
|
||||
// pub mod array_box;
|
||||
// pub use array_box::ArrayBox;
|
||||
|
||||
// null関数も再エクスポート
|
||||
pub use null_box::{NullBox, null};
|
||||
149
src/boxes/null_box.rs
Normal file
149
src/boxes/null_box.rs
Normal file
@ -0,0 +1,149 @@
|
||||
/*!
|
||||
* Nyash Null Box - Null value representation
|
||||
*
|
||||
* null値を表現するBox型
|
||||
* Everything is Box哲学に基づくnull実装
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
|
||||
/// null値を表現するBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NullBox {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl NullBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { id }
|
||||
}
|
||||
|
||||
/// null値かどうかを判定
|
||||
pub fn is_null(&self) -> bool {
|
||||
true // NullBoxは常にnull
|
||||
}
|
||||
|
||||
/// 値がnullでないかを判定
|
||||
pub fn is_not_null(&self) -> bool {
|
||||
false // NullBoxは常にnull
|
||||
}
|
||||
|
||||
/// 他の値がnullかどうかを判定
|
||||
pub fn check_null(value: &dyn NyashBox) -> bool {
|
||||
value.as_any().downcast_ref::<NullBox>().is_some()
|
||||
}
|
||||
|
||||
/// 他の値がnullでないかを判定
|
||||
pub fn check_not_null(value: &dyn NyashBox) -> bool {
|
||||
!Self::check_null(value)
|
||||
}
|
||||
|
||||
/// null安全な値の取得
|
||||
pub fn get_or_default(
|
||||
value: &dyn NyashBox,
|
||||
default: Box<dyn NyashBox>
|
||||
) -> Box<dyn NyashBox> {
|
||||
if Self::check_null(value) {
|
||||
default
|
||||
} else {
|
||||
value.clone_box()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for NullBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"NullBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("null")
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
// すべてのNullBoxは等しい
|
||||
BoolBox::new(other.as_any().downcast_ref::<NullBox>().is_some())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for NullBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "null")
|
||||
}
|
||||
}
|
||||
|
||||
// グローバルnullインスタンス用の関数
|
||||
pub fn null() -> Box<dyn NyashBox> {
|
||||
Box::new(NullBox::new())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::box_trait::IntegerBox;
|
||||
|
||||
#[test]
|
||||
fn test_null_creation() {
|
||||
let null_box = NullBox::new();
|
||||
assert!(null_box.is_null());
|
||||
assert!(!null_box.is_not_null());
|
||||
assert_eq!(null_box.to_string_box().value, "null");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_null_check() {
|
||||
let null_box = null();
|
||||
let int_box = Box::new(IntegerBox::new(42));
|
||||
|
||||
assert!(NullBox::check_null(null_box.as_ref()));
|
||||
assert!(!NullBox::check_null(int_box.as_ref()));
|
||||
|
||||
assert!(!NullBox::check_not_null(null_box.as_ref()));
|
||||
assert!(NullBox::check_not_null(int_box.as_ref()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_null_equality() {
|
||||
let null1 = NullBox::new();
|
||||
let null2 = NullBox::new();
|
||||
let int_box = IntegerBox::new(42);
|
||||
|
||||
assert!(null1.equals(&null2).value);
|
||||
assert!(!null1.equals(&int_box).value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_or_default() {
|
||||
let null_box = null();
|
||||
let default_value = Box::new(IntegerBox::new(100));
|
||||
let actual_value = Box::new(IntegerBox::new(42));
|
||||
|
||||
// nullの場合はデフォルト値を返す
|
||||
let result1 = NullBox::get_or_default(null_box.as_ref(), default_value.clone());
|
||||
assert_eq!(result1.to_string_box().value, "100");
|
||||
|
||||
// null以外の場合は元の値を返す
|
||||
let result2 = NullBox::get_or_default(actual_value.as_ref(), default_value);
|
||||
assert_eq!(result2.to_string_box().value, "42");
|
||||
}
|
||||
}
|
||||
225
src/boxes/random_box.rs
Normal file
225
src/boxes/random_box.rs
Normal file
@ -0,0 +1,225 @@
|
||||
/*!
|
||||
* Nyash Random Box - Random number generation
|
||||
*
|
||||
* 乱数生成を提供するBox型
|
||||
* Everything is Box哲学に基づく乱数ライブラリ
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, ArrayBox};
|
||||
use crate::boxes::math_box::FloatBox;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// 乱数生成を提供するBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RandomBox {
|
||||
// 簡易線形合同法による疑似乱数生成器
|
||||
seed: Arc<Mutex<u64>>,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl RandomBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
// 現在時刻を種として使用
|
||||
let seed = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos() as u64;
|
||||
|
||||
Self {
|
||||
seed: Arc::new(Mutex::new(seed)),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// 種を設定
|
||||
pub fn seed(&self, new_seed: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = new_seed.as_any().downcast_ref::<IntegerBox>() {
|
||||
*self.seed.lock().unwrap() = int_box.value as u64;
|
||||
Box::new(StringBox::new("Seed set"))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: seed() requires integer input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 次の乱数を生成(線形合同法)
|
||||
fn next_random(&self) -> u64 {
|
||||
let mut seed = self.seed.lock().unwrap();
|
||||
// 線形合同法の定数(Numerical Recipes より)
|
||||
*seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
|
||||
*seed
|
||||
}
|
||||
|
||||
/// 0.0-1.0の浮動小数点乱数
|
||||
pub fn random(&self) -> Box<dyn NyashBox> {
|
||||
let r = self.next_random();
|
||||
let normalized = (r as f64) / (u64::MAX as f64);
|
||||
Box::new(FloatBox::new(normalized))
|
||||
}
|
||||
|
||||
/// 指定範囲の整数乱数
|
||||
pub fn randInt(&self, min: Box<dyn NyashBox>, max: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let (Some(min_int), Some(max_int)) = (
|
||||
min.as_any().downcast_ref::<IntegerBox>(),
|
||||
max.as_any().downcast_ref::<IntegerBox>()
|
||||
) {
|
||||
if min_int.value > max_int.value {
|
||||
return Box::new(StringBox::new("Error: min must be <= max"));
|
||||
}
|
||||
|
||||
let range = (max_int.value - min_int.value + 1) as u64;
|
||||
let r = self.next_random() % range;
|
||||
Box::new(IntegerBox::new(min_int.value + r as i64))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: randInt() requires two integer inputs"))
|
||||
}
|
||||
}
|
||||
|
||||
/// true/falseのランダム選択
|
||||
pub fn randBool(&self) -> Box<dyn NyashBox> {
|
||||
let r = self.next_random();
|
||||
Box::new(BoolBox::new(r % 2 == 0))
|
||||
}
|
||||
|
||||
/// 配列からランダム選択
|
||||
pub fn choice(&self, array: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(array_box) = array.as_any().downcast_ref::<ArrayBox>() {
|
||||
let length = array_box.length().to_string_box().value.parse::<i64>().unwrap_or(0);
|
||||
if length == 0 {
|
||||
return Box::new(StringBox::new("Error: cannot choose from empty array"));
|
||||
}
|
||||
|
||||
let index = self.next_random() % (length as u64);
|
||||
match array_box.get(index as usize) {
|
||||
Some(element) => element,
|
||||
None => Box::new(StringBox::new("Error: index out of bounds")),
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: choice() requires array input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 配列をシャッフル
|
||||
pub fn shuffle(&self, array: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(array_box) = array.as_any().downcast_ref::<ArrayBox>() {
|
||||
let length = array_box.length().to_string_box().value.parse::<i64>().unwrap_or(0);
|
||||
if length <= 1 {
|
||||
return array;
|
||||
}
|
||||
|
||||
// 新しい配列を作成
|
||||
let shuffled = ArrayBox::new();
|
||||
|
||||
// 元の配列の要素を全て新しい配列にコピー
|
||||
for i in 0..length {
|
||||
if let Some(element) = array_box.get(i as usize) {
|
||||
shuffled.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
// 簡易シャッフル実装(完全なFisher-Yatesは複雑なので)
|
||||
// 代わりに、元の配列からランダムに選んで新しい配列を作る
|
||||
let result = ArrayBox::new();
|
||||
let mut remaining_indices: Vec<usize> = (0..length as usize).collect();
|
||||
|
||||
while !remaining_indices.is_empty() {
|
||||
let random_idx = (self.next_random() % remaining_indices.len() as u64) as usize;
|
||||
let actual_idx = remaining_indices.remove(random_idx);
|
||||
if let Some(element) = array_box.get(actual_idx) {
|
||||
result.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(result)
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: shuffle() requires array input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// ランダムな文字列生成
|
||||
pub fn randString(&self, length: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(len_int) = length.as_any().downcast_ref::<IntegerBox>() {
|
||||
if len_int.value < 0 {
|
||||
return Box::new(StringBox::new("Error: length must be positive"));
|
||||
}
|
||||
|
||||
let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
let char_vec: Vec<char> = chars.chars().collect();
|
||||
let mut result = String::new();
|
||||
|
||||
for _ in 0..len_int.value {
|
||||
let index = self.next_random() % (char_vec.len() as u64);
|
||||
result.push(char_vec[index as usize]);
|
||||
}
|
||||
|
||||
Box::new(StringBox::new(&result))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: randString() requires integer length"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 指定確率でtrue
|
||||
pub fn probability(&self, prob: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(float_box) = prob.as_any().downcast_ref::<FloatBox>() {
|
||||
if float_box.value < 0.0 || float_box.value > 1.0 {
|
||||
return Box::new(StringBox::new("Error: probability must be 0.0-1.0"));
|
||||
}
|
||||
|
||||
let r = self.next_random() as f64 / u64::MAX as f64;
|
||||
Box::new(BoolBox::new(r < float_box.value))
|
||||
} else if let Some(int_box) = prob.as_any().downcast_ref::<IntegerBox>() {
|
||||
let prob_val = int_box.value as f64;
|
||||
if prob_val < 0.0 || prob_val > 1.0 {
|
||||
return Box::new(StringBox::new("Error: probability must be 0.0-1.0"));
|
||||
}
|
||||
|
||||
let r = self.next_random() as f64 / u64::MAX as f64;
|
||||
Box::new(BoolBox::new(r < prob_val))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: probability() requires numeric input"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for RandomBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"RandomBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("RandomBox()")
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_random) = other.as_any().downcast_ref::<RandomBox>() {
|
||||
BoolBox::new(self.id == other_random.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RandomBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "RandomBox()")
|
||||
}
|
||||
}
|
||||
221
src/boxes/sound_box.rs
Normal file
221
src/boxes/sound_box.rs
Normal file
@ -0,0 +1,221 @@
|
||||
/*!
|
||||
* Nyash Sound Box - Simple sound generation
|
||||
*
|
||||
* 音響効果を提供するBox型
|
||||
* Everything is Box哲学に基づく音響ライブラリ
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
|
||||
/// 音響効果を提供するBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SoundBox {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl SoundBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { id }
|
||||
}
|
||||
|
||||
/// ビープ音を鳴らす(基本)
|
||||
pub fn beep(&self) -> Box<dyn NyashBox> {
|
||||
// 端末ベル文字を出力
|
||||
print!("\x07");
|
||||
Box::new(StringBox::new("Beep!"))
|
||||
}
|
||||
|
||||
/// 指定回数ビープ
|
||||
pub fn beeps(&self, count: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(count_int) = count.as_any().downcast_ref::<IntegerBox>() {
|
||||
if count_int.value <= 0 {
|
||||
return Box::new(StringBox::new("Beep count must be positive"));
|
||||
}
|
||||
|
||||
for i in 0..count_int.value {
|
||||
print!("\x07");
|
||||
if i < count_int.value - 1 {
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(StringBox::new(&format!("Beeped {} times", count_int.value)))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: beeps() requires integer input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 指定周波数のビープ(Linuxのみ)
|
||||
pub fn tone(&self, frequency: Box<dyn NyashBox>, duration: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let (Some(freq_int), Some(dur_int)) = (
|
||||
frequency.as_any().downcast_ref::<IntegerBox>(),
|
||||
duration.as_any().downcast_ref::<IntegerBox>()
|
||||
) {
|
||||
if freq_int.value <= 0 || dur_int.value <= 0 {
|
||||
return Box::new(StringBox::new("Frequency and duration must be positive"));
|
||||
}
|
||||
|
||||
// Linuxのbeepコマンドを試行
|
||||
match Command::new("beep")
|
||||
.arg("-f")
|
||||
.arg(&freq_int.value.to_string())
|
||||
.arg("-l")
|
||||
.arg(&dur_int.value.to_string())
|
||||
.output()
|
||||
{
|
||||
Ok(_) => Box::new(StringBox::new(&format!("Played {}Hz for {}ms", freq_int.value, dur_int.value))),
|
||||
Err(_) => {
|
||||
// beepコマンドが無い場合は端末ベルを使用
|
||||
print!("\x07");
|
||||
std::thread::sleep(Duration::from_millis(dur_int.value as u64));
|
||||
Box::new(StringBox::new(&format!("Fallback beep ({}Hz, {}ms)", freq_int.value, dur_int.value)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: tone() requires two integer inputs (frequency, duration)"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 警告音
|
||||
pub fn alert(&self) -> Box<dyn NyashBox> {
|
||||
// 3回短いビープ
|
||||
for i in 0..3 {
|
||||
print!("\x07");
|
||||
if i < 2 {
|
||||
std::thread::sleep(Duration::from_millis(150));
|
||||
}
|
||||
}
|
||||
Box::new(StringBox::new("Alert sound played"))
|
||||
}
|
||||
|
||||
/// 成功音
|
||||
pub fn success(&self) -> Box<dyn NyashBox> {
|
||||
// 1回長めのビープ
|
||||
print!("\x07");
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
print!("\x07");
|
||||
Box::new(StringBox::new("Success sound played"))
|
||||
}
|
||||
|
||||
/// エラー音
|
||||
pub fn error(&self) -> Box<dyn NyashBox> {
|
||||
// 2回素早いビープ
|
||||
print!("\x07");
|
||||
std::thread::sleep(Duration::from_millis(80));
|
||||
print!("\x07");
|
||||
Box::new(StringBox::new("Error sound played"))
|
||||
}
|
||||
|
||||
/// カスタムビープパターン
|
||||
pub fn pattern(&self, pattern: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(pattern_str) = pattern.as_any().downcast_ref::<StringBox>() {
|
||||
let mut beep_count = 0;
|
||||
|
||||
for ch in pattern_str.value.chars() {
|
||||
match ch {
|
||||
'.' => {
|
||||
// 短いビープ
|
||||
print!("\x07");
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
beep_count += 1;
|
||||
}
|
||||
'-' => {
|
||||
// 長いビープ
|
||||
print!("\x07");
|
||||
std::thread::sleep(Duration::from_millis(300));
|
||||
beep_count += 1;
|
||||
}
|
||||
' ' => {
|
||||
// 無音(待機)
|
||||
std::thread::sleep(Duration::from_millis(200));
|
||||
}
|
||||
_ => {
|
||||
// その他の文字は無視
|
||||
}
|
||||
}
|
||||
|
||||
// 文字間の短い間隔
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
|
||||
Box::new(StringBox::new(&format!("Played pattern '{}' ({} beeps)", pattern_str.value, beep_count)))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: pattern() requires string input (use '.' for short, '-' for long, ' ' for pause)"))
|
||||
}
|
||||
}
|
||||
|
||||
/// システム音量チェック(簡易)
|
||||
pub fn volumeTest(&self) -> Box<dyn NyashBox> {
|
||||
print!("\x07");
|
||||
Box::new(StringBox::new("Volume test beep - can you hear it?"))
|
||||
}
|
||||
|
||||
/// 指定間隔でビープ
|
||||
pub fn interval(&self, times: Box<dyn NyashBox>, interval_ms: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let (Some(times_int), Some(interval_int)) = (
|
||||
times.as_any().downcast_ref::<IntegerBox>(),
|
||||
interval_ms.as_any().downcast_ref::<IntegerBox>()
|
||||
) {
|
||||
if times_int.value <= 0 || interval_int.value < 0 {
|
||||
return Box::new(StringBox::new("Times must be positive, interval must be non-negative"));
|
||||
}
|
||||
|
||||
for i in 0..times_int.value {
|
||||
print!("\x07");
|
||||
if i < times_int.value - 1 {
|
||||
std::thread::sleep(Duration::from_millis(interval_int.value as u64));
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(StringBox::new(&format!("Played {} beeps with {}ms intervals", times_int.value, interval_int.value)))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: interval() requires two integer inputs (times, interval_ms)"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for SoundBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"SoundBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("SoundBox()")
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_sound) = other.as_any().downcast_ref::<SoundBox>() {
|
||||
BoolBox::new(self.id == other_sound.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SoundBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "SoundBox()")
|
||||
}
|
||||
}
|
||||
141
src/boxes/string_box.rs
Normal file
141
src/boxes/string_box.rs
Normal file
@ -0,0 +1,141 @@
|
||||
// StringBox implementation - String values in Nyash
|
||||
use crate::box_trait::NyashBox;
|
||||
use std::any::Any;
|
||||
use std::fmt::Display;
|
||||
|
||||
/// String values in Nyash - UTF-8 encoded text
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct StringBox {
|
||||
pub value: String,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl StringBox {
|
||||
pub fn new(value: impl Into<String>) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self {
|
||||
value: value.into(),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
Self::new("")
|
||||
}
|
||||
|
||||
// ===== String Methods for Nyash =====
|
||||
|
||||
/// Split string by delimiter and return ArrayBox
|
||||
pub fn split(&self, delimiter: &str) -> Box<dyn NyashBox> {
|
||||
use crate::box_trait::ArrayBox;
|
||||
let parts: Vec<String> = self.value.split(delimiter).map(|s| s.to_string()).collect();
|
||||
let array_elements: Vec<Box<dyn NyashBox>> = parts.into_iter()
|
||||
.map(|s| Box::new(StringBox::new(s)) as Box<dyn NyashBox>)
|
||||
.collect();
|
||||
Box::new(ArrayBox::new_with_elements(array_elements))
|
||||
}
|
||||
|
||||
/// Find substring and return position (or -1 if not found)
|
||||
pub fn find(&self, search: &str) -> Box<dyn NyashBox> {
|
||||
use crate::boxes::IntegerBox;
|
||||
match self.value.find(search) {
|
||||
Some(pos) => Box::new(IntegerBox::new(pos as i64)),
|
||||
None => Box::new(IntegerBox::new(-1)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace all occurrences of old with new
|
||||
pub fn replace(&self, old: &str, new: &str) -> Box<dyn NyashBox> {
|
||||
Box::new(StringBox::new(self.value.replace(old, new)))
|
||||
}
|
||||
|
||||
/// Trim whitespace from both ends
|
||||
pub fn trim(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(StringBox::new(self.value.trim()))
|
||||
}
|
||||
|
||||
/// Convert to uppercase
|
||||
pub fn to_upper(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(StringBox::new(self.value.to_uppercase()))
|
||||
}
|
||||
|
||||
/// Convert to lowercase
|
||||
pub fn to_lower(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(StringBox::new(self.value.to_lowercase()))
|
||||
}
|
||||
|
||||
/// Check if string contains substring
|
||||
pub fn contains(&self, search: &str) -> Box<dyn NyashBox> {
|
||||
use crate::boxes::BoolBox;
|
||||
Box::new(BoolBox::new(self.value.contains(search)))
|
||||
}
|
||||
|
||||
/// Check if string starts with prefix
|
||||
pub fn starts_with(&self, prefix: &str) -> Box<dyn NyashBox> {
|
||||
use crate::boxes::BoolBox;
|
||||
Box::new(BoolBox::new(self.value.starts_with(prefix)))
|
||||
}
|
||||
|
||||
/// Check if string ends with suffix
|
||||
pub fn ends_with(&self, suffix: &str) -> Box<dyn NyashBox> {
|
||||
use crate::boxes::BoolBox;
|
||||
Box::new(BoolBox::new(self.value.ends_with(suffix)))
|
||||
}
|
||||
|
||||
/// Join array elements using this string as delimiter
|
||||
pub fn join(&self, array_box: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
use crate::box_trait::ArrayBox;
|
||||
if let Some(array) = array_box.as_any().downcast_ref::<ArrayBox>() {
|
||||
let strings: Vec<String> = array.elements.lock().unwrap()
|
||||
.iter()
|
||||
.map(|element| element.to_string_box().value)
|
||||
.collect();
|
||||
Box::new(StringBox::new(strings.join(&self.value)))
|
||||
} else {
|
||||
// If not an ArrayBox, treat as single element
|
||||
Box::new(StringBox::new(array_box.to_string_box().value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for StringBox {
|
||||
fn to_string_box(&self) -> crate::box_trait::StringBox {
|
||||
crate::box_trait::StringBox::new(self.value.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> crate::box_trait::BoolBox {
|
||||
use crate::box_trait::BoolBox;
|
||||
if let Some(other_string) = other.as_any().downcast_ref::<StringBox>() {
|
||||
BoolBox::new(self.value == other_string.value)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"StringBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StringBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
366
src/boxes/time_box.rs
Normal file
366
src/boxes/time_box.rs
Normal file
@ -0,0 +1,366 @@
|
||||
/*!
|
||||
* Nyash Time Box - Time and Date operations
|
||||
*
|
||||
* 時間と日付操作を提供するBox型
|
||||
* Everything is Box哲学に基づく時間ライブラリ
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
use std::time::{SystemTime, Duration};
|
||||
use chrono::{DateTime, Local, TimeZone, Datelike, Timelike};
|
||||
|
||||
/// 時間操作を提供するBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TimeBox {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl TimeBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self { id }
|
||||
}
|
||||
|
||||
/// 現在時刻を取得
|
||||
pub fn now(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(DateTimeBox::now())
|
||||
}
|
||||
|
||||
/// UNIXタイムスタンプから日時を作成
|
||||
pub fn fromTimestamp(&self, timestamp: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = timestamp.as_any().downcast_ref::<IntegerBox>() {
|
||||
Box::new(DateTimeBox::from_timestamp(int_box.value))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: fromTimestamp() requires integer input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 日時文字列をパース
|
||||
pub fn parse(&self, date_str: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(string_box) = date_str.as_any().downcast_ref::<StringBox>() {
|
||||
match DateTimeBox::parse(&string_box.value) {
|
||||
Ok(dt) => Box::new(dt),
|
||||
Err(e) => Box::new(StringBox::new(&format!("Error: {}", e))),
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: parse() requires string input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// ミリ秒スリープ
|
||||
pub fn sleep(&self, millis: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = millis.as_any().downcast_ref::<IntegerBox>() {
|
||||
if int_box.value > 0 {
|
||||
std::thread::sleep(Duration::from_millis(int_box.value as u64));
|
||||
Box::new(StringBox::new("ok"))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: sleep() requires positive milliseconds"))
|
||||
}
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: sleep() requires integer input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 現在時刻をフォーマット
|
||||
pub fn format(&self, format_str: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(str_box) = format_str.as_any().downcast_ref::<StringBox>() {
|
||||
let now = Local::now();
|
||||
let formatted = now.format(&str_box.value).to_string();
|
||||
Box::new(StringBox::new(formatted))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: format() requires string format pattern"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for TimeBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"TimeBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("TimeBox()")
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_time) = other.as_any().downcast_ref::<TimeBox>() {
|
||||
BoolBox::new(self.id == other_time.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TimeBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "TimeBox()")
|
||||
}
|
||||
}
|
||||
|
||||
/// 日時を表すBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DateTimeBox {
|
||||
pub datetime: DateTime<Local>,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl DateTimeBox {
|
||||
/// 現在時刻で作成
|
||||
pub fn now() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self {
|
||||
datetime: Local::now(),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// UNIXタイムスタンプから作成
|
||||
pub fn from_timestamp(timestamp: i64) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
let datetime = Local.timestamp_opt(timestamp, 0).unwrap();
|
||||
Self { datetime, id }
|
||||
}
|
||||
|
||||
/// 文字列からパース
|
||||
pub fn parse(date_str: &str) -> Result<Self, String> {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
// ISO 8601形式でパース
|
||||
match DateTime::parse_from_rfc3339(date_str) {
|
||||
Ok(dt) => Ok(Self {
|
||||
datetime: dt.with_timezone(&Local),
|
||||
id,
|
||||
}),
|
||||
Err(_) => {
|
||||
// シンプルな形式でパース (YYYY-MM-DD HH:MM:SS)
|
||||
match chrono::NaiveDateTime::parse_from_str(date_str, "%Y-%m-%d %H:%M:%S") {
|
||||
Ok(naive_dt) => {
|
||||
let datetime = Local.from_local_datetime(&naive_dt).unwrap();
|
||||
Ok(Self { datetime, id })
|
||||
}
|
||||
Err(e) => Err(format!("Failed to parse date: {}", e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 年を取得
|
||||
pub fn year(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.datetime.year() as i64))
|
||||
}
|
||||
|
||||
/// 月を取得
|
||||
pub fn month(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.datetime.month() as i64))
|
||||
}
|
||||
|
||||
/// 日を取得
|
||||
pub fn day(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.datetime.day() as i64))
|
||||
}
|
||||
|
||||
/// 時を取得
|
||||
pub fn hour(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.datetime.hour() as i64))
|
||||
}
|
||||
|
||||
/// 分を取得
|
||||
pub fn minute(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.datetime.minute() as i64))
|
||||
}
|
||||
|
||||
/// 秒を取得
|
||||
pub fn second(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.datetime.second() as i64))
|
||||
}
|
||||
|
||||
/// UNIXタイムスタンプを取得
|
||||
pub fn timestamp(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(IntegerBox::new(self.datetime.timestamp()))
|
||||
}
|
||||
|
||||
/// ISO 8601形式でフォーマット
|
||||
pub fn toISOString(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(StringBox::new(&self.datetime.to_rfc3339()))
|
||||
}
|
||||
|
||||
/// カスタムフォーマット
|
||||
pub fn format(&self, fmt: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(string_box) = fmt.as_any().downcast_ref::<StringBox>() {
|
||||
let formatted = self.datetime.format(&string_box.value).to_string();
|
||||
Box::new(StringBox::new(&formatted))
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: format() requires string input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 日付を加算
|
||||
pub fn addDays(&self, days: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = days.as_any().downcast_ref::<IntegerBox>() {
|
||||
let new_datetime = self.datetime + chrono::Duration::days(int_box.value);
|
||||
Box::new(DateTimeBox {
|
||||
datetime: new_datetime,
|
||||
id: self.id,
|
||||
})
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: addDays() requires integer input"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 時間を加算
|
||||
pub fn addHours(&self, hours: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
if let Some(int_box) = hours.as_any().downcast_ref::<IntegerBox>() {
|
||||
let new_datetime = self.datetime + chrono::Duration::hours(int_box.value);
|
||||
Box::new(DateTimeBox {
|
||||
datetime: new_datetime,
|
||||
id: self.id,
|
||||
})
|
||||
} else {
|
||||
Box::new(StringBox::new("Error: addHours() requires integer input"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for DateTimeBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"DateTimeBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(&self.datetime.format("%Y-%m-%d %H:%M:%S").to_string())
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_dt) = other.as_any().downcast_ref::<DateTimeBox>() {
|
||||
BoolBox::new(self.datetime == other_dt.datetime)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DateTimeBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.datetime.format("%Y-%m-%d %H:%M:%S"))
|
||||
}
|
||||
}
|
||||
|
||||
/// タイマーを表すBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TimerBox {
|
||||
start_time: SystemTime,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl TimerBox {
|
||||
pub fn new() -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self {
|
||||
start_time: SystemTime::now(),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// 経過時間をミリ秒で取得
|
||||
pub fn elapsed(&self) -> Box<dyn NyashBox> {
|
||||
match self.start_time.elapsed() {
|
||||
Ok(duration) => {
|
||||
let millis = duration.as_millis() as i64;
|
||||
Box::new(IntegerBox::new(millis))
|
||||
}
|
||||
Err(_) => Box::new(IntegerBox::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
/// タイマーをリセット
|
||||
pub fn reset(&mut self) -> Box<dyn NyashBox> {
|
||||
self.start_time = SystemTime::now();
|
||||
Box::new(StringBox::new("Timer reset"))
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for TimerBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"TimerBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new("TimerBox()")
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_timer) = other.as_any().downcast_ref::<TimerBox>() {
|
||||
BoolBox::new(self.id == other_timer.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TimerBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "TimerBox()")
|
||||
}
|
||||
}
|
||||
24
src/boxes/web/mod.rs
Normal file
24
src/boxes/web/mod.rs
Normal file
@ -0,0 +1,24 @@
|
||||
/*!
|
||||
* Web Boxes Module - ブラウザ専用Box群
|
||||
*
|
||||
* WebAssembly環境専用のBox群を管理
|
||||
* HTML5 APIs、DOM操作、Canvas描画等をNyashから利用可能にする
|
||||
*/
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod web_display_box;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod web_console_box;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod web_canvas_box;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use web_display_box::WebDisplayBox;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use web_console_box::WebConsoleBox;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use web_canvas_box::WebCanvasBox;
|
||||
304
src/boxes/web/web_canvas_box.rs
Normal file
304
src/boxes/web/web_canvas_box.rs
Normal file
@ -0,0 +1,304 @@
|
||||
/*!
|
||||
* WebCanvasBox - ブラウザCanvas完全制御Box
|
||||
*
|
||||
* WebAssembly環境でHTML5 Canvasの完全制御
|
||||
* ピクセルの世界を制圧する革命的Box!
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox};
|
||||
use std::any::Any;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use web_sys::{
|
||||
HtmlCanvasElement,
|
||||
CanvasRenderingContext2d,
|
||||
};
|
||||
|
||||
// 🎨 Browser Canvas complete control Box
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WebCanvasBox {
|
||||
id: u64,
|
||||
canvas_id: String,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl WebCanvasBox {
|
||||
pub fn new(canvas_id: String, width: u32, height: u32) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
let instance = Self {
|
||||
id,
|
||||
canvas_id: canvas_id.clone(),
|
||||
width,
|
||||
height,
|
||||
};
|
||||
|
||||
// キャンバス要素を初期化
|
||||
if let Some(canvas) = instance.get_canvas_element() {
|
||||
canvas.set_width(width);
|
||||
canvas.set_height(height);
|
||||
}
|
||||
|
||||
instance
|
||||
}
|
||||
|
||||
/// Canvas要素を取得
|
||||
fn get_canvas_element(&self) -> Option<HtmlCanvasElement> {
|
||||
let window = web_sys::window()?;
|
||||
let document = window.document()?;
|
||||
let element = document.get_element_by_id(&self.canvas_id)?;
|
||||
element.dyn_into::<HtmlCanvasElement>().ok()
|
||||
}
|
||||
|
||||
/// 2Dレンダリングコンテキストを取得
|
||||
fn get_2d_context(&self) -> Option<CanvasRenderingContext2d> {
|
||||
let canvas = self.get_canvas_element()?;
|
||||
canvas
|
||||
.get_context("2d")
|
||||
.ok()?
|
||||
.and_then(|ctx| ctx.dyn_into::<CanvasRenderingContext2d>().ok())
|
||||
}
|
||||
|
||||
/// キャンバスをクリア
|
||||
pub fn clear(&self) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.clear_rect(0.0, 0.0, self.width as f64, self.height as f64);
|
||||
}
|
||||
}
|
||||
|
||||
/// 塗りつぶし色を設定
|
||||
pub fn set_fill_style(&self, color: &str) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_fill_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
}
|
||||
}
|
||||
|
||||
/// 線の色を設定
|
||||
pub fn set_stroke_style(&self, color: &str) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
}
|
||||
}
|
||||
|
||||
/// 線の太さを設定
|
||||
pub fn set_line_width(&self, width: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_line_width(width);
|
||||
}
|
||||
}
|
||||
|
||||
/// 塗りつぶし矩形を描画
|
||||
pub fn fill_rect(&self, x: f64, y: f64, width: f64, height: f64, color: &str) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_fill_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.fill_rect(x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
/// 枠線矩形を描画
|
||||
pub fn stroke_rect(&self, x: f64, y: f64, width: f64, height: f64, color: &str, line_width: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.set_line_width(line_width);
|
||||
ctx.stroke_rect(x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
/// 塗りつぶし円を描画
|
||||
pub fn fill_circle(&self, x: f64, y: f64, radius: f64, color: &str) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_fill_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.begin_path();
|
||||
ctx.arc(x, y, radius, 0.0, 2.0 * std::f64::consts::PI).unwrap_or_default();
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
/// 枠線円を描画
|
||||
pub fn stroke_circle(&self, x: f64, y: f64, radius: f64, color: &str, line_width: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.set_line_width(line_width);
|
||||
ctx.begin_path();
|
||||
ctx.arc(x, y, radius, 0.0, 2.0 * std::f64::consts::PI).unwrap_or_default();
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
/// 直線を描画
|
||||
pub fn draw_line(&self, x1: f64, y1: f64, x2: f64, y2: f64, color: &str, line_width: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.set_line_width(line_width);
|
||||
ctx.begin_path();
|
||||
ctx.move_to(x1, y1);
|
||||
ctx.line_to(x2, y2);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
/// テキストを描画(塗りつぶし)
|
||||
pub fn fill_text(&self, text: &str, x: f64, y: f64, font: &str, color: &str) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_font(font);
|
||||
ctx.set_fill_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.fill_text(text, x, y).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
/// テキストを描画(枠線)
|
||||
pub fn stroke_text(&self, text: &str, x: f64, y: f64, font: &str, color: &str, line_width: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_font(font);
|
||||
ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.set_line_width(line_width);
|
||||
ctx.stroke_text(text, x, y).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
/// パス描画開始
|
||||
pub fn begin_path(&self) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.begin_path();
|
||||
}
|
||||
}
|
||||
|
||||
/// パスを指定位置に移動
|
||||
pub fn move_to(&self, x: f64, y: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.move_to(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/// パスに直線を追加
|
||||
pub fn line_to(&self, x: f64, y: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.line_to(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/// パスを閉じる
|
||||
pub fn close_path(&self) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.close_path();
|
||||
}
|
||||
}
|
||||
|
||||
/// パスを塗りつぶし
|
||||
pub fn fill(&self, color: &str) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_fill_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
/// パスを枠線描画
|
||||
pub fn stroke(&self, color: &str, line_width: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.set_stroke_style(&wasm_bindgen::JsValue::from_str(color));
|
||||
ctx.set_line_width(line_width);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
/// 現在の描画状態を保存
|
||||
pub fn save(&self) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.save();
|
||||
}
|
||||
}
|
||||
|
||||
/// 描画状態を復元
|
||||
pub fn restore(&self) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
/// 座標系を回転
|
||||
pub fn rotate(&self, angle: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.rotate(angle).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
/// 座標系をスケール
|
||||
pub fn scale(&self, x: f64, y: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.scale(x, y).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
/// 座標系を平行移動
|
||||
pub fn translate(&self, x: f64, y: f64) {
|
||||
if let Some(ctx) = self.get_2d_context() {
|
||||
ctx.translate(x, y).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
/// キャンバスのサイズを取得
|
||||
pub fn get_width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn get_height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
/// キャンバスのサイズを変更
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
|
||||
if let Some(canvas) = self.get_canvas_element() {
|
||||
canvas.set_width(width);
|
||||
canvas.set_height(height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl NyashBox for WebCanvasBox {
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(format!(
|
||||
"WebCanvasBox({}, {}x{})",
|
||||
self.canvas_id,
|
||||
self.width,
|
||||
self.height
|
||||
))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"WebCanvasBox"
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_canvas) = other.as_any().downcast_ref::<WebCanvasBox>() {
|
||||
BoolBox::new(self.id == other_canvas.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
175
src/boxes/web/web_console_box.rs
Normal file
175
src/boxes/web/web_console_box.rs
Normal file
@ -0,0 +1,175 @@
|
||||
/*!
|
||||
* WebConsoleBox - ブラウザHTML要素コンソール出力Box
|
||||
*
|
||||
* WebAssembly環境でHTML要素へのコンソール風出力
|
||||
* F12コンソールの代わりに指定要素に出力
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox};
|
||||
use std::any::Any;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use web_sys::{Element, HtmlElement};
|
||||
|
||||
// 🌐 Browser HTML element console output Box
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WebConsoleBox {
|
||||
id: u64,
|
||||
target_element_id: String,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl WebConsoleBox {
|
||||
pub fn new(element_id: String) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
Self {
|
||||
id,
|
||||
target_element_id: element_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// 指定した要素IDのHTML要素を取得
|
||||
fn get_target_element(&self) -> Option<Element> {
|
||||
let window = web_sys::window()?;
|
||||
let document = window.document()?;
|
||||
document.get_element_by_id(&self.target_element_id)
|
||||
}
|
||||
|
||||
/// コンソール出力を追加(改行付き)
|
||||
fn append_console_line(&self, message: &str, level: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let timestamp = js_sys::Date::new_0().to_iso_string().as_string().unwrap_or_default();
|
||||
let time_part = timestamp.split('T').nth(1).unwrap_or("00:00:00").split('.').nth(0).unwrap_or("00:00:00");
|
||||
|
||||
let (level_prefix, color) = match level {
|
||||
"log" => ("📝", "white"),
|
||||
"warn" => ("⚠️", "yellow"),
|
||||
"error" => ("❌", "red"),
|
||||
"info" => ("ℹ️", "cyan"),
|
||||
"debug" => ("🔍", "gray"),
|
||||
_ => ("📝", "white"),
|
||||
};
|
||||
|
||||
let formatted_line = format!(
|
||||
"<span style='color: {}'>[{}] {} {}</span><br>",
|
||||
color,
|
||||
time_part,
|
||||
level_prefix,
|
||||
message
|
||||
);
|
||||
|
||||
let current_content = element.inner_html();
|
||||
let new_content = format!("{}{}", current_content, formatted_line);
|
||||
element.set_inner_html(&new_content);
|
||||
|
||||
// 自動スクロール
|
||||
if let Some(html_element) = element.dyn_ref::<HtmlElement>() {
|
||||
html_element.set_scroll_top(html_element.scroll_height());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ログメッセージを出力
|
||||
pub fn log(&self, message: &str) {
|
||||
self.append_console_line(message, "log");
|
||||
}
|
||||
|
||||
/// 警告メッセージを出力
|
||||
pub fn warn(&self, message: &str) {
|
||||
self.append_console_line(message, "warn");
|
||||
}
|
||||
|
||||
/// エラーメッセージを出力
|
||||
pub fn error(&self, message: &str) {
|
||||
self.append_console_line(message, "error");
|
||||
}
|
||||
|
||||
/// 情報メッセージを出力
|
||||
pub fn info(&self, message: &str) {
|
||||
self.append_console_line(message, "info");
|
||||
}
|
||||
|
||||
/// デバッグメッセージを出力
|
||||
pub fn debug(&self, message: &str) {
|
||||
self.append_console_line(message, "debug");
|
||||
}
|
||||
|
||||
/// コンソールをクリア
|
||||
pub fn clear(&self) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
element.set_inner_html("");
|
||||
}
|
||||
}
|
||||
|
||||
/// 区切り線を追加
|
||||
pub fn separator(&self) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let current_content = element.inner_html();
|
||||
let separator_line = "<hr style='border: 1px solid #333; margin: 5px 0;'>";
|
||||
let new_content = format!("{}{}", current_content, separator_line);
|
||||
element.set_inner_html(&new_content);
|
||||
}
|
||||
}
|
||||
|
||||
/// グループ開始(見出し付き)
|
||||
pub fn group(&self, title: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let current_content = element.inner_html();
|
||||
let group_header = format!(
|
||||
"<div style='font-weight: bold; color: #4ecdc4; margin: 10px 0 5px 0;'>📂 {}</div><div style='margin-left: 20px; color: white;'>",
|
||||
title
|
||||
);
|
||||
let new_content = format!("{}{}", current_content, group_header);
|
||||
element.set_inner_html(&new_content);
|
||||
}
|
||||
}
|
||||
|
||||
/// グループ終了
|
||||
pub fn group_end(&self) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let current_content = element.inner_html();
|
||||
let group_footer = "</div>";
|
||||
let new_content = format!("{}{}", current_content, group_footer);
|
||||
element.set_inner_html(&new_content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl NyashBox for WebConsoleBox {
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(format!("WebConsoleBox({})", self.target_element_id))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"WebConsoleBox"
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_console) = other.as_any().downcast_ref::<WebConsoleBox>() {
|
||||
BoolBox::new(self.id == other_console.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
169
src/boxes/web/web_display_box.rs
Normal file
169
src/boxes/web/web_display_box.rs
Normal file
@ -0,0 +1,169 @@
|
||||
/*!
|
||||
* WebDisplayBox - ブラウザHTML要素表示制御Box
|
||||
*
|
||||
* WebAssembly環境でHTML要素への直接出力・スタイル制御
|
||||
* プレイグラウンドの出力パネル等を完全制御
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox};
|
||||
use std::any::Any;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use web_sys::{Element, HtmlElement};
|
||||
|
||||
// 🌐 Browser HTML element display control Box
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WebDisplayBox {
|
||||
id: u64,
|
||||
target_element_id: String,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl WebDisplayBox {
|
||||
pub fn new(element_id: String) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
Self {
|
||||
id,
|
||||
target_element_id: element_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// 指定した要素IDのHTML要素を取得
|
||||
fn get_target_element(&self) -> Option<Element> {
|
||||
let window = web_sys::window()?;
|
||||
let document = window.document()?;
|
||||
document.get_element_by_id(&self.target_element_id)
|
||||
}
|
||||
|
||||
/// テキストを追加出力
|
||||
pub fn print(&self, message: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let current_content = element.inner_html();
|
||||
let new_content = if current_content.is_empty() {
|
||||
message.to_string()
|
||||
} else {
|
||||
format!("{}{}", current_content, message)
|
||||
};
|
||||
element.set_inner_html(&new_content);
|
||||
}
|
||||
}
|
||||
|
||||
/// テキストを改行付きで追加出力
|
||||
pub fn println(&self, message: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let current_content = element.inner_html();
|
||||
let new_content = if current_content.is_empty() {
|
||||
message.to_string()
|
||||
} else {
|
||||
format!("{}<br>{}", current_content, message)
|
||||
};
|
||||
element.set_inner_html(&new_content);
|
||||
}
|
||||
}
|
||||
|
||||
/// HTMLコンテンツを完全置換
|
||||
pub fn set_html(&self, html_content: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
element.set_inner_html(html_content);
|
||||
}
|
||||
}
|
||||
|
||||
/// HTMLコンテンツを追加
|
||||
pub fn append_html(&self, html_content: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let current_content = element.inner_html();
|
||||
let new_content = format!("{}{}", current_content, html_content);
|
||||
element.set_inner_html(&new_content);
|
||||
}
|
||||
}
|
||||
|
||||
/// CSSスタイルを設定
|
||||
pub fn set_css(&self, property: &str, value: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
if let Some(html_element) = element.dyn_ref::<HtmlElement>() {
|
||||
// HTMLElement の style プロパティへアクセス
|
||||
let _ = html_element.style().set_property(property, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CSSクラスを追加
|
||||
pub fn add_class(&self, class_name: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let _ = element.class_list().add_1(class_name);
|
||||
}
|
||||
}
|
||||
|
||||
/// CSSクラスを削除
|
||||
pub fn remove_class(&self, class_name: &str) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
let _ = element.class_list().remove_1(class_name);
|
||||
}
|
||||
}
|
||||
|
||||
/// 内容をクリア
|
||||
pub fn clear(&self) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
element.set_inner_html("");
|
||||
}
|
||||
}
|
||||
|
||||
/// 要素を表示
|
||||
pub fn show(&self) {
|
||||
self.set_css("display", "block");
|
||||
}
|
||||
|
||||
/// 要素を非表示
|
||||
pub fn hide(&self) {
|
||||
self.set_css("display", "none");
|
||||
}
|
||||
|
||||
/// スクロールを最下部に移動
|
||||
pub fn scroll_to_bottom(&self) {
|
||||
if let Some(element) = self.get_target_element() {
|
||||
if let Some(html_element) = element.dyn_ref::<HtmlElement>() {
|
||||
html_element.set_scroll_top(html_element.scroll_height());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl NyashBox for WebDisplayBox {
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(format!("WebDisplayBox({})", self.target_element_id))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"WebDisplayBox"
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_display) = other.as_any().downcast_ref::<WebDisplayBox>() {
|
||||
BoolBox::new(self.id == other_display.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
226
src/channel_box.rs
Normal file
226
src/channel_box.rs
Normal file
@ -0,0 +1,226 @@
|
||||
/*!
|
||||
* Nyash P2P Channel System - Arrow構文によるBox間通信
|
||||
*
|
||||
* alice >> bob でメッセージ送信を可能にする
|
||||
* Everything is Box哲学に基づくP2P通信システム
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, VoidBox};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
|
||||
/// チャンネル - Box間の通信路
|
||||
#[derive(Clone)]
|
||||
pub struct ChannelBox {
|
||||
/// 送信者の名前
|
||||
pub sender_name: String,
|
||||
|
||||
/// 受信者の名前
|
||||
pub receiver_name: String,
|
||||
|
||||
/// リンクされたBox(弱参照)
|
||||
linked_boxes: Arc<Mutex<HashMap<String, Weak<Mutex<dyn NyashBox>>>>>,
|
||||
|
||||
/// メッセージハンドラー
|
||||
handlers: Arc<Mutex<HashMap<String, Box<dyn Fn(Box<dyn NyashBox>) -> Box<dyn NyashBox> + Send>>>>,
|
||||
|
||||
/// チャンネルID
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl ChannelBox {
|
||||
/// 新しいチャンネルを作成
|
||||
pub fn new(sender: &str, receiver: &str) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self {
|
||||
sender_name: sender.to_string(),
|
||||
receiver_name: receiver.to_string(),
|
||||
linked_boxes: Arc::new(Mutex::new(HashMap::new())),
|
||||
handlers: Arc::new(Mutex::new(HashMap::new())),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Boxをリンク
|
||||
pub fn link(&self, name: &str, target: Arc<Mutex<dyn NyashBox>>) {
|
||||
self.linked_boxes.lock().unwrap().insert(
|
||||
name.to_string(),
|
||||
Arc::downgrade(&target)
|
||||
);
|
||||
}
|
||||
|
||||
/// メッセージハンドラーを登録
|
||||
pub fn register_handler<F>(&self, method: &str, handler: F)
|
||||
where
|
||||
F: Fn(Box<dyn NyashBox>) -> Box<dyn NyashBox> + Send + 'static
|
||||
{
|
||||
self.handlers.lock().unwrap().insert(
|
||||
method.to_string(),
|
||||
Box::new(handler)
|
||||
);
|
||||
}
|
||||
|
||||
/// メソッド呼び出しを実行
|
||||
pub fn invoke(&self, method: &str, args: Vec<Box<dyn NyashBox>>) -> Box<dyn NyashBox> {
|
||||
// "*" はブロードキャスト
|
||||
if self.receiver_name == "*" {
|
||||
return self.broadcast(method, args);
|
||||
}
|
||||
|
||||
// 通常の送信
|
||||
let handlers = self.handlers.lock().unwrap();
|
||||
if let Some(handler) = handlers.get(method) {
|
||||
// 簡易実装:最初の引数のみ使用
|
||||
let arg = args.get(0)
|
||||
.map(|a| a.clone_box())
|
||||
.unwrap_or_else(|| Box::new(VoidBox::new()));
|
||||
handler(arg)
|
||||
} else {
|
||||
Box::new(StringBox::new(&format!("No handler for method: {}", method)))
|
||||
}
|
||||
}
|
||||
|
||||
/// 送信者名を取得
|
||||
pub fn sender(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(StringBox::new(&self.sender_name))
|
||||
}
|
||||
|
||||
/// 受信者名を取得
|
||||
pub fn receiver(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(StringBox::new(&self.receiver_name))
|
||||
}
|
||||
|
||||
/// ブロードキャスト
|
||||
fn broadcast(&self, _method: &str, _args: Vec<Box<dyn NyashBox>>) -> Box<dyn NyashBox> {
|
||||
let linked = self.linked_boxes.lock().unwrap();
|
||||
let mut results = Vec::new();
|
||||
|
||||
for (name, weak_box) in linked.iter() {
|
||||
if let Some(_strong_box) = weak_box.upgrade() {
|
||||
// 各Boxにメッセージを送信
|
||||
results.push(format!("Sent to {}", name));
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(StringBox::new(&format!("Broadcast complete: {:?}", results)))
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for ChannelBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"ChannelBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(&format!("Channel({} >> {})", self.sender_name, self.receiver_name))
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> crate::box_trait::BoolBox {
|
||||
if let Some(other_channel) = other.as_any().downcast_ref::<ChannelBox>() {
|
||||
crate::box_trait::BoolBox::new(
|
||||
self.sender_name == other_channel.sender_name &&
|
||||
self.receiver_name == other_channel.receiver_name
|
||||
)
|
||||
} else {
|
||||
crate::box_trait::BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ChannelBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_string_box().value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ChannelBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ChannelBox")
|
||||
.field("sender_name", &self.sender_name)
|
||||
.field("receiver_name", &self.receiver_name)
|
||||
.field("id", &self.id)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// メッセージを表すBox
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MessageBox {
|
||||
pub sender: String,
|
||||
pub content: String,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
impl MessageBox {
|
||||
pub fn new(sender: &str, content: &str) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let timestamp = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self {
|
||||
sender: sender.to_string(),
|
||||
content: content.to_string(),
|
||||
timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for MessageBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"MessageBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(&format!("[{}] {}: {}", self.timestamp, self.sender, self.content))
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> crate::box_trait::BoolBox {
|
||||
if let Some(other_msg) = other.as_any().downcast_ref::<MessageBox>() {
|
||||
crate::box_trait::BoolBox::new(
|
||||
self.sender == other_msg.sender &&
|
||||
self.content == other_msg.content
|
||||
)
|
||||
} else {
|
||||
crate::box_trait::BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.timestamp
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MessageBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_string_box().value)
|
||||
}
|
||||
}
|
||||
359
src/environment.rs
Normal file
359
src/environment.rs
Normal file
@ -0,0 +1,359 @@
|
||||
/*!
|
||||
* Nyash Environment System - Rust所有権ベースのスコープ管理
|
||||
*
|
||||
* Python版の曖昧なメモリ管理をRustの借用チェッカーで完全解決!
|
||||
* Everything is Box哲学 + 所有権システム = 完璧なスコープ管理
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, VoidBox};
|
||||
use crate::finalization::BoxFinalizer;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Nyash変数環境 - スコープチェーンを安全に管理
|
||||
#[derive(Debug)]
|
||||
pub struct Environment {
|
||||
/// 現在のスコープの変数バインディング
|
||||
bindings: Mutex<HashMap<String, Box<dyn NyashBox>>>,
|
||||
|
||||
/// 親環境への参照 (Arc<Mutex<>>でスレッド安全)
|
||||
parent: Option<Arc<Mutex<Environment>>>,
|
||||
|
||||
/// スコープレベル (デバッグ用)
|
||||
level: usize,
|
||||
|
||||
/// スコープ名 (関数名、クラス名など)
|
||||
scope_name: String,
|
||||
|
||||
/// Box解放管理
|
||||
finalizer: Mutex<BoxFinalizer>,
|
||||
}
|
||||
|
||||
/// Environment操作エラー
|
||||
#[derive(Error, Debug)]
|
||||
pub enum EnvironmentError {
|
||||
#[error("Variable '{name}' is not defined")]
|
||||
UndefinedVariable { name: String },
|
||||
|
||||
#[error("Cannot access parent environment: {reason}")]
|
||||
ParentAccessError { reason: String },
|
||||
|
||||
#[error("Borrowing conflict in environment: {details}")]
|
||||
BorrowError { details: String },
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
/// 新しいグローバル環境を作成
|
||||
pub fn new_global() -> Arc<Mutex<Self>> {
|
||||
Arc::new(Mutex::new(Self {
|
||||
bindings: Mutex::new(HashMap::new()),
|
||||
parent: None,
|
||||
level: 0,
|
||||
scope_name: "global".to_string(),
|
||||
finalizer: Mutex::new(BoxFinalizer::new()),
|
||||
}))
|
||||
}
|
||||
|
||||
/// 親環境を持つ新しい環境を作成 (関数、クラス用)
|
||||
pub fn new_child(
|
||||
parent: Arc<Mutex<Environment>>,
|
||||
scope_name: impl Into<String>
|
||||
) -> Arc<Mutex<Self>> {
|
||||
let level = parent.lock().unwrap().level + 1;
|
||||
|
||||
Arc::new(Mutex::new(Self {
|
||||
bindings: Mutex::new(HashMap::new()),
|
||||
parent: Some(parent),
|
||||
level,
|
||||
scope_name: scope_name.into(),
|
||||
finalizer: Mutex::new(BoxFinalizer::new()),
|
||||
}))
|
||||
}
|
||||
|
||||
/// 変数を現在のスコープに定義
|
||||
pub fn define(&self, name: impl Into<String>, value: Box<dyn NyashBox>) {
|
||||
let name = name.into();
|
||||
self.bindings.lock().unwrap().insert(name, value);
|
||||
}
|
||||
|
||||
/// 変数を取得 (スコープチェーンを辿る)
|
||||
pub fn get(&self, name: &str) -> Result<Box<dyn NyashBox>, EnvironmentError> {
|
||||
// 現在のスコープから検索
|
||||
if let Some(value) = self.bindings.lock().unwrap().get(name) {
|
||||
return Ok(value.clone_box());
|
||||
}
|
||||
|
||||
// 親スコープから検索
|
||||
if let Some(parent) = &self.parent {
|
||||
return parent.lock().unwrap().get(name);
|
||||
}
|
||||
|
||||
// 見つからない
|
||||
Err(EnvironmentError::UndefinedVariable {
|
||||
name: name.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
/// 変数を設定 (既存変数の更新 or 新規定義)
|
||||
pub fn set(&self, name: impl Into<String>, value: Box<dyn NyashBox>) -> Result<(), EnvironmentError> {
|
||||
let name = name.into();
|
||||
|
||||
// 現在のスコープにある場合は更新
|
||||
if self.bindings.lock().unwrap().contains_key(&name) {
|
||||
self.bindings.lock().unwrap().insert(name, value);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 親スコープで再帰的に検索・設定
|
||||
if let Some(parent) = &self.parent {
|
||||
match parent.lock().unwrap().set(&name, value.clone_box()) {
|
||||
Ok(()) => return Ok(()),
|
||||
Err(EnvironmentError::UndefinedVariable { .. }) => {
|
||||
// 親にもない場合は現在のスコープに新規定義
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
// 新規定義として現在のスコープに追加
|
||||
self.bindings.lock().unwrap().insert(name, value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 変数が存在するかチェック
|
||||
pub fn exists(&self, name: &str) -> bool {
|
||||
self.get(name).is_ok()
|
||||
}
|
||||
|
||||
/// 変数を削除 (現在のスコープからのみ)
|
||||
pub fn undefine(&self, name: &str) -> bool {
|
||||
self.bindings.lock().unwrap().remove(name).is_some()
|
||||
}
|
||||
|
||||
/// 現在のスコープの変数一覧を取得
|
||||
pub fn list_variables(&self) -> Vec<String> {
|
||||
self.bindings.lock().unwrap().keys().cloned().collect()
|
||||
}
|
||||
|
||||
/// スコープ情報を取得 (デバッグ用)
|
||||
pub fn scope_info(&self) -> String {
|
||||
format!("{}[{}] (level {})", self.scope_name, self.bindings.lock().unwrap().len(), self.level)
|
||||
}
|
||||
|
||||
/// スコープチェーン全体の情報を取得
|
||||
pub fn scope_chain_info(&self) -> Vec<String> {
|
||||
let mut chain = vec![self.scope_info()];
|
||||
|
||||
if let Some(parent) = &self.parent {
|
||||
chain.extend(parent.lock().unwrap().scope_chain_info());
|
||||
}
|
||||
|
||||
chain
|
||||
}
|
||||
|
||||
/// 全変数をダンプ (デバッグ用)
|
||||
pub fn dump_all_variables(&self) -> HashMap<String, String> {
|
||||
let mut all_vars = HashMap::new();
|
||||
|
||||
// 現在のスコープの変数
|
||||
for (name, value) in self.bindings.lock().unwrap().iter() {
|
||||
all_vars.insert(
|
||||
format!("{}::{}", self.scope_name, name),
|
||||
value.to_string_box().value
|
||||
);
|
||||
}
|
||||
|
||||
// 親スコープの変数 (再帰的)
|
||||
if let Some(parent) = &self.parent {
|
||||
all_vars.extend(parent.lock().unwrap().dump_all_variables());
|
||||
}
|
||||
|
||||
all_vars
|
||||
}
|
||||
|
||||
/// 新しいBoxを追跡対象に追加
|
||||
pub fn track_box(&self, nyash_box: Box<dyn NyashBox>) {
|
||||
self.finalizer.lock().unwrap().track(nyash_box);
|
||||
}
|
||||
|
||||
/// スコープ終了時にすべてのBoxを解放
|
||||
pub fn finalize_all_boxes(&self) {
|
||||
self.finalizer.lock().unwrap().finalize_all();
|
||||
}
|
||||
|
||||
/// 指定したBoxを解放対象から除外(関数の返り値など)
|
||||
pub fn exclude_from_finalization(&self, nyash_box: &Box<dyn NyashBox>) {
|
||||
self.finalizer.lock().unwrap().exclude_from_finalization(nyash_box);
|
||||
}
|
||||
}
|
||||
|
||||
/// PythonのEnvironmentクラスとの互換性レイヤー
|
||||
#[derive(Debug)]
|
||||
pub struct PythonCompatEnvironment {
|
||||
inner: Arc<Mutex<Environment>>,
|
||||
pub _bindings: HashMap<String, Box<dyn NyashBox>>,
|
||||
}
|
||||
|
||||
impl PythonCompatEnvironment {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Environment::new_global(),
|
||||
_bindings: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_parent(parent: Arc<Mutex<Environment>>) -> Self {
|
||||
Self {
|
||||
inner: Environment::new_child(parent, "python_compat"),
|
||||
_bindings: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Python版のdefineメソッド互換
|
||||
pub fn define(&mut self, name: impl Into<String>, value: Box<dyn NyashBox>) {
|
||||
let name = name.into();
|
||||
self.inner.lock().unwrap().define(&name, value.clone_box());
|
||||
self._bindings.insert(name, value);
|
||||
}
|
||||
|
||||
/// Python版のgetメソッド互換
|
||||
pub fn get(&self, name: &str) -> Box<dyn NyashBox> {
|
||||
self.inner.lock().unwrap().get(name).unwrap_or_else(|_| {
|
||||
Box::new(VoidBox::new())
|
||||
})
|
||||
}
|
||||
|
||||
/// Rustネイティブ環境への参照を取得
|
||||
pub fn as_native(&self) -> Arc<Mutex<Environment>> {
|
||||
self.inner.clone()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Tests =====
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::box_trait::{StringBox, IntegerBox, BoolBox};
|
||||
|
||||
#[test]
|
||||
fn test_global_environment() {
|
||||
let env = Environment::new_global();
|
||||
|
||||
// 変数定義
|
||||
env.lock().unwrap().define("test_var", Box::new(StringBox::new("hello")));
|
||||
|
||||
// 変数取得
|
||||
let value = env.lock().unwrap().get("test_var").unwrap();
|
||||
let string_val = value.as_any().downcast_ref::<StringBox>().unwrap();
|
||||
assert_eq!(string_val.value, "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nested_scopes() {
|
||||
let global = Environment::new_global();
|
||||
let function_scope = Environment::new_child(global.clone(), "test_function");
|
||||
|
||||
// グローバルスコープに変数定義
|
||||
global.lock().unwrap().define("global_var", Box::new(StringBox::new("global")));
|
||||
|
||||
// 関数スコープに変数定義
|
||||
function_scope.lock().unwrap().define("local_var", Box::new(StringBox::new("local")));
|
||||
|
||||
// 関数スコープからグローバル変数にアクセス可能
|
||||
let global_from_function = function_scope.lock().unwrap().get("global_var").unwrap();
|
||||
let global_str = global_from_function.as_any().downcast_ref::<StringBox>().unwrap();
|
||||
assert_eq!(global_str.value, "global");
|
||||
|
||||
// グローバルスコープからローカル変数にはアクセス不可
|
||||
assert!(global.lock().unwrap().get("local_var").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_variable_shadowing() {
|
||||
let global = Environment::new_global();
|
||||
let local = Environment::new_child(global.clone(), "local_scope");
|
||||
|
||||
// 同名変数を両スコープに定義
|
||||
global.lock().unwrap().define("same_name", Box::new(StringBox::new("global_value")));
|
||||
local.lock().unwrap().define("same_name", Box::new(StringBox::new("local_value")));
|
||||
|
||||
// ローカルスコープからはローカル値が取得される (シャドウイング)
|
||||
let value = local.lock().unwrap().get("same_name").unwrap();
|
||||
let string_val = value.as_any().downcast_ref::<StringBox>().unwrap();
|
||||
assert_eq!(string_val.value, "local_value");
|
||||
|
||||
// グローバルスコープからはグローバル値が取得される
|
||||
let global_value = global.lock().unwrap().get("same_name").unwrap();
|
||||
let global_str = global_value.as_any().downcast_ref::<StringBox>().unwrap();
|
||||
assert_eq!(global_str.value, "global_value");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_variable_setting() {
|
||||
let global = Environment::new_global();
|
||||
let local = Environment::new_child(global.clone(), "local_scope");
|
||||
|
||||
// グローバルに変数定義
|
||||
global.lock().unwrap().define("shared_var", Box::new(IntegerBox::new(100)));
|
||||
|
||||
// ローカルスコープから変数を更新
|
||||
local.lock().unwrap().set("shared_var", Box::new(IntegerBox::new(200))).unwrap();
|
||||
|
||||
// グローバルスコープの値が更新されている
|
||||
let updated_value = global.lock().unwrap().get("shared_var").unwrap();
|
||||
let int_val = updated_value.as_any().downcast_ref::<IntegerBox>().unwrap();
|
||||
assert_eq!(int_val.value, 200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scope_info() {
|
||||
let global = Environment::new_global();
|
||||
let func1 = Environment::new_child(global.clone(), "function1");
|
||||
let func2 = Environment::new_child(func1.clone(), "function2");
|
||||
|
||||
// 各スコープに変数を追加
|
||||
global.lock().unwrap().define("g1", Box::new(StringBox::new("global1")));
|
||||
func1.lock().unwrap().define("f1", Box::new(StringBox::new("func1")));
|
||||
func2.lock().unwrap().define("f2", Box::new(StringBox::new("func2")));
|
||||
|
||||
// スコープチェーン情報を確認
|
||||
let chain = func2.lock().unwrap().scope_chain_info();
|
||||
assert_eq!(chain.len(), 3);
|
||||
assert!(chain[0].contains("function2"));
|
||||
assert!(chain[1].contains("function1"));
|
||||
assert!(chain[2].contains("global"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_python_compat() {
|
||||
let mut env = PythonCompatEnvironment::new();
|
||||
|
||||
// Python互換インターフェースで変数操作
|
||||
env.define("test", Box::new(BoolBox::new(true)));
|
||||
|
||||
let value = env.get("test");
|
||||
let bool_val = value.as_any().downcast_ref::<BoolBox>().unwrap();
|
||||
assert_eq!(bool_val.value, true);
|
||||
|
||||
// _bindingsでも確認可能
|
||||
assert!(env._bindings.contains_key("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_handling() {
|
||||
let env = Environment::new_global();
|
||||
|
||||
// 存在しない変数へのアクセス
|
||||
let result = env.lock().unwrap().get("nonexistent");
|
||||
assert!(result.is_err());
|
||||
|
||||
match result {
|
||||
Err(EnvironmentError::UndefinedVariable { name }) => {
|
||||
assert_eq!(name, "nonexistent");
|
||||
}
|
||||
_ => panic!("Expected UndefinedVariable error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
104
src/exception_box.rs
Normal file
104
src/exception_box.rs
Normal file
@ -0,0 +1,104 @@
|
||||
/*!
|
||||
* Exception Boxes for Nyash try/catch/throw system
|
||||
*
|
||||
* Everything is Box哲学に基づく例外システム
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox};
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// 基底例外Box
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ErrorBox {
|
||||
pub message: String,
|
||||
pub stack_trace: Vec<String>,
|
||||
pub cause: Option<Box<ErrorBox>>,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl ErrorBox {
|
||||
pub fn new(message: &str) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
Self {
|
||||
message: message.to_string(),
|
||||
stack_trace: Vec::new(),
|
||||
cause: None,
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_cause(message: &str, cause: ErrorBox) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
Self {
|
||||
message: message.to_string(),
|
||||
stack_trace: Vec::new(),
|
||||
cause: Some(Box::new(cause)),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_stack_frame(&mut self, frame: String) {
|
||||
self.stack_trace.push(frame);
|
||||
}
|
||||
|
||||
pub fn get_full_message(&self) -> String {
|
||||
let mut msg = self.message.clone();
|
||||
if let Some(ref cause) = self.cause {
|
||||
msg.push_str(&format!("\nCaused by: {}", cause.get_full_message()));
|
||||
}
|
||||
msg
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for ErrorBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"ErrorBox"
|
||||
}
|
||||
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(format!("ErrorBox({})", self.message))
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_error) = other.as_any().downcast_ref::<ErrorBox>() {
|
||||
BoolBox::new(self.message == other_error.message)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// 例外タイプの判定ユーティリティ
|
||||
pub fn is_exception_type(exception: &dyn NyashBox, type_name: &str) -> bool {
|
||||
match type_name {
|
||||
"Error" | "ErrorBox" => exception.as_any().downcast_ref::<ErrorBox>().is_some(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// 例外の作成ヘルパー
|
||||
pub fn create_exception(_type_name: &str, message: &str, _extra_info: &HashMap<String, String>) -> Box<dyn NyashBox> {
|
||||
// 現在はErrorBoxのみサポート(シンプル実装)
|
||||
Box::new(ErrorBox::new(message))
|
||||
}
|
||||
111
src/finalization.rs
Normal file
111
src/finalization.rs
Normal file
@ -0,0 +1,111 @@
|
||||
/*!
|
||||
* Nyash Finalization System - Memory Management
|
||||
*
|
||||
* fini()によるメモリ管理システムの実装
|
||||
* - 解放済みBoxの追跡
|
||||
* - スコープベースの自動解放
|
||||
* - 再代入時の自動解放
|
||||
*/
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::fmt;
|
||||
use crate::box_trait::NyashBox;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
/// グローバルな解放済みBox ID管理
|
||||
static ref FINALIZED_BOXES: Arc<Mutex<HashSet<u64>>> = Arc::new(Mutex::new(HashSet::new()));
|
||||
}
|
||||
|
||||
/// Boxが既に解放済みかチェック
|
||||
pub fn is_finalized(box_id: u64) -> bool {
|
||||
FINALIZED_BOXES.lock().unwrap().contains(&box_id)
|
||||
}
|
||||
|
||||
/// Boxを解放済みとして記録
|
||||
pub fn mark_as_finalized(box_id: u64) {
|
||||
FINALIZED_BOXES.lock().unwrap().insert(box_id);
|
||||
}
|
||||
|
||||
/// Box解放管理
|
||||
pub struct BoxFinalizer {
|
||||
/// このスコープで作成されたBox ID
|
||||
created_boxes: Vec<(u64, Box<dyn NyashBox>)>,
|
||||
/// finalization除外対象のBox ID(関数の返り値など)
|
||||
excluded_boxes: HashSet<u64>,
|
||||
}
|
||||
|
||||
impl BoxFinalizer {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
created_boxes: Vec::new(),
|
||||
excluded_boxes: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 新しいBoxを追跡対象に追加
|
||||
pub fn track(&mut self, nyash_box: Box<dyn NyashBox>) {
|
||||
let box_id = nyash_box.box_id();
|
||||
self.created_boxes.push((box_id, nyash_box));
|
||||
}
|
||||
|
||||
/// 指定したBoxを解放対象から除外(関数の返り値など)
|
||||
pub fn exclude_from_finalization(&mut self, nyash_box: &Box<dyn NyashBox>) {
|
||||
let box_id = nyash_box.box_id();
|
||||
self.excluded_boxes.insert(box_id);
|
||||
}
|
||||
|
||||
/// スコープ終了時に全てのBoxを解放(除外対象を除く)
|
||||
pub fn finalize_all(&mut self) {
|
||||
// 作成順(古い順)に解放
|
||||
for (box_id, nyash_box) in &self.created_boxes {
|
||||
// 除外対象は解放しない
|
||||
if self.excluded_boxes.contains(box_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !is_finalized(*box_id) {
|
||||
// fini()メソッドを呼び出す(存在する場合)
|
||||
if let Some(instance) = nyash_box.as_any().downcast_ref::<crate::instance::InstanceBox>() {
|
||||
let _ = instance.fini();
|
||||
}
|
||||
mark_as_finalized(*box_id);
|
||||
}
|
||||
}
|
||||
self.created_boxes.clear();
|
||||
self.excluded_boxes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BoxFinalizer {
|
||||
fn drop(&mut self) {
|
||||
self.finalize_all();
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for BoxFinalizer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("BoxFinalizer")
|
||||
.field("created_boxes_count", &self.created_boxes.len())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_finalization_tracking() {
|
||||
let box_id = 12345;
|
||||
assert!(!is_finalized(box_id));
|
||||
|
||||
mark_as_finalized(box_id);
|
||||
assert!(is_finalized(box_id));
|
||||
|
||||
// 二重解放チェック
|
||||
mark_as_finalized(box_id); // 問題なし
|
||||
assert!(is_finalized(box_id));
|
||||
}
|
||||
}
|
||||
204
src/instance.rs
Normal file
204
src/instance.rs
Normal file
@ -0,0 +1,204 @@
|
||||
/*!
|
||||
* Nyash Instance System - Box Instance Implementation
|
||||
*
|
||||
* BoxインスタンスとClassBoxの実装
|
||||
* Everything is Box哲学に基づくオブジェクト指向システム
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox, VoidBox};
|
||||
use crate::ast::ASTNode;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// Boxインスタンス - フィールドとメソッドを持つオブジェクト
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InstanceBox {
|
||||
/// クラス名
|
||||
pub class_name: String,
|
||||
|
||||
/// フィールド値
|
||||
pub fields: Arc<Mutex<HashMap<String, Box<dyn NyashBox>>>>,
|
||||
|
||||
/// メソッド定義(ClassBoxから共有)
|
||||
pub methods: Arc<HashMap<String, ASTNode>>,
|
||||
|
||||
/// インスタンスID
|
||||
id: u64,
|
||||
|
||||
/// 解放済みフラグ
|
||||
finalized: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
impl InstanceBox {
|
||||
pub fn new(class_name: String, fields: Vec<String>, methods: HashMap<String, ASTNode>) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
// フィールドをVoidBoxで初期化
|
||||
let mut field_map = HashMap::new();
|
||||
for field in fields {
|
||||
field_map.insert(field, Box::new(VoidBox::new()) as Box<dyn NyashBox>);
|
||||
}
|
||||
|
||||
Self {
|
||||
class_name,
|
||||
fields: Arc::new(Mutex::new(field_map)),
|
||||
methods: Arc::new(methods),
|
||||
id,
|
||||
finalized: Arc::new(Mutex::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
/// フィールドの値を取得
|
||||
pub fn get_field(&self, field_name: &str) -> Option<Box<dyn NyashBox>> {
|
||||
self.fields.lock().unwrap().get(field_name).map(|v| v.clone_box())
|
||||
}
|
||||
|
||||
/// フィールドに値を設定
|
||||
pub fn set_field(&self, field_name: &str, value: Box<dyn NyashBox>) -> Result<(), String> {
|
||||
let mut fields = self.fields.lock().unwrap();
|
||||
if fields.contains_key(field_name) {
|
||||
fields.insert(field_name.to_string(), value);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Field '{}' does not exist in {}", field_name, self.class_name))
|
||||
}
|
||||
}
|
||||
|
||||
/// 🌍 GlobalBox用:フィールドを動的に追加・設定
|
||||
pub fn set_field_dynamic(&mut self, field_name: String, value: Box<dyn NyashBox>) {
|
||||
let mut fields = self.fields.lock().unwrap();
|
||||
fields.insert(field_name, value);
|
||||
}
|
||||
|
||||
/// メソッド定義を取得
|
||||
pub fn get_method(&self, method_name: &str) -> Option<&ASTNode> {
|
||||
self.methods.get(method_name)
|
||||
}
|
||||
|
||||
/// メソッドが存在するかチェック
|
||||
pub fn has_method(&self, method_name: &str) -> bool {
|
||||
self.methods.contains_key(method_name)
|
||||
}
|
||||
|
||||
/// 🌍 GlobalBox用:メソッドを動的に追加
|
||||
pub fn add_method(&mut self, method_name: String, method_ast: ASTNode) {
|
||||
// Arc<T>は不変なので、新しいHashMapを作成してArcで包む
|
||||
let mut new_methods = (*self.methods).clone();
|
||||
new_methods.insert(method_name, method_ast);
|
||||
self.methods = Arc::new(new_methods);
|
||||
}
|
||||
|
||||
/// fini()メソッド - インスタンスの解放
|
||||
pub fn fini(&self) -> Result<(), String> {
|
||||
let mut finalized = self.finalized.lock().unwrap();
|
||||
if *finalized {
|
||||
// 既に解放済みなら何もしない
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
*finalized = true;
|
||||
|
||||
// フィールドをクリア
|
||||
let mut fields = self.fields.lock().unwrap();
|
||||
fields.clear();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 解放済みかチェック
|
||||
pub fn is_finalized(&self) -> bool {
|
||||
*self.finalized.lock().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for InstanceBox {
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(format!("<{} instance #{}>", self.class_name, self.id))
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_instance) = other.as_any().downcast_ref::<InstanceBox>() {
|
||||
// 同じインスタンスIDなら等しい
|
||||
BoolBox::new(self.id == other_instance.id)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"InstanceBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
// インスタンスは同じフィールドを共有
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for InstanceBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "<{} instance>", self.class_name)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Tests =====
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::box_trait::IntegerBox;
|
||||
|
||||
#[test]
|
||||
fn test_instance_creation() {
|
||||
let fields = vec!["x".to_string(), "y".to_string()];
|
||||
let methods = HashMap::new();
|
||||
let instance = InstanceBox::new("Point".to_string(), fields, methods);
|
||||
|
||||
assert_eq!(instance.class_name, "Point");
|
||||
assert!(instance.get_field("x").is_some());
|
||||
assert!(instance.get_field("y").is_some());
|
||||
assert!(instance.get_field("z").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_field_access() {
|
||||
let fields = vec!["value".to_string()];
|
||||
let methods = HashMap::new();
|
||||
let instance = InstanceBox::new("TestBox".to_string(), fields, methods);
|
||||
|
||||
// フィールドに値を設定
|
||||
let int_value = Box::new(IntegerBox::new(42)) as Box<dyn NyashBox>;
|
||||
instance.set_field("value", int_value).unwrap();
|
||||
|
||||
// フィールドの値を取得
|
||||
let retrieved = instance.get_field("value").unwrap();
|
||||
let int_box = retrieved.as_any().downcast_ref::<IntegerBox>().unwrap();
|
||||
assert_eq!(int_box.value, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instance_equality() {
|
||||
let instance1 = InstanceBox::new("Test".to_string(), vec![], HashMap::new());
|
||||
let instance2 = InstanceBox::new("Test".to_string(), vec![], HashMap::new());
|
||||
|
||||
// 異なるインスタンスは等しくない
|
||||
assert!(!instance1.equals(&instance2).value);
|
||||
|
||||
// 同じインスタンスは等しい
|
||||
assert!(instance1.equals(&instance1).value);
|
||||
}
|
||||
}
|
||||
1823
src/interpreter/box_methods.rs
Normal file
1823
src/interpreter/box_methods.rs
Normal file
File diff suppressed because it is too large
Load Diff
702
src/interpreter/core.rs
Normal file
702
src/interpreter/core.rs
Normal file
@ -0,0 +1,702 @@
|
||||
/*!
|
||||
* Nyash Interpreter - Rust Implementation
|
||||
*
|
||||
* Python版nyashc_v4.pyのインタープリターをRustで完全再実装
|
||||
* Everything is Box哲学に基づくAST実行エンジン
|
||||
*/
|
||||
|
||||
use crate::ast::{ASTNode, Span};
|
||||
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, VoidBox};
|
||||
use crate::instance::InstanceBox;
|
||||
use crate::parser::ParseError;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use thiserror::Error;
|
||||
use super::{ControlFlow, BoxDeclaration, ConstructorContext, StaticBoxDefinition, StaticBoxState};
|
||||
|
||||
/// 実行時エラー
|
||||
#[derive(Error, Debug)]
|
||||
pub enum RuntimeError {
|
||||
#[error("Undefined variable '{name}'")]
|
||||
UndefinedVariable { name: String },
|
||||
|
||||
#[error("Undefined function '{name}'")]
|
||||
UndefinedFunction { name: String },
|
||||
|
||||
#[error("Undefined class '{name}'")]
|
||||
UndefinedClass { name: String },
|
||||
|
||||
#[error("Type error: {message}")]
|
||||
TypeError { message: String },
|
||||
|
||||
#[error("Invalid operation: {message}")]
|
||||
InvalidOperation { message: String },
|
||||
|
||||
#[error("Break outside of loop")]
|
||||
BreakOutsideLoop,
|
||||
|
||||
#[error("Return outside of function")]
|
||||
ReturnOutsideFunction,
|
||||
|
||||
#[error("Uncaught exception")]
|
||||
UncaughtException,
|
||||
|
||||
#[error("Parse error: {0}")]
|
||||
ParseError(#[from] ParseError),
|
||||
|
||||
#[error("Environment error: {0}")]
|
||||
EnvironmentError(String),
|
||||
|
||||
// === 🔥 Enhanced Errors with Span Information ===
|
||||
|
||||
#[error("Undefined variable '{name}' at {span}")]
|
||||
UndefinedVariableAt { name: String, span: Span },
|
||||
|
||||
#[error("Type error: {message} at {span}")]
|
||||
TypeErrorAt { message: String, span: Span },
|
||||
|
||||
#[error("Invalid operation: {message} at {span}")]
|
||||
InvalidOperationAt { message: String, span: Span },
|
||||
|
||||
#[error("Break outside of loop at {span}")]
|
||||
BreakOutsideLoopAt { span: Span },
|
||||
|
||||
#[error("Return outside of function at {span}")]
|
||||
ReturnOutsideFunctionAt { span: Span },
|
||||
|
||||
#[error("Runtime failure: {message}")]
|
||||
RuntimeFailure { message: String },
|
||||
}
|
||||
|
||||
impl RuntimeError {
|
||||
/// エラーの詳細な文脈付きメッセージを生成
|
||||
pub fn detailed_message(&self, source: Option<&str>) -> String {
|
||||
match self {
|
||||
// Enhanced errors with span information
|
||||
RuntimeError::UndefinedVariableAt { name, span } => {
|
||||
let mut msg = format!("⚠️ Undefined variable '{}'", name);
|
||||
if let Some(src) = source {
|
||||
msg.push('\n');
|
||||
msg.push_str(&span.error_context(src));
|
||||
} else {
|
||||
msg.push_str(&format!(" at {}", span));
|
||||
}
|
||||
msg
|
||||
}
|
||||
|
||||
RuntimeError::TypeErrorAt { message, span } => {
|
||||
let mut msg = format!("⚠️ Type error: {}", message);
|
||||
if let Some(src) = source {
|
||||
msg.push('\n');
|
||||
msg.push_str(&span.error_context(src));
|
||||
} else {
|
||||
msg.push_str(&format!(" at {}", span));
|
||||
}
|
||||
msg
|
||||
}
|
||||
|
||||
RuntimeError::InvalidOperationAt { message, span } => {
|
||||
let mut msg = format!("⚠️ Invalid operation: {}", message);
|
||||
if let Some(src) = source {
|
||||
msg.push('\n');
|
||||
msg.push_str(&span.error_context(src));
|
||||
} else {
|
||||
msg.push_str(&format!(" at {}", span));
|
||||
}
|
||||
msg
|
||||
}
|
||||
|
||||
RuntimeError::BreakOutsideLoopAt { span } => {
|
||||
let mut msg = "⚠️ Break statement outside of loop".to_string();
|
||||
if let Some(src) = source {
|
||||
msg.push('\n');
|
||||
msg.push_str(&span.error_context(src));
|
||||
} else {
|
||||
msg.push_str(&format!(" at {}", span));
|
||||
}
|
||||
msg
|
||||
}
|
||||
|
||||
RuntimeError::ReturnOutsideFunctionAt { span } => {
|
||||
let mut msg = "⚠️ Return statement outside of function".to_string();
|
||||
if let Some(src) = source {
|
||||
msg.push('\n');
|
||||
msg.push_str(&span.error_context(src));
|
||||
} else {
|
||||
msg.push_str(&format!(" at {}", span));
|
||||
}
|
||||
msg
|
||||
}
|
||||
|
||||
// Fallback for old error variants without span
|
||||
_ => format!("⚠️ {}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// スレッド間で共有される状態
|
||||
#[derive(Clone)]
|
||||
pub struct SharedState {
|
||||
/// 🌍 GlobalBox - すべてのトップレベル関数とグローバル変数を管理
|
||||
pub global_box: Arc<Mutex<InstanceBox>>,
|
||||
|
||||
/// Box宣言のレジストリ(読み込みが多いのでRwLock)
|
||||
pub box_declarations: Arc<RwLock<HashMap<String, BoxDeclaration>>>,
|
||||
|
||||
/// 🔥 静的関数のレジストリ(読み込みが多いのでRwLock)
|
||||
pub static_functions: Arc<RwLock<HashMap<String, HashMap<String, ASTNode>>>>,
|
||||
|
||||
/// 🔥 Static Box定義レジストリ(遅延初期化用)
|
||||
pub static_box_definitions: Arc<RwLock<HashMap<String, StaticBoxDefinition>>>,
|
||||
|
||||
/// 読み込み済みファイル(重複防止)
|
||||
pub included_files: Arc<Mutex<HashSet<String>>>,
|
||||
}
|
||||
|
||||
impl SharedState {
|
||||
/// 新しい共有状態を作成
|
||||
pub fn new() -> Self {
|
||||
let global_box = InstanceBox::new(
|
||||
"Global".to_string(),
|
||||
vec![], // フィールド名(空から始める)
|
||||
HashMap::new(), // メソッド(グローバル関数)
|
||||
);
|
||||
|
||||
Self {
|
||||
global_box: Arc::new(Mutex::new(global_box)),
|
||||
box_declarations: Arc::new(RwLock::new(HashMap::new())),
|
||||
static_functions: Arc::new(RwLock::new(HashMap::new())),
|
||||
static_box_definitions: Arc::new(RwLock::new(HashMap::new())),
|
||||
included_files: Arc::new(Mutex::new(HashSet::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Nyashインタープリター - AST実行エンジン
|
||||
pub struct NyashInterpreter {
|
||||
/// 共有状態(スレッド間で共有)
|
||||
pub(super) shared: SharedState,
|
||||
|
||||
/// 📦 local変数スタック(関数呼び出し時の一時変数)
|
||||
pub(super) local_vars: HashMap<String, Box<dyn NyashBox>>,
|
||||
|
||||
/// 📤 outbox変数スタック(static関数内の所有権移転変数)
|
||||
pub(super) outbox_vars: HashMap<String, Box<dyn NyashBox>>,
|
||||
|
||||
/// 制御フロー状態
|
||||
pub(super) control_flow: ControlFlow,
|
||||
|
||||
/// 現在実行中のコンストラクタ情報
|
||||
pub(super) current_constructor_context: Option<ConstructorContext>,
|
||||
}
|
||||
|
||||
impl NyashInterpreter {
|
||||
/// 新しいインタープリターを作成
|
||||
pub fn new() -> Self {
|
||||
let shared = SharedState::new();
|
||||
|
||||
Self {
|
||||
shared,
|
||||
local_vars: HashMap::new(),
|
||||
outbox_vars: HashMap::new(),
|
||||
control_flow: ControlFlow::None,
|
||||
current_constructor_context: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 共有状態から新しいインタープリターを作成(非同期実行用)
|
||||
pub fn with_shared(shared: SharedState) -> Self {
|
||||
Self {
|
||||
shared,
|
||||
local_vars: HashMap::new(),
|
||||
outbox_vars: HashMap::new(),
|
||||
control_flow: ControlFlow::None,
|
||||
current_constructor_context: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// ASTを実行
|
||||
pub fn execute(&mut self, ast: ASTNode) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
self.execute_node(&ast)
|
||||
}
|
||||
|
||||
/// ノードを実行
|
||||
fn execute_node(&mut self, node: &ASTNode) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
match node {
|
||||
ASTNode::Program { statements, .. } => {
|
||||
let mut result: Box<dyn NyashBox> = Box::new(VoidBox::new());
|
||||
|
||||
for statement in statements {
|
||||
result = self.execute_statement(statement)?;
|
||||
|
||||
// 制御フローチェック
|
||||
match &self.control_flow {
|
||||
ControlFlow::Break => {
|
||||
return Err(RuntimeError::BreakOutsideLoop);
|
||||
}
|
||||
ControlFlow::Return(_) => {
|
||||
return Err(RuntimeError::ReturnOutsideFunction);
|
||||
}
|
||||
ControlFlow::Throw(_) => {
|
||||
return Err(RuntimeError::UncaughtException);
|
||||
}
|
||||
ControlFlow::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
// 🎯 Static Box Main パターン - main()メソッドの自動実行
|
||||
let has_main_method = {
|
||||
if let Ok(definitions) = self.shared.static_box_definitions.read() {
|
||||
if let Some(main_definition) = definitions.get("Main") {
|
||||
main_definition.methods.contains_key("main")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if has_main_method {
|
||||
// Main static boxを初期化
|
||||
self.ensure_static_box_initialized("Main")?;
|
||||
|
||||
// Main.main() を呼び出し
|
||||
let main_call_ast = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::FieldAccess {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "statics".to_string(),
|
||||
span: crate::ast::Span::unknown(),
|
||||
}),
|
||||
field: "Main".to_string(),
|
||||
span: crate::ast::Span::unknown(),
|
||||
}),
|
||||
method: "main".to_string(),
|
||||
arguments: vec![],
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
|
||||
// main()の戻り値を最終結果として使用
|
||||
result = self.execute_statement(&main_call_ast)?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
_ => self.execute_statement(node),
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 🌍 GlobalBox変数解決システム ==========
|
||||
|
||||
/// 革命的変数解決: local変数 → GlobalBoxフィールド → エラー
|
||||
pub(super) fn resolve_variable(&self, name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
// 1. outbox変数を最初にチェック(static関数内で優先)
|
||||
if let Some(outbox_value) = self.outbox_vars.get(name) {
|
||||
return Ok(outbox_value.clone_box());
|
||||
}
|
||||
|
||||
// 2. local変数をチェック
|
||||
if let Some(local_value) = self.local_vars.get(name) {
|
||||
return Ok(local_value.clone_box());
|
||||
}
|
||||
|
||||
// 3. GlobalBoxのフィールドをチェック
|
||||
let global_box = self.shared.global_box.lock().unwrap();
|
||||
if let Some(field_value) = global_box.get_field(name) {
|
||||
return Ok(field_value);
|
||||
}
|
||||
|
||||
// 4. エラー:見つからない
|
||||
Err(RuntimeError::UndefinedVariable {
|
||||
name: name.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// 🔥 厳密変数設定: 明示的宣言のみ許可 - Everything is Box哲学
|
||||
pub(super) fn set_variable(&mut self, name: &str, value: Box<dyn NyashBox>) -> Result<(), RuntimeError> {
|
||||
// 1. outbox変数が存在する場合は更新
|
||||
if self.outbox_vars.contains_key(name) {
|
||||
self.outbox_vars.insert(name.to_string(), value);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 2. local変数が存在する場合は更新
|
||||
if self.local_vars.contains_key(name) {
|
||||
self.local_vars.insert(name.to_string(), value);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 3. GlobalBoxのフィールドが既に存在する場合は更新
|
||||
{
|
||||
let global_box = self.shared.global_box.lock().unwrap();
|
||||
if global_box.get_field(name).is_some() {
|
||||
drop(global_box); // lockを解放
|
||||
let mut global_box = self.shared.global_box.lock().unwrap();
|
||||
global_box.set_field_dynamic(name.to_string(), value);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 🚨 未宣言変数への代入は厳密にエラー
|
||||
Err(RuntimeError::UndefinedVariable {
|
||||
name: format!(
|
||||
"{}\n💡 Suggestion: Declare the variable first:\n • For fields: Add '{}' to 'init {{ }}' block\n • For local variables: Use 'local {}'\n • For field access: Use 'me.{}'",
|
||||
name, name, name, name
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
/// local変数を宣言(関数内でのみ有効)
|
||||
pub(super) fn declare_local_variable(&mut self, name: &str, value: Box<dyn NyashBox>) {
|
||||
self.local_vars.insert(name.to_string(), value);
|
||||
}
|
||||
|
||||
/// outbox変数を宣言(static関数内で所有権移転)
|
||||
pub(super) fn declare_outbox_variable(&mut self, name: &str, value: Box<dyn NyashBox>) {
|
||||
self.outbox_vars.insert(name.to_string(), value);
|
||||
}
|
||||
|
||||
/// local変数スタックを保存・復元(関数呼び出し時)
|
||||
pub(super) fn save_local_vars(&self) -> HashMap<String, Box<dyn NyashBox>> {
|
||||
self.local_vars.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone_box()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(super) fn restore_local_vars(&mut self, saved: HashMap<String, Box<dyn NyashBox>>) {
|
||||
self.local_vars = saved;
|
||||
}
|
||||
|
||||
/// outbox変数スタックを保存・復元(static関数呼び出し時)
|
||||
pub(super) fn save_outbox_vars(&self) -> HashMap<String, Box<dyn NyashBox>> {
|
||||
self.outbox_vars.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone_box()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(super) fn restore_outbox_vars(&mut self, saved: HashMap<String, Box<dyn NyashBox>>) {
|
||||
self.outbox_vars = saved;
|
||||
}
|
||||
|
||||
/// トップレベル関数をGlobalBoxのメソッドとして登録
|
||||
pub(super) fn register_global_function(&mut self, name: String, func_ast: ASTNode) -> Result<(), RuntimeError> {
|
||||
let mut global_box = self.shared.global_box.lock().unwrap();
|
||||
global_box.add_method(name, func_ast);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// 値が真と評価されるかチェック
|
||||
pub(super) fn is_truthy(&self, value: &Box<dyn NyashBox>) -> bool {
|
||||
#[allow(unused_imports)]
|
||||
use std::any::Any;
|
||||
|
||||
if let Some(bool_box) = value.as_any().downcast_ref::<BoolBox>() {
|
||||
bool_box.value
|
||||
} else if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
int_box.value != 0
|
||||
} else if let Some(string_box) = value.as_any().downcast_ref::<StringBox>() {
|
||||
!string_box.value.is_empty()
|
||||
} else if value.as_any().downcast_ref::<VoidBox>().is_some() {
|
||||
false
|
||||
} else {
|
||||
true // 他のBoxは真とみなす
|
||||
}
|
||||
}
|
||||
|
||||
/// 🌍 革命的変数取得(テスト用):GlobalBoxのフィールドから取得
|
||||
pub fn get_variable(&self, name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
self.resolve_variable(name)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Tests =====
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::parser::NyashParser;
|
||||
|
||||
#[test]
|
||||
fn test_simple_execution() {
|
||||
let code = r#"
|
||||
x = 42
|
||||
print(x)
|
||||
"#;
|
||||
|
||||
let ast = NyashParser::parse_from_string(code).unwrap();
|
||||
let mut interpreter = NyashInterpreter::new();
|
||||
let result = interpreter.execute(ast);
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arithmetic() {
|
||||
let code = r#"
|
||||
result = 10 + 32
|
||||
"#;
|
||||
|
||||
let ast = NyashParser::parse_from_string(code).unwrap();
|
||||
let mut interpreter = NyashInterpreter::new();
|
||||
interpreter.execute(ast).unwrap();
|
||||
|
||||
// 🌍 革命的変数取得:GlobalBoxから
|
||||
let result = interpreter.get_variable("result").unwrap();
|
||||
assert_eq!(result.to_string_box().value, "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_if_statement() {
|
||||
let code = r#"
|
||||
x = true
|
||||
if x {
|
||||
y = "success"
|
||||
} else {
|
||||
y = "failure"
|
||||
}
|
||||
"#;
|
||||
|
||||
let ast = NyashParser::parse_from_string(code).unwrap();
|
||||
let mut interpreter = NyashInterpreter::new();
|
||||
interpreter.execute(ast).unwrap();
|
||||
|
||||
// 🌍 革命的変数取得:GlobalBoxから
|
||||
let result = interpreter.get_variable("y").unwrap();
|
||||
assert_eq!(result.to_string_box().value, "success");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_box_instance_creation() {
|
||||
let code = r#"
|
||||
box TestBox {
|
||||
value
|
||||
|
||||
getValue() {
|
||||
return this.value
|
||||
}
|
||||
|
||||
setValue(newValue) {
|
||||
this.value = newValue
|
||||
}
|
||||
}
|
||||
|
||||
obj = new TestBox()
|
||||
obj.value = "test123"
|
||||
result = obj.getValue()
|
||||
"#;
|
||||
|
||||
let ast = NyashParser::parse_from_string(code).unwrap();
|
||||
let mut interpreter = NyashInterpreter::new();
|
||||
interpreter.execute(ast).unwrap();
|
||||
|
||||
// 🌍 革命的変数取得:インスタンス作成確認
|
||||
let obj = interpreter.get_variable("obj").unwrap();
|
||||
assert!(obj.as_any().downcast_ref::<InstanceBox>().is_some());
|
||||
|
||||
// 🌍 革命的変数取得:メソッド呼び出し結果確認
|
||||
let result = interpreter.get_variable("result").unwrap();
|
||||
assert_eq!(result.to_string_box().value, "test123");
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 🔥 Static Box管理システム =====
|
||||
|
||||
impl NyashInterpreter {
|
||||
|
||||
/// Static Box定義を登録
|
||||
pub fn register_static_box(&mut self, definition: StaticBoxDefinition) -> Result<(), RuntimeError> {
|
||||
let mut definitions = self.shared.static_box_definitions.write()
|
||||
.map_err(|_| RuntimeError::RuntimeFailure {
|
||||
message: "Failed to acquire write lock for static box definitions".to_string()
|
||||
})?;
|
||||
|
||||
definitions.insert(definition.name.clone(), definition);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Static Box宣言を登録(AST処理から呼ばれる)
|
||||
pub fn register_static_box_declaration(
|
||||
&mut self,
|
||||
name: String,
|
||||
fields: Vec<String>,
|
||||
methods: HashMap<String, ASTNode>,
|
||||
init_fields: Vec<String>,
|
||||
static_init: Option<Vec<ASTNode>>,
|
||||
extends: Option<String>,
|
||||
implements: Vec<String>,
|
||||
type_parameters: Vec<String>
|
||||
) -> Result<(), RuntimeError> {
|
||||
// 🌍 Static Box定義時にstatics名前空間を確実に作成
|
||||
self.ensure_statics_namespace()?;
|
||||
|
||||
let definition = StaticBoxDefinition {
|
||||
name: name.clone(),
|
||||
fields,
|
||||
methods,
|
||||
init_fields,
|
||||
static_init,
|
||||
extends,
|
||||
implements,
|
||||
type_parameters,
|
||||
initialization_state: StaticBoxState::NotInitialized,
|
||||
};
|
||||
|
||||
eprintln!("🔥 Static Box '{}' definition registered in statics namespace", name);
|
||||
self.register_static_box(definition)
|
||||
}
|
||||
|
||||
/// Static Boxの初期化を実行(遅延初期化)
|
||||
pub fn ensure_static_box_initialized(&mut self, name: &str) -> Result<(), RuntimeError> {
|
||||
// 1. 定義を取得
|
||||
let definition = {
|
||||
let definitions = self.shared.static_box_definitions.read()
|
||||
.map_err(|_| RuntimeError::RuntimeFailure {
|
||||
message: "Failed to acquire read lock for static box definitions".to_string()
|
||||
})?;
|
||||
|
||||
match definitions.get(name) {
|
||||
Some(def) => def.clone(),
|
||||
None => return Err(RuntimeError::UndefinedClass { name: name.to_string() }),
|
||||
}
|
||||
};
|
||||
|
||||
// 2. 初期化状態をチェック
|
||||
if definition.initialization_state == StaticBoxState::Initialized {
|
||||
return Ok(()); // 既に初期化済み
|
||||
}
|
||||
|
||||
if definition.initialization_state == StaticBoxState::Initializing {
|
||||
return Err(RuntimeError::RuntimeFailure {
|
||||
message: format!("Circular dependency detected during initialization of static box '{}'", name)
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 初期化開始をマーク
|
||||
self.set_static_box_state(name, StaticBoxState::Initializing)?;
|
||||
|
||||
// 4. 「statics」名前空間をGlobalBoxに作成(未存在の場合)
|
||||
self.ensure_statics_namespace()?;
|
||||
|
||||
// 5. シングルトンインスタンスを作成(メソッドも含む)
|
||||
let singleton = InstanceBox::new(
|
||||
format!("{}_singleton", name),
|
||||
definition.init_fields.clone(),
|
||||
definition.methods.clone(), // ★ メソッドを正しく設定
|
||||
);
|
||||
|
||||
// 6. GlobalBox.staticsに登録
|
||||
self.set_static_instance(name, singleton)?;
|
||||
|
||||
// 7. static初期化ブロックを実行(me変数をバインドして)
|
||||
if let Some(ref init_statements) = definition.static_init {
|
||||
// statics名前空間からシングルトンインスタンスを取得
|
||||
let static_instance = {
|
||||
let global_box = self.shared.global_box.lock().unwrap();
|
||||
let statics_box = global_box.get_field("statics").unwrap();
|
||||
let statics_instance = statics_box.as_any().downcast_ref::<InstanceBox>().unwrap();
|
||||
statics_instance.get_field(name).unwrap()
|
||||
};
|
||||
|
||||
// 🌍 this変数をバインドしてstatic初期化実行(me構文のため)
|
||||
self.declare_local_variable("me", static_instance);
|
||||
|
||||
for stmt in init_statements {
|
||||
self.execute_statement(stmt)?;
|
||||
}
|
||||
|
||||
// 🌍 this変数をクリーンアップ
|
||||
self.local_vars.remove("me");
|
||||
}
|
||||
|
||||
// 8. 初期化完了をマーク
|
||||
self.set_static_box_state(name, StaticBoxState::Initialized)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Static Box初期化状態を設定
|
||||
fn set_static_box_state(&mut self, name: &str, state: StaticBoxState) -> Result<(), RuntimeError> {
|
||||
let mut definitions = self.shared.static_box_definitions.write()
|
||||
.map_err(|_| RuntimeError::RuntimeFailure {
|
||||
message: "Failed to acquire write lock for static box definitions".to_string()
|
||||
})?;
|
||||
|
||||
if let Some(definition) = definitions.get_mut(name) {
|
||||
definition.initialization_state = state;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 「statics」名前空間をGlobalBoxに作成
|
||||
fn ensure_statics_namespace(&mut self) -> Result<(), RuntimeError> {
|
||||
let global_box = self.shared.global_box.lock()
|
||||
.map_err(|_| RuntimeError::RuntimeFailure {
|
||||
message: "Failed to acquire global box lock".to_string()
|
||||
})?;
|
||||
|
||||
// 既に存在する場合はスキップ
|
||||
if global_box.get_field("statics").is_some() {
|
||||
eprintln!("🌍 statics namespace already exists - skipping creation");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 「statics」用のInstanceBoxを作成
|
||||
let statics_box = InstanceBox::new(
|
||||
"statics".to_string(),
|
||||
vec![],
|
||||
HashMap::new(),
|
||||
);
|
||||
|
||||
// GlobalBoxのfieldsに直接挿入
|
||||
{
|
||||
let mut fields = global_box.fields.lock().unwrap();
|
||||
fields.insert("statics".to_string(), Box::new(statics_box));
|
||||
}
|
||||
|
||||
eprintln!("🌍 statics namespace created in GlobalBox successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Static Boxシングルトンインスタンスを設定
|
||||
fn set_static_instance(&mut self, name: &str, instance: InstanceBox) -> Result<(), RuntimeError> {
|
||||
let global_box = self.shared.global_box.lock()
|
||||
.map_err(|_| RuntimeError::RuntimeFailure {
|
||||
message: "Failed to acquire global box lock".to_string()
|
||||
})?;
|
||||
|
||||
// statics名前空間を取得
|
||||
let statics_box = global_box.get_field("statics")
|
||||
.ok_or(RuntimeError::TypeError {
|
||||
message: "statics namespace not found in GlobalBox".to_string()
|
||||
})?;
|
||||
|
||||
let statics_instance = statics_box.as_any()
|
||||
.downcast_ref::<InstanceBox>()
|
||||
.ok_or(RuntimeError::TypeError {
|
||||
message: "statics field is not an InstanceBox".to_string()
|
||||
})?;
|
||||
|
||||
// statics InstanceBoxのfieldsに直接挿入(動的フィールド追加)
|
||||
{
|
||||
let mut fields = statics_instance.fields.lock().unwrap();
|
||||
fields.insert(name.to_string(), Box::new(instance));
|
||||
}
|
||||
|
||||
eprintln!("🔥 Static box '{}' instance registered in statics namespace", name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 🔥 Static Boxかどうかをチェック
|
||||
pub(super) fn is_static_box(&self, name: &str) -> bool {
|
||||
if let Ok(definitions) = self.shared.static_box_definitions.read() {
|
||||
definitions.contains_key(name)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
617
src/interpreter/expressions.rs
Normal file
617
src/interpreter/expressions.rs
Normal file
@ -0,0 +1,617 @@
|
||||
/*!
|
||||
* Expression Processing Module
|
||||
*
|
||||
* Extracted from core.rs lines 408-787 (~380 lines)
|
||||
* Handles expression evaluation, binary operations, method calls, and field access
|
||||
* Core philosophy: "Everything is Box" with clean expression evaluation
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
use crate::ast::UnaryOperator;
|
||||
// TODO: Fix NullBox import issue later
|
||||
// use crate::NullBox;
|
||||
|
||||
impl NyashInterpreter {
|
||||
/// 式を実行 - Expression evaluation engine
|
||||
pub(super) fn execute_expression(&mut self, expression: &ASTNode) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
match expression {
|
||||
ASTNode::Literal { value, .. } => {
|
||||
Ok(value.to_nyash_box())
|
||||
}
|
||||
|
||||
ASTNode::Variable { name, .. } => {
|
||||
// 🌍 革命的変数解決:local変数 → GlobalBoxフィールド → エラー
|
||||
self.resolve_variable(name)
|
||||
.map_err(|_| RuntimeError::UndefinedVariableAt {
|
||||
name: name.clone(),
|
||||
span: expression.span()
|
||||
})
|
||||
}
|
||||
|
||||
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||||
self.execute_binary_op(operator, left, right)
|
||||
}
|
||||
|
||||
ASTNode::UnaryOp { operator, operand, .. } => {
|
||||
self.execute_unary_op(operator, operand)
|
||||
}
|
||||
|
||||
ASTNode::AwaitExpression { expression, .. } => {
|
||||
self.execute_await(expression)
|
||||
}
|
||||
|
||||
ASTNode::MethodCall { object, method, arguments, .. } => {
|
||||
self.execute_method_call(object, method, arguments)
|
||||
}
|
||||
|
||||
ASTNode::FieldAccess { object, field, .. } => {
|
||||
self.execute_field_access(object, field)
|
||||
}
|
||||
|
||||
ASTNode::New { class, arguments, type_arguments, .. } => {
|
||||
self.execute_new(class, arguments, type_arguments)
|
||||
}
|
||||
|
||||
ASTNode::This { .. } => {
|
||||
// 🌍 革命的this解決:local変数から取得
|
||||
self.resolve_variable("me")
|
||||
.map_err(|_| RuntimeError::InvalidOperation {
|
||||
message: "'this' is only available inside methods".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
ASTNode::Me { .. } => {
|
||||
// 🌍 革命的me解決:local変数から取得(thisと同じ)
|
||||
self.resolve_variable("me")
|
||||
.map_err(|_| RuntimeError::InvalidOperation {
|
||||
message: "'me' is only available inside methods".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
ASTNode::ThisField { field, .. } => {
|
||||
// 🌍 革命的this.fieldアクセス:local変数から取得
|
||||
let this_value = self.resolve_variable("me")
|
||||
.map_err(|_| RuntimeError::InvalidOperation {
|
||||
message: "'this' is not bound in the current context".to_string(),
|
||||
})?;
|
||||
|
||||
if let Some(instance) = this_value.as_any().downcast_ref::<InstanceBox>() {
|
||||
instance.get_field(field)
|
||||
.ok_or_else(|| RuntimeError::InvalidOperation {
|
||||
message: format!("Field '{}' not found on this", field)
|
||||
})
|
||||
} else {
|
||||
Err(RuntimeError::TypeError {
|
||||
message: "'this' is not an instance".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ASTNode::MeField { field, .. } => {
|
||||
// 🌍 革命的me.fieldアクセス:local変数から取得
|
||||
let me_value = self.resolve_variable("me")
|
||||
.map_err(|_| RuntimeError::InvalidOperation {
|
||||
message: "'this' is not bound in the current context".to_string(),
|
||||
})?;
|
||||
|
||||
if let Some(instance) = me_value.as_any().downcast_ref::<InstanceBox>() {
|
||||
instance.get_field(field)
|
||||
.ok_or_else(|| RuntimeError::InvalidOperation {
|
||||
message: format!("Field '{}' not found on me", field)
|
||||
})
|
||||
} else {
|
||||
Err(RuntimeError::TypeError {
|
||||
message: "'this' is not an instance".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ASTNode::FunctionCall { name, arguments, .. } => {
|
||||
self.execute_function_call(name, arguments)
|
||||
}
|
||||
|
||||
ASTNode::Arrow { sender, receiver, .. } => {
|
||||
self.execute_arrow(sender, receiver)
|
||||
}
|
||||
|
||||
ASTNode::Include { filename, .. } => {
|
||||
self.execute_include(filename)?;
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
_ => Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Cannot execute {:?} as expression", expression.node_type()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// 二項演算を実行 - Binary operation processing
|
||||
pub(super) fn execute_binary_op(&mut self, op: &BinaryOperator, left: &ASTNode, right: &ASTNode)
|
||||
-> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let left_val = self.execute_expression(left)?;
|
||||
let right_val = self.execute_expression(right)?;
|
||||
|
||||
match op {
|
||||
BinaryOperator::Add => {
|
||||
let add_box = AddBox::new(left_val, right_val);
|
||||
Ok(add_box.execute())
|
||||
}
|
||||
|
||||
BinaryOperator::Equal => {
|
||||
let result = left_val.equals(right_val.as_ref());
|
||||
Ok(Box::new(result))
|
||||
}
|
||||
|
||||
BinaryOperator::NotEqual => {
|
||||
let result = left_val.equals(right_val.as_ref());
|
||||
Ok(Box::new(BoolBox::new(!result.value)))
|
||||
}
|
||||
|
||||
BinaryOperator::And => {
|
||||
let left_bool = self.is_truthy(&left_val);
|
||||
if !left_bool {
|
||||
Ok(Box::new(BoolBox::new(false)))
|
||||
} else {
|
||||
let right_bool = self.is_truthy(&right_val);
|
||||
Ok(Box::new(BoolBox::new(right_bool)))
|
||||
}
|
||||
}
|
||||
|
||||
BinaryOperator::Or => {
|
||||
let left_bool = self.is_truthy(&left_val);
|
||||
if left_bool {
|
||||
Ok(Box::new(BoolBox::new(true)))
|
||||
} else {
|
||||
let right_bool = self.is_truthy(&right_val);
|
||||
Ok(Box::new(BoolBox::new(right_bool)))
|
||||
}
|
||||
}
|
||||
|
||||
BinaryOperator::Subtract => {
|
||||
let sub_box = SubtractBox::new(left_val, right_val);
|
||||
Ok(sub_box.execute())
|
||||
}
|
||||
|
||||
BinaryOperator::Multiply => {
|
||||
let mul_box = MultiplyBox::new(left_val, right_val);
|
||||
Ok(mul_box.execute())
|
||||
}
|
||||
|
||||
BinaryOperator::Divide => {
|
||||
let div_box = DivideBox::new(left_val, right_val);
|
||||
Ok(div_box.execute())
|
||||
}
|
||||
|
||||
BinaryOperator::Less => {
|
||||
let result = CompareBox::less(left_val.as_ref(), right_val.as_ref());
|
||||
Ok(Box::new(result))
|
||||
}
|
||||
|
||||
BinaryOperator::Greater => {
|
||||
let result = CompareBox::greater(left_val.as_ref(), right_val.as_ref());
|
||||
Ok(Box::new(result))
|
||||
}
|
||||
|
||||
BinaryOperator::LessEqual => {
|
||||
let result = CompareBox::less_equal(left_val.as_ref(), right_val.as_ref());
|
||||
Ok(Box::new(result))
|
||||
}
|
||||
|
||||
BinaryOperator::GreaterEqual => {
|
||||
let result = CompareBox::greater_equal(left_val.as_ref(), right_val.as_ref());
|
||||
Ok(Box::new(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 単項演算を実行 - Unary operation processing
|
||||
pub(super) fn execute_unary_op(&mut self, operator: &UnaryOperator, operand: &ASTNode)
|
||||
-> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let operand_val = self.execute_expression(operand)?;
|
||||
|
||||
match operator {
|
||||
UnaryOperator::Minus => {
|
||||
// 数値の符号反転
|
||||
if let Some(int_box) = operand_val.as_any().downcast_ref::<IntegerBox>() {
|
||||
Ok(Box::new(IntegerBox::new(-int_box.value)))
|
||||
} else if let Some(float_box) = operand_val.as_any().downcast_ref::<FloatBox>() {
|
||||
Ok(Box::new(FloatBox::new(-float_box.value)))
|
||||
} else {
|
||||
Err(RuntimeError::TypeError {
|
||||
message: "Unary minus can only be applied to Integer or Float".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
UnaryOperator::Not => {
|
||||
// 論理否定
|
||||
if let Some(bool_box) = operand_val.as_any().downcast_ref::<BoolBox>() {
|
||||
Ok(Box::new(BoolBox::new(!bool_box.value)))
|
||||
} else {
|
||||
// どんな値でもtruthyness判定してnot演算を適用
|
||||
let is_truthy = self.is_truthy(&operand_val);
|
||||
Ok(Box::new(BoolBox::new(!is_truthy)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// メソッド呼び出しを実行 - Method call processing
|
||||
pub(super) fn execute_method_call(&mut self, object: &ASTNode, method: &str, arguments: &[ASTNode])
|
||||
-> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
// 🔥 static関数のチェック
|
||||
if let ASTNode::Variable { name, .. } = object {
|
||||
// static関数が存在するかチェック
|
||||
let static_func = {
|
||||
let static_funcs = self.shared.static_functions.read().unwrap();
|
||||
if let Some(box_statics) = static_funcs.get(name) {
|
||||
if let Some(func) = box_statics.get(method) {
|
||||
Some(func.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(static_func) = static_func {
|
||||
// static関数を実行
|
||||
if let ASTNode::FunctionDeclaration { params, body, .. } = static_func {
|
||||
// 引数を評価
|
||||
let mut arg_values = Vec::new();
|
||||
for arg in arguments {
|
||||
arg_values.push(self.execute_expression(arg)?);
|
||||
}
|
||||
|
||||
// パラメータ数チェック
|
||||
if arg_values.len() != params.len() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Static method {}.{} expects {} arguments, got {}",
|
||||
name, method, params.len(), arg_values.len()),
|
||||
});
|
||||
}
|
||||
|
||||
// 🌍 local変数スタックを保存・クリア(static関数呼び出し開始)
|
||||
let saved_locals = self.save_local_vars();
|
||||
self.local_vars.clear();
|
||||
|
||||
// 📤 outbox変数スタックも保存・クリア(static関数専用)
|
||||
let saved_outbox = self.save_outbox_vars();
|
||||
self.outbox_vars.clear();
|
||||
|
||||
// 引数をlocal変数として設定
|
||||
for (param, value) in params.iter().zip(arg_values.iter()) {
|
||||
self.declare_local_variable(param, value.clone_box());
|
||||
}
|
||||
|
||||
// static関数の本体を実行
|
||||
let mut result = Box::new(VoidBox::new()) as Box<dyn NyashBox>;
|
||||
for statement in &body {
|
||||
result = self.execute_statement(statement)?;
|
||||
|
||||
// return文チェック
|
||||
if let super::ControlFlow::Return(return_val) = &self.control_flow {
|
||||
result = return_val.clone_box();
|
||||
self.control_flow = super::ControlFlow::None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// local変数スタックを復元
|
||||
self.restore_local_vars(saved_locals);
|
||||
|
||||
// outbox変数スタックを復元
|
||||
self.restore_outbox_vars(saved_outbox);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// オブジェクトを評価(通常のメソッド呼び出し)
|
||||
let obj_value = self.execute_expression(object)?;
|
||||
|
||||
// StringBox method calls
|
||||
if let Some(string_box) = obj_value.as_any().downcast_ref::<StringBox>() {
|
||||
return self.execute_string_method(string_box, method, arguments);
|
||||
}
|
||||
|
||||
// ArrayBox method calls
|
||||
if let Some(array_box) = obj_value.as_any().downcast_ref::<ArrayBox>() {
|
||||
return self.execute_array_method(array_box, method, arguments);
|
||||
}
|
||||
|
||||
// FileBox method calls
|
||||
if let Some(file_box) = obj_value.as_any().downcast_ref::<FileBox>() {
|
||||
return self.execute_file_method(file_box, method, arguments);
|
||||
}
|
||||
|
||||
// ResultBox method calls
|
||||
if let Some(result_box) = obj_value.as_any().downcast_ref::<ResultBox>() {
|
||||
return self.execute_result_method(result_box, method, arguments);
|
||||
}
|
||||
|
||||
// FutureBox method calls
|
||||
if let Some(future_box) = obj_value.as_any().downcast_ref::<FutureBox>() {
|
||||
return self.execute_future_method(future_box, method, arguments);
|
||||
}
|
||||
|
||||
// ChannelBox method calls
|
||||
if let Some(channel_box) = obj_value.as_any().downcast_ref::<ChannelBox>() {
|
||||
return self.execute_channel_method(channel_box, method, arguments);
|
||||
}
|
||||
|
||||
// MathBox method calls
|
||||
if let Some(math_box) = obj_value.as_any().downcast_ref::<MathBox>() {
|
||||
return self.execute_math_method(math_box, method, arguments);
|
||||
}
|
||||
|
||||
// NullBox method calls
|
||||
if let Some(null_box) = obj_value.as_any().downcast_ref::<crate::boxes::null_box::NullBox>() {
|
||||
return self.execute_null_method(null_box, method, arguments);
|
||||
}
|
||||
|
||||
// TimeBox method calls
|
||||
if let Some(time_box) = obj_value.as_any().downcast_ref::<TimeBox>() {
|
||||
return self.execute_time_method(time_box, method, arguments);
|
||||
}
|
||||
|
||||
// DateTimeBox method calls
|
||||
if let Some(datetime_box) = obj_value.as_any().downcast_ref::<DateTimeBox>() {
|
||||
return self.execute_datetime_method(datetime_box, method, arguments);
|
||||
}
|
||||
|
||||
// TimerBox method calls
|
||||
if let Some(timer_box) = obj_value.as_any().downcast_ref::<TimerBox>() {
|
||||
return self.execute_timer_method(timer_box, method, arguments);
|
||||
}
|
||||
|
||||
// MapBox method calls
|
||||
if let Some(map_box) = obj_value.as_any().downcast_ref::<MapBox>() {
|
||||
return self.execute_map_method(map_box, method, arguments);
|
||||
}
|
||||
|
||||
// RandomBox method calls
|
||||
if let Some(random_box) = obj_value.as_any().downcast_ref::<RandomBox>() {
|
||||
return self.execute_random_method(random_box, method, arguments);
|
||||
}
|
||||
|
||||
// SoundBox method calls
|
||||
if let Some(sound_box) = obj_value.as_any().downcast_ref::<SoundBox>() {
|
||||
return self.execute_sound_method(sound_box, method, arguments);
|
||||
}
|
||||
|
||||
// DebugBox method calls
|
||||
if let Some(debug_box) = obj_value.as_any().downcast_ref::<DebugBox>() {
|
||||
return self.execute_debug_method(debug_box, method, arguments);
|
||||
}
|
||||
|
||||
// ConsoleBox method calls
|
||||
if let Some(console_box) = obj_value.as_any().downcast_ref::<crate::boxes::console_box::ConsoleBox>() {
|
||||
return self.execute_console_method(console_box, method, arguments);
|
||||
}
|
||||
|
||||
// WebDisplayBox method calls (WASM環境のみ)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if let Some(web_display_box) = obj_value.as_any().downcast_ref::<crate::boxes::WebDisplayBox>() {
|
||||
return self.execute_web_display_method(web_display_box, method, arguments);
|
||||
}
|
||||
|
||||
// WebConsoleBox method calls (WASM環境のみ)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if let Some(web_console_box) = obj_value.as_any().downcast_ref::<crate::boxes::WebConsoleBox>() {
|
||||
return self.execute_web_console_method(web_console_box, method, arguments);
|
||||
}
|
||||
|
||||
// WebCanvasBox method calls (WASM環境のみ)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if let Some(web_canvas_box) = obj_value.as_any().downcast_ref::<crate::boxes::WebCanvasBox>() {
|
||||
return self.execute_web_canvas_method(web_canvas_box, method, arguments);
|
||||
}
|
||||
|
||||
// MethodBox method calls
|
||||
if let Some(method_box) = obj_value.as_any().downcast_ref::<crate::method_box::MethodBox>() {
|
||||
return self.execute_method_box_method(method_box, method, arguments);
|
||||
}
|
||||
|
||||
// IntegerBox method calls
|
||||
if let Some(integer_box) = obj_value.as_any().downcast_ref::<IntegerBox>() {
|
||||
return self.execute_integer_method(integer_box, method, arguments);
|
||||
}
|
||||
|
||||
// FloatBox method calls (将来的に追加予定)
|
||||
|
||||
// RangeBox method calls (将来的に追加予定)
|
||||
|
||||
// InstanceBox method calls
|
||||
if let Some(instance) = obj_value.as_any().downcast_ref::<InstanceBox>() {
|
||||
// fini()は特別処理
|
||||
if method == "fini" {
|
||||
// 既に解放済みの場合は何もしない(二重fini()対策)
|
||||
if instance.is_finalized() {
|
||||
return Ok(Box::new(VoidBox::new()));
|
||||
}
|
||||
|
||||
// まず、Box内で定義されたfini()メソッドがあれば実行
|
||||
if let Some(fini_method) = instance.get_method("fini") {
|
||||
if let ASTNode::FunctionDeclaration { body, .. } = fini_method.clone() {
|
||||
// 🌍 革命的メソッド実行:local変数スタックを使用
|
||||
let saved_locals = self.save_local_vars();
|
||||
self.local_vars.clear();
|
||||
|
||||
// thisをlocal変数として設定
|
||||
self.declare_local_variable("me", obj_value.clone_box());
|
||||
|
||||
// fini()メソッドの本体を実行
|
||||
let mut _result = Box::new(VoidBox::new()) as Box<dyn NyashBox>;
|
||||
for statement in &body {
|
||||
_result = self.execute_statement(statement)?;
|
||||
|
||||
// return文チェック
|
||||
if let super::ControlFlow::Return(_) = &self.control_flow {
|
||||
self.control_flow = super::ControlFlow::None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// local変数スタックを復元
|
||||
self.restore_local_vars(saved_locals);
|
||||
}
|
||||
}
|
||||
|
||||
// インスタンスの内部的な解放処理
|
||||
instance.fini().map_err(|e| RuntimeError::InvalidOperation {
|
||||
message: e,
|
||||
})?;
|
||||
finalization::mark_as_finalized(instance.box_id());
|
||||
return Ok(Box::new(VoidBox::new()));
|
||||
}
|
||||
|
||||
// メソッドを取得
|
||||
let method_ast = instance.get_method(method)
|
||||
.ok_or(RuntimeError::InvalidOperation {
|
||||
message: format!("Method '{}' not found in {}", method, instance.class_name),
|
||||
})?
|
||||
.clone();
|
||||
|
||||
// メソッドが関数宣言の形式であることを確認
|
||||
if let ASTNode::FunctionDeclaration { params, body, .. } = method_ast {
|
||||
// 引数を評価
|
||||
let mut arg_values = Vec::new();
|
||||
for arg in arguments {
|
||||
arg_values.push(self.execute_expression(arg)?);
|
||||
}
|
||||
|
||||
// パラメータ数チェック
|
||||
if arg_values.len() != params.len() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Method {} expects {} arguments, got {}",
|
||||
method, params.len(), arg_values.len()),
|
||||
});
|
||||
}
|
||||
|
||||
// 🌍 革命的メソッド実行:local変数スタックを使用
|
||||
let saved_locals = self.save_local_vars();
|
||||
self.local_vars.clear();
|
||||
|
||||
// thisをlocal変数として設定
|
||||
self.declare_local_variable("me", obj_value.clone_box());
|
||||
|
||||
// パラメータをlocal変数として設定
|
||||
for (param, value) in params.iter().zip(arg_values.iter()) {
|
||||
self.declare_local_variable(param, value.clone_box());
|
||||
}
|
||||
|
||||
// メソッド本体を実行
|
||||
let mut result: Box<dyn NyashBox> = Box::new(VoidBox::new());
|
||||
for statement in &body {
|
||||
result = self.execute_statement(statement)?;
|
||||
|
||||
// return文チェック
|
||||
if let super::ControlFlow::Return(return_val) = &self.control_flow {
|
||||
result = return_val.clone_box();
|
||||
self.control_flow = super::ControlFlow::None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// local変数スタックを復元
|
||||
self.restore_local_vars(saved_locals);
|
||||
|
||||
Ok(result)
|
||||
} else {
|
||||
Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Method '{}' is not a valid function declaration", method),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Err(RuntimeError::TypeError {
|
||||
message: format!("Cannot call method '{}' on non-instance type", method),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// フィールドアクセスを実行 - Field access processing
|
||||
pub(super) fn execute_field_access(&mut self, object: &ASTNode, field: &str)
|
||||
-> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
// 🔥 Static Boxアクセスチェック
|
||||
if let ASTNode::Variable { name, .. } = object {
|
||||
// Static boxの可能性をチェック
|
||||
if self.is_static_box(name) {
|
||||
return self.execute_static_field_access(name, field);
|
||||
}
|
||||
}
|
||||
|
||||
// オブジェクトを評価(通常のフィールドアクセス)
|
||||
let obj_value = self.execute_expression(object)?;
|
||||
|
||||
// InstanceBoxにキャスト
|
||||
if let Some(instance) = obj_value.as_any().downcast_ref::<InstanceBox>() {
|
||||
// フィールドの値を取得
|
||||
instance.get_field(field)
|
||||
.ok_or(RuntimeError::InvalidOperation {
|
||||
message: format!("Field '{}' not found in {}", field, instance.class_name),
|
||||
})
|
||||
} else {
|
||||
Err(RuntimeError::TypeError {
|
||||
message: format!("Cannot access field '{}' on non-instance type. Type: {}", field, obj_value.type_name()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 🔥 Static Box名前空間のフィールドアクセス
|
||||
fn execute_static_field_access(&mut self, static_box_name: &str, field: &str)
|
||||
-> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
// 1. Static Boxの初期化を確実に実行
|
||||
self.ensure_static_box_initialized(static_box_name)?;
|
||||
|
||||
// 2. GlobalBox.statics.{static_box_name} からインスタンスを取得
|
||||
let global_box = self.shared.global_box.lock()
|
||||
.map_err(|_| RuntimeError::RuntimeFailure {
|
||||
message: "Failed to acquire global box lock".to_string()
|
||||
})?;
|
||||
|
||||
let statics_box = global_box.get_field("statics")
|
||||
.ok_or(RuntimeError::RuntimeFailure {
|
||||
message: "statics namespace not found in GlobalBox".to_string()
|
||||
})?;
|
||||
|
||||
let statics_instance = statics_box.as_any()
|
||||
.downcast_ref::<InstanceBox>()
|
||||
.ok_or(RuntimeError::TypeError {
|
||||
message: "statics field is not an InstanceBox".to_string()
|
||||
})?;
|
||||
|
||||
let static_box_instance = statics_instance.get_field(static_box_name)
|
||||
.ok_or(RuntimeError::RuntimeFailure {
|
||||
message: format!("Static box '{}' instance not found in statics namespace", static_box_name)
|
||||
})?;
|
||||
|
||||
let instance = static_box_instance.as_any()
|
||||
.downcast_ref::<InstanceBox>()
|
||||
.ok_or(RuntimeError::TypeError {
|
||||
message: format!("Static box '{}' is not an InstanceBox", static_box_name)
|
||||
})?;
|
||||
|
||||
// 3. フィールドアクセス
|
||||
instance.get_field(field)
|
||||
.ok_or(RuntimeError::InvalidOperation {
|
||||
message: format!("Field '{}' not found in static box '{}'", field, static_box_name),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/// await式を実行 - Execute await expression
|
||||
pub(super) fn execute_await(&mut self, expression: &ASTNode) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let value = self.execute_expression(expression)?;
|
||||
|
||||
// FutureBoxなら待機して結果を取得
|
||||
if let Some(future) = value.as_any().downcast_ref::<FutureBox>() {
|
||||
future.wait_and_get()
|
||||
.map_err(|msg| RuntimeError::InvalidOperation { message: msg })
|
||||
} else {
|
||||
// FutureBoxでなければそのまま返す
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
96
src/interpreter/functions.rs
Normal file
96
src/interpreter/functions.rs
Normal file
@ -0,0 +1,96 @@
|
||||
/*!
|
||||
* Function Processing Module
|
||||
*
|
||||
* Extracted from core.rs - function call and definition handling
|
||||
* Handles function declarations, calls, and function-related operations
|
||||
* Core philosophy: "Everything is Box" with structured function processing
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
|
||||
impl NyashInterpreter {
|
||||
/// 関数呼び出しを実行 - 🌍 革命的実装:GlobalBoxのメソッド呼び出し
|
||||
pub(super) fn execute_function_call(&mut self, name: &str, arguments: &[ASTNode])
|
||||
-> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
// コンストラクタ内での親コンストラクタ呼び出しチェック
|
||||
if let Some(context) = self.current_constructor_context.clone() {
|
||||
if let Some(parent_class) = context.parent_class {
|
||||
if name == parent_class {
|
||||
// 親コンストラクタ呼び出し
|
||||
return self.execute_parent_constructor(&parent_class, arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 🌍 GlobalBoxのメソッドとして実行
|
||||
let global_box = self.shared.global_box.lock().unwrap();
|
||||
let method_ast = global_box.get_method(name)
|
||||
.ok_or(RuntimeError::UndefinedFunction { name: name.to_string() })?
|
||||
.clone();
|
||||
drop(global_box);
|
||||
|
||||
// メソッド呼び出しとして実行(GlobalBoxインスタンス上で)
|
||||
if let ASTNode::FunctionDeclaration { params, body, .. } = method_ast {
|
||||
// 引数を評価
|
||||
let mut arg_values = Vec::new();
|
||||
for arg in arguments {
|
||||
arg_values.push(self.execute_expression(arg)?);
|
||||
}
|
||||
|
||||
// パラメータ数チェック
|
||||
if arg_values.len() != params.len() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Function {} expects {} arguments, got {}",
|
||||
name, params.len(), arg_values.len()),
|
||||
});
|
||||
}
|
||||
|
||||
// 🌍 local変数スタックを保存・クリア(関数呼び出し開始)
|
||||
let saved_locals = self.save_local_vars();
|
||||
self.local_vars.clear();
|
||||
|
||||
// パラメータをlocal変数として設定
|
||||
for (param, value) in params.iter().zip(arg_values.iter()) {
|
||||
self.declare_local_variable(param, value.clone_box());
|
||||
}
|
||||
|
||||
// 関数本体を実行
|
||||
let mut result: Box<dyn NyashBox> = Box::new(VoidBox::new());
|
||||
for statement in &body {
|
||||
result = self.execute_statement(statement)?;
|
||||
|
||||
// return文チェック
|
||||
if let super::ControlFlow::Return(return_val) = &self.control_flow {
|
||||
result = return_val.clone_box();
|
||||
self.control_flow = super::ControlFlow::None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 🌍 local変数スタックを復元(関数呼び出し終了)
|
||||
self.restore_local_vars(saved_locals);
|
||||
|
||||
Ok(result)
|
||||
} else {
|
||||
Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Function '{}' is not a valid function declaration", name),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 関数宣言を登録 - 🌍 革命的実装:GlobalBoxのメソッドとして登録
|
||||
pub(super) fn register_function_declaration(&mut self, name: String, params: Vec<String>, body: Vec<ASTNode>) {
|
||||
// 🌍 GlobalBoxのメソッドとして登録
|
||||
let func_ast = ASTNode::FunctionDeclaration {
|
||||
name: name.clone(),
|
||||
params,
|
||||
body,
|
||||
is_static: false, // 通常の関数は静的でない
|
||||
span: crate::ast::Span::unknown(), // デフォルトspan
|
||||
};
|
||||
|
||||
self.register_global_function(name, func_ast).unwrap_or_else(|err| {
|
||||
eprintln!("Warning: Failed to register global function: {}", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
114
src/interpreter/io.rs
Normal file
114
src/interpreter/io.rs
Normal file
@ -0,0 +1,114 @@
|
||||
/*!
|
||||
* I/O Processing Module
|
||||
*
|
||||
* Extracted from core.rs - file operations and communication
|
||||
* Handles include system, arrow operators, and I/O-related operations
|
||||
* Core philosophy: "Everything is Box" with secure I/O processing
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
use crate::parser::NyashParser;
|
||||
|
||||
impl NyashInterpreter {
|
||||
/// include文を実行:ファイル読み込み・パース・実行 - File inclusion system
|
||||
pub(super) fn execute_include(&mut self, filename: &str) -> Result<(), RuntimeError> {
|
||||
// パス正規化(簡易版)
|
||||
let canonical_path = if filename.starts_with("./") || filename.starts_with("../") {
|
||||
filename.to_string()
|
||||
} else {
|
||||
format!("./{}", filename)
|
||||
};
|
||||
|
||||
// 重複読み込みチェック
|
||||
if self.shared.included_files.lock().unwrap().contains(&canonical_path) {
|
||||
return Ok(()); // 既に読み込み済み
|
||||
}
|
||||
|
||||
// ファイル読み込み
|
||||
let content = std::fs::read_to_string(&canonical_path)
|
||||
.map_err(|e| RuntimeError::InvalidOperation {
|
||||
message: format!("Failed to read file '{}': {}", filename, e),
|
||||
})?;
|
||||
|
||||
// パース
|
||||
let ast = NyashParser::parse_from_string(&content)
|
||||
.map_err(|e| RuntimeError::InvalidOperation {
|
||||
message: format!("Parse error in '{}': {:?}", filename, e),
|
||||
})?;
|
||||
|
||||
// 重複防止リストに追加
|
||||
self.shared.included_files.lock().unwrap().insert(canonical_path);
|
||||
|
||||
// 現在の環境で実行
|
||||
self.execute(ast)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Arrow演算子を実行: sender >> receiver - Channel communication
|
||||
pub(super) fn execute_arrow(&mut self, sender: &ASTNode, receiver: &ASTNode)
|
||||
-> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
// 送信者を評価
|
||||
let sender_value = self.execute_expression(sender)?;
|
||||
|
||||
// 受信者を評価
|
||||
let receiver_str = match receiver {
|
||||
ASTNode::Variable { name, .. } => name.clone(),
|
||||
ASTNode::Literal { value, .. } => {
|
||||
// "*" のようなリテラルの場合
|
||||
value.to_string()
|
||||
}
|
||||
_ => {
|
||||
// その他の式の場合は評価して文字列化
|
||||
let receiver_value = self.execute_expression(receiver)?;
|
||||
receiver_value.to_string_box().value
|
||||
}
|
||||
};
|
||||
|
||||
// 送信者の名前を取得
|
||||
let sender_name = sender_value.to_string_box().value;
|
||||
|
||||
// ChannelBoxを作成して返す
|
||||
let channel_box = Box::new(ChannelBox::new(&sender_name, &receiver_str)) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
Ok(channel_box)
|
||||
}
|
||||
|
||||
/// nowait文を実行 - 非同期実行(真の非同期実装) - Async execution
|
||||
pub(super) fn execute_nowait(&mut self, variable: &str, expression: &ASTNode) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
use crate::box_trait::FutureBox;
|
||||
use std::thread;
|
||||
|
||||
// FutureBoxを作成
|
||||
let future_box = FutureBox::new();
|
||||
let future_box_clone = future_box.clone();
|
||||
|
||||
// 式をクローンして別スレッドで実行
|
||||
let expr_clone = expression.clone();
|
||||
let shared_state = self.shared.clone();
|
||||
|
||||
// 別スレッドで非同期実行
|
||||
thread::spawn(move || {
|
||||
// 新しいインタープリタインスタンスを作成(SharedStateを使用)
|
||||
let mut async_interpreter = NyashInterpreter::with_shared(shared_state);
|
||||
|
||||
// 式を評価
|
||||
match async_interpreter.execute_expression(&expr_clone) {
|
||||
Ok(result) => {
|
||||
future_box_clone.set_result(result);
|
||||
}
|
||||
Err(e) => {
|
||||
// エラーをErrorBoxとして設定
|
||||
let error_box = Box::new(ErrorBox::new("RuntimeError", &format!("{:?}", e)));
|
||||
future_box_clone.set_result(error_box);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// FutureBoxを変数に保存
|
||||
let future_box_instance = Box::new(future_box) as Box<dyn NyashBox>;
|
||||
self.set_variable(variable, future_box_instance)?;
|
||||
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
}
|
||||
98
src/interpreter/mod.rs
Normal file
98
src/interpreter/mod.rs
Normal file
@ -0,0 +1,98 @@
|
||||
/*!
|
||||
* Nyash Interpreter - Modular Rust Implementation
|
||||
*
|
||||
* Refactored from massive 2,633-line interpreter.rs into logical modules
|
||||
* Everything is Box philosophy with clean separation of concerns
|
||||
*/
|
||||
|
||||
// Import all necessary dependencies
|
||||
use crate::ast::{ASTNode, BinaryOperator, CatchClause};
|
||||
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, VoidBox, AddBox, SubtractBox, MultiplyBox, DivideBox, CompareBox, ArrayBox, FileBox, ResultBox, ErrorBox, FutureBox};
|
||||
use crate::instance::InstanceBox;
|
||||
use crate::channel_box::{ChannelBox, MessageBox};
|
||||
use crate::boxes::math_box::{MathBox, FloatBox, RangeBox};
|
||||
use crate::boxes::time_box::{TimeBox, DateTimeBox, TimerBox};
|
||||
use crate::boxes::map_box::MapBox;
|
||||
use crate::boxes::random_box::RandomBox;
|
||||
use crate::boxes::sound_box::SoundBox;
|
||||
use crate::boxes::debug_box::DebugBox;
|
||||
use crate::method_box::MethodBox;
|
||||
use crate::finalization;
|
||||
use crate::exception_box;
|
||||
use std::collections::HashMap;
|
||||
|
||||
// Module declarations
|
||||
mod box_methods;
|
||||
mod core;
|
||||
mod expressions;
|
||||
mod statements;
|
||||
mod functions;
|
||||
mod objects;
|
||||
mod io;
|
||||
|
||||
// Main interpreter implementation - will be moved from interpreter.rs
|
||||
|
||||
|
||||
/// 実行制御フロー
|
||||
#[derive(Debug)]
|
||||
pub enum ControlFlow {
|
||||
None,
|
||||
Break,
|
||||
Return(Box<dyn NyashBox>),
|
||||
Throw(Box<dyn NyashBox>),
|
||||
}
|
||||
|
||||
/// コンストラクタ実行コンテキスト
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConstructorContext {
|
||||
pub class_name: String,
|
||||
pub parent_class: Option<String>,
|
||||
}
|
||||
|
||||
/// Box宣言を保持する構造体
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BoxDeclaration {
|
||||
pub name: String,
|
||||
pub fields: Vec<String>,
|
||||
pub methods: HashMap<String, ASTNode>,
|
||||
pub constructors: HashMap<String, ASTNode>,
|
||||
pub init_fields: Vec<String>,
|
||||
pub is_interface: bool,
|
||||
pub extends: Option<String>,
|
||||
pub implements: Vec<String>,
|
||||
pub type_parameters: Vec<String>, // 🔥 ジェネリクス型パラメータ
|
||||
}
|
||||
|
||||
/// 🔥 Static Box定義を保持する構造体
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StaticBoxDefinition {
|
||||
pub name: String,
|
||||
pub fields: Vec<String>,
|
||||
pub methods: HashMap<String, ASTNode>,
|
||||
pub init_fields: Vec<String>,
|
||||
pub static_init: Option<Vec<ASTNode>>, // static { } ブロック
|
||||
pub extends: Option<String>,
|
||||
pub implements: Vec<String>,
|
||||
pub type_parameters: Vec<String>,
|
||||
/// 初期化状態
|
||||
pub initialization_state: StaticBoxState,
|
||||
}
|
||||
|
||||
/// 🔥 Static Box初期化状態
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum StaticBoxState {
|
||||
NotInitialized, // 未初期化
|
||||
Initializing, // 初期化中(循環参照検出用)
|
||||
Initialized, // 初期化完了
|
||||
}
|
||||
|
||||
/// 関数宣言を保持する構造体
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FunctionDeclaration {
|
||||
pub name: String,
|
||||
pub params: Vec<String>,
|
||||
pub body: Vec<ASTNode>,
|
||||
}
|
||||
|
||||
// Re-export core interpreter types
|
||||
pub use core::*;
|
||||
746
src/interpreter/objects.rs
Normal file
746
src/interpreter/objects.rs
Normal file
@ -0,0 +1,746 @@
|
||||
/*!
|
||||
* Object Processing Module
|
||||
*
|
||||
* Extracted from core.rs - object creation, construction, and inheritance
|
||||
* Handles Box declarations, instantiation, constructors, and inheritance system
|
||||
* Core philosophy: "Everything is Box" with complete OOP support
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
use crate::boxes::null_box::NullBox;
|
||||
use crate::boxes::console_box::ConsoleBox;
|
||||
|
||||
impl NyashInterpreter {
|
||||
/// new式を実行 - Object creation engine
|
||||
pub(super) fn execute_new(&mut self, class: &str, arguments: &[ASTNode], type_arguments: &[String])
|
||||
-> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
// 組み込みBox型のチェック
|
||||
match class {
|
||||
"ArrayBox" => {
|
||||
// ArrayBoxは引数なしで作成
|
||||
if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("ArrayBox constructor expects 0 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let array_box = Box::new(ArrayBox::new()) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(array_box);
|
||||
}
|
||||
"FileBox" => {
|
||||
// FileBoxは引数1個(ファイルパス)で作成
|
||||
if arguments.len() != 1 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("FileBox constructor expects 1 argument, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let path_value = self.execute_expression(&arguments[0])?;
|
||||
if let Some(path_str) = path_value.as_any().downcast_ref::<StringBox>() {
|
||||
let file_box = Box::new(FileBox::new(&path_str.value)) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(file_box);
|
||||
} else {
|
||||
return Err(RuntimeError::TypeError {
|
||||
message: "FileBox constructor requires string path argument".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
"ResultBox" => {
|
||||
// ResultBoxは引数1個(成功値)で作成
|
||||
if arguments.len() != 1 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("ResultBox constructor expects 1 argument, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let value = self.execute_expression(&arguments[0])?;
|
||||
let result_box = Box::new(ResultBox::new_success(value)) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(result_box);
|
||||
}
|
||||
"ErrorBox" => {
|
||||
// ErrorBoxは引数2個(エラータイプ、メッセージ)で作成
|
||||
if arguments.len() != 2 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("ErrorBox constructor expects 2 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let error_type_value = self.execute_expression(&arguments[0])?;
|
||||
let message_value = self.execute_expression(&arguments[1])?;
|
||||
|
||||
if let (Some(error_type_str), Some(message_str)) = (
|
||||
error_type_value.as_any().downcast_ref::<StringBox>(),
|
||||
message_value.as_any().downcast_ref::<StringBox>()
|
||||
) {
|
||||
let error_box = Box::new(ErrorBox::new(&error_type_str.value, &message_str.value)) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(error_box);
|
||||
} else {
|
||||
return Err(RuntimeError::TypeError {
|
||||
message: "ErrorBox constructor requires two string arguments".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
"MathBox" => {
|
||||
// MathBoxは引数なしで作成
|
||||
if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("MathBox constructor expects 0 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let math_box = Box::new(MathBox::new()) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(math_box);
|
||||
}
|
||||
"NullBox" => {
|
||||
// NullBoxは引数なしで作成
|
||||
if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("NullBox constructor expects 0 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let null_box = Box::new(NullBox::new()) as Box<dyn NyashBox>;
|
||||
return Ok(null_box);
|
||||
}
|
||||
"ConsoleBox" => {
|
||||
// ConsoleBoxは引数なしで作成(ブラウザconsole連携用)
|
||||
if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("ConsoleBox constructor expects 0 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let console_box = Box::new(ConsoleBox::new()) as Box<dyn NyashBox>;
|
||||
return Ok(console_box);
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
"WebDisplayBox" => {
|
||||
// WebDisplayBoxは引数1個(要素ID)で作成(ブラウザHTML操作用)
|
||||
if arguments.len() != 1 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("WebDisplayBox constructor expects 1 argument (element_id), got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let element_id_value = self.execute_expression(&arguments[0])?;
|
||||
if let Some(id_str) = element_id_value.as_any().downcast_ref::<StringBox>() {
|
||||
let web_display_box = Box::new(crate::boxes::WebDisplayBox::new(id_str.value.clone())) as Box<dyn NyashBox>;
|
||||
return Ok(web_display_box);
|
||||
} else {
|
||||
return Err(RuntimeError::TypeError {
|
||||
message: "WebDisplayBox constructor requires string element_id argument".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
"WebConsoleBox" => {
|
||||
// WebConsoleBoxは引数1個(要素ID)で作成(ブラウザコンソール風出力用)
|
||||
if arguments.len() != 1 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("WebConsoleBox constructor expects 1 argument (element_id), got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let element_id_value = self.execute_expression(&arguments[0])?;
|
||||
if let Some(id_str) = element_id_value.as_any().downcast_ref::<StringBox>() {
|
||||
let web_console_box = Box::new(crate::boxes::WebConsoleBox::new(id_str.value.clone())) as Box<dyn NyashBox>;
|
||||
return Ok(web_console_box);
|
||||
} else {
|
||||
return Err(RuntimeError::TypeError {
|
||||
message: "WebConsoleBox constructor requires string element_id argument".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
"WebCanvasBox" => {
|
||||
// WebCanvasBoxは引数3個(canvas ID、幅、高さ)で作成
|
||||
if arguments.len() != 3 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("WebCanvasBox constructor expects 3 arguments (canvas_id, width, height), got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
|
||||
// Canvas ID
|
||||
let canvas_id_value = self.execute_expression(&arguments[0])?;
|
||||
let canvas_id = if let Some(id_str) = canvas_id_value.as_any().downcast_ref::<StringBox>() {
|
||||
id_str.value.clone()
|
||||
} else {
|
||||
return Err(RuntimeError::TypeError {
|
||||
message: "WebCanvasBox constructor requires string canvas_id as first argument".to_string(),
|
||||
});
|
||||
};
|
||||
|
||||
// Width
|
||||
let width_value = self.execute_expression(&arguments[1])?;
|
||||
let width = if let Some(int_box) = width_value.as_any().downcast_ref::<IntegerBox>() {
|
||||
int_box.value as u32
|
||||
} else {
|
||||
return Err(RuntimeError::TypeError {
|
||||
message: "WebCanvasBox constructor requires integer width as second argument".to_string(),
|
||||
});
|
||||
};
|
||||
|
||||
// Height
|
||||
let height_value = self.execute_expression(&arguments[2])?;
|
||||
let height = if let Some(int_box) = height_value.as_any().downcast_ref::<IntegerBox>() {
|
||||
int_box.value as u32
|
||||
} else {
|
||||
return Err(RuntimeError::TypeError {
|
||||
message: "WebCanvasBox constructor requires integer height as third argument".to_string(),
|
||||
});
|
||||
};
|
||||
|
||||
let web_canvas_box = Box::new(crate::boxes::WebCanvasBox::new(canvas_id, width, height)) as Box<dyn NyashBox>;
|
||||
return Ok(web_canvas_box);
|
||||
}
|
||||
"FloatBox" => {
|
||||
// FloatBoxは引数1個(数値)で作成
|
||||
if arguments.len() != 1 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("FloatBox constructor expects 1 argument, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let value = self.execute_expression(&arguments[0])?;
|
||||
if let Some(int_box) = value.as_any().downcast_ref::<IntegerBox>() {
|
||||
let float_box = Box::new(FloatBox::new(int_box.value as f64)) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(float_box);
|
||||
} else if let Some(float_box) = value.as_any().downcast_ref::<FloatBox>() {
|
||||
let new_float_box = Box::new(FloatBox::new(float_box.value)) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(new_float_box);
|
||||
} else {
|
||||
return Err(RuntimeError::TypeError {
|
||||
message: "FloatBox constructor requires numeric argument".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
"RangeBox" => {
|
||||
// RangeBoxは引数2-3個(start, end, [step])で作成
|
||||
if arguments.len() < 2 || arguments.len() > 3 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("RangeBox constructor expects 2-3 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let start_value = self.execute_expression(&arguments[0])?;
|
||||
let end_value = self.execute_expression(&arguments[1])?;
|
||||
let step_value = if arguments.len() == 3 {
|
||||
self.execute_expression(&arguments[2])?
|
||||
} else {
|
||||
Box::new(IntegerBox::new(1))
|
||||
};
|
||||
|
||||
if let (Some(start_int), Some(end_int), Some(step_int)) = (
|
||||
start_value.as_any().downcast_ref::<IntegerBox>(),
|
||||
end_value.as_any().downcast_ref::<IntegerBox>(),
|
||||
step_value.as_any().downcast_ref::<IntegerBox>()
|
||||
) {
|
||||
let range_box = Box::new(RangeBox::new(start_int.value, end_int.value, step_int.value)) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(range_box);
|
||||
} else {
|
||||
return Err(RuntimeError::TypeError {
|
||||
message: "RangeBox constructor requires integer arguments".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
"TimeBox" => {
|
||||
// TimeBoxは引数なしで作成
|
||||
if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("TimeBox constructor expects 0 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let time_box = Box::new(TimeBox::new()) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(time_box);
|
||||
}
|
||||
"DateTimeBox" => {
|
||||
// DateTimeBoxは引数なしで現在時刻、または引数1個でタイムスタンプ
|
||||
match arguments.len() {
|
||||
0 => {
|
||||
let datetime_box = Box::new(DateTimeBox::now()) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(datetime_box);
|
||||
}
|
||||
1 => {
|
||||
let timestamp_value = self.execute_expression(&arguments[0])?;
|
||||
if let Some(int_box) = timestamp_value.as_any().downcast_ref::<IntegerBox>() {
|
||||
let datetime_box = Box::new(DateTimeBox::from_timestamp(int_box.value)) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(datetime_box);
|
||||
} else {
|
||||
return Err(RuntimeError::TypeError {
|
||||
message: "DateTimeBox constructor requires integer timestamp".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("DateTimeBox constructor expects 0-1 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
"TimerBox" => {
|
||||
// TimerBoxは引数なしで作成
|
||||
if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("TimerBox constructor expects 0 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let timer_box = Box::new(TimerBox::new()) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(timer_box);
|
||||
}
|
||||
"MapBox" => {
|
||||
// MapBoxは引数なしで作成
|
||||
if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("MapBox constructor expects 0 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let map_box = Box::new(MapBox::new()) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(map_box);
|
||||
}
|
||||
"RandomBox" => {
|
||||
// RandomBoxは引数なしで作成
|
||||
if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("RandomBox constructor expects 0 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let random_box = Box::new(RandomBox::new()) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(random_box);
|
||||
}
|
||||
"SoundBox" => {
|
||||
// SoundBoxは引数なしで作成
|
||||
if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("SoundBox constructor expects 0 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let sound_box = Box::new(SoundBox::new()) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(sound_box);
|
||||
}
|
||||
"DebugBox" => {
|
||||
// DebugBoxは引数なしで作成
|
||||
if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("DebugBox constructor expects 0 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let debug_box = Box::new(DebugBox::new()) as Box<dyn NyashBox>;
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
return Ok(debug_box);
|
||||
}
|
||||
"MethodBox" => {
|
||||
// MethodBoxは引数2個(インスタンス、メソッド名)で作成
|
||||
if arguments.len() != 2 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("MethodBox constructor expects 2 arguments (instance, method_name), got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
|
||||
// インスタンスを評価
|
||||
let instance = self.execute_expression(&arguments[0])?;
|
||||
|
||||
// メソッド名を評価
|
||||
let method_name_value = self.execute_expression(&arguments[1])?;
|
||||
if let Some(method_name_str) = method_name_value.as_any().downcast_ref::<StringBox>() {
|
||||
let method_box = Box::new(MethodBox::new(instance, method_name_str.value.clone())) as Box<dyn NyashBox>;
|
||||
return Ok(method_box);
|
||||
} else {
|
||||
return Err(RuntimeError::TypeError {
|
||||
message: "MethodBox constructor requires string method name as second argument".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// 🔥 Static Boxインスタンス化禁止チェック
|
||||
if self.is_static_box(class) {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Cannot instantiate static box '{}'. Static boxes cannot be instantiated.", class),
|
||||
});
|
||||
}
|
||||
|
||||
// ユーザー定義Box宣言を探す
|
||||
let box_decl = {
|
||||
let box_decls = self.shared.box_declarations.read().unwrap();
|
||||
box_decls.get(class)
|
||||
.ok_or(RuntimeError::UndefinedClass { name: class.to_string() })?
|
||||
.clone()
|
||||
};
|
||||
|
||||
// 🔥 ジェネリクス型引数の検証
|
||||
if !box_decl.type_parameters.is_empty() || !type_arguments.is_empty() {
|
||||
self.validate_generic_arguments(&box_decl, type_arguments)?;
|
||||
}
|
||||
|
||||
// インターフェースはインスタンス化できない
|
||||
if box_decl.is_interface {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Cannot instantiate interface '{}'", class),
|
||||
});
|
||||
}
|
||||
|
||||
// 🚀 ジェネリクス型の特殊化処理
|
||||
let (final_box_decl, actual_class_name) = if !type_arguments.is_empty() {
|
||||
// ジェネリクス型を特殊化
|
||||
let specialized = self.specialize_generic_class(&box_decl, type_arguments)?;
|
||||
let specialized_name = specialized.name.clone();
|
||||
(specialized, specialized_name)
|
||||
} else {
|
||||
(box_decl.clone(), class.to_string())
|
||||
};
|
||||
|
||||
// 継承チェーンを解決してフィールドとメソッドを収集(init_fieldsも含む)
|
||||
let (all_fields, all_methods) = self.resolve_inheritance(&final_box_decl)?;
|
||||
|
||||
// インスタンスを作成
|
||||
let instance = InstanceBox::new(
|
||||
actual_class_name.clone(),
|
||||
all_fields,
|
||||
all_methods
|
||||
);
|
||||
|
||||
let instance_box = Box::new(instance) as Box<dyn NyashBox>;
|
||||
|
||||
// 現在のスコープでBoxを追跡(自動解放のため)
|
||||
// 🌍 革命的実装:Environment tracking廃止
|
||||
|
||||
// コンストラクタを呼び出す
|
||||
let constructor_key = format!("{}/{}", actual_class_name, arguments.len());
|
||||
if let Some(constructor) = final_box_decl.constructors.get(&constructor_key) {
|
||||
// コンストラクタを実行
|
||||
self.execute_constructor(&instance_box, constructor, arguments, &final_box_decl)?;
|
||||
} else if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("No constructor found for {} with {} arguments", class, arguments.len()),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(instance_box)
|
||||
}
|
||||
|
||||
/// コンストラクタを実行 - Constructor execution
|
||||
pub(super) fn execute_constructor(
|
||||
&mut self,
|
||||
instance: &Box<dyn NyashBox>,
|
||||
constructor: &ASTNode,
|
||||
arguments: &[ASTNode],
|
||||
box_decl: &BoxDeclaration
|
||||
) -> Result<(), RuntimeError> {
|
||||
if let ASTNode::FunctionDeclaration { name: _, params, body, .. } = constructor {
|
||||
// 引数を評価
|
||||
let mut arg_values = Vec::new();
|
||||
for arg in arguments {
|
||||
arg_values.push(self.execute_expression(arg)?);
|
||||
}
|
||||
|
||||
// パラメータ数チェック
|
||||
if params.len() != arg_values.len() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Constructor expects {} arguments, got {}", params.len(), arg_values.len()),
|
||||
});
|
||||
}
|
||||
|
||||
// 🌍 革命的コンストラクタ実行:local変数スタックを使用
|
||||
let saved_locals = self.save_local_vars();
|
||||
self.local_vars.clear();
|
||||
|
||||
// パラメータをlocal変数として設定
|
||||
for (param, value) in params.iter().zip(arg_values.iter()) {
|
||||
self.declare_local_variable(param, value.clone_box());
|
||||
}
|
||||
|
||||
// this(me)をlocal変数として設定
|
||||
self.declare_local_variable("me", instance.clone_box());
|
||||
|
||||
// コンストラクタコンテキストを設定
|
||||
let old_context = self.current_constructor_context.clone();
|
||||
self.current_constructor_context = Some(ConstructorContext {
|
||||
class_name: box_decl.name.clone(),
|
||||
parent_class: box_decl.extends.clone(),
|
||||
});
|
||||
|
||||
// コンストラクタを実行
|
||||
let mut result = Ok(());
|
||||
for statement in body.iter() {
|
||||
if let Err(e) = self.execute_statement(statement) {
|
||||
result = Err(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// local変数スタックとコンテキストを復元
|
||||
self.restore_local_vars(saved_locals);
|
||||
self.current_constructor_context = old_context;
|
||||
|
||||
result
|
||||
} else {
|
||||
Err(RuntimeError::InvalidOperation {
|
||||
message: "Invalid constructor node".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Box宣言を登録 - Box declaration registration
|
||||
pub(super) fn register_box_declaration(
|
||||
&mut self,
|
||||
name: String,
|
||||
fields: Vec<String>,
|
||||
methods: HashMap<String, ASTNode>,
|
||||
constructors: HashMap<String, ASTNode>,
|
||||
init_fields: Vec<String>,
|
||||
is_interface: bool,
|
||||
extends: Option<String>,
|
||||
implements: Vec<String>,
|
||||
type_parameters: Vec<String> // 🔥 ジェネリクス型パラメータ追加
|
||||
) {
|
||||
let box_decl = super::BoxDeclaration {
|
||||
name: name.clone(),
|
||||
fields,
|
||||
methods,
|
||||
constructors,
|
||||
init_fields,
|
||||
is_interface,
|
||||
extends,
|
||||
implements,
|
||||
type_parameters, // 🔥 ジェネリクス型パラメータを正しく使用
|
||||
};
|
||||
|
||||
{
|
||||
let mut box_decls = self.shared.box_declarations.write().unwrap();
|
||||
box_decls.insert(name, box_decl);
|
||||
}
|
||||
}
|
||||
|
||||
/// 🔥 ジェネリクス型引数の検証
|
||||
fn validate_generic_arguments(&self, box_decl: &BoxDeclaration, type_arguments: &[String])
|
||||
-> Result<(), RuntimeError> {
|
||||
// 型パラメータと型引数の数が一致するかチェック
|
||||
if box_decl.type_parameters.len() != type_arguments.len() {
|
||||
return Err(RuntimeError::TypeError {
|
||||
message: format!(
|
||||
"Generic class '{}' expects {} type parameters, got {}. Expected: <{}>, Got: <{}>",
|
||||
box_decl.name,
|
||||
box_decl.type_parameters.len(),
|
||||
type_arguments.len(),
|
||||
box_decl.type_parameters.join(", "),
|
||||
type_arguments.join(", ")
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
// 型引数がジェネリクスでない場合、型パラメータがあってはならない
|
||||
if box_decl.type_parameters.is_empty() && !type_arguments.is_empty() {
|
||||
return Err(RuntimeError::TypeError {
|
||||
message: format!(
|
||||
"Class '{}' is not generic, but got type arguments <{}>",
|
||||
box_decl.name,
|
||||
type_arguments.join(", ")
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
// 各型引数が有効なBox型かチェック(基本型のみチェック)
|
||||
for type_arg in type_arguments {
|
||||
if !self.is_valid_type(type_arg) {
|
||||
return Err(RuntimeError::TypeError {
|
||||
message: format!("Unknown type '{}'", type_arg),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 型が有効かどうかをチェック
|
||||
fn is_valid_type(&self, type_name: &str) -> bool {
|
||||
// 基本的なビルトイン型
|
||||
let is_builtin = matches!(type_name,
|
||||
"IntegerBox" | "StringBox" | "BoolBox" | "ArrayBox" | "MapBox" |
|
||||
"FileBox" | "ResultBox" | "FutureBox" | "ChannelBox" | "MathBox" |
|
||||
"TimeBox" | "DateTimeBox" | "TimerBox" | "RandomBox" | "SoundBox" |
|
||||
"DebugBox" | "MethodBox" | "NullBox" | "ConsoleBox" | "FloatBox"
|
||||
);
|
||||
|
||||
// Web専用Box(WASM環境のみ)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let is_web_box = matches!(type_name, "WebDisplayBox" | "WebConsoleBox" | "WebCanvasBox");
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let is_web_box = false;
|
||||
|
||||
is_builtin || is_web_box ||
|
||||
// または登録済みのユーザー定義Box
|
||||
self.shared.box_declarations.read().unwrap().contains_key(type_name)
|
||||
}
|
||||
|
||||
/// 親コンストラクタを実行 - Parent constructor execution
|
||||
pub(super) fn execute_parent_constructor(&mut self, parent_class: &str, arguments: &[ASTNode])
|
||||
-> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
// 親クラスの宣言を取得
|
||||
let parent_decl = {
|
||||
let box_decls = self.shared.box_declarations.read().unwrap();
|
||||
box_decls.get(parent_class)
|
||||
.ok_or(RuntimeError::UndefinedClass { name: parent_class.to_string() })?
|
||||
.clone()
|
||||
};
|
||||
|
||||
// 親コンストラクタを探す
|
||||
let constructor_key = format!("{}/{}", parent_class, arguments.len());
|
||||
if let Some(parent_constructor) = parent_decl.constructors.get(&constructor_key) {
|
||||
// 現在のthis参照を取得
|
||||
// 🌍 革命的this取得:local変数から
|
||||
let this_instance = self.resolve_variable("me")
|
||||
.map_err(|_| RuntimeError::InvalidOperation {
|
||||
message: "'this' not available in parent constructor call".to_string(),
|
||||
})?;
|
||||
|
||||
// 親コンストラクタを実行
|
||||
self.execute_constructor(&this_instance, parent_constructor, arguments, &parent_decl)?;
|
||||
|
||||
// VoidBoxを返す(コンストラクタ呼び出しは値を返さない)
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
} else {
|
||||
Err(RuntimeError::InvalidOperation {
|
||||
message: format!("No constructor found for parent class {} with {} arguments", parent_class, arguments.len()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 継承チェーンを解決してフィールドとメソッドを収集 - Inheritance resolution
|
||||
pub(super) fn resolve_inheritance(&self, box_decl: &BoxDeclaration)
|
||||
-> Result<(Vec<String>, HashMap<String, ASTNode>), RuntimeError> {
|
||||
let mut all_fields = Vec::new();
|
||||
let mut all_methods = HashMap::new();
|
||||
|
||||
// 親クラスの継承チェーンを再帰的に解決
|
||||
if let Some(parent_name) = &box_decl.extends {
|
||||
let parent_decl = {
|
||||
let box_decls = self.shared.box_declarations.read().unwrap();
|
||||
box_decls.get(parent_name)
|
||||
.ok_or(RuntimeError::UndefinedClass { name: parent_name.clone() })?
|
||||
.clone()
|
||||
};
|
||||
|
||||
// インターフェースは継承できない
|
||||
if parent_decl.is_interface {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Cannot extend interface '{}'. Use 'implements' instead.", parent_name),
|
||||
});
|
||||
}
|
||||
|
||||
// 親クラスの継承チェーンを再帰的に解決
|
||||
let (parent_fields, parent_methods) = self.resolve_inheritance(&parent_decl)?;
|
||||
|
||||
// 親のフィールドとメソッドを追加
|
||||
all_fields.extend(parent_fields);
|
||||
all_methods.extend(parent_methods);
|
||||
}
|
||||
|
||||
// 現在のクラスのフィールドとメソッドを追加(オーバーライド可能)
|
||||
all_fields.extend(box_decl.fields.clone());
|
||||
|
||||
// init_fieldsも追加(重複チェック)
|
||||
for init_field in &box_decl.init_fields {
|
||||
if !all_fields.contains(init_field) {
|
||||
all_fields.push(init_field.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for (method_name, method_ast) in &box_decl.methods {
|
||||
all_methods.insert(method_name.clone(), method_ast.clone()); // オーバーライド
|
||||
}
|
||||
|
||||
// インターフェース実装の検証
|
||||
for interface_name in &box_decl.implements {
|
||||
let interface_decl = {
|
||||
let box_decls = self.shared.box_declarations.read().unwrap();
|
||||
box_decls.get(interface_name)
|
||||
.ok_or(RuntimeError::UndefinedClass { name: interface_name.clone() })?
|
||||
.clone()
|
||||
};
|
||||
|
||||
if !interface_decl.is_interface {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("'{}' is not an interface", interface_name),
|
||||
});
|
||||
}
|
||||
|
||||
// インターフェースの全メソッドが実装されているかチェック
|
||||
for (required_method, _) in &interface_decl.methods {
|
||||
if !all_methods.contains_key(required_method) {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Class '{}' must implement method '{}' from interface '{}'",
|
||||
box_decl.name, required_method, interface_name),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((all_fields, all_methods))
|
||||
}
|
||||
|
||||
/// 🚀 ジェネリクス型を特殊化してBoxDeclarationを生成
|
||||
fn specialize_generic_class(
|
||||
&self,
|
||||
generic_decl: &BoxDeclaration,
|
||||
type_arguments: &[String]
|
||||
) -> Result<BoxDeclaration, RuntimeError> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
// 特殊化されたクラス名を生成
|
||||
let specialized_name = format!(
|
||||
"{}_{}",
|
||||
generic_decl.name,
|
||||
type_arguments.join("_")
|
||||
);
|
||||
|
||||
// 型パラメータ → 具体型のマッピングを作成
|
||||
let mut type_mapping = HashMap::new();
|
||||
for (i, param) in generic_decl.type_parameters.iter().enumerate() {
|
||||
type_mapping.insert(param.clone(), type_arguments[i].clone());
|
||||
}
|
||||
|
||||
// 特殊化されたBoxDeclarationを作成
|
||||
let mut specialized = generic_decl.clone();
|
||||
specialized.name = specialized_name.clone();
|
||||
specialized.type_parameters.clear(); // 特殊化後は型パラメータなし
|
||||
|
||||
// 🔄 フィールドの型を置換
|
||||
specialized.init_fields = self.substitute_types_in_fields(
|
||||
&specialized.init_fields,
|
||||
&type_mapping
|
||||
);
|
||||
|
||||
// 🔧 コンストラクタキーを新しいクラス名で更新
|
||||
let mut updated_constructors = HashMap::new();
|
||||
for (old_key, constructor_node) in &generic_decl.constructors {
|
||||
// "Container/1" -> "Container_IntegerBox/1" に変更
|
||||
if let Some(args_count) = old_key.split('/').nth(1) {
|
||||
let new_key = format!("{}/{}", specialized_name, args_count);
|
||||
updated_constructors.insert(new_key, constructor_node.clone());
|
||||
}
|
||||
}
|
||||
specialized.constructors = updated_constructors;
|
||||
|
||||
// 🔄 メソッドの型を置換(現在はプレースホルダー実装)
|
||||
// TODO: メソッド内部のコードも置換が必要
|
||||
|
||||
Ok(specialized)
|
||||
}
|
||||
|
||||
/// フィールドの型置換
|
||||
fn substitute_types_in_fields(
|
||||
&self,
|
||||
fields: &[String],
|
||||
_type_mapping: &HashMap<String, String>
|
||||
) -> Vec<String> {
|
||||
// TODO: フィールド型の置換実装
|
||||
// 現在はシンプルにコピー
|
||||
fields.to_vec()
|
||||
}
|
||||
}
|
||||
427
src/interpreter/statements.rs
Normal file
427
src/interpreter/statements.rs
Normal file
@ -0,0 +1,427 @@
|
||||
/*!
|
||||
* Statement Processing Module
|
||||
*
|
||||
* Extracted from core.rs - statement execution engine
|
||||
* Handles all statement types: assignments, if/else, loops, control flow
|
||||
* Core philosophy: "Everything is Box" with structured statement processing
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
|
||||
impl NyashInterpreter {
|
||||
/// 文を実行 - Core statement execution engine
|
||||
pub(super) fn execute_statement(&mut self, statement: &ASTNode) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
match statement {
|
||||
ASTNode::Assignment { target, value, .. } => {
|
||||
self.execute_assignment(target, value)
|
||||
}
|
||||
|
||||
ASTNode::Print { expression, .. } => {
|
||||
let value = self.execute_expression(expression)?;
|
||||
println!("{}", value.to_string_box());
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
ASTNode::If { condition, then_body, else_body, .. } => {
|
||||
self.execute_if(condition, then_body, else_body)
|
||||
}
|
||||
|
||||
ASTNode::Loop { condition, body, .. } => {
|
||||
self.execute_loop(condition, body)
|
||||
}
|
||||
|
||||
ASTNode::Return { value, .. } => {
|
||||
let return_value = if let Some(val) = value {
|
||||
self.execute_expression(val)?
|
||||
} else {
|
||||
Box::new(VoidBox::new())
|
||||
};
|
||||
self.control_flow = super::ControlFlow::Return(return_value);
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
ASTNode::Break { .. } => {
|
||||
self.control_flow = super::ControlFlow::Break;
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
ASTNode::Nowait { variable, expression, .. } => {
|
||||
self.execute_nowait(variable, expression)
|
||||
}
|
||||
|
||||
ASTNode::BoxDeclaration { name, fields, methods, constructors, init_fields, is_interface, extends, implements, type_parameters, is_static, static_init, .. } => {
|
||||
if *is_static {
|
||||
// 🔥 Static Box宣言の処理
|
||||
self.register_static_box_declaration(
|
||||
name.clone(),
|
||||
fields.clone(),
|
||||
methods.clone(),
|
||||
init_fields.clone(),
|
||||
static_init.clone(),
|
||||
extends.clone(),
|
||||
implements.clone(),
|
||||
type_parameters.clone()
|
||||
)?;
|
||||
} else {
|
||||
// 通常のBox宣言の処理
|
||||
self.register_box_declaration(
|
||||
name.clone(),
|
||||
fields.clone(),
|
||||
methods.clone(),
|
||||
constructors.clone(),
|
||||
init_fields.clone(),
|
||||
*is_interface,
|
||||
extends.clone(),
|
||||
implements.clone(),
|
||||
type_parameters.clone() // 🔥 ジェネリクス型パラメータ追加
|
||||
);
|
||||
}
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
ASTNode::FunctionDeclaration { name, params, body, is_static, .. } => {
|
||||
if *is_static {
|
||||
// 🔥 静的関数:box名.関数名の形式で解析
|
||||
if let Some(dot_pos) = name.find('.') {
|
||||
let box_name = name[..dot_pos].to_string();
|
||||
let func_name = name[dot_pos + 1..].to_string();
|
||||
|
||||
// boxのstaticメソッドとして登録
|
||||
let func_ast = ASTNode::FunctionDeclaration {
|
||||
name: func_name.clone(),
|
||||
params: params.clone(),
|
||||
body: body.clone(),
|
||||
is_static: true,
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
|
||||
{
|
||||
let mut static_funcs = self.shared.static_functions.write().unwrap();
|
||||
static_funcs
|
||||
.entry(box_name.clone())
|
||||
.or_insert_with(HashMap::new)
|
||||
.insert(func_name.clone(), func_ast);
|
||||
}
|
||||
|
||||
eprintln!("🔥 Static function '{}.{}' registered", box_name, func_name);
|
||||
} else {
|
||||
// box名なしのstatic関数(将来的にはエラーにする)
|
||||
eprintln!("⚠️ Static function '{}' needs box prefix (e.g., Math.min)", name);
|
||||
}
|
||||
} else {
|
||||
// 通常の関数:従来通りGlobalBoxメソッドとして登録
|
||||
self.register_function_declaration(name.clone(), params.clone(), body.clone());
|
||||
}
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
ASTNode::GlobalVar { name, value, .. } => {
|
||||
let val = self.execute_expression(value)?;
|
||||
// 🌍 革命的グローバル変数:GlobalBoxのフィールドとして設定
|
||||
self.set_variable(name, val.clone_box())?;
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
|
||||
self.execute_try_catch(try_body, catch_clauses, finally_body)
|
||||
}
|
||||
|
||||
ASTNode::Throw { expression, .. } => {
|
||||
self.execute_throw(expression)
|
||||
}
|
||||
|
||||
ASTNode::Local { variables, initial_values, .. } => {
|
||||
// 🌍 革命的local変数宣言:local変数スタックに追加(初期化対応)
|
||||
for (i, var_name) in variables.iter().enumerate() {
|
||||
if let Some(Some(init_expr)) = initial_values.get(i) {
|
||||
// 🚀 初期化付きlocal宣言: local x = value
|
||||
let init_value = self.execute_expression(init_expr)?;
|
||||
self.declare_local_variable(var_name, init_value);
|
||||
} else {
|
||||
// 従来のlocal宣言: local x
|
||||
self.declare_local_variable(var_name, Box::new(VoidBox::new()));
|
||||
}
|
||||
}
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
ASTNode::Outbox { variables, initial_values, .. } => {
|
||||
// 📤 革命的outbox変数宣言:static関数内で所有権移転(初期化対応)
|
||||
for (i, var_name) in variables.iter().enumerate() {
|
||||
if let Some(Some(init_expr)) = initial_values.get(i) {
|
||||
// 🚀 初期化付きoutbox宣言: outbox x = value
|
||||
let init_value = self.execute_expression(init_expr)?;
|
||||
self.declare_outbox_variable(var_name, init_value);
|
||||
} else {
|
||||
// 従来のoutbox宣言: outbox x
|
||||
self.declare_outbox_variable(var_name, Box::new(VoidBox::new()));
|
||||
}
|
||||
}
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
// 式文
|
||||
_ => self.execute_expression(statement),
|
||||
}
|
||||
}
|
||||
|
||||
/// 条件分岐を実行 - If/else statement processing
|
||||
pub(super) fn execute_if(&mut self, condition: &ASTNode, then_body: &[ASTNode], else_body: &Option<Vec<ASTNode>>)
|
||||
-> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let condition_value = self.execute_expression(condition)?;
|
||||
|
||||
// 条件を真偉値として評価
|
||||
let is_true = self.is_truthy(&condition_value);
|
||||
|
||||
if is_true {
|
||||
for statement in then_body {
|
||||
self.execute_statement(statement)?;
|
||||
if !matches!(self.control_flow, super::ControlFlow::None) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if let Some(else_statements) = else_body {
|
||||
for statement in else_statements {
|
||||
self.execute_statement(statement)?;
|
||||
if !matches!(self.control_flow, super::ControlFlow::None) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
/// ループを実行 - Loop processing: loop(condition) { body } のみ
|
||||
pub(super) fn execute_loop(&mut self, condition: &Box<ASTNode>, body: &[ASTNode]) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
loop {
|
||||
// 常に条件をチェック
|
||||
let condition_result = self.execute_expression(condition)?;
|
||||
if let Some(bool_box) = condition_result.as_any().downcast_ref::<BoolBox>() {
|
||||
if !bool_box.value {
|
||||
break; // 条件がfalseの場合はループ終了
|
||||
}
|
||||
} else {
|
||||
// 条件が真偉値でない場合は、Interpreter::is_truthy()を使用
|
||||
if !self.is_truthy(&condition_result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ループ本体を実行
|
||||
for statement in body {
|
||||
self.execute_statement(statement)?;
|
||||
|
||||
match &self.control_flow {
|
||||
super::ControlFlow::Break => {
|
||||
self.control_flow = super::ControlFlow::None;
|
||||
return Ok(Box::new(VoidBox::new()));
|
||||
}
|
||||
super::ControlFlow::Return(_) => {
|
||||
// returnはループを抜けるが、上位に伝播
|
||||
return Ok(Box::new(VoidBox::new()));
|
||||
}
|
||||
super::ControlFlow::Throw(_) => {
|
||||
// 例外はループを抜けて上位に伝播
|
||||
return Ok(Box::new(VoidBox::new()));
|
||||
}
|
||||
super::ControlFlow::None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
|
||||
/// 代入処理を実行 - Assignment processing
|
||||
pub(super) fn execute_assignment(&mut self, target: &ASTNode, value: &ASTNode) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let val = self.execute_expression(value)?;
|
||||
|
||||
match target {
|
||||
ASTNode::Variable { name, .. } => {
|
||||
// 🌍 革命的代入:local変数 → GlobalBoxフィールド
|
||||
self.set_variable(name, val.clone_box())?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
ASTNode::FieldAccess { object, field, .. } => {
|
||||
// フィールドへの代入
|
||||
let obj_value = self.execute_expression(object)?;
|
||||
|
||||
if let Some(instance) = obj_value.as_any().downcast_ref::<InstanceBox>() {
|
||||
// 既存のフィールド値があればfini()を呼ぶ
|
||||
if let Some(old_field_value) = instance.get_field(field) {
|
||||
if let Some(old_instance) = old_field_value.as_any().downcast_ref::<InstanceBox>() {
|
||||
let _ = old_instance.fini();
|
||||
finalization::mark_as_finalized(old_instance.box_id());
|
||||
}
|
||||
}
|
||||
|
||||
instance.set_field(field, val.clone_box())
|
||||
.map_err(|e| RuntimeError::InvalidOperation { message: e })?;
|
||||
Ok(val)
|
||||
} else {
|
||||
Err(RuntimeError::TypeError {
|
||||
message: format!("Cannot set field '{}' on non-instance type", field),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ASTNode::ThisField { field, .. } => {
|
||||
// 🌍 革命的this.field代入:local変数から取得
|
||||
let this_value = self.resolve_variable("me")
|
||||
.map_err(|_| RuntimeError::InvalidOperation {
|
||||
message: "'this' is not bound in the current context".to_string(),
|
||||
})?;
|
||||
|
||||
if let Some(instance) = this_value.as_any().downcast_ref::<InstanceBox>() {
|
||||
// 既存のthis.field値があればfini()を呼ぶ
|
||||
if let Some(old_field_value) = instance.get_field(field) {
|
||||
if let Some(old_instance) = old_field_value.as_any().downcast_ref::<InstanceBox>() {
|
||||
let _ = old_instance.fini();
|
||||
finalization::mark_as_finalized(old_instance.box_id());
|
||||
}
|
||||
}
|
||||
|
||||
instance.set_field(field, val.clone_box())
|
||||
.map_err(|e| RuntimeError::InvalidOperation { message: e })?;
|
||||
Ok(val)
|
||||
} else {
|
||||
Err(RuntimeError::TypeError {
|
||||
message: "'this' is not an instance".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ASTNode::MeField { field, .. } => {
|
||||
// 🌍 革命的me.field代入:local変数から取得
|
||||
let me_value = self.resolve_variable("me")
|
||||
.map_err(|_| RuntimeError::InvalidOperation {
|
||||
message: "'this' is not bound in the current context".to_string(),
|
||||
})?;
|
||||
|
||||
if let Some(instance) = me_value.as_any().downcast_ref::<InstanceBox>() {
|
||||
// 既存のme.field値があればfini()を呼ぶ
|
||||
if let Some(old_field_value) = instance.get_field(field) {
|
||||
if let Some(old_instance) = old_field_value.as_any().downcast_ref::<InstanceBox>() {
|
||||
let _ = old_instance.fini();
|
||||
finalization::mark_as_finalized(old_instance.box_id());
|
||||
}
|
||||
}
|
||||
|
||||
instance.set_field(field, val.clone_box())
|
||||
.map_err(|e| RuntimeError::InvalidOperation { message: e })?;
|
||||
Ok(val)
|
||||
} else {
|
||||
Err(RuntimeError::TypeError {
|
||||
message: "'this' is not an instance".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_ => Err(RuntimeError::InvalidOperation {
|
||||
message: "Invalid assignment target".to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// try/catch/finally文を実行 - Exception handling
|
||||
pub(super) fn execute_try_catch(&mut self, try_body: &[ASTNode], catch_clauses: &[super::CatchClause], finally_body: &Option<Vec<ASTNode>>)
|
||||
-> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let mut thrown_exception: Option<Box<dyn NyashBox>> = None;
|
||||
|
||||
// Try block execution
|
||||
let mut try_result = Ok(Box::new(VoidBox::new()));
|
||||
for statement in try_body {
|
||||
match self.execute_statement(statement) {
|
||||
Ok(_) => {
|
||||
// 制御フローをチェック
|
||||
if !matches!(self.control_flow, super::ControlFlow::None) {
|
||||
if let super::ControlFlow::Throw(exception) = &self.control_flow {
|
||||
thrown_exception = Some(exception.clone_box());
|
||||
self.control_flow = super::ControlFlow::None;
|
||||
break;
|
||||
} else {
|
||||
break; // Return/Break等は上位に伝播
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// RuntimeErrorを例外として扱う
|
||||
thrown_exception = Some(Box::new(exception_box::ErrorBox::new(&format!("{:?}", e))));
|
||||
try_result = Err(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Catch clause processing
|
||||
if let Some(exception) = &thrown_exception {
|
||||
for catch_clause in catch_clauses {
|
||||
// 型チェック
|
||||
if let Some(exception_type) = &catch_clause.exception_type {
|
||||
if !exception_box::is_exception_type(exception.as_ref(), exception_type) {
|
||||
continue; // 型が合わない場合は次のcatch句へ
|
||||
}
|
||||
}
|
||||
|
||||
// 🌍 革命的例外変数束縛:local変数として設定
|
||||
if let Some(var_name) = &catch_clause.variable_name {
|
||||
self.declare_local_variable(var_name, exception.clone_box());
|
||||
}
|
||||
|
||||
// Catch body execution
|
||||
for statement in &catch_clause.body {
|
||||
self.execute_statement(statement)?;
|
||||
if !matches!(self.control_flow, super::ControlFlow::None) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 🌍 革命的例外変数クリーンアップ:local変数から削除
|
||||
if let Some(var_name) = &catch_clause.variable_name {
|
||||
self.local_vars.remove(var_name);
|
||||
}
|
||||
|
||||
thrown_exception = None; // 例外が処理された
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally block execution (always executed)
|
||||
if let Some(ref finally_statements) = finally_body {
|
||||
for statement in finally_statements {
|
||||
self.execute_statement(statement)?;
|
||||
if !matches!(self.control_flow, super::ControlFlow::None) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 未処理の例外があれば再スロー
|
||||
if let Some(exception) = thrown_exception {
|
||||
self.control_flow = super::ControlFlow::Throw(exception);
|
||||
}
|
||||
|
||||
match try_result {
|
||||
Ok(result) => Ok(result),
|
||||
Err(_) => Ok(Box::new(VoidBox::new()) as Box<dyn NyashBox>),
|
||||
}
|
||||
}
|
||||
|
||||
/// throw文を実行 - Throw exception
|
||||
pub(super) fn execute_throw(&mut self, expression: &ASTNode) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
let value = self.execute_expression(expression)?;
|
||||
|
||||
// 値を例外として扱う
|
||||
let exception = if let Some(error_box) = value.as_any().downcast_ref::<exception_box::ErrorBox>() {
|
||||
Box::new(error_box.clone()) as Box<dyn NyashBox>
|
||||
} else {
|
||||
// 文字列や他の値はErrorBoxに変換
|
||||
Box::new(exception_box::ErrorBox::new(&value.to_string_box().value))
|
||||
};
|
||||
|
||||
self.control_flow = super::ControlFlow::Throw(exception);
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
}
|
||||
}
|
||||
181
src/lib.rs
Normal file
181
src/lib.rs
Normal file
@ -0,0 +1,181 @@
|
||||
/*!
|
||||
* Nyash Programming Language - Rust Implementation Library
|
||||
*
|
||||
* Everything is Box philosophy implemented in memory-safe Rust
|
||||
*/
|
||||
|
||||
// 🌐 WebAssembly support
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub mod box_trait;
|
||||
pub mod boxes;
|
||||
// pub mod stdlib;
|
||||
pub mod environment;
|
||||
pub mod tokenizer;
|
||||
pub mod ast; // Using old ast.rs for now
|
||||
pub mod parser; // Using old parser.rs for now
|
||||
pub mod interpreter;
|
||||
pub mod instance;
|
||||
pub mod channel_box;
|
||||
pub mod finalization;
|
||||
pub mod exception_box;
|
||||
pub mod method_box;
|
||||
pub mod type_box; // 🌟 TypeBox revolutionary system
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod wasm_test;
|
||||
|
||||
// Re-export main types for easy access
|
||||
pub use box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, VoidBox, AddBox};
|
||||
pub use environment::{Environment, PythonCompatEnvironment};
|
||||
pub use tokenizer::{NyashTokenizer, TokenType, Token};
|
||||
pub use type_box::{TypeBox, TypeRegistry, MethodSignature}; // 🌟 TypeBox exports
|
||||
pub use ast::{ASTNode, BinaryOperator, LiteralValue};
|
||||
pub use parser::{NyashParser, ParseError};
|
||||
pub use interpreter::{NyashInterpreter, RuntimeError};
|
||||
pub use instance::InstanceBox;
|
||||
pub use channel_box::{ChannelBox, MessageBox};
|
||||
pub use boxes::math_box::{MathBox, FloatBox, RangeBox};
|
||||
pub use boxes::time_box::{TimeBox, DateTimeBox, TimerBox};
|
||||
pub use boxes::map_box::MapBox;
|
||||
pub use boxes::random_box::RandomBox;
|
||||
pub use boxes::sound_box::SoundBox;
|
||||
pub use boxes::debug_box::DebugBox;
|
||||
pub use boxes::console_box::ConsoleBox;
|
||||
pub use method_box::{MethodBox, BoxType, FunctionDefinition, EphemeralInstance};
|
||||
pub use boxes::null_box::{NullBox, null};
|
||||
|
||||
// Direct canvas test export
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm_test::wasm_test::test_direct_canvas_draw;
|
||||
|
||||
// 🌐 WebAssembly exports for browser usage
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub struct NyashWasm {
|
||||
interpreter: NyashInterpreter,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
impl NyashWasm {
|
||||
/// Create a new Nyash interpreter instance for browser use
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
// Setup panic handling for better browser debugging
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
// Create interpreter with browser-specific setup
|
||||
let interpreter = NyashInterpreter::new();
|
||||
|
||||
// Register browser-specific boxes
|
||||
// ConsoleBox is available as a constructor: console = new ConsoleBox()
|
||||
// TODO: Also register DOMBox, CanvasBox etc.
|
||||
|
||||
Self { interpreter }
|
||||
}
|
||||
|
||||
/// Evaluate Nyash code and return result as string
|
||||
#[wasm_bindgen]
|
||||
pub fn eval(&mut self, code: &str) -> String {
|
||||
// Handle empty or whitespace-only input
|
||||
let trimmed_code = code.trim();
|
||||
if trimmed_code.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
// Split multiline code into logical statements for better WASM handling
|
||||
let lines: Vec<&str> = trimmed_code.lines()
|
||||
.map(|line| line.trim())
|
||||
.filter(|line| !line.is_empty() && !line.starts_with("//"))
|
||||
.collect();
|
||||
|
||||
// If single line or looks like a complete static box/box definition, parse as-is
|
||||
if lines.len() == 1 || trimmed_code.contains("static box") || trimmed_code.contains("box ") {
|
||||
return self.eval_single_block(trimmed_code);
|
||||
}
|
||||
|
||||
// For multiple lines, try to execute line by line
|
||||
let mut results = Vec::new();
|
||||
let mut accumulated_code = String::new();
|
||||
|
||||
for line in lines {
|
||||
// Accumulate lines for block structures
|
||||
accumulated_code.push_str(line);
|
||||
accumulated_code.push('\n');
|
||||
|
||||
// Check if we have a complete statement
|
||||
if self.is_complete_statement(&accumulated_code) {
|
||||
let result = self.eval_single_block(accumulated_code.trim());
|
||||
if result.starts_with("Parse Error:") {
|
||||
return result; // Stop on parse error
|
||||
}
|
||||
if !result.is_empty() && result != "void" {
|
||||
results.push(result);
|
||||
}
|
||||
accumulated_code.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Execute any remaining accumulated code
|
||||
if !accumulated_code.trim().is_empty() {
|
||||
let result = self.eval_single_block(accumulated_code.trim());
|
||||
if !result.is_empty() && result != "void" {
|
||||
results.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the most relevant result
|
||||
results.into_iter()
|
||||
.filter(|r| !r.starts_with("Parse Error:") && !r.starts_with("Runtime Error:"))
|
||||
.last()
|
||||
.unwrap_or_else(|| "void".to_string())
|
||||
}
|
||||
|
||||
/// Evaluate a single block of code
|
||||
fn eval_single_block(&mut self, code: &str) -> String {
|
||||
// First parse the code into an AST
|
||||
let ast = match NyashParser::parse_from_string(code) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => return format!("Parse Error: {}", e),
|
||||
};
|
||||
|
||||
// Then execute the AST
|
||||
match self.interpreter.execute(ast) {
|
||||
Ok(result_box) => {
|
||||
// Format the result for browser display
|
||||
let result_str = result_box.to_string_box().value;
|
||||
if result_str == "void" || result_str.is_empty() {
|
||||
"void".to_string()
|
||||
} else {
|
||||
result_str
|
||||
}
|
||||
}
|
||||
Err(e) => format!("Runtime Error: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if code represents a complete statement (heuristic)
|
||||
fn is_complete_statement(&self, code: &str) -> bool {
|
||||
let trimmed = code.trim();
|
||||
|
||||
// Always complete: assignments, function calls, simple expressions
|
||||
if trimmed.contains('=') && !trimmed.ends_with('=') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Block structures need closing braces
|
||||
let open_braces = trimmed.chars().filter(|&c| c == '{').count();
|
||||
let close_braces = trimmed.chars().filter(|&c| c == '}').count();
|
||||
|
||||
// Complete if braces are balanced or no braces at all
|
||||
open_braces == 0 || open_braces == close_braces
|
||||
}
|
||||
|
||||
/// Get the current version info
|
||||
#[wasm_bindgen]
|
||||
pub fn version() -> String {
|
||||
String::from("Nyash WASM v0.1.0 - Everything is Box in Browser!")
|
||||
}
|
||||
}
|
||||
1006
src/main.rs
Normal file
1006
src/main.rs
Normal file
File diff suppressed because it is too large
Load Diff
207
src/method_box.rs
Normal file
207
src/method_box.rs
Normal file
@ -0,0 +1,207 @@
|
||||
/*!
|
||||
* MethodBox - Function Pointer Implementation for Nyash
|
||||
*
|
||||
* イベントハンドラーやコールバックを実現するためのMethodBox実装
|
||||
* ChatGPT先生のアドバイスを全面採用
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox};
|
||||
use crate::ast::ASTNode;
|
||||
use crate::instance::InstanceBox;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// BoxType enum - ChatGPT先生の提案に従い、Box型を分類
|
||||
#[derive(Debug)]
|
||||
pub enum BoxType {
|
||||
/// 通常のインスタンス
|
||||
Instance(Box<dyn NyashBox>),
|
||||
|
||||
/// 関数定義(インスタンスなし)
|
||||
Function(FunctionDefinition),
|
||||
|
||||
/// メソッド参照(インスタンス+メソッド)
|
||||
Method(MethodBox),
|
||||
}
|
||||
|
||||
/// 関数定義情報
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FunctionDefinition {
|
||||
pub name: String,
|
||||
pub params: Vec<String>,
|
||||
pub body: Vec<ASTNode>,
|
||||
pub is_static: bool,
|
||||
}
|
||||
|
||||
/// MethodBox - インスタンスとメソッドの組み合わせ
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MethodBox {
|
||||
/// メソッドを持つインスタンス
|
||||
pub instance: Arc<Mutex<Box<dyn NyashBox>>>,
|
||||
|
||||
/// メソッド名
|
||||
pub method_name: String,
|
||||
|
||||
/// メソッド定義(キャッシュ用)
|
||||
pub method_def: Option<FunctionDefinition>,
|
||||
|
||||
/// ユニークID
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl MethodBox {
|
||||
/// 新しいMethodBoxを作成
|
||||
pub fn new(instance: Box<dyn NyashBox>, method_name: String) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
// メソッド定義をキャッシュ(可能であれば)
|
||||
let method_def = if let Some(inst) = instance.as_any().downcast_ref::<InstanceBox>() {
|
||||
inst.get_method(&method_name).and_then(|ast| {
|
||||
if let ASTNode::FunctionDeclaration { name, params, body, is_static, .. } = ast {
|
||||
Some(FunctionDefinition {
|
||||
name: name.clone(),
|
||||
params: params.clone(),
|
||||
body: body.clone(),
|
||||
is_static: *is_static,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Self {
|
||||
instance: Arc::new(Mutex::new(instance)),
|
||||
method_name,
|
||||
method_def,
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// メソッドを呼び出す
|
||||
pub fn invoke(&self, _args: Vec<Box<dyn NyashBox>>) -> Result<Box<dyn NyashBox>, String> {
|
||||
// TODO: インタープリタとの統合が必要
|
||||
// 現在は仮実装
|
||||
Err(format!("MethodBox.invoke not yet implemented for method '{}'", self.method_name))
|
||||
}
|
||||
|
||||
/// インスタンスを取得(内部使用)
|
||||
pub fn get_instance(&self) -> Arc<Mutex<Box<dyn NyashBox>>> {
|
||||
Arc::clone(&self.instance)
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for MethodBox {
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(format!("<MethodBox: {}>", self.method_name))
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_method) = other.as_any().downcast_ref::<MethodBox>() {
|
||||
// 同じインスタンス、同じメソッド名なら等しい
|
||||
let self_inst = self.instance.lock().unwrap();
|
||||
let other_inst = other_method.instance.lock().unwrap();
|
||||
BoolBox::new(
|
||||
self_inst.box_id() == other_inst.box_id() &&
|
||||
self.method_name == other_method.method_name
|
||||
)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"MethodBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MethodBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "<MethodBox: {}>", self.method_name)
|
||||
}
|
||||
}
|
||||
|
||||
/// EphemeralInstance - 一時的なスコープ(関数実行時)
|
||||
#[derive(Debug)]
|
||||
pub struct EphemeralInstance {
|
||||
/// ローカル変数
|
||||
pub locals: HashMap<String, Box<dyn NyashBox>>,
|
||||
|
||||
/// 親インスタンス(メソッド呼び出しの場合)
|
||||
pub parent_instance: Option<Arc<Mutex<Box<dyn NyashBox>>>>,
|
||||
}
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
impl EphemeralInstance {
|
||||
/// 新しい一時スコープを作成
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
locals: HashMap::new(),
|
||||
parent_instance: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// インスタンスから一時スコープを作成
|
||||
pub fn from_instance(instance: Arc<Mutex<Box<dyn NyashBox>>>) -> Self {
|
||||
Self {
|
||||
locals: HashMap::new(),
|
||||
parent_instance: Some(instance),
|
||||
}
|
||||
}
|
||||
|
||||
/// ローカル変数を設定
|
||||
pub fn set_local(&mut self, name: String, value: Box<dyn NyashBox>) {
|
||||
self.locals.insert(name, value);
|
||||
}
|
||||
|
||||
/// ローカル変数を取得
|
||||
pub fn get_local(&self, name: &str) -> Option<Box<dyn NyashBox>> {
|
||||
self.locals.get(name).map(|v| v.clone_box())
|
||||
}
|
||||
|
||||
/// 変数を解決(local → instance fields → global)
|
||||
pub fn resolve_variable(&self, name: &str) -> Option<Box<dyn NyashBox>> {
|
||||
// 1. ローカル変数
|
||||
if let Some(value) = self.get_local(name) {
|
||||
return Some(value);
|
||||
}
|
||||
|
||||
// 2. インスタンスフィールド(parent_instanceがある場合)
|
||||
if let Some(parent) = &self.parent_instance {
|
||||
let inst = parent.lock().unwrap();
|
||||
if let Some(instance_box) = inst.as_any().downcast_ref::<InstanceBox>() {
|
||||
if let Some(field_value) = instance_box.get_field(name) {
|
||||
return Some(field_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. グローバル(TODO: インタープリタとの統合が必要)
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// MethodBox作成用のビルダー構文サポート
|
||||
pub fn create_method_box(instance: Box<dyn NyashBox>, method_name: &str) -> Box<dyn NyashBox> {
|
||||
Box::new(MethodBox::new(instance, method_name.to_string()))
|
||||
}
|
||||
2279
src/parser.rs.backup
Normal file
2279
src/parser.rs.backup
Normal file
File diff suppressed because it is too large
Load Diff
414
src/parser/expressions.rs
Normal file
414
src/parser/expressions.rs
Normal file
@ -0,0 +1,414 @@
|
||||
/*!
|
||||
* Nyash Parser - Expression Parsing Module
|
||||
*
|
||||
* 式(Expression)の解析を担当するモジュール
|
||||
* 演算子の優先順位に従った再帰下降パーサー実装
|
||||
*/
|
||||
|
||||
use crate::tokenizer::TokenType;
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, UnaryOperator, Span};
|
||||
use super::{NyashParser, ParseError};
|
||||
|
||||
impl NyashParser {
|
||||
/// 式をパース (演算子優先順位あり)
|
||||
pub(super) fn parse_expression(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.parse_or()
|
||||
}
|
||||
|
||||
/// OR演算子をパース: ||
|
||||
fn parse_or(&mut self) -> Result<ASTNode, ParseError> {
|
||||
let mut expr = self.parse_and()?;
|
||||
|
||||
while self.match_token(&TokenType::OR) {
|
||||
let operator = BinaryOperator::Or;
|
||||
self.advance();
|
||||
let right = self.parse_and()?;
|
||||
expr = ASTNode::BinaryOp {
|
||||
operator,
|
||||
left: Box::new(expr),
|
||||
right: Box::new(right),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
/// AND演算子をパース: &&
|
||||
fn parse_and(&mut self) -> Result<ASTNode, ParseError> {
|
||||
let mut expr = self.parse_equality()?;
|
||||
|
||||
while self.match_token(&TokenType::AND) {
|
||||
let operator = BinaryOperator::And;
|
||||
self.advance();
|
||||
let right = self.parse_equality()?;
|
||||
expr = ASTNode::BinaryOp {
|
||||
operator,
|
||||
left: Box::new(expr),
|
||||
right: Box::new(right),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
/// 等値演算子をパース: == !=
|
||||
fn parse_equality(&mut self) -> Result<ASTNode, ParseError> {
|
||||
let mut expr = self.parse_comparison()?;
|
||||
|
||||
while self.match_token(&TokenType::EQUALS) || self.match_token(&TokenType::NotEquals) {
|
||||
let operator = match &self.current_token().token_type {
|
||||
TokenType::EQUALS => BinaryOperator::Equal,
|
||||
TokenType::NotEquals => BinaryOperator::NotEqual,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.advance();
|
||||
let right = self.parse_comparison()?;
|
||||
expr = ASTNode::BinaryOp {
|
||||
operator,
|
||||
left: Box::new(expr),
|
||||
right: Box::new(right),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
/// 比較演算子をパース: < <= > >=
|
||||
fn parse_comparison(&mut self) -> Result<ASTNode, ParseError> {
|
||||
let mut expr = self.parse_term()?;
|
||||
|
||||
while self.match_token(&TokenType::LESS) ||
|
||||
self.match_token(&TokenType::LessEquals) ||
|
||||
self.match_token(&TokenType::GREATER) ||
|
||||
self.match_token(&TokenType::GreaterEquals) {
|
||||
let operator = match &self.current_token().token_type {
|
||||
TokenType::LESS => BinaryOperator::Less,
|
||||
TokenType::LessEquals => BinaryOperator::LessEqual,
|
||||
TokenType::GREATER => BinaryOperator::Greater,
|
||||
TokenType::GreaterEquals => BinaryOperator::GreaterEqual,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.advance();
|
||||
let right = self.parse_term()?;
|
||||
expr = ASTNode::BinaryOp {
|
||||
operator,
|
||||
left: Box::new(expr),
|
||||
right: Box::new(right),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
/// 項をパース: + - >>
|
||||
fn parse_term(&mut self) -> Result<ASTNode, ParseError> {
|
||||
let mut expr = self.parse_factor()?;
|
||||
|
||||
while self.match_token(&TokenType::PLUS) || self.match_token(&TokenType::MINUS) || self.match_token(&TokenType::ARROW) {
|
||||
if self.match_token(&TokenType::ARROW) {
|
||||
// >> Arrow演算子
|
||||
self.advance();
|
||||
let right = self.parse_factor()?;
|
||||
expr = ASTNode::Arrow {
|
||||
sender: Box::new(expr),
|
||||
receiver: Box::new(right),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
} else {
|
||||
let operator = match &self.current_token().token_type {
|
||||
TokenType::PLUS => BinaryOperator::Add,
|
||||
TokenType::MINUS => BinaryOperator::Subtract,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.advance();
|
||||
let right = self.parse_factor()?;
|
||||
expr = ASTNode::BinaryOp {
|
||||
operator,
|
||||
left: Box::new(expr),
|
||||
right: Box::new(right),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
/// 因子をパース: * /
|
||||
fn parse_factor(&mut self) -> Result<ASTNode, ParseError> {
|
||||
let mut expr = self.parse_unary()?;
|
||||
|
||||
while self.match_token(&TokenType::MULTIPLY) || self.match_token(&TokenType::DIVIDE) {
|
||||
let operator = match &self.current_token().token_type {
|
||||
TokenType::MULTIPLY => BinaryOperator::Multiply,
|
||||
TokenType::DIVIDE => BinaryOperator::Divide,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.advance();
|
||||
let right = self.parse_unary()?;
|
||||
expr = ASTNode::BinaryOp {
|
||||
operator,
|
||||
left: Box::new(expr),
|
||||
right: Box::new(right),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
/// 単項演算子をパース
|
||||
fn parse_unary(&mut self) -> Result<ASTNode, ParseError> {
|
||||
if self.match_token(&TokenType::MINUS) {
|
||||
self.advance(); // consume '-'
|
||||
let operand = self.parse_unary()?; // 再帰的に単項演算をパース
|
||||
return Ok(ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Minus,
|
||||
operand: Box::new(operand),
|
||||
span: Span::unknown(),
|
||||
});
|
||||
}
|
||||
|
||||
if self.match_token(&TokenType::NOT) {
|
||||
self.advance(); // consume 'not'
|
||||
let operand = self.parse_unary()?; // 再帰的に単項演算をパース
|
||||
return Ok(ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Not,
|
||||
operand: Box::new(operand),
|
||||
span: Span::unknown(),
|
||||
});
|
||||
}
|
||||
|
||||
if self.match_token(&TokenType::AWAIT) {
|
||||
self.advance(); // consume 'await'
|
||||
let expression = self.parse_unary()?; // 再帰的にパース
|
||||
return Ok(ASTNode::AwaitExpression {
|
||||
expression: Box::new(expression),
|
||||
span: Span::unknown(),
|
||||
});
|
||||
}
|
||||
|
||||
self.parse_call()
|
||||
}
|
||||
|
||||
/// 関数・メソッド呼び出しをパース
|
||||
fn parse_call(&mut self) -> Result<ASTNode, ParseError> {
|
||||
let mut expr = self.parse_primary()?;
|
||||
|
||||
loop {
|
||||
if self.match_token(&TokenType::DOT) {
|
||||
self.advance(); // consume '.'
|
||||
|
||||
if let TokenType::IDENTIFIER(method_name) = &self.current_token().token_type {
|
||||
let method_name = method_name.clone();
|
||||
self.advance();
|
||||
|
||||
if self.match_token(&TokenType::LPAREN) {
|
||||
// メソッド呼び出し: obj.method(args)
|
||||
self.advance(); // consume '('
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
arguments.push(self.parse_expression()?);
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
|
||||
expr = ASTNode::MethodCall {
|
||||
object: Box::new(expr),
|
||||
method: method_name,
|
||||
arguments,
|
||||
span: Span::unknown(),
|
||||
};
|
||||
} else {
|
||||
// フィールドアクセス: obj.field
|
||||
expr = ASTNode::FieldAccess {
|
||||
object: Box::new(expr),
|
||||
field: method_name,
|
||||
span: Span::unknown(),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "identifier".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
} 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() {
|
||||
arguments.push(self.parse_expression()?);
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
|
||||
expr = ASTNode::FunctionCall { name, arguments, span: Span::unknown() };
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
/// 基本式をパース: リテラル、変数、括弧、this、new
|
||||
fn parse_primary(&mut self) -> Result<ASTNode, ParseError> {
|
||||
match &self.current_token().token_type {
|
||||
TokenType::STRING(s) => {
|
||||
let value = s.clone();
|
||||
self.advance();
|
||||
Ok(ASTNode::Literal {
|
||||
value: LiteralValue::String(value),
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
TokenType::NUMBER(n) => {
|
||||
let value = *n;
|
||||
self.advance();
|
||||
Ok(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(value),
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
TokenType::FLOAT(f) => {
|
||||
let value = *f;
|
||||
self.advance();
|
||||
Ok(ASTNode::Literal {
|
||||
value: LiteralValue::Float(value),
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
TokenType::TRUE => {
|
||||
self.advance();
|
||||
Ok(ASTNode::Literal {
|
||||
value: LiteralValue::Bool(true),
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
TokenType::FALSE => {
|
||||
self.advance();
|
||||
Ok(ASTNode::Literal {
|
||||
value: LiteralValue::Bool(false),
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
TokenType::THIS => {
|
||||
self.advance();
|
||||
Ok(ASTNode::This { span: Span::unknown() })
|
||||
}
|
||||
|
||||
TokenType::ME => {
|
||||
self.advance();
|
||||
Ok(ASTNode::Me { span: Span::unknown() })
|
||||
}
|
||||
|
||||
TokenType::NEW => {
|
||||
self.advance();
|
||||
|
||||
if let TokenType::IDENTIFIER(class_name) = &self.current_token().token_type {
|
||||
let class_name = class_name.clone();
|
||||
self.advance();
|
||||
|
||||
// 🔥 ジェネリクス型引数のパース (<IntegerBox, StringBox>)
|
||||
let type_arguments = if self.match_token(&TokenType::LESS) {
|
||||
self.advance(); // consume '<'
|
||||
let mut args = Vec::new();
|
||||
|
||||
loop {
|
||||
if let TokenType::IDENTIFIER(type_name) = &self.current_token().token_type {
|
||||
args.push(type_name.clone());
|
||||
self.advance();
|
||||
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance(); // consume ','
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "type argument".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::GREATER)?; // consume '>'
|
||||
args
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
arguments.push(self.parse_expression()?);
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
|
||||
Ok(ASTNode::New {
|
||||
class: class_name,
|
||||
arguments,
|
||||
type_arguments,
|
||||
span: Span::unknown(),
|
||||
})
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "class name".to_string(),
|
||||
line,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
TokenType::IDENTIFIER(name) => {
|
||||
let name = name.clone();
|
||||
self.advance();
|
||||
Ok(ASTNode::Variable { name, span: Span::unknown() })
|
||||
}
|
||||
|
||||
TokenType::LPAREN => {
|
||||
self.advance(); // consume '('
|
||||
let expr = self.parse_expression()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
_ => {
|
||||
let line = self.current_token().line;
|
||||
Err(ParseError::InvalidExpression { line })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1250
src/parser/mod.rs
Normal file
1250
src/parser/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
409
src/parser/statements.rs
Normal file
409
src/parser/statements.rs
Normal file
@ -0,0 +1,409 @@
|
||||
/*!
|
||||
* Nyash Parser - Statement Parsing Module
|
||||
*
|
||||
* 文(Statement)の解析を担当するモジュール
|
||||
* if, loop, break, return, print等の制御構文を処理
|
||||
*/
|
||||
|
||||
use crate::tokenizer::TokenType;
|
||||
use crate::ast::{ASTNode, CatchClause, Span};
|
||||
use super::{NyashParser, ParseError};
|
||||
|
||||
impl NyashParser {
|
||||
/// 文をパース
|
||||
pub(super) fn parse_statement(&mut self) -> Result<ASTNode, ParseError> {
|
||||
match &self.current_token().token_type {
|
||||
TokenType::BOX => self.parse_box_declaration(),
|
||||
TokenType::INTERFACE => self.parse_interface_box_declaration(),
|
||||
TokenType::GLOBAL => self.parse_global_var(),
|
||||
TokenType::FUNCTION => self.parse_function_declaration(),
|
||||
TokenType::STATIC => self.parse_static_declaration(), // 🔥 静的宣言 (function/box)
|
||||
TokenType::IF => self.parse_if(),
|
||||
TokenType::LOOP => self.parse_loop(),
|
||||
TokenType::BREAK => self.parse_break(),
|
||||
TokenType::RETURN => self.parse_return(),
|
||||
TokenType::PRINT => self.parse_print(),
|
||||
TokenType::NOWAIT => self.parse_nowait(),
|
||||
TokenType::INCLUDE => self.parse_include(),
|
||||
TokenType::LOCAL => self.parse_local(),
|
||||
TokenType::OUTBOX => self.parse_outbox(),
|
||||
TokenType::TRY => self.parse_try_catch(),
|
||||
TokenType::THROW => self.parse_throw(),
|
||||
TokenType::IDENTIFIER(_) => {
|
||||
// function宣言 または 代入文 または 関数呼び出し
|
||||
self.parse_assignment_or_function_call()
|
||||
}
|
||||
TokenType::THIS | TokenType::ME => {
|
||||
// this/me で始まる文も通常の代入文または関数呼び出しとして処理
|
||||
self.parse_assignment_or_function_call()
|
||||
}
|
||||
_ => {
|
||||
let line = self.current_token().line;
|
||||
Err(ParseError::InvalidStatement { line })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// if文をパース: if (condition) { body } else if ... else { body }
|
||||
pub(super) fn parse_if(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.advance(); // consume 'if'
|
||||
|
||||
// 条件部分を取得
|
||||
let condition = Box::new(self.parse_expression()?);
|
||||
|
||||
// then部分を取得
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
let mut then_body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
then_body.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
|
||||
// else if/else部分を処理
|
||||
let else_body = if self.match_token(&TokenType::ELSE) {
|
||||
self.advance(); // consume 'else'
|
||||
|
||||
if self.match_token(&TokenType::IF) {
|
||||
// else if を ネストしたifとして処理
|
||||
let nested_if = self.parse_if()?;
|
||||
Some(vec![nested_if])
|
||||
} else {
|
||||
// plain else
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
let mut else_stmts = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
else_stmts.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
Some(else_stmts)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(ASTNode::If {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
/// loop文をパース
|
||||
pub(super) fn parse_loop(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.advance(); // consume 'loop'
|
||||
|
||||
// 条件部分を取得
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let condition = Some(Box::new(self.parse_expression()?));
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
|
||||
// body部分を取得
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
let mut body = 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::Loop {
|
||||
condition: condition.unwrap(),
|
||||
body,
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
/// break文をパース
|
||||
pub(super) fn parse_break(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.advance(); // consume 'break'
|
||||
Ok(ASTNode::Break { span: Span::unknown() })
|
||||
}
|
||||
|
||||
/// return文をパース
|
||||
pub(super) fn parse_return(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.advance(); // consume 'return'
|
||||
|
||||
// returnの後に式があるかチェック
|
||||
let value = if self.is_at_end() || self.match_token(&TokenType::NEWLINE) {
|
||||
// return単体の場合はvoidを返す
|
||||
None
|
||||
} else {
|
||||
// 式をパースして返す
|
||||
Some(Box::new(self.parse_expression()?))
|
||||
};
|
||||
|
||||
Ok(ASTNode::Return { value, span: Span::unknown() })
|
||||
}
|
||||
|
||||
/// print文をパース
|
||||
pub(super) fn parse_print(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.advance(); // consume 'print'
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let value = Box::new(self.parse_expression()?);
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
|
||||
Ok(ASTNode::Print { expression: value, span: Span::unknown() })
|
||||
}
|
||||
|
||||
/// nowait文をパース: nowait variable = expression
|
||||
pub(super) fn parse_nowait(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.advance(); // consume 'nowait'
|
||||
|
||||
// 変数名を取得
|
||||
let variable = if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
|
||||
let name = name.clone();
|
||||
self.advance();
|
||||
name
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "variable name".to_string(),
|
||||
line,
|
||||
});
|
||||
};
|
||||
|
||||
self.consume(TokenType::ASSIGN)?;
|
||||
let expression = Box::new(self.parse_expression()?);
|
||||
|
||||
Ok(ASTNode::Nowait {
|
||||
variable,
|
||||
expression,
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
/// include文をパース
|
||||
pub(super) fn parse_include(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.advance(); // consume 'include'
|
||||
|
||||
let path = if let TokenType::STRING(path) = &self.current_token().token_type {
|
||||
let path = path.clone();
|
||||
self.advance();
|
||||
path
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "string literal".to_string(),
|
||||
line,
|
||||
});
|
||||
};
|
||||
|
||||
Ok(ASTNode::Include { filename: path, span: Span::unknown() })
|
||||
}
|
||||
|
||||
/// local変数宣言をパース: local var1, var2, var3 または local x = 10
|
||||
pub(super) fn parse_local(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.advance(); // consume 'local'
|
||||
|
||||
let mut names = Vec::new();
|
||||
let mut initial_values = Vec::new();
|
||||
|
||||
// 最初の変数名を取得
|
||||
if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
|
||||
names.push(name.clone());
|
||||
self.advance();
|
||||
|
||||
// = があれば初期値を設定
|
||||
if self.match_token(&TokenType::ASSIGN) {
|
||||
self.advance(); // consume '='
|
||||
initial_values.push(Some(Box::new(self.parse_expression()?)));
|
||||
|
||||
// 初期化付きlocalは単一変数のみ(カンマ区切り不可)
|
||||
Ok(ASTNode::Local {
|
||||
variables: names,
|
||||
initial_values,
|
||||
span: Span::unknown(),
|
||||
})
|
||||
} else {
|
||||
// 初期化なしの場合はカンマ区切りで複数変数可能
|
||||
initial_values.push(None);
|
||||
|
||||
// カンマ区切りで追加の変数名を取得
|
||||
while self.match_token(&TokenType::COMMA) {
|
||||
self.advance(); // consume ','
|
||||
|
||||
if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
|
||||
names.push(name.clone());
|
||||
initial_values.push(None);
|
||||
self.advance();
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "identifier".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ASTNode::Local {
|
||||
variables: names,
|
||||
initial_values,
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "identifier".to_string(),
|
||||
line,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// outbox変数宣言をパース: outbox var1, var2, var3
|
||||
pub(super) fn parse_outbox(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.advance(); // consume 'outbox'
|
||||
|
||||
let mut names = Vec::new();
|
||||
|
||||
// 最初の変数名を取得
|
||||
if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
|
||||
names.push(name.clone());
|
||||
self.advance();
|
||||
|
||||
// カンマ区切りで追加の変数名を取得
|
||||
while self.match_token(&TokenType::COMMA) {
|
||||
self.advance(); // consume ','
|
||||
|
||||
if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
|
||||
names.push(name.clone());
|
||||
self.advance();
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "identifier".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let num_vars = names.len();
|
||||
Ok(ASTNode::Outbox {
|
||||
variables: names,
|
||||
initial_values: vec![None; num_vars],
|
||||
span: Span::unknown(),
|
||||
})
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "identifier".to_string(),
|
||||
line,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// try-catch文をパース
|
||||
pub(super) fn parse_try_catch(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.advance(); // consume 'try'
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
|
||||
let mut try_body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
try_body.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
|
||||
let mut catch_clauses = Vec::new();
|
||||
|
||||
// catch節をパース
|
||||
while self.match_token(&TokenType::CATCH) {
|
||||
self.advance(); // consume 'catch'
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
|
||||
// 例外型 (オプション)
|
||||
let exception_type = if let TokenType::IDENTIFIER(type_name) = &self.current_token().token_type {
|
||||
let type_name = type_name.clone();
|
||||
self.advance();
|
||||
Some(type_name)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// 例外変数名
|
||||
let exception_var = if let TokenType::IDENTIFIER(var_name) = &self.current_token().token_type {
|
||||
let var_name = var_name.clone();
|
||||
self.advance();
|
||||
var_name
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "exception variable name".to_string(),
|
||||
line,
|
||||
});
|
||||
};
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
|
||||
let mut catch_body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
catch_body.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
|
||||
catch_clauses.push(CatchClause {
|
||||
exception_type,
|
||||
variable_name: Some(exception_var),
|
||||
body: catch_body,
|
||||
span: Span::unknown(),
|
||||
});
|
||||
}
|
||||
|
||||
// finally節をパース (オプション)
|
||||
let finally_body = if self.match_token(&TokenType::FINALLY) {
|
||||
self.advance(); // consume 'finally'
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
|
||||
let mut body = 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)?;
|
||||
Some(body)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(ASTNode::TryCatch {
|
||||
try_body,
|
||||
catch_clauses,
|
||||
finally_body,
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
/// throw文をパース
|
||||
pub(super) fn parse_throw(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.advance(); // consume 'throw'
|
||||
let value = Box::new(self.parse_expression()?);
|
||||
Ok(ASTNode::Throw { expression: value, span: Span::unknown() })
|
||||
}
|
||||
}
|
||||
612
src/tokenizer.rs
Normal file
612
src/tokenizer.rs
Normal file
@ -0,0 +1,612 @@
|
||||
/*!
|
||||
* Nyash Tokenizer - .nyashソースコードをトークン列に変換
|
||||
*
|
||||
* Python版nyashc_v4.pyのNyashTokenizerをRustで完全再実装
|
||||
* 正規表現ベース → 高速なcharレベル処理に最適化
|
||||
*/
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// トークンの種類を表すenum
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TokenType {
|
||||
// リテラル
|
||||
STRING(String),
|
||||
NUMBER(i64),
|
||||
FLOAT(f64), // 浮動小数点数サポート追加
|
||||
TRUE,
|
||||
FALSE,
|
||||
|
||||
// キーワード
|
||||
BOX,
|
||||
GLOBAL,
|
||||
SINGLETON,
|
||||
NEW,
|
||||
IF,
|
||||
ELSE,
|
||||
LOOP,
|
||||
BREAK,
|
||||
RETURN,
|
||||
FUNCTION,
|
||||
PRINT,
|
||||
THIS,
|
||||
ME,
|
||||
INIT, // init (初期化ブロック)
|
||||
NOWAIT, // nowait
|
||||
AWAIT, // await
|
||||
INTERFACE, // interface
|
||||
FROM, // from (継承用)
|
||||
INCLUDE, // include (ファイル読み込み)
|
||||
TRY, // try
|
||||
CATCH, // catch
|
||||
FINALLY, // finally
|
||||
THROW, // throw
|
||||
LOCAL, // local (一時変数宣言)
|
||||
STATIC, // static (静的メソッド)
|
||||
OUTBOX, // outbox (所有権移転変数)
|
||||
NOT, // not (否定演算子)
|
||||
|
||||
// 演算子 (長いものから先に定義)
|
||||
ARROW, // >>
|
||||
EQUALS, // ==
|
||||
NotEquals, // !=
|
||||
LessEquals, // <=
|
||||
GreaterEquals, // >=
|
||||
AND, // && または and
|
||||
OR, // || または or
|
||||
LESS, // <
|
||||
GREATER, // >
|
||||
ASSIGN, // =
|
||||
PLUS, // +
|
||||
MINUS, // -
|
||||
MULTIPLY, // *
|
||||
DIVIDE, // /
|
||||
|
||||
// 記号
|
||||
DOT, // .
|
||||
LPAREN, // (
|
||||
RPAREN, // )
|
||||
LBRACE, // {
|
||||
RBRACE, // }
|
||||
COMMA, // ,
|
||||
NEWLINE, // \n
|
||||
|
||||
// 識別子
|
||||
IDENTIFIER(String),
|
||||
|
||||
// 特殊
|
||||
EOF,
|
||||
}
|
||||
|
||||
/// トークンの位置情報を含む構造体
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Token {
|
||||
pub token_type: TokenType,
|
||||
pub line: usize,
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn new(token_type: TokenType, line: usize, column: usize) -> Self {
|
||||
Self { token_type, line, column }
|
||||
}
|
||||
}
|
||||
|
||||
/// トークナイズエラー
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TokenizeError {
|
||||
#[error("Unexpected character '{char}' at line {line}, column {column}")]
|
||||
UnexpectedCharacter { char: char, line: usize, column: usize },
|
||||
|
||||
#[error("Unterminated string literal at line {line}")]
|
||||
UnterminatedString { line: usize },
|
||||
|
||||
#[error("Invalid number format at line {line}")]
|
||||
InvalidNumber { line: usize },
|
||||
|
||||
#[error("Comment not closed at line {line}")]
|
||||
UnterminatedComment { line: usize },
|
||||
}
|
||||
|
||||
/// Nyashトークナイザー
|
||||
pub struct NyashTokenizer {
|
||||
input: Vec<char>,
|
||||
position: usize,
|
||||
line: usize,
|
||||
column: usize,
|
||||
}
|
||||
|
||||
impl NyashTokenizer {
|
||||
/// 新しいトークナイザーを作成
|
||||
pub fn new(input: impl Into<String>) -> Self {
|
||||
let input_string = input.into();
|
||||
Self {
|
||||
input: input_string.chars().collect(),
|
||||
position: 0,
|
||||
line: 1,
|
||||
column: 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// 完全なトークナイズを実行
|
||||
pub fn tokenize(&mut self) -> Result<Vec<Token>, TokenizeError> {
|
||||
let mut tokens = Vec::new();
|
||||
|
||||
while !self.is_at_end() {
|
||||
// 空白をスキップ
|
||||
self.skip_whitespace();
|
||||
|
||||
if self.is_at_end() {
|
||||
break;
|
||||
}
|
||||
|
||||
// 次のトークンを読み取り
|
||||
let token = self.tokenize_next()?;
|
||||
tokens.push(token);
|
||||
}
|
||||
|
||||
// EOF トークンを追加
|
||||
tokens.push(Token::new(TokenType::EOF, self.line, self.column));
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
/// 次の一つのトークンを読み取り
|
||||
fn tokenize_next(&mut self) -> Result<Token, TokenizeError> {
|
||||
let start_line = self.line;
|
||||
let start_column = self.column;
|
||||
|
||||
match self.current_char() {
|
||||
Some('"') => {
|
||||
let string_value = self.read_string()?;
|
||||
Ok(Token::new(TokenType::STRING(string_value), start_line, start_column))
|
||||
}
|
||||
Some(c) if c.is_ascii_digit() => {
|
||||
let token_type = self.read_numeric_literal()?;
|
||||
Ok(Token::new(token_type, start_line, start_column))
|
||||
}
|
||||
Some(c) if c.is_alphabetic() || c == '_' => {
|
||||
let token_type = self.read_keyword_or_identifier();
|
||||
Ok(Token::new(token_type, start_line, start_column))
|
||||
}
|
||||
Some('/') if self.peek_char() == Some('/') => {
|
||||
self.skip_line_comment();
|
||||
self.skip_whitespace(); // コメント後の空白もスキップ
|
||||
return self.tokenize_next();
|
||||
}
|
||||
Some('#') => {
|
||||
self.skip_line_comment();
|
||||
self.skip_whitespace(); // コメント後の空白もスキップ
|
||||
return self.tokenize_next();
|
||||
}
|
||||
Some('>') if self.peek_char() == Some('>') => {
|
||||
self.advance();
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::ARROW, start_line, start_column))
|
||||
}
|
||||
Some('=') if self.peek_char() == Some('=') => {
|
||||
self.advance();
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::EQUALS, start_line, start_column))
|
||||
}
|
||||
Some('!') if self.peek_char() == Some('=') => {
|
||||
self.advance();
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::NotEquals, start_line, start_column))
|
||||
}
|
||||
Some('<') if self.peek_char() == Some('=') => {
|
||||
self.advance();
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::LessEquals, start_line, start_column))
|
||||
}
|
||||
Some('>') if self.peek_char() == Some('=') => {
|
||||
self.advance();
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::GreaterEquals, start_line, start_column))
|
||||
}
|
||||
Some('&') if self.peek_char() == Some('&') => {
|
||||
self.advance();
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::AND, start_line, start_column))
|
||||
}
|
||||
Some('|') if self.peek_char() == Some('|') => {
|
||||
self.advance();
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::OR, start_line, start_column))
|
||||
}
|
||||
Some('<') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::LESS, start_line, start_column))
|
||||
}
|
||||
Some('>') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::GREATER, start_line, start_column))
|
||||
}
|
||||
Some('=') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::ASSIGN, start_line, start_column))
|
||||
}
|
||||
Some('+') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::PLUS, start_line, start_column))
|
||||
}
|
||||
Some('-') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::MINUS, start_line, start_column))
|
||||
}
|
||||
Some('*') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::MULTIPLY, start_line, start_column))
|
||||
}
|
||||
Some('/') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::DIVIDE, start_line, start_column))
|
||||
}
|
||||
Some('.') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::DOT, start_line, start_column))
|
||||
}
|
||||
Some('(') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::LPAREN, start_line, start_column))
|
||||
}
|
||||
Some(')') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::RPAREN, start_line, start_column))
|
||||
}
|
||||
Some('{') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::LBRACE, start_line, start_column))
|
||||
}
|
||||
Some('}') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::RBRACE, start_line, start_column))
|
||||
}
|
||||
Some(',') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::COMMA, start_line, start_column))
|
||||
}
|
||||
Some('\n') => {
|
||||
self.advance();
|
||||
Ok(Token::new(TokenType::NEWLINE, start_line, start_column))
|
||||
}
|
||||
Some(c) => {
|
||||
Err(TokenizeError::UnexpectedCharacter {
|
||||
char: c,
|
||||
line: self.line,
|
||||
column: self.column,
|
||||
})
|
||||
}
|
||||
None => {
|
||||
Ok(Token::new(TokenType::EOF, self.line, self.column))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 文字列リテラルを読み取り
|
||||
fn read_string(&mut self) -> Result<String, TokenizeError> {
|
||||
let start_line = self.line;
|
||||
self.advance(); // 開始の '"' をスキップ
|
||||
|
||||
let mut string_value = String::new();
|
||||
|
||||
while let Some(c) = self.current_char() {
|
||||
if c == '"' {
|
||||
self.advance(); // 終了の '"' をスキップ
|
||||
return Ok(string_value);
|
||||
}
|
||||
|
||||
// エスケープ文字の処理
|
||||
if c == '\\' {
|
||||
self.advance();
|
||||
match self.current_char() {
|
||||
Some('n') => string_value.push('\n'),
|
||||
Some('t') => string_value.push('\t'),
|
||||
Some('r') => string_value.push('\r'),
|
||||
Some('\\') => string_value.push('\\'),
|
||||
Some('"') => string_value.push('"'),
|
||||
Some(c) => {
|
||||
string_value.push('\\');
|
||||
string_value.push(c);
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
} else {
|
||||
string_value.push(c);
|
||||
}
|
||||
|
||||
self.advance();
|
||||
}
|
||||
|
||||
Err(TokenizeError::UnterminatedString { line: start_line })
|
||||
}
|
||||
|
||||
/// 数値リテラル(整数または浮動小数点数)を読み取り
|
||||
fn read_numeric_literal(&mut self) -> Result<TokenType, TokenizeError> {
|
||||
let start_line = self.line;
|
||||
let mut number_str = String::new();
|
||||
let mut has_dot = false;
|
||||
|
||||
// 整数部分を読み取り
|
||||
while let Some(c) = self.current_char() {
|
||||
if c.is_ascii_digit() {
|
||||
number_str.push(c);
|
||||
self.advance();
|
||||
} else if c == '.' && !has_dot && self.peek_char().map_or(false, |ch| ch.is_ascii_digit()) {
|
||||
// 小数点の後に数字が続く場合のみ受け入れる
|
||||
has_dot = true;
|
||||
number_str.push(c);
|
||||
self.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if has_dot {
|
||||
// 浮動小数点数として解析
|
||||
number_str.parse::<f64>()
|
||||
.map(TokenType::FLOAT)
|
||||
.map_err(|_| TokenizeError::InvalidNumber { line: start_line })
|
||||
} else {
|
||||
// 整数として解析
|
||||
number_str.parse::<i64>()
|
||||
.map(TokenType::NUMBER)
|
||||
.map_err(|_| TokenizeError::InvalidNumber { line: start_line })
|
||||
}
|
||||
}
|
||||
|
||||
/// キーワードまたは識別子を読み取り
|
||||
fn read_keyword_or_identifier(&mut self) -> TokenType {
|
||||
let mut identifier = String::new();
|
||||
|
||||
while let Some(c) = self.current_char() {
|
||||
if c.is_alphanumeric() || c == '_' {
|
||||
identifier.push(c);
|
||||
self.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// キーワードチェック
|
||||
match identifier.as_str() {
|
||||
"box" => TokenType::BOX,
|
||||
"global" => TokenType::GLOBAL,
|
||||
"singleton" => TokenType::SINGLETON,
|
||||
"new" => TokenType::NEW,
|
||||
"if" => TokenType::IF,
|
||||
"else" => TokenType::ELSE,
|
||||
"loop" => TokenType::LOOP,
|
||||
"break" => TokenType::BREAK,
|
||||
"return" => TokenType::RETURN,
|
||||
"function" => TokenType::FUNCTION,
|
||||
"print" => TokenType::PRINT,
|
||||
"this" => TokenType::THIS,
|
||||
"me" => TokenType::ME,
|
||||
"init" => TokenType::INIT,
|
||||
"nowait" => TokenType::NOWAIT,
|
||||
"await" => TokenType::AWAIT,
|
||||
"interface" => TokenType::INTERFACE,
|
||||
"from" => TokenType::FROM,
|
||||
"include" => TokenType::INCLUDE,
|
||||
"try" => TokenType::TRY,
|
||||
"catch" => TokenType::CATCH,
|
||||
"finally" => TokenType::FINALLY,
|
||||
"throw" => TokenType::THROW,
|
||||
"local" => TokenType::LOCAL,
|
||||
"static" => TokenType::STATIC,
|
||||
"outbox" => TokenType::OUTBOX,
|
||||
"not" => TokenType::NOT,
|
||||
"and" => TokenType::AND,
|
||||
"or" => TokenType::OR,
|
||||
"true" => TokenType::TRUE,
|
||||
"false" => TokenType::FALSE,
|
||||
_ => TokenType::IDENTIFIER(identifier),
|
||||
}
|
||||
}
|
||||
|
||||
/// 行コメントをスキップ
|
||||
fn skip_line_comment(&mut self) {
|
||||
while let Some(c) = self.current_char() {
|
||||
if c == '\n' {
|
||||
break; // 改行文字は消費せずに残す
|
||||
}
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
/// 空白文字をスキップ(改行は除く)
|
||||
fn skip_whitespace(&mut self) {
|
||||
while let Some(c) = self.current_char() {
|
||||
if c.is_whitespace() && c != '\n' {
|
||||
self.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 現在の文字を取得
|
||||
fn current_char(&self) -> Option<char> {
|
||||
self.input.get(self.position).copied()
|
||||
}
|
||||
|
||||
/// 次の文字を先読み
|
||||
fn peek_char(&self) -> Option<char> {
|
||||
self.input.get(self.position + 1).copied()
|
||||
}
|
||||
|
||||
/// 位置を1つ進める
|
||||
fn advance(&mut self) {
|
||||
if let Some(c) = self.current_char() {
|
||||
if c == '\n' {
|
||||
self.line += 1;
|
||||
self.column = 1;
|
||||
} else {
|
||||
self.column += 1;
|
||||
}
|
||||
self.position += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// 入力の終端に達したかチェック
|
||||
fn is_at_end(&self) -> bool {
|
||||
self.position >= self.input.len()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Tests =====
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_simple_tokens() {
|
||||
let mut tokenizer = NyashTokenizer::new("box new = + - *");
|
||||
let tokens = tokenizer.tokenize().unwrap();
|
||||
|
||||
assert_eq!(tokens.len(), 7); // 6 tokens + EOF
|
||||
assert_eq!(tokens[0].token_type, TokenType::BOX);
|
||||
assert_eq!(tokens[1].token_type, TokenType::NEW);
|
||||
assert_eq!(tokens[2].token_type, TokenType::ASSIGN);
|
||||
assert_eq!(tokens[3].token_type, TokenType::PLUS);
|
||||
assert_eq!(tokens[4].token_type, TokenType::MINUS);
|
||||
assert_eq!(tokens[5].token_type, TokenType::MULTIPLY);
|
||||
assert_eq!(tokens[6].token_type, TokenType::EOF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_literal() {
|
||||
let mut tokenizer = NyashTokenizer::new(r#""Hello, World!""#);
|
||||
let tokens = tokenizer.tokenize().unwrap();
|
||||
|
||||
assert_eq!(tokens.len(), 2); // STRING + EOF
|
||||
match &tokens[0].token_type {
|
||||
TokenType::STRING(s) => assert_eq!(s, "Hello, World!"),
|
||||
_ => panic!("Expected STRING token"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_number_literal() {
|
||||
let mut tokenizer = NyashTokenizer::new("42 123 0");
|
||||
let tokens = tokenizer.tokenize().unwrap();
|
||||
|
||||
assert_eq!(tokens.len(), 4); // 3 numbers + EOF
|
||||
match &tokens[0].token_type {
|
||||
TokenType::NUMBER(n) => assert_eq!(*n, 42),
|
||||
_ => panic!("Expected NUMBER token"),
|
||||
}
|
||||
match &tokens[1].token_type {
|
||||
TokenType::NUMBER(n) => assert_eq!(*n, 123),
|
||||
_ => panic!("Expected NUMBER token"),
|
||||
}
|
||||
match &tokens[2].token_type {
|
||||
TokenType::NUMBER(n) => assert_eq!(*n, 0),
|
||||
_ => panic!("Expected NUMBER token"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_identifier() {
|
||||
let mut tokenizer = NyashTokenizer::new("test_var myBox getValue");
|
||||
let tokens = tokenizer.tokenize().unwrap();
|
||||
|
||||
assert_eq!(tokens.len(), 4); // 3 identifiers + EOF
|
||||
match &tokens[0].token_type {
|
||||
TokenType::IDENTIFIER(s) => assert_eq!(s, "test_var"),
|
||||
_ => panic!("Expected IDENTIFIER token"),
|
||||
}
|
||||
match &tokens[1].token_type {
|
||||
TokenType::IDENTIFIER(s) => assert_eq!(s, "myBox"),
|
||||
_ => panic!("Expected IDENTIFIER token"),
|
||||
}
|
||||
match &tokens[2].token_type {
|
||||
TokenType::IDENTIFIER(s) => assert_eq!(s, "getValue"),
|
||||
_ => panic!("Expected IDENTIFIER token"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_operators() {
|
||||
let mut tokenizer = NyashTokenizer::new(">> == != <= >= < >");
|
||||
let tokens = tokenizer.tokenize().unwrap();
|
||||
|
||||
assert_eq!(tokens[0].token_type, TokenType::ARROW);
|
||||
assert_eq!(tokens[1].token_type, TokenType::EQUALS);
|
||||
assert_eq!(tokens[2].token_type, TokenType::NotEquals);
|
||||
assert_eq!(tokens[3].token_type, TokenType::LessEquals);
|
||||
assert_eq!(tokens[4].token_type, TokenType::GreaterEquals);
|
||||
assert_eq!(tokens[5].token_type, TokenType::LESS);
|
||||
assert_eq!(tokens[6].token_type, TokenType::GREATER);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_code() {
|
||||
let code = r#"
|
||||
box TestBox {
|
||||
value
|
||||
|
||||
getValue() {
|
||||
return this.value
|
||||
}
|
||||
}
|
||||
|
||||
obj = new TestBox()
|
||||
obj.value = "test123"
|
||||
"#;
|
||||
|
||||
let mut tokenizer = NyashTokenizer::new(code);
|
||||
let tokens = tokenizer.tokenize().unwrap();
|
||||
|
||||
// 基本的なトークンがある事を確認
|
||||
let token_types: Vec<_> = tokens.iter().map(|t| &t.token_type).collect();
|
||||
assert!(token_types.contains(&&TokenType::BOX));
|
||||
assert!(token_types.contains(&&TokenType::NEW));
|
||||
assert!(token_types.contains(&&TokenType::THIS));
|
||||
assert!(token_types.contains(&&TokenType::RETURN));
|
||||
assert!(token_types.contains(&&TokenType::DOT));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_line_numbers() {
|
||||
let code = "box\ntest\nvalue";
|
||||
let mut tokenizer = NyashTokenizer::new(code);
|
||||
let tokens = tokenizer.tokenize().unwrap();
|
||||
|
||||
assert_eq!(tokens[0].line, 1); // box
|
||||
assert_eq!(tokens[1].line, 2); // test
|
||||
assert_eq!(tokens[2].line, 3); // value
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comments() {
|
||||
let code = r#"box Test // this is a comment
|
||||
# this is also a comment
|
||||
value"#;
|
||||
|
||||
let mut tokenizer = NyashTokenizer::new(code);
|
||||
let tokens = tokenizer.tokenize().unwrap();
|
||||
|
||||
// コメントは除外されている
|
||||
let token_types: Vec<_> = tokens.iter().map(|t| &t.token_type).collect();
|
||||
assert_eq!(token_types.len(), 4); // box, Test, value, EOF
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_handling() {
|
||||
let mut tokenizer = NyashTokenizer::new("@#$%");
|
||||
let result = tokenizer.tokenize();
|
||||
|
||||
assert!(result.is_err());
|
||||
match result {
|
||||
Err(TokenizeError::UnexpectedCharacter { char, line, column }) => {
|
||||
assert_eq!(char, '@');
|
||||
assert_eq!(line, 1);
|
||||
assert_eq!(column, 1);
|
||||
}
|
||||
_ => panic!("Expected UnexpectedCharacter error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
433
src/type_box.rs
Normal file
433
src/type_box.rs
Normal file
@ -0,0 +1,433 @@
|
||||
/*!
|
||||
* TypeBox - Everything is Box極限実現
|
||||
*
|
||||
* 型情報もBoxとして表現し、実行時型チェック、メタプログラミング、
|
||||
* ジェネリクス基盤を提供する革命的システム
|
||||
*/
|
||||
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::any::Any;
|
||||
|
||||
/// メソッドシグニチャ情報
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MethodSignature {
|
||||
pub name: String,
|
||||
pub parameters: Vec<String>,
|
||||
pub parameter_types: Vec<Arc<TypeBox>>,
|
||||
pub return_type: Arc<TypeBox>,
|
||||
pub is_static: bool,
|
||||
}
|
||||
|
||||
impl MethodSignature {
|
||||
pub fn new(name: String, parameters: Vec<String>) -> Self {
|
||||
Self {
|
||||
name,
|
||||
parameters,
|
||||
parameter_types: Vec::new(),
|
||||
return_type: Arc::new(TypeBox::void_type()),
|
||||
is_static: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_types(
|
||||
name: String,
|
||||
parameters: Vec<String>,
|
||||
parameter_types: Vec<Arc<TypeBox>>,
|
||||
return_type: Arc<TypeBox>
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
parameters,
|
||||
parameter_types,
|
||||
return_type,
|
||||
is_static: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 🔥 TypeBox - 型情報をBoxとして表現
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypeBox {
|
||||
/// 型名
|
||||
pub name: String,
|
||||
|
||||
/// フィールド情報 (field_name -> field_type)
|
||||
pub fields: HashMap<String, Arc<TypeBox>>,
|
||||
|
||||
/// メソッドシグニチャ情報
|
||||
pub methods: HashMap<String, MethodSignature>,
|
||||
|
||||
/// 親型(継承)
|
||||
pub parent_type: Option<Arc<TypeBox>>,
|
||||
|
||||
/// ジェネリクス型パラメータ
|
||||
pub type_parameters: Vec<String>,
|
||||
|
||||
/// インスタンス化された具体型(ジェネリクス用)
|
||||
pub concrete_types: HashMap<String, Arc<TypeBox>>,
|
||||
|
||||
/// ビルトイン型かどうか
|
||||
pub is_builtin: bool,
|
||||
|
||||
/// ユニークID
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl TypeBox {
|
||||
/// 新しいTypeBoxを作成
|
||||
pub fn new(name: &str) -> Self {
|
||||
static mut COUNTER: u64 = 0;
|
||||
let id = unsafe {
|
||||
COUNTER += 1;
|
||||
COUNTER
|
||||
};
|
||||
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
fields: HashMap::new(),
|
||||
methods: HashMap::new(),
|
||||
parent_type: None,
|
||||
type_parameters: Vec::new(),
|
||||
concrete_types: HashMap::new(),
|
||||
is_builtin: false,
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// ビルトイン型を作成
|
||||
pub fn builtin(name: &str) -> Self {
|
||||
let mut type_box = Self::new(name);
|
||||
type_box.is_builtin = true;
|
||||
type_box
|
||||
}
|
||||
|
||||
/// フィールドを追加
|
||||
pub fn add_field(&mut self, name: &str, field_type: Arc<TypeBox>) {
|
||||
self.fields.insert(name.to_string(), field_type);
|
||||
}
|
||||
|
||||
/// メソッドを追加
|
||||
pub fn add_method(&mut self, method: MethodSignature) {
|
||||
self.methods.insert(method.name.clone(), method);
|
||||
}
|
||||
|
||||
/// 親型を設定
|
||||
pub fn set_parent(&mut self, parent: Arc<TypeBox>) {
|
||||
self.parent_type = Some(parent);
|
||||
}
|
||||
|
||||
/// 型パラメータを追加
|
||||
pub fn add_type_parameter(&mut self, param: String) {
|
||||
self.type_parameters.push(param);
|
||||
}
|
||||
|
||||
/// 具体型を設定(ジェネリクス用)
|
||||
pub fn set_concrete_type(&mut self, param: &str, concrete_type: Arc<TypeBox>) {
|
||||
self.concrete_types.insert(param.to_string(), concrete_type);
|
||||
}
|
||||
|
||||
/// フィールドの型を取得
|
||||
pub fn get_field_type(&self, field_name: &str) -> Option<Arc<TypeBox>> {
|
||||
// 自分のフィールドをチェック
|
||||
if let Some(field_type) = self.fields.get(field_name) {
|
||||
return Some(Arc::clone(field_type));
|
||||
}
|
||||
|
||||
// 親型のフィールドをチェック(継承)
|
||||
if let Some(parent) = &self.parent_type {
|
||||
return parent.get_field_type(field_name);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// メソッドシグニチャを取得
|
||||
pub fn get_method_signature(&self, method_name: &str) -> Option<&MethodSignature> {
|
||||
// 自分のメソッドをチェック
|
||||
if let Some(method) = self.methods.get(method_name) {
|
||||
return Some(method);
|
||||
}
|
||||
|
||||
// 親型のメソッドをチェック(継承)
|
||||
if let Some(parent) = &self.parent_type {
|
||||
return parent.get_method_signature(method_name);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// 型互換性チェック
|
||||
pub fn is_compatible_with(&self, other: &TypeBox) -> bool {
|
||||
// 同じ型
|
||||
if self.name == other.name {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 継承チェック
|
||||
if let Some(parent) = &self.parent_type {
|
||||
if parent.is_compatible_with(other) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// ジェネリクス型かどうか
|
||||
pub fn is_generic(&self) -> bool {
|
||||
!self.type_parameters.is_empty()
|
||||
}
|
||||
|
||||
/// 具体化されたジェネリクス型かどうか
|
||||
pub fn is_concrete_generic(&self) -> bool {
|
||||
!self.concrete_types.is_empty()
|
||||
}
|
||||
|
||||
/// 型名を完全表示(ジェネリクス対応)
|
||||
pub fn full_name(&self) -> String {
|
||||
if self.concrete_types.is_empty() {
|
||||
self.name.clone()
|
||||
} else {
|
||||
let mut result = self.name.clone();
|
||||
result.push('<');
|
||||
|
||||
let concrete_names: Vec<String> = self.type_parameters.iter()
|
||||
.map(|param| {
|
||||
self.concrete_types.get(param)
|
||||
.map(|t| t.name.clone())
|
||||
.unwrap_or_else(|| param.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
result.push_str(&concrete_names.join(", "));
|
||||
result.push('>');
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// 基本型の定数
|
||||
pub fn void_type() -> TypeBox {
|
||||
TypeBox::builtin("Void")
|
||||
}
|
||||
|
||||
pub fn string_type() -> TypeBox {
|
||||
TypeBox::builtin("StringBox")
|
||||
}
|
||||
|
||||
pub fn integer_type() -> TypeBox {
|
||||
TypeBox::builtin("IntegerBox")
|
||||
}
|
||||
|
||||
pub fn bool_type() -> TypeBox {
|
||||
TypeBox::builtin("BoolBox")
|
||||
}
|
||||
|
||||
pub fn array_type() -> TypeBox {
|
||||
let mut type_box = TypeBox::builtin("ArrayBox");
|
||||
type_box.add_type_parameter("T".to_string());
|
||||
type_box
|
||||
}
|
||||
|
||||
pub fn method_box_type() -> TypeBox {
|
||||
let mut type_box = TypeBox::builtin("MethodBox");
|
||||
type_box.add_type_parameter("T".to_string());
|
||||
type_box
|
||||
}
|
||||
}
|
||||
|
||||
/// TypeBoxをNyashBoxとして実装
|
||||
impl NyashBox for TypeBox {
|
||||
fn to_string_box(&self) -> StringBox {
|
||||
StringBox::new(format!("<TypeBox: {}>", self.full_name()))
|
||||
}
|
||||
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
|
||||
if let Some(other_type) = other.as_any().downcast_ref::<TypeBox>() {
|
||||
BoolBox::new(self.name == other_type.name)
|
||||
} else {
|
||||
BoolBox::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
"TypeBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn box_id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TypeBox {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "<TypeBox: {}>", self.full_name())
|
||||
}
|
||||
}
|
||||
|
||||
/// TypeBoxレジストリ - グローバル型管理
|
||||
#[derive(Debug)]
|
||||
pub struct TypeRegistry {
|
||||
/// 登録済み型
|
||||
types: HashMap<String, Arc<TypeBox>>,
|
||||
|
||||
/// 継承チェーン情報(高速化用)
|
||||
inheritance_chains: HashMap<String, Vec<String>>,
|
||||
|
||||
/// メソッドキャッシュ(将来の最適化用)
|
||||
#[allow(dead_code)]
|
||||
method_cache: HashMap<(String, String), MethodSignature>,
|
||||
}
|
||||
|
||||
impl TypeRegistry {
|
||||
/// 新しいTypeRegistryを作成
|
||||
pub fn new() -> Self {
|
||||
let mut registry = Self {
|
||||
types: HashMap::new(),
|
||||
inheritance_chains: HashMap::new(),
|
||||
method_cache: HashMap::new(),
|
||||
};
|
||||
|
||||
// ビルトイン型を登録
|
||||
registry.register_builtin_types();
|
||||
registry
|
||||
}
|
||||
|
||||
/// ビルトイン型を登録
|
||||
fn register_builtin_types(&mut self) {
|
||||
self.register_type(Arc::new(TypeBox::void_type()));
|
||||
self.register_type(Arc::new(TypeBox::string_type()));
|
||||
self.register_type(Arc::new(TypeBox::integer_type()));
|
||||
self.register_type(Arc::new(TypeBox::bool_type()));
|
||||
self.register_type(Arc::new(TypeBox::array_type()));
|
||||
self.register_type(Arc::new(TypeBox::method_box_type()));
|
||||
}
|
||||
|
||||
/// 型を登録
|
||||
pub fn register_type(&mut self, type_box: Arc<TypeBox>) {
|
||||
let name = type_box.name.clone();
|
||||
|
||||
// 継承チェーンを構築
|
||||
let mut chain = vec![name.clone()];
|
||||
let mut current = &type_box.parent_type;
|
||||
while let Some(parent) = current {
|
||||
chain.push(parent.name.clone());
|
||||
current = &parent.parent_type;
|
||||
}
|
||||
|
||||
self.inheritance_chains.insert(name.clone(), chain);
|
||||
self.types.insert(name, type_box);
|
||||
}
|
||||
|
||||
/// 型を取得
|
||||
pub fn get_type(&self, name: &str) -> Option<Arc<TypeBox>> {
|
||||
self.types.get(name).map(Arc::clone)
|
||||
}
|
||||
|
||||
/// 型互換性チェック
|
||||
pub fn is_compatible(&self, from_type: &str, to_type: &str) -> bool {
|
||||
if from_type == to_type {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(chain) = self.inheritance_chains.get(from_type) {
|
||||
chain.contains(&to_type.to_string())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// すべての型名を取得
|
||||
pub fn get_all_type_names(&self) -> Vec<String> {
|
||||
self.types.keys().cloned().collect()
|
||||
}
|
||||
|
||||
/// ジェネリクス型をインスタンス化
|
||||
pub fn instantiate_generic(&mut self, base_type: &str, concrete_types: &[&str]) -> Result<Arc<TypeBox>, String> {
|
||||
let base = self.get_type(base_type)
|
||||
.ok_or_else(|| format!("Base type '{}' not found", base_type))?;
|
||||
|
||||
if !base.is_generic() {
|
||||
return Err(format!("Type '{}' is not generic", base_type));
|
||||
}
|
||||
|
||||
if base.type_parameters.len() != concrete_types.len() {
|
||||
return Err(format!(
|
||||
"Generic type '{}' expects {} type parameters, got {}",
|
||||
base_type, base.type_parameters.len(), concrete_types.len()
|
||||
));
|
||||
}
|
||||
|
||||
// 新しい具体化型を作成
|
||||
let mut concrete_type = (*base).clone();
|
||||
concrete_type.name = format!("{}_{}", base_type, concrete_types.join("_"));
|
||||
concrete_type.concrete_types.clear();
|
||||
|
||||
// 具体型を設定
|
||||
for (i, param) in base.type_parameters.iter().enumerate() {
|
||||
let concrete = self.get_type(concrete_types[i])
|
||||
.ok_or_else(|| format!("Concrete type '{}' not found", concrete_types[i]))?;
|
||||
concrete_type.set_concrete_type(param, concrete);
|
||||
}
|
||||
|
||||
let result = Arc::new(concrete_type);
|
||||
|
||||
// レジストリに登録
|
||||
self.register_type(Arc::clone(&result));
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// TypeBoxビルダー - 便利な構築関数
|
||||
pub struct TypeBoxBuilder {
|
||||
type_box: TypeBox,
|
||||
}
|
||||
|
||||
impl TypeBoxBuilder {
|
||||
/// 新しいビルダーを作成
|
||||
pub fn new(name: &str) -> Self {
|
||||
Self {
|
||||
type_box: TypeBox::new(name),
|
||||
}
|
||||
}
|
||||
|
||||
/// フィールドを追加
|
||||
pub fn field(mut self, name: &str, field_type: Arc<TypeBox>) -> Self {
|
||||
self.type_box.add_field(name, field_type);
|
||||
self
|
||||
}
|
||||
|
||||
/// メソッドを追加
|
||||
pub fn method(mut self, method: MethodSignature) -> Self {
|
||||
self.type_box.add_method(method);
|
||||
self
|
||||
}
|
||||
|
||||
/// 親型を設定
|
||||
pub fn parent(mut self, parent: Arc<TypeBox>) -> Self {
|
||||
self.type_box.set_parent(parent);
|
||||
self
|
||||
}
|
||||
|
||||
/// 型パラメータを追加
|
||||
pub fn type_param(mut self, param: &str) -> Self {
|
||||
self.type_box.add_type_parameter(param.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// TypeBoxを完成
|
||||
pub fn build(self) -> TypeBox {
|
||||
self.type_box
|
||||
}
|
||||
}
|
||||
49
src/wasm_test.rs
Normal file
49
src/wasm_test.rs
Normal file
@ -0,0 +1,49 @@
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod wasm_test {
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{window, HtmlCanvasElement, CanvasRenderingContext2d};
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn test_direct_canvas_draw() -> Result<(), JsValue> {
|
||||
// Get window and document
|
||||
let window = window().ok_or("no window")?;
|
||||
let document = window.document().ok_or("no document")?;
|
||||
|
||||
// Get canvas element
|
||||
let canvas = document
|
||||
.get_element_by_id("test-canvas")
|
||||
.ok_or("canvas not found")?
|
||||
.dyn_into::<HtmlCanvasElement>()?;
|
||||
|
||||
// Set canvas size
|
||||
canvas.set_width(400);
|
||||
canvas.set_height(300);
|
||||
|
||||
// Get 2D context
|
||||
let context = canvas
|
||||
.get_context("2d")?
|
||||
.ok_or("no 2d context")?
|
||||
.dyn_into::<CanvasRenderingContext2d>()?;
|
||||
|
||||
// Draw black background
|
||||
context.set_fill_style(&JsValue::from_str("black"));
|
||||
context.fill_rect(0.0, 0.0, 400.0, 300.0);
|
||||
|
||||
// Draw red rectangle
|
||||
context.set_fill_style(&JsValue::from_str("red"));
|
||||
context.fill_rect(50.0, 50.0, 100.0, 80.0);
|
||||
|
||||
// Draw blue circle
|
||||
context.set_fill_style(&JsValue::from_str("blue"));
|
||||
context.begin_path();
|
||||
context.arc(250.0, 100.0, 40.0, 0.0, 2.0 * std::f64::consts::PI)?;
|
||||
context.fill();
|
||||
|
||||
// Draw text
|
||||
context.set_font("20px Arial");
|
||||
context.set_fill_style(&JsValue::from_str("white"));
|
||||
context.fill_text("Hello Direct Canvas!", 100.0, 200.0)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user