Skip to main content

endbasic_std/
lib.rs

1// EndBASIC
2// Copyright 2021 Julio Merino
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! The EndBASIC standard library.
18
19use std::cell::RefCell;
20use std::collections::HashMap;
21use std::io;
22use std::rc::Rc;
23
24use async_channel::{Receiver, Sender, TryRecvError};
25use async_trait::async_trait;
26use endbasic_core::{
27    CallError, Callable, CallableMetadata, Compiler, CompilerError, GlobalDef, Image, LineCol,
28    StopReason, SymbolKey, Vm,
29};
30
31// TODO(jmmv): Should narrow the exposed interface by 1.0.0.
32pub mod arrays;
33pub mod console;
34pub mod data;
35pub mod exec;
36pub mod gfx;
37pub mod gpio;
38pub mod help;
39pub mod numerics;
40pub mod program;
41pub mod spi;
42pub mod storage;
43pub mod strings;
44pub mod testutils;
45
46/// Error types for callable execution.
47#[derive(Debug, thiserror::Error)]
48pub enum Error {
49    /// Fails due to a callable-specific execution error.
50    #[error("{0}")]
51    CallError(#[from] CallError),
52
53    /// Fails due to a program compilation error.
54    #[error("{0}")]
55    CompilerError(#[from] CompilerError),
56
57    /// Fails due to an I/O error in the underlying runtime.
58    #[error("{0}")]
59    IoError(#[from] io::Error),
60
61    /// Fails due to a runtime error at a specific source location.
62    #[error("{0}: {1}")]
63    RuntimeError(LineCol, String),
64
65    /// Aborts execution due to an external break signal.
66    #[error("Break")]
67    Break,
68}
69
70/// Result type for callable execution.
71pub type Result<T> = std::result::Result<T, Error>;
72
73/// Trait for objects that maintain state that can be reset to defaults.
74pub trait Clearable {
75    /// Resets any state held by the object to default values.
76    fn reset_state(&self);
77}
78
79/// Actions that callables can request the Machine to perform after an upcall returns.
80///
81/// Because callables don't have direct access to the Machine, they push requests onto an
82/// action queue.  The Machine's exec loop drains this queue after each upcall, performing
83/// any requested side effects before resuming execution.
84#[derive(Clone, Debug, Eq, PartialEq)]
85pub enum MachineAction {
86    /// Reset all runtime state (variables, heap, clearables, last error).
87    Clear,
88
89    /// Switches execution to the given program.
90    Run(String),
91}
92
93/// Signals that can be delivered to the machine.
94#[derive(Clone, Debug, Eq, PartialEq)]
95pub enum Signal {
96    /// Asks the machine to stop execution of the currently-running program.
97    Break,
98}
99
100/// Trait to decide when the machine should cooperatively yield to the host.
101#[async_trait(?Send)]
102pub trait Yielder {
103    /// Yields execution to the host.
104    async fn yield_now(&mut self);
105}
106
107/// Executes an EndBASIC program and tracks its state.
108pub struct Machine {
109    compiler: Compiler,
110    image: Image,
111    vm: Vm,
112    callables: HashMap<SymbolKey, Rc<dyn Callable>>,
113    clearables: Vec<Box<dyn Clearable>>,
114    actions: Rc<RefCell<Vec<MachineAction>>>,
115    global_defs: Vec<GlobalDef>,
116    console: Rc<RefCell<dyn console::Console>>,
117    yielder: Option<Box<dyn Yielder>>,
118    signals_chan: (Sender<Signal>, Receiver<Signal>),
119}
120
121impl Machine {
122    /// Resets the state of the machine by clearing all variables.
123    ///
124    /// This clears the runtime state (variables, heap, last error), resets the compiler's symbol
125    /// table, and starts with a fresh image.  The net effect is equivalent to starting a new machine
126    /// session: all user variables and compiled bytecode are gone, but registered callables remain.
127    pub fn clear(&mut self) {
128        for clearable in self.clearables.as_slice() {
129            clearable.reset_state();
130        }
131        self.vm.reset();
132        self.compiler = Compiler::new(&self.callables, &self.global_defs)
133            .expect("Compiler creation succeeded during Machine init; must also succeed here");
134        self.image = Image::default();
135    }
136
137    fn run(&mut self, program: String) -> Result<()> {
138        self.clear();
139        self.compile(&mut program.as_bytes())
140    }
141
142    /// Drops any deferred actions from the most recent async upcall.
143    fn clear_actions(&mut self) {
144        self.actions.borrow_mut().clear();
145    }
146
147    /// Applies and consumes all deferred actions from the most recent async upcall.
148    ///
149    /// Returns whether subsequent execution switched to the stored program due to `RUN`.
150    fn drain_actions(&mut self) -> Result<bool> {
151        let actions: Vec<MachineAction> = self.actions.borrow_mut().drain(..).collect();
152        let mut running_stored_program = false;
153        for action in actions {
154            match action {
155                MachineAction::Clear => self.clear(),
156                MachineAction::Run(program) => {
157                    self.run(program)?;
158                    running_stored_program = true;
159                }
160            }
161        }
162        Ok(running_stored_program)
163    }
164
165    /// Consumes any pending signals so they don't affect future executions.
166    pub fn drain_signals(&mut self) {
167        while self.signals_chan.1.try_recv().is_ok() {
168            // Do nothing.
169        }
170    }
171
172    /// Returns true if execution should stop because we have hit a stop condition.
173    fn should_stop(&mut self) -> bool {
174        match self.signals_chan.1.try_recv() {
175            Ok(Signal::Break) => true,
176            Err(TryRecvError::Empty) => false,
177            Err(TryRecvError::Closed) => false,
178        }
179    }
180
181    /// Returns true if execution should stop after yielding to the host once.
182    async fn should_stop_after_yield(&mut self) -> bool {
183        if let Some(yielder) = self.yielder.as_mut() {
184            yielder.yield_now().await;
185        }
186        self.should_stop()
187    }
188
189    /// Compiles the code in `input` and _appends_ it to the current machine context.
190    pub fn compile(&mut self, input: &mut dyn io::Read) -> Result<()> {
191        self.compiler.compile_more(&mut self.image, input)?;
192        Ok(())
193    }
194
195    /// Resumes (or starts) execution from the last compiled code.
196    pub async fn exec(&mut self) -> Result<Option<i32>> {
197        let mut running_stored_program = false;
198        let result = loop {
199            match self.vm.exec(&self.image) {
200                StopReason::Eof => {
201                    break Ok(None);
202                }
203
204                StopReason::End(code) => {
205                    if !running_stored_program {
206                        break Ok(Some(code.to_i32()));
207                    }
208
209                    if !code.is_success() {
210                        self.console
211                            .borrow_mut()
212                            .print(&format!("Program exited with code {}", code.to_i32()))?;
213                    }
214
215                    break Ok(None);
216                }
217
218                StopReason::Exception(pos, msg) => {
219                    break Err(Error::RuntimeError(pos, msg));
220                }
221
222                StopReason::UpcallAsync(handler) => {
223                    let upcall_result = handler.invoke().await;
224
225                    // Before checking if the upcall failed, we need to honor stop signals.
226                    // This is because we want to favor forceful termination over any errors that might
227                    // arise from the upcall so that, e.g. Ctrl+C cannot be caught as a keyboard event
228                    // and instead we abort execution.
229                    if self.should_stop() {
230                        self.clear_actions();
231                        self.vm.interrupt(&self.image);
232                        break Err(Error::Break);
233                    }
234
235                    if let Err(e) = upcall_result {
236                        self.clear_actions();
237                        let (pos, message) = e.parts();
238                        break Err(Error::RuntimeError(pos, message));
239                    }
240
241                    if self.drain_actions()? {
242                        running_stored_program = true;
243                    }
244                }
245
246                StopReason::Yield => {
247                    if self.should_stop_after_yield().await {
248                        self.vm.interrupt(&self.image);
249                        break Err(Error::Break);
250                    }
251                }
252            }
253        };
254        if running_stored_program {
255            self.vm.clear_error_handler();
256        }
257        result
258    }
259}
260
261/// Builder pattern to construct an EndBASIC interpreter.
262///
263/// Unless otherwise specified, the interpreter is connected to a terminal-based console.
264#[derive(Default)]
265pub struct MachineBuilder {
266    callables: HashMap<SymbolKey, Rc<dyn Callable>>,
267    callables_metadata: Rc<RefCell<HashMap<SymbolKey, Rc<CallableMetadata>>>>,
268    clearables: Vec<Box<dyn Clearable>>,
269    console: Option<Rc<RefCell<dyn console::Console>>>,
270    gpio_pins: Option<Rc<RefCell<dyn gpio::Pins>>>,
271    sleep_fn: Option<exec::SleepFn>,
272    actions: Rc<RefCell<Vec<MachineAction>>>,
273    yielder: Option<Box<dyn Yielder>>,
274    signals_chan: Option<(Sender<Signal>, Receiver<Signal>)>,
275    global_defs: Vec<GlobalDef>,
276}
277
278impl MachineBuilder {
279    /// Returns a shared reference to the machine's action queue.
280    ///
281    /// This is used by callables that need to request machine-level side effects (such as CLEAR).
282    pub fn actions(&self) -> Rc<RefCell<Vec<MachineAction>>> {
283        self.actions.clone()
284    }
285
286    /// Registers the given builtin callable, which must not yet be registered.
287    pub fn add_callable(&mut self, callable: Rc<dyn Callable>) {
288        let metadata = callable.metadata();
289        let key = SymbolKey::from(metadata.name());
290
291        let previous = self.callables.insert(key.clone(), callable);
292        debug_assert!(previous.is_none(), "Cannot insert a callable twice");
293
294        let previous = self.callables_metadata.borrow_mut().insert(key, metadata);
295        debug_assert!(previous.is_none(), "Cannot insert callable metadata twice");
296    }
297
298    /// Returns metadata for all callables currently registered in the builder.
299    pub fn callables_metadata(&self) -> Rc<RefCell<HashMap<SymbolKey, Rc<CallableMetadata>>>> {
300        self.callables_metadata.clone()
301    }
302
303    /// Registers the given clearable.
304    ///
305    /// In the common case, functions and commands hold a reference to the out-of-machine state
306    /// they interact with.  This state is invisible from here, but we may need to have access
307    /// to it to reset it as part of the `clear` operation.  In those cases, such state must be
308    /// registered via this hook.
309    pub fn add_clearable(&mut self, clearable: Box<dyn Clearable>) {
310        self.clearables.push(clearable);
311    }
312
313    /// Overrides the default terminal-based console with the given one.
314    pub fn with_console(mut self, console: Rc<RefCell<dyn console::Console>>) -> Self {
315        self.console = Some(console);
316        self
317    }
318
319    /// Sets a global variable to an initial value.
320    pub fn with_globals(mut self, defs: Vec<GlobalDef>) -> Self {
321        self.global_defs.extend(defs);
322        self
323    }
324
325    /// Overrides the default hardware-based GPIO pins with the given ones.
326    pub fn with_gpio_pins(mut self, pins: Rc<RefCell<dyn gpio::Pins>>) -> Self {
327        self.gpio_pins = Some(pins);
328        self
329    }
330
331    /// Overrides the default sleep function with the given one.
332    pub fn with_sleep_fn(mut self, sleep_fn: exec::SleepFn) -> Self {
333        self.sleep_fn = Some(sleep_fn);
334        self
335    }
336
337    /// Overrides the default yielder with the given one.
338    pub fn with_yielder(mut self, yielder: Box<dyn Yielder>) -> Self {
339        self.yielder = Some(yielder);
340        self
341    }
342
343    /// Overrides the default signals channel with the given one.
344    pub fn with_signals_chan(mut self, chan: (Sender<Signal>, Receiver<Signal>)) -> Self {
345        self.signals_chan = Some(chan);
346        self
347    }
348
349    /// Lazily initializes the `console` field with a default value and returns it.
350    pub fn get_console(&mut self) -> Rc<RefCell<dyn console::Console>> {
351        if self.console.is_none() {
352            self.console = Some(Rc::from(RefCell::from(console::TrivialConsole::default())));
353        }
354        self.console.clone().unwrap()
355    }
356
357    /// Lazily initializes the `gpio_pins` field with a default value and returns it.
358    fn get_gpio_pins(&mut self) -> Rc<RefCell<dyn gpio::Pins>> {
359        if self.gpio_pins.is_none() {
360            self.gpio_pins = Some(Rc::from(RefCell::from(gpio::NoopPins::default())))
361        }
362        self.gpio_pins.as_ref().expect("Must have been initialized above").clone()
363    }
364
365    /// Builds the interpreter.
366    pub fn build(mut self) -> Machine {
367        let console = self.get_console();
368        let gpio_pins = self.get_gpio_pins();
369
370        let signals_chan = match self.signals_chan.take() {
371            Some(pair) => pair,
372            None => async_channel::unbounded(),
373        };
374
375        arrays::add_all(&mut self);
376        console::add_all(&mut self, console.clone());
377        gfx::add_all(&mut self, console.clone());
378        data::add_all(&mut self);
379        gpio::add_all(&mut self, gpio_pins);
380        let sleep_fn = self.sleep_fn.take();
381        exec::add_scripting(&mut self, sleep_fn);
382        numerics::add_all(&mut self);
383        strings::add_all(&mut self);
384
385        Machine {
386            compiler: Compiler::new(&self.callables, &self.global_defs)
387                .expect("Injected globals must be valid"),
388            image: Image::default(),
389            vm: Vm::new(self.callables.clone()),
390            callables: self.callables,
391            clearables: self.clearables,
392            actions: self.actions.clone(),
393            global_defs: self.global_defs.clone(),
394            console,
395            yielder: self.yielder.take(),
396            signals_chan,
397        }
398    }
399
400    /// Extends the machine with interactive (REPL) features.
401    pub fn make_interactive(self) -> InteractiveMachineBuilder {
402        InteractiveMachineBuilder::from(self)
403    }
404}
405
406/// Builder pattern to construct an interpreter for REPL operation.
407///
408/// This is a superset of a `ScriptingMachineBuilder`.
409///
410/// Unless otherwise specified, the interpreter is connected to an in-memory drive and to a stored
411/// program that can be edited interactively.
412pub struct InteractiveMachineBuilder {
413    builder: MachineBuilder,
414    program: Option<Rc<RefCell<dyn program::Program>>>,
415    storage: Option<Rc<RefCell<storage::Storage>>>,
416}
417
418impl InteractiveMachineBuilder {
419    /// Constructs an interactive machine builder from a non-interactive builder.
420    fn from(builder: MachineBuilder) -> Self {
421        InteractiveMachineBuilder { builder, program: None, storage: None }
422    }
423
424    /// Returns the console that will be used for the machine.
425    pub fn get_console(&mut self) -> Rc<RefCell<dyn console::Console>> {
426        self.builder.get_console()
427    }
428
429    /// Lazily initializes the `program` field with a default value and returns it.
430    pub fn get_program(&mut self) -> Rc<RefCell<dyn program::Program>> {
431        if self.program.is_none() {
432            self.program = Some(Rc::from(RefCell::from(program::ImmutableProgram::default())));
433        }
434        self.program.clone().unwrap()
435    }
436
437    /// Returns the storage subsystem that will be used for the machine.
438    pub fn get_storage(&mut self) -> Rc<RefCell<storage::Storage>> {
439        if self.storage.is_none() {
440            self.storage = Some(Rc::from(RefCell::from(storage::Storage::default())));
441        }
442        self.storage.clone().unwrap()
443    }
444
445    /// Overrides the default stored program with the given one.
446    pub fn with_program(mut self, program: Rc<RefCell<dyn program::Program>>) -> Self {
447        self.program = Some(program);
448        self
449    }
450
451    /// Overrides the default storage subsystem with the given one.
452    pub fn with_storage(mut self, storage: Rc<RefCell<storage::Storage>>) -> Self {
453        self.storage = Some(storage);
454        self
455    }
456
457    /// Builds the interpreter.
458    pub fn build(mut self) -> Machine {
459        let console = self.builder.get_console();
460        let program = self.get_program();
461        let storage = self.get_storage();
462
463        exec::add_interactive(&mut self.builder);
464        program::add_all(&mut self.builder, program, console.clone(), storage.clone());
465        storage::add_all(&mut self.builder, console.clone(), storage);
466        help::add_all(&mut self.builder, console);
467
468        self.builder.build()
469    }
470}
471
472#[cfg(test)]
473mod tests {
474    use super::*;
475
476    #[test]
477    fn test_should_stop_with_closed_channel() {
478        let (signals_tx, signals_rx) = async_channel::unbounded();
479        let mut machine =
480            MachineBuilder::default().with_signals_chan((signals_tx, signals_rx)).build();
481
482        machine.signals_chan.0.close();
483        assert!(!machine.should_stop());
484    }
485}