Skip to main content

endbasic_core/compiler/
mod.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//! Converts our AST into bytecode.
17
18use crate::ast::*;
19use crate::bytecode::*;
20use crate::parser;
21use crate::reader::LineCol;
22use crate::syms::{CallableMetadata, CallableMetadataBuilder, Symbol, SymbolKey, Symbols};
23use std::borrow::Cow;
24use std::collections::HashMap;
25#[cfg(test)]
26use std::collections::HashSet;
27use std::io;
28
29mod args;
30pub use args::*;
31mod exprs;
32use exprs::{compile_expr, compile_expr_as_type};
33
34/// Compilation errors.
35#[derive(Debug, thiserror::Error)]
36#[allow(missing_docs)] // The error messages and names are good enough.
37pub enum Error {
38    #[error("{0}: Cannot index array with {1} subscripts; need {2}")]
39    ArrayIndexSubscriptsError(LineCol, usize, usize),
40
41    #[error("{0}: Cannot {1} {2} and {3}")]
42    BinaryOpTypeError(LineCol, &'static str, ExprType, ExprType),
43
44    #[error("{0}: {} expected {}", .1.name(), .1.syntax())]
45    CallableSyntaxError(LineCol, CallableMetadata),
46
47    #[error("{0}: Duplicate label {1}")]
48    DuplicateLabel(LineCol, String),
49
50    #[error("{0}: Cannot assign value of type {1} to variable of type {2}")]
51    IncompatibleTypesInAssignment(LineCol, ExprType, ExprType),
52
53    #[error("{0}: Incompatible type annotation in {1} reference")]
54    IncompatibleTypeAnnotationInReference(LineCol, VarRef),
55
56    #[error("{0}: Cannot index non-array {1}")]
57    IndexNonArray(LineCol, String),
58
59    #[error("{0}: I/O error during compilation: {1}")]
60    IoError(LineCol, io::Error),
61
62    #[error("{0}: EXIT {1} outside of {1}")]
63    MisplacedExit(LineCol, &'static str),
64
65    #[error("{0}: {1} is not a command")]
66    NotACommand(LineCol, VarRef),
67
68    #[error("{0}: {1} is not a number")]
69    NotANumber(LineCol, ExprType),
70
71    #[error("{0}: Requires a reference, not a value")]
72    NotAReference(LineCol),
73
74    #[error("{0}: {1} is not a variable")]
75    NotAVariable(LineCol, VarRef),
76
77    #[error("{0}: {1} is not an array nor a function")]
78    NotArrayOrFunction(LineCol, VarRef),
79
80    #[error("{0}: {1}")]
81    ParseError(LineCol, String),
82
83    #[error("{0}: Cannot define already-defined symbol {1}")]
84    RedefinitionError(LineCol, SymbolKey),
85
86    #[error("{0}: Expected {2} but found {1}")]
87    TypeMismatch(LineCol, ExprType, ExprType),
88
89    #[error("{0}: Cannot {1} {2}")]
90    UnaryOpTypeError(LineCol, &'static str, ExprType),
91
92    #[error("{0}: Undefined symbol {1}")]
93    UndefinedSymbol(LineCol, VarRef),
94
95    #[error("{0}: Unknown label {1}")]
96    UnknownLabel(LineCol, String),
97}
98
99impl From<parser::Error> for Error {
100    fn from(value: parser::Error) -> Self {
101        match value {
102            parser::Error::Bad(pos, message) => Self::ParseError(pos, message),
103            parser::Error::Io(pos, e) => Self::IoError(pos, e),
104        }
105    }
106}
107
108/// Result for compiler return values.
109pub type Result<T> = std::result::Result<T, Error>;
110
111/// Information about a symbol in the symbols table.
112#[derive(Clone)]
113enum SymbolPrototype {
114    /// Information about an array.  The integer indicates the number of dimensions in the array.
115    Array(ExprType, usize),
116
117    /// Information about a callable that's a builtin and requires an upcall to execute.
118    /// The integer indicates the runtime upcall index of the callable.
119    BuiltinCallable(CallableMetadata, usize),
120
121    /// Information about a callable.
122    Callable(CallableMetadata),
123
124    /// Information about a variable.
125    Variable(ExprType),
126}
127
128/// The symbols table used during compilation.
129///
130/// Symbols are represented as a two-layer map: the globals map contains all symbols that are
131/// visible by all scopes, and the scope contains all symbols that are only visible within a
132/// given scope.
133///
134/// The collection of symbols that is visible at any given point in time is thus the union of
135/// the global symbols and the symbols in the last scope.
136///
137/// There is always at least one scope in the scopes stack: at the program level outside of
138/// functions, variables are not global by default and thus they are kept in their own scope.
139/// But because we do not support nested function definitions, the scopes "stack" should
140/// always have size of one or two.
141struct SymbolsTable {
142    /// Map of global symbol names to their definitions.
143    globals: HashMap<SymbolKey, SymbolPrototype>,
144
145    /// Map of local symbol names to their definitions.
146    scopes: Vec<HashMap<SymbolKey, SymbolPrototype>>,
147}
148
149impl Default for SymbolsTable {
150    fn default() -> Self {
151        Self { globals: HashMap::default(), scopes: vec![HashMap::default()] }
152    }
153}
154
155impl From<HashMap<SymbolKey, SymbolPrototype>> for SymbolsTable {
156    fn from(globals: HashMap<SymbolKey, SymbolPrototype>) -> Self {
157        Self { globals, scopes: vec![HashMap::default()] }
158    }
159}
160
161impl From<&Symbols> for SymbolsTable {
162    fn from(syms: &Symbols) -> Self {
163        let globals = {
164            let mut globals = HashMap::default();
165
166            let callables = syms.callables();
167            let mut names = callables.keys().copied().collect::<Vec<&SymbolKey>>();
168            // This is only necessary for testing really... but may also remove some confusion
169            // when inspecting the bytecode because it helps keep upcall indexes stable across
170            // different compilations.
171            names.sort();
172
173            for (i, name) in names.into_iter().enumerate() {
174                let callable = callables.get(&name).unwrap();
175                let proto = SymbolPrototype::BuiltinCallable(callable.metadata().clone(), i);
176                globals.insert(name.clone(), proto);
177            }
178
179            globals
180        };
181
182        let mut scope = HashMap::default();
183        for (name, symbol) in syms.locals() {
184            let proto = match symbol {
185                Symbol::Array(array) => {
186                    SymbolPrototype::Array(array.subtype(), array.dimensions().len())
187                }
188                Symbol::Callable(_) => {
189                    unreachable!("Callables must only be global");
190                }
191                Symbol::Variable(var) => SymbolPrototype::Variable(var.as_exprtype()),
192            };
193            scope.insert(name.clone(), proto);
194        }
195
196        Self { globals, scopes: vec![scope] }
197    }
198}
199
200impl SymbolsTable {
201    /// Enters a new scope.
202    fn enter_scope(&mut self) {
203        self.scopes.push(HashMap::default());
204    }
205
206    /// Leaves the current scope.
207    fn leave_scope(&mut self) {
208        let last = self.scopes.pop();
209        assert!(last.is_some(), "Must have at least one scope to pop");
210        assert!(!self.scopes.is_empty(), "Cannot pop the global scope");
211    }
212
213    /// Returns true if the symbols table contains `key`.
214    fn contains_key(&mut self, key: &SymbolKey) -> bool {
215        self.scopes.last().unwrap().contains_key(key) || self.globals.contains_key(key)
216    }
217
218    /// Returns the information for the symbol `key` if it exists, otherwise `None`.
219    fn get(&self, key: &SymbolKey) -> Option<&SymbolPrototype> {
220        let proto = self.scopes.last().unwrap().get(key);
221        if proto.is_some() {
222            return proto;
223        }
224
225        self.globals.get(key)
226    }
227
228    /// Inserts the new information `proto` about symbol `key` into the symbols table.
229    /// The symbol must not yet exist.
230    fn insert(&mut self, key: SymbolKey, proto: SymbolPrototype) {
231        debug_assert!(!self.globals.contains_key(&key), "Cannot redefine a symbol");
232        let previous = self.scopes.last_mut().unwrap().insert(key, proto);
233        debug_assert!(previous.is_none(), "Cannot redefine a symbol");
234    }
235
236    /// Inserts the builtin callable described by `md` and assigns an upcall index.
237    /// The symbol must not yet exist.
238    #[cfg(test)]
239    fn insert_builtin_callable(&mut self, key: SymbolKey, md: CallableMetadata) {
240        let next_upcall_index = self
241            .globals
242            .values()
243            .filter(|proto| matches!(proto, SymbolPrototype::BuiltinCallable(..)))
244            .count();
245
246        debug_assert!(!self.globals.contains_key(&key), "Cannot redefine a symbol");
247        let proto = SymbolPrototype::BuiltinCallable(md, next_upcall_index);
248        let previous = self.globals.insert(key, proto);
249        debug_assert!(previous.is_none(), "Cannot redefine a symbol");
250    }
251
252    /// Inserts the new information `proto` about symbol `key` into the symbols table.
253    /// The symbol must not yet exist.
254    fn insert_global(&mut self, key: SymbolKey, proto: SymbolPrototype) {
255        let previous = self.globals.insert(key, proto);
256        debug_assert!(previous.is_none(), "Cannot redefine a symbol");
257    }
258
259    /// Removes information about the symbol `key`.
260    fn remove(&mut self, key: SymbolKey) {
261        let previous = self.scopes.last_mut().unwrap().remove(&key);
262        debug_assert!(previous.is_some(), "Cannot unset a non-existing symbol");
263    }
264
265    /// Returns a view of the keys in the symbols table.
266    #[cfg(test)]
267    fn keys(&self) -> HashSet<&SymbolKey> {
268        let mut keys = HashSet::default();
269        keys.extend(self.globals.keys());
270        keys.extend(self.scopes.last().unwrap().keys());
271        keys
272    }
273
274    /// Calculates the list of upcalls in this symbols table in the order in which they were
275    /// assigned indexes.
276    fn upcalls(&self) -> Vec<SymbolKey> {
277        let mut builtins = self
278            .globals
279            .iter()
280            .filter_map(|(key, proto)| {
281                if let SymbolPrototype::BuiltinCallable(_md, upcall_index) = proto {
282                    Some((upcall_index, key))
283                } else {
284                    None
285                }
286            })
287            .collect::<Vec<(&usize, &SymbolKey)>>();
288        builtins.sort_by_key(|(upcall_index, _key)| *upcall_index);
289        builtins.into_iter().map(|(_upcall_index, key)| key.clone()).collect()
290    }
291}
292
293/// Describes a location in the code needs fixing up after all addresses have been laid out.
294#[cfg_attr(test, derive(Debug, PartialEq))]
295enum Fixup {
296    CallAddr(String, LineCol),
297    Call(VarRef, LineCol),
298    GotoAddr(String, LineCol),
299    OnErrorGotoAddr(String, LineCol),
300}
301
302impl Fixup {
303    /// Constructs a `Fixup` for an `EXIT` instruction.
304    fn from_exit(target: String, span: ExitSpan) -> Self {
305        Self::GotoAddr(target, span.pos)
306    }
307
308    /// Constructs a `Fixup` for a `GOSUB` instruction.
309    fn from_gosub(span: GotoSpan) -> Self {
310        Self::CallAddr(span.target, span.target_pos)
311    }
312
313    /// Constructs a `Fixup` for a `GOTO` instruction.
314    fn from_goto(span: GotoSpan) -> Self {
315        Self::GotoAddr(span.target, span.target_pos)
316    }
317
318    /// Constructs a `Fixup` for a `ON ERROR GOTO` instruction.
319    fn from_on_error(span: GotoSpan) -> Self {
320        Self::OnErrorGotoAddr(span.target, span.target_pos)
321    }
322}
323
324/// Compilation context to accumulate the results of the translation of various translation units.
325#[derive(Default)]
326struct Compiler {
327    /// Current nesting of `DO` loops, needed to assign targets for `EXIT DO` statements.
328    ///
329    /// The first component of this pair indicates which block of `EXIT DO`s we are dealing with and
330    /// the second component indicates their nesting.  Every time the second component reaches zero,
331    /// the first component has to be incremented.
332    exit_do_level: (usize, usize),
333
334    /// Current nesting of `FOR` loops, needed to assign targets for `EXIT FOR` statements.
335    ///
336    /// The first component of this pair indicates which block of `EXIT FOR`s we are dealing with and
337    /// the second component indicates their nesting.  Every time the second component reaches zero,
338    /// the first component has to be incremented.
339    exit_for_level: (usize, usize),
340
341    /// Current number of `SELECT` statements, needed to assign internal variable names.
342    selects: usize,
343
344    /// Mapping of discovered labels to the addresses where they are.
345    labels: HashMap<String, Address>,
346
347    /// Mapping of addresses that need fixing up to the type of the fixup they require.
348    fixups: HashMap<Address, Fixup>,
349
350    /// Instructions compiled so far.
351    instrs: Vec<Instruction>,
352
353    /// Data discovered so far.
354    data: Vec<Option<Expr>>,
355
356    /// Symbols table.
357    symtable: SymbolsTable,
358
359    /// Name of the function being compiled, needed to set the return value in assignment operators.
360    ///
361    /// The boolean indicates whether this callable has a return value or not and is used to
362    /// differentiate between functions and subroutines.
363    current_callable: Option<(SymbolKey, bool)>,
364
365    /// Callables to be compiled.
366    callable_spans: Vec<CallableSpan>,
367}
368
369impl Compiler {
370    /// Appends an instruction to the bytecode and returns its address.
371    fn emit(&mut self, instr: Instruction) -> Address {
372        self.instrs.push(instr);
373        self.instrs.len() - 1
374    }
375
376    /// Generates a fake label for the epilogue a callable based on its `name`.
377    ///
378    /// This is a little hack to reuse the same machinery that handles `GOTO`s to handle early
379    /// `EXIT`s.  We can do this because we know that users cannot specify custom labels that
380    /// start with a digit and all user-provided labels that do start with a digit are also fully
381    /// numeric.
382    fn exit_label_for_callable(name: &SymbolKey) -> String {
383        format!("0{}", name)
384    }
385
386    /// Generates a fake label for the end of a scope based on the current nesting `level` and the
387    /// `name` of the loop.
388    ///
389    /// This is a little hack to reuse the same machinery that handles `GOTO`s to handle early
390    /// `EXIT`s.  We can do this because we know that users cannot specify custom labels that
391    /// start with a digit and all user-provided labels that do start with a digit are also fully
392    /// numeric.
393    fn exit_label_for_loop(name: &'static str, level: (usize, usize)) -> String {
394        format!("0{}{}_{}", name, level.0, level.1)
395    }
396
397    /// Generates the name of the symbol that holds the return value of the function `name`.
398    fn return_key(name: &SymbolKey) -> SymbolKey {
399        SymbolKey::from(format!("0return_{}", name))
400    }
401
402    /// Generates an internal variable name to hold the result of a `SELECT` test evaluation.
403    ///
404    /// This is a little hack needed because we don't yet have a value stack nor registers.  We can
405    /// do this because we know that users cannot specify custom variable names that start with a
406    /// digit.
407    fn select_test_var_name(selects: usize) -> String {
408        format!("0select{}", selects)
409    }
410
411    /// Emits the necessary casts to convert the value at the top of the stack from its type `from`
412    /// to the new type `target`.
413    ///
414    /// Returns `target` if the conversion is possible, or `from` otherwise.
415    fn maybe_cast(&mut self, target: ExprType, from: ExprType) -> ExprType {
416        match (target, from) {
417            (ExprType::Double, ExprType::Integer) => {
418                self.emit(Instruction::IntegerToDouble);
419                target
420            }
421            (ExprType::Integer, ExprType::Double) => {
422                self.emit(Instruction::DoubleToInteger);
423                target
424            }
425            _ => from,
426        }
427    }
428
429    /// Compiles the indices used to address an array.
430    fn compile_array_indices(
431        &mut self,
432        exp_nargs: usize,
433        args: Vec<Expr>,
434        name_pos: LineCol,
435    ) -> Result<()> {
436        let mut instrs = vec![];
437        match exprs::compile_array_indices(
438            &mut instrs,
439            &mut self.fixups,
440            &self.symtable,
441            exp_nargs,
442            args,
443            name_pos,
444        ) {
445            Ok(result) => {
446                self.instrs.append(&mut instrs);
447                Ok(result)
448            }
449            Err(e) => Err(e),
450        }
451    }
452
453    /// Compiles an assignment to an array position.
454    fn compile_array_assignment(&mut self, span: ArrayAssignmentSpan) -> Result<()> {
455        let key = SymbolKey::from(&span.vref.name);
456        let (atype, dims) = match self.symtable.get(&key) {
457            Some(SymbolPrototype::Array(atype, dims)) => (*atype, *dims),
458            Some(_) => {
459                return Err(Error::IndexNonArray(span.vref_pos, span.vref.name));
460            }
461            None => {
462                return Err(Error::UndefinedSymbol(span.vref_pos, span.vref.clone()));
463            }
464        };
465
466        if !span.vref.accepts(atype) {
467            return Err(Error::IncompatibleTypeAnnotationInReference(span.vref_pos, span.vref));
468        }
469
470        let etype = self.compile_expr(span.expr)?;
471        let etype = self.maybe_cast(atype, etype);
472        if etype != atype {
473            return Err(Error::IncompatibleTypesInAssignment(span.vref_pos, etype, atype));
474        }
475
476        let nargs = span.subscripts.len();
477        self.compile_array_indices(dims, span.subscripts, span.vref_pos)?;
478
479        self.emit(Instruction::ArrayAssignment(key, span.vref_pos, nargs));
480
481        Ok(())
482    }
483
484    /// Compiles an assignment, be it from the code or one synthesized during compilation.
485    ///
486    /// It's important to always use this function instead of manually emitting `Instruction::Assign`
487    /// instructions to ensure consistent handling of the symbols table.
488    fn compile_assignment(&mut self, vref: VarRef, vref_pos: LineCol, expr: Expr) -> Result<()> {
489        let mut key = SymbolKey::from(&vref.name);
490        let etype = self.compile_expr(expr)?;
491
492        if let Some(current_callable) = self.current_callable.as_ref()
493            && key == current_callable.0
494        {
495            key = Compiler::return_key(&current_callable.0);
496        }
497
498        let vtype = match self.symtable.get(&key) {
499            Some(SymbolPrototype::Variable(vtype)) => *vtype,
500            Some(_) => return Err(Error::RedefinitionError(vref_pos, key)),
501            None => {
502                // TODO(jmmv): Compile separate Dim instructions for new variables instead of
503                // checking this every time.
504                let key = key.clone();
505                let vtype = vref.ref_type.unwrap_or(etype);
506                self.symtable.insert(key, SymbolPrototype::Variable(vtype));
507                vtype
508            }
509        };
510
511        let etype = self.maybe_cast(vtype, etype);
512        if etype != vtype {
513            return Err(Error::IncompatibleTypesInAssignment(vref_pos, etype, vtype));
514        }
515
516        if let Some(ref_type) = vref.ref_type
517            && ref_type != vtype
518        {
519            return Err(Error::IncompatibleTypeAnnotationInReference(vref_pos, vref));
520        }
521
522        self.emit(Instruction::Assign(key));
523
524        Ok(())
525    }
526
527    /// Compiles a `FUNCTION` or `SUB` definition.
528    fn compile_callable(&mut self, span: CallableSpan) -> Result<()> {
529        let key = SymbolKey::from(&span.name.name);
530        if self.symtable.contains_key(&key) {
531            return Err(Error::RedefinitionError(span.name_pos, key));
532        }
533
534        let mut syntax = vec![];
535        for (i, param) in span.params.iter().enumerate() {
536            let sep = if i == span.params.len() - 1 {
537                ArgSepSyntax::End
538            } else {
539                ArgSepSyntax::Exactly(ArgSep::Long)
540            };
541            syntax.push(SingularArgSyntax::RequiredValue(
542                RequiredValueSyntax {
543                    name: Cow::Owned(param.name.to_owned()),
544                    vtype: param.ref_type.unwrap_or(ExprType::Integer),
545                },
546                sep,
547            ));
548        }
549
550        let mut builder = CallableMetadataBuilder::new_dynamic(span.name.name.to_owned())
551            .with_dynamic_syntax(vec![(syntax, None)]);
552        if let Some(ctype) = span.name.ref_type {
553            builder = builder.with_return_type(ctype);
554        }
555        self.symtable.insert_global(key, SymbolPrototype::Callable(builder.build()));
556        self.callable_spans.push(span);
557
558        Ok(())
559    }
560
561    /// Compiles a `DIM` statement.
562    fn compile_dim(&mut self, span: DimSpan) -> Result<()> {
563        let key = SymbolKey::from(&span.name);
564        if self.symtable.contains_key(&key) {
565            return Err(Error::RedefinitionError(span.name_pos, key));
566        }
567
568        self.emit(Instruction::Dim(DimISpan {
569            name: key.clone(),
570            shared: span.shared,
571            vtype: span.vtype,
572        }));
573        if span.shared {
574            self.symtable.insert_global(key, SymbolPrototype::Variable(span.vtype));
575        } else {
576            self.symtable.insert(key, SymbolPrototype::Variable(span.vtype));
577        }
578
579        Ok(())
580    }
581
582    /// Compiles a `DO` loop and appends its instructions to the compilation context.
583    fn compile_do(&mut self, span: DoSpan) -> Result<()> {
584        self.exit_do_level.1 += 1;
585
586        let end_pc;
587        match span.guard {
588            DoGuard::Infinite => {
589                let start_pc = self.instrs.len();
590                self.compile_many(span.body)?;
591                end_pc = self.emit(Instruction::Jump(JumpISpan { addr: start_pc }));
592            }
593
594            DoGuard::PreUntil(guard) => {
595                let start_pc = self.instrs.len();
596                self.compile_expr_guard(guard)?;
597                let jump_pc = self.emit(Instruction::Nop);
598                self.compile_many(span.body)?;
599                end_pc = self.emit(Instruction::Jump(JumpISpan { addr: start_pc }));
600                self.instrs[jump_pc] = Instruction::JumpIfTrue(self.instrs.len());
601            }
602
603            DoGuard::PreWhile(guard) => {
604                let start_pc = self.instrs.len();
605                self.compile_expr_guard(guard)?;
606                let jump_pc = self.emit(Instruction::Nop);
607                self.compile_many(span.body)?;
608                end_pc = self.emit(Instruction::Jump(JumpISpan { addr: start_pc }));
609                self.instrs[jump_pc] = Instruction::JumpIfNotTrue(self.instrs.len());
610            }
611
612            DoGuard::PostUntil(guard) => {
613                let start_pc = self.instrs.len();
614                self.compile_many(span.body)?;
615                self.compile_expr_guard(guard)?;
616                end_pc = self.emit(Instruction::JumpIfNotTrue(start_pc));
617            }
618
619            DoGuard::PostWhile(guard) => {
620                let start_pc = self.instrs.len();
621                self.compile_many(span.body)?;
622                self.compile_expr_guard(guard)?;
623                end_pc = self.emit(Instruction::JumpIfTrue(start_pc));
624            }
625        }
626
627        let existing =
628            self.labels.insert(Compiler::exit_label_for_loop("do", self.exit_do_level), end_pc + 1);
629        assert!(existing.is_none(), "Auto-generated label must be unique");
630        self.exit_do_level.1 -= 1;
631        if self.exit_do_level.1 == 0 {
632            self.exit_do_level.0 += 1;
633        }
634
635        Ok(())
636    }
637
638    /// Compiles a `FOR` loop and appends its instructions to the compilation context.
639    fn compile_for(&mut self, span: ForSpan) -> Result<()> {
640        debug_assert!(
641            span.iter.ref_type.is_none()
642                || span.iter.ref_type.unwrap() == ExprType::Double
643                || span.iter.ref_type.unwrap() == ExprType::Integer
644        );
645
646        self.exit_for_level.1 += 1;
647
648        if span.iter_double && span.iter.ref_type.is_none() {
649            let key = SymbolKey::from(&span.iter.name);
650            let skip_pc = self.emit(Instruction::Nop);
651
652            if self.symtable.get(&key).is_none() {
653                self.emit(Instruction::Dim(DimISpan {
654                    name: key.clone(),
655                    shared: false,
656                    vtype: ExprType::Double,
657                }));
658                self.symtable.insert(key.clone(), SymbolPrototype::Variable(ExprType::Double));
659            }
660
661            self.instrs[skip_pc] = Instruction::JumpIfDefined(JumpIfDefinedISpan {
662                var: key,
663                addr: self.instrs.len(),
664            });
665        }
666
667        self.compile_assignment(span.iter.clone(), span.iter_pos, span.start)?;
668
669        let start_pc = self.instrs.len();
670        let end_etype = self.compile_expr(span.end)?;
671        match end_etype {
672            ExprType::Boolean => (),
673            _ => panic!("Synthesized end condition for FOR must yield a boolean"),
674        };
675        let jump_pc = self.emit(Instruction::Nop);
676
677        self.compile_many(span.body)?;
678
679        self.compile_assignment(span.iter.clone(), span.iter_pos, span.next)?;
680
681        let end_pc = self.emit(Instruction::Jump(JumpISpan { addr: start_pc }));
682
683        self.instrs[jump_pc] = Instruction::JumpIfNotTrue(self.instrs.len());
684
685        let existing = self
686            .labels
687            .insert(Compiler::exit_label_for_loop("for", self.exit_for_level), end_pc + 1);
688        assert!(existing.is_none(), "Auto-generated label must be unique");
689        self.exit_for_level.1 -= 1;
690        if self.exit_for_level.1 == 0 {
691            self.exit_for_level.0 += 1;
692        }
693
694        Ok(())
695    }
696
697    /// Compiles an `IF` statement and appends its instructions to the compilation context.
698    fn compile_if(&mut self, span: IfSpan) -> Result<()> {
699        let mut end_pcs = vec![];
700
701        let mut iter = span.branches.into_iter();
702        let mut next = iter.next();
703        while let Some(branch) = next {
704            let next2 = iter.next();
705
706            self.compile_expr_guard(branch.guard)?;
707            let jump_pc = self.emit(Instruction::Nop);
708            self.compile_many(branch.body)?;
709
710            if next2.is_some() {
711                end_pcs.push(self.instrs.len());
712                self.emit(Instruction::Nop);
713            }
714
715            self.instrs[jump_pc] = Instruction::JumpIfNotTrue(self.instrs.len());
716
717            next = next2;
718        }
719
720        for end_pc in end_pcs {
721            self.instrs[end_pc] = Instruction::Jump(JumpISpan { addr: self.instrs.len() });
722        }
723
724        Ok(())
725    }
726
727    /// Compiles an `ON ERROR` statement and appends its instructions to the compilation context.
728    fn compile_on_error(&mut self, span: OnErrorSpan) {
729        match span {
730            OnErrorSpan::Goto(span) => {
731                let goto_pc = self.emit(Instruction::Nop);
732                self.fixups.insert(goto_pc, Fixup::from_on_error(span));
733            }
734            OnErrorSpan::Reset => {
735                self.emit(Instruction::SetErrorHandler(ErrorHandlerISpan::None));
736            }
737            OnErrorSpan::ResumeNext => {
738                self.emit(Instruction::SetErrorHandler(ErrorHandlerISpan::ResumeNext));
739            }
740        }
741    }
742
743    /// Generates the expression to evaluate a list of `guards`, which are compared against the
744    /// test expression stored in `test_vref`.
745    fn compile_case_guards(test_vref: &VarRef, guards: Vec<CaseGuardSpan>) -> Option<Expr> {
746        let mut expr = None;
747        for guard in guards {
748            let one_expr = match guard {
749                CaseGuardSpan::Is(relop, expr) => {
750                    let pos = expr.start_pos();
751                    let test_expr = Expr::Symbol(SymbolSpan { vref: test_vref.clone(), pos });
752                    let binop = Box::from(BinaryOpSpan { lhs: test_expr, rhs: expr, pos });
753                    match relop {
754                        CaseRelOp::Equal => Expr::Equal(binop),
755                        CaseRelOp::NotEqual => Expr::NotEqual(binop),
756                        CaseRelOp::Less => Expr::Less(binop),
757                        CaseRelOp::LessEqual => Expr::LessEqual(binop),
758                        CaseRelOp::Greater => Expr::Greater(binop),
759                        CaseRelOp::GreaterEqual => Expr::GreaterEqual(binop),
760                    }
761                }
762
763                CaseGuardSpan::To(from_expr, to_expr) => {
764                    let from_pos = from_expr.start_pos();
765                    let to_pos = to_expr.start_pos();
766                    let test_expr =
767                        Expr::Symbol(SymbolSpan { vref: test_vref.clone(), pos: from_pos });
768                    Expr::And(Box::from(BinaryOpSpan {
769                        lhs: Expr::GreaterEqual(Box::from(BinaryOpSpan {
770                            lhs: test_expr.clone(),
771                            rhs: from_expr,
772                            pos: from_pos,
773                        })),
774                        rhs: Expr::LessEqual(Box::from(BinaryOpSpan {
775                            lhs: test_expr,
776                            rhs: to_expr,
777                            pos: to_pos,
778                        })),
779                        pos: from_pos,
780                    }))
781                }
782            };
783
784            expr = match expr {
785                None => Some(one_expr),
786                Some(expr) => {
787                    let pos = expr.start_pos();
788                    Some(Expr::Or(Box::from(BinaryOpSpan { lhs: expr, rhs: one_expr, pos })))
789                }
790            };
791        }
792        expr
793    }
794
795    /// Compiles a `SELECT` statement and appends its instructions to the compilation context.
796    fn compile_select(&mut self, span: SelectSpan) -> Result<()> {
797        let mut end_pcs = vec![];
798
799        self.selects += 1;
800        let test_vref = VarRef::new(Compiler::select_test_var_name(self.selects), None);
801        self.compile_assignment(test_vref.clone(), span.expr.start_pos(), span.expr)?;
802
803        let mut iter = span.cases.into_iter();
804        let mut next = iter.next();
805        while let Some(case) = next {
806            let next2 = iter.next();
807
808            match Compiler::compile_case_guards(&test_vref, case.guards) {
809                None => {
810                    self.compile_many(case.body)?;
811                }
812                Some(guard) => {
813                    self.compile_expr_guard(guard)?;
814                    let jump_pc = self.emit(Instruction::Nop);
815                    self.compile_many(case.body)?;
816
817                    if next2.is_some() {
818                        end_pcs.push(self.instrs.len());
819                        self.emit(Instruction::Nop);
820                    }
821
822                    self.instrs[jump_pc] = Instruction::JumpIfNotTrue(self.instrs.len());
823                }
824            }
825
826            next = next2;
827        }
828
829        for end_pc in end_pcs {
830            self.instrs[end_pc] = Instruction::Jump(JumpISpan { addr: self.instrs.len() });
831        }
832
833        let test_key = SymbolKey::from(test_vref.name);
834        self.emit(Instruction::Unset(UnsetISpan { name: test_key.clone(), pos: span.end_pos }));
835        self.symtable.remove(test_key);
836
837        Ok(())
838    }
839
840    /// Compiles a `WHILE` loop and appends its instructions to the compilation context.
841    fn compile_while(&mut self, span: WhileSpan) -> Result<()> {
842        let start_pc = self.instrs.len();
843        self.compile_expr_guard(span.expr)?;
844        let jump_pc = self.emit(Instruction::Nop);
845
846        self.compile_many(span.body)?;
847
848        self.emit(Instruction::Jump(JumpISpan { addr: start_pc }));
849
850        self.instrs[jump_pc] = Instruction::JumpIfNotTrue(self.instrs.len());
851
852        Ok(())
853    }
854
855    /// Compiles the evaluation of an expression, appends its instructions to the
856    /// compilation context, and returns the type of the compiled expression.
857    fn compile_expr(&mut self, expr: Expr) -> Result<ExprType> {
858        match compile_expr(&mut self.instrs, &mut self.fixups, &self.symtable, expr, false) {
859            Ok(result) => Ok(result),
860            Err(e) => Err(e),
861        }
862    }
863
864    /// Compiles the evaluation of an expression with casts to a target type, appends its
865    /// instructions to the compilation context, and returns the type of the compiled expression.
866    fn compile_expr_as_type(&mut self, expr: Expr, target: ExprType) -> Result<()> {
867        compile_expr_as_type(&mut self.instrs, &mut self.fixups, &self.symtable, expr, target)?;
868        Ok(())
869    }
870
871    /// Compiles an expression that guards a conditional statement.  Returns an error if the
872    /// expression is invalid or if it does not evaluate to a boolean.
873    fn compile_expr_guard(&mut self, guard: Expr) -> Result<()> {
874        let pos = guard.start_pos();
875        match self.compile_expr(guard)? {
876            ExprType::Boolean => Ok(()),
877            etype => Err(Error::TypeMismatch(pos, etype, ExprType::Boolean)),
878        }
879    }
880
881    /// Compiles one statement and appends its bytecode to the current compilation context.
882    fn compile_one(&mut self, stmt: Statement) -> Result<()> {
883        match stmt {
884            Statement::ArrayAssignment(span) => {
885                self.compile_array_assignment(span)?;
886            }
887
888            Statement::Assignment(span) => {
889                self.compile_assignment(span.vref, span.vref_pos, span.expr)?;
890            }
891
892            Statement::Call(span) => {
893                let key = SymbolKey::from(&span.vref.name);
894                let (md, upcall_index) = match self.symtable.get(&key) {
895                    Some(SymbolPrototype::BuiltinCallable(md, upcall_index)) => {
896                        if md.is_function() {
897                            return Err(Error::NotACommand(span.vref_pos, span.vref));
898                        }
899                        (md.clone(), Some(*upcall_index))
900                    }
901
902                    Some(SymbolPrototype::Callable(md)) => {
903                        if md.is_function() {
904                            return Err(Error::NotACommand(span.vref_pos, span.vref));
905                        }
906                        (md.clone(), None)
907                    }
908
909                    Some(_) => {
910                        return Err(Error::NotACommand(span.vref_pos, span.vref));
911                    }
912
913                    None => return Err(Error::UndefinedSymbol(span.vref_pos, span.vref)),
914                };
915
916                let name_pos = span.vref_pos;
917                let nargs = compile_command_args(
918                    &md,
919                    &mut self.instrs,
920                    &mut self.fixups,
921                    &mut self.symtable,
922                    name_pos,
923                    span.args,
924                )?;
925                if let Some(upcall_index) = upcall_index {
926                    self.emit(Instruction::BuiltinCall(BuiltinCallISpan {
927                        name: key,
928                        name_pos: span.vref_pos,
929                        upcall_index,
930                        nargs,
931                    }));
932                } else {
933                    let call_pc = self.emit(Instruction::Nop);
934                    self.fixups.insert(call_pc, Fixup::Call(span.vref, span.vref_pos));
935                }
936            }
937
938            Statement::Callable(span) => {
939                self.compile_callable(span)?;
940            }
941
942            Statement::Data(mut span) => {
943                self.data.append(&mut span.values);
944            }
945
946            Statement::Dim(span) => {
947                self.compile_dim(span)?;
948            }
949
950            Statement::DimArray(span) => {
951                let key = SymbolKey::from(&span.name);
952                if self.symtable.contains_key(&key) {
953                    return Err(Error::RedefinitionError(span.name_pos, key));
954                }
955
956                let nargs = span.dimensions.len();
957                for arg in span.dimensions.into_iter().rev() {
958                    self.compile_expr_as_type(arg, ExprType::Integer)?;
959                }
960                self.emit(Instruction::DimArray(DimArrayISpan {
961                    name: key.clone(),
962                    name_pos: span.name_pos,
963                    shared: span.shared,
964                    dimensions: nargs,
965                    subtype: span.subtype,
966                    subtype_pos: span.subtype_pos,
967                }));
968
969                if span.shared {
970                    self.symtable.insert_global(key, SymbolPrototype::Array(span.subtype, nargs));
971                } else {
972                    self.symtable.insert(key, SymbolPrototype::Array(span.subtype, nargs));
973                }
974            }
975
976            Statement::Do(span) => {
977                self.compile_do(span)?;
978            }
979
980            Statement::End(span) => match span.code {
981                Some(expr) => {
982                    self.compile_expr_as_type(expr, ExprType::Integer)?;
983                    self.emit(Instruction::End(true));
984                }
985                None => {
986                    self.emit(Instruction::End(false));
987                }
988            },
989
990            Statement::ExitDo(span) => {
991                if self.exit_do_level.1 == 0 {
992                    return Err(Error::MisplacedExit(span.pos, "DO"));
993                }
994                let exit_do_pc = self.emit(Instruction::Nop);
995                self.fixups.insert(
996                    exit_do_pc,
997                    Fixup::from_exit(Compiler::exit_label_for_loop("do", self.exit_do_level), span),
998                );
999            }
1000
1001            Statement::ExitFor(span) => {
1002                if self.exit_for_level.1 == 0 {
1003                    return Err(Error::MisplacedExit(span.pos, "FOR"));
1004                }
1005                let exit_for_pc = self.emit(Instruction::Nop);
1006                self.fixups.insert(
1007                    exit_for_pc,
1008                    Fixup::from_exit(
1009                        Compiler::exit_label_for_loop("for", self.exit_for_level),
1010                        span,
1011                    ),
1012                );
1013            }
1014
1015            Statement::ExitFunction(span) => {
1016                let Some(current_callable) = self.current_callable.as_ref() else {
1017                    return Err(Error::MisplacedExit(span.pos, "FUNCTION"));
1018                };
1019                if !current_callable.1 {
1020                    return Err(Error::MisplacedExit(span.pos, "FUNCTION"));
1021                }
1022
1023                let exit_label = Compiler::exit_label_for_callable(&current_callable.0);
1024                let exit_function_pc = self.emit(Instruction::Nop);
1025                self.fixups.insert(exit_function_pc, Fixup::from_exit(exit_label, span));
1026            }
1027
1028            Statement::ExitSub(span) => {
1029                let Some(current_callable) = self.current_callable.as_ref() else {
1030                    return Err(Error::MisplacedExit(span.pos, "SUB"));
1031                };
1032                if current_callable.1 {
1033                    return Err(Error::MisplacedExit(span.pos, "SUB"));
1034                }
1035
1036                let exit_label = Compiler::exit_label_for_callable(&current_callable.0);
1037                let exit_sub_pc = self.emit(Instruction::Nop);
1038                self.fixups.insert(exit_sub_pc, Fixup::from_exit(exit_label, span));
1039            }
1040
1041            Statement::For(span) => {
1042                self.compile_for(span)?;
1043            }
1044
1045            Statement::Gosub(span) => {
1046                let gosub_pc = self.emit(Instruction::Nop);
1047                self.fixups.insert(gosub_pc, Fixup::from_gosub(span));
1048            }
1049
1050            Statement::Goto(span) => {
1051                let goto_pc = self.emit(Instruction::Nop);
1052                self.fixups.insert(goto_pc, Fixup::from_goto(span));
1053            }
1054
1055            Statement::If(span) => {
1056                self.compile_if(span)?;
1057            }
1058
1059            Statement::Label(span) => {
1060                if self.labels.insert(span.name.clone(), self.instrs.len()).is_some() {
1061                    return Err(Error::DuplicateLabel(span.name_pos, span.name));
1062                }
1063            }
1064
1065            Statement::OnError(span) => {
1066                self.compile_on_error(span);
1067            }
1068
1069            Statement::Return(span) => {
1070                self.emit(Instruction::Return(span.pos));
1071            }
1072
1073            Statement::Select(span) => {
1074                self.compile_select(span)?;
1075            }
1076
1077            Statement::While(span) => {
1078                self.compile_while(span)?;
1079            }
1080        }
1081
1082        Ok(())
1083    }
1084
1085    /// Compiles a collection of statements and appends their bytecode to the current compilation
1086    /// context.
1087    fn compile_many(&mut self, stmts: Vec<Statement>) -> Result<()> {
1088        for stmt in stmts {
1089            self.compile_one(stmt)?;
1090        }
1091
1092        Ok(())
1093    }
1094
1095    /// Compiles all callables discovered during the first phase and fixes up all call sites to
1096    /// point to the compiled code.
1097    ///
1098    /// Returns a mapping of function and subroutine names to start addresses.
1099    fn compile_callables(&mut self) -> Result<HashMap<SymbolKey, usize>> {
1100        if self.callable_spans.is_empty() {
1101            return Ok(HashMap::default());
1102        }
1103
1104        let end = self.emit(Instruction::Nop);
1105
1106        let mut callables = HashMap::with_capacity(self.callable_spans.len());
1107        let callable_spans = std::mem::take(&mut self.callable_spans);
1108        for span in callable_spans {
1109            let pc = self.instrs.len();
1110
1111            let key = SymbolKey::from(span.name.name);
1112            let return_value = Compiler::return_key(&key);
1113            match span.name.ref_type {
1114                Some(return_type) => {
1115                    self.emit(Instruction::EnterScope);
1116                    self.symtable.enter_scope();
1117
1118                    self.emit(Instruction::Dim(DimISpan {
1119                        name: return_value.clone(),
1120                        shared: false,
1121                        vtype: return_type,
1122                    }));
1123                    self.symtable
1124                        .insert(return_value.clone(), SymbolPrototype::Variable(return_type));
1125
1126                    for param in span.params {
1127                        let key = SymbolKey::from(param.name);
1128                        let ptype = param.ref_type.unwrap_or(ExprType::Integer);
1129                        self.emit(Instruction::Assign(key.clone()));
1130                        self.symtable.insert(key, SymbolPrototype::Variable(ptype));
1131                    }
1132
1133                    debug_assert!(self.current_callable.is_none(), "Callables cannot be nested");
1134                    self.current_callable = Some((key.clone(), true));
1135                    self.compile_many(span.body)?;
1136                    self.current_callable = None;
1137
1138                    let load_inst = match return_type {
1139                        ExprType::Boolean => Instruction::LoadBoolean,
1140                        ExprType::Double => Instruction::LoadDouble,
1141                        ExprType::Integer => Instruction::LoadInteger,
1142                        ExprType::Text => Instruction::LoadString,
1143                    };
1144                    let epilogue_pc = self.emit(load_inst(return_value.clone(), span.end_pos));
1145
1146                    self.emit(Instruction::LeaveScope);
1147                    self.symtable.leave_scope();
1148                    self.emit(Instruction::Return(span.end_pos));
1149
1150                    let existing =
1151                        self.labels.insert(Compiler::exit_label_for_callable(&key), epilogue_pc);
1152                    assert!(existing.is_none(), "Auto-generated label must be unique");
1153
1154                    callables.insert(key, pc);
1155                }
1156                None => {
1157                    self.emit(Instruction::EnterScope);
1158                    self.symtable.enter_scope();
1159
1160                    for param in span.params {
1161                        let key = SymbolKey::from(param.name);
1162                        let ptype = param.ref_type.unwrap_or(ExprType::Integer);
1163                        self.emit(Instruction::Assign(key.clone()));
1164                        self.symtable.insert(key, SymbolPrototype::Variable(ptype));
1165                    }
1166
1167                    debug_assert!(self.current_callable.is_none(), "Callables cannot be nested");
1168                    self.current_callable = Some((key.clone(), false));
1169                    self.compile_many(span.body)?;
1170                    self.current_callable = None;
1171
1172                    let epilogue_pc = self.emit(Instruction::LeaveScope);
1173                    self.symtable.leave_scope();
1174                    self.emit(Instruction::Return(span.end_pos));
1175
1176                    let existing =
1177                        self.labels.insert(Compiler::exit_label_for_callable(&key), epilogue_pc);
1178                    assert!(existing.is_none(), "Auto-generated label must be unique");
1179
1180                    callables.insert(key, pc);
1181                }
1182            }
1183        }
1184
1185        self.instrs[end] = Instruction::Jump(JumpISpan { addr: self.instrs.len() });
1186
1187        Ok(callables)
1188    }
1189
1190    /// Gets the address of a label.
1191    fn get_label_addr(
1192        labels: &HashMap<String, Address>,
1193        label: String,
1194        pos: LineCol,
1195    ) -> Result<usize> {
1196        match labels.get(&label) {
1197            Some(addr) => Ok(*addr),
1198            None => Err(Error::UnknownLabel(pos, label.to_owned())),
1199        }
1200    }
1201
1202    /// Finishes compilation and returns the image representing the compiled program.
1203    #[allow(clippy::wrong_self_convention)]
1204    fn to_image(mut self) -> Result<(Image, SymbolsTable)> {
1205        let callables = self.compile_callables()?;
1206
1207        for (pc, fixup) in self.fixups {
1208            let new_instr = match fixup {
1209                Fixup::CallAddr(label, pos) => {
1210                    let addr = Self::get_label_addr(&self.labels, label, pos)?;
1211                    Instruction::Call(JumpISpan { addr })
1212                }
1213
1214                Fixup::Call(vref, pos) => {
1215                    let key = SymbolKey::from(&vref.name);
1216                    let Some(addr) = callables.get(&key) else {
1217                        return Err(Error::UndefinedSymbol(pos, vref));
1218                    };
1219                    Instruction::Call(JumpISpan { addr: *addr })
1220                }
1221
1222                Fixup::GotoAddr(label, pos) => {
1223                    let addr = Self::get_label_addr(&self.labels, label, pos)?;
1224                    Instruction::Jump(JumpISpan { addr })
1225                }
1226
1227                Fixup::OnErrorGotoAddr(label, pos) => {
1228                    let addr = Self::get_label_addr(&self.labels, label, pos)?;
1229                    Instruction::SetErrorHandler(ErrorHandlerISpan::Jump(addr))
1230                }
1231            };
1232            debug_assert_eq!(
1233                std::mem::discriminant(&Instruction::Nop),
1234                std::mem::discriminant(&self.instrs[pc]),
1235                "Fixup target address must contain a Nop instruction",
1236            );
1237            self.instrs[pc] = new_instr;
1238        }
1239
1240        let data = compile_data(self.data);
1241
1242        let image = Image { upcalls: self.symtable.upcalls(), instrs: self.instrs, data };
1243        Ok((image, self.symtable))
1244    }
1245}
1246
1247/// Translates the reduced set of expressions that represent data into values.
1248fn compile_data(data: Vec<Option<Expr>>) -> Vec<Option<Value>> {
1249    data.into_iter()
1250        .map(|expr| match expr {
1251            None => None,
1252            Some(Expr::Boolean(span)) => Some(Value::Boolean(span.value)),
1253            Some(Expr::Double(span)) => Some(Value::Double(span.value)),
1254            Some(Expr::Integer(span)) => Some(Value::Integer(span.value)),
1255            Some(Expr::Text(span)) => Some(Value::Text(span.value)),
1256            _ => unreachable!("Valid data types enforced at parse time"),
1257        })
1258        .collect()
1259}
1260
1261/// Compiles a collection of statements into an image ready for execution.
1262///
1263/// `symtable` is the symbols table as used by the compiler and should be prepopulated with any
1264/// callables that the compiled program should recognize.
1265fn compile_aux(input: &mut dyn io::Read, symtable: SymbolsTable) -> Result<(Image, SymbolsTable)> {
1266    let mut compiler = Compiler { symtable, ..Default::default() };
1267    for stmt in parser::parse(input) {
1268        compiler.compile_one(stmt?)?;
1269    }
1270    compiler.to_image()
1271}
1272
1273/// Compiles a collection of statements into an image ready for execution.
1274///
1275/// `syms` is a reference to the execution symbols and is used to obtain the names of the callables
1276/// that exist in the virtual machine.
1277// TODO(jmmv): This is ugly.  Now that we have a symbols table in here, we should not _also_ have a
1278// Symbols object to maintain runtime state (or if we do, we shouldn't be getting it here).
1279pub fn compile(input: &mut dyn io::Read, syms: &Symbols) -> Result<Image> {
1280    compile_aux(input, SymbolsTable::from(syms)).map(|(image, _symtable)| image)
1281}
1282
1283#[cfg(test)]
1284mod testutils {
1285    use super::*;
1286    use crate::syms::CallableMetadataBuilder;
1287
1288    /// Builder pattern to prepare the compiler for testing purposes.
1289    #[derive(Default)]
1290    #[must_use]
1291    pub(crate) struct Tester {
1292        source: String,
1293        symtable: SymbolsTable,
1294        exp_upcalls: Vec<SymbolKey>,
1295    }
1296
1297    impl Tester {
1298        /// Inserts `name` into the symbols table with the type defined by `proto`.
1299        pub(crate) fn define<K: Into<SymbolKey>>(
1300            mut self,
1301            name: K,
1302            proto: SymbolPrototype,
1303        ) -> Self {
1304            self.symtable.insert(name.into(), proto);
1305            self
1306        }
1307
1308        /// Inserts `name` into the symbols table as a callable.
1309        ///
1310        /// This is syntactic sugar over `define` to simplify the definition of callables.
1311        pub(crate) fn define_callable(mut self, builder: CallableMetadataBuilder) -> Self {
1312            let md = builder.test_build();
1313            let key = SymbolKey::from(md.name());
1314            self.symtable.insert_builtin_callable(key.clone(), md);
1315            self.exp_upcalls.push(key);
1316            self
1317        }
1318
1319        /// Parses and appends statements to the list of statements to be compiled.
1320        pub(crate) fn parse(mut self, input: &str) -> Self {
1321            self.source.push_str(input);
1322            self.source.push('\n');
1323            self
1324        }
1325
1326        /// Compiles the collection of accumulated statements and returns a `Checker` object to
1327        /// validate expectations about the compilation.
1328        pub(crate) fn compile(self) -> Checker {
1329            Checker {
1330                result: compile_aux(&mut self.source.as_bytes(), self.symtable),
1331                exp_error: None,
1332                ignore_instrs: false,
1333                exp_upcalls: self.exp_upcalls,
1334                exp_instrs: vec![],
1335                exp_data: vec![],
1336                exp_symtable: HashMap::default(),
1337            }
1338        }
1339    }
1340
1341    /// Captures expectations about a compilation and validates them.
1342    #[must_use]
1343    pub(crate) struct Checker {
1344        result: Result<(Image, SymbolsTable)>,
1345        exp_error: Option<String>,
1346        ignore_instrs: bool,
1347        exp_upcalls: Vec<SymbolKey>,
1348        exp_instrs: Vec<Instruction>,
1349        exp_data: Vec<Option<Value>>,
1350        exp_symtable: HashMap<SymbolKey, SymbolPrototype>,
1351    }
1352
1353    impl Checker {
1354        /// Records an instruction to be expected in the compiled output with the given address.
1355        ///
1356        /// If the given address is past the last address of the bytecode, the bytecode is extended
1357        /// with "nops" until the given address.
1358        pub(crate) fn expect_instr(mut self, addr: Address, instr: Instruction) -> Self {
1359            if addr >= self.exp_instrs.len() {
1360                self.exp_instrs.resize_with(addr + 1, || Instruction::Nop);
1361            }
1362            self.exp_instrs[addr] = instr;
1363            self
1364        }
1365
1366        /// Records an entry to be expected in the symbols table.
1367        pub(crate) fn expect_symtable(mut self, key: SymbolKey, proto: SymbolPrototype) -> Self {
1368            let previous = self.exp_symtable.insert(key, proto);
1369            assert!(previous.is_none());
1370            self
1371        }
1372
1373        /// Indicates that we do not care about the generated instructions in this test.
1374        pub(crate) fn ignore_instrs(mut self) -> Self {
1375            self.ignore_instrs = true;
1376            self
1377        }
1378
1379        /// Records a new datum in the compiled output.
1380        pub(crate) fn expect_datum(mut self, datum: Option<Value>) -> Self {
1381            self.exp_data.push(datum);
1382            self
1383        }
1384
1385        /// Records that the compilation should fail with the given `message`.
1386        pub(crate) fn expect_err<S: Into<String>>(mut self, message: S) -> Self {
1387            let message = message.into();
1388            self.exp_error = Some(message);
1389            self
1390        }
1391
1392        /// Validates all expectations.
1393        pub(crate) fn check(self) {
1394            if let Some(message) = self.exp_error {
1395                match self.result {
1396                    Ok(_) => panic!("Compilation succeeded but expected error: {}", message),
1397                    Err(e) => assert_eq!(message, e.to_string()),
1398                }
1399                return;
1400            }
1401            let (image, symtable) = self.result.unwrap();
1402
1403            assert_eq!(self.exp_upcalls, symtable.upcalls());
1404
1405            if self.ignore_instrs {
1406                assert!(
1407                    self.exp_instrs.is_empty(),
1408                    "Cannot ignore instructions if some are expected"
1409                );
1410            } else {
1411                assert_eq!(self.exp_instrs, image.instrs);
1412            }
1413
1414            assert_eq!(self.exp_data, image.data);
1415
1416            // TODO(jmmv): This should do an equality comparison to check all symbols, not just
1417            // those that tests have specified.  I did not do this when adding this check here
1418            // to avoid having to update all tests that didn't require this feature.
1419            for (key, exp_proto) in self.exp_symtable {
1420                match symtable.get(&key) {
1421                    Some(proto) => assert_eq!(
1422                        std::mem::discriminant(&exp_proto),
1423                        std::mem::discriminant(proto)
1424                    ),
1425                    None => panic!("Expected symbol {:?} not defined", key),
1426                }
1427            }
1428        }
1429    }
1430
1431    /// Syntactic sugar to instantiate a `LineCol` for testing.
1432    pub(crate) fn lc(line: usize, col: usize) -> LineCol {
1433        LineCol { line, col }
1434    }
1435}
1436
1437#[cfg(test)]
1438mod tests {
1439    use super::testutils::*;
1440    use super::*;
1441    use crate::bytecode::{BuiltinCallISpan, DimArrayISpan};
1442    use crate::syms::CallableMetadataBuilder;
1443    use std::borrow::Cow;
1444
1445    #[test]
1446    fn test_compile_nothing() {
1447        Tester::default().compile().check();
1448    }
1449
1450    #[test]
1451    fn test_compile_array_assignment_exprs() {
1452        Tester::default()
1453            .define("foo", SymbolPrototype::Array(ExprType::Integer, 3))
1454            .define("i", SymbolPrototype::Variable(ExprType::Integer))
1455            .parse("foo(3, 4 + i, i) = 5")
1456            .compile()
1457            .expect_instr(0, Instruction::PushInteger(5, lc(1, 20)))
1458            .expect_instr(1, Instruction::LoadInteger(SymbolKey::from("i"), lc(1, 15)))
1459            .expect_instr(2, Instruction::PushInteger(4, lc(1, 8)))
1460            .expect_instr(3, Instruction::LoadInteger(SymbolKey::from("i"), lc(1, 12)))
1461            .expect_instr(4, Instruction::AddIntegers(lc(1, 10)))
1462            .expect_instr(5, Instruction::PushInteger(3, lc(1, 5)))
1463            .expect_instr(6, Instruction::ArrayAssignment(SymbolKey::from("foo"), lc(1, 1), 3))
1464            .check();
1465    }
1466
1467    #[test]
1468    fn test_compile_array_assignment_ok_annotation() {
1469        Tester::default()
1470            .define("a", SymbolPrototype::Array(ExprType::Integer, 1))
1471            .parse("a%(0) = 1")
1472            .compile()
1473            .expect_instr(0, Instruction::PushInteger(1, lc(1, 9)))
1474            .expect_instr(1, Instruction::PushInteger(0, lc(1, 4)))
1475            .expect_instr(2, Instruction::ArrayAssignment(SymbolKey::from("a"), lc(1, 1), 1))
1476            .check();
1477    }
1478
1479    #[test]
1480    fn test_compile_array_assignment_index_double() {
1481        Tester::default()
1482            .define("a", SymbolPrototype::Array(ExprType::Integer, 1))
1483            .parse("a(1.2) = 1")
1484            .compile()
1485            .expect_instr(0, Instruction::PushInteger(1, lc(1, 10)))
1486            .expect_instr(1, Instruction::PushDouble(1.2, lc(1, 3)))
1487            .expect_instr(2, Instruction::DoubleToInteger)
1488            .expect_instr(3, Instruction::ArrayAssignment(SymbolKey::from("a"), lc(1, 1), 1))
1489            .check();
1490    }
1491
1492    #[test]
1493    fn test_compile_array_assignment_index_bad_type() {
1494        Tester::default()
1495            .define("a", SymbolPrototype::Array(ExprType::Integer, 1))
1496            .parse("a(TRUE) = 1")
1497            .compile()
1498            .expect_err("1:3: BOOLEAN is not a number")
1499            .check();
1500    }
1501
1502    #[test]
1503    fn test_compile_array_assignment_bad_annotation() {
1504        Tester::default()
1505            .define("a", SymbolPrototype::Array(ExprType::Integer, 1))
1506            .parse("a#(0) = 1")
1507            .compile()
1508            .expect_err("1:1: Incompatible type annotation in a# reference")
1509            .check();
1510    }
1511
1512    #[test]
1513    fn test_compile_array_assignment_double_to_integer_promotion() {
1514        Tester::default()
1515            .define("a", SymbolPrototype::Array(ExprType::Integer, 1))
1516            .define("d", SymbolPrototype::Variable(ExprType::Double))
1517            .parse("a(3) = d")
1518            .compile()
1519            .expect_instr(0, Instruction::LoadDouble(SymbolKey::from("d"), lc(1, 8)))
1520            .expect_instr(1, Instruction::DoubleToInteger)
1521            .expect_instr(2, Instruction::PushInteger(3, lc(1, 3)))
1522            .expect_instr(3, Instruction::ArrayAssignment(SymbolKey::from("a"), lc(1, 1), 1))
1523            .check();
1524    }
1525
1526    #[test]
1527    fn test_compile_array_assignment_integer_to_double_promotion() {
1528        Tester::default()
1529            .define("a", SymbolPrototype::Array(ExprType::Double, 1))
1530            .define("i", SymbolPrototype::Variable(ExprType::Integer))
1531            .parse("a(3) = i")
1532            .compile()
1533            .expect_instr(0, Instruction::LoadInteger(SymbolKey::from("i"), lc(1, 8)))
1534            .expect_instr(1, Instruction::IntegerToDouble)
1535            .expect_instr(2, Instruction::PushInteger(3, lc(1, 3)))
1536            .expect_instr(3, Instruction::ArrayAssignment(SymbolKey::from("a"), lc(1, 1), 1))
1537            .check();
1538    }
1539
1540    #[test]
1541    fn test_compile_array_assignment_bad_types() {
1542        Tester::default()
1543            .define("a", SymbolPrototype::Array(ExprType::Double, 1))
1544            .define("i", SymbolPrototype::Variable(ExprType::Integer))
1545            .parse("a(3) = FALSE")
1546            .compile()
1547            .expect_err("1:1: Cannot assign value of type BOOLEAN to variable of type DOUBLE")
1548            .check();
1549    }
1550
1551    #[test]
1552    fn test_compile_array_assignment_bad_dimensions() {
1553        Tester::default()
1554            .define("a", SymbolPrototype::Array(ExprType::Double, 1))
1555            .define("i", SymbolPrototype::Variable(ExprType::Integer))
1556            .parse("a(3, 5) = 7")
1557            .compile()
1558            .expect_err("1:1: Cannot index array with 2 subscripts; need 1")
1559            .check();
1560    }
1561
1562    #[test]
1563    fn test_compile_array_assignment_not_defined() {
1564        Tester::default()
1565            .parse("a(3) = FALSE")
1566            .compile()
1567            .expect_err("1:1: Undefined symbol a")
1568            .check();
1569    }
1570
1571    #[test]
1572    fn test_compile_array_assignment_not_an_array() {
1573        Tester::default()
1574            .define("a", SymbolPrototype::Variable(ExprType::Integer))
1575            .parse("a(3) = FALSE")
1576            .compile()
1577            .expect_err("1:1: Cannot index non-array a")
1578            .check();
1579    }
1580
1581    #[test]
1582    fn test_compile_assignment_literal() {
1583        Tester::default()
1584            .parse("foo = 5")
1585            .compile()
1586            .expect_instr(0, Instruction::PushInteger(5, lc(1, 7)))
1587            .expect_instr(1, Instruction::Assign(SymbolKey::from("foo")))
1588            .check();
1589    }
1590
1591    #[test]
1592    fn test_compile_assignment_varref_is_evaluated() {
1593        Tester::default()
1594            .define("i", SymbolPrototype::Variable(ExprType::Integer))
1595            .parse("foo = i")
1596            .compile()
1597            .expect_instr(0, Instruction::LoadInteger(SymbolKey::from("i"), lc(1, 7)))
1598            .expect_instr(1, Instruction::Assign(SymbolKey::from("foo")))
1599            .check();
1600    }
1601
1602    #[test]
1603    fn test_compile_assignment_new_var_auto_ref_expr_determines_type() {
1604        Tester::default()
1605            .parse("foo = 2.3")
1606            .compile()
1607            .expect_instr(0, Instruction::PushDouble(2.3, lc(1, 7)))
1608            .expect_instr(1, Instruction::Assign(SymbolKey::from("foo")))
1609            .expect_symtable(SymbolKey::from("foo"), SymbolPrototype::Variable(ExprType::Double))
1610            .check();
1611    }
1612
1613    #[test]
1614    fn test_compile_assignment_new_var_explicit_ref_determines_type() {
1615        Tester::default()
1616            .parse("foo# = 2")
1617            .compile()
1618            .expect_instr(0, Instruction::PushInteger(2, lc(1, 8)))
1619            .expect_instr(1, Instruction::IntegerToDouble)
1620            .expect_instr(2, Instruction::Assign(SymbolKey::from("foo")))
1621            .expect_symtable(SymbolKey::from("foo"), SymbolPrototype::Variable(ExprType::Double))
1622            .check();
1623    }
1624
1625    #[test]
1626    fn test_compile_assignment_bad_types_existing_var() {
1627        Tester::default()
1628            .parse("foo# = TRUE")
1629            .compile()
1630            .expect_err("1:1: Cannot assign value of type BOOLEAN to variable of type DOUBLE")
1631            .check();
1632    }
1633
1634    #[test]
1635    fn test_compile_assignment_bad_annotation_existing_var() {
1636        Tester::default()
1637            .define(SymbolKey::from("foo"), SymbolPrototype::Variable(ExprType::Text))
1638            .parse("foo# = \"hello\"")
1639            .compile()
1640            .expect_err("1:1: Incompatible type annotation in foo# reference")
1641            .check();
1642    }
1643
1644    #[test]
1645    fn test_compile_builtin_call_no_args() {
1646        Tester::default()
1647            .define_callable(CallableMetadataBuilder::new("CMD"))
1648            .parse("CMD")
1649            .compile()
1650            .expect_instr(
1651                0,
1652                Instruction::BuiltinCall(BuiltinCallISpan {
1653                    name: SymbolKey::from("CMD"),
1654                    name_pos: lc(1, 1),
1655                    upcall_index: 0,
1656                    nargs: 0,
1657                }),
1658            )
1659            .check();
1660    }
1661
1662    #[test]
1663    fn test_compile_builtin_call_increments_next_pc() {
1664        Tester::default()
1665            .define_callable(CallableMetadataBuilder::new("CMD").with_syntax(&[(
1666                &[],
1667                Some(&RepeatedSyntax {
1668                    name: Cow::Borrowed("expr"),
1669                    type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Integer),
1670                    sep: ArgSepSyntax::Exactly(ArgSep::Long),
1671                    require_one: false,
1672                    allow_missing: false,
1673                }),
1674            )]))
1675            .parse("IF TRUE THEN: CMD 1, 2: END IF")
1676            .compile()
1677            .expect_instr(0, Instruction::PushBoolean(true, lc(1, 4)))
1678            .expect_instr(1, Instruction::JumpIfNotTrue(5))
1679            .expect_instr(2, Instruction::PushInteger(2, lc(1, 22)))
1680            .expect_instr(3, Instruction::PushInteger(1, lc(1, 19)))
1681            .expect_instr(
1682                4,
1683                Instruction::BuiltinCall(BuiltinCallISpan {
1684                    name: SymbolKey::from("CMD"),
1685                    name_pos: lc(1, 15),
1686                    upcall_index: 0,
1687                    nargs: 2,
1688                }),
1689            )
1690            .check();
1691    }
1692
1693    #[test]
1694    fn test_compile_data_top_level() {
1695        Tester::default()
1696            .parse("DATA TRUE, 3\nDATA , 1")
1697            .compile()
1698            .expect_datum(Some(Value::Boolean(true)))
1699            .expect_datum(Some(Value::Integer(3)))
1700            .expect_datum(None)
1701            .expect_datum(Some(Value::Integer(1)))
1702            .check();
1703    }
1704
1705    #[test]
1706    fn test_compile_data_interspersed() {
1707        Tester::default()
1708            .parse("IF FALSE THEN: DATA TRUE: END IF")
1709            .parse("FOR i = 1 TO 10: DATA , 5: NEXT")
1710            .parse("WHILE FALSE: DATA 2.3: WEND")
1711            .compile()
1712            .expect_datum(Some(Value::Boolean(true)))
1713            .expect_datum(None)
1714            .expect_datum(Some(Value::Integer(5)))
1715            .expect_datum(Some(Value::Double(2.3)))
1716            .ignore_instrs()
1717            .check();
1718    }
1719
1720    #[test]
1721    fn test_compile_dim_ok() {
1722        Tester::default()
1723            .parse("DIM var AS BOOLEAN")
1724            .compile()
1725            .expect_instr(
1726                0,
1727                Instruction::Dim(DimISpan {
1728                    name: SymbolKey::from("var"),
1729                    shared: false,
1730                    vtype: ExprType::Boolean,
1731                }),
1732            )
1733            .check();
1734        Tester::default()
1735            .parse("DIM var AS DOUBLE")
1736            .compile()
1737            .expect_instr(
1738                0,
1739                Instruction::Dim(DimISpan {
1740                    name: SymbolKey::from("var"),
1741                    shared: false,
1742                    vtype: ExprType::Double,
1743                }),
1744            )
1745            .check();
1746        Tester::default()
1747            .parse("DIM var AS INTEGER")
1748            .compile()
1749            .expect_instr(
1750                0,
1751                Instruction::Dim(DimISpan {
1752                    name: SymbolKey::from("var"),
1753                    shared: false,
1754                    vtype: ExprType::Integer,
1755                }),
1756            )
1757            .check();
1758        Tester::default()
1759            .parse("DIM var AS STRING")
1760            .compile()
1761            .expect_instr(
1762                0,
1763                Instruction::Dim(DimISpan {
1764                    name: SymbolKey::from("var"),
1765                    shared: false,
1766                    vtype: ExprType::Text,
1767                }),
1768            )
1769            .check();
1770    }
1771
1772    #[test]
1773    fn test_compile_dim_case_insensitivity() {
1774        Tester::default()
1775            .parse("DIM foo: DIM Foo")
1776            .compile()
1777            .expect_err("1:14: Cannot define already-defined symbol FOO")
1778            .check();
1779    }
1780
1781    #[test]
1782    fn test_compile_dim_name_overlap() {
1783        Tester::default()
1784            .define("SomeArray", SymbolPrototype::Array(ExprType::Integer, 3))
1785            .parse("DIM somearray")
1786            .compile()
1787            .expect_err("1:5: Cannot define already-defined symbol SOMEARRAY")
1788            .check();
1789
1790        Tester::default()
1791            .define_callable(
1792                CallableMetadataBuilder::new("OUT").with_return_type(ExprType::Integer),
1793            )
1794            .parse("DIM OuT")
1795            .compile()
1796            .expect_err("1:5: Cannot define already-defined symbol OUT")
1797            .check();
1798
1799        Tester::default()
1800            .define("SomeVar", SymbolPrototype::Variable(ExprType::Integer))
1801            .parse("DIM SOMEVAR")
1802            .compile()
1803            .expect_err("1:5: Cannot define already-defined symbol SOMEVAR")
1804            .check();
1805    }
1806
1807    #[test]
1808    fn test_compile_dim_shared_ok() {
1809        Tester::default()
1810            .parse("DIM SHARED var AS BOOLEAN")
1811            .compile()
1812            .expect_instr(
1813                0,
1814                Instruction::Dim(DimISpan {
1815                    name: SymbolKey::from("var"),
1816                    shared: true,
1817                    vtype: ExprType::Boolean,
1818                }),
1819            )
1820            .check();
1821    }
1822
1823    #[test]
1824    fn test_compile_dim_shared_name_overlap_with_local() {
1825        Tester::default()
1826            .parse("DIM var AS BOOLEAN: DIM SHARED Var AS BOOLEAN")
1827            .compile()
1828            .expect_err("1:32: Cannot define already-defined symbol VAR")
1829            .check();
1830
1831        Tester::default()
1832            .parse("DIM SHARED var AS BOOLEAN: DIM Var AS BOOLEAN")
1833            .compile()
1834            .expect_err("1:32: Cannot define already-defined symbol VAR")
1835            .check();
1836    }
1837
1838    #[test]
1839    fn test_compile_dim_array_immediate() {
1840        Tester::default()
1841            .parse("DIM var(1) AS INTEGER")
1842            .compile()
1843            .expect_instr(0, Instruction::PushInteger(1, lc(1, 9)))
1844            .expect_instr(
1845                1,
1846                Instruction::DimArray(DimArrayISpan {
1847                    name: SymbolKey::from("var"),
1848                    name_pos: lc(1, 5),
1849                    shared: false,
1850                    dimensions: 1,
1851                    subtype: ExprType::Integer,
1852                    subtype_pos: lc(1, 15),
1853                }),
1854            )
1855            .check();
1856    }
1857
1858    #[test]
1859    fn test_compile_dim_array_exprs() {
1860        Tester::default()
1861            .define("i", SymbolPrototype::Variable(ExprType::Integer))
1862            .parse("DIM var(i, 3 + 4) AS INTEGER")
1863            .compile()
1864            .expect_instr(0, Instruction::PushInteger(3, lc(1, 12)))
1865            .expect_instr(1, Instruction::PushInteger(4, lc(1, 16)))
1866            .expect_instr(2, Instruction::AddIntegers(lc(1, 14)))
1867            .expect_instr(3, Instruction::LoadInteger(SymbolKey::from("i"), lc(1, 9)))
1868            .expect_instr(
1869                4,
1870                Instruction::DimArray(DimArrayISpan {
1871                    name: SymbolKey::from("var"),
1872                    name_pos: lc(1, 5),
1873                    shared: false,
1874                    dimensions: 2,
1875                    subtype: ExprType::Integer,
1876                    subtype_pos: lc(1, 22),
1877                }),
1878            )
1879            .check();
1880    }
1881
1882    #[test]
1883    fn test_compile_dim_array_double_to_integer() {
1884        Tester::default()
1885            .parse("DIM var(3.7) AS INTEGER")
1886            .compile()
1887            .expect_instr(0, Instruction::PushDouble(3.7, lc(1, 9)))
1888            .expect_instr(1, Instruction::DoubleToInteger)
1889            .expect_instr(
1890                2,
1891                Instruction::DimArray(DimArrayISpan {
1892                    name: SymbolKey::from("var"),
1893                    name_pos: lc(1, 5),
1894                    shared: false,
1895                    dimensions: 1,
1896                    subtype: ExprType::Integer,
1897                    subtype_pos: lc(1, 17),
1898                }),
1899            )
1900            .check();
1901    }
1902
1903    #[test]
1904    fn test_compile_dim_array_dimension_type_error() {
1905        Tester::default()
1906            .parse("DIM var(TRUE) AS INTEGER")
1907            .compile()
1908            .expect_err("1:9: BOOLEAN is not a number")
1909            .check();
1910    }
1911
1912    #[test]
1913    fn test_compile_dim_array_shared_ok() {
1914        Tester::default()
1915            .parse("DIM SHARED var(1) AS INTEGER")
1916            .compile()
1917            .expect_instr(0, Instruction::PushInteger(1, lc(1, 16)))
1918            .expect_instr(
1919                1,
1920                Instruction::DimArray(DimArrayISpan {
1921                    name: SymbolKey::from("var"),
1922                    name_pos: lc(1, 12),
1923                    shared: true,
1924                    dimensions: 1,
1925                    subtype: ExprType::Integer,
1926                    subtype_pos: lc(1, 22),
1927                }),
1928            )
1929            .check();
1930    }
1931
1932    #[test]
1933    fn test_compile_dim_array_shared_name_overlap_with_local() {
1934        Tester::default()
1935            .parse("DIM var(1) AS BOOLEAN: DIM SHARED Var(1) AS BOOLEAN")
1936            .compile()
1937            .expect_err("1:35: Cannot define already-defined symbol VAR")
1938            .check();
1939
1940        Tester::default()
1941            .parse("DIM SHARED var(1) AS BOOLEAN: DIM Var(1) AS BOOLEAN")
1942            .compile()
1943            .expect_err("1:35: Cannot define already-defined symbol VAR")
1944            .check();
1945    }
1946
1947    #[test]
1948    fn test_compile_do_infinite() {
1949        Tester::default()
1950            .define_callable(CallableMetadataBuilder::new("FOO"))
1951            .parse("DO\nFOO\nLOOP")
1952            .compile()
1953            .expect_instr(
1954                0,
1955                Instruction::BuiltinCall(BuiltinCallISpan {
1956                    name: SymbolKey::from("FOO"),
1957                    name_pos: lc(2, 1),
1958                    upcall_index: 0,
1959                    nargs: 0,
1960                }),
1961            )
1962            .expect_instr(1, Instruction::Jump(JumpISpan { addr: 0 }))
1963            .check();
1964    }
1965
1966    #[test]
1967    fn test_compile_do_pre_guard() {
1968        Tester::default()
1969            .define_callable(CallableMetadataBuilder::new("FOO"))
1970            .parse("DO WHILE TRUE\nFOO\nLOOP")
1971            .compile()
1972            .expect_instr(0, Instruction::PushBoolean(true, lc(1, 10)))
1973            .expect_instr(1, Instruction::JumpIfNotTrue(4))
1974            .expect_instr(
1975                2,
1976                Instruction::BuiltinCall(BuiltinCallISpan {
1977                    name: SymbolKey::from("FOO"),
1978                    name_pos: lc(2, 1),
1979                    upcall_index: 0,
1980                    nargs: 0,
1981                }),
1982            )
1983            .expect_instr(3, Instruction::Jump(JumpISpan { addr: 0 }))
1984            .check();
1985    }
1986
1987    #[test]
1988    fn test_compile_do_post_guard() {
1989        Tester::default()
1990            .define_callable(CallableMetadataBuilder::new("FOO"))
1991            .parse("DO\nFOO\nLOOP WHILE TRUE")
1992            .compile()
1993            .expect_instr(
1994                0,
1995                Instruction::BuiltinCall(BuiltinCallISpan {
1996                    name: SymbolKey::from("FOO"),
1997                    name_pos: lc(2, 1),
1998                    upcall_index: 0,
1999                    nargs: 0,
2000                }),
2001            )
2002            .expect_instr(1, Instruction::PushBoolean(true, lc(3, 12)))
2003            .expect_instr(2, Instruction::JumpIfTrue(0))
2004            .check();
2005    }
2006
2007    #[test]
2008    fn test_compile_end_without_exit_code() {
2009        Tester::default().parse("END").compile().expect_instr(0, Instruction::End(false)).check();
2010    }
2011
2012    #[test]
2013    fn test_compile_end_with_exit_code_expr() {
2014        Tester::default()
2015            .define("i", SymbolPrototype::Variable(ExprType::Integer))
2016            .parse("END 2 + i")
2017            .compile()
2018            .expect_instr(0, Instruction::PushInteger(2, lc(1, 5)))
2019            .expect_instr(1, Instruction::LoadInteger(SymbolKey::from("i"), lc(1, 9)))
2020            .expect_instr(2, Instruction::AddIntegers(lc(1, 7)))
2021            .expect_instr(3, Instruction::End(true))
2022            .check();
2023    }
2024
2025    #[test]
2026    fn test_compile_end_with_exit_code_varref() {
2027        Tester::default()
2028            .define("i", SymbolPrototype::Variable(ExprType::Integer))
2029            .parse("END i")
2030            .compile()
2031            .expect_instr(0, Instruction::LoadInteger(SymbolKey::from("i"), lc(1, 5)))
2032            .expect_instr(1, Instruction::End(true))
2033            .check();
2034    }
2035
2036    #[test]
2037    fn test_compile_end_with_exit_code_type_error() {
2038        Tester::default()
2039            .parse("END TRUE")
2040            .compile()
2041            .expect_err("1:5: BOOLEAN is not a number")
2042            .check();
2043    }
2044
2045    #[test]
2046    fn test_compile_end_with_exit_code_double_to_integer() {
2047        Tester::default()
2048            .parse("END 2.3")
2049            .compile()
2050            .expect_instr(0, Instruction::PushDouble(2.3, lc(1, 5)))
2051            .expect_instr(1, Instruction::DoubleToInteger)
2052            .expect_instr(2, Instruction::End(true))
2053            .check();
2054    }
2055
2056    #[test]
2057    fn test_compile_exit_do_infinite_simple() {
2058        Tester::default()
2059            .parse("DO\nEXIT DO\nLOOP")
2060            .compile()
2061            .expect_instr(0, Instruction::Jump(JumpISpan { addr: 2 }))
2062            .expect_instr(1, Instruction::Jump(JumpISpan { addr: 0 }))
2063            .check();
2064    }
2065
2066    #[test]
2067    fn test_compile_exit_do_pre_simple() {
2068        Tester::default()
2069            .parse("DO WHILE TRUE\nEXIT DO\nLOOP")
2070            .compile()
2071            .expect_instr(0, Instruction::PushBoolean(true, lc(1, 10)))
2072            .expect_instr(1, Instruction::JumpIfNotTrue(4))
2073            .expect_instr(2, Instruction::Jump(JumpISpan { addr: 4 }))
2074            .expect_instr(3, Instruction::Jump(JumpISpan { addr: 0 }))
2075            .check();
2076    }
2077
2078    #[test]
2079    fn test_compile_exit_do_nested() {
2080        Tester::default()
2081            .parse("DO WHILE TRUE\nEXIT DO\nDO UNTIL FALSE\nEXIT DO\nLOOP\nLOOP")
2082            .compile()
2083            .expect_instr(0, Instruction::PushBoolean(true, lc(1, 10)))
2084            .expect_instr(1, Instruction::JumpIfNotTrue(8))
2085            .expect_instr(2, Instruction::Jump(JumpISpan { addr: 8 }))
2086            .expect_instr(3, Instruction::PushBoolean(false, lc(3, 10)))
2087            .expect_instr(4, Instruction::JumpIfTrue(7))
2088            .expect_instr(5, Instruction::Jump(JumpISpan { addr: 7 }))
2089            .expect_instr(6, Instruction::Jump(JumpISpan { addr: 3 }))
2090            .expect_instr(7, Instruction::Jump(JumpISpan { addr: 0 }))
2091            .check();
2092    }
2093
2094    #[test]
2095    fn test_compile_exit_do_sequential() {
2096        Tester::default()
2097            .parse("DO WHILE TRUE\nEXIT DO\nLOOP\nDO WHILE TRUE\nEXIT DO\nLOOP")
2098            .compile()
2099            .expect_instr(0, Instruction::PushBoolean(true, lc(1, 10)))
2100            .expect_instr(1, Instruction::JumpIfNotTrue(4))
2101            .expect_instr(2, Instruction::Jump(JumpISpan { addr: 4 }))
2102            .expect_instr(3, Instruction::Jump(JumpISpan { addr: 0 }))
2103            .expect_instr(4, Instruction::PushBoolean(true, lc(4, 10)))
2104            .expect_instr(5, Instruction::JumpIfNotTrue(8))
2105            .expect_instr(6, Instruction::Jump(JumpISpan { addr: 8 }))
2106            .expect_instr(7, Instruction::Jump(JumpISpan { addr: 4 }))
2107            .check();
2108    }
2109
2110    #[test]
2111    fn test_compile_exit_do_from_nested_while() {
2112        Tester::default()
2113            .parse("DO WHILE TRUE\nEXIT DO\nWHILE FALSE\nEXIT DO\nWEND\nLOOP")
2114            .compile()
2115            .expect_instr(0, Instruction::PushBoolean(true, lc(1, 10)))
2116            .expect_instr(1, Instruction::JumpIfNotTrue(8))
2117            .expect_instr(2, Instruction::Jump(JumpISpan { addr: 8 }))
2118            .expect_instr(3, Instruction::PushBoolean(false, lc(3, 7)))
2119            .expect_instr(4, Instruction::JumpIfNotTrue(7))
2120            .expect_instr(5, Instruction::Jump(JumpISpan { addr: 8 }))
2121            .expect_instr(6, Instruction::Jump(JumpISpan { addr: 3 }))
2122            .expect_instr(7, Instruction::Jump(JumpISpan { addr: 0 }))
2123            .check();
2124    }
2125
2126    #[test]
2127    fn test_compile_exit_do_outside_of_loop() {
2128        Tester::default()
2129            .parse("EXIT DO")
2130            .compile()
2131            .expect_err("1:1: EXIT DO outside of DO")
2132            .check();
2133
2134        Tester::default()
2135            .parse("WHILE TRUE: EXIT DO: WEND")
2136            .compile()
2137            .expect_err("1:13: EXIT DO outside of DO")
2138            .check();
2139    }
2140
2141    #[test]
2142    fn test_compile_exit_for_infinite_simple() {
2143        Tester::default()
2144            .parse("FOR i = 1 to 10\nEXIT FOR\nNEXT")
2145            .compile()
2146            .expect_instr(0, Instruction::PushInteger(1, lc(1, 9)))
2147            .expect_instr(1, Instruction::Assign(SymbolKey::from("i")))
2148            .expect_instr(2, Instruction::LoadInteger(SymbolKey::from("i"), lc(1, 5)))
2149            .expect_instr(3, Instruction::PushInteger(10, lc(1, 14)))
2150            .expect_instr(4, Instruction::LessEqualIntegers(lc(1, 11)))
2151            .expect_instr(5, Instruction::JumpIfNotTrue(12))
2152            .expect_instr(6, Instruction::Jump(JumpISpan { addr: 12 }))
2153            .expect_instr(7, Instruction::LoadInteger(SymbolKey::from("i"), lc(1, 5)))
2154            .expect_instr(8, Instruction::PushInteger(1, lc(1, 16)))
2155            .expect_instr(9, Instruction::AddIntegers(lc(1, 11)))
2156            .expect_instr(10, Instruction::Assign(SymbolKey::from("i")))
2157            .expect_instr(11, Instruction::Jump(JumpISpan { addr: 2 }))
2158            .check();
2159    }
2160
2161    #[test]
2162    fn test_compile_exit_for_nested() {
2163        Tester::default()
2164            .parse("FOR i = 1 to 10\nFOR j = 2 to 20\nEXIT FOR\nNEXT\nEXIT FOR\nNEXT")
2165            .compile()
2166            .expect_instr(0, Instruction::PushInteger(1, lc(1, 9)))
2167            .expect_instr(1, Instruction::Assign(SymbolKey::from("i")))
2168            .expect_instr(2, Instruction::LoadInteger(SymbolKey::from("i"), lc(1, 5)))
2169            .expect_instr(3, Instruction::PushInteger(10, lc(1, 14)))
2170            .expect_instr(4, Instruction::LessEqualIntegers(lc(1, 11)))
2171            .expect_instr(5, Instruction::JumpIfNotTrue(24))
2172            // Begin nested for loop.
2173            .expect_instr(6, Instruction::PushInteger(2, lc(2, 9)))
2174            .expect_instr(7, Instruction::Assign(SymbolKey::from("j")))
2175            .expect_instr(8, Instruction::LoadInteger(SymbolKey::from("j"), lc(2, 5)))
2176            .expect_instr(9, Instruction::PushInteger(20, lc(2, 14)))
2177            .expect_instr(10, Instruction::LessEqualIntegers(lc(2, 11)))
2178            .expect_instr(11, Instruction::JumpIfNotTrue(18))
2179            .expect_instr(12, Instruction::Jump(JumpISpan { addr: 18 })) // Exit for.
2180            .expect_instr(13, Instruction::LoadInteger(SymbolKey::from("j"), lc(2, 5)))
2181            .expect_instr(14, Instruction::PushInteger(1, lc(2, 16)))
2182            .expect_instr(15, Instruction::AddIntegers(lc(2, 11)))
2183            .expect_instr(16, Instruction::Assign(SymbolKey::from("j")))
2184            .expect_instr(17, Instruction::Jump(JumpISpan { addr: 8 }))
2185            // Begin nested for loop.
2186            .expect_instr(18, Instruction::Jump(JumpISpan { addr: 24 })) // Exit for.
2187            .expect_instr(19, Instruction::LoadInteger(SymbolKey::from("i"), lc(1, 5)))
2188            .expect_instr(20, Instruction::PushInteger(1, lc(1, 16)))
2189            .expect_instr(21, Instruction::AddIntegers(lc(1, 11)))
2190            .expect_instr(22, Instruction::Assign(SymbolKey::from("i")))
2191            .expect_instr(23, Instruction::Jump(JumpISpan { addr: 2 }))
2192            .check();
2193    }
2194
2195    #[test]
2196    fn test_compile_exit_for_outside_of_loop() {
2197        Tester::default()
2198            .parse("EXIT FOR")
2199            .compile()
2200            .expect_err("1:1: EXIT FOR outside of FOR")
2201            .check();
2202
2203        Tester::default()
2204            .parse("WHILE TRUE: EXIT FOR: WEND")
2205            .compile()
2206            .expect_err("1:13: EXIT FOR outside of FOR")
2207            .check();
2208    }
2209
2210    #[test]
2211    fn test_compile_exit_do_and_exit_for() {
2212        Tester::default()
2213            .parse("DO\nFOR i = 1 to 10\nDO\nEXIT DO\nLOOP\nEXIT FOR\nNEXT\nLOOP")
2214            .compile()
2215            // Begin nested for loop.
2216            .expect_instr(0, Instruction::PushInteger(1, lc(2, 9)))
2217            .expect_instr(1, Instruction::Assign(SymbolKey::from("i")))
2218            .expect_instr(2, Instruction::LoadInteger(SymbolKey::from("i"), lc(2, 5)))
2219            .expect_instr(3, Instruction::PushInteger(10, lc(2, 14)))
2220            .expect_instr(4, Instruction::LessEqualIntegers(lc(2, 11)))
2221            .expect_instr(5, Instruction::JumpIfNotTrue(14))
2222            .expect_instr(6, Instruction::Jump(JumpISpan { addr: 8 })) // Exit do.
2223            // Begin nested do loop.
2224            .expect_instr(7, Instruction::Jump(JumpISpan { addr: 6 }))
2225            // End nested do loop.
2226            .expect_instr(8, Instruction::Jump(JumpISpan { addr: 14 })) // Exit for.
2227            .expect_instr(9, Instruction::LoadInteger(SymbolKey::from("i"), lc(2, 5)))
2228            .expect_instr(10, Instruction::PushInteger(1, lc(2, 16)))
2229            .expect_instr(11, Instruction::AddIntegers(lc(2, 11)))
2230            .expect_instr(12, Instruction::Assign(SymbolKey::from("i")))
2231            .expect_instr(13, Instruction::Jump(JumpISpan { addr: 2 }))
2232            // End nested for loop.
2233            .expect_instr(14, Instruction::Jump(JumpISpan { addr: 0 }))
2234            .check();
2235    }
2236
2237    #[test]
2238    fn test_compile_for_simple_literals() {
2239        Tester::default()
2240            .parse("FOR iter = 1 TO 5: a = FALSE: NEXT")
2241            .compile()
2242            .expect_instr(0, Instruction::PushInteger(1, lc(1, 12)))
2243            .expect_instr(1, Instruction::Assign(SymbolKey::from("iter")))
2244            .expect_instr(2, Instruction::LoadInteger(SymbolKey::from("iter"), lc(1, 5)))
2245            .expect_instr(3, Instruction::PushInteger(5, lc(1, 17)))
2246            .expect_instr(4, Instruction::LessEqualIntegers(lc(1, 14)))
2247            .expect_instr(5, Instruction::JumpIfNotTrue(13))
2248            .expect_instr(6, Instruction::PushBoolean(false, lc(1, 24)))
2249            .expect_instr(7, Instruction::Assign(SymbolKey::from("a")))
2250            .expect_instr(8, Instruction::LoadInteger(SymbolKey::from("iter"), lc(1, 5)))
2251            .expect_instr(9, Instruction::PushInteger(1, lc(1, 18)))
2252            .expect_instr(10, Instruction::AddIntegers(lc(1, 14)))
2253            .expect_instr(11, Instruction::Assign(SymbolKey::from("iter")))
2254            .expect_instr(12, Instruction::Jump(JumpISpan { addr: 2 }))
2255            .check();
2256    }
2257
2258    #[test]
2259    fn test_compile_for_simple_varrefs_are_evaluated() {
2260        Tester::default()
2261            .define("i", SymbolPrototype::Variable(ExprType::Integer))
2262            .define("j", SymbolPrototype::Variable(ExprType::Integer))
2263            .parse("FOR iter = i TO j: a = FALSE: NEXT")
2264            .compile()
2265            .expect_instr(0, Instruction::LoadInteger(SymbolKey::from("i"), lc(1, 12)))
2266            .expect_instr(1, Instruction::Assign(SymbolKey::from("iter")))
2267            .expect_instr(2, Instruction::LoadInteger(SymbolKey::from("iter"), lc(1, 5)))
2268            .expect_instr(3, Instruction::LoadInteger(SymbolKey::from("j"), lc(1, 17)))
2269            .expect_instr(4, Instruction::LessEqualIntegers(lc(1, 14)))
2270            .expect_instr(5, Instruction::JumpIfNotTrue(13))
2271            .expect_instr(6, Instruction::PushBoolean(false, lc(1, 24)))
2272            .expect_instr(7, Instruction::Assign(SymbolKey::from("a")))
2273            .expect_instr(8, Instruction::LoadInteger(SymbolKey::from("iter"), lc(1, 5)))
2274            .expect_instr(9, Instruction::PushInteger(1, lc(1, 18)))
2275            .expect_instr(10, Instruction::AddIntegers(lc(1, 14)))
2276            .expect_instr(11, Instruction::Assign(SymbolKey::from("iter")))
2277            .expect_instr(12, Instruction::Jump(JumpISpan { addr: 2 }))
2278            .check();
2279    }
2280
2281    #[test]
2282    fn test_compile_for_expressions() {
2283        Tester::default()
2284            .define("i", SymbolPrototype::Variable(ExprType::Integer))
2285            .define("j", SymbolPrototype::Variable(ExprType::Integer))
2286            .parse("FOR iter = (i + 1) TO (2 + j): a = FALSE: NEXT")
2287            .compile()
2288            .expect_instr(0, Instruction::LoadInteger(SymbolKey::from("i"), lc(1, 13)))
2289            .expect_instr(1, Instruction::PushInteger(1, lc(1, 17)))
2290            .expect_instr(2, Instruction::AddIntegers(lc(1, 15)))
2291            .expect_instr(3, Instruction::Assign(SymbolKey::from("iter")))
2292            .expect_instr(4, Instruction::LoadInteger(SymbolKey::from("iter"), lc(1, 5)))
2293            .expect_instr(5, Instruction::PushInteger(2, lc(1, 24)))
2294            .expect_instr(6, Instruction::LoadInteger(SymbolKey::from("j"), lc(1, 28)))
2295            .expect_instr(7, Instruction::AddIntegers(lc(1, 26)))
2296            .expect_instr(8, Instruction::LessEqualIntegers(lc(1, 20)))
2297            .expect_instr(9, Instruction::JumpIfNotTrue(17))
2298            .expect_instr(10, Instruction::PushBoolean(false, lc(1, 36)))
2299            .expect_instr(11, Instruction::Assign(SymbolKey::from("a")))
2300            .expect_instr(12, Instruction::LoadInteger(SymbolKey::from("iter"), lc(1, 5)))
2301            .expect_instr(13, Instruction::PushInteger(1, lc(1, 30)))
2302            .expect_instr(14, Instruction::AddIntegers(lc(1, 20)))
2303            .expect_instr(15, Instruction::Assign(SymbolKey::from("iter")))
2304            .expect_instr(16, Instruction::Jump(JumpISpan { addr: 4 }))
2305            .check();
2306    }
2307
2308    #[test]
2309    fn test_compile_for_double_auto_iterator() {
2310        Tester::default()
2311            .parse("FOR iter = 0 TO 2 STEP 0.1\nNEXT")
2312            .compile()
2313            .expect_instr(
2314                0,
2315                Instruction::JumpIfDefined(JumpIfDefinedISpan {
2316                    var: SymbolKey::from("iter"),
2317                    addr: 2,
2318                }),
2319            )
2320            .expect_instr(
2321                1,
2322                Instruction::Dim(DimISpan {
2323                    name: SymbolKey::from("iter"),
2324                    shared: false,
2325                    vtype: ExprType::Double,
2326                }),
2327            )
2328            .expect_instr(2, Instruction::PushInteger(0, lc(1, 12)))
2329            .expect_instr(3, Instruction::IntegerToDouble)
2330            .expect_instr(4, Instruction::Assign(SymbolKey::from("iter")))
2331            .expect_instr(5, Instruction::LoadDouble(SymbolKey::from("iter"), lc(1, 5)))
2332            .expect_instr(6, Instruction::PushInteger(2, lc(1, 17)))
2333            .expect_instr(7, Instruction::IntegerToDouble)
2334            .expect_instr(8, Instruction::LessEqualDoubles(lc(1, 14)))
2335            .expect_instr(9, Instruction::JumpIfNotTrue(15))
2336            .expect_instr(10, Instruction::LoadDouble(SymbolKey::from("iter"), lc(1, 5)))
2337            .expect_instr(11, Instruction::PushDouble(0.1, lc(1, 24)))
2338            .expect_instr(12, Instruction::AddDoubles(lc(1, 14)))
2339            .expect_instr(13, Instruction::Assign(SymbolKey::from("iter")))
2340            .expect_instr(14, Instruction::Jump(JumpISpan { addr: 5 }))
2341            .check();
2342    }
2343
2344    #[test]
2345    fn test_compile_function_and_nothing_else() {
2346        Tester::default()
2347            .parse("FUNCTION foo: a = 3: END FUNCTION")
2348            .compile()
2349            .expect_instr(0, Instruction::Jump(JumpISpan { addr: 8 }))
2350            .expect_instr(1, Instruction::EnterScope)
2351            .expect_instr(
2352                2,
2353                Instruction::Dim(DimISpan {
2354                    name: SymbolKey::from("0return_foo"),
2355                    shared: false,
2356                    vtype: ExprType::Integer,
2357                }),
2358            )
2359            .expect_instr(3, Instruction::PushInteger(3, lc(1, 19)))
2360            .expect_instr(4, Instruction::Assign(SymbolKey::from("a")))
2361            .expect_instr(5, Instruction::LoadInteger(SymbolKey::from("0return_foo"), lc(1, 22)))
2362            .expect_instr(6, Instruction::LeaveScope)
2363            .expect_instr(7, Instruction::Return(lc(1, 22)))
2364            .expect_symtable(
2365                SymbolKey::from("foo"),
2366                SymbolPrototype::Callable(
2367                    CallableMetadataBuilder::new_dynamic("USER DEFINED FUNCTION")
2368                        .with_syntax(&[(&[], None)])
2369                        .build(),
2370                ),
2371            )
2372            .check();
2373    }
2374
2375    #[test]
2376    fn test_compile_function_defined_between_code() {
2377        Tester::default()
2378            .parse("before = 1: FUNCTION foo: END FUNCTION: after = 2")
2379            .compile()
2380            .expect_instr(0, Instruction::PushInteger(1, lc(1, 10)))
2381            .expect_instr(1, Instruction::Assign(SymbolKey::from("before")))
2382            .expect_instr(2, Instruction::PushInteger(2, lc(1, 49)))
2383            .expect_instr(3, Instruction::Assign(SymbolKey::from("after")))
2384            .expect_instr(4, Instruction::Jump(JumpISpan { addr: 10 }))
2385            .expect_instr(5, Instruction::EnterScope)
2386            .expect_instr(
2387                6,
2388                Instruction::Dim(DimISpan {
2389                    name: SymbolKey::from("0return_foo"),
2390                    shared: false,
2391                    vtype: ExprType::Integer,
2392                }),
2393            )
2394            .expect_instr(7, Instruction::LoadInteger(SymbolKey::from("0return_foo"), lc(1, 27)))
2395            .expect_instr(8, Instruction::LeaveScope)
2396            .expect_instr(9, Instruction::Return(lc(1, 27)))
2397            .expect_symtable(
2398                SymbolKey::from("foo"),
2399                SymbolPrototype::Callable(
2400                    CallableMetadataBuilder::new_dynamic("USER DEFINED FUNCTION")
2401                        .with_syntax(&[(&[], None)])
2402                        .build(),
2403                ),
2404            )
2405            .check();
2406    }
2407
2408    #[test]
2409    fn test_compile_function_early_exit() {
2410        Tester::default()
2411            .parse("FUNCTION foo: a = 3: EXIT FUNCTION: a = 4: END FUNCTION")
2412            .compile()
2413            .expect_instr(0, Instruction::Jump(JumpISpan { addr: 11 }))
2414            .expect_instr(1, Instruction::EnterScope)
2415            .expect_instr(
2416                2,
2417                Instruction::Dim(DimISpan {
2418                    name: SymbolKey::from("0return_foo"),
2419                    shared: false,
2420                    vtype: ExprType::Integer,
2421                }),
2422            )
2423            .expect_instr(3, Instruction::PushInteger(3, lc(1, 19)))
2424            .expect_instr(4, Instruction::Assign(SymbolKey::from("a")))
2425            .expect_instr(5, Instruction::Jump(JumpISpan { addr: 8 }))
2426            .expect_instr(6, Instruction::PushInteger(4, lc(1, 41)))
2427            .expect_instr(7, Instruction::Assign(SymbolKey::from("a")))
2428            .expect_instr(8, Instruction::LoadInteger(SymbolKey::from("0return_foo"), lc(1, 44)))
2429            .expect_instr(9, Instruction::LeaveScope)
2430            .expect_instr(10, Instruction::Return(lc(1, 44)))
2431            .expect_symtable(
2432                SymbolKey::from("foo"),
2433                SymbolPrototype::Callable(
2434                    CallableMetadataBuilder::new_dynamic("USER DEFINED FUNCTION")
2435                        .with_syntax(&[(&[], None)])
2436                        .build(),
2437                ),
2438            )
2439            .check();
2440    }
2441
2442    #[test]
2443    fn test_compile_function_misplaced_exit() {
2444        Tester::default()
2445            .parse("FUNCTION a: END FUNCTION: EXIT FUNCTION")
2446            .compile()
2447            .expect_err("1:27: EXIT FUNCTION outside of FUNCTION")
2448            .check();
2449    }
2450
2451    #[test]
2452    fn test_compile_function_mismatched_exit() {
2453        Tester::default()
2454            .parse("FUNCTION a: EXIT SUB: END FUNCTION")
2455            .compile()
2456            .expect_err("1:13: EXIT SUB outside of SUB")
2457            .check();
2458    }
2459
2460    #[test]
2461    fn test_compile_function_redefined_was_variable() {
2462        Tester::default()
2463            .parse("a = 1: FUNCTION a: END FUNCTION")
2464            .compile()
2465            .expect_err("1:17: Cannot define already-defined symbol A")
2466            .check();
2467    }
2468
2469    #[test]
2470    fn test_compile_function_redefined_was_function() {
2471        Tester::default()
2472            .parse("FUNCTION a: END FUNCTION: FUNCTION A: END FUNCTION")
2473            .compile()
2474            .expect_err("1:36: Cannot define already-defined symbol A")
2475            .check();
2476    }
2477
2478    #[test]
2479    fn test_compile_function_redefined_was_sub() {
2480        Tester::default()
2481            .parse("SUB a: END SUB: FUNCTION A: END FUNCTION")
2482            .compile()
2483            .expect_err("1:26: Cannot define already-defined symbol A")
2484            .check();
2485    }
2486
2487    #[test]
2488    fn test_compile_sub_early_exit() {
2489        Tester::default()
2490            .parse("SUB foo: a = 3: EXIT SUB: a = 4: END SUB")
2491            .compile()
2492            .expect_instr(0, Instruction::Jump(JumpISpan { addr: 9 }))
2493            .expect_instr(1, Instruction::EnterScope)
2494            .expect_instr(2, Instruction::PushInteger(3, lc(1, 14)))
2495            .expect_instr(3, Instruction::Assign(SymbolKey::from("a")))
2496            .expect_instr(4, Instruction::Jump(JumpISpan { addr: 7 }))
2497            .expect_instr(5, Instruction::PushInteger(4, lc(1, 31)))
2498            .expect_instr(6, Instruction::Assign(SymbolKey::from("a")))
2499            .expect_instr(7, Instruction::LeaveScope)
2500            .expect_instr(8, Instruction::Return(lc(1, 34)))
2501            .expect_symtable(
2502                SymbolKey::from("foo"),
2503                SymbolPrototype::Callable(
2504                    CallableMetadataBuilder::new_dynamic("USER DEFINED SUB")
2505                        .with_syntax(&[(&[], None)])
2506                        .build(),
2507                ),
2508            )
2509            .check();
2510    }
2511
2512    #[test]
2513    fn test_compile_sub_misplaced_exit() {
2514        Tester::default()
2515            .parse("SUB a: END SUB: EXIT SUB")
2516            .compile()
2517            .expect_err("1:17: EXIT SUB outside of SUB")
2518            .check();
2519    }
2520
2521    #[test]
2522    fn test_compile_sub_mismatched_exit() {
2523        Tester::default()
2524            .parse("SUB a: EXIT FUNCTION: END SUB")
2525            .compile()
2526            .expect_err("1:8: EXIT FUNCTION outside of FUNCTION")
2527            .check();
2528    }
2529
2530    #[test]
2531    fn test_compile_sub_redefined_was_variable() {
2532        Tester::default()
2533            .parse("a = 1: SUB a: END SUB")
2534            .compile()
2535            .expect_err("1:12: Cannot define already-defined symbol A")
2536            .check();
2537    }
2538
2539    #[test]
2540    fn test_compile_sub_redefined_was_function() {
2541        Tester::default()
2542            .parse("FUNCTION a: END FUNCTION: SUB A: END SUB")
2543            .compile()
2544            .expect_err("1:31: Cannot define already-defined symbol A")
2545            .check();
2546    }
2547
2548    #[test]
2549    fn test_compile_sub_redefined_was_sub() {
2550        Tester::default()
2551            .parse("SUB a: END SUB: SUB A: END SUB")
2552            .compile()
2553            .expect_err("1:21: Cannot define already-defined symbol A")
2554            .check();
2555    }
2556
2557    #[test]
2558    fn test_compile_goto() {
2559        Tester::default()
2560            .parse("@first GOTO @second")
2561            .parse("@second: GOTO @first")
2562            .compile()
2563            .expect_instr(0, Instruction::Jump(JumpISpan { addr: 1 }))
2564            .expect_instr(1, Instruction::Jump(JumpISpan { addr: 0 }))
2565            .check();
2566    }
2567
2568    #[test]
2569    fn test_compile_gosub_and_return() {
2570        Tester::default()
2571            .define_callable(CallableMetadataBuilder::new("FOO"))
2572            .parse("@sub\nFOO\nRETURN\nGOSUB @sub")
2573            .compile()
2574            .expect_instr(
2575                0,
2576                Instruction::BuiltinCall(BuiltinCallISpan {
2577                    name: SymbolKey::from("FOO"),
2578                    name_pos: lc(2, 1),
2579                    upcall_index: 0,
2580                    nargs: 0,
2581                }),
2582            )
2583            .expect_instr(1, Instruction::Return(lc(3, 1)))
2584            .expect_instr(2, Instruction::Call(JumpISpan { addr: 0 }))
2585            .check();
2586    }
2587
2588    #[test]
2589    fn test_compile_goto_unknown_label() {
2590        Tester::default()
2591            .parse("@fo: GOTO @foo")
2592            .compile()
2593            .expect_err("1:11: Unknown label foo")
2594            .check();
2595    }
2596
2597    #[test]
2598    fn test_compile_if_one_branch() {
2599        Tester::default()
2600            .define_callable(CallableMetadataBuilder::new("FOO"))
2601            .parse("IF FALSE THEN: FOO: END IF")
2602            .compile()
2603            .expect_instr(0, Instruction::PushBoolean(false, lc(1, 4)))
2604            .expect_instr(1, Instruction::JumpIfNotTrue(3))
2605            .expect_instr(
2606                2,
2607                Instruction::BuiltinCall(BuiltinCallISpan {
2608                    name: SymbolKey::from("FOO"),
2609                    name_pos: lc(1, 16),
2610                    upcall_index: 0,
2611                    nargs: 0,
2612                }),
2613            )
2614            .check();
2615    }
2616
2617    #[test]
2618    fn test_compile_if_many_branches() {
2619        Tester::default()
2620            .define_callable(CallableMetadataBuilder::new("FOO"))
2621            .define_callable(CallableMetadataBuilder::new("BAR"))
2622            .define_callable(CallableMetadataBuilder::new("BAZ"))
2623            .parse("IF FALSE THEN\nFOO\nELSEIF TRUE THEN\nBAR\nELSE\nBAZ\nEND IF")
2624            .compile()
2625            .expect_instr(0, Instruction::PushBoolean(false, lc(1, 4)))
2626            .expect_instr(1, Instruction::JumpIfNotTrue(4))
2627            .expect_instr(
2628                2,
2629                Instruction::BuiltinCall(BuiltinCallISpan {
2630                    name: SymbolKey::from("FOO"),
2631                    name_pos: lc(2, 1),
2632                    upcall_index: 0,
2633                    nargs: 0,
2634                }),
2635            )
2636            .expect_instr(3, Instruction::Jump(JumpISpan { addr: 11 }))
2637            .expect_instr(4, Instruction::PushBoolean(true, lc(3, 8)))
2638            .expect_instr(5, Instruction::JumpIfNotTrue(8))
2639            .expect_instr(
2640                6,
2641                Instruction::BuiltinCall(BuiltinCallISpan {
2642                    name: SymbolKey::from("BAR"),
2643                    name_pos: lc(4, 1),
2644                    upcall_index: 1,
2645                    nargs: 0,
2646                }),
2647            )
2648            .expect_instr(7, Instruction::Jump(JumpISpan { addr: 11 }))
2649            .expect_instr(8, Instruction::PushBoolean(true, lc(5, 1)))
2650            .expect_instr(9, Instruction::JumpIfNotTrue(11))
2651            .expect_instr(
2652                10,
2653                Instruction::BuiltinCall(BuiltinCallISpan {
2654                    name: SymbolKey::from("BAZ"),
2655                    name_pos: lc(6, 1),
2656                    upcall_index: 2,
2657                    nargs: 0,
2658                }),
2659            )
2660            .check();
2661    }
2662
2663    #[test]
2664    fn test_compile_on_error_reset() {
2665        Tester::default()
2666            .parse("ON ERROR GOTO 0")
2667            .compile()
2668            .expect_instr(0, Instruction::SetErrorHandler(ErrorHandlerISpan::None))
2669            .check();
2670    }
2671
2672    #[test]
2673    fn test_compile_on_error_goto_label() {
2674        Tester::default()
2675            .parse("ON ERROR GOTO @foo\n\n\n@foo")
2676            .compile()
2677            .expect_instr(0, Instruction::SetErrorHandler(ErrorHandlerISpan::Jump(1)))
2678            .check();
2679    }
2680
2681    #[test]
2682    fn test_compile_on_error_goto_unknown_label() {
2683        Tester::default()
2684            .parse("ON ERROR GOTO @foo")
2685            .compile()
2686            .expect_err("1:15: Unknown label foo")
2687            .check();
2688    }
2689
2690    #[test]
2691    fn test_compile_on_error_resume_next() {
2692        Tester::default()
2693            .parse("ON ERROR RESUME NEXT")
2694            .compile()
2695            .expect_instr(0, Instruction::SetErrorHandler(ErrorHandlerISpan::ResumeNext))
2696            .check();
2697    }
2698
2699    /// Tests that parsing one or more `guards` as supplied after `CASE` yields the expected
2700    /// expression in `exp_expr`.
2701    ///
2702    /// `exp_expr` must assume that its position starts at `(2, 6)`.
2703    fn do_compile_case_guard_test(guards: &str, exp_expr_instrs: Vec<Instruction>) {
2704        let mut t = Tester::default()
2705            .define_callable(CallableMetadataBuilder::new("FOO"))
2706            .parse(&format!("SELECT CASE 5\nCASE {}\nFOO\nEND SELECT", guards))
2707            .compile()
2708            .expect_instr(0, Instruction::PushInteger(5, lc(1, 13)))
2709            .expect_instr(1, Instruction::Assign(SymbolKey::from("0select1")));
2710        let mut n = 2;
2711        for instr in exp_expr_instrs {
2712            t = t.expect_instr(n, instr);
2713            n += 1;
2714        }
2715        t.expect_instr(n, Instruction::JumpIfNotTrue(n + 2))
2716            .expect_instr(
2717                n + 1,
2718                Instruction::BuiltinCall(BuiltinCallISpan {
2719                    name: SymbolKey::from("FOO"),
2720                    name_pos: lc(3, 1),
2721                    upcall_index: 0,
2722                    nargs: 0,
2723                }),
2724            )
2725            .expect_instr(
2726                n + 2,
2727                Instruction::Unset(UnsetISpan { name: SymbolKey::from("0select1"), pos: lc(4, 1) }),
2728            )
2729            .check();
2730    }
2731
2732    #[test]
2733    fn test_compile_case_guards_equals() {
2734        do_compile_case_guard_test(
2735            "1 + 2",
2736            vec![
2737                Instruction::LoadInteger(SymbolKey::from("0select1"), lc(2, 6)),
2738                Instruction::PushInteger(1, lc(2, 6)),
2739                Instruction::PushInteger(2, lc(2, 10)),
2740                Instruction::AddIntegers(lc(2, 8)),
2741                Instruction::EqualIntegers(lc(2, 6)),
2742            ],
2743        );
2744    }
2745
2746    #[test]
2747    fn test_compile_case_guards_is() {
2748        do_compile_case_guard_test(
2749            "IS = 9 + 8",
2750            vec![
2751                Instruction::LoadInteger(SymbolKey::from("0select1"), lc(2, 11)),
2752                Instruction::PushInteger(9, lc(2, 11)),
2753                Instruction::PushInteger(8, lc(2, 15)),
2754                Instruction::AddIntegers(lc(2, 13)),
2755                Instruction::EqualIntegers(lc(2, 11)),
2756            ],
2757        );
2758
2759        do_compile_case_guard_test(
2760            "IS <> 9",
2761            vec![
2762                Instruction::LoadInteger(SymbolKey::from("0select1"), lc(2, 12)),
2763                Instruction::PushInteger(9, lc(2, 12)),
2764                Instruction::NotEqualIntegers(lc(2, 12)),
2765            ],
2766        );
2767
2768        do_compile_case_guard_test(
2769            "IS < 9",
2770            vec![
2771                Instruction::LoadInteger(SymbolKey::from("0select1"), lc(2, 11)),
2772                Instruction::PushInteger(9, lc(2, 11)),
2773                Instruction::LessIntegers(lc(2, 11)),
2774            ],
2775        );
2776
2777        do_compile_case_guard_test(
2778            "IS <= 9",
2779            vec![
2780                Instruction::LoadInteger(SymbolKey::from("0select1"), lc(2, 12)),
2781                Instruction::PushInteger(9, lc(2, 12)),
2782                Instruction::LessEqualIntegers(lc(2, 12)),
2783            ],
2784        );
2785
2786        do_compile_case_guard_test(
2787            "IS > 9",
2788            vec![
2789                Instruction::LoadInteger(SymbolKey::from("0select1"), lc(2, 11)),
2790                Instruction::PushInteger(9, lc(2, 11)),
2791                Instruction::GreaterIntegers(lc(2, 11)),
2792            ],
2793        );
2794
2795        do_compile_case_guard_test(
2796            "IS >= 9",
2797            vec![
2798                Instruction::LoadInteger(SymbolKey::from("0select1"), lc(2, 12)),
2799                Instruction::PushInteger(9, lc(2, 12)),
2800                Instruction::GreaterEqualIntegers(lc(2, 12)),
2801            ],
2802        );
2803    }
2804
2805    #[test]
2806    fn test_compile_case_guards_to() {
2807        do_compile_case_guard_test(
2808            "1 TO 2",
2809            vec![
2810                Instruction::LoadInteger(SymbolKey::from("0select1"), lc(2, 6)),
2811                Instruction::PushInteger(1, lc(2, 6)),
2812                Instruction::GreaterEqualIntegers(lc(2, 6)),
2813                Instruction::LoadInteger(SymbolKey::from("0select1"), lc(2, 6)),
2814                Instruction::PushInteger(2, lc(2, 11)),
2815                Instruction::LessEqualIntegers(lc(2, 11)),
2816                Instruction::LogicalAnd(lc(2, 6)),
2817            ],
2818        );
2819    }
2820
2821    #[test]
2822    fn test_compile_case_guards_many() {
2823        do_compile_case_guard_test(
2824            "IS <> 9, 8",
2825            vec![
2826                Instruction::LoadInteger(SymbolKey::from("0select1"), lc(2, 12)),
2827                Instruction::PushInteger(9, lc(2, 12)),
2828                Instruction::NotEqualIntegers(lc(2, 12)),
2829                Instruction::LoadInteger(SymbolKey::from("0select1"), lc(2, 15)),
2830                Instruction::PushInteger(8, lc(2, 15)),
2831                Instruction::EqualIntegers(lc(2, 15)),
2832                Instruction::LogicalOr(lc(2, 12)),
2833            ],
2834        );
2835    }
2836
2837    #[test]
2838    fn test_compile_select_no_cases() {
2839        Tester::default()
2840            .parse("SELECT CASE 5 + 3: END SELECT")
2841            .compile()
2842            .expect_instr(0, Instruction::PushInteger(5, lc(1, 13)))
2843            .expect_instr(1, Instruction::PushInteger(3, lc(1, 17)))
2844            .expect_instr(2, Instruction::AddIntegers(lc(1, 15)))
2845            .expect_instr(3, Instruction::Assign(SymbolKey::from("0select1")))
2846            .expect_instr(
2847                4,
2848                Instruction::Unset(UnsetISpan {
2849                    name: SymbolKey::from("0select1"),
2850                    pos: lc(1, 20),
2851                }),
2852            )
2853            .check();
2854    }
2855
2856    #[test]
2857    fn test_compile_select_one_case() {
2858        Tester::default()
2859            .define_callable(CallableMetadataBuilder::new("FOO"))
2860            .parse("SELECT CASE 5\nCASE 7\nFOO\nEND SELECT")
2861            .compile()
2862            .expect_instr(0, Instruction::PushInteger(5, lc(1, 13)))
2863            .expect_instr(1, Instruction::Assign(SymbolKey::from("0select1")))
2864            .expect_instr(2, Instruction::LoadInteger(SymbolKey::from("0select1"), lc(2, 6)))
2865            .expect_instr(3, Instruction::PushInteger(7, lc(2, 6)))
2866            .expect_instr(4, Instruction::EqualIntegers(lc(2, 6)))
2867            .expect_instr(5, Instruction::JumpIfNotTrue(7))
2868            .expect_instr(
2869                6,
2870                Instruction::BuiltinCall(BuiltinCallISpan {
2871                    name: SymbolKey::from("FOO"),
2872                    name_pos: lc(3, 1),
2873                    upcall_index: 0,
2874                    nargs: 0,
2875                }),
2876            )
2877            .expect_instr(
2878                7,
2879                Instruction::Unset(UnsetISpan { name: SymbolKey::from("0select1"), pos: lc(4, 1) }),
2880            )
2881            .check();
2882    }
2883
2884    #[test]
2885    fn test_compile_select_one_case_varref_is_evaluated() {
2886        Tester::default()
2887            .define("i", SymbolPrototype::Variable(ExprType::Integer))
2888            .parse("SELECT CASE i: END SELECT")
2889            .compile()
2890            .expect_instr(0, Instruction::LoadInteger(SymbolKey::from("i"), lc(1, 13)))
2891            .expect_instr(1, Instruction::Assign(SymbolKey::from("0select1")))
2892            .expect_instr(
2893                2,
2894                Instruction::Unset(UnsetISpan {
2895                    name: SymbolKey::from("0select1"),
2896                    pos: lc(1, 16),
2897                }),
2898            )
2899            .check();
2900    }
2901
2902    #[test]
2903    fn test_compile_select_only_case_else() {
2904        Tester::default()
2905            .define_callable(CallableMetadataBuilder::new("FOO"))
2906            .parse("SELECT CASE 5\nCASE ELSE\nFOO\nEND SELECT")
2907            .compile()
2908            .expect_instr(0, Instruction::PushInteger(5, lc(1, 13)))
2909            .expect_instr(1, Instruction::Assign(SymbolKey::from("0select1")))
2910            .expect_instr(
2911                2,
2912                Instruction::BuiltinCall(BuiltinCallISpan {
2913                    name: SymbolKey::from("FOO"),
2914                    name_pos: lc(3, 1),
2915                    upcall_index: 0,
2916                    nargs: 0,
2917                }),
2918            )
2919            .expect_instr(
2920                3,
2921                Instruction::Unset(UnsetISpan { name: SymbolKey::from("0select1"), pos: lc(4, 1) }),
2922            )
2923            .check();
2924    }
2925
2926    #[test]
2927    fn test_compile_select_many_cases_without_else() {
2928        Tester::default()
2929            .define_callable(CallableMetadataBuilder::new("FOO"))
2930            .define_callable(CallableMetadataBuilder::new("BAR"))
2931            .parse("SELECT CASE 5\nCASE 7\nFOO\nCASE IS <> 8\nBAR\nEND SELECT")
2932            .compile()
2933            .expect_instr(0, Instruction::PushInteger(5, lc(1, 13)))
2934            .expect_instr(1, Instruction::Assign(SymbolKey::from("0select1")))
2935            .expect_instr(2, Instruction::LoadInteger(SymbolKey::from("0select1"), lc(2, 6)))
2936            .expect_instr(3, Instruction::PushInteger(7, lc(2, 6)))
2937            .expect_instr(4, Instruction::EqualIntegers(lc(2, 6)))
2938            .expect_instr(5, Instruction::JumpIfNotTrue(8))
2939            .expect_instr(
2940                6,
2941                Instruction::BuiltinCall(BuiltinCallISpan {
2942                    name: SymbolKey::from("FOO"),
2943                    name_pos: lc(3, 1),
2944                    upcall_index: 0,
2945                    nargs: 0,
2946                }),
2947            )
2948            .expect_instr(7, Instruction::Jump(JumpISpan { addr: 13 }))
2949            .expect_instr(8, Instruction::LoadInteger(SymbolKey::from("0select1"), lc(4, 12)))
2950            .expect_instr(9, Instruction::PushInteger(8, lc(4, 12)))
2951            .expect_instr(10, Instruction::NotEqualIntegers(lc(4, 12)))
2952            .expect_instr(11, Instruction::JumpIfNotTrue(13))
2953            .expect_instr(
2954                12,
2955                Instruction::BuiltinCall(BuiltinCallISpan {
2956                    name: SymbolKey::from("BAR"),
2957                    name_pos: lc(5, 1),
2958                    upcall_index: 1,
2959                    nargs: 0,
2960                }),
2961            )
2962            .expect_instr(
2963                13,
2964                Instruction::Unset(UnsetISpan { name: SymbolKey::from("0select1"), pos: lc(6, 1) }),
2965            )
2966            .check();
2967    }
2968
2969    #[test]
2970    fn test_compile_select_many_cases_with_else() {
2971        Tester::default()
2972            .define_callable(CallableMetadataBuilder::new("FOO"))
2973            .define_callable(CallableMetadataBuilder::new("BAR"))
2974            .parse("SELECT CASE 5\nCASE 7\nFOO\nCASE ELSE\nBAR\nEND SELECT")
2975            .compile()
2976            .expect_instr(0, Instruction::PushInteger(5, lc(1, 13)))
2977            .expect_instr(1, Instruction::Assign(SymbolKey::from("0select1")))
2978            .expect_instr(2, Instruction::LoadInteger(SymbolKey::from("0select1"), lc(2, 6)))
2979            .expect_instr(3, Instruction::PushInteger(7, lc(2, 6)))
2980            .expect_instr(4, Instruction::EqualIntegers(lc(2, 6)))
2981            .expect_instr(5, Instruction::JumpIfNotTrue(8))
2982            .expect_instr(
2983                6,
2984                Instruction::BuiltinCall(BuiltinCallISpan {
2985                    name: SymbolKey::from("FOO"),
2986                    name_pos: lc(3, 1),
2987                    upcall_index: 0,
2988                    nargs: 0,
2989                }),
2990            )
2991            .expect_instr(7, Instruction::Jump(JumpISpan { addr: 9 }))
2992            .expect_instr(
2993                8,
2994                Instruction::BuiltinCall(BuiltinCallISpan {
2995                    name: SymbolKey::from("BAR"),
2996                    name_pos: lc(5, 1),
2997                    upcall_index: 1,
2998                    nargs: 0,
2999                }),
3000            )
3001            .expect_instr(
3002                9,
3003                Instruction::Unset(UnsetISpan { name: SymbolKey::from("0select1"), pos: lc(6, 1) }),
3004            )
3005            .check();
3006    }
3007
3008    #[test]
3009    fn test_compile_select_internal_var_names() {
3010        Tester::default()
3011            .parse("SELECT CASE 0: END SELECT\nSELECT CASE 0: END SELECT")
3012            .compile()
3013            .expect_instr(0, Instruction::PushInteger(0, lc(1, 13)))
3014            .expect_instr(1, Instruction::Assign(SymbolKey::from("0select1")))
3015            .expect_instr(
3016                2,
3017                Instruction::Unset(UnsetISpan {
3018                    name: SymbolKey::from("0select1"),
3019                    pos: lc(1, 16),
3020                }),
3021            )
3022            .expect_instr(3, Instruction::PushInteger(0, lc(2, 13)))
3023            .expect_instr(4, Instruction::Assign(SymbolKey::from("0select2")))
3024            .expect_instr(
3025                5,
3026                Instruction::Unset(UnsetISpan {
3027                    name: SymbolKey::from("0select2"),
3028                    pos: lc(2, 16),
3029                }),
3030            )
3031            .check();
3032    }
3033
3034    #[test]
3035    fn test_compile_while() {
3036        Tester::default()
3037            .define_callable(CallableMetadataBuilder::new("FOO"))
3038            .parse("WHILE TRUE\nFOO\nWEND")
3039            .compile()
3040            .expect_instr(0, Instruction::PushBoolean(true, lc(1, 7)))
3041            .expect_instr(1, Instruction::JumpIfNotTrue(4))
3042            .expect_instr(
3043                2,
3044                Instruction::BuiltinCall(BuiltinCallISpan {
3045                    name: SymbolKey::from("FOO"),
3046                    name_pos: lc(2, 1),
3047                    upcall_index: 0,
3048                    nargs: 0,
3049                }),
3050            )
3051            .expect_instr(3, Instruction::Jump(JumpISpan { addr: 0 }))
3052            .check();
3053    }
3054}