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