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