use crate::lang::adapter::Adapter;
use crate::lang::args::should_append_stdin;
use crate::lang::error::Error;
use crate::lang::error::ErrorKind;
use crate::lang::machine::Machine;
use crate::lang::node::Node;
use crate::value::Value;
#[derive(Debug)]
pub struct Arity {
min: usize,
max: Option<usize>,
}
impl Arity {
pub fn new(arity: usize) -> Self {
Self {
min: arity,
max: Some(arity),
}
}
pub fn min(min: usize) -> Self {
Self { min, max: None }
}
pub fn max(max: usize) -> Self {
Self {
min: 0,
max: Some(max),
}
}
pub fn minmax(min: usize, max: usize) -> Self {
debug_assert!(min > 0, "{min} {max}");
debug_assert!(max > min, "{min} {max}");
Self {
min,
max: Some(max),
}
}
}
pub(crate) fn assert_func_arity(
adapter: &impl Adapter,
machine: &Machine<'_>,
stdin: Option<&Value>,
node: &Node,
arity: &Arity,
) -> Result<(), Error> {
let Arity { min, max } = arity;
let name = parse_func_name(adapter, machine, node)?;
let name_count = 1;
let stdin_count = stdin
.filter(|_| should_append_stdin(node))
.map(|_| 1)
.unwrap_or_default();
let actual = node
.list()
.iter()
.filter(|p| !matches!(p, Node::Space(_)))
.count()
.saturating_sub(name_count)
.saturating_add(stdin_count);
if let Some(max) = max {
if max == min && actual != *max {
return Err(Error {
kind: ErrorKind::Func,
source: adapter.template_source().map(String::from),
span: machine.span,
message: format!(
"function {name} expects {max} {}: got {}",
values_suffix(*max),
actual
),
});
}
}
if let Some(max) = max {
if actual < *min || actual > *max {
return Err(Error {
kind: ErrorKind::Func,
source: adapter.template_source().map(String::from),
span: machine.span,
message: format!(
"function {name} expects {min}-{max} {}: got {}",
values_suffix(*max),
actual
),
});
}
}
if actual < arity.min {
return Err(Error {
kind: ErrorKind::Func,
source: adapter.template_source().map(String::from),
span: machine.span,
message: format!(
"function {name} expects at least {min} {}: got {}",
values_suffix(arity.min),
actual
),
});
}
Ok(())
}
pub(crate) fn parse_func_name<'a>(
adapter: &impl Adapter,
machine: &Machine<'_>,
list: &'a Node,
) -> Result<&'a str, Error> {
let Some(Node::Label(_, name)) = list.list().first() else {
return Err(Error {
kind: ErrorKind::Func,
source: adapter.template_source().map(String::from),
span: machine.span,
message: "missing function name".into(),
});
};
core::str::from_utf8(name.as_bytes()).map_err(|_| Error {
kind: ErrorKind::Func,
source: adapter.template_source().map(String::from),
span: machine.span,
message: "bad function name encoding".into(),
})
}
fn values_suffix(n: usize) -> &'static str {
match n {
1 => "argument",
_ => "arguments",
}
}