Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand cucumber expression AST into regex (#124) #154

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7228d03
Bootstrap basic parser
ilslv Nov 2, 2021
eea0c30
Start implementing custom errors
ilslv Nov 2, 2021
d681997
WIP [skip ci]
ilslv Nov 2, 2021
180b95d
More custom errors [skip ci]
ilslv Nov 3, 2021
bd7c640
More custom errors [skip ci]
ilslv Nov 3, 2021
9bae945
More custom errors [skip ci]
ilslv Nov 3, 2021
959cdd5
Add docs and spec for escaped0 [skip ci]
ilslv Nov 3, 2021
a774ffd
Separate parser combinators and document them
ilslv Nov 3, 2021
ef7b8cf
Document and add spec for parameter()
ilslv Nov 3, 2021
3403951
Document and add spec for parameter() [skip ci]
ilslv Nov 3, 2021
f7a2851
Document and add spec for parameter() [skip ci]
ilslv Nov 4, 2021
fbeb537
Document and add spec for optional() [skip ci]
ilslv Nov 4, 2021
f466e18
Document and add spec for optional() [skip ci]
ilslv Nov 4, 2021
5775823
Document and add spec for alternative() [skip ci]
ilslv Nov 4, 2021
b2dfabf
Document and add spec for alternation() and fix alternative() [skip ci]
ilslv Nov 4, 2021
3470bf9
Document and add spec for expression() [skip ci]
ilslv Nov 4, 2021
a883b52
Document and add spec for expression() [skip ci]
ilslv Nov 5, 2021
e633df6
Tidy up [skip ci]
ilslv Nov 5, 2021
c2042c1
Add README [skip ci]
ilslv Nov 5, 2021
290144b
Add CHANGELOG [skip ci]
ilslv Nov 5, 2021
85ae536
Corrections
ilslv Nov 5, 2021
2b11992
Merge branch 'main' into 124-cucumber-expression
ilslv Nov 5, 2021
12b35ac
Corrections
ilslv Nov 5, 2021
b341793
Bootstrap cucumber expression expansion into regex [skip ci]
ilslv Nov 5, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,5 @@ name = "wait"
harness = false

[workspace]
members = ["codegen"]
members = ["codegen", "expression"]
exclude = ["book/tests"]
6 changes: 3 additions & 3 deletions codegen/src/attribute.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) 2020 Brendan Molloy <[email protected]>,
// Ilya Solovyiov <[email protected]>,
// Kai Ren <[email protected]>
// Copyright (c) 2020-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
Expand Down
6 changes: 3 additions & 3 deletions codegen/src/derive.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) 2020 Brendan Molloy <[email protected]>,
// Ilya Solovyiov <[email protected]>,
// Kai Ren <[email protected]>
// Copyright (c) 2020-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
Expand Down
6 changes: 3 additions & 3 deletions codegen/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) 2020 Brendan Molloy <[email protected]>,
// Ilya Solovyiov <[email protected]>,
// Kai Ren <[email protected]>
// Copyright (c) 2020-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
Expand Down
23 changes: 23 additions & 0 deletions expression/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
`cucumber-expression` changelog
============================

All user visible changes to `cucumber-expression` crate will be documented in this file. This project uses [Semantic Versioning 2.0.0].




## [0.11.0] · 2021-??-??
[0.11.0]: /../../tree/v0.11.0/expression

[Milestone](/../../milestone/3)

### Added

- Cucumber expression AST and parser. ([#153])

[#153]: /../../pull/153




[Semantic Versioning 2.0.0]: https://semver.org
25 changes: 25 additions & 0 deletions expression/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "cucumber-expression"
version = "0.11.0-dev" # should be the same as main crate version
edition = "2021"
rust-version = "1.56"
description = "Cucumber expression parser."
license = "MIT OR Apache-2.0"
authors = [
"Ilya Solovyiov <[email protected]>",
"Kai Ren <[email protected]>",
]
documentation = "https://docs.rs/cucumber-expression"
homepage = "https://github.com/cucumber-rs/cucumber/tree/main/expression"
repository = "https://github.com/cucumber-rs/cucumber"
readme = "README.md"
categories = ["development-tools::testing", "parser-implementations"]
keywords = ["cucumber", "codegen", "macros"]
exclude = ["/tests/"]

[dependencies]
derive_more = { version = "0.99.16", features = ["as_ref", "deref", "deref_mut", "display", "error"], default_features = false }
either = "1.6.1"
nom = "7.0"
nom_locate = "4.0"
regex = "1.5.4"
29 changes: 29 additions & 0 deletions expression/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
`cucumber-expression` crate
========================

[![Documentation](https://docs.rs/cucumber-expression/badge.svg)](https://docs.rs/cucumber-expression)
[![CI](https://github.com/cucumber-rs/cucumber/workflows/CI/badge.svg?branch=master "CI")](https://github.com/cucumber-rs/cucumber/actions?query=workflow%3ACI+branch%3Amaster)
[![Rust 1.56+](https://img.shields.io/badge/rustc-1.56+-lightgray.svg "Rust 1.56+")](https://blog.rust-lang.org/2021/10/21/Rust-1.56.0.html)
[![Unsafe Forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance)

- [Changelog](https://github.com/cucumber-rs/cucumber/blob/main/expression/CHANGELOG.md)

Parser for [`cucumber expression`].




## License

This project is licensed under either of

* Apache License, Version 2.0 ([LICENSE-APACHE](https://github.com/cucumber-rs/cucumber/blob/main/LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
* MIT license ([LICENSE-MIT](https://github.com/cucumber-rs/cucumber/blob/main/LICENSE-MIT) or <http://opensource.org/licenses/MIT>)

at your option.




[`cucumber`]: https://docs.rs/cucumber
[`cucumber expression`]: https://github.com/cucumber/cucumber-expressions#readme
154 changes: 154 additions & 0 deletions expression/src/ast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// 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 expression][1] [AST][2] definitions.
//!
//! [1]: https://github.com/cucumber/cucumber-expressions#readme
//! [2]: https://en.wikipedia.org/wiki/Abstract_syntax_tree

use std::fmt::Display;

use derive_more::{AsRef, Deref, DerefMut};
use nom::{error::ErrorKind, AsChar, Err, InputIter, InputLength};
use nom_locate::LocatedSpan;
use regex::Regex;

use crate::{parse, Error};

/// A set of meta information about the location of a token.
pub type Spanned<'s> = LocatedSpan<&'s str>;

/// [Cucumber expression][1].
///
/// See [`parse::expression()`] for detailed syntax and examples.
///
/// [1]: https://github.com/cucumber/cucumber-expressions#readme
#[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 = 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) => Error::Needed(n),
})
.and_then(|(rest, parsed)| {
if rest.is_empty() {
Ok(parsed)
} else {
Err(Error::Other(rest, ErrorKind::Verify))
}
})
}
}

impl<'s> Expression<Spanned<'s>> {
/// Tries to `input` into [`Expression`].
///
/// # Errors
///
/// See [`Error`] for more details.
pub fn parse<I: AsRef<str> + ?Sized>(
input: &'s I,
) -> Result<Self, Error<Spanned<'s>>> {
Self::try_from(input.as_ref())
}
}

/// Building block of an [`Expression`].
///
/// See [`parse::single_expression()`] for detailed syntax and examples.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SingleExpression<Input> {
/// [`Alternation`].
Alternation(Alternation<Input>),

/// [`Optional`].
Optional(Optional<Input>),

/// [`Parameter`].
Parameter(Parameter<Input>),

/// Text without whitespaces.
Text(Input),

/// Whitespaces are treated as special case to avoid lookaheads and
/// lookbehinds described in the [`architecture`][1]. This allows parser to
/// have `O(n)` complexity.
///
/// [1]: https://bit.ly/3k8DfcW
Whitespace,
}

/// Allows to match one of [`SingleAlternation`]s.
///
/// See [`parse::alternation()`] for detailed syntax and examples.
#[derive(AsRef, Clone, Debug, Deref, DerefMut, Eq, PartialEq)]
pub struct Alternation<Input>(pub Vec<SingleAlternation<Input>>);

/// Building block an [`Alternation`].
pub type SingleAlternation<Input> = Vec<Alternative<Input>>;

impl<Input: InputLength> Alternation<Input> {
/// Returns length of capture from `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 one of [`SingleAlternation`]s consists only from
/// [`Optional`]s.
pub(crate) fn contains_only_optional(&self) -> bool {
for single_alt in &**self {
if single_alt
.iter()
.all(|alt| matches!(alt, Alternative::Optional(_)))
{
return true;
}
}
false
}
}

/// Building block of a [`SingleAlternation`].
///
/// See [`parse::alternative()`] for detailed syntax and examples.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Alternative<Input> {
/// [`Optional`].
Optional(Optional<Input>),

/// Text.
Text(Input),
}

/// Allows to match optional `Input`.
///
/// See [`parse::optional()`] for detailed syntax and examples.
#[derive(AsRef, Clone, Copy, Debug, Deref, DerefMut, Eq, PartialEq)]
pub struct Optional<Input>(pub Input);

/// Allows to match some special `Input` descried by a [`Parameter`] name.
///
/// See [`parse::parameter()`] for detailed syntax and examples.
#[derive(AsRef, Clone, Copy, Debug, Deref, DerefMut, Eq, PartialEq)]
pub struct Parameter<Input>(pub Input);
Loading