use std::borrow::Cow;
use nom::{
IResult, Input as _, Parser as _,
branch::alt,
bytes::complete::tag,
character::complete::{char, multispace0, one_of},
combinator::{cut, fail as fail_parser, map},
error::{ContextError, ErrorKind, ParseError as _, context},
multi::{many0, separated_list0},
sequence::{preceded, terminated}
};
use crate::{
ast::{
Add, ArithmeticExpression, Binding, Constant, CustomDice,
DiceExpression, Div, DropHighest, DropLowest, Exp, Expression,
Function, Group, Mod, Mul, Neg, Parameter, Range, StandardDice, Sub,
Variable
},
span::{SourceSpan, Spanned}
};
use super::{
BINDING_CONTEXT, BINDING_EXPRESSION_CONTEXT, CLOSING_BRACE_CONTEXT,
CLOSING_BRACKET_CONTEXT, CLOSING_PAREN_CONTEXT, CONSTANT_CONTEXT,
CUSTOM_FACES_CONTEXT, DICE_CONTEXT, DICE_COUNT_CONTEXT,
DROP_DIRECTION_CONTEXT, DROP_EXPRESSION_CONTEXT, EXPRESSION_CONTEXT,
FUNCTION_BODY_CONTEXT, FUNCTION_CONTEXT, GROUP_CONTEXT, IDENTIFIER_CONTEXT,
NEXT_PARAMETER_CONTEXT, NomErrorKind, PARAMETER_CONTEXT, ParseError,
RANGE_CONTEXT, RANGE_END_CONTEXT, RANGE_START_CONTEXT,
RIGHT_OPERAND_CONTEXT, STANDARD_FACES_CONTEXT, Span, VARIABLE_CONTEXT,
braced_name, canonical_name, constant, custom_faces, d_operator,
identifier, is_token_space, name_space0, negative_constant, parameter,
parameters
};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum Goal
{
Function,
AddSub,
MulDivMod,
Unary,
Exponent,
Primary,
Group,
Binding,
Range,
Dice,
PrimaryDice,
StandardDice,
CustomDice,
Atom,
DropLowest,
DropHighest
}
#[derive(Debug)]
pub(super) enum Value<'src>
{
Function(Function<'src>),
Expression(Expression<'src>),
Group(Group<'src>),
Binding(Binding<'src>),
Range(Range<'src>),
Dice(DiceExpression<'src>),
StandardDice(StandardDice<'src>),
CustomDice(CustomDice<'src>),
Drop(Option<Box<Expression<'src>>>)
}
impl<'src> Value<'src>
{
pub(super) fn into_function(self) -> Function<'src>
{
match self
{
Value::Function(function) => function,
value => unreachable!("expected a function: {:?}", value)
}
}
pub(super) fn into_expression(self) -> Expression<'src>
{
match self
{
Value::Expression(expression) => expression,
value => unreachable!("expected an expression: {:?}", value)
}
}
pub(super) fn into_group(self) -> Group<'src>
{
match self
{
Value::Group(group) => group,
value => unreachable!("expected a group: {:?}", value)
}
}
pub(super) fn into_binding(self) -> Binding<'src>
{
match self
{
Value::Binding(binding) => binding,
value => unreachable!("expected a binding: {:?}", value)
}
}
pub(super) fn into_range(self) -> Range<'src>
{
match self
{
Value::Range(range) => range,
value => unreachable!("expected a range: {:?}", value)
}
}
pub(super) fn into_dice(self) -> DiceExpression<'src>
{
match self
{
Value::Dice(dice) => dice,
value => unreachable!("expected a dice expression: {:?}", value)
}
}
pub(super) fn into_standard_dice(self) -> StandardDice<'src>
{
match self
{
Value::StandardDice(dice) => dice,
value => unreachable!("expected standard dice: {:?}", value)
}
}
pub(super) fn into_custom_dice(self) -> CustomDice<'src>
{
match self
{
Value::CustomDice(dice) => dice,
value => unreachable!("expected custom dice: {:?}", value)
}
}
pub(super) fn into_drop(self) -> Option<Box<Expression<'src>>>
{
match self
{
Value::Drop(drop) => drop,
value => unreachable!("expected a drop expression: {:?}", value)
}
}
}
pub(super) type Outcome<'src> =
IResult<Span<'src>, Value<'src>, ParseError<'src>>;
enum Step<'src>
{
Call(Goal, Span<'src>),
Return(Outcome<'src>)
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum Level
{
Additive,
Multiplicative
}
impl Level
{
fn operators(self) -> &'static str
{
match self
{
Level::Additive => "+-",
Level::Multiplicative => "*×/÷%"
}
}
fn operand(self) -> Goal
{
match self
{
Level::Additive => Goal::MulDivMod,
Level::Multiplicative => Goal::Unary
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum DropDirection
{
Lowest,
Highest
}
enum Frame<'src>
{
FunctionBody
{
start: usize,
parameters: Option<Vec<Parameter<'src>>>,
input: Span<'src>
},
BinaryFirst(Level),
BinaryRight
{
level: Level,
left: Expression<'src>,
operator: char,
input: Span<'src>
},
UnaryNegation
{
input: Span<'src>,
error: ParseError<'src>
},
UnaryExponent
{
input: Span<'src>,
error: ParseError<'src>
},
ExponentBase,
ExponentPower
{
base: Expression<'src>,
input: Span<'src>
},
PrimaryRange(Span<'src>),
PrimaryDice
{
input: Span<'src>,
error: ParseError<'src>
},
PrimaryGroup
{
input: Span<'src>,
error: ParseError<'src>
},
AtomGroup
{
input: Span<'src>,
error: ParseError<'src>
},
GroupExpression
{
start: usize,
input: Span<'src>
},
BindingExpression
{
head: BindingHead<'src>,
paren: usize,
input: Span<'src>
},
RangeStart
{
start: usize,
input: Span<'src>
},
RangeEnd
{
start: usize,
first: Expression<'src>,
input: Span<'src>
},
DiceCount
{
primary: bool,
input: Span<'src>
},
DiceFaces
{
start: usize,
count: Expression<'src>,
input: Span<'src>
},
DiceDrop
{
dice: DiceExpression<'src>,
direction: DropDirection,
rest: Span<'src>,
input: Span<'src>
},
StandardCount(Span<'src>),
StandardFaces
{
count: Expression<'src>,
input: Span<'src>
},
CustomCount(Span<'src>),
DropExpression
{
rest: Span<'src>,
input: Span<'src>
}
}
#[derive(Clone)]
struct BindingHead<'src>
{
start: usize,
name: Cow<'src, str>,
name_span: SourceSpan,
atom: Option<Span<'src>>
}
#[cfg_attr(doc, aquamarine::aquamarine)]
pub(super) fn run(goal: Goal, input: Span<'_>) -> Outcome<'_>
{
drive(goal, input, &mut Recoverer::new(&mut Strict, input))
}
pub(super) fn run_recovering<'src, R: Recovery<'src>>(
input: Span<'src>,
policy: &mut R
) -> Outcome<'src>
{
drive(Goal::Function, input, &mut Recoverer::new(policy, input))
}
fn drive<'src, R: Recovery<'src>>(
goal: Goal,
input: Span<'src>,
rec: &mut Recoverer<'src, '_, R>
) -> Outcome<'src>
{
let mut stack = Vec::new();
let mut step = Step::Call(goal, input);
loop
{
#[cfg(test)]
STEPS.with(|steps| steps.set(steps.get() + 1));
if rec.recovering()
&& let Some(input) = rec.restart.take()
{
step = rec.restart_function(input, &mut stack);
}
step = match step
{
Step::Call(goal, input) =>
{
rec.called(goal, input, &stack);
start(goal, input, &mut stack, rec)
},
Step::Return(outcome) if rec.intercepts(&outcome, &stack) =>
{
rec.recover_goal(outcome, &mut stack)
},
Step::Return(outcome) => match stack.pop()
{
Some(frame) =>
{
rec.popped(stack.len());
resume(frame, outcome, &mut stack, rec)
},
None => return outcome
}
}
}
}
#[cfg(test)]
thread_local! {
static STEPS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
#[cfg(test)]
pub(crate) fn steps() -> usize { STEPS.with(std::cell::Cell::get) }
fn start<'src, R: Recovery<'src>>(
goal: Goal,
input: Span<'src>,
stack: &mut Vec<Frame<'src>>,
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
match goal
{
Goal::Function => start_function(input, stack, rec),
Goal::AddSub => call(
stack,
Frame::BinaryFirst(Level::Additive),
Goal::MulDivMod,
input
),
Goal::MulDivMod => call(
stack,
Frame::BinaryFirst(Level::Multiplicative),
Goal::Unary,
input
),
Goal::Unary => start_unary(input, stack, rec),
Goal::Exponent =>
{
call(stack, Frame::ExponentBase, Goal::Primary, input)
},
Goal::Primary =>
{
call(stack, Frame::PrimaryRange(input), Goal::Range, input)
},
Goal::Group => start_group(input, stack),
Goal::Binding => start_binding(input, stack, rec),
Goal::Range => start_range(input, stack),
Goal::Dice => call(
stack,
Frame::DiceCount {
primary: false,
input
},
Goal::Atom,
input
),
Goal::PrimaryDice => call(
stack,
Frame::DiceCount {
primary: true,
input
},
Goal::Atom,
input
),
Goal::StandardDice =>
{
call(stack, Frame::StandardCount(input), Goal::Atom, input)
},
Goal::CustomDice =>
{
call(stack, Frame::CustomCount(input), Goal::Atom, input)
},
Goal::Atom => start_atom(input, stack, rec),
Goal::DropLowest => start_drop(input, "lowest", stack),
Goal::DropHighest => start_drop(input, "highest", stack)
}
}
fn resume<'src, R: Recovery<'src>>(
frame: Frame<'src>,
outcome: Outcome<'src>,
stack: &mut Vec<Frame<'src>>,
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
match frame
{
Frame::FunctionBody {
start,
parameters,
input
} => resume_function_body(start, parameters, input, outcome),
Frame::BinaryFirst(level) => match outcome
{
Ok((rest, left)) =>
{
binary_continue(level, left.into_expression(), rest, stack)
},
Err(e) => Step::Return(Err(e))
},
Frame::BinaryRight {
level,
left,
operator,
input
} => resume_binary_right(level, left, operator, input, outcome, stack),
Frame::UnaryNegation { input, error } =>
{
resume_unary_negation(input, error, outcome, stack)
},
Frame::UnaryExponent { input, error } =>
{
resume_unary_exponent(input, error, outcome)
},
Frame::ExponentBase => resume_exponent_base(outcome, stack),
Frame::ExponentPower { base, input } =>
{
resume_exponent_power(base, input, outcome)
},
Frame::PrimaryRange(input) =>
{
resume_primary_range(input, outcome, stack)
},
Frame::PrimaryDice { input, error } =>
{
resume_primary_dice(input, error, outcome, stack)
},
Frame::PrimaryGroup { input, error } =>
{
resume_primary_group(input, error, outcome, stack, rec)
},
Frame::AtomGroup { input, error } =>
{
resume_atom_group(input, error, outcome)
},
Frame::GroupExpression { start, input } =>
{
resume_group_expression(start, input, outcome, stack, rec)
},
Frame::BindingExpression { head, paren, input } =>
{
resume_binding_expression(head, paren, input, outcome, stack, rec)
},
Frame::RangeStart { start, input } =>
{
resume_range_start(start, input, outcome, stack, rec)
},
Frame::RangeEnd {
start,
first,
input
} => resume_range_end(start, first, input, outcome, stack, rec),
Frame::DiceCount { primary, input } =>
{
resume_dice_count(primary, input, outcome, stack, rec)
},
Frame::DiceFaces {
start,
count,
input
} => resume_dice_faces(start, count, input, outcome, stack, rec),
Frame::DiceDrop {
dice,
direction,
rest,
input
} => resume_dice_drop(dice, direction, rest, input, outcome, stack, rec),
Frame::StandardCount(input) =>
{
resume_standard_count(input, outcome, stack)
},
Frame::StandardFaces { count, input } =>
{
resume_standard_faces(count, input, outcome)
},
Frame::CustomCount(input) => resume_custom_count(input, outcome),
Frame::DropExpression { rest, input } =>
{
resume_drop_expression(rest, input, outcome)
},
}
}
fn call<'src>(
stack: &mut Vec<Frame<'src>>,
frame: Frame<'src>,
goal: Goal,
input: Span<'src>
) -> Step<'src>
{
stack.push(frame);
Step::Call(goal, input)
}
fn succeed<'src>(rest: Span<'src>, value: Value<'src>) -> Step<'src>
{
Step::Return(Ok((rest, value)))
}
fn fail(e: nom::Err<ParseError<'_>>) -> Step<'_> { Step::Return(Err(e)) }
fn with_context<'src>(
input: Span<'src>,
label: &'static str,
e: nom::Err<ParseError<'src>>
) -> nom::Err<ParseError<'src>>
{
e.map(|e| ParseError::add_context(input, label, e))
}
fn cut_error(e: nom::Err<ParseError<'_>>) -> nom::Err<ParseError<'_>>
{
match e
{
nom::Err::Error(e) => nom::Err::Failure(e),
e => e
}
}
fn merge<'src>(
accumulated: ParseError<'src>,
e: ParseError<'src>
) -> ParseError<'src>
{
accumulated.or(e)
}
fn exhaust<'src>(input: Span<'src>, error: ParseError<'src>) -> Step<'src>
{
fail(nom::Err::Error(ParseError::append(
input,
ErrorKind::Alt,
error
)))
}
fn start_function<'src, R: Recovery<'src>>(
input: Span<'src>,
stack: &mut Vec<Frame<'src>>,
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
let start = input.location_offset();
let overlaid = rec.recovering() && rec.overlays_parameter(input);
let (rest, parameters) = match parameters.parse_complete(input)
{
Ok(parsed) if !overlaid => parsed,
Err(e) if !rec.recovering() => return fail(e),
_ => match recover_parameters(input, stack, rec)
{
Ok(parsed) => parsed,
Err(e) => return fail(e)
}
};
let input = skip_whitespace(rest);
call(
stack,
Frame::FunctionBody {
start,
parameters,
input
},
Goal::AddSub,
input
)
}
fn resume_function_body<'src>(
start: usize,
parameters: Option<Vec<Parameter<'src>>>,
input: Span<'src>,
outcome: Outcome<'src>
) -> Step<'src>
{
match outcome
{
Ok((rest, body)) =>
{
let end = rest.location_offset();
succeed(
rest,
Value::Function(Function {
parameters,
body: body.into_expression(),
span: SourceSpan { start, end }
})
)
},
Err(e) => fail(with_context(input, FUNCTION_BODY_CONTEXT, e))
}
}
fn binary_continue<'src>(
level: Level,
left: Expression<'src>,
rest: Span<'src>,
stack: &mut Vec<Frame<'src>>
) -> Step<'src>
{
let operator: IResult<Span, char, ParseError> =
preceded(multispace0, one_of(level.operators())).parse_complete(rest);
match operator
{
Ok((after, operator)) =>
{
let input = skip_whitespace(after);
call(
stack,
Frame::BinaryRight {
level,
left,
operator,
input
},
level.operand(),
input
)
},
Err(_) => succeed(rest, Value::Expression(left))
}
}
fn resume_binary_right<'src>(
level: Level,
left: Expression<'src>,
operator: char,
input: Span<'src>,
outcome: Outcome<'src>,
stack: &mut Vec<Frame<'src>>
) -> Step<'src>
{
match outcome
{
Ok((rest, right)) =>
{
let right = right.into_expression();
let span = SourceSpan {
start: left.span().start,
end: right.span().end
};
let left = Box::new(left);
let right = Box::new(right);
let arithmetic = match operator
{
'+' => ArithmeticExpression::Add(Add { left, right, span }),
'-' => ArithmeticExpression::Sub(Sub { left, right, span }),
'*' | '×' =>
{
ArithmeticExpression::Mul(Mul { left, right, span })
},
'/' | '÷' =>
{
ArithmeticExpression::Div(Div { left, right, span })
},
'%' => ArithmeticExpression::Mod(Mod { left, right, span }),
_ => unreachable!("unexpected operator: {}", operator)
};
binary_continue(
level,
Expression::Arithmetic(arithmetic),
rest,
stack
)
},
Err(e) => fail(cut_error(with_context(input, RIGHT_OPERAND_CONTEXT, e)))
}
}
fn start_unary<'src, R: Recovery<'src>>(
input: Span<'src>,
stack: &mut Vec<Frame<'src>>,
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
let error = match parse_negative_constant(input, rec)
{
Ok((rest, constant)) =>
{
return succeed(rest, Value::Expression(constant))
},
Err(nom::Err::Error(e)) => e,
Err(e) => return fail(e)
};
let sign: IResult<Span, char, ParseError> = char('-')(input);
match sign
{
Ok((after, _)) =>
{
let operand = skip_whitespace(after);
call(
stack,
Frame::UnaryNegation { input, error },
Goal::Unary,
operand
)
},
Err(nom::Err::Error(e)) =>
{
unary_exponent(input, merge(error, e), stack)
},
Err(e) => fail(e)
}
}
fn unary_exponent<'src>(
input: Span<'src>,
error: ParseError<'src>,
stack: &mut Vec<Frame<'src>>
) -> Step<'src>
{
let exponent = skip_whitespace(input);
call(
stack,
Frame::UnaryExponent { input, error },
Goal::Exponent,
exponent
)
}
fn resume_unary_negation<'src>(
input: Span<'src>,
error: ParseError<'src>,
outcome: Outcome<'src>,
stack: &mut Vec<Frame<'src>>
) -> Step<'src>
{
match outcome
{
Ok((rest, operand)) =>
{
let operand = operand.into_expression();
let span = SourceSpan {
start: input.location_offset(),
end: operand.span().end
};
let negation = match operand
{
Expression::Constant(Constant { value: 0, .. }) =>
{
Expression::Constant(Constant { value: 0, span })
},
operand =>
{
Expression::Arithmetic(ArithmeticExpression::Neg(Neg {
operand: Box::new(operand),
span
}))
},
};
succeed(rest, Value::Expression(negation))
},
Err(nom::Err::Error(e)) =>
{
unary_exponent(input, merge(error, e), stack)
},
Err(e) => fail(e)
}
}
fn resume_unary_exponent<'src>(
input: Span<'src>,
error: ParseError<'src>,
outcome: Outcome<'src>
) -> Step<'src>
{
match outcome
{
Ok((rest, value)) => succeed(rest, value),
Err(nom::Err::Error(e)) => exhaust(input, merge(error, e)),
Err(e) => fail(e)
}
}
fn resume_exponent_base<'src>(
outcome: Outcome<'src>,
stack: &mut Vec<Frame<'src>>
) -> Step<'src>
{
let (rest, base) = match outcome
{
Ok((rest, base)) => (rest, base.into_expression()),
Err(e) => return fail(e)
};
let caret: IResult<Span, char, ParseError> =
preceded(multispace0, char('^')).parse_complete(rest);
match caret
{
Ok((after, _)) =>
{
let input = skip_whitespace(after);
call(
stack,
Frame::ExponentPower { base, input },
Goal::Unary,
input
)
},
Err(_) => succeed(rest, Value::Expression(base))
}
}
fn resume_exponent_power<'src>(
base: Expression<'src>,
input: Span<'src>,
outcome: Outcome<'src>
) -> Step<'src>
{
match outcome
{
Ok((rest, power)) =>
{
let power = power.into_expression();
let span = SourceSpan {
start: base.span().start,
end: power.span().end
};
succeed(
rest,
Value::Expression(Expression::Arithmetic(
ArithmeticExpression::Exp(Exp {
left: Box::new(base),
right: Box::new(power),
span
})
))
)
},
Err(e) => fail(cut_error(with_context(input, RIGHT_OPERAND_CONTEXT, e)))
}
}
fn resume_primary_range<'src>(
input: Span<'src>,
outcome: Outcome<'src>,
stack: &mut Vec<Frame<'src>>
) -> Step<'src>
{
match with_context_on_error(input, RANGE_CONTEXT, outcome)
{
Ok((rest, range)) => succeed(
rest,
Value::Expression(Expression::Range(range.into_range()))
),
Err(nom::Err::Error(error)) => call(
stack,
Frame::PrimaryDice { input, error },
Goal::PrimaryDice,
input
),
Err(e) => fail(e)
}
}
fn resume_primary_dice<'src>(
input: Span<'src>,
error: ParseError<'src>,
outcome: Outcome<'src>,
stack: &mut Vec<Frame<'src>>
) -> Step<'src>
{
match with_context_on_error(input, DICE_CONTEXT, outcome)
{
Ok((rest, Value::Dice(dice))) =>
{
succeed(rest, Value::Expression(Expression::Dice(dice)))
},
Ok((rest, count)) => succeed(rest, count),
Err(nom::Err::Error(e)) => call(
stack,
Frame::PrimaryGroup {
input,
error: merge(error, e)
},
Goal::Group,
input
),
Err(e) => fail(e)
}
}
fn resume_primary_group<'src, R: Recovery<'src>>(
input: Span<'src>,
error: ParseError<'src>,
outcome: Outcome<'src>,
stack: &mut Vec<Frame<'src>>,
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
let error = match with_context_on_error(input, GROUP_CONTEXT, outcome)
{
Ok((rest, group)) =>
{
return succeed(
rest,
Value::Expression(Expression::Group(group.into_group()))
);
},
Err(nom::Err::Error(e)) => merge(error, e),
Err(e) => return fail(e)
};
let error = match parse_variable(input, stack, rec)
{
Ok((rest, (variable, name_span))) =>
{
return variable_or_binding(
input, rest, variable, name_span, stack, rec
);
},
Err(nom::Err::Error(e)) => merge(error, e),
Err(e) => return fail(e)
};
match parse_constant(input, rec)
{
Ok((rest, constant)) =>
{
succeed(rest, Value::Expression(Expression::Constant(constant)))
},
Err(nom::Err::Error(e)) => exhaust(input, merge(error, e)),
Err(e) => fail(e)
}
}
fn variable_or_binding<'src, R: Recovery<'src>>(
input: Span<'src>,
rest: Span<'src>,
variable: Variable<'src>,
name_span: SourceSpan,
stack: &mut Vec<Frame<'src>>,
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
let at: IResult<Span, char, ParseError> =
preceded(multispace0, char('@')).parse_complete(rest);
match at
{
Ok((after_at, _)) =>
{
let head = BindingHead {
start: variable.span.start,
name: variable.name,
name_span,
atom: Some(input)
};
bind(head, after_at, stack, rec)
},
Err(_) =>
{
succeed(rest, Value::Expression(Expression::Variable(variable)))
},
}
}
fn start_atom<'src, R: Recovery<'src>>(
input: Span<'src>,
stack: &mut Vec<Frame<'src>>,
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
let error = match parse_constant(input, rec)
{
Ok((rest, constant)) =>
{
return succeed(
rest,
Value::Expression(Expression::Constant(constant))
);
},
Err(nom::Err::Error(e)) => e,
Err(e) => return fail(e)
};
match parse_variable(input, stack, rec)
{
Ok((rest, (variable, name_span))) =>
{
variable_or_binding(input, rest, variable, name_span, stack, rec)
},
Err(nom::Err::Error(e)) => call(
stack,
Frame::AtomGroup {
input,
error: merge(error, e)
},
Goal::Group,
input
),
Err(e) => fail(e)
}
}
fn resume_atom_group<'src>(
input: Span<'src>,
error: ParseError<'src>,
outcome: Outcome<'src>
) -> Step<'src>
{
match with_context_on_error(input, GROUP_CONTEXT, outcome)
{
Ok((rest, group)) => succeed(
rest,
Value::Expression(Expression::Group(group.into_group()))
),
Err(nom::Err::Error(e)) => exhaust(input, merge(error, e)),
Err(e) => fail(e)
}
}
fn start_group<'src>(
input: Span<'src>,
stack: &mut Vec<Frame<'src>>
) -> Step<'src>
{
let start = input.location_offset();
let paren: IResult<Span, char, ParseError> = char('(')(input);
match paren
{
Ok((after, _)) =>
{
let input = skip_whitespace(after);
call(
stack,
Frame::GroupExpression { start, input },
Goal::AddSub,
input
)
},
Err(e) => fail(e)
}
}
fn resume_group_expression<'src, R: Recovery<'src>>(
start: usize,
input: Span<'src>,
outcome: Outcome<'src>,
stack: &[Frame<'src>],
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
let (rest, expression) = match outcome
{
Ok((rest, expression)) => (rest, expression.into_expression()),
Err(e) =>
{
return fail(cut_error(with_context(input, EXPRESSION_CONTEXT, e)));
}
};
let site = Site::Closer {
opener: start,
closer: ')'
};
let rest = match close(')', CLOSING_PAREN_CONTEXT, rest)
{
Ok(rest) => rest,
Err(e) => match rec.repair(site, e, stack)
{
Ok(at) => at,
Err(e) => return fail(e)
}
};
let end = rest.location_offset();
succeed(
rest,
Value::Group(Group {
expression: Box::new(expression),
span: SourceSpan { start, end }
})
)
}
fn start_binding<'src, R: Recovery<'src>>(
input: Span<'src>,
stack: &mut Vec<Frame<'src>>,
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
let (after_name, name) = match braced_name(input)
{
Ok(parsed) => parsed,
Err(e) => return fail(e)
};
let at: IResult<Span, char, ParseError> =
preceded(multispace0, char('@')).parse_complete(after_name);
match at
{
Ok((after_at, _)) =>
{
let head = BindingHead {
start: input.location_offset(),
name: canonical_name(name.fragment()),
name_span: span_of(name),
atom: None
};
bind(head, after_at, stack, rec)
},
Err(_) => fail(nom::Err::Error(ParseError::from_error_kind(
input,
ErrorKind::Tag
)))
}
}
fn bind<'src, R: Recovery<'src>>(
head: BindingHead<'src>,
after_at: Span<'src>,
stack: &mut Vec<Frame<'src>>,
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
let paren: IResult<Span, char, ParseError> =
cut(preceded(multispace0, char('('))).parse_complete(after_at);
let (paren, after) = match paren
{
Ok((after, _)) => (after.location_offset() - 1, after),
Err(e) =>
{
let e = within_binding(head.atom, e);
match rec.repair(Site::BindingParen, e, stack)
{
Ok(at) => (at.location_offset(), at),
Err(e) => return fail(e)
}
}
};
let input = skip_whitespace(after);
call(
stack,
Frame::BindingExpression { head, paren, input },
Goal::AddSub,
input
)
}
fn resume_binding_expression<'src, R: Recovery<'src>>(
head: BindingHead<'src>,
paren: usize,
input: Span<'src>,
outcome: Outcome<'src>,
stack: &[Frame<'src>],
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
let (rest, expression) = match outcome
{
Ok((rest, expression)) => (rest, expression.into_expression()),
Err(e) =>
{
return fail(within_binding(
head.atom,
cut_error(with_context(input, BINDING_EXPRESSION_CONTEXT, e))
));
}
};
let site = Site::Closer {
opener: paren,
closer: ')'
};
let rest = match close(')', CLOSING_PAREN_CONTEXT, rest)
{
Ok(rest) => rest,
Err(e) => match rec.repair(site, within_binding(head.atom, e), stack)
{
Ok(at) => at,
Err(e) => return fail(e)
}
};
let end = rest.location_offset();
let binding = Binding {
name: head.name,
name_span: head.name_span,
expression: Box::new(expression),
span: SourceSpan {
start: head.start,
end
}
};
match head.atom
{
Some(_) =>
{
succeed(rest, Value::Expression(Expression::Binding(binding)))
},
None => succeed(rest, Value::Binding(binding))
}
}
fn start_range<'src>(
input: Span<'src>,
stack: &mut Vec<Frame<'src>>
) -> Step<'src>
{
let start = input.location_offset();
let bracket: IResult<Span, char, ParseError> = char('[')(input);
match bracket
{
Ok((after, _)) =>
{
let input = skip_whitespace(after);
call(
stack,
Frame::RangeStart { start, input },
Goal::AddSub,
input
)
},
Err(e) => fail(e)
}
}
fn resume_range_start<'src, R: Recovery<'src>>(
start: usize,
input: Span<'src>,
outcome: Outcome<'src>,
stack: &mut Vec<Frame<'src>>,
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
let (rest, first) = match outcome
{
Ok((rest, first)) => (rest, first.into_expression()),
Err(e) =>
{
return fail(cut_error(with_context(input, RANGE_START_CONTEXT, e)));
}
};
let colon: IResult<Span, char, ParseError> =
cut(preceded(multispace0, char(':'))).parse_complete(rest);
let after = match colon
{
Ok((after, _)) => after,
Err(e) => match rec.repair(Site::Colon { opener: start }, e, stack)
{
Ok(at) => at,
Err(e) => return fail(e)
}
};
let input = skip_whitespace(after);
call(
stack,
Frame::RangeEnd {
start,
first,
input
},
Goal::AddSub,
input
)
}
fn resume_range_end<'src, R: Recovery<'src>>(
start: usize,
first: Expression<'src>,
input: Span<'src>,
outcome: Outcome<'src>,
stack: &[Frame<'src>],
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
let (rest, last) = match outcome
{
Ok((rest, last)) => (rest, last.into_expression()),
Err(e) =>
{
return fail(cut_error(with_context(input, RANGE_END_CONTEXT, e)));
}
};
let site = Site::Closer {
opener: start,
closer: ']'
};
let rest = match close(']', CLOSING_BRACKET_CONTEXT, rest)
{
Ok(rest) => rest,
Err(e) => match rec.repair(site, e, stack)
{
Ok(at) => at,
Err(e) => return fail(e)
}
};
let end = rest.location_offset();
succeed(
rest,
Value::Range(Range {
start: Box::new(first),
end: Box::new(last),
span: SourceSpan { start, end }
})
)
}
fn resume_dice_count<'src, R: Recovery<'src>>(
primary: bool,
input: Span<'src>,
outcome: Outcome<'src>,
stack: &mut Vec<Frame<'src>>,
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
let (rest, count) =
match with_context_on_error(input, DICE_COUNT_CONTEXT, outcome)
{
Ok((rest, count)) => (rest, count.into_expression()),
Err(e) => return fail(e)
};
let operator: IResult<Span, char, ParseError> =
preceded(multispace0, d_operator).parse_complete(rest);
match operator
{
Ok((after, _)) =>
{
if rec.recovering()
{
rec.operator = Some(skip_whitespace(rest));
}
let faces = skip_whitespace(after);
call(
stack,
Frame::DiceFaces {
start: input.location_offset(),
count,
input: faces
},
Goal::Atom,
faces
)
},
Err(_) if primary => succeed(rest, Value::Expression(count)),
Err(e) => fail(e)
}
}
fn resume_dice_faces<'src, R: Recovery<'src>>(
start: usize,
count: Expression<'src>,
input: Span<'src>,
outcome: Outcome<'src>,
stack: &mut Vec<Frame<'src>>,
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
let error =
match with_context_on_error(input, STANDARD_FACES_CONTEXT, outcome)
{
Ok((rest, faces)) =>
{
let dice = standard_dice(count, faces.into_expression());
return dice_continue(dice, rest, stack, rec);
},
Err(nom::Err::Error(e)) => e,
Err(e) => return fail(e)
};
let custom = match context(CUSTOM_FACES_CONTEXT, custom_faces)
.parse_complete(input)
{
Err(nom::Err::Failure(_)) if rec.recovering() =>
{
recover_custom_faces(input, stack, rec)
},
custom => custom
};
match custom
{
Ok((rest, faces)) =>
{
let end = rest.location_offset();
let dice = DiceExpression::Custom(CustomDice {
count: Box::new(count),
faces,
span: SourceSpan { start, end }
});
dice_continue(dice, rest, stack, rec)
},
Err(nom::Err::Error(e)) =>
{
let e = nom::Err::Failure(ParseError::append(
input,
ErrorKind::Alt,
merge(error, e)
));
match rec.repair_faces(e, input, stack)
{
Ok(overlay) =>
{
rec.overlay = Some(overlay);
call(
stack,
Frame::DiceFaces {
start,
count,
input
},
Goal::Atom,
input
)
},
Err(e) => fail(e)
}
},
Err(e) => fail(e)
}
}
#[inline]
fn standard_dice<'src>(
count: Expression<'src>,
faces: Expression<'src>
) -> DiceExpression<'src>
{
let span = SourceSpan {
start: count.span().start,
end: faces.span().end
};
DiceExpression::Standard(StandardDice {
count: Box::new(count),
faces: Box::new(faces),
span
})
}
fn dice_continue<'src, R: Recovery<'src>>(
dice: DiceExpression<'src>,
rest: Span<'src>,
stack: &mut Vec<Frame<'src>>,
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
let keyword: IResult<Span, Span, ParseError> =
preceded(multispace0, tag("drop")).parse_complete(rest);
let after_keyword = match keyword
{
Ok((after_keyword, _)) => after_keyword,
Err(_) => return succeed(rest, Value::Dice(dice))
};
let direction: IResult<Span, DropDirection, ParseError> = cut(preceded(
multispace0,
context(
DROP_DIRECTION_CONTEXT,
alt((
map(tag("lowest"), |_| DropDirection::Lowest),
map(tag("highest"), |_| DropDirection::Highest)
))
)
))
.parse_complete(after_keyword);
let (after_direction, direction) = match direction
{
Ok(parsed) => parsed,
Err(e) => match rec.repair(Site::Direction, e, stack)
{
Ok(at) => (at, DropDirection::Lowest),
Err(e) => return fail(e)
}
};
let input = skip_whitespace(after_direction);
call(
stack,
Frame::DiceDrop {
dice,
direction,
rest: after_direction,
input
},
Goal::Atom,
input
)
}
fn resume_dice_drop<'src, R: Recovery<'src>>(
dice: DiceExpression<'src>,
direction: DropDirection,
rest: Span<'src>,
input: Span<'src>,
outcome: Outcome<'src>,
stack: &mut Vec<Frame<'src>>,
rec: &mut Recoverer<'src, '_, R>
) -> Step<'src>
{
let (rest, drop) = match optional_drop(rest, input, outcome)
{
Ok(parsed) => parsed,
Err(e) => return fail(e)
};
let span = SourceSpan {
start: dice.span().start,
end: rest.location_offset()
};
let dice = Box::new(dice);
let dice = match direction
{
DropDirection::Lowest =>
{
DiceExpression::DropLowest(DropLowest { dice, drop, span })
},
DropDirection::Highest =>
{
DiceExpression::DropHighest(DropHighest { dice, drop, span })
},
};
dice_continue(dice, rest, stack, rec)
}
fn resume_standard_count<'src>(
input: Span<'src>,
outcome: Outcome<'src>,
stack: &mut Vec<Frame<'src>>
) -> Step<'src>
{
let (rest, count) =
match with_context_on_error(input, DICE_COUNT_CONTEXT, outcome)
{
Ok((rest, count)) => (rest, count.into_expression()),
Err(e) => return fail(e)
};
let operator: IResult<Span, char, ParseError> =
preceded(multispace0, d_operator).parse_complete(rest);
match operator
{
Ok((after, _)) =>
{
let input = skip_whitespace(after);
call(
stack,
Frame::StandardFaces { count, input },
Goal::Atom,
input
)
},
Err(e) => fail(e)
}
}
fn resume_standard_faces<'src>(
count: Expression<'src>,
input: Span<'src>,
outcome: Outcome<'src>
) -> Step<'src>
{
match with_context_on_error(input, STANDARD_FACES_CONTEXT, outcome)
{
Ok((rest, faces)) =>
{
let faces = faces.into_expression();
let span = SourceSpan {
start: count.span().start,
end: faces.span().end
};
succeed(
rest,
Value::StandardDice(StandardDice {
count: Box::new(count),
faces: Box::new(faces),
span
})
)
},
Err(e) => fail(e)
}
}
fn resume_custom_count<'src>(
input: Span<'src>,
outcome: Outcome<'src>
) -> Step<'src>
{
let (rest, count) =
match with_context_on_error(input, DICE_COUNT_CONTEXT, outcome)
{
Ok((rest, count)) => (rest, count.into_expression()),
Err(e) => return fail(e)
};
let faces = preceded(multispace0, d_operator)
.and(preceded(
multispace0,
context(CUSTOM_FACES_CONTEXT, custom_faces)
))
.parse_complete(rest);
match faces
{
Ok((rest, (_, faces))) =>
{
let start = input.location_offset();
let end = rest.location_offset();
succeed(
rest,
Value::CustomDice(CustomDice {
count: Box::new(count),
faces,
span: SourceSpan { start, end }
})
)
},
Err(e) => fail(e)
}
}
fn start_drop<'src>(
input: Span<'src>,
direction: &'static str,
stack: &mut Vec<Frame<'src>>
) -> Step<'src>
{
let keywords: IResult<Span, (Span, Span), ParseError> =
preceded(multispace0, tag("drop"))
.and(preceded(multispace0, tag(direction)))
.parse_complete(input);
match keywords
{
Ok((rest, _)) =>
{
let input = skip_whitespace(rest);
call(
stack,
Frame::DropExpression { rest, input },
Goal::Atom,
input
)
},
Err(e) => fail(e)
}
}
fn resume_drop_expression<'src>(
rest: Span<'src>,
input: Span<'src>,
outcome: Outcome<'src>
) -> Step<'src>
{
match optional_drop(rest, input, outcome)
{
Ok((rest, drop)) => succeed(rest, Value::Drop(drop)),
Err(e) => fail(e)
}
}
pub(crate) const PLACEHOLDER_OPERAND: i32 = 0;
pub(crate) const PLACEHOLDER_FACES: i32 = 6;
pub(crate) const PLACEHOLDER_FACE: i32 = 0;
pub(crate) const PLACEHOLDER_NAME: &str = "x";
pub(crate) trait Recovery<'src>
{
const ENABLED: bool = true;
fn repair(&mut self, site: &FailureSite<'src>) -> Repair;
}
struct Strict;
impl<'src> Recovery<'src> for Strict
{
const ENABLED: bool = false;
fn repair(&mut self, _site: &FailureSite<'src>) -> Repair { Repair::Stop }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum Repair
{
Stop,
Fix,
Variable
{
end: usize
},
Skip
{
end: usize
},
Reread,
Retract,
Break
{
end: usize
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum Site
{
Goal
{
goal: Goal,
range: Option<usize>
},
ParameterName,
ParameterColon,
LeadingComma,
Closer
{
opener: usize,
closer: char
},
Colon
{
opener: usize
},
BindingParen,
VariableName
{
opener: usize
},
Faces,
FaceValue
{
opener: usize
},
Direction,
TrailingInput
}
impl Site
{
fn is_fixable(self) -> bool { !matches!(self, Site::LeadingComma) }
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct FailureSite<'src>
{
pub(crate) site: Site,
pub(crate) error: ParseError<'src>
}
impl<'src> FailureSite<'src>
{
pub(crate) fn position(&self) -> usize
{
self.error.errors[0].0.location_offset()
}
pub(crate) fn input(&self) -> Span<'src> { self.error.errors[0].0 }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum Role
{
Catch,
Cut,
Pass
}
impl Role
{
fn of(frame: &Frame<'_>) -> Self
{
match frame
{
Frame::UnaryNegation { .. }
| Frame::PrimaryRange(_)
| Frame::PrimaryDice { .. }
| Frame::PrimaryGroup { .. }
| Frame::DiceFaces { .. }
| Frame::DiceDrop { .. }
| Frame::DropExpression { .. } => Role::Catch,
Frame::BinaryRight { .. }
| Frame::ExponentPower { .. }
| Frame::GroupExpression { .. }
| Frame::BindingExpression { .. }
| Frame::RangeStart { .. }
| Frame::RangeEnd { .. } => Role::Cut,
Frame::FunctionBody { .. }
| Frame::BinaryFirst(_)
| Frame::UnaryExponent { .. }
| Frame::ExponentBase
| Frame::AtomGroup { .. }
| Frame::DiceCount { .. }
| Frame::StandardCount(_)
| Frame::StandardFaces { .. }
| Frame::CustomCount(_) => Role::Pass
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum Overlay<'src>
{
Constant
{
at: usize,
value: i32
},
Reread
{
at: usize,
operator: Span<'src>
},
Variable
{
at: usize,
end: usize
},
Brace
{
opener: usize,
end: usize
}
}
struct Recoverer<'src, 'p, R>
{
policy: &'p mut R,
active: bool,
root: Span<'src>,
overlay: Option<Overlay<'src>>,
operator: Option<Span<'src>>,
calls: Vec<(Goal, Span<'src>)>,
barriers: Vec<usize>,
restart: Option<Span<'src>>
}
impl<'src, 'p, R: Recovery<'src>> Recoverer<'src, 'p, R>
{
fn new(policy: &'p mut R, root: Span<'src>) -> Self
{
Self {
policy,
active: R::ENABLED,
root,
overlay: None,
operator: None,
calls: Vec::new(),
barriers: Vec::new(),
restart: None
}
}
#[inline(always)]
fn recovering(&self) -> bool { R::ENABLED && self.active }
#[inline(always)]
fn called(&mut self, goal: Goal, input: Span<'src>, stack: &[Frame<'src>])
{
if !self.recovering() || stack.is_empty()
{
return;
}
let index = stack.len() - 1;
if self.calls.len() > index
{
self.calls.truncate(index);
}
else if Role::of(&stack[index]) != Role::Pass
{
self.barriers.push(index);
}
self.calls.push((goal, input));
}
#[inline(always)]
fn popped(&mut self, index: usize)
{
if !self.recovering()
{
return;
}
self.calls.truncate(index);
if self.barriers.last() == Some(&index)
{
self.barriers.pop();
}
}
#[inline(always)]
fn intercepts(&self, outcome: &Outcome<'src>, stack: &[Frame<'src>])
-> bool
{
self.recovering()
&& matches!(outcome, Err(nom::Err::Error(_)))
&& self
.barriers
.last()
.is_none_or(|&index| Role::of(&stack[index]) == Role::Cut)
}
fn recover_goal(
&mut self,
outcome: Outcome<'src>,
stack: &mut Vec<Frame<'src>>
) -> Step<'src>
{
let Err(e) = &outcome
else
{
unreachable!("only errors are intercepted")
};
let Some(&(goal, _)) = self.calls.last()
else
{
return self.stop(outcome);
};
if self.overlay.is_some()
{
return self.stop(outcome);
}
let anchor = self.barriers.last().copied().unwrap_or(0);
let range = match &stack[anchor]
{
Frame::RangeEnd { start, .. } => Some(*start),
_ => None
};
let site = FailureSite {
site: Site::Goal { goal, range },
error: settle(e.clone(), stack, self.root)
};
let at = site.position();
let (goal, input) = self.calls[anchor];
match self.policy.repair(&site)
{
Repair::Fix =>
{
self.overlay = Some(Overlay::Constant {
at,
value: PLACEHOLDER_OPERAND
});
},
Repair::Variable { end } if end > at =>
{
self.overlay = Some(Overlay::Variable { at, end });
if starts_body(&stack[anchor], at)
&& precedes_parameters(input.take_from(end - at))
{
return self.restart_function(input, stack);
}
},
Repair::Skip { end }
if end > at
&& skip_whitespace(input).location_offset() == at =>
{
let input =
skip_whitespace(skip_whitespace(input).take_from(end - at));
if let Frame::FunctionBody {
parameters: None, ..
} = stack[anchor]
{
return self.restart_function(input, stack);
}
stack.truncate(anchor + 1);
self.calls.truncate(anchor + 1);
return Step::Call(goal, input);
},
_ => return self.stop(outcome)
}
stack.truncate(anchor + 1);
self.calls.truncate(anchor + 1);
Step::Call(goal, input)
}
fn restart_function(
&mut self,
input: Span<'src>,
stack: &mut Vec<Frame<'src>>
) -> Step<'src>
{
stack.clear();
self.calls.clear();
self.barriers.clear();
Step::Call(Goal::Function, input)
}
#[inline(always)]
fn repair(
&mut self,
site: Site,
e: nom::Err<ParseError<'src>>,
stack: &[Frame<'src>]
) -> Result<Span<'src>, nom::Err<ParseError<'src>>>
{
if !self.recovering()
{
return Err(e);
}
self.consult(site, e, stack)
}
fn consult(
&mut self,
site: Site,
e: nom::Err<ParseError<'src>>,
stack: &[Frame<'src>]
) -> Result<Span<'src>, nom::Err<ParseError<'src>>>
{
self.consult_with(site, e, stack, |repair, input| {
(repair == Repair::Fix && site.is_fixable()).then_some(input)
})
}
fn consult_with<T>(
&mut self,
site: Site,
e: nom::Err<ParseError<'src>>,
stack: &[Frame<'src>],
accept: impl FnOnce(Repair, Span<'src>) -> Option<T>
) -> Result<T, nom::Err<ParseError<'src>>>
{
if self.overlay.is_some()
{
self.active = false;
return Err(e);
}
let site = FailureSite {
site,
error: settle(e.clone(), stack, self.root)
};
match accept(self.policy.repair(&site), site.input())
{
Some(accepted) => Ok(accepted),
None =>
{
self.active = false;
Err(e)
}
}
}
fn repair_faces(
&mut self,
e: nom::Err<ParseError<'src>>,
input: Span<'src>,
stack: &[Frame<'src>]
) -> Result<Overlay<'src>, nom::Err<ParseError<'src>>>
{
if !self.recovering()
{
return Err(e);
}
let faces = skip_whitespace(input).location_offset();
let operator = self.operator.filter(|operator| {
skip_whitespace(operator.take_from(1)).location_offset() == faces
});
self.consult_with(Site::Faces, e, stack, |repair, input| {
let at = input.location_offset();
match repair
{
Repair::Fix => Some(Overlay::Constant {
at,
value: PLACEHOLDER_FACES
}),
Repair::Variable { end } if at == faces && end > at =>
{
Some(Overlay::Variable { at, end })
},
Repair::Reread if at == faces =>
{
operator.map(|operator| Overlay::Reread { at, operator })
},
_ => None
}
})
}
fn stop(&mut self, outcome: Outcome<'src>) -> Step<'src>
{
self.active = false;
Step::Return(outcome)
}
fn overlays_parameter(&self, input: Span<'src>) -> bool
{
let start = input.location_offset();
match self.overlay
{
Some(Overlay::Variable { at, .. }) => at == start,
Some(Overlay::Brace { opener, .. }) => opener == start,
_ => false
}
}
fn take_parameter(
&mut self,
input: Span<'src>
) -> Option<(Span<'src>, Parameter<'src>)>
{
let start = input.location_offset();
let (name, end) = match self.overlay?
{
Overlay::Variable { at, end } if at == start => (start, end),
Overlay::Brace { opener, end } if opener == start =>
{
let after = &input.fragment()[1..end - start];
(end - after.trim_start().len(), end)
},
_ => return None
};
self.overlay = None;
Some((
input.take_from(end - start),
Parameter {
name: canonical_name(
&input.fragment()[name - start..end - start]
),
span: SourceSpan { start: name, end }
}
))
}
fn take_variable(
&mut self,
input: Span<'src>
) -> Option<(Span<'src>, Variable<'src>)>
{
let start = input.location_offset();
let Some(Overlay::Variable { at, end }) = self.overlay
else
{
return None;
};
if at != start
{
return None;
}
self.overlay = None;
let length = end - start;
Some((
input.take_from(length),
Variable {
name: canonical_name(&input.fragment()[..length]),
span: SourceSpan { start, end }
}
))
}
fn take_constant(
&mut self,
input: Span<'src>
) -> Option<(Span<'src>, Constant)>
{
let start = input.location_offset();
let (rest, at, value) = match self.overlay?
{
Overlay::Constant { at, value } if at == start =>
{
(input, at, value)
},
Overlay::Constant { at, value }
if at == start + 1 && input.fragment().starts_with('-') =>
{
(input.take_from(1), at, -value)
},
Overlay::Reread { at, operator } if at == start =>
{
(operator, at, PLACEHOLDER_FACES)
},
_ => return None
};
self.overlay = None;
Some((
rest,
Constant {
value,
span: SourceSpan { start, end: at }
}
))
}
fn take_negative_constant(
&mut self,
input: Span<'src>
) -> Option<(Span<'src>, Expression<'src>)>
{
let Some(Overlay::Constant { at, value }) = self.overlay
else
{
return None;
};
let sign: IResult<Span, char, ParseError> = char('-')(input);
let rest = skip_whitespace(sign.ok()?.0);
if rest.location_offset() != at
|| rest
.fragment()
.trim_start_matches(is_token_space)
.starts_with(['d', 'D', '^'])
{
return None;
}
self.overlay = None;
Some((
rest,
Expression::Constant(Constant {
value: -value,
span: SourceSpan {
start: input.location_offset(),
end: at
}
})
))
}
}
fn settle<'src>(
mut e: nom::Err<ParseError<'src>>,
stack: &[Frame<'src>],
root: Span<'src>
) -> ParseError<'src>
{
for frame in stack.iter().rev()
{
if let nom::Err::Failure(error) = &e
&& leading(error) < error.errors.len()
{
break;
}
e = unwind(frame, e);
}
let mut error = match with_context(root, FUNCTION_CONTEXT, e)
{
nom::Err::Error(e) | nom::Err::Failure(e) => e,
nom::Err::Incomplete(_) => unreachable!("parsing is complete")
};
error.errors.truncate(leading(&error));
error
}
fn leading(error: &ParseError<'_>) -> usize
{
let position = error.errors[0].0.location_offset();
error
.errors
.iter()
.take_while(|(span, _)| span.location_offset() == position)
.count()
}
fn unwind<'src>(
frame: &Frame<'src>,
e: nom::Err<ParseError<'src>>
) -> nom::Err<ParseError<'src>>
{
debug_assert!(
matches!(e, nom::Err::Failure(_)) || Role::of(frame) != Role::Catch,
"a frame that catches the error cannot unwind it"
);
match frame
{
Frame::FunctionBody { input, .. } =>
{
with_context(*input, FUNCTION_BODY_CONTEXT, e)
},
Frame::BinaryFirst(_)
| Frame::UnaryNegation { .. }
| Frame::ExponentBase => e,
Frame::BinaryRight { input, .. }
| Frame::ExponentPower { input, .. } =>
{
cut_error(with_context(*input, RIGHT_OPERAND_CONTEXT, e))
},
Frame::UnaryExponent { input, error } => match e
{
nom::Err::Error(e) => exhausted(*input, merge(error.clone(), e)),
e => e
},
Frame::PrimaryRange(input) => with_context(*input, RANGE_CONTEXT, e),
Frame::PrimaryDice { input, .. } =>
{
with_context(*input, DICE_CONTEXT, e)
},
Frame::PrimaryGroup { input, .. } =>
{
with_context(*input, GROUP_CONTEXT, e)
},
Frame::AtomGroup { input, error } =>
{
match with_context(*input, GROUP_CONTEXT, e)
{
nom::Err::Error(e) =>
{
exhausted(*input, merge(error.clone(), e))
},
e => e
}
},
Frame::GroupExpression { input, .. } =>
{
cut_error(with_context(*input, EXPRESSION_CONTEXT, e))
},
Frame::BindingExpression { head, input, .. } => within_binding(
head.atom,
cut_error(with_context(*input, BINDING_EXPRESSION_CONTEXT, e))
),
Frame::RangeStart { input, .. } =>
{
cut_error(with_context(*input, RANGE_START_CONTEXT, e))
},
Frame::RangeEnd { input, .. } =>
{
cut_error(with_context(*input, RANGE_END_CONTEXT, e))
},
Frame::DiceCount { input, .. }
| Frame::StandardCount(input)
| Frame::CustomCount(input) => with_context(*input, DICE_COUNT_CONTEXT, e),
Frame::DiceFaces { input, .. } | Frame::StandardFaces { input, .. } =>
{
with_context(*input, STANDARD_FACES_CONTEXT, e)
},
Frame::DiceDrop { input, .. } | Frame::DropExpression { input, .. } =>
{
with_context(*input, DROP_EXPRESSION_CONTEXT, e)
},
}
}
fn exhausted<'src>(
input: Span<'src>,
error: ParseError<'src>
) -> nom::Err<ParseError<'src>>
{
nom::Err::Error(ParseError::append(input, ErrorKind::Alt, error))
}
#[inline(always)]
fn parse_constant<'src, R: Recovery<'src>>(
input: Span<'src>,
rec: &mut Recoverer<'src, '_, R>
) -> IResult<Span<'src>, Constant, ParseError<'src>>
{
if rec.recovering()
&& let Some(parsed) = rec.take_constant(input)
{
return Ok(parsed);
}
context(CONSTANT_CONTEXT, constant).parse_complete(input)
}
#[inline(always)]
fn parse_negative_constant<'src, R: Recovery<'src>>(
input: Span<'src>,
rec: &mut Recoverer<'src, '_, R>
) -> IResult<Span<'src>, Expression<'src>, ParseError<'src>>
{
if rec.recovering()
&& let Some(parsed) = rec.take_negative_constant(input)
{
return Ok(parsed);
}
negative_constant(input)
}
#[inline(always)]
fn parse_variable<'src, R: Recovery<'src>>(
input: Span<'src>,
stack: &[Frame<'src>],
rec: &mut Recoverer<'src, '_, R>
) -> IResult<Span<'src>, (Variable<'src>, SourceSpan), ParseError<'src>>
{
if rec.recovering()
&& let Some((rest, variable)) = rec.take_variable(input)
{
let name_span = variable.span;
return Ok((rest, (variable, name_span)));
}
match context(VARIABLE_CONTEXT, braced_name).parse_complete(input)
{
Ok((rest, name)) =>
{
let variable = Variable {
name: canonical_name(name.fragment()),
span: SourceSpan {
start: input.location_offset(),
end: rest.location_offset()
}
};
Ok((rest, (variable, span_of(name))))
},
Err(nom::Err::Failure(_)) if rec.recovering() =>
{
recover_variable(input, stack, rec)
},
Err(e) => Err(e)
}
}
fn recover_variable<'src, R: Recovery<'src>>(
input: Span<'src>,
stack: &[Frame<'src>],
rec: &mut Recoverer<'src, '_, R>
) -> IResult<Span<'src>, (Variable<'src>, SourceSpan), ParseError<'src>>
{
let start = input.location_offset();
let within = |e| with_context(input, VARIABLE_CONTEXT, e);
let (after, _) = char('{').parse_complete(input)?;
let (after, name, name_span) = match cut(preceded(
name_space0,
context(IDENTIFIER_CONTEXT, identifier)
))
.parse_complete(after)
{
Ok((after, name)) =>
{
(after, canonical_name(name.fragment()), span_of(name))
},
Err(e) =>
{
let site = Site::VariableName { opener: start };
let at = rec.repair(site, within(e), stack)?;
let position = at.location_offset();
let name_span = SourceSpan {
start: position,
end: position
};
(at, Cow::Borrowed(PLACEHOLDER_NAME), name_span)
}
};
let brace: IResult<Span, char, ParseError> = cut(preceded(
name_space0,
context(CLOSING_BRACE_CONTEXT, char('}'))
))
.parse_complete(after);
let (rest, name, name_span) = match brace
{
Ok((rest, _)) => (rest, name, name_span),
Err(e) =>
{
let site = Site::Closer {
opener: start,
closer: '}'
};
let broken =
rec.consult_with(site, within(e), stack, |repair, at| {
match repair
{
Repair::Fix => Some((at, None)),
Repair::Break { end }
if name_span.start < end
&& end <= name_span.end =>
{
Some((input.take_from(end - start), Some(end)))
},
_ => None
}
})?;
match broken
{
(rest, None) => (rest, name, name_span),
(rest, Some(end)) =>
{
if stack
.first()
.is_some_and(|frame| starts_body(frame, start))
&& precedes_parameters(rest)
{
rec.overlay =
Some(Overlay::Brace { opener: start, end });
rec.restart = Some(input);
}
let written =
&input.fragment()[name_span.start - start..end - start];
let name_span = SourceSpan {
start: name_span.start,
end
};
(rest, canonical_name(written), name_span)
}
}
}
};
let variable = Variable {
name,
span: SourceSpan {
start,
end: rest.location_offset()
}
};
Ok((rest, (variable, name_span)))
}
fn recover_custom_faces<'src, R: Recovery<'src>>(
input: Span<'src>,
stack: &[Frame<'src>],
rec: &mut Recoverer<'src, '_, R>
) -> IResult<Span<'src>, Vec<i32>, ParseError<'src>>
{
let opener = input.location_offset();
let within = |e| with_context(input, CUSTOM_FACES_CONTEXT, e);
let face = || {
preceded(
multispace0,
context(CONSTANT_CONTEXT, map(constant, |c| c.value))
)
};
let (after, _) = char('[').parse_complete(input)?;
let (after, first) = match face().parse_complete(after)
{
Ok(parsed) => parsed,
Err(e) =>
{
let site = Site::FaceValue { opener };
(
rec.repair(site, within(cut_error(e)), stack)?,
PLACEHOLDER_FACE
)
}
};
let (after, rest) =
many0(preceded(preceded(multispace0, char(',')), face()))
.parse_complete(after)?;
let mut faces = Vec::with_capacity(rest.len() + 1);
faces.push(first);
faces.extend(rest);
let bracket: IResult<Span, char, ParseError> = cut(preceded(
multispace0,
context(CLOSING_BRACKET_CONTEXT, char(']'))
))
.parse_complete(after);
let rest = match bracket
{
Ok((rest, _)) => rest,
Err(e) =>
{
let site = Site::Closer {
opener,
closer: ']'
};
rec.repair(site, within(e), stack)?
}
};
Ok((rest, faces))
}
fn recover_parameters<'src, R: Recovery<'src>>(
input: Span<'src>,
stack: &[Frame<'src>],
rec: &mut Recoverer<'src, '_, R>
) -> IResult<Span<'src>, Option<Vec<Parameter<'src>>>, ParseError<'src>>
{
let comma = || preceded(multispace0, char(','));
let name = || preceded(multispace0, parameter);
let formal = |name: Span<'src>| Parameter {
name: canonical_name(name.fragment()),
span: SourceSpan {
start: name.location_offset(),
end: name.location_offset() + name.fragment().len()
}
};
let (mut rest, mut parameters) = match rec.take_parameter(input)
{
Some((rest, first)) =>
{
let (rest, names) =
many0(preceded(comma(), name())).parse_complete(rest)?;
let mut parameters = vec![first];
parameters.extend(names.into_iter().map(formal));
(rest, parameters)
},
None =>
{
let (rest, names) =
separated_list0(comma(), name()).parse_complete(input)?;
(rest, names.into_iter().map(formal).collect::<Vec<_>>())
}
};
loop
{
let trailing: IResult<Span, char, ParseError> = terminated(
comma(),
context(PARAMETER_CONTEXT, fail_parser::<_, (), ParseError>())
)
.parse_complete(rest);
let e = match trailing
{
Err(nom::Err::Error(e))
if matches!(
e.errors[0].1,
NomErrorKind::Nom(ErrorKind::Fail)
) =>
{
nom::Err::Failure(e)
},
_ => break
};
let site = if parameters.is_empty()
{
Site::LeadingComma
}
else
{
Site::ParameterName
};
let (at, supplied) =
rec.consult_with(site, e, stack, |repair, at| match repair
{
Repair::Fix if site.is_fixable() =>
{
let position = at.location_offset();
let supplied = Parameter {
name: Cow::Borrowed(PLACEHOLDER_NAME),
span: SourceSpan {
start: position,
end: position
}
};
Some((at, Some(supplied)))
},
Repair::Retract
if site == Site::ParameterName
&& precedes_parameters(at) =>
{
Some((at, None))
},
Repair::Variable { end } if site == Site::ParameterName =>
{
let name = skip_whitespace(at);
let start = name.location_offset();
(end > start).then(|| {
let length = end - start;
let read = Parameter {
name: canonical_name(&name.fragment()[..length]),
span: SourceSpan { start, end }
};
(name.take_from(length), Some(read))
})
},
_ => None
})?;
parameters.extend(supplied);
let (after, names) =
many0(preceded(comma(), name())).parse_complete(at)?;
parameters.extend(names.into_iter().map(formal));
rest = after;
}
if parameters.is_empty()
{
return Ok((rest, None));
}
let colon: IResult<Span, char, ParseError> =
preceded(multispace0, context(NEXT_PARAMETER_CONTEXT, char(':')))
.parse_complete(rest);
let rest = match colon
{
Ok((rest, _)) => rest,
Err(_) if parameters.len() == 1 => return Ok((input, None)),
Err(e) => rec.repair(Site::ParameterColon, e, stack)?
};
Ok((rest, Some(parameters)))
}
fn skip_whitespace(input: Span<'_>) -> Span<'_>
{
let skipped: IResult<Span, Span, ParseError> =
multispace0.parse_complete(input);
skipped.map_or(input, |(rest, _)| rest)
}
fn starts_body(frame: &Frame<'_>, at: usize) -> bool
{
matches!(
frame,
Frame::FunctionBody {
parameters: None,
input,
..
} if input.location_offset() == at
)
}
fn precedes_parameters(input: Span<'_>) -> bool
{
skip_whitespace(input).fragment().starts_with([',', ':'])
}
fn span_of(name: Span<'_>) -> SourceSpan
{
let start = name.location_offset();
SourceSpan {
start,
end: start + name.fragment().len()
}
}
fn within_binding<'src>(
atom: Option<Span<'src>>,
e: nom::Err<ParseError<'src>>
) -> nom::Err<ParseError<'src>>
{
match atom
{
Some(input) => with_context(input, BINDING_CONTEXT, e),
None => e
}
}
fn with_context_on_error<'src>(
input: Span<'src>,
label: &'static str,
outcome: Outcome<'src>
) -> Outcome<'src>
{
outcome.map_err(|e| with_context(input, label, e))
}
fn close<'src>(
closer: char,
label: &'static str,
input: Span<'src>
) -> Result<Span<'src>, nom::Err<ParseError<'src>>>
{
cut(preceded(multispace0, context(label, char(closer))))
.parse_complete(input)
.map(|(rest, _)| rest)
}
fn optional_drop<'src>(
rest: Span<'src>,
input: Span<'src>,
outcome: Outcome<'src>
) -> IResult<Span<'src>, Option<Box<Expression<'src>>>, ParseError<'src>>
{
match with_context_on_error(input, DROP_EXPRESSION_CONTEXT, outcome)
{
Ok((rest, drop)) => Ok((rest, Some(Box::new(drop.into_expression())))),
Err(nom::Err::Error(_)) => Ok((rest, None)),
Err(e) => Err(e)
}
}