use crate::common::syntax::Mode;
use crate::common::syntax::parse_arguments;
use std::num::NonZeroUsize;
use std::num::ParseIntError;
use thiserror::Error;
use yash_env::Env;
use yash_env::semantics::Field;
use yash_env::source::pretty::Report;
use yash_env::source::pretty::ReportType;
use yash_env::source::pretty::Snippet;
#[derive(Clone, Debug, Eq, Error, PartialEq)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
CommonError(#[from] crate::common::syntax::ParseError<'static>),
#[error("too many operands")]
TooManyOperands(Vec<Field>),
#[error("invalid numeric operand")]
InvalidNumber(Field, ParseIntError),
}
impl Error {
#[must_use]
pub fn to_report(&self) -> Report<'_> {
let (location, label) = match self {
Self::CommonError(e) => return e.to_report(),
Self::TooManyOperands(operands) => (
&operands[1].origin,
format!("{}: redundant operand", operands[1].value),
),
Self::InvalidNumber(operand, e) => {
(&operand.origin, format!("{}: {}", operand.value, e))
}
};
let mut report = Report::new();
report.r#type = ReportType::Error;
report.title = self.to_string().into();
report.snippets = Snippet::with_primary_span(location, label.into());
report
}
}
impl<'a> From<&'a Error> for Report<'a> {
#[inline]
fn from(error: &'a Error) -> Self {
error.to_report()
}
}
pub type Result = std::result::Result<NonZeroUsize, Error>;
pub fn parse<S>(env: &Env<S>, args: Vec<Field>) -> Result {
let (_options, mut operands) = parse_arguments(&[], Mode::with_env(env), args)?;
if operands.len() > 1 {
return Err(Error::TooManyOperands(operands));
}
match operands.pop() {
None => Ok(NonZeroUsize::new(1).unwrap()),
Some(field) => field
.value
.parse()
.map_err(|e| Error::InvalidNumber(field, e)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use std::num::IntErrorKind;
#[test]
fn default_count() {
let env = Env::new_virtual();
let result = parse(&env, vec![]);
assert_eq!(result, Ok(NonZeroUsize::new(1).unwrap()));
}
#[test]
fn valid_counts() {
let env = Env::new_virtual();
let args = Field::dummies(["1"]);
let result = parse(&env, args);
assert_eq!(result, Ok(NonZeroUsize::new(1).unwrap()));
let args = Field::dummies(["2"]);
let result = parse(&env, args);
assert_eq!(result, Ok(NonZeroUsize::new(2).unwrap()));
}
#[test]
fn too_many_operands() {
let env = Env::new_virtual();
let args = Field::dummies(["1", "2"]);
let result = parse(&env, args.clone());
assert_eq!(result, Err(Error::TooManyOperands(args)));
}
#[test]
fn non_positive_integer() {
let env = Env::new_virtual();
let arg = Field::dummy("0");
let result = parse(&env, vec![arg.clone()]);
assert_matches!(result, Err(Error::InvalidNumber(field, error)) => {
assert_eq!(field, arg);
assert_eq!(error.kind(), &IntErrorKind::Zero);
});
}
}