[−][src]Trait wlambda::vval::VValUserData
You can implement your own VVal data type and provide it via global functions:
use std::rc::Rc; use std::cell::RefCell; use wlambda::vval::Env; use wlambda::{VVal, GlobalEnv, StackAction}; #[derive(Clone, Debug)] struct MyType { x: Rc<RefCell<(i64, i64)>>, } impl wlambda::vval::VValUserData for MyType { fn s(&self) -> String { format!("$<MyType({:?})>", self.x.borrow()) } fn i(&self) -> i64 { self.x.borrow_mut().1 } fn get_key(&self, key: &str) -> Option<VVal> { Some(VVal::new_str(key)) } fn call(&self, args: &[VVal]) -> Result<VVal, StackAction> { if args.len() < 0 { return Err(StackAction::panic_msg( format!("{} called with too few arguments", self.s()))); } Ok(args[0].clone()) } fn as_any(&mut self) -> &mut dyn std::any::Any { self } fn clone_ud(&self) -> Box<dyn wlambda::vval::VValUserData> { Box::new(self.clone()) } } let global_env = GlobalEnv::new_default(); global_env.borrow_mut().add_func( "new_mytype", |_env: &mut Env, _argc: usize| { Ok(VVal::Usr(Box::new(MyType { x: Rc::new(RefCell::new((13, 42))) }))) }, Some(0), Some(0)); global_env.borrow_mut().add_func( "modify_mytype", |env: &mut Env, _argc: usize| { Ok(if let VVal::Usr(mut u) = env.arg(0) { if let Some(ud) = u.as_any().downcast_mut::<MyType>() { ud.x.borrow_mut().0 += 1; ud.x.borrow_mut().1 *= 2; VVal::Int(ud.x.borrow().0 + ud.x.borrow().1) } else { VVal::Nul } } else { VVal::Nul }) }, Some(1), Some(1)); let mut ctx = wlambda::compiler::EvalContext::new(global_env); let r = &mut ctx.eval(r#" !x = new_mytype[]; !i = modify_mytype x; $[i, x] "#).unwrap(); assert_eq!( r.s(), "$[98,$<MyType((14, 84))>]", "Userdata implementation works");
Sometimes, if your UserData is a Rc<RefCell<...>>, it can pay off defining some wrapper and From/Into traits for easier handling:
Here an example from a game I worked on:
use wlambda::vval::{VVal, StackAction, VValUserData}; use std::rc::Rc; use std::cell::RefCell; #[derive(Clone)] struct Ship { } #[derive(Clone)] struct ShipWlWrapper(Rc<RefCell<Ship>>); impl From<Rc<RefCell<Ship>>> for ShipWlWrapper { fn from(r: Rc<RefCell<Ship>>) -> ShipWlWrapper { ShipWlWrapper(r) } } impl Into<VVal> for ShipWlWrapper { fn into(self) -> VVal { VVal::Usr(Box::new(self)) } } impl VValUserData for ShipWlWrapper { // ... fn as_any(&mut self) -> &mut dyn std::any::Any { self } fn clone_ud(&self) -> Box<dyn wlambda::vval::VValUserData> { Box::new(self.clone()) } }
Required methods
fn clone_ud(&self) -> Box<dyn VValUserData>
Should clone your user data instance. Whether you are doing a deep clone or a shallow cloen or something else is up to you.
fn as_any(&mut self) -> &mut dyn Any
This should be implemented simply by returning a mutable reference to the concrete type self. It allows you to access your data structure from inside a function yourself.
This is a good default implementation for your struct/type:
fn as_any(&mut self) -> &mut dyn std::any::Any { self }
Provided methods
fn s(&self) -> String
This method should return a human readable syntax representation of your VValUserData.
fn s_raw(&self) -> String
If your data has a plain string representation, you can return the string directly from here.
fn i(&self) -> i64
Returns the i64 representation of your data.
fn f(&self) -> f64
Returns the f64 representation of your data.
fn b(&self) -> bool
Returns the boolean representation of your data. Can for instance be used to check if your data is valid or something.
fn eqv(&self, _other: &Box<dyn VValUserData>) -> bool
Allows you to specify how two instances of your data should be compared for equivalentness.
fn set_key(&self, _key: &VVal, _val: VVal)
Makes your user data act like a map. This can be useful for implementing your own registries or data structures. Implement this method for setting a key to a value.
fn get_key(&self, _key: &str) -> Option<VVal>
This method returns some value that your user data associates with the given key.
fn call(&self, _args: &[VVal]) -> Result<VVal, StackAction>
This method is called when the user data is called.