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