use std::{
fmt::{self, Display, Formatter},
num::NonZeroU32,
str::FromStr,
};
use nom::{
self,
bytes::complete::tag,
character::complete::{digit0, line_ending, multispace0, one_of, space1},
combinator::{all_consuming, map_res, opt, recognize, verify},
error::{FromExternalError, ParseError},
multi::{many1, separated_list0},
sequence::{delimited, pair, preceded},
Offset,
};
use crate::types::{Line, HLTAS};
mod line;
pub use line::{frame_bulk, line};
pub(crate) mod properties;
use properties::properties;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum ErrorKind {
ExpectedChar(char),
Other(nom::error::ErrorKind),
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Context {
ErrorReadingVersion,
VersionTooHigh,
BothAutoJumpAndDuckTap,
NoLeaveGroundAction,
TimesOnLeaveGroundAction,
NoSaveName,
NoSeed,
NoYaw,
NoButtons,
NoLGAGSTMinSpeed,
NoResetSeed,
ErrorParsingLine,
InvalidStrafingAlgorithm,
NoConstraints,
NoPlusMinusBeforeTolerance,
NoFromToParameters,
NoTo,
NoYawspeed,
UnsupportedConstantYawspeedDir,
NegativeYawspeed,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct Error<'a> {
pub input: &'a str,
pub(crate) whole_input: &'a str,
kind: ErrorKind,
pub context: Option<Context>,
}
type IResult<'a, T> = Result<(&'a str, T), nom::Err<Error<'a>>>;
impl Error<'_> {
fn add_context(&mut self, context: Context) {
if self.context.is_some() {
return;
}
self.context = Some(context);
}
}
impl Display for Context {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use Context::*;
match self {
ErrorReadingVersion => write!(f, "failed to read version"),
VersionTooHigh => write!(f, "this version is not supported"),
BothAutoJumpAndDuckTap => write!(
f,
"both autojump and ducktap are specified at the same time"
),
NoLeaveGroundAction => write!(
f,
"no LGAGST action specified (either autojump or ducktap is required)"
),
TimesOnLeaveGroundAction => write!(
f,
"times on autojump or ducktap with LGAGST enabled (put times on LGAGST instead)"
),
NoSaveName => write!(f, "missing save name"),
NoSeed => write!(f, "missing seed value"),
NoButtons => write!(f, "missing button values"),
NoLGAGSTMinSpeed => write!(f, "missing lgagstminspeed value"),
NoResetSeed => write!(f, "missing reset seed"),
NoYaw => write!(f, "missing yaw value"),
ErrorParsingLine => write!(f, "failed to parse the line"),
InvalidStrafingAlgorithm => write!(
f,
"invalid strafing algorithm (only \"yaw\" and \"vectorial\" allowed)"
),
NoConstraints => write!(f, "missing constraints"),
NoPlusMinusBeforeTolerance => write!(f, "missing +- before tolerance"),
NoFromToParameters => write!(f, "missing from/to parameters"),
NoTo => write!(f, "missing \"to\" in the from/to constraint"),
NoYawspeed => write!(f, "missing yawspeed value"),
UnsupportedConstantYawspeedDir => {
write!(f, "cannot pair constant yawspeed with current strafe dir")
}
NegativeYawspeed => {
write!(f, "yawspeed value is negative")
}
}
}
}
impl Display for Error<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut line = 0;
let mut column = 0;
let mut offset = self.whole_input.offset(self.input);
let mut just_error_line = None;
for (j, l) in self.whole_input.lines().enumerate() {
if offset <= l.len() {
line = j;
column = offset;
just_error_line = Some(l);
break;
} else {
offset = offset - l.len() - 1;
}
}
if let Some(context) = self.context {
context.fmt(f)?;
} else {
match self.kind {
ErrorKind::ExpectedChar(c) => {
if let Some(next_char) = self.input.chars().next() {
write!(f, "expected '{}', got '{}'", c, next_char)?;
} else {
write!(f, "expected '{}', got EOF", c)?;
}
}
ErrorKind::Other(nom_kind) => {
write!(f, "error applying {}", nom_kind.description())?
}
}
}
if just_error_line.is_none() {
return Ok(());
}
let just_error_line = just_error_line.unwrap();
let line_number = format!("{} | ", line);
write!(f, "\n{}{}\n", line_number, just_error_line)?;
write!(f, "{:1$}^", ' ', line_number.len() + column)?;
if let ErrorKind::ExpectedChar(c) = self.kind {
write!(f, " expected '{}'", c)?;
}
Ok(())
}
}
impl std::error::Error for Error<'_> {}
impl<'a> ParseError<&'a str> for Error<'a> {
fn from_error_kind(input: &'a str, kind: nom::error::ErrorKind) -> Self {
Self {
input,
whole_input: input,
kind: ErrorKind::Other(kind),
context: None,
}
}
fn append(_input: &'a str, _kind: nom::error::ErrorKind, other: Self) -> Self {
other
}
fn from_char(input: &'a str, c: char) -> Self {
Self {
input,
whole_input: input,
kind: ErrorKind::ExpectedChar(c),
context: None,
}
}
}
impl<'a, E> FromExternalError<&'a str, E> for Error<'a> {
fn from_external_error(input: &'a str, kind: nom::error::ErrorKind, _e: E) -> Self {
Self::from_error_kind(input, kind)
}
}
impl Error<'_> {
pub fn line(&self) -> usize {
let mut line = 0;
let mut offset = self.whole_input.offset(self.input);
for (j, l) in self.whole_input.lines().enumerate() {
if offset <= l.len() {
line = j;
break;
} else {
offset = offset - l.len() - 1;
}
}
line
}
}
fn context<'a, T>(
context: Context,
mut f: impl FnMut(&'a str) -> IResult<'a, T>,
) -> impl FnMut(&'a str) -> IResult<T> {
move |i: &str| {
f(i).map_err(move |error| match error {
nom::Err::Incomplete(needed) => nom::Err::Incomplete(needed),
nom::Err::Error(mut e) => {
e.add_context(context);
nom::Err::Error(e)
}
nom::Err::Failure(mut e) => {
e.add_context(context);
nom::Err::Failure(e)
}
})
}
}
fn non_zero_u32(i: &str) -> IResult<NonZeroU32> {
map_res(
recognize(pair(one_of("123456789"), digit0)),
NonZeroU32::from_str,
)(i)
}
fn version(i: &str) -> IResult<()> {
let version_number = context(
Context::VersionTooHigh,
verify(
context(Context::ErrorReadingVersion, one_of("123456789")),
|&c| c == '1',
),
);
let (i, _) = preceded(tag("version"), preceded(space1, version_number))(i)?;
Ok((i, ()))
}
fn whitespace(i: &str) -> IResult<()> {
let (i, _) = preceded(line_ending, multispace0)(i)?;
Ok((i, ()))
}
pub fn all_consuming_lines(i: &str) -> IResult<Vec<Line>> {
let many_lines = separated_list0(whitespace, line);
all_consuming(delimited(opt(multispace0), many_lines, opt(multispace0)))(i)
}
pub fn hltas(i: &str) -> IResult<HLTAS> {
let (i, _) = context(Context::ErrorReadingVersion, version)(i)?;
let (i, properties) = properties(i)?;
let (i, _) = preceded(many1(line_ending), tag("frames"))(i)?;
let (i, lines) = context(
Context::ErrorParsingLine,
preceded(whitespace, all_consuming_lines),
)(i)?;
Ok((i, HLTAS { properties, lines }))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_0() {
let input = "version 0";
let err = version(input).unwrap_err();
if let nom::Err::Error(err) = err {
assert_eq!(err.context, Some(Context::ErrorReadingVersion));
} else {
unreachable!()
}
}
#[test]
fn version_too_high() {
let input = "version 9";
let err = version(input).unwrap_err();
if let nom::Err::Error(err) = err {
assert_eq!(err.context, Some(Context::VersionTooHigh));
} else {
unreachable!()
}
}
#[test]
fn no_newline_after_frames() {
let input = "version 1\nframesbuttons";
assert!(hltas(input).is_err());
}
#[test]
fn no_newline_after_frames_only_space() {
let input = "version 1\nframes buttons";
assert!(hltas(input).is_err());
}
}