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