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