Skip to main content

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}