Skip to main content

endbasic_core/compiler/
args.rs

1// EndBASIC
2// Copyright 2024 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//! Common compilers for callable arguments.
17
18use crate::ast::*;
19use crate::bytecode::*;
20use crate::compiler::exprs::{compile_expr, compile_expr_as_type};
21use crate::compiler::{Error, ExprType, Fixup, Result, SymbolPrototype, SymbolsTable};
22use crate::exec::ValueTag;
23use crate::reader::LineCol;
24use crate::syms::CallableMetadata;
25use crate::syms::SymbolKey;
26use std::borrow::Cow;
27use std::collections::HashMap;
28use std::ops::RangeInclusive;
29
30/// Details to compile a required scalar parameter.
31#[derive(Clone, Debug)]
32pub struct RequiredValueSyntax {
33    /// The name of the parameter for help purposes.
34    pub name: Cow<'static, str>,
35
36    /// The type of the expected parameter.
37    pub vtype: ExprType,
38}
39
40/// Details to compile a required reference parameter.
41#[derive(Clone, Debug)]
42pub struct RequiredRefSyntax {
43    /// The name of the parameter for help purposes.
44    pub name: Cow<'static, str>,
45
46    /// If true, require an array reference; if false, a variable reference.
47    pub require_array: bool,
48
49    /// If true, allow references to undefined variables because the command will define them when
50    /// missing.  Can only be set to true for commands, not functions, and `require_array` must be
51    /// false.
52    pub define_undefined: bool,
53}
54
55/// Details to compile an optional scalar parameter.
56///
57/// Optional parameters are only supported in commands.
58#[derive(Clone, Debug)]
59pub struct OptionalValueSyntax {
60    /// The name of the parameter for help purposes.
61    pub name: Cow<'static, str>,
62
63    /// The type of the expected parameter.
64    pub vtype: ExprType,
65
66    /// Value to push onto the stack when the parameter is missing.
67    pub missing_value: i32,
68
69    /// Value to push onto the stack when the parameter is present, after which the stack contains
70    /// the parameter value.
71    pub present_value: i32,
72}
73
74/// Details to describe the type of a repeated parameter.
75#[derive(Clone, Debug)]
76pub enum RepeatedTypeSyntax {
77    /// Allows any value type, including empty arguments.  The values pushed onto the stack have
78    /// the same semantics as those pushed by `AnyValueSyntax`.
79    AnyValue,
80
81    /// Expects a value of the given type.
82    TypedValue(ExprType),
83
84    /// Expects a reference to a variable (not an array) and allows the variables to not be defined.
85    VariableRef,
86}
87
88/// Details to compile a repeated parameter.
89///
90/// The repeated parameter must appear after all singular positional parameters.
91#[derive(Clone, Debug)]
92pub struct RepeatedSyntax {
93    /// The name of the parameter for help purposes.
94    pub name: Cow<'static, str>,
95
96    /// The type of the expected parameters.
97    pub type_syn: RepeatedTypeSyntax,
98
99    /// The separator to expect between the repeated parameters.  For functions, this must be the
100    /// long separator (the comma).
101    pub sep: ArgSepSyntax,
102
103    /// Whether the repeated parameter must at least have one element or not.
104    pub require_one: bool,
105
106    /// Whether to allow any parameter to not be present or not.  Can only be true for commands.
107    pub allow_missing: bool,
108}
109
110impl RepeatedSyntax {
111    /// Formats the repeated argument syntax for help purposes into `output`.
112    ///
113    /// `last_singular_sep` contains the separator of the last singular argument syntax, if any,
114    /// which we need to place inside of the optional group.
115    fn describe(&self, output: &mut String, last_singular_sep: Option<&ArgSepSyntax>) {
116        if !self.require_one {
117            output.push('[');
118        }
119
120        if let Some(sep) = last_singular_sep {
121            sep.describe(output);
122        }
123
124        output.push_str(&self.name);
125        output.push('1');
126        if let RepeatedTypeSyntax::TypedValue(vtype) = self.type_syn {
127            output.push(vtype.annotation());
128        }
129
130        if self.require_one {
131            output.push('[');
132        }
133
134        self.sep.describe(output);
135        output.push_str("..");
136        self.sep.describe(output);
137
138        output.push_str(&self.name);
139        output.push('N');
140        if let RepeatedTypeSyntax::TypedValue(vtype) = self.type_syn {
141            output.push(vtype.annotation());
142        }
143
144        output.push(']');
145    }
146}
147
148/// Details to compile a parameter of any scalar type.
149#[derive(Clone, Debug)]
150pub struct AnyValueSyntax {
151    /// The name of the parameter for help purposes.
152    pub name: Cow<'static, str>,
153
154    /// Whether to allow the parameter to not be present or not.  Can only be true for commands.
155    pub allow_missing: bool,
156}
157
158/// Details to process an argument separator.
159#[derive(Copy, Clone, Debug, PartialEq)]
160pub enum ArgSepSyntax {
161    /// The argument separator must exactly be the one give.
162    Exactly(ArgSep),
163
164    /// The argument separator may be any of the ones given.
165    OneOf(ArgSep, ArgSep),
166
167    /// The argument separator is the end of the call.
168    End,
169}
170
171impl ArgSepSyntax {
172    /// Formats the argument separator for help purposes into `output`.
173    fn describe(&self, output: &mut String) {
174        match self {
175            ArgSepSyntax::Exactly(sep) => {
176                let (text, needs_space) = sep.describe();
177
178                if !text.is_empty() && needs_space {
179                    output.push(' ');
180                }
181                output.push_str(text);
182                if !text.is_empty() {
183                    output.push(' ');
184                }
185            }
186
187            ArgSepSyntax::OneOf(sep1, sep2) => {
188                let (text1, _needs_space1) = sep1.describe();
189                let (text2, _needs_space2) = sep2.describe();
190
191                output.push(' ');
192                output.push_str(&format!("<{}|{}>", text1, text2));
193                output.push(' ');
194            }
195
196            ArgSepSyntax::End => (),
197        };
198    }
199}
200
201/// Details to process a non-repeated argument.
202///
203/// Every item in this enum is composed of a struct that provides the details on the parameter and
204/// a struct that provides the details on how this parameter is separated from the next.
205#[derive(Clone, Debug)]
206pub enum SingularArgSyntax {
207    /// A required scalar value.
208    RequiredValue(RequiredValueSyntax, ArgSepSyntax),
209
210    /// A required reference.
211    RequiredRef(RequiredRefSyntax, ArgSepSyntax),
212
213    /// An optional scalar value.
214    OptionalValue(OptionalValueSyntax, ArgSepSyntax),
215
216    /// A required scalar value of any type.
217    AnyValue(AnyValueSyntax, ArgSepSyntax),
218}
219
220/// Details to process the arguments of a callable.
221///
222/// Note that the description of function arguments is more restricted than that of commands.
223/// The arguments compiler panics when these preconditions aren't met with the rationale that
224/// builtin functions must never be ill-defined.
225// TODO(jmmv): It might be nice to try to express these restrictions in the type system, but
226// things are already too verbose as they are...
227#[derive(Clone, Debug)]
228pub(crate) struct CallableSyntax {
229    /// Ordered list of singular arguments that appear before repeated arguments.
230    singular: Cow<'static, [SingularArgSyntax]>,
231
232    /// Details on the repeated argument allowed after singular arguments.
233    repeated: Option<Cow<'static, RepeatedSyntax>>,
234}
235
236impl CallableSyntax {
237    /// Creates a new callable arguments definition from its parts defined statically in the
238    /// code.
239    pub(crate) fn new_static(
240        singular: &'static [SingularArgSyntax],
241        repeated: Option<&'static RepeatedSyntax>,
242    ) -> Self {
243        Self { singular: Cow::Borrowed(singular), repeated: repeated.map(Cow::Borrowed) }
244    }
245
246    /// Creates a new callable arguments definition from its parts defined dynamically at
247    /// runtime.
248    pub(crate) fn new_dynamic(
249        singular: Vec<SingularArgSyntax>,
250        repeated: Option<RepeatedSyntax>,
251    ) -> Self {
252        Self { singular: Cow::Owned(singular), repeated: repeated.map(Cow::Owned) }
253    }
254
255    /// Computes the range of the expected number of parameters for this syntax.
256    fn expected_nargs(&self) -> RangeInclusive<usize> {
257        let mut min = self.singular.len();
258        let mut max = self.singular.len();
259        if let Some(syn) = self.repeated.as_ref() {
260            if syn.require_one {
261                min += 1;
262            }
263            max = usize::MAX;
264        }
265        min..=max
266    }
267
268    /// Returns true if this syntax represents "no arguments".
269    pub(crate) fn is_empty(&self) -> bool {
270        self.singular.is_empty() && self.repeated.is_none()
271    }
272
273    /// Produces a user-friendly description of this callable syntax.
274    pub(crate) fn describe(&self) -> String {
275        let mut description = String::new();
276        let mut last_singular_sep = None;
277        for (i, s) in self.singular.iter().enumerate() {
278            let sep = match s {
279                SingularArgSyntax::RequiredValue(details, sep) => {
280                    description.push_str(&details.name);
281                    description.push(details.vtype.annotation());
282                    sep
283                }
284
285                SingularArgSyntax::RequiredRef(details, sep) => {
286                    description.push_str(&details.name);
287                    sep
288                }
289
290                SingularArgSyntax::OptionalValue(details, sep) => {
291                    description.push('[');
292                    description.push_str(&details.name);
293                    description.push(details.vtype.annotation());
294                    description.push(']');
295                    sep
296                }
297
298                SingularArgSyntax::AnyValue(details, sep) => {
299                    if details.allow_missing {
300                        description.push('[');
301                    }
302                    description.push_str(&details.name);
303                    if details.allow_missing {
304                        description.push(']');
305                    }
306                    sep
307                }
308            };
309
310            if self.repeated.is_none() || i < self.singular.len() - 1 {
311                sep.describe(&mut description);
312            }
313            if i == self.singular.len() - 1 {
314                last_singular_sep = Some(sep);
315            }
316        }
317
318        if let Some(syn) = &self.repeated {
319            syn.describe(&mut description, last_singular_sep);
320        }
321
322        description
323    }
324}
325
326/// Compiles an argument that requires a reference.
327///
328/// If the reference does not exist and the syntax allowed undefined symbols, returns the details
329/// for the symbol to insert into the symbols table, which the caller must handle because we do
330/// not have mutable access to the `symtable` here.
331fn compile_required_ref(
332    instrs: &mut Vec<Instruction>,
333    md: &CallableMetadata,
334    pos: LineCol,
335    symtable: &SymbolsTable,
336    require_array: bool,
337    define_undefined: bool,
338    expr: Option<Expr>,
339) -> Result<Option<(SymbolKey, SymbolPrototype)>> {
340    match expr {
341        Some(Expr::Symbol(span)) => {
342            let key = SymbolKey::from(&span.vref.name);
343            match symtable.get(&key) {
344                None => {
345                    if !define_undefined {
346                        return Err(Error::UndefinedSymbol(span.pos, span.vref));
347                    }
348                    debug_assert!(!require_array);
349
350                    let vtype = span.vref.ref_type.unwrap_or(ExprType::Integer);
351
352                    if !span.vref.accepts(vtype) {
353                        return Err(Error::IncompatibleTypeAnnotationInReference(
354                            span.pos, span.vref,
355                        ));
356                    }
357
358                    instrs.push(Instruction::LoadRef(key.clone(), vtype, span.pos));
359                    Ok(Some((key, SymbolPrototype::Variable(vtype))))
360                }
361
362                Some(SymbolPrototype::Array(vtype, _)) => {
363                    let vtype = *vtype;
364
365                    if !span.vref.accepts(vtype) {
366                        return Err(Error::IncompatibleTypeAnnotationInReference(
367                            span.pos, span.vref,
368                        ));
369                    }
370
371                    if !require_array {
372                        return Err(Error::NotAReference(span.pos));
373                    }
374
375                    instrs.push(Instruction::LoadRef(key, vtype, span.pos));
376                    Ok(None)
377                }
378
379                Some(SymbolPrototype::Variable(vtype)) => {
380                    let vtype = *vtype;
381
382                    if !span.vref.accepts(vtype) {
383                        return Err(Error::IncompatibleTypeAnnotationInReference(
384                            span.pos, span.vref,
385                        ));
386                    }
387
388                    if require_array {
389                        return Err(Error::NotAReference(span.pos));
390                    }
391
392                    instrs.push(Instruction::LoadRef(key, vtype, span.pos));
393                    Ok(None)
394                }
395
396                Some(SymbolPrototype::BuiltinCallable(md, _))
397                | Some(SymbolPrototype::Callable(md)) => {
398                    if !span.vref.accepts_callable(md.return_type()) {
399                        return Err(Error::IncompatibleTypeAnnotationInReference(
400                            span.pos, span.vref,
401                        ));
402                    }
403
404                    Err(Error::NotArrayOrFunction(span.pos, span.vref))
405                }
406            }
407        }
408
409        Some(expr) => Err(Error::NotAReference(expr.start_pos())),
410
411        None => Err(Error::CallableSyntaxError(pos, md.clone())),
412    }
413}
414
415/// Locates the syntax definition that can parse the given number of arguments.
416///
417/// Panics if more than one syntax definition applies.
418fn find_syntax(md: &CallableMetadata, pos: LineCol, nargs: usize) -> Result<&CallableSyntax> {
419    let mut matches = md.syntaxes().iter().filter(|s| s.expected_nargs().contains(&nargs));
420    let syntax = matches.next();
421    match syntax {
422        Some(syntax) => {
423            debug_assert!(matches.next().is_none(), "Ambiguous syntax definitions");
424            Ok(syntax)
425        }
426        None => Err(Error::CallableSyntaxError(pos, md.clone())),
427    }
428}
429
430/// Compiles an argument separator with any necessary tagging.
431///
432/// `instrs` is the list of instructions into which insert the separator tag at `sep_tag_pc`
433/// when it is needed to disambiguate separators at runtime.
434///
435/// `syn` contains the details about the separator syntax that is accepted.
436///
437/// `sep` and `sep_pos` are the details about the separator being compiled.
438///
439/// `is_last` indicates whether this is the last separator in the command call and is used
440/// only for diagnostics purposes.
441#[allow(clippy::too_many_arguments)]
442fn compile_syn_argsep(
443    instrs: &mut Vec<Instruction>,
444    md: &CallableMetadata,
445    syn: &ArgSepSyntax,
446    is_last: bool,
447    sep: ArgSep,
448    sep_pos: LineCol,
449    sep_tag_pc: Address,
450) -> Result<usize> {
451    debug_assert!(
452        (!is_last || sep == ArgSep::End) && (is_last || sep != ArgSep::End),
453        "Parser can only supply an End separator in the last argument"
454    );
455
456    match syn {
457        ArgSepSyntax::Exactly(exp_sep) => {
458            debug_assert!(*exp_sep != ArgSep::End, "Use ArgSepSyntax::End");
459            if sep != ArgSep::End && sep != *exp_sep {
460                return Err(Error::CallableSyntaxError(sep_pos, md.clone()));
461            }
462            Ok(0)
463        }
464
465        ArgSepSyntax::OneOf(exp_sep1, exp_sep2) => {
466            debug_assert!(*exp_sep1 != ArgSep::End, "Use ArgSepSyntax::End");
467            debug_assert!(*exp_sep2 != ArgSep::End, "Use ArgSepSyntax::End");
468            if sep == ArgSep::End {
469                Ok(0)
470            } else {
471                if sep != *exp_sep1 && sep != *exp_sep2 {
472                    return Err(Error::CallableSyntaxError(sep_pos, md.clone()));
473                }
474                instrs.insert(sep_tag_pc, Instruction::PushInteger(sep as i32, sep_pos));
475                Ok(1)
476            }
477        }
478
479        ArgSepSyntax::End => {
480            debug_assert!(is_last);
481            Ok(0)
482        }
483    }
484}
485
486/// Parses the arguments to a command or a function and generates expressions to compute them.
487///
488/// Returns the number of arguments that the instructions added to `instrs` will push into the
489/// stack and returns the list of new symbols that need to be inserted into `symtable`.
490fn compile_args(
491    md: &CallableMetadata,
492    instrs: &mut Vec<Instruction>,
493    fixups: &mut HashMap<Address, Fixup>,
494    symtable: &SymbolsTable,
495    pos: LineCol,
496    args: Vec<ArgSpan>,
497) -> Result<(usize, Vec<(SymbolKey, SymbolPrototype)>)> {
498    let syntax = find_syntax(md, pos, args.len())?;
499
500    let input_nargs = args.len();
501    let mut aiter = args.into_iter().rev();
502
503    let mut nargs = 0;
504    let mut to_insert = vec![];
505
506    let mut remaining;
507    if let Some(syn) = syntax.repeated.as_ref() {
508        let mut min_nargs = syntax.singular.len();
509        if syn.require_one {
510            min_nargs += 1;
511        }
512        if input_nargs < min_nargs {
513            return Err(Error::CallableSyntaxError(pos, md.clone()));
514        }
515
516        let need_tags = syn.allow_missing || matches!(syn.type_syn, RepeatedTypeSyntax::AnyValue);
517
518        remaining = input_nargs;
519        while remaining > syntax.singular.len() {
520            let span = aiter.next().expect("Args and their syntax must advance in unison");
521
522            let sep_tag_pc = instrs.len();
523
524            match span.expr {
525                Some(expr) => {
526                    let pos = expr.start_pos();
527                    match syn.type_syn {
528                        RepeatedTypeSyntax::AnyValue => {
529                            debug_assert!(need_tags);
530                            let etype = compile_expr(instrs, fixups, symtable, expr, false)?;
531                            instrs
532                                .push(Instruction::PushInteger(ValueTag::from(etype) as i32, pos));
533                            nargs += 2;
534                        }
535
536                        RepeatedTypeSyntax::VariableRef => {
537                            let to_insert_one = compile_required_ref(
538                                instrs,
539                                md,
540                                pos,
541                                symtable,
542                                false,
543                                true,
544                                Some(expr),
545                            )?;
546                            if let Some(to_insert_one) = to_insert_one {
547                                to_insert.push(to_insert_one);
548                            }
549                            nargs += 1;
550                        }
551
552                        RepeatedTypeSyntax::TypedValue(vtype) => {
553                            compile_expr_as_type(instrs, fixups, symtable, expr, vtype)?;
554                            if need_tags {
555                                instrs.push(Instruction::PushInteger(
556                                    ValueTag::from(vtype) as i32,
557                                    pos,
558                                ));
559                                nargs += 2;
560                            } else {
561                                nargs += 1;
562                            }
563                        }
564                    }
565                }
566                None => {
567                    if !syn.allow_missing {
568                        return Err(Error::CallableSyntaxError(pos, md.clone()));
569                    }
570                    instrs.push(Instruction::PushInteger(ValueTag::Missing as i32, span.sep_pos));
571                    nargs += 1;
572                }
573            }
574
575            nargs += compile_syn_argsep(
576                instrs,
577                md,
578                &syn.sep,
579                input_nargs == remaining,
580                span.sep,
581                span.sep_pos,
582                sep_tag_pc,
583            )?;
584
585            remaining -= 1;
586        }
587    } else {
588        remaining = syntax.singular.len();
589    }
590
591    for syn in syntax.singular.iter().rev() {
592        let span = aiter.next().expect("Args and their syntax must advance in unison");
593
594        let sep_tag_pc = instrs.len();
595
596        let exp_sep = match syn {
597            SingularArgSyntax::RequiredValue(details, sep) => {
598                match span.expr {
599                    Some(expr) => {
600                        compile_expr_as_type(instrs, fixups, symtable, expr, details.vtype)?;
601                        nargs += 1;
602                    }
603                    None => return Err(Error::CallableSyntaxError(pos, md.clone())),
604                }
605                sep
606            }
607
608            SingularArgSyntax::RequiredRef(details, sep) => {
609                let to_insert_one = compile_required_ref(
610                    instrs,
611                    md,
612                    pos,
613                    symtable,
614                    details.require_array,
615                    details.define_undefined,
616                    span.expr,
617                )?;
618                if let Some(to_insert_one) = to_insert_one {
619                    to_insert.push(to_insert_one);
620                }
621                nargs += 1;
622                sep
623            }
624
625            SingularArgSyntax::OptionalValue(details, sep) => {
626                let (tag, pos) = match span.expr {
627                    Some(expr) => {
628                        let pos = expr.start_pos();
629                        compile_expr_as_type(instrs, fixups, symtable, expr, details.vtype)?;
630                        nargs += 1;
631                        (details.present_value, pos)
632                    }
633                    None => (details.missing_value, span.sep_pos),
634                };
635                instrs.push(Instruction::PushInteger(tag, pos));
636                nargs += 1;
637                sep
638            }
639
640            SingularArgSyntax::AnyValue(details, sep) => {
641                let (tag, pos) = match span.expr {
642                    Some(expr) => {
643                        let pos = expr.start_pos();
644                        let etype = compile_expr(instrs, fixups, symtable, expr, false)?;
645                        nargs += 2;
646                        (ValueTag::from(etype), pos)
647                    }
648                    None => {
649                        if !details.allow_missing {
650                            return Err(Error::CallableSyntaxError(span.sep_pos, md.clone()));
651                        }
652                        nargs += 1;
653                        (ValueTag::Missing, span.sep_pos)
654                    }
655                };
656                instrs.push(Instruction::PushInteger(tag as i32, pos));
657                sep
658            }
659        };
660
661        nargs += compile_syn_argsep(
662            instrs,
663            md,
664            exp_sep,
665            input_nargs == remaining,
666            span.sep,
667            span.sep_pos,
668            sep_tag_pc,
669        )?;
670
671        remaining -= 1;
672    }
673
674    Ok((nargs, to_insert))
675}
676
677/// Parses the arguments to a buitin command and generates expressions to compute them.
678///
679/// This can be used to help the runtime by doing type checking during compilation and then
680/// allowing the runtime to assume that the values on the stack are correctly typed.
681pub(super) fn compile_command_args(
682    md: &CallableMetadata,
683    instrs: &mut Vec<Instruction>,
684    fixups: &mut HashMap<Address, Fixup>,
685    symtable: &mut SymbolsTable,
686    pos: LineCol,
687    args: Vec<ArgSpan>,
688) -> Result<usize> {
689    let (nargs, to_insert) = compile_args(md, instrs, fixups, symtable, pos, args)?;
690    for (key, proto) in to_insert {
691        if !symtable.contains_key(&key) {
692            symtable.insert(key, proto);
693        }
694    }
695    Ok(nargs)
696}
697
698/// Parses the arguments to a function and generates expressions to compute them.
699///
700/// This can be used to help the runtime by doing type checking during compilation and then
701/// allowing the runtime to assume that the values on the stack are correctly typed.
702pub(super) fn compile_function_args(
703    md: &CallableMetadata,
704    instrs: &mut Vec<Instruction>,
705    fixups: &mut HashMap<Address, Fixup>,
706    symtable: &SymbolsTable,
707    pos: LineCol,
708    args: Vec<ArgSpan>,
709) -> Result<usize> {
710    let (nargs, to_insert) = compile_args(md, instrs, fixups, symtable, pos, args)?;
711    debug_assert!(to_insert.is_empty());
712    Ok(nargs)
713}
714
715#[cfg(test)]
716mod testutils {
717    use crate::syms::CallableMetadataBuilder;
718
719    use super::*;
720    use std::collections::HashMap;
721
722    /// Syntactic sugar to instantiate a `LineCol` for tests.
723    pub(super) fn lc(line: usize, col: usize) -> LineCol {
724        LineCol { line, col }
725    }
726
727    /// Builder pattern to instantiate a test scenario.
728    #[derive(Default)]
729    #[must_use]
730    pub(super) struct Tester {
731        syntaxes: Vec<CallableSyntax>,
732        symtable: SymbolsTable,
733    }
734
735    impl Tester {
736        /// Registers a syntax definition in the arguments compiler.
737        pub(super) fn syntax(
738            mut self,
739            singular: &'static [SingularArgSyntax],
740            repeated: Option<&'static RepeatedSyntax>,
741        ) -> Self {
742            self.syntaxes.push(CallableSyntax::new_static(singular, repeated));
743            self
744        }
745
746        /// Registers a pre-existing symbol in the symbols table.
747        pub(super) fn symbol(mut self, key: &str, proto: SymbolPrototype) -> Self {
748            self.symtable.insert(SymbolKey::from(key), proto);
749            self
750        }
751
752        /// Feeds command `args` into the arguments compiler and returns a checker to validate
753        /// expectations.
754        pub(super) fn compile_command<A: Into<Vec<ArgSpan>>>(mut self, args: A) -> Checker {
755            let args = args.into();
756            let mut instrs = vec![
757                // Start with one instruction to validate that the args compiler doesn't touch it.
758                Instruction::Nop,
759            ];
760            let mut fixups = HashMap::default();
761            let md = CallableMetadataBuilder::new("TEST").with_syntaxes(self.syntaxes).test_build();
762            let result = compile_command_args(
763                &md,
764                &mut instrs,
765                &mut fixups,
766                &mut self.symtable,
767                lc(1000, 2000),
768                args,
769            );
770            Checker {
771                result,
772                instrs,
773                fixups,
774                symtable: self.symtable,
775                exp_result: Ok(0),
776                exp_instrs: vec![Instruction::Nop],
777                exp_fixups: HashMap::default(),
778                exp_vars: HashMap::default(),
779            }
780        }
781    }
782
783    /// Builder pattern to validate expectations in a test scenario.
784    #[must_use]
785    pub(super) struct Checker {
786        result: Result<usize>,
787        instrs: Vec<Instruction>,
788        fixups: HashMap<Address, Fixup>,
789        symtable: SymbolsTable,
790        exp_result: Result<usize>,
791        exp_instrs: Vec<Instruction>,
792        exp_fixups: HashMap<Address, Fixup>,
793        exp_vars: HashMap<SymbolKey, ExprType>,
794    }
795
796    impl Checker {
797        /// Expects the compilation to succeeded and produce `nargs` arguments.
798        pub(super) fn exp_nargs(mut self, nargs: usize) -> Self {
799            self.exp_result = Ok(nargs);
800            self
801        }
802
803        /// Expects the compilation to fail with the given `error`.
804        pub(super) fn exp_error(mut self, error: Error) -> Self {
805            self.exp_result = Err(error);
806            self
807        }
808
809        /// Adds the given instruction to the expected instructions on success.
810        pub(super) fn exp_instr(mut self, instr: Instruction) -> Self {
811            self.exp_instrs.push(instr);
812            self
813        }
814
815        /// Expects the compilation to define a new variable `key` of type `etype`.
816        pub(super) fn exp_symbol<K: AsRef<str>>(mut self, key: K, etype: ExprType) -> Self {
817            self.exp_vars.insert(SymbolKey::from(key), etype);
818            self
819        }
820
821        /// Checks that the compilation ended with the configured expectations.
822        pub(super) fn check(self) {
823            let is_ok = self.result.is_ok();
824            assert_eq!(
825                self.exp_result.map_err(|e| format!("{}", e)),
826                self.result.map_err(|e| format!("{}", e)),
827            );
828
829            if !is_ok {
830                return;
831            }
832
833            assert_eq!(self.exp_instrs, self.instrs);
834            assert_eq!(self.exp_fixups, self.fixups);
835
836            let mut exp_keys = self.symtable.keys();
837            for (key, exp_etype) in &self.exp_vars {
838                match self.symtable.get(key) {
839                    Some(SymbolPrototype::Variable(etype)) => {
840                        assert_eq!(
841                            exp_etype, etype,
842                            "Variable {} was defined with the wrong type",
843                            key
844                        );
845                    }
846                    Some(_) => panic!("Symbol {} was defined but not as a variable", key),
847                    None => panic!("Symbol {} was not defined", key),
848                }
849                exp_keys.insert(key);
850            }
851
852            assert_eq!(exp_keys, self.symtable.keys(), "Unexpected variables defined");
853        }
854    }
855}
856
857#[cfg(test)]
858mod description_tests {
859    use super::*;
860
861    #[test]
862    fn test_no_args() {
863        assert_eq!("", CallableSyntax::new_static(&[], None).describe());
864    }
865
866    #[test]
867    fn test_singular_required_value() {
868        assert_eq!(
869            "the-arg%",
870            CallableSyntax::new_static(
871                &[SingularArgSyntax::RequiredValue(
872                    RequiredValueSyntax {
873                        name: Cow::Borrowed("the-arg"),
874                        vtype: ExprType::Integer
875                    },
876                    ArgSepSyntax::End,
877                )],
878                None,
879            )
880            .describe(),
881        );
882    }
883
884    #[test]
885    fn test_singular_required_ref() {
886        assert_eq!(
887            "the-arg",
888            CallableSyntax::new_static(
889                &[SingularArgSyntax::RequiredRef(
890                    RequiredRefSyntax {
891                        name: Cow::Borrowed("the-arg"),
892                        require_array: false,
893                        define_undefined: false
894                    },
895                    ArgSepSyntax::End,
896                )],
897                None,
898            )
899            .describe()
900        );
901    }
902
903    #[test]
904    fn test_singular_optional_value() {
905        assert_eq!(
906            "[the-arg%]",
907            CallableSyntax::new_static(
908                &[SingularArgSyntax::OptionalValue(
909                    OptionalValueSyntax {
910                        name: Cow::Borrowed("the-arg"),
911                        vtype: ExprType::Integer,
912                        missing_value: 0,
913                        present_value: 1,
914                    },
915                    ArgSepSyntax::End,
916                )],
917                None,
918            )
919            .describe()
920        );
921    }
922
923    #[test]
924    fn test_singular_any_value_required() {
925        assert_eq!(
926            "the-arg",
927            CallableSyntax::new_static(
928                &[SingularArgSyntax::AnyValue(
929                    AnyValueSyntax { name: Cow::Borrowed("the-arg"), allow_missing: false },
930                    ArgSepSyntax::End,
931                )],
932                None,
933            )
934            .describe()
935        );
936    }
937
938    #[test]
939    fn test_singular_any_value_optional() {
940        assert_eq!(
941            "[the-arg]",
942            CallableSyntax::new_static(
943                &[SingularArgSyntax::AnyValue(
944                    AnyValueSyntax { name: Cow::Borrowed("the-arg"), allow_missing: true },
945                    ArgSepSyntax::End,
946                )],
947                None,
948            )
949            .describe()
950        );
951    }
952
953    #[test]
954    fn test_singular_exactly_separators() {
955        assert_eq!(
956            "a; b AS c, d",
957            CallableSyntax::new_static(
958                &[
959                    SingularArgSyntax::AnyValue(
960                        AnyValueSyntax { name: Cow::Borrowed("a"), allow_missing: false },
961                        ArgSepSyntax::Exactly(ArgSep::Short),
962                    ),
963                    SingularArgSyntax::AnyValue(
964                        AnyValueSyntax { name: Cow::Borrowed("b"), allow_missing: false },
965                        ArgSepSyntax::Exactly(ArgSep::As),
966                    ),
967                    SingularArgSyntax::AnyValue(
968                        AnyValueSyntax { name: Cow::Borrowed("c"), allow_missing: false },
969                        ArgSepSyntax::Exactly(ArgSep::Long),
970                    ),
971                    SingularArgSyntax::AnyValue(
972                        AnyValueSyntax { name: Cow::Borrowed("d"), allow_missing: false },
973                        ArgSepSyntax::Exactly(ArgSep::End),
974                    ),
975                ],
976                None,
977            )
978            .describe()
979        );
980    }
981
982    #[test]
983    fn test_singular_oneof_separators() {
984        assert_eq!(
985            "a <;|,> b <AS|,> c",
986            CallableSyntax::new_static(
987                &[
988                    SingularArgSyntax::AnyValue(
989                        AnyValueSyntax { name: Cow::Borrowed("a"), allow_missing: false },
990                        ArgSepSyntax::OneOf(ArgSep::Short, ArgSep::Long),
991                    ),
992                    SingularArgSyntax::AnyValue(
993                        AnyValueSyntax { name: Cow::Borrowed("b"), allow_missing: false },
994                        ArgSepSyntax::OneOf(ArgSep::As, ArgSep::Long),
995                    ),
996                    SingularArgSyntax::AnyValue(
997                        AnyValueSyntax { name: Cow::Borrowed("c"), allow_missing: false },
998                        ArgSepSyntax::Exactly(ArgSep::End),
999                    ),
1000                ],
1001                None,
1002            )
1003            .describe()
1004        );
1005    }
1006
1007    #[test]
1008    fn test_repeated_require_one() {
1009        assert_eq!(
1010            "rep1[; ..; repN]",
1011            CallableSyntax::new_static(
1012                &[],
1013                Some(&RepeatedSyntax {
1014                    name: Cow::Borrowed("rep"),
1015                    type_syn: RepeatedTypeSyntax::AnyValue,
1016                    sep: ArgSepSyntax::Exactly(ArgSep::Short),
1017                    require_one: true,
1018                    allow_missing: false,
1019                }),
1020            )
1021            .describe()
1022        );
1023    }
1024
1025    #[test]
1026    fn test_repeated_allow_missing() {
1027        assert_eq!(
1028            "[rep1, .., repN]",
1029            CallableSyntax::new_static(
1030                &[],
1031                Some(&RepeatedSyntax {
1032                    name: Cow::Borrowed("rep"),
1033                    type_syn: RepeatedTypeSyntax::AnyValue,
1034                    sep: ArgSepSyntax::Exactly(ArgSep::Long),
1035                    require_one: false,
1036                    allow_missing: true,
1037                }),
1038            )
1039            .describe()
1040        );
1041    }
1042
1043    #[test]
1044    fn test_repeated_value() {
1045        assert_eq!(
1046            "rep1$[ AS .. AS repN$]",
1047            CallableSyntax::new_static(
1048                &[],
1049                Some(&RepeatedSyntax {
1050                    name: Cow::Borrowed("rep"),
1051                    type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Text),
1052                    sep: ArgSepSyntax::Exactly(ArgSep::As),
1053                    require_one: true,
1054                    allow_missing: false,
1055                }),
1056            )
1057            .describe()
1058        );
1059    }
1060
1061    #[test]
1062    fn test_repeated_ref() {
1063        assert_eq!(
1064            "rep1[ AS .. AS repN]",
1065            CallableSyntax::new_static(
1066                &[],
1067                Some(&RepeatedSyntax {
1068                    name: Cow::Borrowed("rep"),
1069                    type_syn: RepeatedTypeSyntax::VariableRef,
1070                    sep: ArgSepSyntax::Exactly(ArgSep::As),
1071                    require_one: true,
1072                    allow_missing: false,
1073                }),
1074            )
1075            .describe()
1076        );
1077    }
1078
1079    #[test]
1080    fn test_singular_and_repeated() {
1081        assert_eq!(
1082            "arg%[, rep1 <;|,> .. <;|,> repN]",
1083            CallableSyntax::new_static(
1084                &[SingularArgSyntax::RequiredValue(
1085                    RequiredValueSyntax { name: Cow::Borrowed("arg"), vtype: ExprType::Integer },
1086                    ArgSepSyntax::Exactly(ArgSep::Long),
1087                )],
1088                Some(&RepeatedSyntax {
1089                    name: Cow::Borrowed("rep"),
1090                    type_syn: RepeatedTypeSyntax::AnyValue,
1091                    sep: ArgSepSyntax::OneOf(ArgSep::Short, ArgSep::Long),
1092                    require_one: false,
1093                    allow_missing: false,
1094                }),
1095            )
1096            .describe()
1097        );
1098    }
1099}
1100
1101#[cfg(test)]
1102mod compile_tests {
1103    use super::testutils::*;
1104    use super::*;
1105    use crate::syms::CallableMetadataBuilder;
1106
1107    #[test]
1108    fn test_no_args_ok() {
1109        Tester::default().syntax(&[], None).compile_command([]).check();
1110    }
1111
1112    #[test]
1113    fn test_no_args_mismatch() {
1114        Tester::default()
1115            .syntax(&[], None)
1116            .compile_command([ArgSpan {
1117                expr: Some(Expr::Integer(IntegerSpan { value: 3, pos: lc(1, 2) })),
1118                sep: ArgSep::End,
1119                sep_pos: lc(1, 3),
1120            }])
1121            .exp_error(Error::CallableSyntaxError(
1122                lc(1000, 2000),
1123                CallableMetadataBuilder::new("TEST").test_build(),
1124            ))
1125            .check();
1126    }
1127
1128    #[test]
1129    fn test_one_required_value_ok() {
1130        Tester::default()
1131            .syntax(
1132                &[SingularArgSyntax::RequiredValue(
1133                    RequiredValueSyntax { name: Cow::Borrowed("arg1"), vtype: ExprType::Integer },
1134                    ArgSepSyntax::End,
1135                )],
1136                None,
1137            )
1138            .compile_command([ArgSpan {
1139                expr: Some(Expr::Integer(IntegerSpan { value: 3, pos: lc(1, 2) })),
1140                sep: ArgSep::End,
1141                sep_pos: lc(1, 3),
1142            }])
1143            .exp_instr(Instruction::PushInteger(3, lc(1, 2)))
1144            .exp_nargs(1)
1145            .check();
1146    }
1147
1148    #[test]
1149    fn test_one_required_value_type_promotion() {
1150        Tester::default()
1151            .syntax(
1152                &[SingularArgSyntax::RequiredValue(
1153                    RequiredValueSyntax { name: Cow::Borrowed("arg1"), vtype: ExprType::Integer },
1154                    ArgSepSyntax::End,
1155                )],
1156                None,
1157            )
1158            .compile_command([ArgSpan {
1159                expr: Some(Expr::Double(DoubleSpan { value: 3.0, pos: lc(1, 2) })),
1160                sep: ArgSep::End,
1161                sep_pos: lc(1, 5),
1162            }])
1163            .exp_instr(Instruction::PushDouble(3.0, lc(1, 2)))
1164            .exp_instr(Instruction::DoubleToInteger)
1165            .exp_nargs(1)
1166            .check();
1167    }
1168
1169    #[test]
1170    fn test_one_required_ref_variable_ok() {
1171        Tester::default()
1172            .symbol("foo", SymbolPrototype::Variable(ExprType::Text))
1173            .syntax(
1174                &[SingularArgSyntax::RequiredRef(
1175                    RequiredRefSyntax {
1176                        name: Cow::Borrowed("ref"),
1177                        require_array: false,
1178                        define_undefined: false,
1179                    },
1180                    ArgSepSyntax::End,
1181                )],
1182                None,
1183            )
1184            .compile_command([ArgSpan {
1185                expr: Some(Expr::Symbol(SymbolSpan {
1186                    vref: VarRef::new("foo", None),
1187                    pos: lc(1, 2),
1188                })),
1189                sep: ArgSep::End,
1190                sep_pos: lc(1, 5),
1191            }])
1192            .exp_instr(Instruction::LoadRef(SymbolKey::from("foo"), ExprType::Text, lc(1, 2)))
1193            .exp_nargs(1)
1194            .check();
1195    }
1196
1197    #[test]
1198    fn test_one_required_ref_variable_not_defined() {
1199        Tester::default()
1200            .syntax(
1201                &[SingularArgSyntax::RequiredRef(
1202                    RequiredRefSyntax {
1203                        name: Cow::Borrowed("ref"),
1204                        require_array: false,
1205                        define_undefined: false,
1206                    },
1207                    ArgSepSyntax::End,
1208                )],
1209                None,
1210            )
1211            .compile_command([ArgSpan {
1212                expr: Some(Expr::Symbol(SymbolSpan {
1213                    vref: VarRef::new("foo", None),
1214                    pos: lc(1, 2),
1215                })),
1216                sep: ArgSep::End,
1217                sep_pos: lc(1, 5),
1218            }])
1219            .exp_error(Error::UndefinedSymbol(lc(1, 2), VarRef::new("foo", None)))
1220            .check();
1221    }
1222
1223    #[test]
1224    fn test_one_required_ref_variable_disallow_value() {
1225        Tester::default()
1226            .syntax(
1227                &[SingularArgSyntax::RequiredRef(
1228                    RequiredRefSyntax {
1229                        name: Cow::Borrowed("ref"),
1230                        require_array: false,
1231                        define_undefined: false,
1232                    },
1233                    ArgSepSyntax::End,
1234                )],
1235                None,
1236            )
1237            .compile_command([ArgSpan {
1238                expr: Some(Expr::Integer(IntegerSpan { value: 5, pos: lc(1, 2) })),
1239                sep: ArgSep::End,
1240                sep_pos: lc(1, 5),
1241            }])
1242            .exp_error(Error::NotAReference(lc(1, 2)))
1243            .check();
1244    }
1245
1246    #[test]
1247    fn test_one_required_ref_variable_wrong_type() {
1248        Tester::default()
1249            .symbol("foo", SymbolPrototype::Array(ExprType::Text, 1))
1250            .syntax(
1251                &[SingularArgSyntax::RequiredRef(
1252                    RequiredRefSyntax {
1253                        name: Cow::Borrowed("ref"),
1254                        require_array: false,
1255                        define_undefined: false,
1256                    },
1257                    ArgSepSyntax::End,
1258                )],
1259                None,
1260            )
1261            .compile_command([ArgSpan {
1262                expr: Some(Expr::Symbol(SymbolSpan {
1263                    vref: VarRef::new("foo", None),
1264                    pos: lc(1, 2),
1265                })),
1266                sep: ArgSep::End,
1267                sep_pos: lc(1, 5),
1268            }])
1269            .exp_error(Error::NotAReference(lc(1, 2)))
1270            .check();
1271    }
1272
1273    #[test]
1274    fn test_one_required_ref_variable_wrong_annotation() {
1275        Tester::default()
1276            .symbol("foo", SymbolPrototype::Variable(ExprType::Text))
1277            .syntax(
1278                &[SingularArgSyntax::RequiredRef(
1279                    RequiredRefSyntax {
1280                        name: Cow::Borrowed("ref"),
1281                        require_array: false,
1282                        define_undefined: false,
1283                    },
1284                    ArgSepSyntax::End,
1285                )],
1286                None,
1287            )
1288            .compile_command([ArgSpan {
1289                expr: Some(Expr::Symbol(SymbolSpan {
1290                    vref: VarRef::new("foo", Some(ExprType::Integer)),
1291                    pos: lc(1, 2),
1292                })),
1293                sep: ArgSep::End,
1294                sep_pos: lc(1, 5),
1295            }])
1296            .exp_error(Error::IncompatibleTypeAnnotationInReference(
1297                lc(1, 2),
1298                VarRef::new("foo", Some(ExprType::Integer)),
1299            ))
1300            .check();
1301    }
1302
1303    #[test]
1304    fn test_one_required_ref_variable_define_undefined_default_type() {
1305        Tester::default()
1306            .syntax(
1307                &[SingularArgSyntax::RequiredRef(
1308                    RequiredRefSyntax {
1309                        name: Cow::Borrowed("ref"),
1310                        require_array: false,
1311                        define_undefined: true,
1312                    },
1313                    ArgSepSyntax::End,
1314                )],
1315                None,
1316            )
1317            .compile_command([ArgSpan {
1318                expr: Some(Expr::Symbol(SymbolSpan {
1319                    vref: VarRef::new("foo", None),
1320                    pos: lc(1, 2),
1321                })),
1322                sep: ArgSep::End,
1323                sep_pos: lc(1, 5),
1324            }])
1325            .exp_instr(Instruction::LoadRef(SymbolKey::from("foo"), ExprType::Integer, lc(1, 2)))
1326            .exp_nargs(1)
1327            .exp_symbol("foo", ExprType::Integer)
1328            .check();
1329    }
1330
1331    #[test]
1332    fn test_one_required_ref_variable_define_undefined_explicit_type() {
1333        Tester::default()
1334            .syntax(
1335                &[SingularArgSyntax::RequiredRef(
1336                    RequiredRefSyntax {
1337                        name: Cow::Borrowed("ref"),
1338                        require_array: false,
1339                        define_undefined: true,
1340                    },
1341                    ArgSepSyntax::End,
1342                )],
1343                None,
1344            )
1345            .compile_command([ArgSpan {
1346                expr: Some(Expr::Symbol(SymbolSpan {
1347                    vref: VarRef::new("foo", Some(ExprType::Text)),
1348                    pos: lc(1, 2),
1349                })),
1350                sep: ArgSep::End,
1351                sep_pos: lc(1, 6),
1352            }])
1353            .exp_instr(Instruction::LoadRef(SymbolKey::from("foo"), ExprType::Text, lc(1, 2)))
1354            .exp_nargs(1)
1355            .exp_symbol("foo", ExprType::Text)
1356            .check();
1357    }
1358
1359    #[test]
1360    fn test_multiple_required_ref_variable_define_undefined_repeated_ok() {
1361        Tester::default()
1362            .syntax(
1363                &[
1364                    SingularArgSyntax::RequiredRef(
1365                        RequiredRefSyntax {
1366                            name: Cow::Borrowed("ref1"),
1367                            require_array: false,
1368                            define_undefined: true,
1369                        },
1370                        ArgSepSyntax::Exactly(ArgSep::Long),
1371                    ),
1372                    SingularArgSyntax::RequiredRef(
1373                        RequiredRefSyntax {
1374                            name: Cow::Borrowed("ref2"),
1375                            require_array: false,
1376                            define_undefined: true,
1377                        },
1378                        ArgSepSyntax::End,
1379                    ),
1380                ],
1381                None,
1382            )
1383            .compile_command([
1384                ArgSpan {
1385                    expr: Some(Expr::Symbol(SymbolSpan {
1386                        vref: VarRef::new("foo", None),
1387                        pos: lc(1, 2),
1388                    })),
1389                    sep: ArgSep::Long,
1390                    sep_pos: lc(1, 5),
1391                },
1392                ArgSpan {
1393                    expr: Some(Expr::Symbol(SymbolSpan {
1394                        vref: VarRef::new("foo", None),
1395                        pos: lc(1, 2),
1396                    })),
1397                    sep: ArgSep::End,
1398                    sep_pos: lc(1, 5),
1399                },
1400            ])
1401            .exp_instr(Instruction::LoadRef(SymbolKey::from("foo"), ExprType::Integer, lc(1, 2)))
1402            .exp_instr(Instruction::LoadRef(SymbolKey::from("foo"), ExprType::Integer, lc(1, 2)))
1403            .exp_nargs(2)
1404            .exp_symbol("foo", ExprType::Integer)
1405            .check();
1406    }
1407
1408    #[test]
1409    fn test_one_required_ref_array_ok() {
1410        Tester::default()
1411            .symbol("foo", SymbolPrototype::Array(ExprType::Text, 0))
1412            .syntax(
1413                &[SingularArgSyntax::RequiredRef(
1414                    RequiredRefSyntax {
1415                        name: Cow::Borrowed("ref"),
1416                        require_array: true,
1417                        define_undefined: false,
1418                    },
1419                    ArgSepSyntax::End,
1420                )],
1421                None,
1422            )
1423            .compile_command([ArgSpan {
1424                expr: Some(Expr::Symbol(SymbolSpan {
1425                    vref: VarRef::new("foo", None),
1426                    pos: lc(1, 2),
1427                })),
1428                sep: ArgSep::End,
1429                sep_pos: lc(1, 5),
1430            }])
1431            .exp_instr(Instruction::LoadRef(SymbolKey::from("foo"), ExprType::Text, lc(1, 2)))
1432            .exp_nargs(1)
1433            .check();
1434    }
1435
1436    #[test]
1437    fn test_one_required_ref_array_not_defined() {
1438        Tester::default()
1439            .syntax(
1440                &[SingularArgSyntax::RequiredRef(
1441                    RequiredRefSyntax {
1442                        name: Cow::Borrowed("ref"),
1443                        require_array: true,
1444                        define_undefined: false,
1445                    },
1446                    ArgSepSyntax::End,
1447                )],
1448                None,
1449            )
1450            .compile_command([ArgSpan {
1451                expr: Some(Expr::Symbol(SymbolSpan {
1452                    vref: VarRef::new("foo", None),
1453                    pos: lc(1, 2),
1454                })),
1455                sep: ArgSep::End,
1456                sep_pos: lc(1, 5),
1457            }])
1458            .exp_error(Error::UndefinedSymbol(lc(1, 2), VarRef::new("foo", None)))
1459            .check();
1460    }
1461
1462    #[test]
1463    fn test_one_required_ref_array_disallow_value() {
1464        Tester::default()
1465            .syntax(
1466                &[SingularArgSyntax::RequiredRef(
1467                    RequiredRefSyntax {
1468                        name: Cow::Borrowed("ref"),
1469                        require_array: true,
1470                        define_undefined: false,
1471                    },
1472                    ArgSepSyntax::End,
1473                )],
1474                None,
1475            )
1476            .compile_command([ArgSpan {
1477                expr: Some(Expr::Integer(IntegerSpan { value: 5, pos: lc(1, 2) })),
1478                sep: ArgSep::End,
1479                sep_pos: lc(1, 5),
1480            }])
1481            .exp_error(Error::NotAReference(lc(1, 2)))
1482            .check();
1483    }
1484
1485    #[test]
1486    fn test_one_required_ref_array_wrong_type() {
1487        Tester::default()
1488            .symbol("foo", SymbolPrototype::Variable(ExprType::Text))
1489            .syntax(
1490                &[SingularArgSyntax::RequiredRef(
1491                    RequiredRefSyntax {
1492                        name: Cow::Borrowed("ref"),
1493                        require_array: true,
1494                        define_undefined: false,
1495                    },
1496                    ArgSepSyntax::End,
1497                )],
1498                None,
1499            )
1500            .compile_command([ArgSpan {
1501                expr: Some(Expr::Symbol(SymbolSpan {
1502                    vref: VarRef::new("foo", None),
1503                    pos: lc(1, 2),
1504                })),
1505                sep: ArgSep::End,
1506                sep_pos: lc(1, 5),
1507            }])
1508            .exp_error(Error::NotAReference(lc(1, 2)))
1509            .check();
1510    }
1511
1512    #[test]
1513    fn test_one_required_ref_array_wrong_annotation() {
1514        Tester::default()
1515            .symbol("foo", SymbolPrototype::Array(ExprType::Text, 0))
1516            .syntax(
1517                &[SingularArgSyntax::RequiredRef(
1518                    RequiredRefSyntax {
1519                        name: Cow::Borrowed("ref"),
1520                        require_array: true,
1521                        define_undefined: false,
1522                    },
1523                    ArgSepSyntax::End,
1524                )],
1525                None,
1526            )
1527            .compile_command([ArgSpan {
1528                expr: Some(Expr::Symbol(SymbolSpan {
1529                    vref: VarRef::new("foo", Some(ExprType::Integer)),
1530                    pos: lc(1, 2),
1531                })),
1532                sep: ArgSep::End,
1533                sep_pos: lc(1, 5),
1534            }])
1535            .exp_error(Error::IncompatibleTypeAnnotationInReference(
1536                lc(1, 2),
1537                VarRef::new("foo", Some(ExprType::Integer)),
1538            ))
1539            .check();
1540    }
1541
1542    #[test]
1543    fn test_one_optional_value_ok_is_present() {
1544        Tester::default()
1545            .syntax(
1546                &[SingularArgSyntax::OptionalValue(
1547                    OptionalValueSyntax {
1548                        name: Cow::Borrowed("ref"),
1549                        vtype: ExprType::Double,
1550                        missing_value: 10,
1551                        present_value: 20,
1552                    },
1553                    ArgSepSyntax::End,
1554                )],
1555                None,
1556            )
1557            .compile_command([ArgSpan {
1558                expr: Some(Expr::Double(DoubleSpan { value: 3.0, pos: lc(1, 2) })),
1559                sep: ArgSep::End,
1560                sep_pos: lc(1, 5),
1561            }])
1562            .exp_instr(Instruction::PushDouble(3.0, lc(1, 2)))
1563            .exp_instr(Instruction::PushInteger(20, lc(1, 2)))
1564            .exp_nargs(2)
1565            .check();
1566    }
1567
1568    #[test]
1569    fn test_one_optional_value_ok_is_missing() {
1570        Tester::default()
1571            .syntax(
1572                &[SingularArgSyntax::OptionalValue(
1573                    OptionalValueSyntax {
1574                        name: Cow::Borrowed("ref"),
1575                        vtype: ExprType::Double,
1576                        missing_value: 10,
1577                        present_value: 20,
1578                    },
1579                    ArgSepSyntax::End,
1580                )],
1581                None,
1582            )
1583            .compile_command([ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 2) }])
1584            .exp_instr(Instruction::PushInteger(10, lc(1, 2)))
1585            .exp_nargs(1)
1586            .check();
1587    }
1588
1589    #[test]
1590    fn test_multiple_any_value_ok() {
1591        Tester::default()
1592            .syntax(
1593                &[
1594                    SingularArgSyntax::AnyValue(
1595                        AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: false },
1596                        ArgSepSyntax::Exactly(ArgSep::Long),
1597                    ),
1598                    SingularArgSyntax::AnyValue(
1599                        AnyValueSyntax { name: Cow::Borrowed("arg2"), allow_missing: false },
1600                        ArgSepSyntax::Exactly(ArgSep::Long),
1601                    ),
1602                    SingularArgSyntax::AnyValue(
1603                        AnyValueSyntax { name: Cow::Borrowed("arg3"), allow_missing: false },
1604                        ArgSepSyntax::Exactly(ArgSep::Long),
1605                    ),
1606                    SingularArgSyntax::AnyValue(
1607                        AnyValueSyntax { name: Cow::Borrowed("arg4"), allow_missing: false },
1608                        ArgSepSyntax::End,
1609                    ),
1610                ],
1611                None,
1612            )
1613            .compile_command([
1614                ArgSpan {
1615                    expr: Some(Expr::Boolean(BooleanSpan { value: false, pos: lc(1, 2) })),
1616                    sep: ArgSep::Long,
1617                    sep_pos: lc(1, 3),
1618                },
1619                ArgSpan {
1620                    expr: Some(Expr::Double(DoubleSpan { value: 2.0, pos: lc(1, 4) })),
1621                    sep: ArgSep::Long,
1622                    sep_pos: lc(1, 5),
1623                },
1624                ArgSpan {
1625                    expr: Some(Expr::Integer(IntegerSpan { value: 3, pos: lc(1, 6) })),
1626                    sep: ArgSep::Long,
1627                    sep_pos: lc(1, 7),
1628                },
1629                ArgSpan {
1630                    expr: Some(Expr::Text(TextSpan { value: "foo".to_owned(), pos: lc(1, 8) })),
1631                    sep: ArgSep::End,
1632                    sep_pos: lc(1, 9),
1633                },
1634            ])
1635            .exp_instr(Instruction::PushString("foo".to_owned(), lc(1, 8)))
1636            .exp_instr(Instruction::PushInteger(ValueTag::Text as i32, lc(1, 8)))
1637            .exp_instr(Instruction::PushInteger(3, lc(1, 6)))
1638            .exp_instr(Instruction::PushInteger(ValueTag::Integer as i32, lc(1, 6)))
1639            .exp_instr(Instruction::PushDouble(2.0, lc(1, 4)))
1640            .exp_instr(Instruction::PushInteger(ValueTag::Double as i32, lc(1, 4)))
1641            .exp_instr(Instruction::PushBoolean(false, lc(1, 2)))
1642            .exp_instr(Instruction::PushInteger(ValueTag::Boolean as i32, lc(1, 2)))
1643            .exp_nargs(8)
1644            .check();
1645    }
1646
1647    #[test]
1648    fn test_one_any_value_expr_error() {
1649        Tester::default()
1650            .symbol("foo", SymbolPrototype::Variable(ExprType::Double))
1651            .syntax(
1652                &[SingularArgSyntax::AnyValue(
1653                    AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: false },
1654                    ArgSepSyntax::End,
1655                )],
1656                None,
1657            )
1658            .compile_command([ArgSpan {
1659                expr: Some(Expr::Symbol(SymbolSpan {
1660                    vref: VarRef::new("foo", Some(ExprType::Boolean)),
1661                    pos: lc(1, 2),
1662                })),
1663                sep: ArgSep::End,
1664                sep_pos: lc(1, 3),
1665            }])
1666            .exp_error(Error::IncompatibleTypeAnnotationInReference(
1667                lc(1, 2),
1668                VarRef::new("foo", Some(ExprType::Boolean)),
1669            ))
1670            .check();
1671    }
1672
1673    #[test]
1674    fn test_one_any_value_disallow_missing() {
1675        Tester::default()
1676            .symbol("foo", SymbolPrototype::Variable(ExprType::Double))
1677            .syntax(
1678                &[SingularArgSyntax::AnyValue(
1679                    AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: false },
1680                    ArgSepSyntax::End,
1681                )],
1682                None,
1683            )
1684            .compile_command([ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 3) }])
1685            .exp_error(Error::CallableSyntaxError(
1686                lc(1, 3),
1687                CallableMetadataBuilder::new("TEST")
1688                    .with_syntax(&[(
1689                        &[SingularArgSyntax::AnyValue(
1690                            AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: false },
1691                            ArgSepSyntax::End,
1692                        )],
1693                        None,
1694                    )])
1695                    .test_build(),
1696            ))
1697            .check();
1698    }
1699
1700    #[test]
1701    fn test_one_any_value_allow_missing() {
1702        Tester::default()
1703            .syntax(
1704                &[SingularArgSyntax::AnyValue(
1705                    AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: true },
1706                    ArgSepSyntax::End,
1707                )],
1708                None,
1709            )
1710            .compile_command([ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 3) }])
1711            .exp_instr(Instruction::PushInteger(ValueTag::Missing as i32, lc(1, 3)))
1712            .exp_nargs(1)
1713            .check();
1714    }
1715
1716    #[test]
1717    fn test_multiple_separator_types_ok() {
1718        Tester::default()
1719            .syntax(
1720                &[
1721                    SingularArgSyntax::AnyValue(
1722                        AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: true },
1723                        ArgSepSyntax::Exactly(ArgSep::As),
1724                    ),
1725                    SingularArgSyntax::AnyValue(
1726                        AnyValueSyntax { name: Cow::Borrowed("arg2"), allow_missing: true },
1727                        ArgSepSyntax::OneOf(ArgSep::Long, ArgSep::Short),
1728                    ),
1729                    SingularArgSyntax::AnyValue(
1730                        AnyValueSyntax { name: Cow::Borrowed("arg3"), allow_missing: true },
1731                        ArgSepSyntax::OneOf(ArgSep::Long, ArgSep::Short),
1732                    ),
1733                    SingularArgSyntax::AnyValue(
1734                        AnyValueSyntax { name: Cow::Borrowed("arg4"), allow_missing: true },
1735                        ArgSepSyntax::End,
1736                    ),
1737                ],
1738                None,
1739            )
1740            .compile_command([
1741                ArgSpan { expr: None, sep: ArgSep::As, sep_pos: lc(1, 1) },
1742                ArgSpan { expr: None, sep: ArgSep::Long, sep_pos: lc(1, 2) },
1743                ArgSpan { expr: None, sep: ArgSep::Short, sep_pos: lc(1, 3) },
1744                ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 4) },
1745            ])
1746            .exp_instr(Instruction::PushInteger(ValueTag::Missing as i32, lc(1, 4)))
1747            .exp_instr(Instruction::PushInteger(ArgSep::Short as i32, lc(1, 3)))
1748            .exp_instr(Instruction::PushInteger(ValueTag::Missing as i32, lc(1, 3)))
1749            .exp_instr(Instruction::PushInteger(ArgSep::Long as i32, lc(1, 2)))
1750            .exp_instr(Instruction::PushInteger(ValueTag::Missing as i32, lc(1, 2)))
1751            .exp_instr(Instruction::PushInteger(ValueTag::Missing as i32, lc(1, 1)))
1752            .exp_nargs(6)
1753            .check();
1754    }
1755
1756    #[test]
1757    fn test_multiple_separator_exactly_mismatch() {
1758        Tester::default()
1759            .syntax(
1760                &[
1761                    SingularArgSyntax::AnyValue(
1762                        AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: true },
1763                        ArgSepSyntax::Exactly(ArgSep::As),
1764                    ),
1765                    SingularArgSyntax::AnyValue(
1766                        AnyValueSyntax { name: Cow::Borrowed("arg2"), allow_missing: true },
1767                        ArgSepSyntax::End,
1768                    ),
1769                ],
1770                None,
1771            )
1772            .compile_command([
1773                ArgSpan { expr: None, sep: ArgSep::Short, sep_pos: lc(1, 1) },
1774                ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 4) },
1775            ])
1776            .exp_error(Error::CallableSyntaxError(
1777                lc(1, 1),
1778                CallableMetadataBuilder::new("TEST")
1779                    .with_syntax(&[(
1780                        &[
1781                            SingularArgSyntax::AnyValue(
1782                                AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: true },
1783                                ArgSepSyntax::Exactly(ArgSep::As),
1784                            ),
1785                            SingularArgSyntax::AnyValue(
1786                                AnyValueSyntax { name: Cow::Borrowed("arg2"), allow_missing: true },
1787                                ArgSepSyntax::End,
1788                            ),
1789                        ],
1790                        None,
1791                    )])
1792                    .test_build(),
1793            ))
1794            .check();
1795    }
1796
1797    #[test]
1798    fn test_multiple_separator_oneof_mismatch() {
1799        Tester::default()
1800            .syntax(
1801                &[
1802                    SingularArgSyntax::AnyValue(
1803                        AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: true },
1804                        ArgSepSyntax::OneOf(ArgSep::Short, ArgSep::Long),
1805                    ),
1806                    SingularArgSyntax::AnyValue(
1807                        AnyValueSyntax { name: Cow::Borrowed("arg2"), allow_missing: true },
1808                        ArgSepSyntax::End,
1809                    ),
1810                ],
1811                None,
1812            )
1813            .compile_command([
1814                ArgSpan { expr: None, sep: ArgSep::As, sep_pos: lc(1, 1) },
1815                ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 4) },
1816            ])
1817            .exp_error(Error::CallableSyntaxError(
1818                lc(1, 1),
1819                CallableMetadataBuilder::new("TEST")
1820                    .with_syntax(&[(
1821                        &[
1822                            SingularArgSyntax::AnyValue(
1823                                AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: true },
1824                                ArgSepSyntax::OneOf(ArgSep::Short, ArgSep::Long),
1825                            ),
1826                            SingularArgSyntax::AnyValue(
1827                                AnyValueSyntax { name: Cow::Borrowed("arg2"), allow_missing: true },
1828                                ArgSepSyntax::End,
1829                            ),
1830                        ],
1831                        None,
1832                    )])
1833                    .test_build(),
1834            ))
1835            .check();
1836    }
1837
1838    #[test]
1839    fn test_repeated_none() {
1840        Tester::default()
1841            .syntax(
1842                &[],
1843                Some(&RepeatedSyntax {
1844                    name: Cow::Borrowed("arg"),
1845                    type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Integer),
1846                    sep: ArgSepSyntax::Exactly(ArgSep::Long),
1847                    allow_missing: false,
1848                    require_one: false,
1849                }),
1850            )
1851            .compile_command([])
1852            .exp_nargs(0)
1853            .check();
1854    }
1855
1856    #[test]
1857    fn test_repeated_multiple_and_cast() {
1858        Tester::default()
1859            .syntax(
1860                &[],
1861                Some(&RepeatedSyntax {
1862                    name: Cow::Borrowed("arg"),
1863                    type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Integer),
1864                    sep: ArgSepSyntax::Exactly(ArgSep::Long),
1865                    allow_missing: false,
1866                    require_one: false,
1867                }),
1868            )
1869            .compile_command([
1870                ArgSpan {
1871                    expr: Some(Expr::Double(DoubleSpan { value: 3.0, pos: lc(1, 2) })),
1872                    sep: ArgSep::Long,
1873                    sep_pos: lc(1, 2),
1874                },
1875                ArgSpan {
1876                    expr: Some(Expr::Integer(IntegerSpan { value: 5, pos: lc(1, 4) })),
1877                    sep: ArgSep::End,
1878                    sep_pos: lc(1, 3),
1879                },
1880            ])
1881            .exp_instr(Instruction::PushInteger(5, lc(1, 4)))
1882            .exp_instr(Instruction::PushDouble(3.0, lc(1, 2)))
1883            .exp_instr(Instruction::DoubleToInteger)
1884            .exp_nargs(2)
1885            .check();
1886    }
1887
1888    #[test]
1889    fn test_repeated_require_one_just_one() {
1890        Tester::default()
1891            .syntax(
1892                &[],
1893                Some(&RepeatedSyntax {
1894                    name: Cow::Borrowed("arg"),
1895                    type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Integer),
1896                    sep: ArgSepSyntax::Exactly(ArgSep::Long),
1897                    allow_missing: false,
1898                    require_one: true,
1899                }),
1900            )
1901            .compile_command([ArgSpan {
1902                expr: Some(Expr::Integer(IntegerSpan { value: 5, pos: lc(1, 2) })),
1903                sep: ArgSep::End,
1904                sep_pos: lc(1, 2),
1905            }])
1906            .exp_instr(Instruction::PushInteger(5, lc(1, 2)))
1907            .exp_nargs(1)
1908            .check();
1909    }
1910
1911    #[test]
1912    fn test_repeated_require_one_missing() {
1913        Tester::default()
1914            .syntax(
1915                &[],
1916                Some(&RepeatedSyntax {
1917                    name: Cow::Borrowed("arg"),
1918                    type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Integer),
1919                    sep: ArgSepSyntax::Exactly(ArgSep::Long),
1920                    allow_missing: false,
1921                    require_one: true,
1922                }),
1923            )
1924            .compile_command([])
1925            .exp_error(Error::CallableSyntaxError(
1926                lc(1000, 2000),
1927                CallableMetadataBuilder::new("TEST")
1928                    .with_syntax(&[(
1929                        &[],
1930                        Some(&RepeatedSyntax {
1931                            name: Cow::Borrowed("arg"),
1932                            type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Integer),
1933                            sep: ArgSepSyntax::Exactly(ArgSep::Long),
1934                            allow_missing: false,
1935                            require_one: true,
1936                        }),
1937                    )])
1938                    .test_build(),
1939            ))
1940            .check();
1941    }
1942
1943    #[test]
1944    fn test_repeated_require_one_ref_ok() {
1945        Tester::default()
1946            .syntax(
1947                &[],
1948                Some(&RepeatedSyntax {
1949                    name: Cow::Borrowed("arg"),
1950                    type_syn: RepeatedTypeSyntax::VariableRef,
1951                    sep: ArgSepSyntax::Exactly(ArgSep::Long),
1952                    allow_missing: false,
1953                    require_one: true,
1954                }),
1955            )
1956            .compile_command([ArgSpan {
1957                expr: Some(Expr::Symbol(SymbolSpan {
1958                    vref: VarRef::new("foo", Some(ExprType::Text)),
1959                    pos: lc(1, 2),
1960                })),
1961                sep: ArgSep::End,
1962                sep_pos: lc(1, 2),
1963            }])
1964            .exp_instr(Instruction::LoadRef(SymbolKey::from("foo"), ExprType::Text, lc(1, 2)))
1965            .exp_nargs(1)
1966            .check();
1967    }
1968
1969    #[test]
1970    fn test_repeated_require_one_ref_error() {
1971        Tester::default()
1972            .syntax(
1973                &[],
1974                Some(&RepeatedSyntax {
1975                    name: Cow::Borrowed("arg"),
1976                    type_syn: RepeatedTypeSyntax::VariableRef,
1977                    sep: ArgSepSyntax::Exactly(ArgSep::Long),
1978                    allow_missing: false,
1979                    require_one: true,
1980                }),
1981            )
1982            .compile_command([ArgSpan {
1983                expr: Some(Expr::Integer(IntegerSpan { value: 5, pos: lc(1, 2) })),
1984                sep: ArgSep::End,
1985                sep_pos: lc(1, 2),
1986            }])
1987            .exp_error(Error::NotAReference(lc(1, 2)))
1988            .check();
1989    }
1990
1991    #[test]
1992    fn test_repeated_oneof_separator() {
1993        Tester::default()
1994            .syntax(
1995                &[],
1996                Some(&RepeatedSyntax {
1997                    name: Cow::Borrowed("arg"),
1998                    type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Double),
1999                    sep: ArgSepSyntax::OneOf(ArgSep::Long, ArgSep::Short),
2000                    allow_missing: false,
2001                    require_one: false,
2002                }),
2003            )
2004            .compile_command([
2005                ArgSpan {
2006                    expr: Some(Expr::Double(DoubleSpan { value: 3.0, pos: lc(1, 2) })),
2007                    sep: ArgSep::Short,
2008                    sep_pos: lc(1, 3),
2009                },
2010                ArgSpan {
2011                    expr: Some(Expr::Double(DoubleSpan { value: 5.0, pos: lc(1, 4) })),
2012                    sep: ArgSep::Long,
2013                    sep_pos: lc(1, 5),
2014                },
2015                ArgSpan {
2016                    expr: Some(Expr::Double(DoubleSpan { value: 2.0, pos: lc(1, 6) })),
2017                    sep: ArgSep::End,
2018                    sep_pos: lc(1, 7),
2019                },
2020            ])
2021            .exp_instr(Instruction::PushDouble(2.0, lc(1, 6)))
2022            .exp_instr(Instruction::PushInteger(ArgSep::Long as i32, lc(1, 5)))
2023            .exp_instr(Instruction::PushDouble(5.0, lc(1, 4)))
2024            .exp_instr(Instruction::PushInteger(ArgSep::Short as i32, lc(1, 3)))
2025            .exp_instr(Instruction::PushDouble(3.0, lc(1, 2)))
2026            .exp_nargs(5)
2027            .check();
2028    }
2029
2030    #[test]
2031    fn test_repeated_oneof_separator_and_missing_in_last_position() {
2032        Tester::default()
2033            .syntax(
2034                &[],
2035                Some(&RepeatedSyntax {
2036                    name: Cow::Borrowed("arg"),
2037                    type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Double),
2038                    sep: ArgSepSyntax::OneOf(ArgSep::Long, ArgSep::Short),
2039                    allow_missing: true,
2040                    require_one: false,
2041                }),
2042            )
2043            .compile_command([
2044                ArgSpan {
2045                    expr: Some(Expr::Double(DoubleSpan { value: 3.0, pos: lc(1, 2) })),
2046                    sep: ArgSep::Short,
2047                    sep_pos: lc(1, 3),
2048                },
2049                ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 4) },
2050            ])
2051            .exp_instr(Instruction::PushInteger(ValueTag::Missing as i32, lc(1, 4)))
2052            .exp_instr(Instruction::PushInteger(ArgSep::Short as i32, lc(1, 3)))
2053            .exp_instr(Instruction::PushDouble(3.0, lc(1, 2)))
2054            .exp_instr(Instruction::PushInteger(ValueTag::Double as i32, lc(1, 2)))
2055            .exp_nargs(4)
2056            .check();
2057    }
2058
2059    #[test]
2060    fn test_repeated_any_value() {
2061        Tester::default()
2062            .syntax(
2063                &[],
2064                Some(&RepeatedSyntax {
2065                    name: Cow::Borrowed("arg"),
2066                    type_syn: RepeatedTypeSyntax::AnyValue,
2067                    sep: ArgSepSyntax::Exactly(ArgSep::Long),
2068                    allow_missing: true,
2069                    require_one: false,
2070                }),
2071            )
2072            .compile_command([
2073                ArgSpan {
2074                    expr: Some(Expr::Boolean(BooleanSpan { value: false, pos: lc(1, 2) })),
2075                    sep: ArgSep::Long,
2076                    sep_pos: lc(1, 3),
2077                },
2078                ArgSpan {
2079                    expr: Some(Expr::Double(DoubleSpan { value: 2.0, pos: lc(1, 4) })),
2080                    sep: ArgSep::Long,
2081                    sep_pos: lc(1, 5),
2082                },
2083                ArgSpan {
2084                    expr: Some(Expr::Integer(IntegerSpan { value: 3, pos: lc(1, 6) })),
2085                    sep: ArgSep::Long,
2086                    sep_pos: lc(1, 7),
2087                },
2088                ArgSpan {
2089                    expr: Some(Expr::Text(TextSpan { value: "foo".to_owned(), pos: lc(1, 8) })),
2090                    sep: ArgSep::Long,
2091                    sep_pos: lc(1, 9),
2092                },
2093                ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 10) },
2094            ])
2095            .exp_instr(Instruction::PushInteger(ValueTag::Missing as i32, lc(1, 10)))
2096            .exp_instr(Instruction::PushString("foo".to_owned(), lc(1, 8)))
2097            .exp_instr(Instruction::PushInteger(ValueTag::Text as i32, lc(1, 8)))
2098            .exp_instr(Instruction::PushInteger(3, lc(1, 6)))
2099            .exp_instr(Instruction::PushInteger(ValueTag::Integer as i32, lc(1, 6)))
2100            .exp_instr(Instruction::PushDouble(2.0, lc(1, 4)))
2101            .exp_instr(Instruction::PushInteger(ValueTag::Double as i32, lc(1, 4)))
2102            .exp_instr(Instruction::PushBoolean(false, lc(1, 2)))
2103            .exp_instr(Instruction::PushInteger(ValueTag::Boolean as i32, lc(1, 2)))
2104            .exp_nargs(9)
2105            .check();
2106    }
2107
2108    #[test]
2109    fn test_singular_and_repeated() {
2110        Tester::default()
2111            .syntax(
2112                &[SingularArgSyntax::RequiredValue(
2113                    RequiredValueSyntax { name: Cow::Borrowed("arg"), vtype: ExprType::Double },
2114                    ArgSepSyntax::Exactly(ArgSep::Short),
2115                )],
2116                Some(&RepeatedSyntax {
2117                    name: Cow::Borrowed("rep"),
2118                    type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Integer),
2119                    sep: ArgSepSyntax::Exactly(ArgSep::Long),
2120                    allow_missing: false,
2121                    require_one: false,
2122                }),
2123            )
2124            .compile_command([
2125                ArgSpan {
2126                    expr: Some(Expr::Double(DoubleSpan { value: 4.0, pos: lc(1, 2) })),
2127                    sep: ArgSep::Short,
2128                    sep_pos: lc(1, 2),
2129                },
2130                ArgSpan {
2131                    expr: Some(Expr::Integer(IntegerSpan { value: 5, pos: lc(1, 5) })),
2132                    sep: ArgSep::Long,
2133                    sep_pos: lc(1, 2),
2134                },
2135                ArgSpan {
2136                    expr: Some(Expr::Integer(IntegerSpan { value: 6, pos: lc(1, 7) })),
2137                    sep: ArgSep::End,
2138                    sep_pos: lc(1, 2),
2139                },
2140            ])
2141            .exp_nargs(3)
2142            .exp_instr(Instruction::PushInteger(6, lc(1, 7)))
2143            .exp_instr(Instruction::PushInteger(5, lc(1, 5)))
2144            .exp_instr(Instruction::PushDouble(4.0, lc(1, 2)))
2145            .check();
2146    }
2147}