1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! YAPCoL (Yet Another Parser Combinator Library) is a flexible and simple-to-use
//! parser combinator library for Rust.
//!
//! It allows you to build complex parsers by combining smaller, simpler ones.
//! The library is designed to be straightforward, while still providing powerful features like
//! arbitrary lookahead and nested parsers.
//!
//! # Core Concepts
//!
//! - [`Parser`]: The central trait of the crate. Any function that takes a mutable reference
//! to an [`Input`] and returns a `Result<Output, Error>` is a parser.
//! - [`Input`]: A wrapper around an iterator that provides buffering, lookahead, and position
//! tracking capabilities.
//! - Combinators: Functions that take one or more parsers and return a new, more complex
//! parser. Examples: [`is()`], [`many0()`], [`either()`], [`chain_left()`].
//!
//! # Features
//!
//! - Arbitrary Lookahead: backtrack and try alternative parsers using [`attempt()`] and
//! [`look_ahead()`].
//! - Generic Input: works with any iterator whose elements implement the [`InputToken`] trait.
//! - Position Tracking: every token carries a [`input::Position`] (line and column).
//! Parse errors include the position of the offending token, making it easy to produce
//! human-readable error messages.
//!
//! # Quick Start
//!
//! ```
//! use yapcol::{Input, is, many0};
//!
//! let mut input = Input::new_from_chars("aaab".chars(), None);
//!
//! // Combine `is` and `many0` to parse multiple 'a's
//! let is_a = is('a');
//! let parser = many0(&is_a);
//!
//! let result = parser(&mut input);
//! assert_eq!(result, Ok(vec!['a', 'a', 'a']));
//! ```
//!
//! # Error Handling
//!
//! Every parser returns a `Result<Output, Error>`. When parsing fails, the `Err` variant contains
//! one of two possible errors, defined in the [`Error`] enum:
//!
//! - [`Error::UnexpectedToken`]: the parser encountered a token that did not satisfy its
//! requirements.
//! - [`Error::EndOfInput`]: the input stream was exhausted before the parser could match.
//! - [`Error::NonConsumingLoop`]: a repetition parser detected that the inner parser succeeded
//! without consuming any input, which would cause an infinite loop.
//!
//! The code below showcases both error variants in a simple character-based parsing example:
//!
//! ```
//! use yapcol::input::Position;
//! use yapcol::{Error, Input, Mismatch, any, is, many0, success};
//!
//! let source_name = Some(String::from("file.txt"));
//! let mut input = Input::new_from_chars(vec!['a'], source_name.clone());
//!
//! // Fails with UnexpectedToken when the token does not match.
//! let output = is('b')(&mut input);
//! let mismatch = Mismatch::new('b', 'a');
//! assert_eq!(
//! output,
//! Err(Error::UnexpectedToken(
//! source_name,
//! Position::new(1, 1),
//! Some(mismatch)
//! ))
//! );
//!
//! // Consume the only token, then try to read more.
//! is('a')(&mut input).unwrap();
//! assert_eq!(any()(&mut input), Err(Error::EndOfInput(None)));
//!
//! // The `success` combinator always succeeds without consuming any input, so `many0` detects the
//! // loop.
//! let parser = success(());
//! let mut input = Input::new_from_chars("abc".chars(), None);
//! assert_eq!(
//! many0(&parser)(&mut input),
//! Err(Error::NonConsumingLoop(None, Position::new(1, 1)))
//! );
//! ```
//!
//! The [`Error`] type implements [`std::fmt::Display`], so you can print human-readable error
//! messages.
//!
//! ```
//! use yapcol::Error;
//! use yapcol::input::Position;
//!
//! let error = Error::UnexpectedToken(Some("file.txt".to_string()), Position::new(3, 12), None);
//! assert_eq!(error.to_string(), "Unexpected token at file.txt:3:12.");
//!
//! let error = Error::EndOfInput(None);
//! assert_eq!(error.to_string(), "End of input reached.");
//!
//! let error = Error::NonConsumingLoop(Some("file.txt".to_string()), Position::new(3, 12));
//! assert_eq!(
//! error.to_string(),
//! "Non-consuming parser loop at file.txt:3:12."
//! );
//! ```
//!
//! # Examples
//!
//! YAPCoL has two crates in the `examples` directory that demonstrate the library's capabilities.
//! Both of them implement the same application: a simple arithmetic expression parser and
//! evaluator. Each example uses a slightly different implementation to achieve the task:
//! - `evaluate_expression_string` uses a parser that takes a stream of *characters* as input.
//! This example parses the input string directly into the custom `Expression` type.
//! - `evaluate_expression_token` uses a parser that takes a stream of user-defined *tokens* as
//! input. This example first performs lexical analysis (lexing) to turn the input string into
//! a vector of tokens, then parses the token stream into the custom `Expression` type.
//!
//! These two approaches reflect real-world usage of parsers, which might parse text directly or
//! perform lexical analysis beforehand. Check the `README` file in the `examples` directory for
//! more information.
pub use *;
pub use ;
pub use ;
pub use ;