pest/parser_state.rs
1// pest. The Elegant Parser
2// Copyright (c) 2018 Dragoș Tiselice
3//
4// Licensed under the Apache License, Version 2.0
5// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
6// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. All files in the project carrying such notice may not be copied,
8// modified, or distributed except according to those terms.
9
10//! The core functionality of parsing grammar.
11//! State of parser during the process of rules handling.
12
13use alloc::borrow::{Cow, ToOwned};
14use alloc::boxed::Box;
15use alloc::collections::BTreeSet;
16use alloc::rc::Rc;
17use alloc::string::String;
18use alloc::sync::Arc;
19use alloc::vec;
20use alloc::vec::Vec;
21use core::fmt::{Debug, Display, Formatter};
22use core::num::NonZeroUsize;
23use core::ops::Deref; // used in BorrowedOrRc.as_str
24use core::ops::Range;
25use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
26
27use crate::RuleType;
28use crate::error::{Error, ErrorVariant};
29use crate::iterators::pairs::new;
30use crate::iterators::{QueueableToken, pairs};
31use crate::position::Position;
32use crate::span::Span;
33use crate::stack::Stack;
34
35/// The current lookahead status of a [`ParserState`].
36///
37/// [`ParserState`]: struct.ParserState.html
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub enum Lookahead {
40 /// The positive predicate, written as an ampersand &,
41 /// attempts to match its inner expression.
42 /// If the inner expression succeeds, parsing continues,
43 /// but at the same position as the predicate —
44 /// &foo ~ bar is thus a kind of "AND" statement:
45 /// "the input string must match foo AND bar".
46 /// If the inner expression fails,
47 /// the whole expression fails too.
48 Positive,
49 /// The negative predicate, written as an exclamation mark !,
50 /// attempts to match its inner expression.
51 /// If the inner expression fails, the predicate succeeds
52 /// and parsing continues at the same position as the predicate.
53 /// If the inner expression succeeds, the predicate fails —
54 /// !foo ~ bar is thus a kind of "NOT" statement:
55 /// "the input string must match bar but NOT foo".
56 Negative,
57 /// No lookahead (i.e. it will consume input).
58 None,
59}
60
61/// The current atomicity of a [`ParserState`].
62///
63/// [`ParserState`]: struct.ParserState.html
64#[derive(Clone, Copy, Debug, Eq, PartialEq)]
65pub enum Atomicity {
66 /// prevents implicit whitespace: inside an atomic rule,
67 /// the tilde ~ means "immediately followed by",
68 /// and repetition operators (asterisk * and plus sign +)
69 /// have no implicit separation. In addition, all other rules
70 /// called from an atomic rule are also treated as atomic.
71 /// (interior matching rules are silent)
72 Atomic,
73 /// The same as atomic, but inner tokens are produced as normal.
74 CompoundAtomic,
75 /// implicit whitespace is enabled
76 NonAtomic,
77}
78
79/// Type alias to simplify specifying the return value of chained closures.
80pub type ParseResult<S> = Result<S, S>;
81
82/// Match direction for the stack. Used in `PEEK[a..b]`/`stack_match_peek_slice`.
83#[derive(Clone, Copy, Debug, Eq, PartialEq)]
84pub enum MatchDir {
85 /// from the bottom to the top of the stack
86 BottomToTop,
87 /// from the top to the bottom of the stack
88 TopToBottom,
89}
90
91static CALL_LIMIT: AtomicUsize = AtomicUsize::new(0);
92
93/// Sets the maximum cumulative number of counted parser-state calls.
94///
95/// Invocations of [`ParserState::rule`], [`ParserState::sequence`],
96/// [`ParserState::repeat`], [`ParserState::optional`], [`ParserState::lookahead`],
97/// [`ParserState::atomic`], and [`ParserState::stack_push`] consume this budget.
98/// Calls are not refunded after returning or backtracking. Repetition counts
99/// its entry, not each iteration of its closure. Primitive matches and
100/// [`ParserState::restore_on_err`] do not consume this cumulative budget.
101/// A refused entry fails the parse with "call limit reached", even if an
102/// alternative or optional expression would otherwise recover.
103///
104/// This process-wide setting is captured when each [`ParserState`] is created.
105/// `None` disables the cumulative budget. Available native stack space is
106/// checked automatically in `std` builds on the [default native targets].
107/// Other targets require the `stack-guard` feature (which enables `std`).
108/// These checks are independent of this setting; insufficient space fails
109/// parsing with "stack limit reached".
110/// Stack checking is best-effort when native stack bounds cannot be determined
111/// and is not available in `no_std` builds. It cannot protect arbitrary user
112/// callbacks or work outside the parser's guarded entry points.
113/// It is not an elapsed-time or memory limit: individual operations and
114/// user closures may perform work without additional counted calls.
115/// See the [resource-limit guidance] for enforcing deadlines.
116///
117/// [resource-limit guidance]: https://github.com/pest-parser/pest/blob/master/SECURITY.md#parsing-untrusted-input
118/// [default native targets]: https://github.com/pest-parser/pest/blob/master/README.md#native-stack-guard
119pub fn set_call_limit(limit: Option<NonZeroUsize>) {
120 CALL_LIMIT.store(
121 limit.map(|limit| limit.get()).unwrap_or(0),
122 Ordering::Relaxed,
123 );
124}
125
126static ERROR_DETAIL: AtomicBool = AtomicBool::new(false);
127
128/// Sets whether information for more error details
129/// should be collected. This is useful for debugging
130/// parser errors (as it leads to more comprehensive
131/// error messages), but it has a higher performance cost.
132/// (hence, it's off by default)
133///
134/// # Arguments
135///
136/// * `enabled` - Whether to enable the collection for
137/// more error details.
138pub fn set_error_detail(enabled: bool) {
139 ERROR_DETAIL.store(enabled, Ordering::Relaxed);
140}
141
142#[derive(Clone, Copy, Debug, Eq, PartialEq)]
143enum LimitKind {
144 Call,
145 Stack,
146}
147
148const REQUIRED_STACK_SPACE: usize = 64 * 1024;
149
150#[inline]
151fn remaining_stack() -> Option<usize> {
152 #[cfg(feature = "std")]
153 return pest_stack_guard::remaining_stack();
154 #[cfg(not(feature = "std"))]
155 return None;
156}
157
158#[derive(Debug)]
159struct CallLimitTracker {
160 current_call_limit: Option<(usize, usize)>,
161 limit_reached: Option<LimitKind>,
162}
163
164impl Default for CallLimitTracker {
165 fn default() -> Self {
166 let call_limit = CALL_LIMIT.load(Ordering::Relaxed);
167 Self {
168 current_call_limit: (call_limit > 0).then_some((0, call_limit)),
169 limit_reached: None,
170 }
171 }
172}
173
174impl CallLimitTracker {
175 fn limit_reached(&self) -> bool {
176 self.limit_reached.is_some()
177 }
178
179 fn error_message(&self) -> Option<&'static str> {
180 self.limit_reached.map(|cause| match cause {
181 LimitKind::Call => "call limit reached",
182 LimitKind::Stack => "stack limit reached",
183 })
184 }
185
186 fn enter(&mut self, count_call: bool) -> bool {
187 if self.limit_reached() {
188 return false;
189 }
190 if count_call
191 && self
192 .current_call_limit
193 .is_some_and(|(current, limit)| current >= limit)
194 {
195 self.limit_reached = Some(LimitKind::Call);
196 return false;
197 }
198 let remaining = remaining_stack();
199 self.enter_with_stack(count_call, remaining)
200 }
201
202 fn enter_with_stack(&mut self, count_call: bool, stack_space: Option<usize>) -> bool {
203 if self.limit_reached() {
204 return false;
205 }
206 let cause = if count_call
207 && self
208 .current_call_limit
209 .is_some_and(|(current, limit)| current >= limit)
210 {
211 Some(LimitKind::Call)
212 } else if stack_space.is_some_and(|remaining| remaining < REQUIRED_STACK_SPACE) {
213 Some(LimitKind::Stack)
214 } else {
215 None
216 };
217 if let Some(cause) = cause {
218 self.limit_reached = Some(cause);
219 return false;
220 }
221 if count_call && let Some((current, _)) = &mut self.current_call_limit {
222 *current += 1;
223 }
224 true
225 }
226}
227
228/// Number of call stacks that may result from a sequence of rules parsing.
229const CALL_STACK_INITIAL_CAPACITY: usize = 20;
230/// Max (un)expected number of tokens that we may see on the parsing error position.
231const EXPECTED_TOKENS_INITIAL_CAPACITY: usize = 30;
232/// Max rule children number for which we'll extend calls stacks.
233///
234/// In case rule we're working with has too many children rules that failed in parsing,
235/// we don't want to store long stacks for all of them. If rule has more than this number
236/// of failed children, they all will be collapsed in a parent rule.
237const CALL_STACK_CHILDREN_THRESHOLD: usize = 4;
238
239/// Structure tracking errored parsing call (associated with specific `ParserState` function).
240#[derive(Debug, Hash, PartialEq, Eq, Clone, PartialOrd, Ord)]
241pub enum ParseAttempt<R> {
242 /// Call of `rule` errored.
243 Rule(R),
244 /// Call of token element (e.g., `match_string` or `match_insensitive`) errored.
245 /// Works as indicator of that leaf node is not a rule. In order to get the token value we
246 /// can address `ParseAttempts` `(un)expected_tokens`.
247 Token,
248}
249
250impl<R> ParseAttempt<R> {
251 pub fn get_rule(&self) -> Option<&R> {
252 match self {
253 ParseAttempt::Rule(r) => Some(r),
254 ParseAttempt::Token => None,
255 }
256 }
257}
258
259/// Rules call stack.
260/// Contains sequence of rule calls that resulted in new parsing attempt.
261#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
262pub struct RulesCallStack<R> {
263 /// Deepest rule caused a parsing error (ParseAttempt::Token transformed into a rule).
264 pub deepest: ParseAttempt<R>,
265 /// Most top rule covering `deepest`.
266 pub parent: Option<R>,
267}
268
269impl<R> RulesCallStack<R> {
270 fn new(deepest: ParseAttempt<R>) -> RulesCallStack<R> {
271 RulesCallStack {
272 deepest,
273 parent: None,
274 }
275 }
276}
277
278#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
279pub enum ParsingToken {
280 Sensitive { token: String },
281 Insensitive { token: String },
282 Range { start: char, end: char },
283 BuiltInRule,
284}
285
286impl Display for ParsingToken {
287 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
288 match self {
289 ParsingToken::Sensitive { token } => write!(f, "{token}"),
290 ParsingToken::Insensitive { token } => write!(f, "{}", token.to_uppercase()),
291 ParsingToken::Range { start, end } => write!(f, "{start}..{end}"),
292 ParsingToken::BuiltInRule => write!(f, "BUILTIN_RULE"),
293 }
294 }
295}
296/// A helper that provides efficient string handling without unnecessary copying.
297/// We use `Arc<String>` instead of `Cow<'i, str>` to avoid copying strings when cloning the `Owned` variant, since
298/// `Arc::clone` only increments a reference count. [SpanOrLiteral] needs to be [Send] and [Sync]`, so we use [Arc]
299/// instead of [Rc].
300///
301/// (We need to clone this struct to detach it from the `&self` borrow in [SpanOrLiteral::as_borrowed_or_rc], so that
302/// we can then call `self.match_string` (a `mut self` method).
303#[derive(Debug, Clone)]
304enum BorrowedOrArc<'i> {
305 Borrowed(&'i str),
306 Owned(Arc<String>),
307}
308
309/// A holder for a literal string, for use in `push_literal`. This is typically a `&'static str`, but is an owned
310/// `Rc<String>` for the pest vm.
311impl<'i> BorrowedOrArc<'i> {
312 fn as_str<'a: 'i>(&'a self) -> &'a str {
313 match self {
314 BorrowedOrArc::Borrowed(s) => s,
315 BorrowedOrArc::Owned(s) => s.deref(),
316 }
317 }
318}
319
320impl From<Cow<'static, str>> for BorrowedOrArc<'_> {
321 fn from(value: Cow<'static, str>) -> Self {
322 match value {
323 Cow::Borrowed(s) => Self::Borrowed(s),
324 Cow::Owned(s) => Self::Owned(Arc::new(s)),
325 }
326 }
327}
328
329#[derive(Debug, Clone)]
330enum SpanOrLiteral<'i> {
331 Span(Span<'i>),
332 Literal(BorrowedOrArc<'i>),
333}
334
335impl<'i> SpanOrLiteral<'i> {
336 #[inline]
337 fn as_borrowed_or_rc(&self) -> BorrowedOrArc<'i> {
338 match self {
339 Self::Span(s) => BorrowedOrArc::Borrowed(s.as_str()),
340 Self::Literal(s) => s.clone(),
341 }
342 }
343}
344
345/// Structure that tracks all the parsing attempts made on the max position.
346/// We want to give an error hint about parsing rules that succeeded
347/// at the farthest input position.
348/// The intuition is such rules will be most likely the query user initially wanted to write.
349#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
350pub struct ParseAttempts<R> {
351 /// Indicates whether the parsing attempts are tracked.
352 enabled: bool,
353 /// Vec of rule calls sequences awaiting tokens at the same `max_position`.
354 /// If there are several stacks in vec, it means all those rule stacks are "equal"
355 /// because their attempts occurred on the same position.
356 pub call_stacks: Vec<RulesCallStack<R>>,
357 /// Tokens that could be putted at `max_position`
358 /// in order to get a valid grammar query.
359 expected_tokens: Vec<ParsingToken>,
360 /// Tokens that we've prohibited to be putted at `max_position`
361 /// in order to get a valid grammar query.
362 unexpected_tokens: Vec<ParsingToken>,
363 /// Max position at which we were expecting to see one of `expected_tokens`.
364 pub max_position: usize,
365}
366
367impl<R: RuleType> ParseAttempts<R> {
368 /// Create new `ParseAttempts` instance with `call_stacks` and `expected_tokens`
369 /// initialized with capacity.
370 pub fn new() -> Self {
371 Self {
372 call_stacks: Vec::with_capacity(CALL_STACK_INITIAL_CAPACITY),
373 expected_tokens: Vec::with_capacity(EXPECTED_TOKENS_INITIAL_CAPACITY),
374 unexpected_tokens: Vec::with_capacity(EXPECTED_TOKENS_INITIAL_CAPACITY),
375 max_position: 0,
376 enabled: ERROR_DETAIL.load(Ordering::Relaxed),
377 }
378 }
379
380 /// Get number of currently present call stacks.
381 fn call_stacks_number(&self) -> usize {
382 self.call_stacks.len()
383 }
384
385 pub fn expected_tokens(&self) -> Vec<ParsingToken> {
386 self.expected_tokens
387 .iter()
388 .cloned()
389 .collect::<BTreeSet<_>>()
390 .into_iter()
391 .collect()
392 }
393
394 pub fn unexpected_tokens(&self) -> Vec<ParsingToken> {
395 self.unexpected_tokens
396 .iter()
397 .cloned()
398 .collect::<BTreeSet<_>>()
399 .into_iter()
400 .collect()
401 }
402
403 /// Retrieve call stacks.
404 pub fn call_stacks(&self) -> Vec<RulesCallStack<R>> {
405 self.call_stacks
406 .iter()
407 .cloned()
408 .collect::<BTreeSet<_>>()
409 .into_iter()
410 .collect()
411 }
412
413 /// In case we've tried to parse a rule, which start position is bigger than previous
414 /// `max_position` it means that we've advanced in our parsing and found better candidate.
415 ///
416 /// `start_index` is:
417 /// * Number of call stacks present in state at the moment current `rule` was called. The idea
418 /// is that we'd like to update only those stacks that originated from the current `rule` and
419 /// not from those that were called previously.
420 /// * 0 in case we've successfully parsed some token since the moment `rule` was called.
421 fn try_add_new_stack_rule(&mut self, rule: R, start_index: usize) {
422 let mut non_token_call_stacks = Vec::new();
423 let mut token_call_stack_met = false;
424 for call_stack in self.call_stacks.iter().skip(start_index) {
425 if matches!(call_stack.deepest, ParseAttempt::Token) {
426 token_call_stack_met = true;
427 } else {
428 non_token_call_stacks.push(call_stack.clone())
429 }
430 }
431 if token_call_stack_met && non_token_call_stacks.is_empty() {
432 // If `non_token_call_stacks` is not empty we wouldn't like to add a new standalone
433 // `RulesCallStack::new(ParseAttempt::Token)` (that will later be transformed into a
434 // rule) as soon as it doesn't give us any useful additional info.
435 non_token_call_stacks.push(RulesCallStack::new(ParseAttempt::Token));
436 }
437 self.call_stacks
438 .splice(start_index.., non_token_call_stacks);
439
440 let children_number_over_threshold =
441 self.call_stacks_number() - start_index >= CALL_STACK_CHILDREN_THRESHOLD;
442 if children_number_over_threshold {
443 self.call_stacks.truncate(start_index);
444 self.call_stacks
445 .push(RulesCallStack::new(ParseAttempt::Rule(rule)));
446 } else {
447 for call_stack in self.call_stacks.iter_mut().skip(start_index) {
448 if matches!(call_stack.deepest, ParseAttempt::Token) {
449 call_stack.deepest = ParseAttempt::Rule(rule);
450 } else {
451 call_stack.parent = Some(rule);
452 }
453 }
454 }
455 }
456
457 /// If `expected` flag is set to false, it means we've successfully parsed token being in the
458 /// state of negative lookahead and want to track `token` in the `unexpected_tokens`. Otherwise,
459 /// we want to track it the `expected_tokens`. Let's call chosen vec a `target_vec`.
460 ///
461 /// In case `position` is:
462 /// * Equal to `max_position`, add `token` to `target_vec`,
463 /// * Bigger than `max_position`, set `token` as the only new element of `target_vec`.
464 #[allow(clippy::comparison_chain)]
465 fn try_add_new_token(
466 &mut self,
467 token: ParsingToken,
468 start_position: usize,
469 position: usize,
470 negative_lookahead: bool,
471 ) {
472 let target_vec_push_token = |attempts: &mut ParseAttempts<R>| {
473 let target_vec = if negative_lookahead {
474 &mut attempts.unexpected_tokens
475 } else {
476 &mut attempts.expected_tokens
477 };
478 target_vec.push(token);
479 };
480
481 if position > self.max_position {
482 if negative_lookahead && start_position > self.max_position {
483 // We encountered a sequence under negative lookahead.
484 // We would like to track only first failed token in this sequence (which
485 // `start_position` should be equal to `self.max_position`).
486 return;
487 }
488 target_vec_push_token(self);
489
490 if negative_lookahead {
491 // In case of successful parsing of token under negative lookahead the only
492 // thing we'd like to do is to track the token in the `unexpected_tokens`.
493 return;
494 }
495 self.max_position = position;
496 self.expected_tokens.clear();
497 self.unexpected_tokens.clear();
498 self.call_stacks.clear();
499 self.call_stacks
500 .push(RulesCallStack::new(ParseAttempt::Token));
501 } else if position == self.max_position {
502 target_vec_push_token(self);
503 self.call_stacks
504 .push(RulesCallStack::new(ParseAttempt::Token));
505 }
506 }
507
508 /// Reset state in case we've successfully parsed some token in
509 /// `match_string` or `match_insensitive`.
510 fn nullify_expected_tokens(&mut self, new_max_position: usize) {
511 self.call_stacks.clear();
512 self.expected_tokens.clear();
513 self.unexpected_tokens.clear();
514 self.max_position = new_max_position;
515 }
516}
517
518impl<R: RuleType> Default for ParseAttempts<R> {
519 fn default() -> Self {
520 Self::new()
521 }
522}
523
524/// The complete state of a [`Parser`].
525///
526/// [`Parser`]: trait.Parser.html
527#[derive(Debug)]
528pub struct ParserState<'i, R: RuleType> {
529 /// Current position from which we try to apply some parser function.
530 /// Initially is 0.
531 /// E.g., we are parsing `create user 'Bobby'` query, we parsed "create" via `match_insensitive`
532 /// and switched our `position` from 0 to the length of "create".
533 ///
534 /// E.g., see `match_string` -> `self.position.match_string(string)` which updates `self.pos`.
535 position: Position<'i>,
536 /// Queue representing rules partially (`QueueableToken::Start`) and
537 /// totally (`QueueableToken::End`) parsed. When entering rule we put it in the queue in a state
538 /// of `Start` and after all its sublogic (subrules or strings) are parsed, we change it to
539 /// `End` state.
540 queue: Vec<QueueableToken<'i, R>>,
541 /// Status set in case specific lookahead logic is used in grammar.
542 /// See `Lookahead` for more information.
543 lookahead: Lookahead,
544 /// Rules that we HAVE expected, tried to parse, but failed.
545 pos_attempts: Vec<R>,
546 /// Rules that we have NOT expected, tried to parse, but failed.
547 neg_attempts: Vec<R>,
548 /// Max position in the query from which we've tried to parse some rule but failed.
549 attempt_pos: usize,
550 /// Current atomicity status. For more information see `Atomicity`.
551 atomicity: Atomicity,
552 /// Helper structure tracking `Stack` status (used in case grammar contains stack PUSH/POP
553 /// invocations).
554 stack: Stack<SpanOrLiteral<'i>>,
555 /// Tracks cumulative parser-state calls and call/stack limit exhaustion.
556 call_tracker: CallLimitTracker,
557 /// Together with tracking of `pos_attempts` and `attempt_pos`
558 /// as a pair of (list of rules that we've tried to parse but failed, max parsed position)
559 /// we track those rules (which we've tried to parse at the same max pos) at this helper struct.
560 ///
561 /// Note, that we may try to parse several rules on different positions. We want to track only
562 /// those rules, which attempt position is bigger, because we consider that it's nearer to the
563 /// query that user really wanted to pass.
564 ///
565 /// E.g. we have a query `create user "Bobby"` and two root rules:
566 /// * CreateUser = { "create" ~ "user" ~ Name }
567 /// * CreateTable = { "create" ~ "table" ~ Name }
568 /// * Name = { SOME_DEFINITION }
569 ///
570 /// While parsing the query we'll update tracker position to the start of "Bobby", because we'd
571 /// successfully parse "create" + "user" (and not "table").
572 parse_attempts: ParseAttempts<R>,
573}
574
575/// Creates a `ParserState` from a `&str`, supplying it to a closure `f`.
576///
577/// # Examples
578///
579/// ```
580/// # use pest;
581/// let input = "";
582/// pest::state::<(), _>(input, |s| Ok(s)).unwrap();
583/// ```
584#[allow(clippy::perf)]
585pub fn state<'i, R: RuleType, F>(input: &'i str, f: F) -> Result<pairs::Pairs<'i, R>, Error<R>>
586where
587 F: FnOnce(Box<ParserState<'i, R>>) -> ParseResult<Box<ParserState<'i, R>>>,
588{
589 let state = ParserState::new(input);
590
591 match f(state) {
592 Ok(state) if !state.call_tracker.limit_reached() => {
593 let len = state.queue.len();
594 Ok(new(Rc::new(state.queue), input, None, 0, len))
595 }
596 Ok(mut state) | Err(mut state) => {
597 let variant = if let Some(message) = state.call_tracker.error_message() {
598 ErrorVariant::CustomError {
599 message: message.to_owned(),
600 }
601 } else {
602 state.pos_attempts.sort();
603 state.pos_attempts.dedup();
604 state.neg_attempts.sort();
605 state.neg_attempts.dedup();
606 ErrorVariant::ParsingError {
607 positives: state.pos_attempts.clone(),
608 negatives: state.neg_attempts.clone(),
609 }
610 };
611
612 if state.parse_attempts.enabled {
613 Err(Error::new_from_pos_with_parsing_attempts(
614 variant,
615 Position::new_internal(input, state.attempt_pos),
616 state.parse_attempts.clone(),
617 ))
618 } else {
619 Err(Error::new_from_pos(
620 variant,
621 Position::new_internal(input, state.attempt_pos),
622 ))
623 }
624 }
625 }
626}
627
628impl<'i, R: RuleType> ParserState<'i, R> {
629 /// Allocates a fresh `ParserState` object to the heap and returns the owned `Box`. This `Box`
630 /// will be passed from closure to closure based on the needs of the specified `Parser`.
631 ///
632 /// # Examples
633 ///
634 /// ```
635 /// # use pest;
636 /// let input = "";
637 /// let state: Box<pest::ParserState<&str>> = pest::ParserState::new(input);
638 /// ```
639 pub fn new(input: &'i str) -> Box<Self> {
640 Box::new(ParserState {
641 position: Position::from_start(input),
642 queue: vec![],
643 lookahead: Lookahead::None,
644 pos_attempts: vec![],
645 neg_attempts: vec![],
646 attempt_pos: 0,
647 atomicity: Atomicity::NonAtomic,
648 stack: Stack::new(),
649 call_tracker: Default::default(),
650 parse_attempts: ParseAttempts::new(),
651 })
652 }
653
654 /// Get all parse attempts after process of parsing is finished.
655 pub fn get_parse_attempts(&self) -> &ParseAttempts<R> {
656 &self.parse_attempts
657 }
658
659 /// Returns a reference to the current `Position` of the `ParserState`.
660 ///
661 /// # Examples
662 ///
663 /// ```
664 /// # use pest;
665 /// # #[allow(non_camel_case_types)]
666 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
667 /// enum Rule {
668 /// ab
669 /// }
670 ///
671 /// let input = "ab";
672 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
673 /// let position = state.position();
674 /// assert_eq!(position.pos(), 0);
675 /// ```
676 pub fn position(&self) -> &Position<'i> {
677 &self.position
678 }
679
680 /// Returns the current atomicity of the `ParserState`.
681 ///
682 /// # Examples
683 ///
684 /// ```
685 /// # use pest;
686 /// # use pest::Atomicity;
687 /// # #[allow(non_camel_case_types)]
688 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
689 /// enum Rule {
690 /// ab
691 /// }
692 ///
693 /// let input = "ab";
694 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
695 /// let atomicity = state.atomicity();
696 /// assert_eq!(atomicity, Atomicity::NonAtomic);
697 /// ```
698 pub fn atomicity(&self) -> Atomicity {
699 self.atomicity
700 }
701
702 #[inline]
703 fn inc_call_check_limit(mut self: Box<Self>) -> ParseResult<Box<Self>> {
704 if !self.call_tracker.enter(true) {
705 return Err(self);
706 }
707 Ok(self)
708 }
709
710 /// Checks native stack headroom without consuming a counted parser call.
711 #[doc(hidden)]
712 #[inline]
713 pub fn check_stack_limit(mut self: Box<Self>) -> ParseResult<Box<Self>> {
714 if !self.call_tracker.enter(false) {
715 return Err(self);
716 }
717 Ok(self)
718 }
719
720 /// Wrapper needed to generate tokens. This will associate the `R` type rule to the closure
721 /// meant to match the rule.
722 ///
723 /// # Examples
724 ///
725 /// ```
726 /// # use pest;
727 /// # #[allow(non_camel_case_types)]
728 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
729 /// enum Rule {
730 /// a
731 /// }
732 ///
733 /// let input = "a";
734 /// let pairs: Vec<_> = pest::state(input, |state| {
735 /// state.rule(Rule::a, |s| Ok(s))
736 /// }).unwrap().collect();
737 ///
738 /// assert_eq!(pairs.len(), 1);
739 /// ```
740 #[inline]
741 pub fn rule<F>(mut self: Box<Self>, rule: R, f: F) -> ParseResult<Box<Self>>
742 where
743 F: FnOnce(Box<Self>) -> ParseResult<Box<Self>>,
744 {
745 self = self.inc_call_check_limit()?;
746 // Position from which this `rule` starts parsing.
747 let actual_pos = self.position.pos();
748 // Remember index of the `self.queue` element that will be associated with this `rule`.
749 let index = self.queue.len();
750
751 let (pos_attempts_index, neg_attempts_index) = if actual_pos == self.attempt_pos {
752 (self.pos_attempts.len(), self.neg_attempts.len())
753 } else {
754 // Attempts have not been cleared yet since the attempt_pos is older.
755 (0, 0)
756 };
757
758 if self.lookahead == Lookahead::None && self.atomicity != Atomicity::Atomic {
759 // Pair's position will only be known after running the closure.
760 self.queue.push(QueueableToken::Start {
761 end_token_index: 0,
762 input_pos: actual_pos,
763 });
764 }
765
766 // Remember attempts number before `f` call.
767 // In `track` using this variable we can say, how many attempts were added
768 // during children rules traversal.
769 let attempts = self.attempts_at(actual_pos);
770 // Number of call stacks present in `self.parse_attempts` before `f` call.
771 // We need to remember this number only in case there wasn't found any farther attempt.
772 // E.g. we are handling rule, on start position of which may be tested two
773 // children rules. At the moment we'll return from `f` call below,
774 // there will be two more children rules in `self.parse_attempts` that we'll
775 // consider to be the children of current `rule`.
776 let mut remember_call_stacks_number = self.parse_attempts.call_stacks_number();
777 // Max parsing attempt position at the moment of `rule` handling.
778 // It case it's raised during children rules handling, it means
779 // we've made a parsing progress.
780 let remember_max_position = self.parse_attempts.max_position;
781
782 let result = f(self);
783
784 let mut try_add_rule_to_stack = |new_state: &mut Box<ParserState<'_, R>>| {
785 if new_state.parse_attempts.max_position > remember_max_position {
786 // It means that one of `match_string` or e.g. `match_insensitive` function calls
787 // have already erased `self.parse_attempts.call_stacks` and that previously
788 // remembered values are not valid anymore.
789 remember_call_stacks_number = 0;
790 }
791 if !matches!(new_state.atomicity, Atomicity::Atomic) {
792 new_state
793 .parse_attempts
794 .try_add_new_stack_rule(rule, remember_call_stacks_number);
795 }
796 };
797
798 match result {
799 Ok(mut new_state) => {
800 if new_state.lookahead == Lookahead::Negative {
801 new_state.track(
802 rule,
803 actual_pos,
804 pos_attempts_index,
805 neg_attempts_index,
806 attempts,
807 );
808 }
809
810 if new_state.lookahead == Lookahead::None
811 && new_state.atomicity != Atomicity::Atomic
812 {
813 // Index of `QueueableToken::End` token added below
814 // that corresponds to previously added `QueueableToken::Start` token.
815 let new_index = new_state.queue.len();
816 match new_state.queue[index] {
817 QueueableToken::Start {
818 ref mut end_token_index,
819 ..
820 } => *end_token_index = new_index,
821 _ => unreachable!(),
822 };
823
824 let new_pos = new_state.position.pos();
825
826 new_state.queue.push(QueueableToken::End {
827 start_token_index: index,
828 rule,
829 tag: None,
830 input_pos: new_pos,
831 });
832 }
833
834 // Note, that we need to count positive parsing results too, because we can fail in
835 // optional rule call inside which may lie the farthest
836 // parsed token.
837 if new_state.parse_attempts.enabled {
838 try_add_rule_to_stack(&mut new_state);
839 }
840 Ok(new_state)
841 }
842 Err(mut new_state) => {
843 if new_state.lookahead != Lookahead::Negative {
844 new_state.track(
845 rule,
846 actual_pos,
847 pos_attempts_index,
848 neg_attempts_index,
849 attempts,
850 );
851 if new_state.parse_attempts.enabled {
852 try_add_rule_to_stack(&mut new_state);
853 }
854 }
855
856 if new_state.lookahead == Lookahead::None
857 && new_state.atomicity != Atomicity::Atomic
858 {
859 new_state.queue.truncate(index);
860 }
861
862 Err(new_state)
863 }
864 }
865 }
866
867 /// Tag current node
868 ///
869 /// # Examples
870 ///
871 /// Try to recognize the one specified in a set of characters
872 ///
873 /// ```
874 /// use pest::{state, ParseResult, ParserState, iterators::Pair};
875 /// #[allow(non_camel_case_types)]
876 /// #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
877 /// enum Rule {
878 /// character,
879 /// }
880 /// fn mark_c(state: Box<ParserState<Rule>>) -> ParseResult<Box<ParserState<Rule>>> {
881 /// state.sequence(|state| {
882 /// character(state)
883 /// .and_then(|state| character(state))
884 /// .and_then(|state| character(state))
885 /// .and_then(|state| state.tag_node("c"))
886 /// .and_then(|state| character(state))
887 /// })
888 /// }
889 /// fn character(state: Box<ParserState<Rule>>) -> ParseResult<Box<ParserState<Rule>>> {
890 /// state.rule(Rule::character, |state| state.match_range('a'..'z'))
891 /// }
892 ///
893 /// let input = "abcd";
894 /// let pairs = state(input, mark_c).unwrap();
895 /// // find all node tag as `c`
896 /// let find: Vec<Pair<Rule>> = pairs.filter(|s| s.as_node_tag() == Some("c")).collect();
897 /// assert_eq!(find[0].as_str(), "c")
898 /// ```
899 #[inline]
900 pub fn tag_node(mut self: Box<Self>, tag: &'i str) -> ParseResult<Box<Self>> {
901 if self.lookahead != Lookahead::None {
902 return Ok(self);
903 }
904 if let Some(QueueableToken::End { tag: old, .. }) = self.queue.last_mut() {
905 *old = Some(tag)
906 }
907 Ok(self)
908 }
909
910 /// Get number of allowed rules attempts + prohibited rules attempts.
911 fn attempts_at(&self, pos: usize) -> usize {
912 if self.attempt_pos == pos {
913 self.pos_attempts.len() + self.neg_attempts.len()
914 } else {
915 0
916 }
917 }
918
919 fn track(
920 &mut self,
921 rule: R,
922 pos: usize,
923 pos_attempts_index: usize,
924 neg_attempts_index: usize,
925 prev_attempts: usize,
926 ) {
927 if self.atomicity == Atomicity::Atomic {
928 return;
929 }
930
931 // If nested rules made no progress, there is no use to report them; it's only useful to
932 // track the current rule, the exception being when only one attempt has been made during
933 // the children rules.
934 let curr_attempts = self.attempts_at(pos);
935 if curr_attempts > prev_attempts && curr_attempts - prev_attempts == 1 {
936 return;
937 }
938
939 if pos == self.attempt_pos {
940 self.pos_attempts.truncate(pos_attempts_index);
941 self.neg_attempts.truncate(neg_attempts_index);
942 }
943
944 if pos > self.attempt_pos {
945 self.pos_attempts.clear();
946 self.neg_attempts.clear();
947 self.attempt_pos = pos;
948 }
949
950 let attempts = if self.lookahead != Lookahead::Negative {
951 &mut self.pos_attempts
952 } else {
953 &mut self.neg_attempts
954 };
955
956 if pos == self.attempt_pos {
957 attempts.push(rule);
958 }
959 }
960
961 /// Starts a sequence of transformations provided by `f` from the `Box<ParserState>`. Returns
962 /// the same `Result` returned by `f` in the case of an `Ok`, or `Err` with the current
963 /// `Box<ParserState>` otherwise.
964 ///
965 /// This method is useful to parse sequences that only match together which usually come in the
966 /// form of chained `Result`s with
967 /// [`Result::and_then`](https://doc.rust-lang.org/std/result/enum.Result.html#method.and_then).
968 ///
969 ///
970 /// # Examples
971 ///
972 /// ```
973 /// # use pest;
974 /// # #[allow(non_camel_case_types)]
975 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
976 /// enum Rule {
977 /// a
978 /// }
979 ///
980 /// let input = "a";
981 /// let pairs: Vec<_> = pest::state(input, |state| {
982 /// state.sequence(|s| {
983 /// s.rule(Rule::a, |s| Ok(s)).and_then(|s| {
984 /// s.match_string("b")
985 /// })
986 /// }).or_else(|s| {
987 /// Ok(s)
988 /// })
989 /// }).unwrap().collect();
990 ///
991 /// assert_eq!(pairs.len(), 0);
992 /// ```
993 #[inline]
994 pub fn sequence<F>(mut self: Box<Self>, f: F) -> ParseResult<Box<Self>>
995 where
996 F: FnOnce(Box<Self>) -> ParseResult<Box<Self>>,
997 {
998 self = self.inc_call_check_limit()?;
999 let token_index = self.queue.len();
1000 let initial_pos = self.position;
1001
1002 let result = f(self.checkpoint());
1003
1004 match result {
1005 Ok(new_state) => Ok(new_state.checkpoint_ok()),
1006 Err(mut new_state) => {
1007 // Restore the initial position and truncate the token queue.
1008 new_state.position = initial_pos;
1009 new_state.queue.truncate(token_index);
1010 Err(new_state.restore())
1011 }
1012 }
1013 }
1014
1015 /// Repeatedly applies the transformation provided by `f` from the `Box<ParserState>`. Returns
1016 /// `Ok` with the updated `Box<ParserState>` returned by `f` wrapped up in an `Err`.
1017 ///
1018 /// # Examples
1019 ///
1020 /// ```
1021 /// # use pest;
1022 /// # #[allow(non_camel_case_types)]
1023 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1024 /// enum Rule {
1025 /// ab
1026 /// }
1027 ///
1028 /// let input = "aab";
1029 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1030 /// let mut result = state.repeat(|s| {
1031 /// s.match_string("a")
1032 /// });
1033 /// assert!(result.is_ok());
1034 /// assert_eq!(result.unwrap().position().pos(), 2);
1035 ///
1036 /// state = pest::ParserState::new(input);
1037 /// result = state.repeat(|s| {
1038 /// s.match_string("b")
1039 /// });
1040 /// assert!(result.is_ok());
1041 /// assert_eq!(result.unwrap().position().pos(), 0);
1042 /// ```
1043 #[inline]
1044 pub fn repeat<F>(mut self: Box<Self>, mut f: F) -> ParseResult<Box<Self>>
1045 where
1046 F: FnMut(Box<Self>) -> ParseResult<Box<Self>>,
1047 {
1048 self = self.inc_call_check_limit()?;
1049 let mut result = f(self);
1050
1051 loop {
1052 match result {
1053 Ok(state) => result = f(state),
1054 Err(state) if state.call_tracker.limit_reached() => return Err(state),
1055 Err(state) => return Ok(state),
1056 };
1057 }
1058 }
1059
1060 /// Optionally applies the transformation provided by `f` from the `Box<ParserState>`. Returns
1061 /// `Ok` with the updated `Box<ParserState>` returned by `f` regardless of the `Result`.
1062 ///
1063 /// # Examples
1064 ///
1065 /// ```
1066 /// # use pest;
1067 /// # #[allow(non_camel_case_types)]
1068 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1069 /// enum Rule {
1070 /// ab
1071 /// }
1072 ///
1073 /// let input = "ab";
1074 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1075 /// let result = state.optional(|s| {
1076 /// s.match_string("ab")
1077 /// });
1078 /// assert!(result.is_ok());
1079 ///
1080 /// state = pest::ParserState::new(input);
1081 /// let result = state.optional(|s| {
1082 /// s.match_string("ac")
1083 /// });
1084 /// assert!(result.is_ok());
1085 /// ```
1086 #[inline]
1087 pub fn optional<F>(mut self: Box<Self>, f: F) -> ParseResult<Box<Self>>
1088 where
1089 F: FnOnce(Box<Self>) -> ParseResult<Box<Self>>,
1090 {
1091 self = self.inc_call_check_limit()?;
1092 match f(self) {
1093 Err(state) if state.call_tracker.limit_reached() => Err(state),
1094 Ok(state) | Err(state) => Ok(state),
1095 }
1096 }
1097
1098 /// Generic function to handle result of char/string/range parsing
1099 /// in order to track (un)expected tokens.
1100 fn handle_token_parse_result(
1101 &mut self,
1102 start_position: usize,
1103 token: ParsingToken,
1104 parse_succeeded: bool,
1105 ) {
1106 // New position after tracked parsed element for case of `parse_succeeded` is true.
1107 // Position of parsing failure otherwise.
1108 let current_pos = self.position.pos();
1109
1110 if parse_succeeded {
1111 if self.lookahead == Lookahead::Negative {
1112 self.parse_attempts
1113 .try_add_new_token(token, start_position, current_pos, true);
1114 } else if current_pos > self.parse_attempts.max_position {
1115 self.parse_attempts.nullify_expected_tokens(current_pos);
1116 }
1117 } else if self.lookahead != Lookahead::Negative {
1118 self.parse_attempts
1119 .try_add_new_token(token, start_position, current_pos, false);
1120 }
1121 }
1122
1123 /// Attempts to match a single character based on a filter function. Returns `Ok` with the
1124 /// updated `Box<ParserState>` if successful, or `Err` with the updated `Box<ParserState>`
1125 /// otherwise.
1126 ///
1127 /// # Examples
1128 ///
1129 /// ```
1130 /// # use pest;
1131 /// # #[allow(non_camel_case_types)]
1132 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1133 /// enum Rule {}
1134 ///
1135 /// let input = "ab";
1136 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1137 /// let result = state.match_char_by(|c| c.is_ascii());
1138 /// assert!(result.is_ok());
1139 /// assert_eq!(result.unwrap().position().pos(), 1);
1140 ///
1141 /// let input = "❤";
1142 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1143 /// let result = state.match_char_by(|c| c.is_ascii());
1144 /// assert!(result.is_err());
1145 /// assert_eq!(result.unwrap_err().position().pos(), 0);
1146 /// ```
1147 #[inline]
1148 pub fn match_char_by<F>(mut self: Box<Self>, f: F) -> ParseResult<Box<Self>>
1149 where
1150 F: FnOnce(char) -> bool,
1151 {
1152 let start_position = self.position.pos();
1153 let succeeded = self.position.match_char_by(f);
1154 if self.parse_attempts.enabled {
1155 let token = ParsingToken::BuiltInRule;
1156 self.handle_token_parse_result(start_position, token, succeeded);
1157 }
1158 if succeeded { Ok(self) } else { Err(self) }
1159 }
1160
1161 /// Attempts to match the given string. Returns `Ok` with the updated `Box<ParserState>` if
1162 /// successful, or `Err` with the updated `Box<ParserState>` otherwise.
1163 ///
1164 /// # Examples
1165 ///
1166 /// ```
1167 /// # use pest;
1168 /// # #[allow(non_camel_case_types)]
1169 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1170 /// enum Rule {}
1171 ///
1172 /// let input = "ab";
1173 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1174 /// let mut result = state.match_string("ab");
1175 /// assert!(result.is_ok());
1176 /// assert_eq!(result.unwrap().position().pos(), 2);
1177 ///
1178 /// state = pest::ParserState::new(input);
1179 /// result = state.match_string("ac");
1180 /// assert!(result.is_err());
1181 /// assert_eq!(result.unwrap_err().position().pos(), 0);
1182 /// ```
1183 #[inline]
1184 pub fn match_string(mut self: Box<Self>, string: &str) -> ParseResult<Box<Self>> {
1185 let start_position = self.position.pos();
1186 let succeeded = self.position.match_string(string);
1187 if self.parse_attempts.enabled {
1188 let token = ParsingToken::Sensitive {
1189 token: String::from(string),
1190 };
1191 self.handle_token_parse_result(start_position, token, succeeded);
1192 }
1193 if succeeded { Ok(self) } else { Err(self) }
1194 }
1195
1196 /// Pushes the given literal to the stack, and always returns `Ok(Box<ParserState>)`.
1197 ///
1198 /// # Examples
1199 ///
1200 /// ```
1201 /// # use pest;
1202 /// # #[allow(non_camel_case_types)]
1203 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1204 /// enum Rule {}
1205 ///
1206 /// let input = "ab";
1207 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1208 ///
1209 /// let mut result = state.stack_push_literal("a");
1210 /// assert!(result.is_ok());
1211 /// assert_eq!(result.as_ref().unwrap().position().pos(), 0);
1212 ///
1213 /// let mut result = result.unwrap().stack_pop();
1214 /// assert!(result.is_ok());
1215 /// assert_eq!(result.unwrap().position().pos(), 1);
1216 /// ```
1217 #[inline]
1218 pub fn stack_push_literal(
1219 mut self: Box<Self>,
1220 string: impl Into<Cow<'static, str>>,
1221 ) -> ParseResult<Box<Self>> {
1222 // convert string into a Literal, and then into a BorrowedOrRc
1223 self.stack
1224 .push(SpanOrLiteral::Literal(string.into().into()));
1225 Ok(self)
1226 }
1227
1228 /// Attempts to case-insensitively match the given string. Returns `Ok` with the updated
1229 /// `Box<ParserState>` if successful, or `Err` with the updated `Box<ParserState>` otherwise.
1230 ///
1231 /// # Examples
1232 ///
1233 /// ```
1234 /// # use pest;
1235 /// # #[allow(non_camel_case_types)]
1236 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1237 /// enum Rule {}
1238 ///
1239 /// let input = "ab";
1240 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1241 /// let mut result = state.match_insensitive("AB");
1242 /// assert!(result.is_ok());
1243 /// assert_eq!(result.unwrap().position().pos(), 2);
1244 ///
1245 /// state = pest::ParserState::new(input);
1246 /// result = state.match_insensitive("AC");
1247 /// assert!(result.is_err());
1248 /// assert_eq!(result.unwrap_err().position().pos(), 0);
1249 /// ```
1250 #[inline]
1251 pub fn match_insensitive(mut self: Box<Self>, string: &str) -> ParseResult<Box<Self>> {
1252 let start_position: usize = self.position().pos();
1253 let succeeded = self.position.match_insensitive(string);
1254 if self.parse_attempts.enabled {
1255 let token = ParsingToken::Insensitive {
1256 token: String::from(string),
1257 };
1258 self.handle_token_parse_result(start_position, token, succeeded);
1259 }
1260 if succeeded { Ok(self) } else { Err(self) }
1261 }
1262
1263 /// Attempts to match a single character from the given range. Returns `Ok` with the updated
1264 /// `Box<ParserState>` if successful, or `Err` with the updated `Box<ParserState>` otherwise.
1265 ///
1266 /// # Caution
1267 /// The provided `range` is interpreted as inclusive.
1268 ///
1269 /// # Examples
1270 ///
1271 /// ```
1272 /// # use pest;
1273 /// # #[allow(non_camel_case_types)]
1274 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1275 /// enum Rule {}
1276 ///
1277 /// let input = "ab";
1278 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1279 /// let mut result = state.match_range('a'..'z');
1280 /// assert!(result.is_ok());
1281 /// assert_eq!(result.unwrap().position().pos(), 1);
1282 ///
1283 /// state = pest::ParserState::new(input);
1284 /// result = state.match_range('A'..'Z');
1285 /// assert!(result.is_err());
1286 /// assert_eq!(result.unwrap_err().position().pos(), 0);
1287 /// ```
1288 #[inline]
1289 pub fn match_range(mut self: Box<Self>, range: Range<char>) -> ParseResult<Box<Self>> {
1290 let start_position = self.position().pos();
1291 let token = ParsingToken::Range {
1292 start: range.start,
1293 end: range.end,
1294 };
1295 let succeeded = self.position.match_range(range);
1296 if self.parse_attempts.enabled {
1297 self.handle_token_parse_result(start_position, token, succeeded);
1298 }
1299 if succeeded { Ok(self) } else { Err(self) }
1300 }
1301
1302 /// Attempts to skip `n` characters forward. Returns `Ok` with the updated `Box<ParserState>`
1303 /// if successful, or `Err` with the updated `Box<ParserState>` otherwise.
1304 ///
1305 /// # Examples
1306 ///
1307 /// ```
1308 /// # use pest;
1309 /// # #[allow(non_camel_case_types)]
1310 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1311 /// enum Rule {}
1312 ///
1313 /// let input = "ab";
1314 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1315 /// let mut result = state.skip(1);
1316 /// assert!(result.is_ok());
1317 /// assert_eq!(result.unwrap().position().pos(), 1);
1318 ///
1319 /// state = pest::ParserState::new(input);
1320 /// result = state.skip(3);
1321 /// assert!(result.is_err());
1322 /// assert_eq!(result.unwrap_err().position().pos(), 0);
1323 /// ```
1324 #[inline]
1325 pub fn skip(mut self: Box<Self>, n: usize) -> ParseResult<Box<Self>> {
1326 if self.position.skip(n) {
1327 Ok(self)
1328 } else {
1329 Err(self)
1330 }
1331 }
1332
1333 /// Attempts to skip forward until one of the given strings is found. Returns `Ok` with the
1334 /// updated `Box<ParserState>` whether or not one of the strings is found.
1335 ///
1336 /// # Examples
1337 ///
1338 /// ```
1339 /// # use pest;
1340 /// # #[allow(non_camel_case_types)]
1341 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1342 /// enum Rule {}
1343 ///
1344 /// let input = "abcd";
1345 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1346 /// let mut result = state.skip_until(&["c", "d"]);
1347 /// assert!(result.is_ok());
1348 /// assert_eq!(result.unwrap().position().pos(), 2);
1349 /// ```
1350 #[inline]
1351 pub fn skip_until(mut self: Box<Self>, strings: &[&str]) -> ParseResult<Box<Self>> {
1352 self.position.skip_until(strings);
1353 Ok(self)
1354 }
1355
1356 /// Attempts to match the start of the input. Returns `Ok` with the current `Box<ParserState>`
1357 /// if the parser has not yet advanced, or `Err` with the current `Box<ParserState>` otherwise.
1358 ///
1359 /// # Examples
1360 ///
1361 /// ```
1362 /// # use pest;
1363 /// # #[allow(non_camel_case_types)]
1364 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1365 /// enum Rule {}
1366 ///
1367 /// let input = "ab";
1368 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1369 /// let mut result = state.start_of_input();
1370 /// assert!(result.is_ok());
1371 ///
1372 /// state = pest::ParserState::new(input);
1373 /// state = state.match_string("ab").unwrap();
1374 /// result = state.start_of_input();
1375 /// assert!(result.is_err());
1376 /// ```
1377 #[inline]
1378 pub fn start_of_input(self: Box<Self>) -> ParseResult<Box<Self>> {
1379 if self.position.at_start() {
1380 Ok(self)
1381 } else {
1382 Err(self)
1383 }
1384 }
1385
1386 /// Attempts to match the end of the input. Returns `Ok` with the current `Box<ParserState>` if
1387 /// there is no input remaining, or `Err` with the current `Box<ParserState>` otherwise.
1388 ///
1389 /// # Examples
1390 ///
1391 /// ```
1392 /// # use pest;
1393 /// # #[allow(non_camel_case_types)]
1394 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1395 /// enum Rule {}
1396 ///
1397 /// let input = "ab";
1398 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1399 /// let mut result = state.end_of_input();
1400 /// assert!(result.is_err());
1401 ///
1402 /// state = pest::ParserState::new(input);
1403 /// state = state.match_string("ab").unwrap();
1404 /// result = state.end_of_input();
1405 /// assert!(result.is_ok());
1406 /// ```
1407 #[inline]
1408 pub fn end_of_input(self: Box<Self>) -> ParseResult<Box<Self>> {
1409 if self.position.at_end() {
1410 Ok(self)
1411 } else {
1412 Err(self)
1413 }
1414 }
1415
1416 /// Starts a lookahead transformation provided by `f` from the `Box<ParserState>`. It returns
1417 /// `Ok` with the current `Box<ParserState>` if `f` also returns an `Ok`, or `Err` with the current
1418 /// `Box<ParserState>` otherwise. If `is_positive` is `false`, it swaps the `Ok` and `Err`
1419 /// together, negating the `Result`.
1420 ///
1421 /// # Examples
1422 ///
1423 /// ```
1424 /// # use pest;
1425 /// # #[allow(non_camel_case_types)]
1426 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1427 /// enum Rule {
1428 /// a
1429 /// }
1430 ///
1431 /// let input = "a";
1432 /// let pairs: Vec<_> = pest::state(input, |state| {
1433 /// state.lookahead(true, |state| {
1434 /// state.rule(Rule::a, |s| Ok(s))
1435 /// })
1436 /// }).unwrap().collect();
1437 ///
1438 /// assert_eq!(pairs.len(), 0);
1439 /// ```
1440 #[inline]
1441 pub fn lookahead<F>(mut self: Box<Self>, is_positive: bool, f: F) -> ParseResult<Box<Self>>
1442 where
1443 F: FnOnce(Box<Self>) -> ParseResult<Box<Self>>,
1444 {
1445 self = self.inc_call_check_limit()?;
1446 let initial_lookahead = self.lookahead;
1447
1448 self.lookahead = if is_positive {
1449 match initial_lookahead {
1450 Lookahead::None | Lookahead::Positive => Lookahead::Positive,
1451 Lookahead::Negative => Lookahead::Negative,
1452 }
1453 } else {
1454 match initial_lookahead {
1455 Lookahead::None | Lookahead::Positive => Lookahead::Negative,
1456 Lookahead::Negative => Lookahead::Positive,
1457 }
1458 };
1459
1460 let initial_pos = self.position;
1461
1462 let result = f(self.checkpoint());
1463
1464 let result_state = match result {
1465 Ok(mut new_state) => {
1466 new_state.position = initial_pos;
1467 new_state.lookahead = initial_lookahead;
1468 Ok(new_state.restore())
1469 }
1470 Err(mut new_state) => {
1471 new_state.position = initial_pos;
1472 new_state.lookahead = initial_lookahead;
1473 Err(new_state.restore())
1474 }
1475 };
1476
1477 let exhausted = match &result_state {
1478 Ok(state) | Err(state) => state.call_tracker.limit_reached(),
1479 };
1480 if exhausted {
1481 return Err(result_state.unwrap_or_else(|state| state));
1482 }
1483 if is_positive {
1484 result_state
1485 } else {
1486 match result_state {
1487 Ok(state) => Err(state),
1488 Err(state) => Ok(state),
1489 }
1490 }
1491 }
1492
1493 /// Transformation which stops `Token`s from being generated according to `is_atomic`.
1494 /// Used as wrapper over `rule` (or even another `atomic`) call.
1495 ///
1496 /// # Examples
1497 ///
1498 /// ```
1499 /// # use pest::{self, Atomicity};
1500 /// # #[allow(non_camel_case_types)]
1501 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1502 /// enum Rule {
1503 /// a
1504 /// }
1505 ///
1506 /// let input = "a";
1507 /// let pairs: Vec<_> = pest::state(input, |state| {
1508 /// state.atomic(Atomicity::Atomic, |s| {
1509 /// s.rule(Rule::a, |s| Ok(s))
1510 /// })
1511 /// }).unwrap().collect();
1512 ///
1513 /// assert_eq!(pairs.len(), 0);
1514 /// ```
1515 #[inline]
1516 pub fn atomic<F>(mut self: Box<Self>, atomicity: Atomicity, f: F) -> ParseResult<Box<Self>>
1517 where
1518 F: FnOnce(Box<Self>) -> ParseResult<Box<Self>>,
1519 {
1520 self = self.inc_call_check_limit()?;
1521 // In case child parsing call is another `atomic` it will have its own atomicity status.
1522 let initial_atomicity = self.atomicity;
1523 // In case child atomicity is the same as we've demanded, we shouldn't do nothing.
1524 // E.g. we have the following rules:
1525 // * RootRule = @{ InnerRule }
1526 // * InnerRule = @{ ... }
1527 let should_toggle = self.atomicity != atomicity;
1528
1529 // Note that we take atomicity of the top rule and not of the leaf (inner).
1530 if should_toggle {
1531 self.atomicity = atomicity;
1532 }
1533
1534 let result = f(self);
1535
1536 match result {
1537 Ok(mut new_state) => {
1538 if should_toggle {
1539 new_state.atomicity = initial_atomicity;
1540 }
1541 Ok(new_state)
1542 }
1543 Err(mut new_state) => {
1544 if should_toggle {
1545 new_state.atomicity = initial_atomicity;
1546 }
1547 Err(new_state)
1548 }
1549 }
1550 }
1551
1552 /// Evaluates the result of closure `f` and pushes the span of the input consumed from before
1553 /// `f` is called to after `f` is called to the stack. Returns `Ok(Box<ParserState>)` if `f` is
1554 /// called successfully, or `Err(Box<ParserState>)` otherwise.
1555 ///
1556 /// # Examples
1557 ///
1558 /// ```
1559 /// # use pest;
1560 /// # #[allow(non_camel_case_types)]
1561 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1562 /// enum Rule {}
1563 ///
1564 /// let input = "ab";
1565 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1566 /// let mut result = state.stack_push(|state| state.match_string("a"));
1567 /// assert!(result.is_ok());
1568 /// assert_eq!(result.unwrap().position().pos(), 1);
1569 /// ```
1570 #[inline]
1571 pub fn stack_push<F>(mut self: Box<Self>, f: F) -> ParseResult<Box<Self>>
1572 where
1573 F: FnOnce(Box<Self>) -> ParseResult<Box<Self>>,
1574 {
1575 self = self.inc_call_check_limit()?;
1576 let start = self.position;
1577
1578 let result = f(self);
1579
1580 match result {
1581 Ok(mut state) => {
1582 let end = state.position;
1583 state.stack.push(SpanOrLiteral::Span(start.span(&end)));
1584 Ok(state)
1585 }
1586 Err(state) => Err(state),
1587 }
1588 }
1589
1590 /// Peeks the top of the stack and attempts to match the string. Returns `Ok(Box<ParserState>)`
1591 /// if the string is matched successfully, or `Err(Box<ParserState>)` otherwise.
1592 ///
1593 /// # Panics
1594 ///
1595 /// Panics if the stack is empty.
1596 ///
1597 /// # Examples
1598 ///
1599 /// ```
1600 /// # use pest;
1601 /// # #[allow(non_camel_case_types)]
1602 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1603 /// enum Rule {}
1604 ///
1605 /// let input = "aa";
1606 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1607 /// let mut result = state.stack_push(|state| state.match_string("a")).and_then(
1608 /// |state| state.stack_peek()
1609 /// );
1610 /// assert!(result.is_ok());
1611 /// assert_eq!(result.unwrap().position().pos(), 2);
1612 /// ```
1613 #[inline]
1614 pub fn stack_peek(self: Box<Self>) -> ParseResult<Box<Self>> {
1615 let string = self
1616 .stack
1617 .peek()
1618 .expect("peek was called on empty stack")
1619 .as_borrowed_or_rc();
1620 self.match_string(string.as_str())
1621 }
1622
1623 /// Pops the top of the stack and attempts to match the string. Returns `Ok(Box<ParserState>)`
1624 /// if the string is matched successfully, or `Err(Box<ParserState>)` otherwise.
1625 ///
1626 /// # Panics
1627 ///
1628 /// Panics if the stack is empty.
1629 ///
1630 /// # Examples
1631 ///
1632 /// ```
1633 /// # use pest;
1634 /// # #[allow(non_camel_case_types)]
1635 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1636 /// enum Rule {}
1637 ///
1638 /// let input = "aa";
1639 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1640 /// let mut result = state.stack_push(|state| state.match_string("a")).and_then(
1641 /// |state| state.stack_pop()
1642 /// );
1643 /// assert!(result.is_ok());
1644 /// assert_eq!(result.unwrap().position().pos(), 2);
1645 /// ```
1646 #[inline]
1647 pub fn stack_pop(mut self: Box<Self>) -> ParseResult<Box<Self>> {
1648 let string = self
1649 .stack
1650 .pop()
1651 .expect("pop was called on empty stack")
1652 .as_borrowed_or_rc();
1653 self.match_string(string.as_str())
1654 }
1655
1656 /// Matches part of the state of the stack.
1657 ///
1658 /// # Examples
1659 ///
1660 /// ```
1661 /// # use pest::{self, MatchDir};
1662 /// # #[allow(non_camel_case_types)]
1663 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1664 /// enum Rule {}
1665 ///
1666 /// let input = "abcd cd cb";
1667 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1668 /// let mut result = state
1669 /// .stack_push(|state| state.match_string("a"))
1670 /// .and_then(|state| state.stack_push(|state| state.match_string("b")))
1671 /// .and_then(|state| state.stack_push(|state| state.match_string("c")))
1672 /// .and_then(|state| state.stack_push(|state| state.match_string("d")))
1673 /// .and_then(|state| state.match_string(" "))
1674 /// .and_then(|state| state.stack_match_peek_slice(2, None, MatchDir::BottomToTop))
1675 /// .and_then(|state| state.match_string(" "))
1676 /// .and_then(|state| state.stack_match_peek_slice(1, Some(-1), MatchDir::TopToBottom));
1677 /// assert!(result.is_ok());
1678 /// assert_eq!(result.unwrap().position().pos(), 10);
1679 /// ```
1680 #[inline]
1681 pub fn stack_match_peek_slice(
1682 mut self: Box<Self>,
1683 start: i32,
1684 end: Option<i32>,
1685 match_dir: MatchDir,
1686 ) -> ParseResult<Box<Self>> {
1687 let range = match constrain_idxs(start, end, self.stack.len()) {
1688 Some(r) => r,
1689 None => return Err(self),
1690 };
1691 // return true if an empty sequence is requested
1692 if range.end <= range.start {
1693 return Ok(self);
1694 }
1695
1696 let mut position = self.position;
1697 let result = {
1698 let mut iter_b2t = self.stack[range].iter();
1699 let matcher =
1700 |span: &SpanOrLiteral<'_>| position.match_string(span.as_borrowed_or_rc().as_str());
1701 match match_dir {
1702 MatchDir::BottomToTop => iter_b2t.all(matcher),
1703 MatchDir::TopToBottom => iter_b2t.rev().all(matcher),
1704 }
1705 };
1706 if result {
1707 self.position = position;
1708 Ok(self)
1709 } else {
1710 Err(self)
1711 }
1712 }
1713
1714 /// Matches the full state of the stack.
1715 ///
1716 /// # Examples
1717 ///
1718 /// ```
1719 /// # use pest;
1720 /// # #[allow(non_camel_case_types)]
1721 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1722 /// enum Rule {}
1723 ///
1724 /// let input = "abba";
1725 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1726 /// let mut result = state
1727 /// .stack_push(|state| state.match_string("a"))
1728 /// .and_then(|state| { state.stack_push(|state| state.match_string("b")) })
1729 /// .and_then(|state| state.stack_match_peek());
1730 /// assert!(result.is_ok());
1731 /// assert_eq!(result.unwrap().position().pos(), 4);
1732 /// ```
1733 #[inline]
1734 pub fn stack_match_peek(self: Box<Self>) -> ParseResult<Box<Self>> {
1735 self.stack_match_peek_slice(0, None, MatchDir::TopToBottom)
1736 }
1737
1738 /// Matches the full state of the stack. This method will clear the stack as it evaluates.
1739 ///
1740 /// # Examples
1741 ///
1742 /// ```
1743 /// /// # use pest;
1744 /// # #[allow(non_camel_case_types)]
1745 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1746 /// enum Rule {}
1747 ///
1748 /// let input = "aaaa";
1749 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1750 /// let mut result = state.stack_push(|state| state.match_string("a")).and_then(|state| {
1751 /// state.stack_push(|state| state.match_string("a"))
1752 /// }).and_then(|state| state.stack_match_peek());
1753 /// assert!(result.is_ok());
1754 /// assert_eq!(result.unwrap().position().pos(), 4);
1755 /// ```
1756 #[inline]
1757 pub fn stack_match_pop(mut self: Box<Self>) -> ParseResult<Box<Self>> {
1758 let mut position = self.position;
1759 let mut result = true;
1760 while let Some(span) = self.stack.pop() {
1761 result = position.match_string(span.as_borrowed_or_rc().as_str());
1762 if !result {
1763 break;
1764 }
1765 }
1766
1767 if result {
1768 self.position = position;
1769 Ok(self)
1770 } else {
1771 Err(self)
1772 }
1773 }
1774
1775 /// Drops the top of the stack. Returns `Ok(Box<ParserState>)` if there was a value to drop, or
1776 /// `Err(Box<ParserState>)` otherwise.
1777 ///
1778 /// # Examples
1779 ///
1780 /// ```
1781 /// # use pest;
1782 /// # #[allow(non_camel_case_types)]
1783 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1784 /// enum Rule {}
1785 ///
1786 /// let input = "aa";
1787 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1788 /// let mut result = state.stack_push(|state| state.match_string("a")).and_then(
1789 /// |state| state.stack_drop()
1790 /// );
1791 /// assert!(result.is_ok());
1792 /// assert_eq!(result.unwrap().position().pos(), 1);
1793 /// ```
1794 #[inline]
1795 pub fn stack_drop(mut self: Box<Self>) -> ParseResult<Box<Self>> {
1796 match self.stack.pop() {
1797 Some(_) => Ok(self),
1798 None => Err(self),
1799 }
1800 }
1801
1802 /// Restores the original state of the `ParserState` when `f` returns an `Err`. Currently,
1803 /// this method only restores the stack.
1804 ///
1805 /// # Examples
1806 ///
1807 /// ```
1808 /// # use pest;
1809 /// # #[allow(non_camel_case_types)]
1810 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1811 /// enum Rule {}
1812 ///
1813 /// let input = "ab";
1814 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input);
1815 /// let mut result = state.restore_on_err(|state| state.stack_push(|state|
1816 /// state.match_string("a")).and_then(|state| state.match_string("a"))
1817 /// );
1818 ///
1819 /// assert!(result.is_err());
1820 ///
1821 /// // Since the the rule doesn't match, the "a" pushed to the stack will be removed.
1822 /// let catch_panic = std::panic::catch_unwind(|| result.unwrap_err().stack_pop());
1823 /// assert!(catch_panic.is_err());
1824 /// ```
1825 #[inline]
1826 pub fn restore_on_err<F>(mut self: Box<Self>, f: F) -> ParseResult<Box<Self>>
1827 where
1828 F: FnOnce(Box<Self>) -> ParseResult<Box<Self>>,
1829 {
1830 self = self.check_stack_limit()?;
1831 match f(self.checkpoint()) {
1832 Ok(state) => Ok(state.checkpoint_ok()),
1833 Err(state) => Err(state.restore()),
1834 }
1835 }
1836
1837 // Mark the current state as a checkpoint and return the `Box`.
1838 #[inline]
1839 pub(crate) fn checkpoint(mut self: Box<Self>) -> Box<Self> {
1840 self.stack.snapshot();
1841 self
1842 }
1843
1844 // The checkpoint was cleared successfully
1845 // so remove it without touching other stack state.
1846 #[inline]
1847 pub(crate) fn checkpoint_ok(mut self: Box<Self>) -> Box<Self> {
1848 self.stack.clear_snapshot();
1849 self
1850 }
1851
1852 // Restore the current state to the most recent checkpoint.
1853 #[inline]
1854 pub(crate) fn restore(mut self: Box<Self>) -> Box<Self> {
1855 self.stack.restore();
1856 self
1857 }
1858}
1859
1860/// Helper function used only in case stack operations (PUSH/POP) are used in grammar.
1861fn constrain_idxs(start: i32, end: Option<i32>, len: usize) -> Option<Range<usize>> {
1862 let start_norm = normalize_index(start, len)?;
1863 let end_norm = end.map_or(Some(len), |e| normalize_index(e, len))?;
1864 Some(start_norm..end_norm)
1865}
1866
1867/// `constrain_idxs` helper function.
1868/// Normalizes the index using its sequence’s length.
1869/// Returns `None` if the normalized index is OOB.
1870fn normalize_index(i: i32, len: usize) -> Option<usize> {
1871 if i > len as i32 {
1872 None
1873 } else if i >= 0 {
1874 Some(i as usize)
1875 } else {
1876 let real_i = len as i32 + i;
1877 if real_i >= 0 {
1878 Some(real_i as usize)
1879 } else {
1880 None
1881 }
1882 }
1883}
1884
1885#[cfg(test)]
1886mod test {
1887 use super::*;
1888
1889 type TestState = Box<ParserState<'static, ()>>;
1890 type TestResult = ParseResult<TestState>;
1891 type Combinator = fn(TestState, fn(TestState) -> TestResult) -> TestResult;
1892
1893 const COMBINATORS: [(&str, Combinator); 9] = [
1894 ("rule", |state, inner| state.rule((), inner)),
1895 ("sequence", ParserState::sequence),
1896 ("repeat", ParserState::repeat),
1897 ("optional", ParserState::optional),
1898 ("positive lookahead", |state, inner| {
1899 state.lookahead(true, inner)
1900 }),
1901 ("negative lookahead", |state, inner| {
1902 state.lookahead(false, inner)
1903 }),
1904 ("atomic", |state, inner| {
1905 state.atomic(Atomicity::Atomic, inner)
1906 }),
1907 ("stack_push", ParserState::stack_push),
1908 ("restore_on_err", ParserState::restore_on_err),
1909 ];
1910
1911 fn tracker_at_calls(calls: usize, limit: usize) -> CallLimitTracker {
1912 CallLimitTracker {
1913 current_call_limit: Some((calls, limit)),
1914 limit_reached: None,
1915 }
1916 }
1917
1918 #[test]
1919 fn stack_limit_probe_boundary_and_unknown_bounds() {
1920 for stack_space in [
1921 None,
1922 Some(REQUIRED_STACK_SPACE),
1923 Some(REQUIRED_STACK_SPACE - 1),
1924 ] {
1925 let mut tracker = tracker_at_calls(0, 10);
1926 let allowed = stack_space != Some(REQUIRED_STACK_SPACE - 1);
1927 assert_eq!(tracker.enter_with_stack(true, stack_space), allowed);
1928 assert_eq!(tracker.current_call_limit, Some((usize::from(allowed), 10)));
1929 if !allowed {
1930 assert_eq!(tracker.error_message(), Some("stack limit reached"));
1931 assert!(!tracker.enter_with_stack(true, None));
1932 }
1933 }
1934 }
1935
1936 #[test]
1937 fn call_limit_counts_shallow_work() {
1938 let result = state::<(), _>("xxxxxxxxxxxxxxxx", |mut state| {
1939 state.call_tracker = CallLimitTracker {
1940 current_call_limit: Some((0, 3)),
1941 limit_reached: None,
1942 };
1943 state.repeat(|state| state.sequence(|state| state.match_string("x")))
1944 });
1945 assert_eq!(
1946 result.unwrap_err().variant,
1947 ErrorVariant::CustomError {
1948 message: "call limit reached".to_owned()
1949 }
1950 );
1951 }
1952
1953 #[test]
1954 fn call_limit_preserves_combinator_charges() {
1955 for (name, combinator) in COMBINATORS {
1956 for input in ["x", "y"] {
1957 for initial_calls in [0, 2] {
1958 let mut state = ParserState::new(input);
1959 state.call_tracker = tracker_at_calls(initial_calls, 10);
1960 let result = combinator(state, |state| state.match_string("x"));
1961 let should_succeed = match name {
1962 "repeat" | "optional" => true,
1963 "negative lookahead" => input != "x",
1964 _ => input == "x",
1965 };
1966 assert_eq!(result.is_ok(), should_succeed, "{name}");
1967 let state = result.unwrap_or_else(|state| state);
1968 assert_eq!(
1969 state.call_tracker.current_call_limit,
1970 Some((initial_calls + usize::from(name != "restore_on_err"), 10)),
1971 "{name} on {input:?}"
1972 );
1973 assert!(!state.call_tracker.limit_reached(), "{name}");
1974 assert_eq!(state.lookahead, Lookahead::None, "{name}");
1975 assert_eq!(state.atomicity, Atomicity::NonAtomic, "{name}");
1976 }
1977 }
1978 }
1979 }
1980
1981 #[test]
1982 fn call_limit_boundary() {
1983 for (name, combinator) in COMBINATORS {
1984 if name == "restore_on_err" {
1985 continue;
1986 }
1987 for initial_calls in [0, 2] {
1988 for remaining in [1, 2] {
1989 let limit = initial_calls + remaining;
1990 let mut state = ParserState::new("x");
1991 state.call_tracker = tracker_at_calls(initial_calls, limit);
1992 let result = combinator(state, |state| {
1993 state.rule((), |state| state.match_string("x"))
1994 });
1995 let state = result.unwrap_or_else(|state| state);
1996 assert_eq!(
1997 state.call_tracker.current_call_limit,
1998 Some((limit, limit)),
1999 "{name}"
2000 );
2001 let exhausted = remaining == 1 || name == "repeat";
2002 assert_eq!(state.call_tracker.limit_reached(), exhausted, "{name}");
2003 if exhausted {
2004 let state = combinator(state, |_| {
2005 panic!("exhausted state must not reenter a helper")
2006 })
2007 .unwrap_err();
2008 assert_eq!(
2009 state.call_tracker.current_call_limit,
2010 Some((limit, limit)),
2011 "{name}"
2012 );
2013 }
2014 }
2015 }
2016 }
2017 }
2018
2019 #[test]
2020 fn call_limit_restore_on_err_checkpoints() {
2021 for limit in [1, 2] {
2022 for succeeds in [false, true] {
2023 let mut state = ParserState::<()>::new("xy");
2024 state.call_tracker = tracker_at_calls(0, limit);
2025 state
2026 .stack
2027 .push(SpanOrLiteral::Span(Span::new("xy", 0, 1).unwrap()));
2028 let state = state.checkpoint();
2029 let result = state.restore_on_err(|mut state| {
2030 state.stack.pop();
2031 state.restore_on_err(|state| {
2032 state.sequence(|mut state| {
2033 state
2034 .stack
2035 .push(SpanOrLiteral::Span(Span::new("xy", 1, 2).unwrap()));
2036 let state = state.match_string("x").unwrap();
2037 if succeeds { Ok(state) } else { Err(state) }
2038 })
2039 })
2040 });
2041 assert_eq!(result.is_ok(), succeeds);
2042 let state = result.unwrap_or_else(|state| state);
2043 assert_eq!(state.call_tracker.current_call_limit, Some((1, limit)));
2044 assert!(!state.call_tracker.limit_reached());
2045 assert_eq!(state.position.pos(), usize::from(succeeds));
2046 let top = state.stack.peek().unwrap().as_borrowed_or_rc();
2047 assert_eq!(top.as_str(), if succeeds { "y" } else { "x" });
2048 let state = state.restore();
2049 assert_eq!(state.stack.len(), 1);
2050 assert_eq!(
2051 state.stack.peek().unwrap().as_borrowed_or_rc().as_str(),
2052 "x"
2053 );
2054 }
2055 }
2056 }
2057
2058 #[test]
2059 fn call_limit_accumulates_on_backtracking() {
2060 let input = "xxxxxxxxxxxxxxxx";
2061 let mut state = ParserState::<()>::new(input);
2062 state.call_tracker = tracker_at_calls(0, 100);
2063 let state = state
2064 .repeat(|state| {
2065 state
2066 .sequence(|state| state.match_string("y"))
2067 .or_else(|state| state.sequence(|state| state.match_string("x")))
2068 })
2069 .unwrap();
2070 assert_eq!(state.position.pos(), input.len());
2071 assert_eq!(state.call_tracker.current_call_limit, Some((35, 100)));
2072 assert!(!state.call_tracker.limit_reached());
2073 }
2074
2075 #[test]
2076 fn call_limit_cannot_be_swallowed() {
2077 for (name, combinator) in COMBINATORS {
2078 for error_detail in [false, true] {
2079 let result = state::<(), _>("", |mut state| {
2080 state.call_tracker = tracker_at_calls(0, 1);
2081 state.parse_attempts.enabled = error_detail;
2082 combinator(state, |state| {
2083 state.rule((), |state| {
2084 state.rule((), |_| panic!("over-limit closure must not run"))
2085 })
2086 })
2087 .or_else(Ok)
2088 });
2089 assert_eq!(
2090 result.expect_err(name).variant,
2091 ErrorVariant::CustomError {
2092 message: "call limit reached".to_owned(),
2093 },
2094 "{name}, error_detail={error_detail}"
2095 );
2096 }
2097 }
2098 }
2099
2100 #[test]
2101 fn call_limit_stops_recovering_combinator_continuations() {
2102 for (name, combinator) in COMBINATORS {
2103 if !matches!(name, "optional" | "repeat" | "negative lookahead") {
2104 continue;
2105 }
2106 let mut state = ParserState::new("");
2107 state.call_tracker = tracker_at_calls(0, 1);
2108 let result: TestResult = combinator(state, |state| {
2109 state.rule((), |_| panic!("over-budget body must not run"))
2110 })
2111 .map(|_| panic!("fatal resource rejection must stop continuations"));
2112 assert!(result.is_err(), "{name}");
2113 }
2114 }
2115
2116 #[test]
2117 fn normalize_index_pos() {
2118 assert_eq!(normalize_index(4, 6), Some(4));
2119 assert_eq!(normalize_index(5, 5), Some(5));
2120 assert_eq!(normalize_index(6, 3), None);
2121 }
2122
2123 #[test]
2124 fn normalize_index_neg() {
2125 assert_eq!(normalize_index(-4, 6), Some(2));
2126 assert_eq!(normalize_index(-5, 5), Some(0));
2127 assert_eq!(normalize_index(-6, 3), None);
2128 }
2129}