1use 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
27pub(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
38pub struct ReadCommand {
40 metadata: CallableMetadata,
41 index: Rc<RefCell<usize>>,
42}
43
44impl ReadCommand {
45 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
127pub struct RestoreCommand {
129 metadata: CallableMetadata,
130 index: Rc<RefCell<usize>>,
131}
132
133impl RestoreCommand {
134 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
164pub 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 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 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}