Skip to main content

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
18use async_channel::{Receiver, Sender};
19use endbasic_core::exec::{Machine, Result, Signal, YieldNowFn};
20use std::cell::RefCell;
21use std::rc::Rc;
22
23// TODO(jmmv): Should narrow the exposed interface by 1.0.0.
24pub mod arrays;
25pub mod console;
26pub mod data;
27pub mod exec;
28pub mod gfx;
29pub mod gpio;
30pub mod help;
31pub mod numerics;
32pub mod program;
33pub mod spi;
34pub mod storage;
35pub mod strings;
36pub mod testutils;
37
38/// Builder pattern to construct an EndBASIC interpreter.
39///
40/// Unless otherwise specified, the interpreter is connected to a terminal-based console.
41#[derive(Default)]
42pub struct MachineBuilder {
43    console: Option<Rc<RefCell<dyn console::Console>>>,
44    gpio_pins: Option<Rc<RefCell<dyn gpio::Pins>>>,
45    sleep_fn: Option<exec::SleepFn>,
46    yield_now_fn: Option<YieldNowFn>,
47    signals_chan: Option<(Sender<Signal>, Receiver<Signal>)>,
48}
49
50impl MachineBuilder {
51    /// Overrides the default terminal-based console with the given one.
52    pub fn with_console(mut self, console: Rc<RefCell<dyn console::Console>>) -> Self {
53        self.console = Some(console);
54        self
55    }
56
57    /// Overrides the default hardware-based GPIO pins with the given ones.
58    pub fn with_gpio_pins(mut self, pins: Rc<RefCell<dyn gpio::Pins>>) -> Self {
59        self.gpio_pins = Some(pins);
60        self
61    }
62
63    /// Overrides the default sleep function with the given one.
64    pub fn with_sleep_fn(mut self, sleep_fn: exec::SleepFn) -> Self {
65        self.sleep_fn = Some(sleep_fn);
66        self
67    }
68
69    /// Overrides the default yielding function with the given one.
70    pub fn with_yield_now_fn(mut self, yield_now_fn: YieldNowFn) -> Self {
71        self.yield_now_fn = Some(yield_now_fn);
72        self
73    }
74
75    /// Overrides the default signals channel with the given one.
76    pub fn with_signals_chan(mut self, chan: (Sender<Signal>, Receiver<Signal>)) -> Self {
77        self.signals_chan = Some(chan);
78        self
79    }
80
81    /// Lazily initializes the `console` field with a default value and returns it.
82    pub fn get_console(&mut self) -> Rc<RefCell<dyn console::Console>> {
83        if self.console.is_none() {
84            self.console = Some(Rc::from(RefCell::from(console::TrivialConsole::default())));
85        }
86        self.console.clone().unwrap()
87    }
88
89    /// Lazily initializes the `gpio_pins` field with a default value and returns it.
90    fn get_gpio_pins(&mut self) -> Rc<RefCell<dyn gpio::Pins>> {
91        if self.gpio_pins.is_none() {
92            self.gpio_pins = Some(Rc::from(RefCell::from(gpio::NoopPins::default())))
93        }
94        self.gpio_pins.as_ref().expect("Must have been initialized above").clone()
95    }
96
97    /// Builds the interpreter.
98    pub fn build(mut self) -> Result<Machine> {
99        let console = self.get_console();
100        let gpio_pins = self.get_gpio_pins();
101
102        let signals_chan = match self.signals_chan {
103            Some(pair) => pair,
104            None => async_channel::unbounded(),
105        };
106
107        let mut machine =
108            Machine::with_signals_chan_and_yield_now_fn(signals_chan, self.yield_now_fn);
109        arrays::add_all(&mut machine);
110        console::add_all(&mut machine, console.clone());
111        data::add_all(&mut machine);
112        gfx::add_all(&mut machine, console);
113        gpio::add_all(&mut machine, gpio_pins);
114        exec::add_scripting(&mut machine, self.sleep_fn);
115        numerics::add_all(&mut machine);
116        strings::add_all(&mut machine);
117        Ok(machine)
118    }
119
120    /// Extends the machine with interactive (REPL) features.
121    pub fn make_interactive(self) -> InteractiveMachineBuilder {
122        InteractiveMachineBuilder::from(self)
123    }
124}
125
126/// Builder pattern to construct an interpreter for REPL operation.
127///
128/// This is a superset of a `ScriptingMachineBuilder`.
129///
130/// Unless otherwise specified, the interpreter is connected to an in-memory drive and to a stored
131/// program that can be edited interactively.
132pub struct InteractiveMachineBuilder {
133    builder: MachineBuilder,
134    program: Option<Rc<RefCell<dyn program::Program>>>,
135    storage: Rc<RefCell<storage::Storage>>,
136}
137
138impl InteractiveMachineBuilder {
139    /// Constructs an interactive machine builder from a non-interactive builder.
140    fn from(builder: MachineBuilder) -> Self {
141        let storage = Rc::from(RefCell::from(storage::Storage::default()));
142        InteractiveMachineBuilder { builder, program: None, storage }
143    }
144
145    /// Returns the console that will be used for the machine.
146    pub fn get_console(&mut self) -> Rc<RefCell<dyn console::Console>> {
147        self.builder.get_console()
148    }
149
150    /// Lazily initializes the `program` field with a default value and returns it.
151    pub fn get_program(&mut self) -> Rc<RefCell<dyn program::Program>> {
152        if self.program.is_none() {
153            self.program = Some(Rc::from(RefCell::from(program::ImmutableProgram::default())));
154        }
155        self.program.clone().unwrap()
156    }
157
158    /// Returns the storage subsystem that will be used for the machine.
159    pub fn get_storage(&mut self) -> Rc<RefCell<storage::Storage>> {
160        self.storage.clone()
161    }
162
163    /// Overrides the default stored program with the given one.
164    pub fn with_program(mut self, program: Rc<RefCell<dyn program::Program>>) -> Self {
165        self.program = Some(program);
166        self
167    }
168
169    /// Builds the interpreter.
170    pub fn build(mut self) -> Result<Machine> {
171        let console = self.builder.get_console();
172        let program = self.get_program();
173        let storage = self.get_storage();
174        let mut machine = self.builder.build()?;
175
176        exec::add_interactive(&mut machine);
177        help::add_all(&mut machine, console.clone());
178        program::add_all(&mut machine, program, console.clone(), storage.clone());
179        storage::add_all(&mut machine, console, storage);
180
181        Ok(machine)
182    }
183}