endbasic_std/
lib.rs

1// EndBASIC
2// Copyright 2021 Julio Merino
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5// use this file except in compliance with the License.  You may obtain a copy
6// of the License at:
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
13// License for the specific language governing permissions and limitations
14// under the License.
15
16//! The EndBASIC standard library.
17
18// Keep these in sync with other top-level files.
19#![allow(clippy::await_holding_refcell_ref)]
20#![allow(clippy::collapsible_else_if)]
21#![warn(anonymous_parameters, bad_style, missing_docs)]
22#![warn(unused, unused_extern_crates, unused_import_braces, unused_qualifications)]
23#![warn(unsafe_code)]
24
25use async_channel::{Receiver, Sender};
26use endbasic_core::exec::{Machine, Result, Signal, YieldNowFn};
27use std::cell::RefCell;
28use std::rc::Rc;
29
30// TODO(jmmv): Should narrow the exposed interface by 1.0.0.
31pub mod arrays;
32pub mod console;
33pub mod data;
34pub mod exec;
35pub mod gfx;
36pub mod gpio;
37pub mod help;
38pub mod numerics;
39pub mod program;
40pub mod storage;
41pub mod strings;
42pub mod testutils;
43
44/// Builder pattern to construct an EndBASIC interpreter.
45///
46/// Unless otherwise specified, the interpreter is connected to a terminal-based console.
47#[derive(Default)]
48pub struct MachineBuilder {
49    console: Option<Rc<RefCell<dyn console::Console>>>,
50    gpio_pins: Option<Rc<RefCell<dyn gpio::Pins>>>,
51    sleep_fn: Option<exec::SleepFn>,
52    yield_now_fn: Option<YieldNowFn>,
53    signals_chan: Option<(Sender<Signal>, Receiver<Signal>)>,
54}
55
56impl MachineBuilder {
57    /// Overrides the default terminal-based console with the given one.
58    pub fn with_console(mut self, console: Rc<RefCell<dyn console::Console>>) -> Self {
59        self.console = Some(console);
60        self
61    }
62
63    /// Overrides the default hardware-based GPIO pins with the given ones.
64    pub fn with_gpio_pins(mut self, pins: Rc<RefCell<dyn gpio::Pins>>) -> Self {
65        self.gpio_pins = Some(pins);
66        self
67    }
68
69    /// Overrides the default sleep function with the given one.
70    pub fn with_sleep_fn(mut self, sleep_fn: exec::SleepFn) -> Self {
71        self.sleep_fn = Some(sleep_fn);
72        self
73    }
74
75    /// Overrides the default yielding function with the given one.
76    pub fn with_yield_now_fn(mut self, yield_now_fn: YieldNowFn) -> Self {
77        self.yield_now_fn = Some(yield_now_fn);
78        self
79    }
80
81    /// Overrides the default signals channel with the given one.
82    pub fn with_signals_chan(mut self, chan: (Sender<Signal>, Receiver<Signal>)) -> Self {
83        self.signals_chan = Some(chan);
84        self
85    }
86
87    /// Lazily initializes the `console` field with a default value and returns it.
88    pub fn get_console(&mut self) -> Rc<RefCell<dyn console::Console>> {
89        if self.console.is_none() {
90            self.console = Some(Rc::from(RefCell::from(console::TrivialConsole::default())));
91        }
92        self.console.clone().unwrap()
93    }
94
95    /// Lazily initializes the `gpio_pins` field with a default value and returns it.
96    fn get_gpio_pins(&mut self) -> Rc<RefCell<dyn gpio::Pins>> {
97        if self.gpio_pins.is_none() {
98            self.gpio_pins = Some(Rc::from(RefCell::from(gpio::NoopPins::default())))
99        }
100        self.gpio_pins.as_ref().expect("Must have been initialized above").clone()
101    }
102
103    /// Builds the interpreter.
104    pub fn build(mut self) -> Result<Machine> {
105        let console = self.get_console();
106        let gpio_pins = self.get_gpio_pins();
107
108        let signals_chan = match self.signals_chan {
109            Some(pair) => pair,
110            None => async_channel::unbounded(),
111        };
112
113        let mut machine =
114            Machine::with_signals_chan_and_yield_now_fn(signals_chan, self.yield_now_fn);
115        arrays::add_all(&mut machine);
116        console::add_all(&mut machine, console.clone());
117        data::add_all(&mut machine);
118        gfx::add_all(&mut machine, console);
119        gpio::add_all(&mut machine, gpio_pins);
120        exec::add_scripting(&mut machine, self.sleep_fn);
121        numerics::add_all(&mut machine);
122        strings::add_all(&mut machine);
123        Ok(machine)
124    }
125
126    /// Extends the machine with interactive (REPL) features.
127    pub fn make_interactive(self) -> InteractiveMachineBuilder {
128        InteractiveMachineBuilder::from(self)
129    }
130}
131
132/// Builder pattern to construct an interpreter for REPL operation.
133///
134/// This is a superset of a `ScriptingMachineBuilder`.
135///
136/// Unless otherwise specified, the interpreter is connected to an in-memory drive and to a stored
137/// program that can be edited interactively.
138pub struct InteractiveMachineBuilder {
139    builder: MachineBuilder,
140    program: Option<Rc<RefCell<dyn program::Program>>>,
141    storage: Rc<RefCell<storage::Storage>>,
142}
143
144impl InteractiveMachineBuilder {
145    /// Constructs an interactive machine builder from a non-interactive builder.
146    fn from(builder: MachineBuilder) -> Self {
147        let storage = Rc::from(RefCell::from(storage::Storage::default()));
148        InteractiveMachineBuilder { builder, program: None, storage }
149    }
150
151    /// Returns the console that will be used for the machine.
152    pub fn get_console(&mut self) -> Rc<RefCell<dyn console::Console>> {
153        self.builder.get_console()
154    }
155
156    /// Lazily initializes the `program` field with a default value and returns it.
157    pub fn get_program(&mut self) -> Rc<RefCell<dyn program::Program>> {
158        if self.program.is_none() {
159            self.program = Some(Rc::from(RefCell::from(program::ImmutableProgram::default())));
160        }
161        self.program.clone().unwrap()
162    }
163
164    /// Returns the storage subsystem that will be used for the machine.
165    pub fn get_storage(&mut self) -> Rc<RefCell<storage::Storage>> {
166        self.storage.clone()
167    }
168
169    /// Overrides the default stored program with the given one.
170    pub fn with_program(mut self, program: Rc<RefCell<dyn program::Program>>) -> Self {
171        self.program = Some(program);
172        self
173    }
174
175    /// Builds the interpreter.
176    pub fn build(mut self) -> Result<Machine> {
177        let console = self.builder.get_console();
178        let program = self.get_program();
179        let storage = self.get_storage();
180        let mut machine = self.builder.build()?;
181
182        exec::add_interactive(&mut machine);
183        help::add_all(&mut machine, console.clone());
184        program::add_all(&mut machine, program, console.clone(), storage.clone());
185        storage::add_all(&mut machine, console, storage);
186
187        Ok(machine)
188    }
189}