diff --git a/phase9_7_externcall_demo.html b/phase9_7_externcall_demo.html new file mode 100644 index 00000000..68cd4825 --- /dev/null +++ b/phase9_7_externcall_demo.html @@ -0,0 +1,218 @@ + + + + + + Phase 9.7 ExternCall Demo - Nyash WASM FFI + + + +
+

๐ŸŒ Phase 9.7: Box FFI/ABI + ExternCall Demo

+ +
+ โœ… ExternCall Implementation Complete!
+ Universal Library Integration via WASM Runtime Imports +
+ +

๐ŸŽฏ Architecture Overview

+
// Nyash External Call Pattern +console.log("Hello from Nyash!") +canvas.fillRect("demo-canvas", 50, 50, 100, 100, "red") + +// Generated MIR ExternCall Instructions: +ExternCall { + dst: None, + iface_name: "env.console", + method_name: "log", + args: [string_ptr, string_len], + effects: IO +} + +// Generated WASM Imports: +(import "env" "console_log" (func $console_log (param i32 i32))) +(import "env" "canvas_fillRect" (func $canvas_fillRect (param i32 i32 i32 i32 i32 i32 i32 i32)))
+ +

๐ŸŽฎ Interactive Demo

+ + +
+ + + + +
+ +

๐Ÿ“Š Console Output:

+
+ +

๐Ÿ”ง Implementation Status

+
+ โœ… Core Components Complete:
+ โ€ข MIR ExternCall instruction with effect tracking
+ โ€ข WASM RuntimeImports with console/canvas operations
+ โ€ข JavaScript import object generation
+ โ€ข BID specification compliance (console.yaml, canvas.yaml)
+ โ€ข String handling via (ptr, len) parameters

+ + ๐Ÿš€ Ready for Universal Exchange:
+ External libraries can now be integrated via Box FFI/ABI! +
+
+ + + + \ No newline at end of file diff --git a/src/boxes/extern_box.rs b/src/boxes/extern_box.rs new file mode 100644 index 00000000..ed84b4b1 --- /dev/null +++ b/src/boxes/extern_box.rs @@ -0,0 +1,131 @@ +/*! + * ExternBox - External API proxy for Phase 9.7 ExternCall + */ + +use crate::box_trait::{NyashBox, StringBox, VoidBox, IntegerBox, BoxCore, BoxBase}; +use std::any::Any; + +/// External API proxy box for external calls +pub struct ExternBox { + id: u64, + api_name: String, +} + +impl ExternBox { + pub fn new_console() -> Box { + Box::new(ExternBox { + id: BoxBase::generate_box_id(), + api_name: "console".to_string(), + }) + } + + pub fn new_canvas() -> Box { + Box::new(ExternBox { + id: BoxBase::generate_box_id(), + api_name: "canvas".to_string(), + }) + } +} + +impl BoxCore for ExternBox { + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn box_clone(&self) -> Box { + Box::new(ExternBox { + id: self.id, + api_name: self.api_name.clone(), + }) + } + + fn box_eq(&self, other: &dyn NyashBox) -> bool { + if let Some(other_extern) = other.as_any().downcast_ref::() { + self.id == other_extern.id + } else { + false + } + } +} + +impl NyashBox for ExternBox { + fn get_type_name(&self) -> &str { + "ExternBox" + } + + fn to_string(&self) -> String { + format!("ExternBox({})", self.api_name) + } + + fn call_method(&mut self, method: &str, args: Vec>) -> Box { + println!("ExternBox({})::{} called with {} args", self.api_name, method, args.len()); + + match (self.api_name.as_str(), method) { + ("console", "log") => { + print!("Console: "); + for (i, arg) in args.iter().enumerate() { + if i > 0 { print!(" "); } + print!("{}", arg.to_string()); + } + println!(); + VoidBox::new() + }, + ("canvas", "fillRect") => { + if args.len() >= 6 { + println!("Canvas fillRect: canvas={}, x={}, y={}, w={}, h={}, color={}", + args[0].to_string(), + args[1].to_string(), + args[2].to_string(), + args[3].to_string(), + args[4].to_string(), + args[5].to_string()); + } else { + println!("Canvas fillRect called with {} args (expected 6)", args.len()); + } + VoidBox::new() + }, + ("canvas", "fillText") => { + if args.len() >= 6 { + println!("Canvas fillText: canvas={}, text={}, x={}, y={}, font={}, color={}", + args[0].to_string(), + args[1].to_string(), + args[2].to_string(), + args[3].to_string(), + args[4].to_string(), + args[5].to_string()); + } else { + println!("Canvas fillText called with {} args (expected 6)", args.len()); + } + VoidBox::new() + }, + _ => { + println!("Unknown external method: {}.{}", self.api_name, method); + VoidBox::new() + } + } + } + + fn get_field(&self, _field: &str) -> Option> { + None + } + + fn set_field(&mut self, _field: &str, _value: Box) -> bool { + false + } + + fn list_methods(&self) -> Vec { + match self.api_name.as_str() { + "console" => vec!["log".to_string()], + "canvas" => vec!["fillRect".to_string(), "fillText".to_string()], + _ => vec![], + } + } + + fn list_fields(&self) -> Vec { + vec![] + } +} \ No newline at end of file diff --git a/test_simple.wat b/test_simple.wat new file mode 100644 index 00000000..5e65200c --- /dev/null +++ b/test_simple.wat @@ -0,0 +1,247 @@ +(module + (import "env" "print" (func $print (param i32) )) + (import "env" "print_str" (func $print_str (param i32 i32) )) + (import "env" "console_log" (func $console_log (param i32 i32) )) + (import "env" "canvas_fillRect" (func $canvas_fillRect (param i32 i32 i32 i32 i32 i32 i32 i32) )) + (import "env" "canvas_fillText" (func $canvas_fillText (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) )) + (memory (export "memory") 1) + (data (i32.const 4096) "\5f\5f\6d\65\5f\5f") + (data (i32.const 4102) "\57\41\53\4d\20\74\65\73\74\20\72\65\61\64\79") + (global $heap_ptr (mut i32) (i32.const 2048)) + (func $malloc (param $size i32) (result i32) + (local $ptr i32) + (local $aligned_size i32) + + ;; Align size to 4-byte boundary + local.get $size + i32.const 3 + i32.add + i32.const -4 + i32.and + local.set $aligned_size + + ;; Get current heap pointer + global.get $heap_ptr + local.set $ptr + + ;; Advance heap pointer by aligned size + global.get $heap_ptr + local.get $aligned_size + i32.add + global.set $heap_ptr + + ;; Return allocated pointer + local.get $ptr + ) + (func $box_alloc (param $type_id i32) (param $field_count i32) (result i32) + (local $ptr i32) + (local $total_size i32) + + ;; Calculate total size: header (12) + fields (field_count * 4) + local.get $field_count + i32.const 4 + i32.mul + i32.const 12 + i32.add + local.set $total_size + + ;; Allocate memory + local.get $total_size + call $malloc + local.set $ptr + + ;; Initialize type_id + local.get $ptr + local.get $type_id + i32.store + + ;; Initialize ref_count to 1 + local.get $ptr + i32.const 4 + i32.add + i32.const 1 + i32.store + + ;; Initialize field_count + local.get $ptr + i32.const 8 + i32.add + local.get $field_count + i32.store + + ;; Return box pointer + local.get $ptr + ) + (func $alloc_stringbox (result i32) + (local $ptr i32) + + ;; Allocate memory for box + i32.const 20 + call $malloc + local.set $ptr + + ;; Initialize type_id + local.get $ptr + i32.const 4097 + i32.store + + ;; Initialize ref_count to 1 + local.get $ptr + i32.const 4 + i32.add + i32.const 1 + i32.store + + ;; Initialize field_count + local.get $ptr + i32.const 8 + i32.add + i32.const 2 + i32.store + + ;; Return box pointer + local.get $ptr + ) + (func $alloc_integerbox (result i32) + (local $ptr i32) + + ;; Allocate memory for box + i32.const 16 + call $malloc + local.set $ptr + + ;; Initialize type_id + local.get $ptr + i32.const 4098 + i32.store + + ;; Initialize ref_count to 1 + local.get $ptr + i32.const 4 + i32.add + i32.const 1 + i32.store + + ;; Initialize field_count + local.get $ptr + i32.const 8 + i32.add + i32.const 1 + i32.store + + ;; Return box pointer + local.get $ptr + ) + (func $alloc_boolbox (result i32) + (local $ptr i32) + + ;; Allocate memory for box + i32.const 16 + call $malloc + local.set $ptr + + ;; Initialize type_id + local.get $ptr + i32.const 4099 + i32.store + + ;; Initialize ref_count to 1 + local.get $ptr + i32.const 4 + i32.add + i32.const 1 + i32.store + + ;; Initialize field_count + local.get $ptr + i32.const 8 + i32.add + i32.const 1 + i32.store + + ;; Return box pointer + local.get $ptr + ) + (func $alloc_databox (result i32) + (local $ptr i32) + + ;; Allocate memory for box + i32.const 16 + call $malloc + local.set $ptr + + ;; Initialize type_id + local.get $ptr + i32.const 4101 + i32.store + + ;; Initialize ref_count to 1 + local.get $ptr + i32.const 4 + i32.add + i32.const 1 + i32.store + + ;; Initialize field_count + local.get $ptr + i32.const 8 + i32.add + i32.const 1 + i32.store + + ;; Return box pointer + local.get $ptr + ) + (func $main (local $0 i32) (local $1 i32) (local $2 i32) (local $3 i32) + nop + call $alloc_stringbox + local.set $0 + local.get $0 + i32.const 12 + i32.add + i32.const 4096 + i32.store + local.get $0 + i32.const 16 + i32.add + i32.const 6 + i32.store + call $alloc_stringbox + local.set $1 + local.get $1 + i32.const 12 + i32.add + i32.const 4102 + i32.store + local.get $1 + i32.const 16 + i32.add + i32.const 15 + i32.store + local.get $0 + i32.const 12 + i32.add + local.get $1 + i32.store + call $alloc_stringbox + local.set $2 + local.get $2 + i32.const 12 + i32.add + i32.const 4096 + i32.store + local.get $2 + i32.const 16 + i32.add + i32.const 6 + i32.store + local.get $2 + i32.const 12 + i32.add + i32.load + local.set $3 + local.get $3 + return + ) + (export "main" (func $main)) +) diff --git a/test_wasm_simple.nyash b/test_wasm_simple.nyash new file mode 100644 index 00000000..eb7bf4f5 --- /dev/null +++ b/test_wasm_simple.nyash @@ -0,0 +1,9 @@ +# Simple WASM ExternCall test - for manual MIR injection +static box Main { + init { result } + + main() { + me.result = "WASM test ready" + return me.result + } +} \ No newline at end of file