cucumber_expressions/ast.rs
1// Copyright (c) 2021-2025 Brendan Molloy <brendan@bbqsrc.net>,
2// Ilya Solovyiov <ilya.solovyiov@gmail.com>,
3// Kai Ren <tyranron@gmail.com>
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11//! [Cucumber Expressions][1] [AST].
12//!
13//! See details in the [grammar spec][0].
14//!
15//! [0]: crate#grammar
16//! [1]: https://github.com/cucumber/cucumber-expressions#readme
17//! [AST]: https://en.wikipedia.org/wiki/Abstract_syntax_tree
18
19use derive_more::with_trait::{AsRef, Deref, DerefMut};
20use nom::{error::ErrorKind, Err, Input};
21use nom_locate::LocatedSpan;
22
23use crate::parse;
24
25/// [`str`] along with its location information in the original input.
26pub type Spanned<'s> = LocatedSpan<&'s str>;
27
28/// Top-level `expression` defined in the [grammar spec][0].
29///
30/// See [`parse::expression()`] for the detailed grammar and examples.
31///
32/// [0]: crate#grammar
33#[derive(AsRef, Clone, Debug, Deref, DerefMut, Eq, PartialEq)]
34pub struct Expression<Input>(pub Vec<SingleExpression<Input>>);
35
36impl<'s> TryFrom<&'s str> for Expression<Spanned<'s>> {
37 type Error = parse::Error<Spanned<'s>>;
38
39 fn try_from(value: &'s str) -> Result<Self, Self::Error> {
40 parse::expression(Spanned::new(value))
41 .map_err(|e| match e {
42 Err::Error(e) | Err::Failure(e) => e,
43 Err::Incomplete(n) => parse::Error::Needed(n),
44 })
45 .and_then(|(rest, parsed)| {
46 rest.is_empty()
47 .then_some(parsed)
48 .ok_or(parse::Error::Other(rest, ErrorKind::Verify))
49 })
50 }
51}
52
53impl<'s> Expression<Spanned<'s>> {
54 /// Parses the given `input` as an [`Expression`].
55 ///
56 /// # Errors
57 ///
58 /// See [`parse::Error`] for details.
59 pub fn parse<I: AsRef<str> + ?Sized>(
60 input: &'s I,
61 ) -> Result<Self, parse::Error<Spanned<'s>>> {
62 Self::try_from(input.as_ref())
63 }
64}
65
66/// `single-expression` defined in the [grammar spec][0], representing a single
67/// entry of an [`Expression`].
68///
69/// See [`parse::single_expression()`] for the detailed grammar and examples.
70///
71/// [0]: crate#grammar
72#[derive(Clone, Debug, Eq, PartialEq)]
73pub enum SingleExpression<Input> {
74 /// [`alternation`][0] expression.
75 ///
76 /// [0]: crate#grammar
77 Alternation(Alternation<Input>),
78
79 /// [`optional`][0] expression.
80 ///
81 /// [0]: crate#grammar
82 Optional(Optional<Input>),
83
84 /// [`parameter`][0] expression.
85 ///
86 /// [0]: crate#grammar
87 Parameter(Parameter<Input>),
88
89 /// [`text-without-whitespace+`][0] expression.
90 ///
91 /// [0]: crate#grammar
92 Text(Input),
93
94 /// [`whitespace+`][0] expression.
95 ///
96 /// [0]: crate#grammar
97 Whitespaces(Input),
98}
99
100/// `single-alternation` defined in the [grammar spec][0], representing a
101/// building block of an [`Alternation`].
102///
103/// [0]: crate#grammar
104pub type SingleAlternation<Input> = Vec<Alternative<Input>>;
105
106/// `alternation` defined in the [grammar spec][0], allowing to match one of
107/// [`SingleAlternation`]s.
108///
109/// See [`parse::alternation()`] for the detailed grammar and examples.
110///
111/// [0]: crate#grammar
112#[derive(AsRef, Clone, Debug, Deref, DerefMut, Eq, PartialEq)]
113pub struct Alternation<Input>(pub Vec<SingleAlternation<Input>>);
114
115impl<I: Input> Alternation<I> {
116 /// Returns length of this [`Alternation`]'s span in the `Input`.
117 pub(crate) fn span_len(&self) -> usize {
118 self.0
119 .iter()
120 .flatten()
121 .map(|alt| match alt {
122 Alternative::Text(t) => t.input_len(),
123 Alternative::Optional(opt) => opt.input_len() + 2,
124 })
125 .sum::<usize>()
126 + self.len()
127 - 1
128 }
129
130 /// Indicates whether any of [`SingleAlternation`]s consists only from
131 /// [`Optional`]s.
132 pub(crate) fn contains_only_optional(&self) -> bool {
133 (**self).iter().any(|single_alt| {
134 single_alt
135 .iter()
136 .all(|alt| matches!(alt, Alternative::Optional(_)))
137 })
138 }
139}
140
141/// `alternative` defined in the [grammar spec][0].
142///
143/// See [`parse::alternative()`] for the detailed grammar and examples.
144///
145/// [0]: crate#grammar
146#[derive(Clone, Copy, Debug, Eq, PartialEq)]
147pub enum Alternative<Input> {
148 /// [`optional`][1] expression.
149 ///
150 /// [1]: crate#grammar
151 Optional(Optional<Input>),
152
153 /// Text.
154 Text(Input),
155}
156
157/// `optional` defined in the [grammar spec][0], allowing to match an optional
158/// `Input`.
159///
160/// See [`parse::optional()`] for the detailed grammar and examples.
161///
162/// [0]: crate#grammar
163#[derive(AsRef, Clone, Copy, Debug, Deref, DerefMut, Eq, PartialEq)]
164pub struct Optional<Input>(pub Input);
165
166/// `parameter` defined in the [grammar spec][0], allowing to match some special
167/// `Input` described by a [`Parameter`] name.
168///
169/// See [`parse::parameter()`] for the detailed grammar and examples.
170///
171/// [0]: crate#grammar
172#[derive(AsRef, Clone, Copy, Debug, Deref, DerefMut, Eq, PartialEq)]
173pub struct Parameter<Input> {
174 /// Inner `Input`.
175 #[deref]
176 #[deref_mut]
177 pub input: Input,
178
179 /// Unique ID of this [`Parameter`] in the parsed [`Expression`].
180 #[as_ref(ignore)]
181 pub id: usize,
182}