#[macro_use] mod utils; mod types; use ic10emu::grammar::{LogicType, SlotLogicType}; use serde::{Deserialize, Serialize}; use types::{Registers, Stack}; use std::{cell::RefCell, rc::Rc, str::FromStr}; use itertools::Itertools; // use itertools::Itertools; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { fn alert(s: &str); } #[wasm_bindgen] pub struct DeviceRef { device: Rc>, vm: Rc>, } use thiserror::Error; #[derive(Error, Debug, Serialize, Deserialize)] pub enum BindingError { #[error("{0} is not a valid variant")] InvalidEnumVariant(String), #[error("Index {0} is out of range {1}")] OutOfBounds(usize, usize), } #[wasm_bindgen] impl DeviceRef { fn from_device(device: Rc>, vm: Rc>) -> Self { DeviceRef { device, vm } } #[wasm_bindgen(getter)] pub fn id(&self) -> u32 { self.device.borrow().id } #[wasm_bindgen(getter)] pub fn ic(&self) -> Option { self.device.borrow().ic } #[wasm_bindgen(getter)] pub fn name(&self) -> Option { self.device.borrow().name.clone() } #[wasm_bindgen(getter, js_name = "nameHash")] pub fn name_hash(&self) -> Option { self.device.borrow().name_hash } #[wasm_bindgen(getter, js_name = "prefabName")] pub fn prefab_name(&self) -> Option { self.device.borrow().prefab_name.clone() } #[wasm_bindgen(getter, js_name = "prefabHash")] pub fn prefab_hash(&self) -> Option { self.device.borrow().prefab_hash } #[wasm_bindgen(getter, skip_typescript)] pub fn fields(&self) -> JsValue { serde_wasm_bindgen::to_value(&self.device.borrow().get_fields(&self.vm.borrow())).unwrap() } #[wasm_bindgen(getter, skip_typescript)] pub fn slots(&self) -> Vec { self.device .borrow() .slots .iter() .map(|slot| { let flat_slot: types::Slot = slot.into(); serde_wasm_bindgen::to_value(&flat_slot).unwrap() }) .collect_vec() } #[wasm_bindgen(getter, skip_typescript)] pub fn reagents(&self) -> JsValue { serde_wasm_bindgen::to_value(&self.device.borrow().reagents).unwrap() } #[wasm_bindgen(getter, skip_typescript)] pub fn connections(&self) -> JsValue { serde_wasm_bindgen::to_value(&self.device.borrow().connections).unwrap() } #[wasm_bindgen(getter, js_name = "ip")] pub fn ic_ip(&self) -> Option { self.device.borrow().ic.as_ref().and_then(|ic| { self.vm .borrow() .ics .get(ic) .map(|ic| ic.as_ref().borrow().ip) }) } #[wasm_bindgen(getter, js_name = "instructionCount")] pub fn ic_instruction_count(&self) -> Option { self.device.borrow().ic.as_ref().and_then(|ic| { self.vm .borrow() .ics .get(ic) .map(|ic| ic.as_ref().borrow().ic) }) } #[wasm_bindgen(getter, js_name = "stack")] pub fn ic_stack(&self) -> Option { self.device.borrow().ic.as_ref().and_then(|ic| { self.vm .borrow() .ics .get(ic) .map(|ic| Stack(ic.as_ref().borrow().stack)) }) } #[wasm_bindgen(getter, js_name = "registers")] pub fn ic_registers(&self) -> Option { self.device.borrow().ic.as_ref().and_then(|ic| { self.vm .borrow() .ics .get(ic) .map(|ic| Registers(ic.as_ref().borrow().registers)) }) } #[wasm_bindgen(getter, js_name = "aliases", skip_typescript)] pub fn ic_aliases(&self) -> JsValue { serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| { self.vm .borrow() .ics .get(ic) .map(|ic| ic.as_ref().borrow().aliases.clone()) })) .unwrap() } #[wasm_bindgen(getter, js_name = "defines", skip_typescript)] pub fn ic_defines(&self) -> JsValue { serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| { self.vm .borrow() .ics .get(ic) .map(|ic| ic.as_ref().borrow().defines.clone()) })) .unwrap() } #[wasm_bindgen(getter, js_name = "pins", skip_typescript)] pub fn ic_pins(&self) -> JsValue { serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| { self.vm .borrow() .ics .get(ic) .map(|ic| ic.as_ref().borrow().pins) })) .unwrap() } #[wasm_bindgen(getter, js_name = "state")] pub fn ic_state(&self) -> Option { self.device .borrow() .ic .as_ref() .and_then(|ic| { self.vm .borrow() .ics .get(ic) .map(|ic| ic.borrow().state.clone()) }) .map(|state| state.to_string()) } #[wasm_bindgen(getter, js_name = "program", skip_typescript)] pub fn ic_program(&self) -> JsValue { serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| { self.vm .borrow() .ics .get(ic) .map(|ic| ic.borrow().program.clone()) })) .unwrap() } #[wasm_bindgen(js_name = "step")] pub fn step_ic(&self, advance_ip_on_err: bool) -> Result { let id = self.device.borrow().id; Ok(self.vm.borrow().step_ic(id, advance_ip_on_err)?) } #[wasm_bindgen(js_name = "run")] pub fn run_ic(&self, ignore_errors: bool) -> Result { let id = self.device.borrow().id; Ok(self.vm.borrow().run_ic(id, ignore_errors)?) } #[wasm_bindgen(js_name = "reset")] pub fn reset_ic(&self) -> Result { let id = self.device.borrow().id; Ok(self.vm.borrow().reset_ic(id)?) } #[wasm_bindgen(js_name = "setCode")] /// Set program code if it's valid pub fn set_code(&self, code: &str) -> Result { let id = self.device.borrow().id; Ok(self.vm.borrow().set_code(id, code)?) } #[wasm_bindgen(js_name = "setCodeInvalid")] /// Set program code and translate invalid lines to Nop, collecting errors pub fn set_code_invlaid(&self, code: &str) -> Result { let id = self.device.borrow().id; Ok(self.vm.borrow().set_code_invalid(id, code)?) } #[wasm_bindgen(js_name = "setRegister")] pub fn ic_set_register(&self, index: u32, val: f64) -> Result { let ic_id = *self .device .borrow() .ic .as_ref() .ok_or(ic10emu::VMError::NoIC(self.device.borrow().id))?; let vm_borrow = self.vm.borrow(); let ic = vm_borrow .ics .get(&ic_id) .ok_or(ic10emu::VMError::NoIC(self.device.borrow().id))?; let result = ic.borrow_mut().set_register(0, index, val)?; Ok(result) } #[wasm_bindgen(js_name = "setStack")] pub fn ic_set_stack(&self, address: f64, val: f64) -> Result { let ic_id = *self .device .borrow() .ic .as_ref() .ok_or(ic10emu::VMError::NoIC(self.device.borrow().id))?; let vm_borrow = self.vm.borrow(); let ic = vm_borrow .ics .get(&ic_id) .ok_or(ic10emu::VMError::NoIC(self.device.borrow().id))?; let result = ic.borrow_mut().poke(address, val)?; Ok(result) } #[wasm_bindgen(js_name = "setName")] pub fn set_name(&self, name: &str) { self.device.borrow_mut().set_name(name) } #[wasm_bindgen(js_name = "setField")] pub fn set_field(&self, field: &str, value: f64) -> Result<(), JsError> { let logic_typ = LogicType::from_str(field)?; let mut device_ref = self.device.borrow_mut(); device_ref.set_field(logic_typ, value, &self.vm.borrow())?; Ok(()) } #[wasm_bindgen(js_name = "setSlotField")] pub fn set_slot_field(&self, slot: f64, field: &str, value: f64) -> Result<(), JsError> { let logic_typ = SlotLogicType::from_str(field)?; let mut device_ref = self.device.borrow_mut(); device_ref.set_slot_field(slot, logic_typ, value, &self.vm.borrow())?; Ok(()) } #[wasm_bindgen(js_name = "getSlotField")] pub fn get_slot_field(&self, slot: f64, field: &str) -> Result { let logic_typ = SlotLogicType::from_str(field)?; let device_ref = self.device.borrow_mut(); Ok(device_ref.get_slot_field(slot, logic_typ, &self.vm.borrow())?) } #[wasm_bindgen(js_name = "getSlotFields", skip_typescript)] pub fn get_slot_fields(&self, slot: f64) -> Result { let device_ref = self.device.borrow_mut(); let fields = device_ref.get_slot_fields(slot, &self.vm.borrow())?; Ok(serde_wasm_bindgen::to_value(&fields).unwrap()) } #[wasm_bindgen(js_name = "setConnection")] pub fn set_connection(&self, conn: usize, net: Option) -> Result<(), JsError> { let device_id = self.device.borrow().id; self.vm .borrow() .set_device_connection(device_id, conn, net)?; Ok(()) } #[wasm_bindgen(js_name = "removeDeviceFromNetwork")] pub fn remove_device_from_network(&self, network_id: u32) -> Result { let id = self.device.borrow().id; Ok(self .vm .borrow() .remove_device_from_network(id, network_id)?) } #[wasm_bindgen(js_name = "setPin")] pub fn set_pin(&self, pin: usize, val: Option) -> Result { let id = self.device.borrow().id; Ok(self.vm.borrow().set_pin(id, pin, val)?) } } #[wasm_bindgen] #[derive(Debug)] pub struct VM { vm: Rc>, } #[wasm_bindgen] impl VM { #[wasm_bindgen(constructor)] pub fn new() -> Self { VM { vm: Rc::new(RefCell::new(ic10emu::VM::new())), } } #[wasm_bindgen(js_name = "addDevice")] pub fn add_device(&self, network: Option) -> Result { Ok(self.vm.borrow_mut().add_device(network)?) } #[wasm_bindgen(js_name = "getDevice")] pub fn get_device(&self, id: u32) -> Option { let device = self.vm.borrow().get_device(id); device.map(|d| DeviceRef::from_device(d.clone(), self.vm.clone())) } #[wasm_bindgen(js_name = "setCode")] /// Set program code if it's valid pub fn set_code(&self, id: u32, code: &str) -> Result { Ok(self.vm.borrow().set_code(id, code)?) } #[wasm_bindgen(js_name = "setCodeInvalid")] /// Set program code and translate invalid lines to Nop, collecting errors pub fn set_code_invalid(&self, id: u32, code: &str) -> Result { Ok(self.vm.borrow().set_code_invalid(id, code)?) } #[wasm_bindgen(js_name = "stepIC")] pub fn step_ic(&self, id: u32, advance_ip_on_err: bool) -> Result { Ok(self.vm.borrow().step_ic(id, advance_ip_on_err)?) } #[wasm_bindgen(js_name = "runIC")] pub fn run_ic(&self, id: u32, ignore_errors: bool) -> Result { Ok(self.vm.borrow().run_ic(id, ignore_errors)?) } #[wasm_bindgen(js_name = "resetIC")] pub fn reset_ic(&self, id: u32) -> Result { Ok(self.vm.borrow().reset_ic(id)?) } #[wasm_bindgen(getter, js_name = "defaultNetwork")] pub fn default_network(&self) -> u32 { self.vm.borrow().default_network } #[wasm_bindgen(getter)] pub fn devices(&self) -> Vec { self.vm.borrow().devices.keys().copied().collect_vec() } #[wasm_bindgen(getter)] pub fn networks(&self) -> Vec { self.vm.borrow().networks.keys().copied().collect_vec() } #[wasm_bindgen(getter)] pub fn ics(&self) -> Vec { self.vm.borrow().ics.keys().copied().collect_vec() } #[wasm_bindgen(getter, js_name = "lastOperationModified")] pub fn last_operation_modified(&self) -> Vec { self.vm.borrow().last_operation_modified() } #[wasm_bindgen(js_name = "visibleDevices")] pub fn visible_devices(&self, source: u32) -> Vec { self.vm.borrow().visible_devices(source) } #[wasm_bindgen(js_name = "setDeviceConnection")] pub fn set_device_connection( &self, id: u32, connection: usize, network_id: Option, ) -> Result { Ok(self .vm .borrow() .set_device_connection(id, connection, network_id)?) } #[wasm_bindgen(js_name = "removeDeviceFromNetwork")] pub fn remove_device_from_network(&self, id: u32, network_id: u32) -> Result { Ok(self .vm .borrow() .remove_device_from_network(id, network_id)?) } #[wasm_bindgen(js_name = "setPin")] pub fn set_pin(&self, id: u32, pin: usize, val: Option) -> Result { Ok(self.vm.borrow().set_pin(id, pin, val)?) } } impl Default for VM { fn default() -> Self { Self::new() } } #[wasm_bindgen] pub fn init() -> VM { utils::set_panic_hook(); let vm = VM::new(); log!("Hello from ic10emu!"); vm }