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::{Err, Input, error::ErrorKind};
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 let (rest, parsed) =
41 parse::expression(Spanned::new(value)).map_err(|e| match e {
42 Err::Error(e) | Err::Failure(e) => e,
43 Err::Incomplete(n) => parse::Error::Needed(n),
44 })?;
45 rest.is_empty()
46 .then_some(parsed)
47 .ok_or(parse::Error::Other(rest, ErrorKind::Verify))
48 }
49}
50
51impl<'s> Expression<Spanned<'s>> {
52 /// Parses the given `input` as an [`Expression`].
53 ///
54 /// # Errors
55 ///
56 /// See [`parse::Error`] for details.
57 pub fn parse<I: AsRef<str> + ?Sized>(
58 input: &'s I,
59 ) -> Result<Self, parse::Error<Spanned<'s>>> {
60 Self::try_from(input.as_ref())
61 }
62}
63
64/// `single-expression` defined in the [grammar spec][0], representing a single
65/// entry of an [`Expression`].
66///
67/// See [`parse::single_expression()`] for the detailed grammar and examples.
68///
69/// [0]: crate#grammar
70#[derive(Clone, Debug, Eq, PartialEq)]
71pub enum SingleExpression<Input> {
72 /// [`alternation`][0] expression.
73 ///
74 /// [0]: crate#grammar
75 Alternation(Alternation<Input>),
76
77 /// [`optional`][0] expression.
78 ///
79 /// [0]: crate#grammar
80 Optional(Optional<Input>),
81
82 /// [`parameter`][0] expression.
83 ///
84 /// [0]: crate#grammar
85 Parameter(Parameter<Input>),
86
87 /// [`text-without-whitespace+`][0] expression.
88 ///
89 /// [0]: crate#grammar
90 Text(Input),
91
92 /// [`whitespace+`][0] expression.
93 ///
94 /// [0]: crate#grammar
95 Whitespaces(Input),
96}
97
98/// `single-alternation` defined in the [grammar spec][0], representing a
99/// building block of an [`Alternation`].
100///
101/// [0]: crate#grammar
102pub type SingleAlternation<Input> = Vec<Alternative<Input>>;
103
104/// `alternation` defined in the [grammar spec][0], allowing to match one of
105/// [`SingleAlternation`]s.
106///
107/// See [`parse::alternation()`] for the detailed grammar and examples.
108///
109/// [0]: crate#grammar
110#[derive(AsRef, Clone, Debug, Deref, DerefMut, Eq, PartialEq)]
111pub struct Alternation<Input>(pub Vec<SingleAlternation<Input>>);
112
113impl<I: Input> Alternation<I> {
114 /// Returns length of this [`Alternation`]'s span in the `Input`.
115 pub(crate) fn span_len(&self) -> usize {
116 self.0
117 .iter()
118 .flatten()
119 .map(|alt| match alt {
120 Alternative::Text(t) => t.input_len(),
121 Alternative::Optional(opt) => opt.input_len() + 2,
122 })
123 .sum::<usize>()
124 + self.len()
125 - 1
126 }
127
128 /// Indicates whether any of [`SingleAlternation`]s consists only from
129 /// [`Optional`]s.
130 pub(crate) fn contains_only_optional(&self) -> bool {
131 (**self).iter().any(|single_alt| {
132 single_alt.iter().all(|alt| matches!(alt, Alternative::Optional(_)))
133 })
134 }
135}
136
137/// `alternative` defined in the [grammar spec][0].
138///
139/// See [`parse::alternative()`] for the detailed grammar and examples.
140///
141/// [0]: crate#grammar
142#[derive(Clone, Copy, Debug, Eq, PartialEq)]
143pub enum Alternative<Input> {
144 /// [`optional`][1] expression.
145 ///
146 /// [1]: crate#grammar
147 Optional(Optional<Input>),
148
149 /// Text.
150 Text(Input),
151}
152
153/// `optional` defined in the [grammar spec][0], allowing to match an optional
154/// `Input`.
155///
156/// See [`parse::optional()`] for the detailed grammar and examples.
157///
158/// [0]: crate#grammar
159#[derive(AsRef, Clone, Copy, Debug, Deref, DerefMut, Eq, PartialEq)]
160pub struct Optional<Input>(pub Input);
161
162/// `parameter` defined in the [grammar spec][0], allowing to match some special
163/// `Input` described by a [`Parameter`] name.
164///
165/// See [`parse::parameter()`] for the detailed grammar and examples.
166///
167/// [0]: crate#grammar
168#[derive(AsRef, Clone, Copy, Debug, Deref, DerefMut, Eq, PartialEq)]
169pub struct Parameter<Input> {
170 /// Inner `Input`.
171 #[deref]
172 #[deref_mut]
173 pub input: Input,
174
175 /// Unique ID of this [`Parameter`] in the parsed [`Expression`].
176 #[as_ref(ignore)]
177 pub id: usize,
178}