Skip to content

Commit 4e10be8

Browse files
authored
Flattens choice/sequence operators (#46)
Implements a `flatten` a higher-order function to operate on binary operators that have associativity like `choice`/`sequence`. Resolves #37.
1 parent 497bb91 commit 4e10be8

File tree

1 file changed

+124
-30
lines changed

1 file changed

+124
-30
lines changed

pest-ion/src/lib.rs

Lines changed: 124 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,46 @@ impl PestToElement for AstRuleType {
206206
}
207207
}
208208

209+
/// Signalling result for the callback in [`flatten`].
210+
enum ShouldFlatten {
211+
/// Indicates that the children of the operand should be flattened recursively.
212+
Yes(Box<Expr>, Box<Expr>),
213+
/// Indicates that the operand should not be flattened and is transfered back to the caller.
214+
No(Box<Expr>),
215+
}
216+
217+
/// High order function to flatten associative binary nodes in a Pest expression.
218+
///
219+
/// Certain nodes like the `choice` and `sequence` nodes are associative
220+
/// (though not commutative) and can be flattened in to a variadic node instead
221+
/// of the fixed binary one that the Pest AST has.
222+
///
223+
/// The caller is responsible for seeding the vector with the tag (e.g. `choice`).
224+
///
225+
/// The `determine_flatten` function parameter returns [`ShouldFlatten::Yes`] when the underlying
226+
/// binary operator for an operand should be flattened recursively (for its underlying children),
227+
/// and returns [`ShouldFlatten::No`] when it should not be flattened and the operand is moved
228+
/// back to the caller to covert to [`Element`] normally.
229+
fn flatten<F>(
230+
sexp_elements: &mut Vec<OwnedElement>,
231+
left: Box<Expr>,
232+
right: Box<Expr>,
233+
determine_flatten: F,
234+
) where
235+
F: Fn(Box<Expr>) -> ShouldFlatten + Copy,
236+
{
237+
for operand in std::array::IntoIter::new([left, right]) {
238+
match determine_flatten(operand) {
239+
ShouldFlatten::Yes(child_left, child_right) => {
240+
flatten(sexp_elements, child_left, child_right, determine_flatten);
241+
}
242+
ShouldFlatten::No(original) => {
243+
sexp_elements.push(original.pest_to_element());
244+
}
245+
}
246+
}
247+
}
248+
209249
impl PestToElement for Expr {
210250
type Element = OwnedElement;
211251

@@ -240,16 +280,36 @@ impl PestToElement for Expr {
240280
text_token("negative").into(),
241281
expr.pest_to_element(),
242282
],
243-
Expr::Seq(left, right) => vec![
244-
text_token("sequence").into(),
245-
left.pest_to_element(),
246-
right.pest_to_element(),
247-
],
248-
Expr::Choice(left, right) => vec![
249-
text_token("choice").into(),
250-
left.pest_to_element(),
251-
right.pest_to_element(),
252-
],
283+
Expr::Seq(left, right) => {
284+
let mut elements = vec![text_token("sequence").into()];
285+
flatten(
286+
&mut elements,
287+
left,
288+
right,
289+
|operand: Box<_>| match *operand {
290+
Expr::Seq(child_left, child_right) => {
291+
ShouldFlatten::Yes(child_left, child_right)
292+
}
293+
_ => ShouldFlatten::No(operand),
294+
},
295+
);
296+
elements
297+
}
298+
Expr::Choice(left, right) => {
299+
let mut elements = vec![text_token("choice").into()];
300+
flatten(
301+
&mut elements,
302+
left,
303+
right,
304+
|operand: Box<_>| match *operand {
305+
Expr::Choice(child_left, child_right) => {
306+
ShouldFlatten::Yes(child_left, child_right)
307+
}
308+
_ => ShouldFlatten::No(operand),
309+
},
310+
);
311+
elements
312+
}
253313
Expr::Opt(expr) => {
254314
vec![text_token("optional").into(), expr.pest_to_element()]
255315
}
@@ -371,10 +431,8 @@ mod tests {
371431
type: normal,
372432
expression:
373433
(sequence
374-
(sequence
375-
(string exact "a")
376-
(string insensitive "b")
377-
)
434+
(string exact "a")
435+
(string insensitive "b")
378436
(string exact "c")
379437
)
380438
}
@@ -388,10 +446,8 @@ mod tests {
388446
type: normal,
389447
expression:
390448
(choice
391-
(choice
392-
(string exact "a")
393-
(string insensitive "b")
394-
)
449+
(string exact "a")
450+
(string insensitive "b")
395451
(string exact "c")
396452
)
397453
}
@@ -405,18 +461,14 @@ mod tests {
405461
type: normal,
406462
expression:
407463
(choice
408-
(choice
409-
(sequence
410-
(string exact "a")
411-
(string insensitive "b")
412-
)
413-
(sequence
414-
(sequence
415-
(string exact "c")
416-
(string insensitive "d")
417-
)
418-
(string exact "e")
419-
)
464+
(sequence
465+
(string exact "a")
466+
(string insensitive "b")
467+
)
468+
(sequence
469+
(string exact "c")
470+
(string insensitive "d")
471+
(string exact "e")
420472
)
421473
(sequence
422474
(string exact "f")
@@ -426,6 +478,48 @@ mod tests {
426478
}
427479
}"#
428480
)]
481+
#[case::mix_choice_grouping_1(
482+
r#"a = { "a" ~ (^"b" | "c") ~ ^"d" ~ ("e" | "f") ~ "g" }"#,
483+
r#"
484+
{
485+
a: {
486+
type: normal,
487+
expression:
488+
(sequence
489+
(string exact "a")
490+
(choice
491+
(string insensitive "b")
492+
(string exact "c")
493+
)
494+
(string insensitive "d")
495+
(choice
496+
(string exact "e")
497+
(string exact "f")
498+
)
499+
(string exact "g")
500+
)
501+
}
502+
}"#
503+
)]
504+
#[case::all_choice_grouping(
505+
r#"a = { "a" | (^"b" | "c") | ^"d" | ("e" | "f") | "g" }"#,
506+
r#"
507+
{
508+
a: {
509+
type: normal,
510+
expression:
511+
(choice
512+
(string exact "a")
513+
(string insensitive "b")
514+
(string exact "c")
515+
(string insensitive "d")
516+
(string exact "e")
517+
(string exact "f")
518+
(string exact "g")
519+
)
520+
}
521+
}"#
522+
)]
429523
#[case::optional(
430524
r#"a = { "a"? }"#,
431525
r#"

0 commit comments

Comments
 (0)