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