1use async_trait::async_trait;
20use endbasic_core::{
21 CallResult, Callable, CallableMetadata, CallableMetadataBuilder, ExprType, Scope,
22};
23use std::cell::RefCell;
24use std::rc::Rc;
25
26use crate::{MachineAction, MachineBuilder};
27
28pub(crate) const CATEGORY: &str = "Interpreter";
30
31pub struct ClearCommand {
33 metadata: Rc<CallableMetadata>,
34 actions: Rc<RefCell<Vec<MachineAction>>>,
35}
36
37impl ClearCommand {
38 pub fn new(actions: Rc<RefCell<Vec<MachineAction>>>) -> Rc<Self> {
40 Rc::from(Self {
41 metadata: CallableMetadataBuilder::new("CLEAR")
42 .with_async(true)
43 .with_syntax(&[(&[], None)])
44 .with_category(CATEGORY)
45 .with_description(
46 "Restores initial machine state but keeps the stored program.
47This command resets the machine to a semi-pristine state by clearing all user-defined variables \
48and restoring the state of shared resources. These resources include: the console, whose color \
49and video syncing bit are reset; and the GPIO pins, which are set to their default state.
50The stored program is kept in memory. To clear that too, use NEW (but don't forget to first \
51SAVE your program!).
52This command is for interactive use only.",
53 )
54 .build(),
55 actions,
56 })
57 }
58}
59
60#[async_trait(?Send)]
61impl Callable for ClearCommand {
62 fn metadata(&self) -> Rc<CallableMetadata> {
63 self.metadata.clone()
64 }
65
66 async fn async_exec(&self, _scope: Scope<'_>) -> CallResult<()> {
67 self.actions.borrow_mut().push(MachineAction::Clear);
68 Ok(())
69 }
70}
71
72pub struct ErrmsgFunction {
74 metadata: Rc<CallableMetadata>,
75}
76
77impl ErrmsgFunction {
78 pub fn new() -> Rc<Self> {
80 Rc::from(Self {
81 metadata: CallableMetadataBuilder::new("ERRMSG")
82 .with_return_type(ExprType::Text)
83 .with_syntax(&[(&[], None)])
84 .with_category(CATEGORY)
85 .with_description(
86 "Returns the last captured error message.
87When used in combination of ON ERROR to set an error handler, this function returns the string \
88representation of the last captured error. If this is called before any error is captured, \
89returns the empty string.",
90 )
91 .build(),
92 })
93 }
94}
95
96#[async_trait(?Send)]
97impl Callable for ErrmsgFunction {
98 fn metadata(&self) -> Rc<CallableMetadata> {
99 self.metadata.clone()
100 }
101
102 fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
103 debug_assert_eq!(0, scope.nargs());
104
105 let message = scope
106 .last_error()
107 .map(|(pos, message)| format!("{}: {}", pos, message))
108 .unwrap_or_default();
109 scope.return_string(message)
110 }
111}
112
113pub fn add_scripting(machine: &mut MachineBuilder) {
115 machine.add_callable(ErrmsgFunction::new());
116}
117
118pub fn add_interactive(machine: &mut MachineBuilder) {
120 machine.add_callable(ClearCommand::new(machine.actions()));
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::testutils::*;
126 use crate::{Error, MachineBuilder, Signal, Yielder};
127 use async_trait::async_trait;
128 use futures_lite::FutureExt;
129 use futures_lite::future::block_on;
130 use std::cell::RefCell;
131 use std::rc::Rc;
132 use std::time::Duration;
133
134 #[test]
135 fn test_clear_ok() {
136 Tester::default().run("a = 1: CLEAR").expect_clear().check();
137 Tester::default()
138 .run_n(&["DIM a(2): CLEAR", "DIM a(5) AS STRING: CLEAR"])
139 .expect_clear()
140 .expect_clear()
141 .check();
142 }
143
144 #[test]
145 fn test_clear_inside_gosub_stops_execution() {
146 Tester::default().run("GOSUB @sub: END\n@sub:\nCLEAR").expect_clear().check();
149 }
150
151 #[test]
152 fn test_clear_inside_sub_stops_execution() {
153 Tester::default()
157 .run("SUB foo: PRINT 3: CLEAR: PRINT 5: END SUB: foo: foo")
158 .expect_prints([" 3"])
159 .expect_clear()
160 .check();
161 }
162
163 #[test]
164 fn test_clear_errors() {
165 check_stmt_compilation_err("1:1: CLEAR expected no arguments", "CLEAR 123");
166 }
167
168 #[test]
169 fn test_errmsg_before_error() {
170 check_expr_ok("", r#"ERRMSG"#);
171 }
172
173 #[test]
174 fn test_errmsg_after_error() {
175 Tester::default()
176 .run("ON ERROR RESUME NEXT: COLOR -1: PRINT \"Captured: \"; ERRMSG")
177 .expect_prints(["Captured: 1:29: Color out of range"])
178 .check();
179 }
180
181 #[test]
182 fn test_errmsg_errors() {
183 check_expr_compilation_error("1:10: ERRMSG expected no arguments", r#"ERRMSG()"#);
184 check_expr_compilation_error("1:10: ERRMSG expected no arguments", r#"ERRMSG(3)"#);
185 }
186
187 #[test]
188 fn test_break_stops_after_upcall() {
189 let (tx, rx) = async_channel::unbounded();
190 let break_tx = tx.clone();
191 let datetime = Rc::from(MockDateTime::default());
192 datetime.set_sleep_fn(Box::new(
193 move |_d: Duration| -> futures_lite::future::BoxedLocal<Result<(), String>> {
194 let break_tx = break_tx.clone();
195 async move {
196 break_tx.send(Signal::Break).await.unwrap();
197 Ok(())
198 }
199 .boxed_local()
200 },
201 ));
202
203 let mut machine = MachineBuilder::default()
204 .with_signals_chan((tx.clone(), rx))
205 .with_datetime(datetime)
206 .build();
207 machine.compile(&mut "DO: SLEEP 0: LOOP".as_bytes()).unwrap();
208
209 match block_on(machine.exec()) {
210 Err(Error::Break) => (),
211 r => panic!("Expected Break but got {:?}", r),
212 }
213 assert_eq!(0, tx.len());
214 }
215
216 #[test]
217 fn test_yielder_called_on_stop_reason_yield() {
218 struct CountingYielder {
219 count: Rc<RefCell<usize>>,
220 }
221
222 #[async_trait(?Send)]
223 impl Yielder for CountingYielder {
224 async fn yield_now(&mut self) {
225 *self.count.borrow_mut() += 1;
226 }
227 }
228
229 let (tx, rx) = async_channel::unbounded();
230 let yield_count = Rc::from(RefCell::from(0));
231
232 let mut machine = MachineBuilder::default()
233 .with_signals_chan((tx.clone(), rx))
234 .with_yielder(Rc::from(RefCell::from(CountingYielder { count: yield_count.clone() })))
235 .build();
236
237 block_on(tx.send(Signal::Break)).unwrap();
238 machine.compile(&mut "@here: GOTO @here".as_bytes()).unwrap();
239 match block_on(machine.exec()) {
240 Err(Error::Break) => (),
241 r => panic!("Expected Break but got {:?}", r),
242 }
243
244 assert_eq!(1, *yield_count.borrow());
245 }
246
247 #[test]
248 fn test_drain_signals_ignores_pending_break() {
249 let (tx, rx) = async_channel::unbounded();
250 let mut machine = MachineBuilder::default().with_signals_chan((tx.clone(), rx)).build();
251
252 block_on(tx.send(Signal::Break)).unwrap();
253 machine.drain_signals();
254
255 machine.compile(&mut "a = 1".as_bytes()).unwrap();
256 match block_on(machine.exec()) {
257 Ok(None) => (),
258 r => panic!("Expected Ok(None) but got {:?}", r),
259 }
260 assert_eq!(0, tx.len());
261 }
262
263 fn do_no_check_stop_test(code: &str) {
264 let (tx, rx) = async_channel::unbounded();
265 let mut machine = MachineBuilder::default().with_signals_chan((tx.clone(), rx)).build();
266
267 block_on(tx.send(Signal::Break)).unwrap();
268
269 machine.compile(&mut code.as_bytes()).unwrap();
270 match block_on(machine.exec()) {
271 Ok(None) => (),
272 r => panic!("Expected Ok(None) but got {:?}", r),
273 }
274
275 assert_eq!(1, tx.len());
276 }
277
278 fn do_check_stop_test(code: &str) {
279 let (tx, rx) = async_channel::unbounded();
280 let mut machine = MachineBuilder::default().with_signals_chan((tx.clone(), rx)).build();
281
282 block_on(tx.send(Signal::Break)).unwrap();
283
284 machine.compile(&mut code.as_bytes()).unwrap();
285 match block_on(machine.exec()) {
286 Err(Error::Break) => (),
287 r => panic!("Expected Break but got {:?}", r),
288 }
289
290 assert_eq!(0, tx.len());
291 }
292
293 #[test]
294 fn test_goto_forward_does_not_check_stop() {
295 do_no_check_stop_test("GOTO @after: a = 1: @after");
296 }
297
298 #[test]
299 fn test_if_taken_does_not_check_stop() {
300 do_no_check_stop_test("a = 3: IF a = 3 THEN b = 0 ELSE b = 1: a = 7");
301 }
302
303 #[test]
304 fn test_if_not_taken_does_not_check_stop() {
305 do_no_check_stop_test("a = 3: IF a = 5 THEN b = 0 ELSE b = 1: a = 7");
306 }
307
308 #[test]
309 fn test_goto_checks_stop() {
310 do_check_stop_test("@here: GOTO @here");
311 do_check_stop_test("@before: a = 1: GOTO @before");
312 }
313
314 #[test]
315 fn test_gosub_checks_stop() {
316 do_check_stop_test("GOTO @skip: @sub: a = 1: RETURN: @skip: GOSUB @sub: a = 1");
317 }
318
319 #[test]
320 fn test_do_checks_stop() {
321 do_check_stop_test("DO: LOOP");
322 do_check_stop_test("DO: a = 1: LOOP");
323
324 do_check_stop_test("DO UNTIL FALSE: LOOP");
325 do_check_stop_test("DO UNTIL FALSE: a = 1: LOOP");
326
327 do_check_stop_test("DO WHILE TRUE: LOOP");
328 do_check_stop_test("DO WHILE TRUE: a = 1: LOOP");
329
330 do_check_stop_test("DO: LOOP UNTIL FALSE");
331 do_check_stop_test("DO: a = 1: LOOP UNTIL FALSE");
332
333 do_check_stop_test("DO: LOOP WHILE TRUE");
334 do_check_stop_test("DO: a = 1: LOOP WHILE TRUE");
335 }
336
337 #[test]
338 fn test_for_checks_stop() {
339 do_check_stop_test("FOR a = 1 TO 10: NEXT");
340 do_check_stop_test("FOR a = 1 TO 10: b = 2: NEXT");
341 }
342
343 #[test]
344 fn test_while_checks_stop() {
345 do_check_stop_test("WHILE TRUE: WEND");
346 do_check_stop_test("WHILE TRUE: a = 1: WEND");
347 }
348}