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