Phase 12.7文法改革: ドキュメント文法統一 + VMリファクタリング準備

🌟 Phase 12.7文法改革に基づくドキュメント更新
- init {} → field: TypeBox 個別フィールド宣言形式
- init() → birth() コンストラクタ統一
- pack() → 廃止(birth()に統一)
- public {}/private {} → 個別フィールド修飾子
- override → 廃止(メソッド定義はシンプルに)

📚 更新したドキュメント
- CLAUDE.md: メイン開発ガイド
- docs/quick-reference/syntax-cheatsheet.md: 構文早見表
- docs/reference/language/LANGUAGE_REFERENCE_2025.md: 言語リファレンス
- docs/development/roadmap/phases/phase-15/README.md: Phase 15計画

🔧 VMリファクタリング準備
- vm_methods.rs: VMメソッド呼び出しの分離
- plugin_loader.rs → plugin_loader/: ディレクトリ構造化
- mir/builder/exprs.rs: 式ビルダー分離

📝 新規ドキュメント追加
- 論文戦略・ロードマップ
- Phase 15セルフホスティング準備資料
- Codex Androidセットアップガイド

ビルドは正常に通ることを確認済み!🎉

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Moe Charm
2025-09-04 06:27:39 +09:00
parent 6488b0542e
commit 4e824fa00e
27 changed files with 2804 additions and 1656 deletions

View File

@ -17,6 +17,7 @@ pub mod vm_control_flow;
mod vm_gc; // A3: GC roots & diagnostics extracted
mod vm_exec; // A3: execution loop extracted
mod vm_state; // A3: state & basic helpers extracted
mod vm_methods; // A3-S1: method dispatch wrappers extracted
pub mod abi_util; // Shared ABI/utility helpers
pub mod mir_interpreter; // Lightweight MIR interpreter

View File

@ -244,67 +244,7 @@ pub struct VM {
impl VM {
pub fn runtime_ref(&self) -> &NyashRuntime { &self.runtime }
/// Print a simple breakdown of root VMValue kinds and top BoxRef types (old-moved placeholder)
pub(super) fn gc_print_roots_breakdown_old(&self) {
use std::collections::HashMap;
let roots = self.scope_tracker.roots_snapshot();
let mut kinds: HashMap<&'static str, u64> = HashMap::new();
let mut box_types: HashMap<String, u64> = HashMap::new();
for v in &roots {
match v {
VMValue::Integer(_) => *kinds.entry("Integer").or_insert(0) += 1,
VMValue::Float(_) => *kinds.entry("Float").or_insert(0) += 1,
VMValue::Bool(_) => *kinds.entry("Bool").or_insert(0) += 1,
VMValue::String(_) => *kinds.entry("String").or_insert(0) += 1,
VMValue::Future(_) => *kinds.entry("Future").or_insert(0) += 1,
VMValue::Void => *kinds.entry("Void").or_insert(0) += 1,
VMValue::BoxRef(b) => {
let tn = b.type_name().to_string();
*box_types.entry(tn).or_insert(0) += 1;
}
}
}
eprintln!("[GC] roots_breakdown: kinds={:?}", kinds);
let mut top: Vec<(String, u64)> = box_types.into_iter().collect();
top.sort_by(|a, b| b.1.cmp(&a.1));
top.truncate(5);
eprintln!("[GC] roots_boxref_top5: {:?}", top);
}
pub(super) fn gc_print_reachability_depth2_old(&self) {
use std::collections::HashMap;
let roots = self.scope_tracker.roots_snapshot();
let mut child_types: HashMap<String, u64> = HashMap::new();
let mut child_count = 0u64;
for v in &roots {
if let VMValue::BoxRef(b) = v {
if let Some(arr) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
if let Ok(items) = arr.items.read() {
for item in items.iter() {
let tn = item.type_name().to_string();
*child_types.entry(tn).or_insert(0) += 1;
child_count += 1;
}
}
}
if let Some(map) = b.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
let vals = map.values();
if let Some(arr2) = vals.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
if let Ok(items) = arr2.items.read() {
for item in items.iter() {
let tn = item.type_name().to_string();
*child_types.entry(tn).or_insert(0) += 1;
child_count += 1;
}
}
}
}
}
}
let mut top: Vec<(String, u64)> = child_types.into_iter().collect();
top.sort_by(|a, b| b.1.cmp(&a.1));
top.truncate(5);
eprintln!("[GC] depth2_children: total={} top5={:?}", child_count, top);
}
// TODO: Re-enable when interpreter refactoring is complete
@ -330,232 +270,11 @@ impl VM {
}
*/
/// Execute a MIR module (old placeholder; moved to vm_exec.rs)
pub fn execute_module_old_moved(&mut self, _module: &MirModule) -> Result<Box<dyn NyashBox>, VMError> {
Ok(Box::new(VoidBox::new()))
}
fn print_cache_stats_summary_old(&self) {
let sites_poly = self.boxcall_poly_pic.len();
let entries_poly: usize = self.boxcall_poly_pic.values().map(|v| v.len()).sum();
let avg_entries = if sites_poly > 0 { (entries_poly as f64) / (sites_poly as f64) } else { 0.0 };
let sites_mono = self.boxcall_pic_funcname.len();
let hits_total: u64 = self.boxcall_pic_hits.values().map(|v| *v as u64).sum();
let vt_entries = self.boxcall_vtable_funcname.len();
eprintln!(
"[VM] PIC/VT summary: poly_sites={} avg_entries={:.2} mono_sites={} hits_total={} vt_entries={} | hits: vt={} poly={} mono={} generic={}",
sites_poly, avg_entries, sites_mono, hits_total, vt_entries,
self.boxcall_hits_vtable, self.boxcall_hits_poly_pic, self.boxcall_hits_mono_pic, self.boxcall_hits_generic
);
// Top sites by hits (up to 5)
let mut hits: Vec<(&String, &u32)> = self.boxcall_pic_hits.iter().collect();
hits.sort_by(|a, b| b.1.cmp(a.1));
for (i, (k, v)) in hits.into_iter().take(5).enumerate() {
eprintln!(" #{} {} hits={}", i+1, k, v);
}
}
/// Call a MIR function by name with VMValue arguments
pub(super) fn call_function_by_name_old(&mut self, func_name: &str, args: Vec<VMValue>) -> Result<VMValue, VMError> {
// Root region: ensure args stay rooted during nested call
self.enter_root_region();
self.pin_roots(args.iter());
let module_ref = self.module.as_ref().ok_or_else(|| VMError::InvalidInstruction("No active module".to_string()))?;
let function_ref = module_ref.get_function(func_name)
.ok_or_else(|| VMError::InvalidInstruction(format!("Function '{}' not found", func_name)))?;
// Clone function to avoid borrowing conflicts during execution
let function = function_ref.clone();
// Save current frame
let saved_values = std::mem::take(&mut self.values);
let saved_current_function = self.current_function.clone();
let saved_current_block = self.frame.current_block;
let saved_previous_block = self.previous_block;
let saved_pc = self.frame.pc;
let saved_last_result = self.frame.last_result;
// Bind parameters
for (i, param_id) in function.params.iter().enumerate() {
if let Some(arg) = args.get(i) {
self.set_value(*param_id, arg.clone());
}
}
// Heuristic: map `me` (first param) to class name parsed from function name (e.g., User.method/N)
if let Some(first) = function.params.get(0) {
if let Some((class_part, _rest)) = func_name.split_once('.') {
// Record class for internal field visibility checks
self.object_class.insert(*first, class_part.to_string());
// Mark internal reference
self.object_internal.insert(*first);
}
}
// Execute the function
let result = self.execute_function(&function);
// Restore frame
self.values = saved_values;
self.current_function = saved_current_function;
self.frame.current_block = saved_current_block;
self.previous_block = saved_previous_block;
self.frame.pc = saved_pc;
self.frame.last_result = saved_last_result;
// Leave GC root region
self.scope_tracker.leave_root_region();
result
}
/// Execute a single function
fn execute_function_old(&mut self, function: &MirFunction) -> Result<VMValue, VMError> {
self.current_function = Some(function.signature.name.clone());
// Phase 10_a: JIT profiling (function entry)
if let Some(jm) = &mut self.jit_manager {
// Allow threshold to react to env updates (e.g., DebugConfigBox.apply at runtime)
if let Ok(s) = std::env::var("NYASH_JIT_THRESHOLD") {
if let Ok(t) = s.parse::<u32>() { if t > 0 { jm.set_threshold(t); } }
}
jm.record_entry(&function.signature.name);
// Try compile if hot (no-op for now, returns fake handle)
let _ = jm.maybe_compile(&function.signature.name, function);
// Record per-function lower stats captured during last JIT lower (if any)
// Note: The current engine encapsulates its LowerCore; expose via last_stats on a new instance as needed.
if jm.is_compiled(&function.signature.name) && std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
if let Some(h) = jm.handle_of(&function.signature.name) {
eprintln!("[JIT] dispatch would go to handle={} for {} (stub)", h, function.signature.name);
}
}
}
// Initialize loop executor for this function
self.loop_executor.initialize();
// Enter a new scope for this function
self.scope_tracker.push_scope();
crate::runtime::global_hooks::push_task_scope();
// Phase 10_c: try a JIT dispatch when enabled; fallback to VM on trap/miss
// Prepare arguments from current frame params before borrowing jit_manager mutably
let args_vec: Vec<VMValue> = function
.params
.iter()
.filter_map(|pid| self.get_value(*pid).ok())
.collect();
if std::env::var("NYASH_JIT_EXEC").ok().as_deref() == Some("1") {
let jit_only = std::env::var("NYASH_JIT_ONLY").ok().as_deref() == Some("1");
// Root regionize args for JIT call
self.enter_root_region();
self.pin_roots(args_vec.iter());
if let Some(compiled) = self.jit_manager.as_ref().map(|jm| jm.is_compiled(&function.signature.name)) {
if compiled {
crate::runtime::host_api::set_current_vm(self as *mut _);
let jit_val = if let Some(jm_mut) = self.jit_manager.as_mut() { jm_mut.execute_compiled(&function.signature.name, &function.signature.return_type, &args_vec) } else { None };
crate::runtime::host_api::clear_current_vm();
if let Some(val) = jit_val {
// Exit scope before returning
self.leave_root_region();
self.scope_tracker.pop_scope();
crate::runtime::global_hooks::pop_task_scope();
return Ok(val);
} else if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") ||
std::env::var("NYASH_JIT_TRAP_LOG").ok().as_deref() == Some("1") {
eprintln!("[JIT] fallback: VM path taken for {}", function.signature.name);
if jit_only {
self.leave_root_region();
self.scope_tracker.pop_scope();
crate::runtime::global_hooks::pop_task_scope();
return Err(VMError::InvalidInstruction(format!("JIT-only enabled and JIT trap occurred for {}", function.signature.name)));
}
}
} else if jit_only {
// Try to compile now and execute; if not possible, error out
if let Some(jm_mut) = self.jit_manager.as_mut() { let _ = jm_mut.maybe_compile(&function.signature.name, function); }
if self.jit_manager.as_ref().map(|jm| jm.is_compiled(&function.signature.name)).unwrap_or(false) {
crate::runtime::host_api::set_current_vm(self as *mut _);
let jit_val = if let Some(jm_mut) = self.jit_manager.as_mut() { jm_mut.execute_compiled(&function.signature.name, &function.signature.return_type, &args_vec) } else { None };
crate::runtime::host_api::clear_current_vm();
if let Some(val) = jit_val {
self.leave_root_region();
self.scope_tracker.pop_scope();
crate::runtime::global_hooks::pop_task_scope();
return Ok(val);
} else {
self.leave_root_region();
self.scope_tracker.pop_scope();
crate::runtime::global_hooks::pop_task_scope();
return Err(VMError::InvalidInstruction(format!("JIT-only enabled and JIT execution failed for {}", function.signature.name)));
}
} else {
self.leave_root_region();
self.scope_tracker.pop_scope();
crate::runtime::global_hooks::pop_task_scope();
return Err(VMError::InvalidInstruction(format!("JIT-only enabled but function not compiled: {}", function.signature.name)));
}
}
}
// Leave root region if not compiled or after fallback
self.leave_root_region();
} else {
if let Some(jm_mut) = &mut self.jit_manager {
let argc = function.params.len();
let _would = jm_mut.maybe_dispatch(&function.signature.name, argc);
}
}
// Start at entry block
let mut current_block = function.entry_block;
loop {
let block = function.get_block(current_block)
.ok_or_else(|| VMError::InvalidBasicBlock(format!("Block {} not found", current_block)))?;
self.frame.current_block = Some(current_block);
self.frame.pc = 0;
let mut next_block = None;
let mut should_return = None;
// Execute instructions in this block (including terminator)
let all_instructions: Vec<_> = block.all_instructions().collect();
for (index, instruction) in all_instructions.iter().enumerate() {
self.frame.pc = index;
match self.execute_instruction(instruction)? {
ControlFlow::Continue => continue,
ControlFlow::Jump(target) => {
next_block = Some(target);
break;
},
ControlFlow::Return(value) => {
should_return = Some(value);
break;
},
}
}
// Handle control flow
if let Some(return_value) = should_return {
// Exit scope before returning
self.scope_tracker.pop_scope();
crate::runtime::global_hooks::pop_task_scope();
return Ok(return_value);
} else if let Some(target) = next_block {
// Update previous block before jumping and record transition via control_flow helper
control_flow::record_transition(&mut self.previous_block, &mut self.loop_executor, current_block, target).ok();
current_block = target;
} else {
// Block ended without terminator - this shouldn't happen in well-formed MIR
// but let's handle it gracefully by returning void
// Exit scope before returning
self.scope_tracker.pop_scope();
crate::runtime::global_hooks::pop_task_scope();
return Ok(VMValue::Void);
}
}
}
/// Execute a single instruction (old placeholder; moved to vm_exec.rs)
fn execute_instruction_old(&mut self, _instruction: &MirInstruction) -> Result<ControlFlow, VMError> { unreachable!("moved") }
@ -565,49 +284,11 @@ impl VM {
/// Phase 9.78a: Unified method dispatch for all Box types
fn call_unified_method(&self, box_value: Box<dyn NyashBox>, method: &str, args: Vec<Box<dyn NyashBox>>) -> Result<Box<dyn NyashBox>, VMError> {
// For now, we use the simplified method dispatch
// In a full implementation, this would check for InstanceBox and dispatch appropriately
self.call_box_method_impl(box_value, method, args)
}
/// Call a method on a Box - simplified version of interpreter method dispatch
pub(super) fn call_box_method(&self, box_value: Box<dyn NyashBox>, method: &str, mut _args: Vec<Box<dyn NyashBox>>) -> Result<Box<dyn NyashBox>, VMError> {
// For now, implement basic methods for common box types
// This is a simplified version - real implementation would need full method dispatch
// 🌟 Universal methods pre-dispatch (non-invasive)
match method {
"toString" => {
if !_args.is_empty() {
return Ok(Box::new(StringBox::new(format!("Error: toString() expects 0 arguments, got {}", _args.len()))));
}
return Ok(Box::new(StringBox::new(box_value.to_string_box().value)));
}
"type" => {
if !_args.is_empty() {
return Ok(Box::new(StringBox::new(format!("Error: type() expects 0 arguments, got {}", _args.len()))));
}
return Ok(Box::new(StringBox::new(box_value.type_name())));
}
"equals" => {
if _args.len() != 1 {
return Ok(Box::new(StringBox::new(format!("Error: equals() expects 1 argument, got {}", _args.len()))));
}
let rhs = _args.remove(0);
let eq = box_value.equals(&*rhs);
return Ok(Box::new(eq));
}
"clone" => {
if !_args.is_empty() {
return Ok(Box::new(StringBox::new(format!("Error: clone() expects 0 arguments, got {}", _args.len()))));
}
return Ok(box_value.clone_box());
}
_ => {}
}
// Call a method on a Box - moved to vm_methods.rs (wrapper now in vm_methods)
// removed: old inline implementation
/*
// ResultBox (NyashResultBox - new)
if let Some(result_box) = box_value.as_any().downcast_ref::<crate::boxes::result::NyashResultBox>() {
match method {
@ -1004,10 +685,7 @@ impl VM {
}
return Ok(Box::new(VoidBox::new()));
}
// Default: return void for any unrecognized box type or method
Ok(Box::new(VoidBox::new()))
}
*/
}
/// RAII guard for GC root regions
@ -1024,7 +702,7 @@ impl Default for VM {
#[cfg(test)]
mod tests {
use super::*;
use crate::mir::{MirModule, MirFunction, FunctionSignature, MirType, EffectMask, BasicBlock};
use crate::mir::{MirModule, MirFunction, FunctionSignature, MirType, EffectMask, BasicBlock, BinaryOp};
use crate::parser::NyashParser;
use crate::runtime::NyashRuntime;
use crate::core::model::BoxDeclaration as CoreBoxDecl;

34
src/backend/vm_methods.rs Normal file
View File

@ -0,0 +1,34 @@
/*!
* VM Methods Glue
*
* Extracted wrappers for Box method dispatch to keep vm.rs slim.
* These delegate to the real implementation in vm_boxcall.rs, preserving
* the existing VM API surface.
*/
use crate::box_trait::NyashBox;
use super::vm::{VM, VMError};
impl VM {
/// Unified method dispatch entry. Currently delegates to `call_box_method_impl`.
fn call_unified_method(
&self,
box_value: Box<dyn NyashBox>,
method: &str,
args: Vec<Box<dyn NyashBox>>,
) -> Result<Box<dyn NyashBox>, VMError> {
self.call_box_method_impl(box_value, method, args)
}
/// Public-facing method call used by vm_instructions::boxcall.
/// Kept as a thin wrapper to the implementation in vm_boxcall.rs.
pub(super) fn call_box_method(
&self,
box_value: Box<dyn NyashBox>,
method: &str,
args: Vec<Box<dyn NyashBox>>,
) -> Result<Box<dyn NyashBox>, VMError> {
self.call_box_method_impl(box_value, method, args)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,171 @@
//! Loader entrypoints for dynamic plugins
use std::ffi::{CString, c_char, c_void};
#[cfg(feature = "dynamic-file")]
use libloading::{Library, Symbol};
use crate::box_trait::NyashBox;
use crate::interpreter::RuntimeError;
use super::proxies::{FileBoxProxy, MathBoxProxy, RandomBoxProxy, TimeBoxProxy, DateTimeBoxProxy};
use super::types::{PLUGIN_CACHE, LoadedPlugin, PluginInfo};
/// Public plugin loader API
pub struct PluginLoader;
impl PluginLoader {
/// Load File plugin
#[cfg(feature = "dynamic-file")]
pub fn load_file_plugin() -> Result<(), RuntimeError> {
let mut cache = PLUGIN_CACHE.write().unwrap();
if cache.contains_key("file") { return Ok(()); }
let lib_name = if cfg!(target_os = "windows") { "nyash_file.dll" } else if cfg!(target_os = "macos") { "libnyash_file.dylib" } else { "libnyash_file.so" };
let possible_paths = vec![
format!("./target/release/{}", lib_name),
format!("./target/debug/{}", lib_name),
format!("./plugins/{}", lib_name),
format!("./{}", lib_name),
];
let lib_path = possible_paths.iter().find(|p| std::path::Path::new(p.as_str()).exists()).cloned()
.ok_or_else(|| RuntimeError::InvalidOperation { message: format!("Failed to find file plugin library. Searched paths: {:?}", possible_paths) })?;
unsafe {
let library = Library::new(&lib_path).map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to load file plugin: {}", e) })?;
let init_fn: Symbol<unsafe extern "C" fn() -> *const c_void> = library.get(b"nyash_plugin_init\0").map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to get plugin init: {}", e) })?;
let plugin_info_ptr = init_fn();
if plugin_info_ptr.is_null() { return Err(RuntimeError::InvalidOperation { message: "Plugin initialization failed".to_string() }); }
let info = PluginInfo { name: "file".to_string(), version: 1, api_version: 1 };
cache.insert("file".to_string(), LoadedPlugin { library, info });
}
Ok(())
}
/// Create FileBox
#[cfg(feature = "dynamic-file")]
pub fn create_file_box(path: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
Self::load_file_plugin()?;
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("file") {
let c_path = CString::new(path).map_err(|_| RuntimeError::InvalidOperation { message: "Invalid path string".to_string() })?;
unsafe {
let open_fn: Symbol<unsafe extern "C" fn(*const c_char) -> *mut c_void> = plugin.library.get(b"nyash_file_open\0").map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to get nyash_file_open: {}", e) })?;
let handle = open_fn(c_path.as_ptr());
if handle.is_null() { return Err(RuntimeError::InvalidOperation { message: format!("Failed to open file: {}", path) }); }
Ok(Box::new(FileBoxProxy::new(handle, path.to_string())))
}
} else { Err(RuntimeError::InvalidOperation { message: "File plugin not loaded".to_string() }) }
}
/// Check FileBox existence
#[cfg(feature = "dynamic-file")]
pub fn file_exists(path: &str) -> Result<bool, RuntimeError> {
Self::load_file_plugin()?;
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("file") {
let c_path = CString::new(path).map_err(|_| RuntimeError::InvalidOperation { message: "Invalid path string".to_string() })?;
unsafe {
let exists_fn: Symbol<unsafe extern "C" fn(*const c_char) -> i32> = plugin.library.get(b"nyash_file_exists\0").map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to get nyash_file_exists: {}", e) })?;
Ok(exists_fn(c_path.as_ptr()) != 0)
}
} else { Err(RuntimeError::InvalidOperation { message: "File plugin not loaded".to_string() }) }
}
/// Load Math plugin
#[cfg(feature = "dynamic-file")]
pub fn load_math_plugin() -> Result<(), RuntimeError> {
let mut cache = PLUGIN_CACHE.write().unwrap();
if cache.contains_key("math") { return Ok(()); }
let lib_name = if cfg!(target_os = "windows") { "nyash_math.dll" } else if cfg!(target_os = "macos") { "libnyash_math.dylib" } else { "libnyash_math.so" };
let possible_paths = vec![
format!("./target/release/{}", lib_name),
format!("./target/debug/{}", lib_name),
format!("./plugins/{}", lib_name),
format!("./{}", lib_name),
];
let lib_path = possible_paths.iter().find(|p| std::path::Path::new(p.as_str()).exists()).cloned()
.ok_or_else(|| RuntimeError::InvalidOperation { message: format!("Failed to find math plugin library. Searched paths: {:?}", possible_paths) })?;
unsafe {
let library = Library::new(&lib_path).map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to load math plugin: {}", e) })?;
let info = PluginInfo { name: "math".to_string(), version: 1, api_version: 1 };
cache.insert("math".to_string(), LoadedPlugin { library, info });
}
Ok(())
}
/// Create MathBox
#[cfg(feature = "dynamic-file")]
pub fn create_math_box() -> Result<Box<dyn NyashBox>, RuntimeError> {
Self::load_math_plugin()?;
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("math") {
unsafe {
let create_fn: Symbol<unsafe extern "C" fn() -> *mut c_void> = plugin.library.get(b"nyash_math_create\0").map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to get nyash_math_create: {}", e) })?;
let handle = create_fn();
if handle.is_null() { return Err(RuntimeError::InvalidOperation { message: "Failed to create MathBox".to_string() }); }
Ok(Box::new(MathBoxProxy::new(handle)))
}
} else { Err(RuntimeError::InvalidOperation { message: "Math plugin not loaded".to_string() }) }
}
/// Create RandomBox
#[cfg(feature = "dynamic-file")]
pub fn create_random_box() -> Result<Box<dyn NyashBox>, RuntimeError> {
Self::load_math_plugin()?;
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("math") {
unsafe {
let create_fn: Symbol<unsafe extern "C" fn() -> *mut c_void> = plugin.library.get(b"nyash_random_create\0").map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to get nyash_random_create: {}", e) })?;
let handle = create_fn();
if handle.is_null() { return Err(RuntimeError::InvalidOperation { message: "Failed to create RandomBox".to_string() }); }
Ok(Box::new(RandomBoxProxy::new(handle)))
}
} else { Err(RuntimeError::InvalidOperation { message: "Math plugin not loaded".to_string() }) }
}
/// Create TimeBox
#[cfg(feature = "dynamic-file")]
pub fn create_time_box() -> Result<Box<dyn NyashBox>, RuntimeError> {
Self::load_math_plugin()?;
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("math") {
unsafe {
let create_fn: Symbol<unsafe extern "C" fn() -> *mut c_void> = plugin.library.get(b"nyash_time_create\0").map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to get nyash_time_create: {}", e) })?;
let handle = create_fn();
if handle.is_null() { return Err(RuntimeError::InvalidOperation { message: "Failed to create TimeBox".to_string() }); }
Ok(Box::new(TimeBoxProxy::new(handle)))
}
} else { Err(RuntimeError::InvalidOperation { message: "Math plugin not loaded".to_string() }) }
}
/// Create DateTimeBox (now)
#[cfg(feature = "dynamic-file")]
pub fn create_datetime_now() -> Result<Box<dyn NyashBox>, RuntimeError> {
Self::load_math_plugin()?;
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("math") {
unsafe {
let now_fn: Symbol<unsafe extern "C" fn() -> *mut c_void> = plugin.library.get(b"nyash_time_now\0").map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to get nyash_time_now: {}", e) })?;
let handle = now_fn();
if handle.is_null() { return Err(RuntimeError::InvalidOperation { message: "Failed to create DateTimeBox".to_string() }); }
Ok(Box::new(DateTimeBoxProxy::new(handle)))
}
} else { Err(RuntimeError::InvalidOperation { message: "Math plugin not loaded".to_string() }) }
}
/// Create DateTimeBox from string
#[cfg(feature = "dynamic-file")]
pub fn create_datetime_from_string(time_str: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
Self::load_math_plugin()?;
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("math") {
let c_str = CString::new(time_str).map_err(|_| RuntimeError::InvalidOperation { message: "Invalid time string".to_string() })?;
unsafe {
let parse_fn: Symbol<unsafe extern "C" fn(*const c_char) -> *mut c_void> = plugin.library.get(b"nyash_time_parse\0").map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to get nyash_time_parse: {}", e) })?;
let handle = parse_fn(c_str.as_ptr());
if handle.is_null() { return Err(RuntimeError::InvalidOperation { message: format!("Failed to parse time string: {}", time_str) }); }
Ok(Box::new(DateTimeBoxProxy::new(handle)))
}
} else { Err(RuntimeError::InvalidOperation { message: "Math plugin not loaded".to_string() }) }
}
}

View File

@ -0,0 +1,23 @@
//! Dynamic Plugin Loader for Nyash (split module)
//!
//! Refactored into smaller files to improve readability while preserving
//! the original public API surface used across the interpreter:
//! - types.rs: globals and native handles
//! - proxies.rs: Box proxy implementations
//! - loader.rs: public loader entrypoints
mod types;
mod proxies;
mod loader;
// Re-export to preserve original paths like
// crate::interpreter::plugin_loader::{PluginLoader, FileBoxProxy, ..., PLUGIN_CACHE}
pub use loader::PluginLoader;
pub use proxies::{
FileBoxProxy, MathBoxProxy, RandomBoxProxy, TimeBoxProxy, DateTimeBoxProxy,
};
pub use types::{
PLUGIN_CACHE, LoadedPlugin, PluginInfo, FileBoxHandle, MathBoxHandle,
RandomBoxHandle, TimeBoxHandle, DateTimeBoxHandle,
};

View File

@ -0,0 +1,274 @@
//! Proxies for dynamic plugins (File/Math/Random/Time/DateTime)
use std::ffi::{CStr, CString, c_char, c_void};
use std::sync::Arc;
#[cfg(feature = "dynamic-file")]
use libloading::Symbol;
use crate::box_trait::{NyashBox, StringBox, BoolBox, BoxCore, BoxBase, IntegerBox};
use crate::boxes::FloatBox;
use crate::interpreter::RuntimeError;
use super::types::{PLUGIN_CACHE, FileBoxHandle, MathBoxHandle, RandomBoxHandle, TimeBoxHandle, DateTimeBoxHandle};
use super::PluginLoader;
// ================== FileBoxProxy ==================
#[derive(Debug)]
pub struct FileBoxProxy {
pub(crate) handle: Arc<FileBoxHandle>,
pub(crate) path: String,
pub(crate) base: BoxBase,
}
unsafe impl Send for FileBoxProxy {}
unsafe impl Sync for FileBoxProxy {}
impl FileBoxProxy {
pub fn new(handle: *mut c_void, path: String) -> Self {
FileBoxProxy { handle: Arc::new(FileBoxHandle { ptr: handle }), path, base: BoxBase::new() }
}
pub fn read(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
#[cfg(feature = "dynamic-file")]
{
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("file") {
unsafe {
let read_fn: Symbol<unsafe extern "C" fn(*mut c_void) -> *mut c_char> =
plugin.library.get(b"nyash_file_read\0").map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to get nyash_file_read: {}", e) })?;
let result_ptr = read_fn(self.handle.ptr);
if result_ptr.is_null() { return Err(RuntimeError::InvalidOperation { message: "Failed to read file".to_string() }); }
let content = CStr::from_ptr(result_ptr).to_string_lossy().into_owned();
let free_fn: Symbol<unsafe extern "C" fn(*mut c_char)> =
plugin.library.get(b"nyash_string_free\0").map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to get nyash_string_free: {}", e) })?;
free_fn(result_ptr);
Ok(Box::new(StringBox::new(content)))
}
} else { Err(RuntimeError::InvalidOperation { message: "File plugin not loaded".to_string() }) }
}
#[cfg(not(feature = "dynamic-file"))]
{ Err(RuntimeError::InvalidOperation { message: "Dynamic file support not enabled".to_string() }) }
}
pub fn write(&self, content: Box<dyn NyashBox>) -> Result<Box<dyn NyashBox>, RuntimeError> {
#[cfg(feature = "dynamic-file")]
{
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("file") {
let content_str = content.to_string_box().value;
let c_content = CString::new(content_str).map_err(|_| RuntimeError::InvalidOperation { message: "Invalid content string".to_string() })?;
unsafe {
let write_fn: Symbol<unsafe extern "C" fn(*mut c_void, *const c_char) -> i32> =
plugin.library.get(b"nyash_file_write\0").map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to get nyash_file_write: {}", e) })?;
let result = write_fn(self.handle.ptr, c_content.as_ptr());
if result == 0 { return Err(RuntimeError::InvalidOperation { message: "Failed to write file".to_string() }); }
Ok(Box::new(StringBox::new("ok")))
}
} else { Err(RuntimeError::InvalidOperation { message: "File plugin not loaded".to_string() }) }
}
#[cfg(not(feature = "dynamic-file"))]
{ Err(RuntimeError::InvalidOperation { message: "Dynamic file support not enabled".to_string() }) }
}
pub fn exists(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
Ok(Box::new(BoolBox::new(std::path::Path::new(&self.path).exists())))
}
}
impl BoxCore for FileBoxProxy {
fn box_id(&self) -> u64 { self.base.id }
fn parent_type_id(&self) -> Option<std::any::TypeId> { self.base.parent_type_id }
fn fmt_box(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "FileBox({})", self.path) }
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
}
impl NyashBox for FileBoxProxy {
fn type_name(&self) -> &'static str { "FileBox" }
fn clone_box(&self) -> Box<dyn NyashBox> { match PluginLoader::create_file_box(&self.path) { Ok(b) => b, Err(_) => Box::new(FileBoxProxy::new(self.handle.ptr, self.path.clone())) } }
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
fn to_string_box(&self) -> StringBox { StringBox::new(format!("FileBox({})", self.path)) }
fn equals(&self, other: &dyn NyashBox) -> BoolBox { other.as_any().downcast_ref::<FileBoxProxy>().is_some().into() }
}
impl std::fmt::Display for FileBoxProxy { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } }
// ================== MathBoxProxy ==================
#[derive(Debug)]
pub struct MathBoxProxy { pub(crate) handle: Arc<MathBoxHandle>, pub(crate) base: BoxBase }
unsafe impl Send for MathBoxProxy {}
unsafe impl Sync for MathBoxProxy {}
impl MathBoxProxy { pub fn new(handle: *mut c_void) -> Self { MathBoxProxy { handle: Arc::new(MathBoxHandle { ptr: handle }), base: BoxBase::new() } } }
impl BoxCore for MathBoxProxy {
fn box_id(&self) -> u64 { self.base.id }
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "MathBox") }
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
}
impl NyashBox for MathBoxProxy {
fn type_name(&self) -> &'static str { "MathBox" }
fn clone_box(&self) -> Box<dyn NyashBox> { match PluginLoader::create_math_box() { Ok(new_box) => new_box, Err(_) => Box::new(MathBoxProxy { handle: Arc::clone(&self.handle), base: BoxBase::new() }) } }
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
fn to_string_box(&self) -> StringBox { StringBox::new("MathBox") }
fn equals(&self, other: &dyn NyashBox) -> BoolBox { other.as_any().downcast_ref::<MathBoxProxy>().is_some().into() }
}
impl std::fmt::Display for MathBoxProxy { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } }
// ================== RandomBoxProxy ==================
#[derive(Debug)]
pub struct RandomBoxProxy { pub(crate) handle: Arc<RandomBoxHandle>, pub(crate) base: BoxBase }
unsafe impl Send for RandomBoxProxy {}
unsafe impl Sync for RandomBoxProxy {}
impl RandomBoxProxy { pub fn new(handle: *mut c_void) -> Self { RandomBoxProxy { handle: Arc::new(RandomBoxHandle { ptr: handle }), base: BoxBase::new() } } }
impl RandomBoxProxy {
pub fn next(&self) -> Result<Box<dyn NyashBox>, RuntimeError> {
#[cfg(feature = "dynamic-file")]
{
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("math") {
unsafe {
let next_fn: Symbol<unsafe extern "C" fn(*mut c_void) -> f64> = plugin.library.get(b"nyash_random_next\0").map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to get nyash_random_next: {}", e) })?;
let value = next_fn(self.handle.ptr);
Ok(Box::new(FloatBox::new(value)))
}
} else { Err(RuntimeError::InvalidOperation { message: "Math plugin not loaded".to_string() }) }
}
#[cfg(not(feature = "dynamic-file"))]
{ Err(RuntimeError::InvalidOperation { message: "Dynamic loading not enabled".to_string() }) }
}
pub fn range(&self, min: f64, max: f64) -> Result<Box<dyn NyashBox>, RuntimeError> {
#[cfg(feature = "dynamic-file")]
{
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("math") {
unsafe {
let range_fn: Symbol<unsafe extern "C" fn(*mut c_void, f64, f64) -> f64> = plugin.library.get(b"nyash_random_range\0").map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to get nyash_random_range: {}", e) })?;
let value = range_fn(self.handle.ptr, min, max);
Ok(Box::new(FloatBox::new(value)))
}
} else { Err(RuntimeError::InvalidOperation { message: "Math plugin not loaded".to_string() }) }
}
#[cfg(not(feature = "dynamic-file"))]
{ Err(RuntimeError::InvalidOperation { message: "Dynamic loading not enabled".to_string() }) }
}
pub fn int(&self, min: i64, max: i64) -> Result<Box<dyn NyashBox>, RuntimeError> {
#[cfg(feature = "dynamic-file")]
{
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("math") {
unsafe {
let int_fn: Symbol<unsafe extern "C" fn(*mut c_void, i64, i64) -> i64> = plugin.library.get(b"nyash_random_int\0").map_err(|e| RuntimeError::InvalidOperation { message: format!("Failed to get nyash_random_int: {}", e) })?;
let value = int_fn(self.handle.ptr, min, max);
Ok(Box::new(IntegerBox::new(value)))
}
} else { Err(RuntimeError::InvalidOperation { message: "Math plugin not loaded".to_string() }) }
}
#[cfg(not(feature = "dynamic-file"))]
{ Err(RuntimeError::InvalidOperation { message: "Dynamic loading not enabled".to_string() }) }
}
}
impl BoxCore for RandomBoxProxy {
fn box_id(&self) -> u64 { self.base.id }
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "RandomBox") }
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
}
impl NyashBox for RandomBoxProxy {
fn type_name(&self) -> &'static str { "RandomBox" }
fn clone_box(&self) -> Box<dyn NyashBox> { match PluginLoader::create_random_box() { Ok(new_box) => new_box, Err(_) => Box::new(RandomBoxProxy { handle: Arc::clone(&self.handle), base: BoxBase::new() }) } }
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
fn to_string_box(&self) -> StringBox { StringBox::new("RandomBox") }
fn equals(&self, other: &dyn NyashBox) -> BoolBox { other.as_any().downcast_ref::<RandomBoxProxy>().is_some().into() }
}
impl std::fmt::Display for RandomBoxProxy { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } }
// ================== TimeBoxProxy ==================
#[derive(Debug)]
pub struct TimeBoxProxy { pub(crate) handle: Arc<TimeBoxHandle>, pub(crate) base: BoxBase }
unsafe impl Send for TimeBoxProxy {}
unsafe impl Sync for TimeBoxProxy {}
impl TimeBoxProxy { pub fn new(handle: *mut c_void) -> Self { TimeBoxProxy { handle: Arc::new(TimeBoxHandle { ptr: handle }), base: BoxBase::new() } } }
impl BoxCore for TimeBoxProxy {
fn box_id(&self) -> u64 { self.base.id }
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "TimeBox") }
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
}
impl NyashBox for TimeBoxProxy {
fn type_name(&self) -> &'static str { "TimeBox" }
fn clone_box(&self) -> Box<dyn NyashBox> { match PluginLoader::create_time_box() { Ok(new_box) => new_box, Err(_) => Box::new(TimeBoxProxy { handle: Arc::clone(&self.handle), base: BoxBase::new() }) } }
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
fn to_string_box(&self) -> StringBox { StringBox::new("TimeBox") }
fn equals(&self, other: &dyn NyashBox) -> BoolBox { other.as_any().downcast_ref::<TimeBoxProxy>().is_some().into() }
}
impl std::fmt::Display for TimeBoxProxy { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } }
// ================== DateTimeBoxProxy ==================
#[derive(Debug)]
pub struct DateTimeBoxProxy { pub(crate) handle: Arc<DateTimeBoxHandle>, pub(crate) base: BoxBase }
unsafe impl Send for DateTimeBoxProxy {}
unsafe impl Sync for DateTimeBoxProxy {}
impl DateTimeBoxProxy { pub fn new(handle: *mut c_void) -> Self { DateTimeBoxProxy { handle: Arc::new(DateTimeBoxHandle { ptr: handle }), base: BoxBase::new() } } }
impl BoxCore for DateTimeBoxProxy {
fn box_id(&self) -> u64 { self.base.id }
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "DateTimeBox") }
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
}
impl NyashBox for DateTimeBoxProxy {
fn type_name(&self) -> &'static str { "DateTimeBox" }
fn clone_box(&self) -> Box<dyn NyashBox> { match PluginLoader::create_datetime_now() { Ok(new_box) => new_box, Err(_) => Box::new(DateTimeBoxProxy { handle: Arc::clone(&self.handle), base: BoxBase::new() }) } }
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
fn to_string_box(&self) -> StringBox { StringBox::new("DateTimeBox") }
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(other_datetime) = other.as_any().downcast_ref::<DateTimeBoxProxy>() {
#[cfg(feature = "dynamic-file")]
{
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("math") {
unsafe {
if let Ok(timestamp_fn) = plugin.library.get::<Symbol<unsafe extern "C" fn(*mut c_void) -> i64>>(b"nyash_datetime_timestamp\0") {
let this_ts = timestamp_fn(self.handle.ptr);
let other_ts = timestamp_fn(other_datetime.handle.ptr);
return BoolBox::new(this_ts == other_ts);
}
}
}
}
BoolBox::new(false)
} else { BoolBox::new(false) }
}
}
impl std::fmt::Display for DateTimeBoxProxy { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) } }

View File

@ -0,0 +1,159 @@
//! Types and globals for interpreter plugin loader
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::ffi::c_void;
#[cfg(feature = "dynamic-file")]
use libloading::Library;
lazy_static::lazy_static! {
/// Global cache for loaded plugins (keyed by simple name like "file" or "math")
pub(crate) static ref PLUGIN_CACHE: RwLock<HashMap<String, LoadedPlugin>> = RwLock::new(HashMap::new());
}
/// Loaded plugin handle + basic info
#[cfg(feature = "dynamic-file")]
pub(crate) struct LoadedPlugin {
pub(crate) library: Library,
pub(crate) info: PluginInfo,
}
/// Minimal plugin info (simplified)
#[derive(Clone)]
pub(crate) struct PluginInfo {
pub(crate) name: String,
pub(crate) version: u32,
pub(crate) api_version: u32,
}
/// FileBox native handle wrapper
#[derive(Debug)]
pub(crate) struct FileBoxHandle { pub(crate) ptr: *mut c_void }
impl Drop for FileBoxHandle {
fn drop(&mut self) {
#[cfg(feature = "dynamic-file")]
{
if !self.ptr.is_null() {
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("file") {
unsafe {
use libloading::Symbol;
if let Ok(free_fn) = plugin.library.get::<Symbol<unsafe extern "C" fn(*mut c_void)>>(b"nyash_file_free\0") {
free_fn(self.ptr);
}
}
}
}
}
}
}
unsafe impl Send for FileBoxHandle {}
unsafe impl Sync for FileBoxHandle {}
/// MathBox native handle wrapper
#[derive(Debug)]
pub(crate) struct MathBoxHandle { pub(crate) ptr: *mut c_void }
impl Drop for MathBoxHandle {
fn drop(&mut self) {
#[cfg(feature = "dynamic-file")]
{
if !self.ptr.is_null() {
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("math") {
unsafe {
use libloading::Symbol;
if let Ok(free_fn) = plugin.library.get::<Symbol<unsafe extern "C" fn(*mut c_void)>>(b"nyash_math_free\0") {
free_fn(self.ptr);
}
}
}
}
}
}
}
unsafe impl Send for MathBoxHandle {}
unsafe impl Sync for MathBoxHandle {}
/// RandomBox native handle wrapper
#[derive(Debug)]
pub(crate) struct RandomBoxHandle { pub(crate) ptr: *mut c_void }
impl Drop for RandomBoxHandle {
fn drop(&mut self) {
#[cfg(feature = "dynamic-file")]
{
if !self.ptr.is_null() {
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("math") {
unsafe {
use libloading::Symbol;
if let Ok(free_fn) = plugin.library.get::<Symbol<unsafe extern "C" fn(*mut c_void)>>(b"nyash_random_free\0") {
free_fn(self.ptr);
}
}
}
}
}
}
}
unsafe impl Send for RandomBoxHandle {}
unsafe impl Sync for RandomBoxHandle {}
/// TimeBox native handle wrapper
#[derive(Debug)]
pub(crate) struct TimeBoxHandle { pub(crate) ptr: *mut c_void }
impl Drop for TimeBoxHandle {
fn drop(&mut self) {
#[cfg(feature = "dynamic-file")]
{
if !self.ptr.is_null() {
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("math") {
unsafe {
use libloading::Symbol;
if let Ok(free_fn) = plugin.library.get::<Symbol<unsafe extern "C" fn(*mut c_void)>>(b"nyash_time_free\0") {
free_fn(self.ptr);
}
}
}
}
}
}
}
unsafe impl Send for TimeBoxHandle {}
unsafe impl Sync for TimeBoxHandle {}
/// DateTimeBox native handle wrapper
#[derive(Debug)]
pub(crate) struct DateTimeBoxHandle { pub(crate) ptr: *mut c_void }
impl Drop for DateTimeBoxHandle {
fn drop(&mut self) {
#[cfg(feature = "dynamic-file")]
{
if !self.ptr.is_null() {
let cache = PLUGIN_CACHE.read().unwrap();
if let Some(plugin) = cache.get("math") {
unsafe {
use libloading::Symbol;
if let Ok(free_fn) = plugin.library.get::<Symbol<unsafe extern "C" fn(*mut c_void)>>(b"nyash_datetime_free\0") {
free_fn(self.ptr);
}
}
}
}
}
}
}
unsafe impl Send for DateTimeBoxHandle {}
unsafe impl Sync for DateTimeBoxHandle {}

View File

@ -19,6 +19,7 @@ mod builder_calls;
mod stmts;
mod ops;
mod utils;
mod exprs; // expression lowering split
// moved helpers to builder/utils.rs
@ -230,6 +231,12 @@ impl MirBuilder {
/// Build an expression and return its value ID
pub(super) fn build_expression(&mut self, ast: ASTNode) -> Result<ValueId, String> {
// Delegated to exprs.rs to keep this file lean
self.build_expression_impl(ast)
}
// Moved implementation to exprs.rs; keeping a small shim here improves readability
pub(super) fn build_expression_impl_legacy(&mut self, ast: ASTNode) -> Result<ValueId, String> {
match ast {
ASTNode::Literal { value, .. } => {
self.build_literal(value)
@ -616,7 +623,7 @@ impl MirBuilder {
}
/// Build a literal value
fn build_literal(&mut self, literal: LiteralValue) -> Result<ValueId, String> {
pub(super) fn build_literal(&mut self, literal: LiteralValue) -> Result<ValueId, String> {
// Determine type without moving literal
let ty_for_dst = match &literal {
LiteralValue::Integer(_) => Some(super::MirType::Integer),
@ -650,7 +657,7 @@ impl MirBuilder {
// build_unary_op moved to builder/ops.rs
/// Build variable access
fn build_variable_access(&mut self, name: String) -> Result<ValueId, String> {
pub(super) fn build_variable_access(&mut self, name: String) -> Result<ValueId, String> {
if let Some(&value_id) = self.variable_map.get(&name) {
Ok(value_id)
} else {
@ -659,7 +666,7 @@ impl MirBuilder {
}
/// Build assignment
fn build_assignment(&mut self, var_name: String, value: ASTNode) -> Result<ValueId, String> {
pub(super) fn build_assignment(&mut self, var_name: String, value: ASTNode) -> Result<ValueId, String> {
let value_id = self.build_expression(value)?;
// In SSA form, each assignment creates a new value
@ -738,7 +745,7 @@ impl MirBuilder {
/// Build static box (e.g., Main) - extracts main() method body and converts to Program
/// Also lowers other static methods into standalone MIR functions: BoxName.method/N
fn build_static_main_box(&mut self, box_name: String, methods: std::collections::HashMap<String, ASTNode>) -> Result<ValueId, String> {
pub(super) fn build_static_main_box(&mut self, box_name: String, methods: std::collections::HashMap<String, ASTNode>) -> Result<ValueId, String> {
// Lower other static methods (except main) to standalone MIR functions so JIT can see them
for (mname, mast) in methods.iter() {
if mname == "main" { continue; }
@ -792,7 +799,7 @@ impl MirBuilder {
}
/// Build field access: object.field
fn build_field_access(&mut self, object: ASTNode, field: String) -> Result<ValueId, String> {
pub(super) fn build_field_access(&mut self, object: ASTNode, field: String) -> Result<ValueId, String> {
// Clone the object before building expression if we need to check it later
let object_clone = object.clone();
@ -841,7 +848,7 @@ impl MirBuilder {
}
/// Build new expression: new ClassName(arguments)
fn build_new_expression(&mut self, class: String, arguments: Vec<ASTNode>) -> Result<ValueId, String> {
pub(super) fn build_new_expression(&mut self, class: String, arguments: Vec<ASTNode>) -> Result<ValueId, String> {
// Phase 9.78a: Unified Box creation using NewBox instruction
// Optimization: Primitive wrappers → emit Const directly when possible
@ -900,7 +907,7 @@ impl MirBuilder {
}
/// Build field assignment: object.field = value
fn build_field_assignment(&mut self, object: ASTNode, field: String, value: ASTNode) -> Result<ValueId, String> {
pub(super) fn build_field_assignment(&mut self, object: ASTNode, field: String, value: ASTNode) -> Result<ValueId, String> {
// Build the object and value expressions
let object_value = self.build_expression(object)?;
let mut value_result = self.build_expression(value)?;
@ -978,7 +985,7 @@ impl MirBuilder {
// lower_static_method_as_function_legacy removed (use builder_calls::lower_static_method_as_function)
/// Build box declaration: box Name { fields... methods... }
fn build_box_declaration(&mut self, name: String, methods: std::collections::HashMap<String, ASTNode>, fields: Vec<String>, weak_fields: Vec<String>) -> Result<(), String> {
pub(super) fn build_box_declaration(&mut self, name: String, methods: std::collections::HashMap<String, ASTNode>, fields: Vec<String>, weak_fields: Vec<String>) -> Result<(), String> {
// For Phase 8.4, we'll emit metadata instructions to register the box type
// In a full implementation, this would register type information for later use

244
src/mir/builder/exprs.rs Normal file
View File

@ -0,0 +1,244 @@
// Expression lowering split from builder.rs to keep files lean
use super::{MirInstruction, ConstValue, BasicBlockId, ValueId};
use crate::ast::{ASTNode, LiteralValue};
impl super::MirBuilder {
// Main expression dispatcher
pub(super) fn build_expression_impl(&mut self, ast: ASTNode) -> Result<ValueId, String> {
match ast {
ASTNode::Literal { value, .. } => self.build_literal(value),
ASTNode::BinaryOp { left, operator, right, .. } =>
self.build_binary_op(*left, operator, *right),
ASTNode::UnaryOp { operator, operand, .. } => {
let op_string = match operator {
crate::ast::UnaryOperator::Minus => "-".to_string(),
crate::ast::UnaryOperator::Not => "not".to_string(),
};
self.build_unary_op(op_string, *operand)
}
ASTNode::Variable { name, .. } => self.build_variable_access(name.clone()),
ASTNode::Me { .. } => self.build_me_expression(),
ASTNode::MethodCall { object, method, arguments, .. } => {
if (method == "is" || method == "as") && arguments.len() == 1 {
if let Some(type_name) = Self::extract_string_literal(&arguments[0]) {
let obj_val = self.build_expression_impl(*object.clone())?;
let ty = Self::parse_type_name_to_mir(&type_name);
let dst = self.value_gen.next();
let op = if method == "is" { crate::mir::TypeOpKind::Check } else { crate::mir::TypeOpKind::Cast };
self.emit_instruction(MirInstruction::TypeOp { dst, op, value: obj_val, ty })?;
return Ok(dst);
}
}
self.build_method_call(*object.clone(), method.clone(), arguments.clone())
}
ASTNode::FromCall { parent, method, arguments, .. } =>
self.build_from_expression(parent.clone(), method.clone(), arguments.clone()),
ASTNode::Assignment { target, value, .. } => {
if let ASTNode::FieldAccess { object, field, .. } = target.as_ref() {
self.build_field_assignment(*object.clone(), field.clone(), *value.clone())
} else if let ASTNode::Variable { name, .. } = target.as_ref() {
self.build_assignment(name.clone(), *value.clone())
} else {
Err("Complex assignment targets not yet supported".to_string())
}
}
ASTNode::FunctionCall { name, arguments, .. } =>
self.build_function_call(name.clone(), arguments.clone()),
ASTNode::Call { callee, arguments, .. } => {
let mut arg_ids: Vec<ValueId> = Vec::new();
let callee_id = self.build_expression_impl(*callee.clone())?;
for a in arguments { arg_ids.push(self.build_expression_impl(a)?); }
let dst = self.value_gen.next();
self.emit_instruction(MirInstruction::Call { dst: Some(dst), func: callee_id, args: arg_ids, effects: crate::mir::EffectMask::PURE })?;
Ok(dst)
}
ASTNode::QMarkPropagate { expression, .. } => {
let res_val = self.build_expression_impl(*expression.clone())?;
let ok_id = self.value_gen.next();
self.emit_instruction(MirInstruction::PluginInvoke { dst: Some(ok_id), box_val: res_val, method: "isOk".to_string(), args: vec![], effects: crate::mir::EffectMask::PURE })?;
let then_block = self.block_gen.next();
let else_block = self.block_gen.next();
self.emit_instruction(MirInstruction::Branch { condition: ok_id, then_bb: then_block, else_bb: else_block })?;
self.start_new_block(then_block)?;
self.emit_instruction(MirInstruction::Return { value: Some(res_val) })?;
self.start_new_block(else_block)?;
let val_id = self.value_gen.next();
self.emit_instruction(MirInstruction::PluginInvoke { dst: Some(val_id), box_val: res_val, method: "getValue".to_string(), args: vec![], effects: crate::mir::EffectMask::PURE })?;
Ok(val_id)
}
ASTNode::PeekExpr { scrutinee, arms, else_expr, .. } => {
let scr_val = self.build_expression_impl(*scrutinee.clone())?;
let merge_block: BasicBlockId = self.block_gen.next();
let result_val = self.value_gen.next();
let mut phi_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new();
let mut next_block = self.block_gen.next();
self.start_new_block(next_block)?;
for (i, (label, arm_expr)) in arms.iter().enumerate() {
let then_block = self.block_gen.next();
if let LiteralValue::String(ref s) = label {
let lit_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: lit_id, value: ConstValue::String(s.clone()) })?;
let cond_id = self.value_gen.next();
self.emit_instruction(crate::mir::MirInstruction::Compare { dst: cond_id, op: crate::mir::CompareOp::Eq, lhs: scr_val, rhs: lit_id })?;
self.emit_instruction(crate::mir::MirInstruction::Branch { condition: cond_id, then_bb: then_block, else_bb: next_block })?;
self.start_new_block(then_block)?;
let then_val = self.build_expression_impl(arm_expr.clone())?;
phi_inputs.push((then_block, then_val));
self.emit_instruction(crate::mir::MirInstruction::Jump { target: merge_block })?;
if i < arms.len() - 1 { let b = self.block_gen.next(); self.start_new_block(b)?; next_block = b; }
}
}
let else_block_id = next_block; self.start_new_block(else_block_id)?;
let else_val = self.build_expression_impl(*else_expr.clone())?;
phi_inputs.push((else_block_id, else_val));
self.emit_instruction(crate::mir::MirInstruction::Jump { target: merge_block })?;
self.start_new_block(merge_block)?;
self.emit_instruction(crate::mir::MirInstruction::Phi { dst: result_val, inputs: phi_inputs })?;
Ok(result_val)
}
ASTNode::Lambda { params, body, .. } => {
use std::collections::HashSet;
let mut used: HashSet<String> = HashSet::new();
let mut locals: HashSet<String> = HashSet::new(); for p in &params { locals.insert(p.clone()); }
fn collect_vars(ast: &ASTNode, used: &mut std::collections::HashSet<String>, locals: &mut std::collections::HashSet<String>) {
match ast {
ASTNode::Variable { name, .. } => { if !locals.contains(name) { used.insert(name.clone()); } }
ASTNode::Assignment { target, value, .. } => { collect_vars(target, used, locals); collect_vars(value, used, locals); }
ASTNode::BinaryOp { left, right, .. } => { collect_vars(left, used, locals); collect_vars(right, used, locals); }
ASTNode::UnaryOp { operand, .. } => { collect_vars(operand, used, locals); }
ASTNode::MethodCall { object, arguments, .. } => { collect_vars(object, used, locals); for a in arguments { collect_vars(a, used, locals); } }
ASTNode::FunctionCall { arguments, .. } => { for a in arguments { collect_vars(a, used, locals); } }
ASTNode::Call { callee, arguments, .. } => { collect_vars(callee, used, locals); for a in arguments { collect_vars(a, used, locals); } }
ASTNode::FieldAccess { object, .. } => { collect_vars(object, used, locals); }
ASTNode::New { arguments, .. } => { for a in arguments { collect_vars(a, used, locals); } }
ASTNode::If { condition, then_body, else_body, .. } => {
collect_vars(condition, used, locals);
for st in then_body { collect_vars(st, used, locals); }
if let Some(eb) = else_body { for st in eb { collect_vars(st, used, locals); } }
}
ASTNode::Loop { condition, body, .. } => { collect_vars(condition, used, locals); for st in body { collect_vars(st, used, locals); } }
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
for st in try_body { collect_vars(st, used, locals); }
for c in catch_clauses { for st in &c.body { collect_vars(st, used, locals); } }
if let Some(fb) = finally_body { for st in fb { collect_vars(st, used, locals); } }
}
ASTNode::Throw { expression, .. } => { collect_vars(expression, used, locals); }
ASTNode::Print { expression, .. } => { collect_vars(expression, used, locals); }
ASTNode::Return { value, .. } => { if let Some(v) = value { collect_vars(v, used, locals); } }
ASTNode::AwaitExpression { expression, .. } => { collect_vars(expression, used, locals); }
ASTNode::PeekExpr { scrutinee, arms, else_expr, .. } => {
collect_vars(scrutinee, used, locals);
for (_, e) in arms { collect_vars(e, used, locals); }
collect_vars(else_expr, used, locals);
}
ASTNode::Program { statements, .. } => { for st in statements { collect_vars(st, used, locals); } }
ASTNode::FunctionDeclaration { params, body, .. } => {
let mut inner = locals.clone();
for p in params { inner.insert(p.clone()); }
for st in body { collect_vars(st, used, &mut inner); }
}
_ => {}
}
}
for st in body.iter() { collect_vars(st, &mut used, &mut locals); }
let mut captures: Vec<(String, ValueId)> = Vec::new();
for name in used.into_iter() {
if let Some(&vid) = self.variable_map.get(&name) { captures.push((name, vid)); }
}
let me = self.variable_map.get("me").copied();
let dst = self.value_gen.next();
self.emit_instruction(MirInstruction::FunctionNew { dst, params: params.clone(), body: body.clone(), captures, me })?;
self.value_types.insert(dst, crate::mir::MirType::Box("FunctionBox".to_string()));
Ok(dst)
}
ASTNode::Return { value, .. } => self.build_return_statement(value.clone()),
ASTNode::Local { variables, initial_values, .. } =>
self.build_local_statement(variables.clone(), initial_values.clone()),
ASTNode::BoxDeclaration { name, methods, is_static, fields, constructors, weak_fields, .. } => {
if is_static && name == "Main" {
self.build_static_main_box(name.clone(), methods.clone())
} else {
self.user_defined_boxes.insert(name.clone());
self.build_box_declaration(name.clone(), methods.clone(), fields.clone(), weak_fields.clone())?;
for (ctor_key, ctor_ast) in constructors.clone() {
if let ASTNode::FunctionDeclaration { params, body, .. } = ctor_ast {
let func_name = format!("{}.{}", name, ctor_key);
self.lower_method_as_function(func_name, name.clone(), params.clone(), body.clone())?;
}
}
for (method_name, method_ast) in methods.clone() {
if let ASTNode::FunctionDeclaration { params, body, is_static, .. } = method_ast {
if !is_static {
let func_name = format!("{}.{}{}", name, method_name, format!("/{}", params.len()));
self.lower_method_as_function(func_name, name.clone(), params.clone(), body.clone())?;
}
}
}
let void_val = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: void_val, value: ConstValue::Void })?;
Ok(void_val)
}
}
ASTNode::FieldAccess { object, field, .. } =>
self.build_field_access(*object.clone(), field.clone()),
ASTNode::New { class, arguments, .. } =>
self.build_new_expression(class.clone(), arguments.clone()),
ASTNode::Nowait { variable, expression, .. } =>
self.build_nowait_statement(variable.clone(), *expression.clone()),
ASTNode::AwaitExpression { expression, .. } =>
self.build_await_expression(*expression.clone()),
ASTNode::Include { filename, .. } => {
let mut path = super::utils::resolve_include_path_builder(&filename);
if std::path::Path::new(&path).is_dir() {
path = format!("{}/index.nyash", path.trim_end_matches('/'));
} else if std::path::Path::new(&path).extension().is_none() {
path.push_str(".nyash");
}
if self.include_loading.contains(&path) {
return Err(format!("Circular include detected: {}", path));
}
if let Some(name) = self.include_box_map.get(&path).cloned() {
return self.build_new_expression(name, vec![]);
}
self.include_loading.insert(path.clone());
let content = std::fs::read_to_string(&path)
.map_err(|e| format!("Include read error '{}': {}", filename, e))?;
let included_ast = crate::parser::NyashParser::parse_from_string(&content)
.map_err(|e| format!("Include parse error '{}': {:?}", filename, e))?;
let mut box_name: Option<String> = None;
if let ASTNode::Program { statements, .. } = &included_ast {
for st in statements {
if let ASTNode::BoxDeclaration { name, is_static, .. } = st { if *is_static { box_name = Some(name.clone()); break; } }
}
}
let bname = box_name.ok_or_else(|| format!("Include target '{}' has no static box", filename))?;
let _ = self.build_expression_impl(included_ast)?;
self.include_loading.remove(&path);
self.include_box_map.insert(path.clone(), bname.clone());
self.build_new_expression(bname, vec![])
}
_ => Err(format!("Unsupported AST node type: {:?}", ast)),
}
}
}