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}