use crate::ast::*;
use crate::bytecode::*;
use crate::compiler::exprs::{compile_expr, compile_expr_as_type};
use crate::compiler::{ExprType, SymbolPrototype, SymbolsTable};
use crate::exec::ValueTag;
use crate::reader::LineCol;
use crate::syms::{CallError, SymbolKey};
use std::borrow::Cow;
use std::ops::RangeInclusive;
type Result<T> = std::result::Result<T, CallError>;
#[derive(Clone, Debug)]
pub struct RequiredValueSyntax {
pub name: Cow<'static, str>,
pub vtype: ExprType,
}
#[derive(Clone, Debug)]
pub struct RequiredRefSyntax {
pub name: Cow<'static, str>,
pub require_array: bool,
pub define_undefined: bool,
}
#[derive(Clone, Debug)]
pub struct OptionalValueSyntax {
pub name: Cow<'static, str>,
pub vtype: ExprType,
pub missing_value: i32,
pub present_value: i32,
}
#[derive(Clone, Debug)]
pub enum RepeatedTypeSyntax {
AnyValue,
TypedValue(ExprType),
VariableRef,
}
#[derive(Clone, Debug)]
pub struct RepeatedSyntax {
pub name: Cow<'static, str>,
pub type_syn: RepeatedTypeSyntax,
pub sep: ArgSepSyntax,
pub require_one: bool,
pub allow_missing: bool,
}
impl RepeatedSyntax {
fn describe(&self, output: &mut String, last_singular_sep: Option<&ArgSepSyntax>) {
if !self.require_one {
output.push('[');
}
if let Some(sep) = last_singular_sep {
sep.describe(output);
}
output.push_str(&self.name);
output.push('1');
if let RepeatedTypeSyntax::TypedValue(vtype) = self.type_syn {
output.push(vtype.annotation());
}
if self.require_one {
output.push('[');
}
self.sep.describe(output);
output.push_str("..");
self.sep.describe(output);
output.push_str(&self.name);
output.push('N');
if let RepeatedTypeSyntax::TypedValue(vtype) = self.type_syn {
output.push(vtype.annotation());
}
output.push(']');
}
}
#[derive(Clone, Debug)]
pub struct AnyValueSyntax {
pub name: Cow<'static, str>,
pub allow_missing: bool,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ArgSepSyntax {
Exactly(ArgSep),
OneOf(ArgSep, ArgSep),
End,
}
impl ArgSepSyntax {
fn describe(&self, output: &mut String) {
match self {
ArgSepSyntax::Exactly(sep) => {
let (text, needs_space) = sep.describe();
if !text.is_empty() && needs_space {
output.push(' ');
}
output.push_str(text);
if !text.is_empty() {
output.push(' ');
}
}
ArgSepSyntax::OneOf(sep1, sep2) => {
let (text1, _needs_space1) = sep1.describe();
let (text2, _needs_space2) = sep2.describe();
output.push(' ');
output.push_str(&format!("<{}|{}>", text1, text2));
output.push(' ');
}
ArgSepSyntax::End => (),
};
}
}
#[derive(Clone, Debug)]
pub enum SingularArgSyntax {
RequiredValue(RequiredValueSyntax, ArgSepSyntax),
RequiredRef(RequiredRefSyntax, ArgSepSyntax),
OptionalValue(OptionalValueSyntax, ArgSepSyntax),
AnyValue(AnyValueSyntax, ArgSepSyntax),
}
#[derive(Clone, Debug)]
pub(crate) struct CallableSyntax {
singular: Cow<'static, [SingularArgSyntax]>,
repeated: Option<Cow<'static, RepeatedSyntax>>,
}
impl CallableSyntax {
pub(crate) fn new_static(
singular: &'static [SingularArgSyntax],
repeated: Option<&'static RepeatedSyntax>,
) -> Self {
Self { singular: Cow::Borrowed(singular), repeated: repeated.map(Cow::Borrowed) }
}
pub(crate) fn new_dynamic(
singular: Vec<SingularArgSyntax>,
repeated: Option<RepeatedSyntax>,
) -> Self {
Self { singular: Cow::Owned(singular), repeated: repeated.map(Cow::Owned) }
}
fn expected_nargs(&self) -> RangeInclusive<usize> {
let mut min = self.singular.len();
let mut max = self.singular.len();
if let Some(syn) = self.repeated.as_ref() {
if syn.require_one {
min += 1;
}
max = usize::MAX;
}
min..=max
}
pub(crate) fn describe(&self) -> String {
let mut description = String::new();
let mut last_singular_sep = None;
for (i, s) in self.singular.iter().enumerate() {
let sep = match s {
SingularArgSyntax::RequiredValue(details, sep) => {
description.push_str(&details.name);
description.push(details.vtype.annotation());
sep
}
SingularArgSyntax::RequiredRef(details, sep) => {
description.push_str(&details.name);
sep
}
SingularArgSyntax::OptionalValue(details, sep) => {
description.push('[');
description.push_str(&details.name);
description.push(details.vtype.annotation());
description.push(']');
sep
}
SingularArgSyntax::AnyValue(details, sep) => {
if details.allow_missing {
description.push('[');
}
description.push_str(&details.name);
if details.allow_missing {
description.push(']');
}
sep
}
};
if self.repeated.is_none() || i < self.singular.len() - 1 {
sep.describe(&mut description);
}
if i == self.singular.len() - 1 {
last_singular_sep = Some(sep);
}
}
if let Some(syn) = &self.repeated {
syn.describe(&mut description, last_singular_sep);
}
description
}
}
fn compile_required_ref(
instrs: &mut Vec<Instruction>,
symtable: &SymbolsTable,
require_array: bool,
define_undefined: bool,
expr: Option<Expr>,
) -> Result<Option<(SymbolKey, SymbolPrototype)>> {
match expr {
Some(Expr::Symbol(span)) => {
let key = SymbolKey::from(span.vref.name());
match symtable.get(&key) {
None => {
if !define_undefined {
let message = if require_array {
format!("Undefined array {}", span.vref.name())
} else {
format!("Undefined variable {}", span.vref.name())
};
return Err(CallError::ArgumentError(span.pos, message));
}
debug_assert!(!require_array);
let vtype = span.vref.ref_type().unwrap_or(ExprType::Integer);
if !span.vref.accepts(vtype) {
return Err(CallError::ArgumentError(
span.pos,
format!("Incompatible type annotation in {} reference", span.vref),
));
}
instrs.push(Instruction::LoadRef(key.clone(), vtype, span.pos));
Ok(Some((key, SymbolPrototype::Variable(vtype))))
}
Some(SymbolPrototype::Array(vtype, _)) => {
let vtype = *vtype;
if !span.vref.accepts(vtype) {
return Err(CallError::ArgumentError(
span.pos,
format!("Incompatible type annotation in {} reference", span.vref),
));
}
if !require_array {
return Err(CallError::ArgumentError(
span.pos,
format!("{} is not a variable reference", span.vref),
));
}
instrs.push(Instruction::LoadRef(key, vtype, span.pos));
Ok(None)
}
Some(SymbolPrototype::Variable(vtype)) => {
let vtype = *vtype;
if !span.vref.accepts(vtype) {
return Err(CallError::ArgumentError(
span.pos,
format!("Incompatible type annotation in {} reference", span.vref),
));
}
if require_array {
return Err(CallError::ArgumentError(
span.pos,
format!("{} is not an array reference", span.vref),
));
}
instrs.push(Instruction::LoadRef(key, vtype, span.pos));
Ok(None)
}
Some(SymbolPrototype::Callable(md)) => {
if !span.vref.accepts_callable(md.return_type()) {
return Err(CallError::ArgumentError(
span.pos,
format!("Incompatible type annotation in {} reference", span.vref),
));
}
Err(CallError::ArgumentError(
span.pos,
format!("{} is not an array nor a function", span.vref.name()),
))
}
}
}
Some(expr) => {
let message = if require_array {
"Requires an array reference, not a value"
} else {
"Requires a variable reference, not a value"
};
Err(CallError::ArgumentError(expr.start_pos(), message.to_owned()))
}
None => Err(CallError::SyntaxError),
}
}
fn find_syntax(syntaxes: &[CallableSyntax], nargs: usize) -> Result<&CallableSyntax> {
let mut matches = syntaxes.iter().filter(|s| s.expected_nargs().contains(&nargs));
let syntax = matches.next();
debug_assert!(matches.next().is_none(), "Ambiguous syntax definitions");
match syntax {
Some(syntax) => Ok(syntax),
None => Err(CallError::SyntaxError),
}
}
fn compile_syn_argsep(
instrs: &mut Vec<Instruction>,
syn: &ArgSepSyntax,
is_last: bool,
sep: ArgSep,
sep_pos: LineCol,
sep_tag_pc: Address,
) -> Result<usize> {
debug_assert!(
(!is_last || sep == ArgSep::End) && (is_last || sep != ArgSep::End),
"Parser can only supply an End separator in the last argument"
);
match syn {
ArgSepSyntax::Exactly(exp_sep) => {
debug_assert!(*exp_sep != ArgSep::End, "Use ArgSepSyntax::End");
if sep != ArgSep::End && sep != *exp_sep {
return Err(CallError::SyntaxError);
}
Ok(0)
}
ArgSepSyntax::OneOf(exp_sep1, exp_sep2) => {
debug_assert!(*exp_sep1 != ArgSep::End, "Use ArgSepSyntax::End");
debug_assert!(*exp_sep2 != ArgSep::End, "Use ArgSepSyntax::End");
if sep == ArgSep::End {
Ok(0)
} else {
if sep != *exp_sep1 && sep != *exp_sep2 {
return Err(CallError::SyntaxError);
}
instrs.insert(sep_tag_pc, Instruction::PushInteger(sep as i32, sep_pos));
Ok(1)
}
}
ArgSepSyntax::End => {
debug_assert!(is_last);
Ok(0)
}
}
}
fn compile_arg_expr(
instrs: &mut Vec<Instruction>,
symtable: &SymbolsTable,
expr: Expr,
target: ExprType,
) -> Result<()> {
match compile_expr_as_type(instrs, symtable, expr, target) {
Ok(()) => Ok(()),
Err(e) => Err(CallError::ArgumentError(e.pos, e.message)),
}
}
fn compile_args(
syntaxes: &[CallableSyntax],
instrs: &mut Vec<Instruction>,
symtable: &SymbolsTable,
_pos: LineCol,
args: Vec<ArgSpan>,
) -> Result<(usize, Vec<(SymbolKey, SymbolPrototype)>)> {
let syntax = find_syntax(syntaxes, args.len())?;
let input_nargs = args.len();
let mut aiter = args.into_iter().rev();
let mut nargs = 0;
let mut to_insert = vec![];
let mut remaining;
if let Some(syn) = syntax.repeated.as_ref() {
let mut min_nargs = syntax.singular.len();
if syn.require_one {
min_nargs += 1;
}
if input_nargs < min_nargs {
return Err(CallError::SyntaxError);
}
let need_tags = syn.allow_missing || matches!(syn.type_syn, RepeatedTypeSyntax::AnyValue);
remaining = input_nargs;
while remaining > syntax.singular.len() {
let span = aiter.next().expect("Args and their syntax must advance in unison");
let sep_tag_pc = instrs.len();
match span.expr {
Some(expr) => {
let pos = expr.start_pos();
match syn.type_syn {
RepeatedTypeSyntax::AnyValue => {
debug_assert!(need_tags);
let etype = compile_expr(instrs, symtable, expr, false)?;
instrs
.push(Instruction::PushInteger(ValueTag::from(etype) as i32, pos));
nargs += 2;
}
RepeatedTypeSyntax::VariableRef => {
let to_insert_one =
compile_required_ref(instrs, symtable, false, true, Some(expr))?;
if let Some(to_insert_one) = to_insert_one {
to_insert.push(to_insert_one);
}
nargs += 1;
}
RepeatedTypeSyntax::TypedValue(vtype) => {
compile_arg_expr(instrs, symtable, expr, vtype)?;
if need_tags {
instrs.push(Instruction::PushInteger(
ValueTag::from(vtype) as i32,
pos,
));
nargs += 2;
} else {
nargs += 1;
}
}
}
}
None => {
if !syn.allow_missing {
return Err(CallError::SyntaxError);
}
instrs.push(Instruction::PushInteger(ValueTag::Missing as i32, span.sep_pos));
nargs += 1;
}
}
nargs += compile_syn_argsep(
instrs,
&syn.sep,
input_nargs == remaining,
span.sep,
span.sep_pos,
sep_tag_pc,
)?;
remaining -= 1;
}
} else {
remaining = syntax.singular.len();
}
for syn in syntax.singular.iter().rev() {
let span = aiter.next().expect("Args and their syntax must advance in unison");
let sep_tag_pc = instrs.len();
let exp_sep = match syn {
SingularArgSyntax::RequiredValue(details, sep) => {
match span.expr {
Some(expr) => {
compile_arg_expr(instrs, symtable, expr, details.vtype)?;
nargs += 1;
}
None => return Err(CallError::SyntaxError),
}
sep
}
SingularArgSyntax::RequiredRef(details, sep) => {
let to_insert_one = compile_required_ref(
instrs,
symtable,
details.require_array,
details.define_undefined,
span.expr,
)?;
if let Some(to_insert_one) = to_insert_one {
to_insert.push(to_insert_one);
}
nargs += 1;
sep
}
SingularArgSyntax::OptionalValue(details, sep) => {
let (tag, pos) = match span.expr {
Some(expr) => {
let pos = expr.start_pos();
compile_arg_expr(instrs, symtable, expr, details.vtype)?;
nargs += 1;
(details.present_value, pos)
}
None => (details.missing_value, span.sep_pos),
};
instrs.push(Instruction::PushInteger(tag, pos));
nargs += 1;
sep
}
SingularArgSyntax::AnyValue(details, sep) => {
let (tag, pos) = match span.expr {
Some(expr) => {
let pos = expr.start_pos();
let etype = compile_expr(instrs, symtable, expr, false)?;
nargs += 2;
(ValueTag::from(etype), pos)
}
None => {
if !details.allow_missing {
return Err(CallError::ArgumentError(
span.sep_pos,
"Missing expression before separator".to_owned(),
));
}
nargs += 1;
(ValueTag::Missing, span.sep_pos)
}
};
instrs.push(Instruction::PushInteger(tag as i32, pos));
sep
}
};
nargs += compile_syn_argsep(
instrs,
exp_sep,
input_nargs == remaining,
span.sep,
span.sep_pos,
sep_tag_pc,
)?;
remaining -= 1;
}
Ok((nargs, to_insert))
}
pub(super) fn compile_command_args(
syntaxes: &[CallableSyntax],
instrs: &mut Vec<Instruction>,
symtable: &mut SymbolsTable,
pos: LineCol,
args: Vec<ArgSpan>,
) -> Result<usize> {
let (nargs, to_insert) = compile_args(syntaxes, instrs, symtable, pos, args)?;
for (key, proto) in to_insert {
if !symtable.contains_key(&key) {
symtable.insert(key, proto);
}
}
Ok(nargs)
}
pub(super) fn compile_function_args(
syntaxes: &[CallableSyntax],
instrs: &mut Vec<Instruction>,
symtable: &SymbolsTable,
pos: LineCol,
args: Vec<ArgSpan>,
) -> Result<usize> {
let (nargs, to_insert) = compile_args(syntaxes, instrs, symtable, pos, args)?;
debug_assert!(to_insert.is_empty());
Ok(nargs)
}
#[cfg(test)]
mod testutils {
use super::*;
use std::collections::HashMap;
pub(super) fn lc(line: usize, col: usize) -> LineCol {
LineCol { line, col }
}
#[derive(Default)]
#[must_use]
pub(super) struct Tester {
syntaxes: Vec<CallableSyntax>,
symtable: SymbolsTable,
}
impl Tester {
pub(super) fn syntax(
mut self,
singular: &'static [SingularArgSyntax],
repeated: Option<&'static RepeatedSyntax>,
) -> Self {
self.syntaxes.push(CallableSyntax::new_static(singular, repeated));
self
}
pub(super) fn symbol(mut self, key: &str, proto: SymbolPrototype) -> Self {
self.symtable.insert(SymbolKey::from(key), proto);
self
}
pub(super) fn compile_command<A: Into<Vec<ArgSpan>>>(mut self, args: A) -> Checker {
let args = args.into();
let mut instrs = vec![
Instruction::Nop,
];
let result = compile_command_args(
&self.syntaxes,
&mut instrs,
&mut self.symtable,
lc(1000, 2000),
args,
);
Checker {
result,
instrs,
symtable: self.symtable,
exp_result: Ok(0),
exp_instrs: vec![Instruction::Nop],
exp_vars: HashMap::default(),
}
}
}
#[must_use]
pub(super) struct Checker {
result: Result<usize>,
instrs: Vec<Instruction>,
symtable: SymbolsTable,
exp_result: Result<usize>,
exp_instrs: Vec<Instruction>,
exp_vars: HashMap<SymbolKey, ExprType>,
}
impl Checker {
pub(super) fn exp_nargs(mut self, nargs: usize) -> Self {
self.exp_result = Ok(nargs);
self
}
pub(super) fn exp_error(mut self, error: CallError) -> Self {
self.exp_result = Err(error);
self
}
pub(super) fn exp_instr(mut self, instr: Instruction) -> Self {
self.exp_instrs.push(instr);
self
}
pub(super) fn exp_symbol<K: AsRef<str>>(mut self, key: K, etype: ExprType) -> Self {
self.exp_vars.insert(SymbolKey::from(key), etype);
self
}
fn format_call_error(e: CallError) -> String {
match e {
CallError::ArgumentError(pos, e) => format!("{}:{}: {}", pos.line, pos.col, e),
CallError::EvalError(pos, e) => format!("{}:{}: {}", pos.line, pos.col, e),
CallError::InternalError(_pos, e) => panic!("Must not happen here: {}", e),
CallError::IoError(e) => panic!("Must not happen here: {}", e),
CallError::NestedError(e) => panic!("Must not happen here: {}", e),
CallError::SyntaxError => "Syntax error".to_owned(),
}
}
pub(super) fn check(self) {
let is_ok = self.result.is_ok();
assert_eq!(
self.exp_result.map_err(Self::format_call_error),
self.result.map_err(Self::format_call_error),
);
if !is_ok {
return;
}
assert_eq!(self.exp_instrs, self.instrs);
let mut exp_keys = self.symtable.keys();
for (key, exp_etype) in &self.exp_vars {
match self.symtable.get(key) {
Some(SymbolPrototype::Variable(etype)) => {
assert_eq!(
exp_etype, etype,
"Variable {} was defined with the wrong type",
key
);
}
Some(_) => panic!("Symbol {} was defined but not as a variable", key),
None => panic!("Symbol {} was not defined", key),
}
exp_keys.insert(key);
}
assert_eq!(exp_keys, self.symtable.keys(), "Unexpected variables defined");
}
}
}
#[cfg(test)]
mod description_tests {
use super::*;
#[test]
fn test_no_args() {
assert_eq!("", CallableSyntax::new_static(&[], None).describe());
}
#[test]
fn test_singular_required_value() {
assert_eq!(
"the-arg%",
CallableSyntax::new_static(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax {
name: Cow::Borrowed("the-arg"),
vtype: ExprType::Integer
},
ArgSepSyntax::End,
)],
None,
)
.describe(),
);
}
#[test]
fn test_singular_required_ref() {
assert_eq!(
"the-arg",
CallableSyntax::new_static(
&[SingularArgSyntax::RequiredRef(
RequiredRefSyntax {
name: Cow::Borrowed("the-arg"),
require_array: false,
define_undefined: false
},
ArgSepSyntax::End,
)],
None,
)
.describe()
);
}
#[test]
fn test_singular_optional_value() {
assert_eq!(
"[the-arg%]",
CallableSyntax::new_static(
&[SingularArgSyntax::OptionalValue(
OptionalValueSyntax {
name: Cow::Borrowed("the-arg"),
vtype: ExprType::Integer,
missing_value: 0,
present_value: 1,
},
ArgSepSyntax::End,
)],
None,
)
.describe()
);
}
#[test]
fn test_singular_any_value_required() {
assert_eq!(
"the-arg",
CallableSyntax::new_static(
&[SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("the-arg"), allow_missing: false },
ArgSepSyntax::End,
)],
None,
)
.describe()
);
}
#[test]
fn test_singular_any_value_optional() {
assert_eq!(
"[the-arg]",
CallableSyntax::new_static(
&[SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("the-arg"), allow_missing: true },
ArgSepSyntax::End,
)],
None,
)
.describe()
);
}
#[test]
fn test_singular_exactly_separators() {
assert_eq!(
"a; b AS c, d",
CallableSyntax::new_static(
&[
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("a"), allow_missing: false },
ArgSepSyntax::Exactly(ArgSep::Short),
),
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("b"), allow_missing: false },
ArgSepSyntax::Exactly(ArgSep::As),
),
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("c"), allow_missing: false },
ArgSepSyntax::Exactly(ArgSep::Long),
),
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("d"), allow_missing: false },
ArgSepSyntax::Exactly(ArgSep::End),
),
],
None,
)
.describe()
);
}
#[test]
fn test_singular_oneof_separators() {
assert_eq!(
"a <;|,> b <AS|,> c",
CallableSyntax::new_static(
&[
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("a"), allow_missing: false },
ArgSepSyntax::OneOf(ArgSep::Short, ArgSep::Long),
),
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("b"), allow_missing: false },
ArgSepSyntax::OneOf(ArgSep::As, ArgSep::Long),
),
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("c"), allow_missing: false },
ArgSepSyntax::Exactly(ArgSep::End),
),
],
None,
)
.describe()
);
}
#[test]
fn test_repeated_require_one() {
assert_eq!(
"rep1[; ..; repN]",
CallableSyntax::new_static(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("rep"),
type_syn: RepeatedTypeSyntax::AnyValue,
sep: ArgSepSyntax::Exactly(ArgSep::Short),
require_one: true,
allow_missing: false,
}),
)
.describe()
);
}
#[test]
fn test_repeated_allow_missing() {
assert_eq!(
"[rep1, .., repN]",
CallableSyntax::new_static(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("rep"),
type_syn: RepeatedTypeSyntax::AnyValue,
sep: ArgSepSyntax::Exactly(ArgSep::Long),
require_one: false,
allow_missing: true,
}),
)
.describe()
);
}
#[test]
fn test_repeated_value() {
assert_eq!(
"rep1$[ AS .. AS repN$]",
CallableSyntax::new_static(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("rep"),
type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Text),
sep: ArgSepSyntax::Exactly(ArgSep::As),
require_one: true,
allow_missing: false,
}),
)
.describe()
);
}
#[test]
fn test_repeated_ref() {
assert_eq!(
"rep1[ AS .. AS repN]",
CallableSyntax::new_static(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("rep"),
type_syn: RepeatedTypeSyntax::VariableRef,
sep: ArgSepSyntax::Exactly(ArgSep::As),
require_one: true,
allow_missing: false,
}),
)
.describe()
);
}
#[test]
fn test_singular_and_repeated() {
assert_eq!(
"arg%[, rep1 <;|,> .. <;|,> repN]",
CallableSyntax::new_static(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax { name: Cow::Borrowed("arg"), vtype: ExprType::Integer },
ArgSepSyntax::Exactly(ArgSep::Long),
)],
Some(&RepeatedSyntax {
name: Cow::Borrowed("rep"),
type_syn: RepeatedTypeSyntax::AnyValue,
sep: ArgSepSyntax::OneOf(ArgSep::Short, ArgSep::Long),
require_one: false,
allow_missing: false,
}),
)
.describe()
);
}
}
#[cfg(test)]
mod compile_tests {
use super::testutils::*;
use super::*;
#[test]
fn test_no_syntaxes_yields_error() {
Tester::default().compile_command([]).exp_error(CallError::SyntaxError).check();
}
#[test]
fn test_no_args_ok() {
Tester::default().syntax(&[], None).compile_command([]).check();
}
#[test]
fn test_no_args_mismatch() {
Tester::default()
.syntax(&[], None)
.compile_command([ArgSpan {
expr: Some(Expr::Integer(IntegerSpan { value: 3, pos: lc(1, 2) })),
sep: ArgSep::End,
sep_pos: lc(1, 3),
}])
.exp_error(CallError::SyntaxError)
.check();
}
#[test]
fn test_one_required_value_ok() {
Tester::default()
.syntax(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax { name: Cow::Borrowed("arg1"), vtype: ExprType::Integer },
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Integer(IntegerSpan { value: 3, pos: lc(1, 2) })),
sep: ArgSep::End,
sep_pos: lc(1, 3),
}])
.exp_instr(Instruction::PushInteger(3, lc(1, 2)))
.exp_nargs(1)
.check();
}
#[test]
fn test_one_required_value_type_promotion() {
Tester::default()
.syntax(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax { name: Cow::Borrowed("arg1"), vtype: ExprType::Integer },
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Double(DoubleSpan { value: 3.0, pos: lc(1, 2) })),
sep: ArgSep::End,
sep_pos: lc(1, 5),
}])
.exp_instr(Instruction::PushDouble(3.0, lc(1, 2)))
.exp_instr(Instruction::DoubleToInteger)
.exp_nargs(1)
.check();
}
#[test]
fn test_one_required_ref_variable_ok() {
Tester::default()
.symbol("foo", SymbolPrototype::Variable(ExprType::Text))
.syntax(
&[SingularArgSyntax::RequiredRef(
RequiredRefSyntax {
name: Cow::Borrowed("ref"),
require_array: false,
define_undefined: false,
},
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Symbol(SymbolSpan {
vref: VarRef::new("foo", None),
pos: lc(1, 2),
})),
sep: ArgSep::End,
sep_pos: lc(1, 5),
}])
.exp_instr(Instruction::LoadRef(SymbolKey::from("foo"), ExprType::Text, lc(1, 2)))
.exp_nargs(1)
.check();
}
#[test]
fn test_one_required_ref_variable_not_defined() {
Tester::default()
.syntax(
&[SingularArgSyntax::RequiredRef(
RequiredRefSyntax {
name: Cow::Borrowed("ref"),
require_array: false,
define_undefined: false,
},
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Symbol(SymbolSpan {
vref: VarRef::new("foo", None),
pos: lc(1, 2),
})),
sep: ArgSep::End,
sep_pos: lc(1, 5),
}])
.exp_error(CallError::ArgumentError(lc(1, 2), "Undefined variable foo".to_owned()))
.check();
}
#[test]
fn test_one_required_ref_variable_disallow_value() {
Tester::default()
.syntax(
&[SingularArgSyntax::RequiredRef(
RequiredRefSyntax {
name: Cow::Borrowed("ref"),
require_array: false,
define_undefined: false,
},
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Integer(IntegerSpan { value: 5, pos: lc(1, 2) })),
sep: ArgSep::End,
sep_pos: lc(1, 5),
}])
.exp_error(CallError::ArgumentError(
lc(1, 2),
"Requires a variable reference, not a value".to_owned(),
))
.check();
}
#[test]
fn test_one_required_ref_variable_wrong_type() {
Tester::default()
.symbol("foo", SymbolPrototype::Array(ExprType::Text, 1))
.syntax(
&[SingularArgSyntax::RequiredRef(
RequiredRefSyntax {
name: Cow::Borrowed("ref"),
require_array: false,
define_undefined: false,
},
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Symbol(SymbolSpan {
vref: VarRef::new("foo", None),
pos: lc(1, 2),
})),
sep: ArgSep::End,
sep_pos: lc(1, 5),
}])
.exp_error(CallError::ArgumentError(
lc(1, 2),
"foo is not a variable reference".to_owned(),
))
.check();
}
#[test]
fn test_one_required_ref_variable_wrong_annotation() {
Tester::default()
.symbol("foo", SymbolPrototype::Variable(ExprType::Text))
.syntax(
&[SingularArgSyntax::RequiredRef(
RequiredRefSyntax {
name: Cow::Borrowed("ref"),
require_array: false,
define_undefined: false,
},
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Symbol(SymbolSpan {
vref: VarRef::new("foo", Some(ExprType::Integer)),
pos: lc(1, 2),
})),
sep: ArgSep::End,
sep_pos: lc(1, 5),
}])
.exp_error(CallError::ArgumentError(
lc(1, 2),
"Incompatible type annotation in foo% reference".to_owned(),
))
.check();
}
#[test]
fn test_one_required_ref_variable_define_undefined_default_type() {
Tester::default()
.syntax(
&[SingularArgSyntax::RequiredRef(
RequiredRefSyntax {
name: Cow::Borrowed("ref"),
require_array: false,
define_undefined: true,
},
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Symbol(SymbolSpan {
vref: VarRef::new("foo", None),
pos: lc(1, 2),
})),
sep: ArgSep::End,
sep_pos: lc(1, 5),
}])
.exp_instr(Instruction::LoadRef(SymbolKey::from("foo"), ExprType::Integer, lc(1, 2)))
.exp_nargs(1)
.exp_symbol("foo", ExprType::Integer)
.check();
}
#[test]
fn test_one_required_ref_variable_define_undefined_explicit_type() {
Tester::default()
.syntax(
&[SingularArgSyntax::RequiredRef(
RequiredRefSyntax {
name: Cow::Borrowed("ref"),
require_array: false,
define_undefined: true,
},
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Symbol(SymbolSpan {
vref: VarRef::new("foo", Some(ExprType::Text)),
pos: lc(1, 2),
})),
sep: ArgSep::End,
sep_pos: lc(1, 6),
}])
.exp_instr(Instruction::LoadRef(SymbolKey::from("foo"), ExprType::Text, lc(1, 2)))
.exp_nargs(1)
.exp_symbol("foo", ExprType::Text)
.check();
}
#[test]
fn test_multiple_required_ref_variable_define_undefined_repeated_ok() {
Tester::default()
.syntax(
&[
SingularArgSyntax::RequiredRef(
RequiredRefSyntax {
name: Cow::Borrowed("ref1"),
require_array: false,
define_undefined: true,
},
ArgSepSyntax::Exactly(ArgSep::Long),
),
SingularArgSyntax::RequiredRef(
RequiredRefSyntax {
name: Cow::Borrowed("ref2"),
require_array: false,
define_undefined: true,
},
ArgSepSyntax::End,
),
],
None,
)
.compile_command([
ArgSpan {
expr: Some(Expr::Symbol(SymbolSpan {
vref: VarRef::new("foo", None),
pos: lc(1, 2),
})),
sep: ArgSep::Long,
sep_pos: lc(1, 5),
},
ArgSpan {
expr: Some(Expr::Symbol(SymbolSpan {
vref: VarRef::new("foo", None),
pos: lc(1, 2),
})),
sep: ArgSep::End,
sep_pos: lc(1, 5),
},
])
.exp_instr(Instruction::LoadRef(SymbolKey::from("foo"), ExprType::Integer, lc(1, 2)))
.exp_instr(Instruction::LoadRef(SymbolKey::from("foo"), ExprType::Integer, lc(1, 2)))
.exp_nargs(2)
.exp_symbol("foo", ExprType::Integer)
.check();
}
#[test]
fn test_one_required_ref_array_ok() {
Tester::default()
.symbol("foo", SymbolPrototype::Array(ExprType::Text, 0))
.syntax(
&[SingularArgSyntax::RequiredRef(
RequiredRefSyntax {
name: Cow::Borrowed("ref"),
require_array: true,
define_undefined: false,
},
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Symbol(SymbolSpan {
vref: VarRef::new("foo", None),
pos: lc(1, 2),
})),
sep: ArgSep::End,
sep_pos: lc(1, 5),
}])
.exp_instr(Instruction::LoadRef(SymbolKey::from("foo"), ExprType::Text, lc(1, 2)))
.exp_nargs(1)
.check();
}
#[test]
fn test_one_required_ref_array_not_defined() {
Tester::default()
.syntax(
&[SingularArgSyntax::RequiredRef(
RequiredRefSyntax {
name: Cow::Borrowed("ref"),
require_array: true,
define_undefined: false,
},
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Symbol(SymbolSpan {
vref: VarRef::new("foo", None),
pos: lc(1, 2),
})),
sep: ArgSep::End,
sep_pos: lc(1, 5),
}])
.exp_error(CallError::ArgumentError(lc(1, 2), "Undefined array foo".to_owned()))
.check();
}
#[test]
fn test_one_required_ref_array_disallow_value() {
Tester::default()
.syntax(
&[SingularArgSyntax::RequiredRef(
RequiredRefSyntax {
name: Cow::Borrowed("ref"),
require_array: true,
define_undefined: false,
},
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Integer(IntegerSpan { value: 5, pos: lc(1, 2) })),
sep: ArgSep::End,
sep_pos: lc(1, 5),
}])
.exp_error(CallError::ArgumentError(
lc(1, 2),
"Requires an array reference, not a value".to_owned(),
))
.check();
}
#[test]
fn test_one_required_ref_array_wrong_type() {
Tester::default()
.symbol("foo", SymbolPrototype::Variable(ExprType::Text))
.syntax(
&[SingularArgSyntax::RequiredRef(
RequiredRefSyntax {
name: Cow::Borrowed("ref"),
require_array: true,
define_undefined: false,
},
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Symbol(SymbolSpan {
vref: VarRef::new("foo", None),
pos: lc(1, 2),
})),
sep: ArgSep::End,
sep_pos: lc(1, 5),
}])
.exp_error(CallError::ArgumentError(
lc(1, 2),
"foo is not an array reference".to_owned(),
))
.check();
}
#[test]
fn test_one_required_ref_array_wrong_annotation() {
Tester::default()
.symbol("foo", SymbolPrototype::Array(ExprType::Text, 0))
.syntax(
&[SingularArgSyntax::RequiredRef(
RequiredRefSyntax {
name: Cow::Borrowed("ref"),
require_array: true,
define_undefined: false,
},
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Symbol(SymbolSpan {
vref: VarRef::new("foo", Some(ExprType::Integer)),
pos: lc(1, 2),
})),
sep: ArgSep::End,
sep_pos: lc(1, 5),
}])
.exp_error(CallError::ArgumentError(
lc(1, 2),
"Incompatible type annotation in foo% reference".to_owned(),
))
.check();
}
#[test]
fn test_one_optional_value_ok_is_present() {
Tester::default()
.syntax(
&[SingularArgSyntax::OptionalValue(
OptionalValueSyntax {
name: Cow::Borrowed("ref"),
vtype: ExprType::Double,
missing_value: 10,
present_value: 20,
},
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Double(DoubleSpan { value: 3.0, pos: lc(1, 2) })),
sep: ArgSep::End,
sep_pos: lc(1, 5),
}])
.exp_instr(Instruction::PushDouble(3.0, lc(1, 2)))
.exp_instr(Instruction::PushInteger(20, lc(1, 2)))
.exp_nargs(2)
.check();
}
#[test]
fn test_one_optional_value_ok_is_missing() {
Tester::default()
.syntax(
&[SingularArgSyntax::OptionalValue(
OptionalValueSyntax {
name: Cow::Borrowed("ref"),
vtype: ExprType::Double,
missing_value: 10,
present_value: 20,
},
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 2) }])
.exp_instr(Instruction::PushInteger(10, lc(1, 2)))
.exp_nargs(1)
.check();
}
#[test]
fn test_multiple_any_value_ok() {
Tester::default()
.syntax(
&[
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: false },
ArgSepSyntax::Exactly(ArgSep::Long),
),
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("arg2"), allow_missing: false },
ArgSepSyntax::Exactly(ArgSep::Long),
),
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("arg3"), allow_missing: false },
ArgSepSyntax::Exactly(ArgSep::Long),
),
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("arg4"), allow_missing: false },
ArgSepSyntax::End,
),
],
None,
)
.compile_command([
ArgSpan {
expr: Some(Expr::Boolean(BooleanSpan { value: false, pos: lc(1, 2) })),
sep: ArgSep::Long,
sep_pos: lc(1, 3),
},
ArgSpan {
expr: Some(Expr::Double(DoubleSpan { value: 2.0, pos: lc(1, 4) })),
sep: ArgSep::Long,
sep_pos: lc(1, 5),
},
ArgSpan {
expr: Some(Expr::Integer(IntegerSpan { value: 3, pos: lc(1, 6) })),
sep: ArgSep::Long,
sep_pos: lc(1, 7),
},
ArgSpan {
expr: Some(Expr::Text(TextSpan { value: "foo".to_owned(), pos: lc(1, 8) })),
sep: ArgSep::End,
sep_pos: lc(1, 9),
},
])
.exp_instr(Instruction::PushString("foo".to_owned(), lc(1, 8)))
.exp_instr(Instruction::PushInteger(ValueTag::Text as i32, lc(1, 8)))
.exp_instr(Instruction::PushInteger(3, lc(1, 6)))
.exp_instr(Instruction::PushInteger(ValueTag::Integer as i32, lc(1, 6)))
.exp_instr(Instruction::PushDouble(2.0, lc(1, 4)))
.exp_instr(Instruction::PushInteger(ValueTag::Double as i32, lc(1, 4)))
.exp_instr(Instruction::PushBoolean(false, lc(1, 2)))
.exp_instr(Instruction::PushInteger(ValueTag::Boolean as i32, lc(1, 2)))
.exp_nargs(8)
.check();
}
#[test]
fn test_one_any_value_expr_error() {
Tester::default()
.symbol("foo", SymbolPrototype::Variable(ExprType::Double))
.syntax(
&[SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: false },
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan {
expr: Some(Expr::Symbol(SymbolSpan {
vref: VarRef::new("foo", Some(ExprType::Boolean)),
pos: lc(1, 2),
})),
sep: ArgSep::End,
sep_pos: lc(1, 3),
}])
.exp_error(CallError::ArgumentError(
lc(1, 2),
"Incompatible type annotation in foo? reference".to_owned(),
))
.check();
}
#[test]
fn test_one_any_value_disallow_missing() {
Tester::default()
.symbol("foo", SymbolPrototype::Variable(ExprType::Double))
.syntax(
&[SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: false },
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 3) }])
.exp_error(CallError::ArgumentError(
lc(1, 3),
"Missing expression before separator".to_owned(),
))
.check();
}
#[test]
fn test_one_any_value_allow_missing() {
Tester::default()
.syntax(
&[SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: true },
ArgSepSyntax::End,
)],
None,
)
.compile_command([ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 3) }])
.exp_instr(Instruction::PushInteger(ValueTag::Missing as i32, lc(1, 3)))
.exp_nargs(1)
.check();
}
#[test]
fn test_multiple_separator_types_ok() {
Tester::default()
.syntax(
&[
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: true },
ArgSepSyntax::Exactly(ArgSep::As),
),
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("arg2"), allow_missing: true },
ArgSepSyntax::OneOf(ArgSep::Long, ArgSep::Short),
),
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("arg3"), allow_missing: true },
ArgSepSyntax::OneOf(ArgSep::Long, ArgSep::Short),
),
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("arg4"), allow_missing: true },
ArgSepSyntax::End,
),
],
None,
)
.compile_command([
ArgSpan { expr: None, sep: ArgSep::As, sep_pos: lc(1, 1) },
ArgSpan { expr: None, sep: ArgSep::Long, sep_pos: lc(1, 2) },
ArgSpan { expr: None, sep: ArgSep::Short, sep_pos: lc(1, 3) },
ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 4) },
])
.exp_instr(Instruction::PushInteger(ValueTag::Missing as i32, lc(1, 4)))
.exp_instr(Instruction::PushInteger(ArgSep::Short as i32, lc(1, 3)))
.exp_instr(Instruction::PushInteger(ValueTag::Missing as i32, lc(1, 3)))
.exp_instr(Instruction::PushInteger(ArgSep::Long as i32, lc(1, 2)))
.exp_instr(Instruction::PushInteger(ValueTag::Missing as i32, lc(1, 2)))
.exp_instr(Instruction::PushInteger(ValueTag::Missing as i32, lc(1, 1)))
.exp_nargs(6)
.check();
}
#[test]
fn test_multiple_separator_exactly_mismatch() {
Tester::default()
.syntax(
&[
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: true },
ArgSepSyntax::Exactly(ArgSep::As),
),
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("arg2"), allow_missing: true },
ArgSepSyntax::End,
),
],
None,
)
.compile_command([
ArgSpan { expr: None, sep: ArgSep::Short, sep_pos: lc(1, 1) },
ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 4) },
])
.exp_error(CallError::SyntaxError)
.check();
}
#[test]
fn test_multiple_separator_oneof_mismatch() {
Tester::default()
.syntax(
&[
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("arg1"), allow_missing: true },
ArgSepSyntax::OneOf(ArgSep::Short, ArgSep::Long),
),
SingularArgSyntax::AnyValue(
AnyValueSyntax { name: Cow::Borrowed("arg2"), allow_missing: true },
ArgSepSyntax::End,
),
],
None,
)
.compile_command([
ArgSpan { expr: None, sep: ArgSep::As, sep_pos: lc(1, 1) },
ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 4) },
])
.exp_error(CallError::SyntaxError)
.check();
}
#[test]
fn test_repeated_none() {
Tester::default()
.syntax(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("arg"),
type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Integer),
sep: ArgSepSyntax::Exactly(ArgSep::Long),
allow_missing: false,
require_one: false,
}),
)
.compile_command([])
.exp_nargs(0)
.check();
}
#[test]
fn test_repeated_multiple_and_cast() {
Tester::default()
.syntax(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("arg"),
type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Integer),
sep: ArgSepSyntax::Exactly(ArgSep::Long),
allow_missing: false,
require_one: false,
}),
)
.compile_command([
ArgSpan {
expr: Some(Expr::Double(DoubleSpan { value: 3.0, pos: lc(1, 2) })),
sep: ArgSep::Long,
sep_pos: lc(1, 2),
},
ArgSpan {
expr: Some(Expr::Integer(IntegerSpan { value: 5, pos: lc(1, 4) })),
sep: ArgSep::End,
sep_pos: lc(1, 3),
},
])
.exp_instr(Instruction::PushInteger(5, lc(1, 4)))
.exp_instr(Instruction::PushDouble(3.0, lc(1, 2)))
.exp_instr(Instruction::DoubleToInteger)
.exp_nargs(2)
.check();
}
#[test]
fn test_repeated_require_one_just_one() {
Tester::default()
.syntax(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("arg"),
type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Integer),
sep: ArgSepSyntax::Exactly(ArgSep::Long),
allow_missing: false,
require_one: true,
}),
)
.compile_command([ArgSpan {
expr: Some(Expr::Integer(IntegerSpan { value: 5, pos: lc(1, 2) })),
sep: ArgSep::End,
sep_pos: lc(1, 2),
}])
.exp_instr(Instruction::PushInteger(5, lc(1, 2)))
.exp_nargs(1)
.check();
}
#[test]
fn test_repeated_require_one_missing() {
Tester::default()
.syntax(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("arg"),
type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Integer),
sep: ArgSepSyntax::Exactly(ArgSep::Long),
allow_missing: false,
require_one: true,
}),
)
.compile_command([])
.exp_error(CallError::SyntaxError)
.check();
}
#[test]
fn test_repeated_require_one_ref_ok() {
Tester::default()
.syntax(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("arg"),
type_syn: RepeatedTypeSyntax::VariableRef,
sep: ArgSepSyntax::Exactly(ArgSep::Long),
allow_missing: false,
require_one: true,
}),
)
.compile_command([ArgSpan {
expr: Some(Expr::Symbol(SymbolSpan {
vref: VarRef::new("foo", Some(ExprType::Text)),
pos: lc(1, 2),
})),
sep: ArgSep::End,
sep_pos: lc(1, 2),
}])
.exp_instr(Instruction::LoadRef(SymbolKey::from("foo"), ExprType::Text, lc(1, 2)))
.exp_nargs(1)
.check();
}
#[test]
fn test_repeated_require_one_ref_error() {
Tester::default()
.syntax(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("arg"),
type_syn: RepeatedTypeSyntax::VariableRef,
sep: ArgSepSyntax::Exactly(ArgSep::Long),
allow_missing: false,
require_one: true,
}),
)
.compile_command([ArgSpan {
expr: Some(Expr::Integer(IntegerSpan { value: 5, pos: lc(1, 2) })),
sep: ArgSep::End,
sep_pos: lc(1, 2),
}])
.exp_error(CallError::ArgumentError(
lc(1, 2),
"Requires a variable reference, not a value".to_owned(),
))
.check();
}
#[test]
fn test_repeated_oneof_separator() {
Tester::default()
.syntax(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("arg"),
type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Double),
sep: ArgSepSyntax::OneOf(ArgSep::Long, ArgSep::Short),
allow_missing: false,
require_one: false,
}),
)
.compile_command([
ArgSpan {
expr: Some(Expr::Double(DoubleSpan { value: 3.0, pos: lc(1, 2) })),
sep: ArgSep::Short,
sep_pos: lc(1, 3),
},
ArgSpan {
expr: Some(Expr::Double(DoubleSpan { value: 5.0, pos: lc(1, 4) })),
sep: ArgSep::Long,
sep_pos: lc(1, 5),
},
ArgSpan {
expr: Some(Expr::Double(DoubleSpan { value: 2.0, pos: lc(1, 6) })),
sep: ArgSep::End,
sep_pos: lc(1, 7),
},
])
.exp_instr(Instruction::PushDouble(2.0, lc(1, 6)))
.exp_instr(Instruction::PushInteger(ArgSep::Long as i32, lc(1, 5)))
.exp_instr(Instruction::PushDouble(5.0, lc(1, 4)))
.exp_instr(Instruction::PushInteger(ArgSep::Short as i32, lc(1, 3)))
.exp_instr(Instruction::PushDouble(3.0, lc(1, 2)))
.exp_nargs(5)
.check();
}
#[test]
fn test_repeated_oneof_separator_and_missing_in_last_position() {
Tester::default()
.syntax(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("arg"),
type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Double),
sep: ArgSepSyntax::OneOf(ArgSep::Long, ArgSep::Short),
allow_missing: true,
require_one: false,
}),
)
.compile_command([
ArgSpan {
expr: Some(Expr::Double(DoubleSpan { value: 3.0, pos: lc(1, 2) })),
sep: ArgSep::Short,
sep_pos: lc(1, 3),
},
ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 4) },
])
.exp_instr(Instruction::PushInteger(ValueTag::Missing as i32, lc(1, 4)))
.exp_instr(Instruction::PushInteger(ArgSep::Short as i32, lc(1, 3)))
.exp_instr(Instruction::PushDouble(3.0, lc(1, 2)))
.exp_instr(Instruction::PushInteger(ValueTag::Double as i32, lc(1, 2)))
.exp_nargs(4)
.check();
}
#[test]
fn test_repeated_any_value() {
Tester::default()
.syntax(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("arg"),
type_syn: RepeatedTypeSyntax::AnyValue,
sep: ArgSepSyntax::Exactly(ArgSep::Long),
allow_missing: true,
require_one: false,
}),
)
.compile_command([
ArgSpan {
expr: Some(Expr::Boolean(BooleanSpan { value: false, pos: lc(1, 2) })),
sep: ArgSep::Long,
sep_pos: lc(1, 3),
},
ArgSpan {
expr: Some(Expr::Double(DoubleSpan { value: 2.0, pos: lc(1, 4) })),
sep: ArgSep::Long,
sep_pos: lc(1, 5),
},
ArgSpan {
expr: Some(Expr::Integer(IntegerSpan { value: 3, pos: lc(1, 6) })),
sep: ArgSep::Long,
sep_pos: lc(1, 7),
},
ArgSpan {
expr: Some(Expr::Text(TextSpan { value: "foo".to_owned(), pos: lc(1, 8) })),
sep: ArgSep::Long,
sep_pos: lc(1, 9),
},
ArgSpan { expr: None, sep: ArgSep::End, sep_pos: lc(1, 10) },
])
.exp_instr(Instruction::PushInteger(ValueTag::Missing as i32, lc(1, 10)))
.exp_instr(Instruction::PushString("foo".to_owned(), lc(1, 8)))
.exp_instr(Instruction::PushInteger(ValueTag::Text as i32, lc(1, 8)))
.exp_instr(Instruction::PushInteger(3, lc(1, 6)))
.exp_instr(Instruction::PushInteger(ValueTag::Integer as i32, lc(1, 6)))
.exp_instr(Instruction::PushDouble(2.0, lc(1, 4)))
.exp_instr(Instruction::PushInteger(ValueTag::Double as i32, lc(1, 4)))
.exp_instr(Instruction::PushBoolean(false, lc(1, 2)))
.exp_instr(Instruction::PushInteger(ValueTag::Boolean as i32, lc(1, 2)))
.exp_nargs(9)
.check();
}
#[test]
fn test_singular_and_repeated() {
Tester::default()
.syntax(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax { name: Cow::Borrowed("arg"), vtype: ExprType::Double },
ArgSepSyntax::Exactly(ArgSep::Short),
)],
Some(&RepeatedSyntax {
name: Cow::Borrowed("rep"),
type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Integer),
sep: ArgSepSyntax::Exactly(ArgSep::Long),
allow_missing: false,
require_one: false,
}),
)
.compile_command([
ArgSpan {
expr: Some(Expr::Double(DoubleSpan { value: 4.0, pos: lc(1, 2) })),
sep: ArgSep::Short,
sep_pos: lc(1, 2),
},
ArgSpan {
expr: Some(Expr::Integer(IntegerSpan { value: 5, pos: lc(1, 5) })),
sep: ArgSep::Long,
sep_pos: lc(1, 2),
},
ArgSpan {
expr: Some(Expr::Integer(IntegerSpan { value: 6, pos: lc(1, 7) })),
sep: ArgSep::End,
sep_pos: lc(1, 2),
},
])
.exp_nargs(3)
.exp_instr(Instruction::PushInteger(6, lc(1, 7)))
.exp_instr(Instruction::PushInteger(5, lc(1, 5)))
.exp_instr(Instruction::PushDouble(4.0, lc(1, 2)))
.check();
}
}