use std::cell::RefCell;
use std::collections::HashMap;
use std::io;
use std::rc::Rc;
use async_channel::{Receiver, Sender, TryRecvError};
use async_trait::async_trait;
use endbasic_core::{
CallError, Callable, CallableMetadata, Compiler, CompilerError, GlobalDef, Image, LineCol,
StopReason, SymbolKey, Vm,
};
pub mod arrays;
pub mod console;
pub mod data;
pub mod exec;
pub mod gfx;
pub mod gpio;
pub mod help;
pub mod numerics;
pub mod program;
pub mod spi;
pub mod storage;
pub mod strings;
pub mod testutils;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{0}")]
CallError(#[from] CallError),
#[error("{0}")]
CompilerError(#[from] CompilerError),
#[error("{0}")]
IoError(#[from] io::Error),
#[error("{0}: {1}")]
RuntimeError(LineCol, String),
#[error("Break")]
Break,
}
pub type Result<T> = std::result::Result<T, Error>;
pub trait Clearable {
fn reset_state(&self);
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum MachineAction {
Clear,
Run(String),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Signal {
Break,
}
#[async_trait(?Send)]
pub trait Yielder {
async fn yield_now(&mut self);
}
pub struct Machine {
compiler: Compiler,
image: Image,
vm: Vm,
callables: HashMap<SymbolKey, Rc<dyn Callable>>,
clearables: Vec<Box<dyn Clearable>>,
actions: Rc<RefCell<Vec<MachineAction>>>,
global_defs: Vec<GlobalDef>,
console: Rc<RefCell<dyn console::Console>>,
yielder: Option<Box<dyn Yielder>>,
signals_chan: (Sender<Signal>, Receiver<Signal>),
}
impl Machine {
pub fn clear(&mut self) {
for clearable in self.clearables.as_slice() {
clearable.reset_state();
}
self.vm.reset();
self.compiler = Compiler::new(&self.callables, &self.global_defs)
.expect("Compiler creation succeeded during Machine init; must also succeed here");
self.image = Image::default();
}
fn run(&mut self, program: String) -> Result<()> {
self.clear();
self.compile(&mut program.as_bytes())
}
fn clear_actions(&mut self) {
self.actions.borrow_mut().clear();
}
fn drain_actions(&mut self) -> Result<bool> {
let actions: Vec<MachineAction> = self.actions.borrow_mut().drain(..).collect();
let mut running_stored_program = false;
for action in actions {
match action {
MachineAction::Clear => self.clear(),
MachineAction::Run(program) => {
self.run(program)?;
running_stored_program = true;
}
}
}
Ok(running_stored_program)
}
pub fn drain_signals(&mut self) {
while self.signals_chan.1.try_recv().is_ok() {
}
}
fn should_stop(&mut self) -> bool {
match self.signals_chan.1.try_recv() {
Ok(Signal::Break) => true,
Err(TryRecvError::Empty) => false,
Err(TryRecvError::Closed) => false,
}
}
async fn should_stop_after_yield(&mut self) -> bool {
if let Some(yielder) = self.yielder.as_mut() {
yielder.yield_now().await;
}
self.should_stop()
}
pub fn compile(&mut self, input: &mut dyn io::Read) -> Result<()> {
self.compiler.compile_more(&mut self.image, input)?;
Ok(())
}
pub async fn exec(&mut self) -> Result<Option<i32>> {
let mut running_stored_program = false;
let result = loop {
match self.vm.exec(&self.image) {
StopReason::Eof => {
break Ok(None);
}
StopReason::End(code) => {
if !running_stored_program {
break Ok(Some(code.to_i32()));
}
if !code.is_success() {
self.console
.borrow_mut()
.print(&format!("Program exited with code {}", code.to_i32()))?;
}
break Ok(None);
}
StopReason::Exception(pos, msg) => {
break Err(Error::RuntimeError(pos, msg));
}
StopReason::UpcallAsync(handler) => {
let upcall_result = handler.invoke().await;
if self.should_stop() {
self.clear_actions();
self.vm.interrupt(&self.image);
break Err(Error::Break);
}
if let Err(e) = upcall_result {
self.clear_actions();
let (pos, message) = e.parts();
break Err(Error::RuntimeError(pos, message));
}
if self.drain_actions()? {
running_stored_program = true;
}
}
StopReason::Yield => {
if self.should_stop_after_yield().await {
self.vm.interrupt(&self.image);
break Err(Error::Break);
}
}
}
};
if running_stored_program {
self.vm.clear_error_handler();
}
result
}
}
#[derive(Default)]
pub struct MachineBuilder {
callables: HashMap<SymbolKey, Rc<dyn Callable>>,
callables_metadata: Rc<RefCell<HashMap<SymbolKey, Rc<CallableMetadata>>>>,
clearables: Vec<Box<dyn Clearable>>,
console: Option<Rc<RefCell<dyn console::Console>>>,
gpio_pins: Option<Rc<RefCell<dyn gpio::Pins>>>,
sleep_fn: Option<exec::SleepFn>,
actions: Rc<RefCell<Vec<MachineAction>>>,
yielder: Option<Box<dyn Yielder>>,
signals_chan: Option<(Sender<Signal>, Receiver<Signal>)>,
global_defs: Vec<GlobalDef>,
}
impl MachineBuilder {
pub fn actions(&self) -> Rc<RefCell<Vec<MachineAction>>> {
self.actions.clone()
}
pub fn add_callable(&mut self, callable: Rc<dyn Callable>) {
let metadata = callable.metadata();
let key = SymbolKey::from(metadata.name());
let previous = self.callables.insert(key.clone(), callable);
debug_assert!(previous.is_none(), "Cannot insert a callable twice");
let previous = self.callables_metadata.borrow_mut().insert(key, metadata);
debug_assert!(previous.is_none(), "Cannot insert callable metadata twice");
}
pub fn callables_metadata(&self) -> Rc<RefCell<HashMap<SymbolKey, Rc<CallableMetadata>>>> {
self.callables_metadata.clone()
}
pub fn add_clearable(&mut self, clearable: Box<dyn Clearable>) {
self.clearables.push(clearable);
}
pub fn with_console(mut self, console: Rc<RefCell<dyn console::Console>>) -> Self {
self.console = Some(console);
self
}
pub fn with_globals(mut self, defs: Vec<GlobalDef>) -> Self {
self.global_defs.extend(defs);
self
}
pub fn with_gpio_pins(mut self, pins: Rc<RefCell<dyn gpio::Pins>>) -> Self {
self.gpio_pins = Some(pins);
self
}
pub fn with_sleep_fn(mut self, sleep_fn: exec::SleepFn) -> Self {
self.sleep_fn = Some(sleep_fn);
self
}
pub fn with_yielder(mut self, yielder: Box<dyn Yielder>) -> Self {
self.yielder = Some(yielder);
self
}
pub fn with_signals_chan(mut self, chan: (Sender<Signal>, Receiver<Signal>)) -> Self {
self.signals_chan = Some(chan);
self
}
pub fn get_console(&mut self) -> Rc<RefCell<dyn console::Console>> {
if self.console.is_none() {
self.console = Some(Rc::from(RefCell::from(console::TrivialConsole::default())));
}
self.console.clone().unwrap()
}
fn get_gpio_pins(&mut self) -> Rc<RefCell<dyn gpio::Pins>> {
if self.gpio_pins.is_none() {
self.gpio_pins = Some(Rc::from(RefCell::from(gpio::NoopPins::default())))
}
self.gpio_pins.as_ref().expect("Must have been initialized above").clone()
}
pub fn build(mut self) -> Machine {
let console = self.get_console();
let gpio_pins = self.get_gpio_pins();
let signals_chan = match self.signals_chan.take() {
Some(pair) => pair,
None => async_channel::unbounded(),
};
arrays::add_all(&mut self);
console::add_all(&mut self, console.clone());
gfx::add_all(&mut self, console.clone());
data::add_all(&mut self);
gpio::add_all(&mut self, gpio_pins);
let sleep_fn = self.sleep_fn.take();
exec::add_scripting(&mut self, sleep_fn);
numerics::add_all(&mut self);
strings::add_all(&mut self);
Machine {
compiler: Compiler::new(&self.callables, &self.global_defs)
.expect("Injected globals must be valid"),
image: Image::default(),
vm: Vm::new(self.callables.clone()),
callables: self.callables,
clearables: self.clearables,
actions: self.actions.clone(),
global_defs: self.global_defs.clone(),
console,
yielder: self.yielder.take(),
signals_chan,
}
}
pub fn make_interactive(self) -> InteractiveMachineBuilder {
InteractiveMachineBuilder::from(self)
}
}
pub struct InteractiveMachineBuilder {
builder: MachineBuilder,
program: Option<Rc<RefCell<dyn program::Program>>>,
storage: Option<Rc<RefCell<storage::Storage>>>,
}
impl InteractiveMachineBuilder {
fn from(builder: MachineBuilder) -> Self {
InteractiveMachineBuilder { builder, program: None, storage: None }
}
pub fn get_console(&mut self) -> Rc<RefCell<dyn console::Console>> {
self.builder.get_console()
}
pub fn get_program(&mut self) -> Rc<RefCell<dyn program::Program>> {
if self.program.is_none() {
self.program = Some(Rc::from(RefCell::from(program::ImmutableProgram::default())));
}
self.program.clone().unwrap()
}
pub fn get_storage(&mut self) -> Rc<RefCell<storage::Storage>> {
if self.storage.is_none() {
self.storage = Some(Rc::from(RefCell::from(storage::Storage::default())));
}
self.storage.clone().unwrap()
}
pub fn with_program(mut self, program: Rc<RefCell<dyn program::Program>>) -> Self {
self.program = Some(program);
self
}
pub fn with_storage(mut self, storage: Rc<RefCell<storage::Storage>>) -> Self {
self.storage = Some(storage);
self
}
pub fn build(mut self) -> Machine {
let console = self.builder.get_console();
let program = self.get_program();
let storage = self.get_storage();
exec::add_interactive(&mut self.builder);
program::add_all(&mut self.builder, program, console.clone(), storage.clone());
storage::add_all(&mut self.builder, console.clone(), storage);
help::add_all(&mut self.builder, console);
self.builder.build()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_stop_with_closed_channel() {
let (signals_tx, signals_rx) = async_channel::unbounded();
let mut machine =
MachineBuilder::default().with_signals_chan((signals_tx, signals_rx)).build();
machine.signals_chan.0.close();
assert!(!machine.should_stop());
}
}