Skip to main content

endbasic_std/
data.rs

1// EndBASIC
2// Copyright 2022 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//! Commands to interact with the data provided by `DATA` statements.
17
18use async_trait::async_trait;
19use endbasic_core::ast::{ArgSep, ExprType, Value, VarRef};
20use endbasic_core::compiler::{ArgSepSyntax, RepeatedSyntax, RepeatedTypeSyntax};
21use endbasic_core::exec::{Clearable, Error, Machine, Result, Scope};
22use endbasic_core::syms::{Callable, CallableMetadata, CallableMetadataBuilder};
23use std::borrow::Cow;
24use std::cell::RefCell;
25use std::rc::Rc;
26
27/// Category description for all symbols provided by this module.
28pub(crate) const CATEGORY: &str = "Data management";
29
30struct ClearableIndex(Rc<RefCell<usize>>);
31
32impl Clearable for ClearableIndex {
33    fn reset_state(&self, _syms: &mut endbasic_core::syms::Symbols) {
34        *self.0.borrow_mut() = 0;
35    }
36}
37
38/// The `READ` command.
39pub struct ReadCommand {
40    metadata: CallableMetadata,
41    index: Rc<RefCell<usize>>,
42}
43
44impl ReadCommand {
45    /// Creates a new `READ` command.
46    pub fn new(index: Rc<RefCell<usize>>) -> Rc<Self> {
47        Rc::from(Self {
48            metadata: CallableMetadataBuilder::new("READ")
49                .with_syntax(&[(
50                    &[],
51                    Some(&RepeatedSyntax {
52                        name: Cow::Borrowed("vref"),
53                        type_syn: RepeatedTypeSyntax::VariableRef,
54                        sep: ArgSepSyntax::Exactly(ArgSep::Long),
55                        require_one: true,
56                        allow_missing: false,
57                    }),
58                )])
59                .with_category(CATEGORY)
60                .with_description(
61                    "Extracts data values from DATA statements.
62DATA statements can appear anywhere in the program and they register data values into an array of \
63values.  READ is then used to extract values from this array into the provided variables in the \
64order in which they were defined.  In other words: READ maintains an internal index into the data \
65array that gets incremented by the number of provided variables every time it is executed.
66The variable references in the vref1..vrefN list must match the types or be compatible with the \
67values in the corresponding position of the data array.  Empty values in the data array can be \
68specified by DATA, and those are converted into the default values for the relevant types: \
69booleans are false, numbers are 0, and strings are empty.
70Attempting to extract more values than are defined by DATA results in an \"out of data\" error.
71The index that READ uses to extract DATA values can be reset by RESTORE and, more generally, by \
72CLEAR.",
73                )
74                .build(),
75            index,
76        })
77    }
78}
79
80#[async_trait(?Send)]
81impl Callable for ReadCommand {
82    fn metadata(&self) -> &CallableMetadata {
83        &self.metadata
84    }
85
86    async fn exec(&self, mut scope: Scope<'_>, machine: &mut Machine) -> Result<()> {
87        debug_assert_ne!(0, scope.nargs());
88
89        let mut vrefs = Vec::with_capacity(scope.nargs());
90        while scope.nargs() > 0 {
91            vrefs.push(scope.pop_varref_with_pos());
92        }
93
94        let mut index = self.index.borrow_mut();
95        for (vname, vtype, pos) in vrefs {
96            let datum = {
97                let data = machine.get_data();
98                debug_assert!(*index <= data.len());
99                if *index == data.len() {
100                    return Err(Error::InternalError(
101                        pos,
102                        format!("Out of data reading into {}", vname),
103                    ));
104                }
105
106                match (vtype, &data[*index]) {
107                    (_, Some(datum)) => datum.clone(),
108                    (ExprType::Boolean, None) => Value::Boolean(false),
109                    (ExprType::Double, None) => Value::Double(0.0),
110                    (ExprType::Integer, None) => Value::Integer(0),
111                    (ExprType::Text, None) => Value::Text("".to_owned()),
112                }
113            };
114            *index += 1;
115
116            let vref = VarRef::new(vname.to_string(), Some(vtype));
117            machine
118                .get_mut_symbols()
119                .set_var(&vref, datum)
120                .map_err(|e| Error::SyntaxError(pos, format!("{}", e)))?;
121        }
122
123        Ok(())
124    }
125}
126
127/// The `RESTORE` command.
128pub struct RestoreCommand {
129    metadata: CallableMetadata,
130    index: Rc<RefCell<usize>>,
131}
132
133impl RestoreCommand {
134    /// Creates a new `RESTORE` command.
135    pub fn new(index: Rc<RefCell<usize>>) -> Rc<Self> {
136        Rc::from(Self {
137            metadata: CallableMetadataBuilder::new("RESTORE")
138                .with_syntax(&[(&[], None)])
139                .with_category(CATEGORY)
140                .with_description(
141                    "Resets the index of the data element to be returned.
142This allows READ to re-return the same elements that were previously extracted from the array of \
143values defined by DATA.",
144                )
145                .build(),
146            index,
147        })
148    }
149}
150
151#[async_trait(?Send)]
152impl Callable for RestoreCommand {
153    fn metadata(&self) -> &CallableMetadata {
154        &self.metadata
155    }
156
157    async fn exec(&self, scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
158        debug_assert_eq!(0, scope.nargs());
159        *self.index.borrow_mut() = 0;
160        Ok(())
161    }
162}
163
164/// Instantiates all symbols in this module and adds them to the `machine`.
165pub fn add_all(machine: &mut Machine) {
166    let index = Rc::from(RefCell::from(0));
167    machine.add_clearable(Box::from(ClearableIndex(index.clone())));
168    machine.add_callable(ReadCommand::new(index.clone()));
169    machine.add_callable(RestoreCommand::new(index));
170}
171
172#[cfg(test)]
173mod tests {
174    use crate::testutils::*;
175    use endbasic_core::ast::Value;
176
177    #[test]
178    fn test_read_simple() {
179        Tester::default()
180            .run(
181                r#"
182            READ i: PRINT i
183            READ i: PRINT i
184            DATA 3, 5, -7
185            READ i: PRINT i
186            "#,
187            )
188            .expect_prints([" 3", " 5", "-7"])
189            .expect_var("I", Value::Integer(-7))
190            .check();
191    }
192
193    #[test]
194    fn test_read_multiple() {
195        Tester::default()
196            .run(
197                r#"
198            READ i, j, k, i
199            DATA 3, 5, 7, 6
200            "#,
201            )
202            .expect_var("I", Value::Integer(6))
203            .expect_var("J", Value::Integer(5))
204            .expect_var("K", Value::Integer(7))
205            .check();
206    }
207
208    #[test]
209    fn test_read_defaults_with_annotations() {
210        Tester::default()
211            .run(r#"DATA , , , ,: READ a, b?, d#, i%, s$"#)
212            .expect_var("a", Value::Integer(0))
213            .expect_var("b", Value::Boolean(false))
214            .expect_var("d", Value::Double(0.0))
215            .expect_var("i", Value::Integer(0))
216            .expect_var("s", Value::Text("".to_owned()))
217            .check();
218    }
219
220    #[test]
221    fn test_read_defaults_without_annotations() {
222        Tester::default()
223            .run(
224                r#"
225            DIM b AS BOOLEAN
226            DIM d AS DOUBLE
227            DIM i AS INTEGER
228            DIM s AS STRING
229            DATA , , , ,
230            READ a, b, d, i, s
231            "#,
232            )
233            .expect_var("a", Value::Integer(0))
234            .expect_var("b", Value::Boolean(false))
235            .expect_var("d", Value::Double(0.0))
236            .expect_var("i", Value::Integer(0))
237            .expect_var("s", Value::Text("".to_owned()))
238            .check();
239    }
240
241    #[test]
242    fn test_read_double_to_integer() {
243        Tester::default().run(r#"DATA 5.6: READ i%"#).expect_var("i", Value::Integer(6)).check();
244    }
245
246    #[test]
247    fn test_read_integer_to_double() {
248        Tester::default().run(r#"DATA 5: READ d#"#).expect_var("d", Value::Double(5.0)).check();
249    }
250
251    #[test]
252    fn test_read_out_of_data() {
253        Tester::default()
254            .run(r#"DATA 5: READ i: READ j"#)
255            .expect_err("1:22: Out of data reading into J")
256            .expect_var("I", Value::Integer(5))
257            .check();
258    }
259
260    #[test]
261    fn test_read_clear_on_run() {
262        Tester::default()
263            .set_program(None, "DATA 1: READ i: PRINT i")
264            .run(r#"RUN: RUN"#)
265            .expect_clear()
266            .expect_prints([" 1"])
267            .expect_clear()
268            .expect_prints([" 1"])
269            .expect_var("I", Value::Integer(1))
270            .expect_program(None as Option<String>, "DATA 1: READ i: PRINT i")
271            .check();
272    }
273
274    #[test]
275    fn test_read_index_remains_out_of_bounds() {
276        let mut t = Tester::default();
277        t.run(r#"DATA 1: READ i, j"#)
278            .expect_var("i", Value::Integer(1))
279            .expect_err("1:17: Out of data reading into J")
280            .check();
281
282        // This represents a second invocation in the REPL, which in principle should work to avoid
283        // surprises but currently doesn't due to the fact that we maintain the index outside of the
284        // machine and `machine.exec()` cannot clear it upfront.  Note how the read into `i` picks
285        // up the second value, not the first one, because the `DATA` is only [1, 2], NOT [1, 1, 2],
286        // but the index is still 1, not 0.  This is kind of intentional though, because adding
287        // extra hooks into `machine.exec()` just for this single use case seems overkill.
288        t.run(r#"DATA 1, 2: READ i, j"#)
289            .expect_var("i", Value::Integer(2))
290            .expect_err("1:20: Out of data reading into J")
291            .check();
292
293        // Running `CLEAR` explicitly should resolve the issue described above and give us the
294        // expected behavior.
295        t.run(r#"CLEAR"#).expect_clear().check();
296        t.run(r#"DATA 1, 2: READ i, j"#)
297            .expect_clear()
298            .expect_var("i", Value::Integer(1))
299            .expect_var("j", Value::Integer(2))
300            .check();
301    }
302
303    #[test]
304    fn test_read_errors() {
305        check_stmt_compilation_err("1:1: READ expected vref1[, .., vrefN]", "READ");
306        check_stmt_compilation_err("1:6: Requires a reference, not a value", "READ 3");
307        check_stmt_compilation_err("1:7: READ expected vref1[, .., vrefN]", "READ i; j");
308
309        check_stmt_err(
310            "1:16: Cannot assign value of type STRING to variable of type INTEGER",
311            "DATA \"x\": READ i",
312        );
313        check_stmt_err(
314            "1:18: Cannot assign value of type BOOLEAN to variable of type INTEGER",
315            "DATA FALSE: READ s%",
316        );
317    }
318
319    #[test]
320    fn test_restore_nothing() {
321        Tester::default().run("RESTORE").check();
322    }
323
324    #[test]
325    fn test_restore_before_read() {
326        Tester::default()
327            .run(
328                r#"
329            DATA 3, 5, 7
330            RESTORE
331            READ i: PRINT i
332            READ i: PRINT i
333            "#,
334            )
335            .expect_prints([" 3", " 5"])
336            .expect_var("I", Value::Integer(5))
337            .check();
338    }
339
340    #[test]
341    fn test_restore_after_read() {
342        Tester::default()
343            .run(
344                r#"
345            DATA 3, -5, 7
346            READ i: PRINT i
347            READ i: PRINT i
348            RESTORE
349            READ i: PRINT i
350            "#,
351            )
352            .expect_prints([" 3", "-5", " 3"])
353            .expect_var("I", Value::Integer(3))
354            .check();
355    }
356
357    #[test]
358    fn test_restore_errors() {
359        check_stmt_compilation_err("1:1: RESTORE expected no arguments", "RESTORE 123");
360    }
361}