-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cucumber expressions AST and parser (#1)
Co-authored-by: Ilya Solovyiov <[email protected]>
- Loading branch information
Showing
8 changed files
with
2,719 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ name = "cucumber-expressions" | |
version = "0.1.0-dev" | ||
edition = "2021" | ||
rust-version = "1.56" | ||
description = "Cucumber Expressions AST and parser." | ||
license = "MIT OR Apache-2.0" | ||
authors = [ | ||
"Ilya Solovyiov <[email protected]>", | ||
|
@@ -17,3 +18,9 @@ keywords = ["cucumber", "expression", "expressions", "cucumber-expressions"] | |
include = ["/src/", "/LICENSE-*", "/README.md", "/CHANGELOG.md"] | ||
|
||
[dependencies] | ||
derive_more = { version = "0.99.16", features = ["as_ref", "deref", "deref_mut", "display", "error"], default_features = false } | ||
nom = "7.0" | ||
nom_locate = "4.0" | ||
|
||
# TODO: Remove once `derive_more` 0.99.17 is released. | ||
syn = "1.0.81" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
// Copyright (c) 2021 Brendan Molloy <[email protected]>, | ||
// Ilya Solovyiov <[email protected]>, | ||
// Kai Ren <[email protected]> | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
//! [Cucumber Expressions][1] [AST]. | ||
//! | ||
//! See details in the [grammar spec][0]. | ||
//! | ||
//! [0]: crate#grammar | ||
//! [1]: https://github.com/cucumber/cucumber-expressions#readme | ||
//! [AST]: https://en.wikipedia.org/wiki/Abstract_syntax_tree | ||
|
||
use derive_more::{AsRef, Deref, DerefMut}; | ||
use nom::{error::ErrorKind, Err, InputLength}; | ||
use nom_locate::LocatedSpan; | ||
|
||
use crate::parse; | ||
|
||
/// [`str`] along with its location information in the original input. | ||
pub type Spanned<'s> = LocatedSpan<&'s str>; | ||
|
||
/// Top-level `expression` defined in the [grammar spec][0]. | ||
/// | ||
/// See [`parse::expression()`] for the detailed grammar and examples. | ||
/// | ||
/// [0]: crate#grammar | ||
#[derive(AsRef, Clone, Debug, Deref, DerefMut, Eq, PartialEq)] | ||
pub struct Expression<Input>(pub Vec<SingleExpression<Input>>); | ||
|
||
impl<'s> TryFrom<&'s str> for Expression<Spanned<'s>> { | ||
type Error = parse::Error<Spanned<'s>>; | ||
|
||
fn try_from(value: &'s str) -> Result<Self, Self::Error> { | ||
parse::expression(Spanned::new(value)) | ||
.map_err(|e| match e { | ||
Err::Error(e) | Err::Failure(e) => e, | ||
Err::Incomplete(n) => parse::Error::Needed(n), | ||
}) | ||
.and_then(|(rest, parsed)| { | ||
rest.is_empty() | ||
.then(|| parsed) | ||
.ok_or(parse::Error::Other(rest, ErrorKind::Verify)) | ||
}) | ||
} | ||
} | ||
|
||
impl<'s> Expression<Spanned<'s>> { | ||
/// Parses the given `input` as an [`Expression`]. | ||
/// | ||
/// # Errors | ||
/// | ||
/// See [`parse::Error`] for details. | ||
pub fn parse<I: AsRef<str> + ?Sized>( | ||
input: &'s I, | ||
) -> Result<Self, parse::Error<Spanned<'s>>> { | ||
Self::try_from(input.as_ref()) | ||
} | ||
} | ||
|
||
/// `single-expression` defined in the [grammar spec][0], representing a single | ||
/// entry of an [`Expression`]. | ||
/// | ||
/// See [`parse::single_expression()`] for the detailed grammar and examples. | ||
/// | ||
/// [0]: crate#grammar | ||
#[derive(Clone, Debug, Eq, PartialEq)] | ||
pub enum SingleExpression<Input> { | ||
/// [`alternation`][0] expression. | ||
/// | ||
/// [0]: crate#grammar | ||
Alternation(Alternation<Input>), | ||
|
||
/// [`optional`][0] expression. | ||
/// | ||
/// [0]: crate#grammar | ||
Optional(Optional<Input>), | ||
|
||
/// [`parameter`][0] expression. | ||
/// | ||
/// [0]: crate#grammar | ||
Parameter(Parameter<Input>), | ||
|
||
/// Text without whitespaces. | ||
Text(Input), | ||
|
||
/// Whitespaces are treated as a special case to avoid placing every `text` | ||
/// character in a separate [AST] node, as described in the | ||
/// [grammar spec][0]. | ||
/// | ||
/// [0]: crate#grammar | ||
/// [AST]: https://en.wikipedia.org/wiki/Abstract_syntax_tree | ||
Whitespaces(Input), | ||
} | ||
|
||
/// `single-alternation` defined in the [grammar spec][0], representing a | ||
/// building block of an [`Alternation`]. | ||
/// | ||
/// [0]: crate#grammar | ||
pub type SingleAlternation<Input> = Vec<Alternative<Input>>; | ||
|
||
/// `alternation` defined in the [grammar spec][0], allowing to match one of | ||
/// [`SingleAlternation`]s. | ||
/// | ||
/// See [`parse::alternation()`] for the detailed grammar and examples. | ||
/// | ||
/// [0]: crate#grammar | ||
#[derive(AsRef, Clone, Debug, Deref, DerefMut, Eq, PartialEq)] | ||
pub struct Alternation<Input>(pub Vec<SingleAlternation<Input>>); | ||
|
||
impl<Input: InputLength> Alternation<Input> { | ||
/// Returns length of this [`Alternation`]'s span in the `Input`. | ||
pub(crate) fn span_len(&self) -> usize { | ||
self.0 | ||
.iter() | ||
.flatten() | ||
.map(|alt| match alt { | ||
Alternative::Text(t) => t.input_len(), | ||
Alternative::Optional(opt) => opt.input_len() + 2, | ||
}) | ||
.sum::<usize>() | ||
+ self.len() | ||
- 1 | ||
} | ||
|
||
/// Indicates whether any of [`SingleAlternation`]s consists only from | ||
/// [`Optional`]s. | ||
pub(crate) fn contains_only_optional(&self) -> bool { | ||
(**self).iter().any(|single_alt| { | ||
single_alt | ||
.iter() | ||
.all(|alt| matches!(alt, Alternative::Optional(_))) | ||
}) | ||
} | ||
} | ||
|
||
/// `alternative` defined in the [grammar spec][0]. | ||
/// | ||
/// See [`parse::alternative()`] for the detailed grammar and examples. | ||
/// | ||
/// [0]: crate#grammar | ||
#[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
pub enum Alternative<Input> { | ||
/// [`optional`][1] expression. | ||
/// | ||
/// [1]: crate#grammar | ||
Optional(Optional<Input>), | ||
|
||
/// Text. | ||
Text(Input), | ||
} | ||
|
||
/// `optional` defined in the [grammar spec][0], allowing to match an optional | ||
/// `Input`. | ||
/// | ||
/// See [`parse::optional()`] for the detailed grammar and examples. | ||
/// | ||
/// [0]: crate#grammar | ||
#[derive(AsRef, Clone, Copy, Debug, Deref, DerefMut, Eq, PartialEq)] | ||
pub struct Optional<Input>(pub Input); | ||
|
||
/// `parameter` defined in the [grammar spec][0], allowing to match some special | ||
/// `Input` described by a [`Parameter`] name. | ||
/// | ||
/// See [`parse::parameter()`] for the detailed grammar and examples. | ||
/// | ||
/// [0]: crate#grammar | ||
#[derive(AsRef, Clone, Copy, Debug, Deref, DerefMut, Eq, PartialEq)] | ||
pub struct Parameter<Input>(pub Input); |
Oops, something went wrong.