pest/iterators/pairs_builder.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
10use alloc::rc::Rc;
11use alloc::vec::Vec;
12
13use super::line_index::LineIndex;
14use super::pairs::{self, Pairs};
15use super::queueable_token::QueueableToken;
16use crate::RuleType;
17
18/// A builder that constructs [`Pairs`] manually, without running a parser.
19///
20/// This is meant for unit tests: code that consumes parser output often takes a
21/// [`Pair`] or [`Pairs`] as input, and testing it would otherwise require
22/// running a real parse just to obtain the right shape of tokens. With a
23/// `PairsBuilder` the expected tree can be spelled out directly.
24///
25/// A pair is described by a [`Rule`], a `start` byte offset and an `end` byte
26/// offset into `input` (the same `[start, end)` convention used by
27/// [`Span`]), and, optionally, inner pairs and a node tag.
28///
29/// [`Pair`]: struct.Pair.html
30/// [`Rule`]: ../trait.RuleType.html
31/// [`Span`]: ../struct.Span.html
32///
33/// # Examples
34///
35/// Building a single leaf pair — the motivating use case, a `Pair` handed to a
36/// function under test without invoking the parser:
37///
38/// ```
39/// # use pest::iterators::PairsBuilder;
40/// # #[allow(non_camel_case_types)]
41/// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
42/// enum Rule {
43/// integer,
44/// }
45///
46/// let pairs = PairsBuilder::new("42").rule(Rule::integer, 0, 2).build();
47/// let pair = pairs.peek().unwrap();
48///
49/// assert_eq!(pair.as_rule(), Rule::integer);
50/// assert_eq!(pair.as_str(), "42");
51/// ```
52///
53/// Nesting inner pairs with [`rule_with`] and attaching tags with [`tag`]:
54///
55/// ```
56/// # use pest::iterators::PairsBuilder;
57/// # #[allow(non_camel_case_types)]
58/// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
59/// enum Rule {
60/// sum,
61/// number,
62/// }
63///
64/// // Describes the parse tree of "1+2": a `sum` containing two `number`s.
65/// let pairs = PairsBuilder::new("1+2")
66/// .rule_with(Rule::sum, 0, 3, |inner| {
67/// inner
68/// .rule(Rule::number, 0, 1)
69/// .tag("lhs")
70/// .rule(Rule::number, 2, 3)
71/// .tag("rhs")
72/// })
73/// .build();
74///
75/// let sum = pairs.peek().unwrap();
76/// assert_eq!(sum.as_rule(), Rule::sum);
77///
78/// let numbers: Vec<_> = sum.into_inner().map(|p| p.as_str()).collect();
79/// assert_eq!(numbers, ["1", "2"]);
80/// ```
81///
82/// [`rule_with`]: struct.PairsBuilder.html#method.rule_with
83/// [`tag`]: struct.PairsBuilder.html#method.tag
84#[derive(Clone, Debug)]
85pub struct PairsBuilder<'i, R> {
86 input: &'i str,
87 nodes: Vec<BuilderNode<'i, R>>,
88}
89
90#[derive(Clone, Debug)]
91struct BuilderNode<'i, R> {
92 rule: R,
93 start: usize,
94 end: usize,
95 tag: Option<&'i str>,
96 children: Vec<BuilderNode<'i, R>>,
97}
98
99impl<'i, R: RuleType> PairsBuilder<'i, R> {
100 /// Creates an empty builder over `input`.
101 ///
102 /// All offsets passed to [`rule`] and [`rule_with`] index into this string.
103 ///
104 /// [`rule`]: struct.PairsBuilder.html#method.rule
105 /// [`rule_with`]: struct.PairsBuilder.html#method.rule_with
106 pub fn new(input: &'i str) -> Self {
107 PairsBuilder {
108 input,
109 nodes: Vec::new(),
110 }
111 }
112
113 /// Appends a leaf [`Pair`] for `rule` spanning `input[start..end]`.
114 ///
115 /// [`Pair`]: struct.Pair.html
116 ///
117 /// # Examples
118 ///
119 /// ```
120 /// # use pest::iterators::PairsBuilder;
121 /// # #[allow(non_camel_case_types)]
122 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
123 /// # enum Rule { a, b }
124 /// let pairs = PairsBuilder::new("a b")
125 /// .rule(Rule::a, 0, 1)
126 /// .rule(Rule::b, 2, 3)
127 /// .build();
128 ///
129 /// let strings: Vec<_> = pairs.map(|p| p.as_str()).collect();
130 /// assert_eq!(strings, ["a", "b"]);
131 /// ```
132 pub fn rule(mut self, rule: R, start: usize, end: usize) -> Self {
133 self.nodes.push(BuilderNode {
134 rule,
135 start,
136 end,
137 tag: None,
138 children: Vec::new(),
139 });
140 self
141 }
142
143 /// Appends a [`Pair`] for `rule` spanning `input[start..end]`, whose inner
144 /// [`Pairs`] are produced by `build_children`.
145 ///
146 /// `build_children` receives a fresh builder over the same `input`; the
147 /// pairs it appends become the inner pairs of this rule, in the order they
148 /// are added.
149 ///
150 /// [`Pair`]: struct.Pair.html
151 ///
152 /// # Examples
153 ///
154 /// ```
155 /// # use pest::iterators::PairsBuilder;
156 /// # #[allow(non_camel_case_types)]
157 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
158 /// # enum Rule { list, item }
159 /// let pairs = PairsBuilder::new("[a,b]")
160 /// .rule_with(Rule::list, 0, 5, |inner| {
161 /// inner.rule(Rule::item, 1, 2).rule(Rule::item, 3, 4)
162 /// })
163 /// .build();
164 ///
165 /// let list = pairs.peek().unwrap();
166 /// assert_eq!(list.into_inner().count(), 2);
167 /// ```
168 pub fn rule_with<F>(mut self, rule: R, start: usize, end: usize, build_children: F) -> Self
169 where
170 F: FnOnce(PairsBuilder<'i, R>) -> PairsBuilder<'i, R>,
171 {
172 let children = build_children(PairsBuilder::new(self.input)).nodes;
173 self.nodes.push(BuilderNode {
174 rule,
175 start,
176 end,
177 tag: None,
178 children,
179 });
180 self
181 }
182
183 /// Attaches `tag` to the most recently appended pair, so that it can be
184 /// looked up with [`Pairs::find_first_tagged`], [`Pairs::find_tagged`] or
185 /// [`Pair::as_node_tag`].
186 ///
187 /// [`Pairs::find_first_tagged`]: struct.Pairs.html#method.find_first_tagged
188 /// [`Pairs::find_tagged`]: struct.Pairs.html#method.find_tagged
189 /// [`Pair::as_node_tag`]: struct.Pair.html#method.as_node_tag
190 ///
191 /// # Panics
192 ///
193 /// Panics if called before any rule has been appended to this builder.
194 ///
195 /// # Examples
196 ///
197 /// ```
198 /// # use pest::iterators::PairsBuilder;
199 /// # #[allow(non_camel_case_types)]
200 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
201 /// # enum Rule { number }
202 /// let pairs = PairsBuilder::new("7").rule(Rule::number, 0, 1).tag("answer").build();
203 ///
204 /// assert_eq!(pairs.find_first_tagged("answer").unwrap().as_str(), "7");
205 /// ```
206 pub fn tag(mut self, tag: &'i str) -> Self {
207 self.nodes
208 .last_mut()
209 .expect("PairsBuilder::tag called before any rule was added")
210 .tag = Some(tag);
211 self
212 }
213
214 /// Consumes the builder and produces the [`Pairs`].
215 ///
216 /// # Panics
217 ///
218 /// Panics if any appended rule's `[start, end)` is not a valid range into
219 /// `input` — that is, if `start > end`, `end` is past the end of `input`, or
220 /// either offset does not fall on a UTF-8 character boundary. This mirrors
221 /// the invariant enforced by [`Span::new`], which the resulting pairs would
222 /// otherwise violate.
223 ///
224 /// [`Span::new`]: ../struct.Span.html#method.new
225 pub fn build(self) -> Pairs<'i, R> {
226 let mut queue = Vec::new();
227
228 for node in &self.nodes {
229 push_node(&mut queue, self.input, node);
230 }
231
232 let end = queue.len();
233
234 pairs::new(
235 Rc::new(queue),
236 self.input,
237 Some(Rc::new(LineIndex::new(self.input))),
238 0,
239 end,
240 )
241 }
242}
243
244/// Flattens `node` (and its descendants, depth-first) into the token `queue`,
245/// preserving the `Start`/`End` matched-index invariant that [`Pairs`] relies
246/// on for O(1) navigation.
247fn push_node<'i, R: RuleType>(
248 queue: &mut Vec<QueueableToken<'i, R>>,
249 input: &'i str,
250 node: &BuilderNode<'i, R>,
251) {
252 assert!(
253 input.get(node.start..node.end).is_some(),
254 "PairsBuilder: invalid span {}..{} for input of length {}; \
255 start..end must be an ascending range on UTF-8 character boundaries",
256 node.start,
257 node.end,
258 input.len()
259 );
260
261 let start_index = queue.len();
262 queue.push(QueueableToken::Start {
263 end_token_index: 0,
264 input_pos: node.start,
265 });
266
267 for child in &node.children {
268 push_node(queue, input, child);
269 }
270
271 let end_index = queue.len();
272 match queue[start_index] {
273 QueueableToken::Start {
274 ref mut end_token_index,
275 ..
276 } => *end_token_index = end_index,
277 // We only ever push a `Start` at `start_index` just above.
278 _ => unreachable!(),
279 }
280
281 queue.push(QueueableToken::End {
282 start_token_index: start_index,
283 rule: node.rule,
284 tag: node.tag,
285 input_pos: node.end,
286 });
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292 use alloc::vec;
293 use alloc::vec::Vec;
294
295 #[allow(non_camel_case_types)]
296 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
297 enum Rule {
298 a,
299 b,
300 sum,
301 number,
302 }
303
304 #[test]
305 fn single_leaf() {
306 let pairs = PairsBuilder::new("42").rule(Rule::number, 0, 2).build();
307 let pair = pairs.peek().unwrap();
308
309 assert_eq!(pair.as_rule(), Rule::number);
310 assert_eq!(pair.as_str(), "42");
311 assert_eq!(pair.as_span().start(), 0);
312 assert_eq!(pair.as_span().end(), 2);
313 assert_eq!(pair.into_inner().count(), 0);
314 }
315
316 #[test]
317 fn multiple_top_level_leaves() {
318 let mut pairs = PairsBuilder::new("a b")
319 .rule(Rule::a, 0, 1)
320 .rule(Rule::b, 2, 3)
321 .build();
322
323 assert_eq!(pairs.len(), 2);
324
325 let first = pairs.next().unwrap();
326 assert_eq!(first.as_rule(), Rule::a);
327 assert_eq!(first.as_str(), "a");
328
329 let second = pairs.next().unwrap();
330 assert_eq!(second.as_rule(), Rule::b);
331 assert_eq!(second.as_str(), "b");
332
333 assert!(pairs.next().is_none());
334 }
335
336 #[test]
337 fn nested_pairs() {
338 let pairs = PairsBuilder::new("1+2")
339 .rule_with(Rule::sum, 0, 3, |inner| {
340 inner.rule(Rule::number, 0, 1).rule(Rule::number, 2, 3)
341 })
342 .build();
343
344 let sum = pairs.peek().unwrap();
345 assert_eq!(sum.as_rule(), Rule::sum);
346 assert_eq!(sum.as_str(), "1+2");
347
348 let inner: Vec<_> = sum.into_inner().collect();
349 assert_eq!(inner.len(), 2);
350 assert_eq!(inner[0].as_str(), "1");
351 assert_eq!(inner[0].as_rule(), Rule::number);
352 assert_eq!(inner[1].as_str(), "2");
353 assert_eq!(inner[1].as_rule(), Rule::number);
354 }
355
356 #[test]
357 fn tokens_round_trip() {
358 use crate::Token;
359
360 let pairs = PairsBuilder::new("1+2")
361 .rule_with(Rule::sum, 0, 3, |inner| {
362 inner.rule(Rule::number, 0, 1).rule(Rule::number, 2, 3)
363 })
364 .build();
365
366 let tokens: Vec<_> = pairs.tokens().collect();
367 assert_eq!(
368 tokens,
369 vec![
370 Token::Start {
371 rule: Rule::sum,
372 pos: pos("1+2", 0)
373 },
374 Token::Start {
375 rule: Rule::number,
376 pos: pos("1+2", 0)
377 },
378 Token::End {
379 rule: Rule::number,
380 pos: pos("1+2", 1)
381 },
382 Token::Start {
383 rule: Rule::number,
384 pos: pos("1+2", 2)
385 },
386 Token::End {
387 rule: Rule::number,
388 pos: pos("1+2", 3)
389 },
390 Token::End {
391 rule: Rule::sum,
392 pos: pos("1+2", 3)
393 },
394 ]
395 );
396 }
397
398 fn pos(input: &str, pos: usize) -> crate::Position<'_> {
399 crate::Position::new(input, pos).unwrap()
400 }
401
402 #[test]
403 fn tags_are_queryable() {
404 let pairs = PairsBuilder::new("1+2")
405 .rule_with(Rule::sum, 0, 3, |inner| {
406 inner
407 .rule(Rule::number, 0, 1)
408 .tag("lhs")
409 .rule(Rule::number, 2, 3)
410 .tag("rhs")
411 })
412 .build();
413
414 assert_eq!(
415 pairs.clone().find_first_tagged("lhs").unwrap().as_str(),
416 "1"
417 );
418 assert_eq!(
419 pairs.clone().find_first_tagged("rhs").unwrap().as_str(),
420 "2"
421 );
422 assert!(pairs.find_first_tagged("missing").is_none());
423 }
424
425 #[test]
426 fn empty_builder_yields_no_pairs() {
427 let pairs = PairsBuilder::<Rule>::new("").build();
428 assert!(pairs.clone().peek().is_none());
429 assert_eq!(pairs.count(), 0);
430 }
431
432 #[test]
433 fn line_col_is_computed() {
434 let pairs = PairsBuilder::new("ab\ncd").rule(Rule::a, 3, 5).build();
435 let pair = pairs.peek().unwrap();
436 assert_eq!(pair.line_col(), (2, 1));
437 }
438
439 #[test]
440 fn multibyte_span() {
441 // "héllo": 'é' is two bytes, so byte offset 3 lands right after it.
442 let pairs = PairsBuilder::new("héllo").rule(Rule::a, 0, 3).build();
443 assert_eq!(pairs.peek().unwrap().as_str(), "hé");
444 }
445
446 #[test]
447 #[should_panic(expected = "invalid span")]
448 fn out_of_bounds_span_panics() {
449 let _ = PairsBuilder::new("ab").rule(Rule::a, 0, 5).build();
450 }
451
452 #[test]
453 #[should_panic(expected = "invalid span")]
454 fn non_char_boundary_span_panics() {
455 // Byte offset 1 splits the two-byte 'é'.
456 let _ = PairsBuilder::new("é").rule(Rule::a, 0, 1).build();
457 }
458
459 #[test]
460 #[should_panic(expected = "before any rule")]
461 fn tag_without_rule_panics() {
462 let _ = PairsBuilder::<Rule>::new("x").tag("oops");
463 }
464}