use std::mem;
use std::ops::ControlFlow;
use crate::cst::{Node, TerminalKindExtensions, TextIndex};
use crate::parser::parser_support::context::{Marker, ParserContext};
use crate::parser::parser_support::ParserResult;
use crate::parser::ParseError;
#[must_use]
pub struct ChoiceHelper {
result: ParserResult,
start_position: Marker,
last_progress: usize,
recovered_errors: Vec<ParseError>,
}
impl ChoiceHelper {
pub fn new(input: &mut ParserContext<'_>) -> Self {
Self {
result: ParserResult::default(),
start_position: input.mark(),
recovered_errors: vec![],
last_progress: input.position(),
}
}
pub fn is_done(&self) -> bool {
match &self.result {
ParserResult::Match(r#match) if r#match.is_full_recursive() => true,
ParserResult::PrattOperatorMatch(..) => true,
_ => false,
}
}
fn attempt_pick(&mut self, input: &mut ParserContext<'_>, next_result: ParserResult) {
let better_pick = match (&mut self.result, &next_result) {
(ParserResult::Match(running), _) if running.is_full_recursive() => {
debug_assert!(self.is_done());
return;
}
(ParserResult::PrattOperatorMatch(..), _) => {
debug_assert!(self.is_done());
return;
}
(ParserResult::NoMatch(running), ParserResult::NoMatch(next)) => {
running
.expected_terminals
.extend(next.expected_terminals.clone());
false
}
(_, ParserResult::NoMatch(..)) => false,
(_, ParserResult::Match(next)) if next.is_full_recursive() => true,
(_, ParserResult::PrattOperatorMatch(..)) => true,
(cur, next) => total_not_skipped_span(cur) < total_not_skipped_span(next),
};
if better_pick {
self.result = next_result;
self.recovered_errors = input.errors_since(self.start_position).to_vec();
self.last_progress = input.position();
}
}
pub fn run(
input: &mut ParserContext<'_>,
f: impl FnOnce(Self, &mut ParserContext<'_>) -> ControlFlow<ParserResult, Self>,
) -> ParserResult {
match f(ChoiceHelper::new(input), input) {
ControlFlow::Break(result) => result,
ControlFlow::Continue(..) => {
panic!("ChoiceHelper not `finish`()-ed in the `run` closure")
}
}
}
pub fn consider(
&mut self,
input: &mut ParserContext<'_>,
value: ParserResult,
) -> ControlFlow<ParserResult, &mut ChoiceHelper> {
self.attempt_pick(input, value);
if self.is_done() {
ControlFlow::Break(mem::take(&mut self.result))
} else {
input.rewind(self.start_position);
ControlFlow::Continue(self)
}
}
pub fn finish(self, input: &mut ParserContext<'_>) -> ControlFlow<ParserResult, Self> {
assert!(!self.is_done());
input.set_position(self.last_progress);
input.extend_errors(self.recovered_errors);
ControlFlow::Break(self.result)
}
}
pub fn total_not_skipped_span(result: &ParserResult) -> usize {
let nodes = match result {
ParserResult::Match(match_) => &match_.nodes,
ParserResult::IncompleteMatch(incomplete_match) => &incomplete_match.nodes,
ParserResult::SkippedUntil(skipped) => &skipped.nodes,
ParserResult::NoMatch(_) => &[][..],
ParserResult::PrattOperatorMatch(_) => unreachable!(
"PrattOperatorMatch is always considered a better pick, so it should never be considered here"
),
};
nodes
.iter()
.flat_map(|edge| {
edge.node
.clone()
.create_cursor(TextIndex::ZERO)
.remaining_nodes()
})
.filter_map(|edge| match edge.node {
Node::Terminal(terminal) if terminal.kind.is_valid() => Some(terminal.text.len()),
_ => None,
})
.sum()
}