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}