Open ilslv opened 2 years ago
How exciting to have a Rust port! ❤️
This looks good to me. Any feedback @mpkorstanje?
@ilslv would you be interested in donating your parser implementation to this repo (under a rust
directory)? That way we can more easily share the same test suite, align the release cycle and keep everything in one place. We'd also set up a GitHub Action to release it as a crate whenever we make a release.
@aslakhellesoy my implementation may be incompatible with test suit in this repository. As I could see tests under parser
directory parse nested optionals just find and error only on expansion to the regex in matching
directory. My implementation is 1-step that matches only correct Cucumber Expressions
.
Do you think it would be possible to make your implementation compatible with the official test suite? If not, do you think there is something we could change in the official implementation's test suite (or implementation) in order to make it easier for your implementation to be compatible?
@aslakhellesoy you added the EBNF phrase at https://github.com/cucumber/cucumber-expressions/commit/0875a402cc7efa2d01c1031c96cd2cc448f94b78#diff-8f6366fd8e7a5fa30f7f05879999195b7cf5fa1e3d157822eb37f0e91d226cfdR7. Note that the code highlighting should still use EBNF because that makes the pseudo-grammar somewhat readable but the grammar itself is not EBNF.
@aslakhellesoy
it would be possible to make your implementation compatible with the official test suite
Yes, but it would introduce some performance overhead, which we would like to avoid.
do you think there is something we could change in the official implementation's test suite (or implementation) in order to make it easier for your implementation to be compatible
It would be possible, but I don't think it costs the effort. As I mentioned only subset of the official test suite is incompatible. The most important tests for final matching and regex transformation are ok. I even think that only this test case would be incompatible. This happens because other implementations are using current ARCHITECTURE.md
approach of generating AST which is wider than Cucumber Expression
and error only later. My implementation generates only right Cucumber Expression
from the start.
Mainly because of those difference I would hold on merging Rust implementation here. But you can still reference it using submodules or just mention it in the README.md
.
@ilslv the grammar as written is context-sensitive and not in EBNF form (EBNF also can't express context sensitive grammars).
Provided grammar describes superset of Cucumber Expressions and I don't really see reasons to do it.
Users of Cucumbers are not typically familiar with the concepts involved in parsers or grammars. As such the typical error messages from a parser are incomprehensible. By accepting a super set of all cucumber expressions we can more clearly provide error messages for typical mistakes such as using optional parameter types, empty optionals, etc, etc by pointing the entire AST node that is in the wrong place rather then a single character.
text = (- text-to-escape) | ('\', text-to-escape)
This puts each character into a single AST node. This isn't great for languages with garbage collection and makes debugging much harder.
alternation = single-alternation, ('/', single_alternation)+
It is now longer obvious that alternation
is bounded by whitespace, parameter types or start/end of expression which means that we are losing some of the intention.
parameter = '{', name*, '}'
name = (- name_to_escape) | ('\', name-to-escape)
name-to-escape = '{' | '}' | '(' | '/' | '\'
The *-to-escape
is pretty neat. This is definitively worth including.
Now I've only given this a cursory review but other then the issues mentioned above I don't see any immediate technical problem with the provided EBNF. Might be worth updating the documentation somewhat.
@mpkorstanje
typical error messages from a parser are incomprehensible
Yes, but only if you are using some sort of parser-generator. Rust implementation has exactly the same errors, which can point at troublesome characters or range of characters. All this while preserving minimal overhead (1 dynamic allocation on every nested expression and even it can be avoided, I just don't think it costs the effort for now).
This puts each character into a single AST node. This isn't great for languages with garbage collection and makes debugging much harder.
There is no need to translate provided grammar 1-to-1. I propose adding it only to provide some formal specification for the Cucumber Expressions
language.
My proposal is following: let's add formal EBNF specification for the language while preserving the old one and add documentation that it only exists only for implementation suggestion. This would allow Rust implementation (or any other one) to strictly follow it, because for now there is no way for me to reason that Rust implementation is correct, as there is no formal specification for Cucumber Expressions
.
If you are on board with that idea, I will happily prepare PR documenting all this.
No. If we only have a formal specification without a closely matching implementation and test set in this repository we are only going to see uncontrolled drift.
Though I don't believe a formal specification has to be in EBNF. As long as the context sensitive form can be procedurally be written to EBNF it can serve as a formal specification for your purposes.
@mpkorstanje
No. If we only have a formal specification without a closely matching implementation and test set in this repository we are only going to see uncontrolled drift.
It looks like there is a misunderstanding, as I never proposed to remove anything, only add a formal specification. Let me sum up everything discussed in this issue.
I would like to see formal specification with some context-free grammar. Original issue shows the EBNF example.
It is now longer obvious that
alternation
is bounded by whitespace, parameter types or start/end of expression which means that we are losing some of the intention.
It's not obvious, but alternation
is still bounded by whitespace, because of following rules
text-in-alternative = (- alternative-to-escape) | ('\', alternative-to-escape)
alternative-to-escape = ' ' | '(' | '{' | '/' | '\'
You have to escape whitespace for it to match.
I agree that it's hard to spot. That's why I'm proposing only to add formal specification, leaving existing one as one of implementation possibilities.
For now there as there is no formal specification, only some non-existing grammar, I can't reason why Rust implementation is correct.
ARCHITECTURE.md
?ARCHITECTURE.md
doesn't give full picture.
There is no way to know how escaping works in different AST elements only from it. I had to dig throughout all the test cases to be sure, that I didn't miss anything. We should provide formal specification to reason about implementation correctness. ARCHITECTURE.md
forces to use suboptimal implementation.
It encourages to generate middleware AST that is wider, than Cucumber Expression
and that is using context-sensitive grammar with lookbehinds and lookaheads. As shown by the Rust implementation there is a way to avoid both of those overheads by generating valid Cucumber Expression
from the start, basing on context-free grammar. All that while still preserving friendly error messages. @ilslv could you please send a pull request with your proposed changes?
@aslakhellesoy #44
There is no misunderstanding.
From the discussion in https://github.com/cucumber-rs/cucumber-expressions/pull/1 I see that you have asked to implement the parser in a way that closely matches the specification. You are now looking to update the specification to match your implementation.
However the proposed grammar is does not accept the superset of all Cucumber valid expressions. As such we do not have a reference implementation or test set that accurately describes it. Additionally if the EBNF can not be derived from the context-sensitive grammar it can not be said to be correct. If it can be derived from the context sensitive grammar it need not be explicitly included.
Currently the EBNF can't derived from the context sensitive grammar because it is not well specified. So I'm specifically requesting a fix to the context-sensitive grammar rather then an addition of an otherwise untested, unverified (in this repo) piece of specification.
@mpkorstanje
update the specification to match your implementation
No, I'm trying to introduce formal specification as there is none yet.
does not accept the superset of all Cucumber valid expressions
Yes, thats the point to describe Cucumber Expressions
through it's own formal specification, rather then relying on superset specification in non-existent grammar with number of notes on post-validation.
EBNF can not be derived from the context-sensitive
Yes, because Cucumber Expressions
specification doesn't need to be context-sensitive.
it can not be said to be correct
There is no notion of correctness right now, as no formal specification exists.
fix to the context-sensitive grammar
My grammar is context-free. Can you please provide concrete examples, where my attempt at formalisation fails?
@ilslv @mpkorstanje it seems to me that the discussion becomes a little bit overheated. Let's sum it up a little with facts and coclusions, to push it into a constructive direction.
The current grammar described in ARCHITECTURE.md
:
Cucumber Expressions
followed by post-validation rules for the sake of better spanning info and error messages in the current parser implementations.This imposes problems for alternative implementations:
Cucumber Expressions
, but in fact, having no ability to reason whether it's the right implementation or not (which is bad for future proofing and compatibility).We'd really like to have implementation freedom, while preserve the ability to formally check correctness. For this purpose @ilslv proposed to add a "formal spec" grammar, which is context-free and implementation-free, but describes the actual Cucumber Expressions
precise enough, so any implementation can refer to its as the source of truth.
But @mpkorstanje is against having a formal spec without an ability to verify whether implementations do follow it, as it will introduce an obvious drift between the actual spec and its implementations.
Please, correct me if I'm wrong.
So, from my point of view:
It worths, of course, to provide a precise "formal spec" grammar proposed by @ilslv. But we should also provide a generic set of tests for it which would be suitable for any implementation.
With this, we can satisfy @mpkorstanje point about "actual correctness", while also provide third-party alternative implementations (like ours) with an obvious way to verify its correctness. Having this tests suite will allow to strip out major part of existing tests, leaving only the implementation-specific ones (like spanning/errors handling, or language-dependent stuff).
Is there any arguments against this way? Or is there any issue that I do miss?
@tyranron thanks for pulling this discussion together and @ilslv thanks for all your contributions so far. I share Aslak's excitement at seeing a Cucumber in Rust!
If people would like to talk about this in a more real-time context, we can be found on the https://cucumber.io/community#slack in the #committers channel.
We also have a regular Zoom call on Thursdays at 7am PST (4pm CET) that you'd be more than welcome to join us for. If you hop into the Slack we can share the Zoom link in there or add you to the invite.
@mattwynne thanks for the invitation! But I'd like to have discussion here manly for 2 reasons:
We also have a regular Zoom call on Thursdays at 7am PST (4pm CET) that you'd be more than welcome to join us for.
If issue won't be resolved by Thursday I thinks I will be able to join you on the call. They have tendency to be more constructive, as time is limited 😅
@ilslv no, my request is very simple:
To fix the context sensitive grammar in such a way that a the EBNF can be logically derived from it.
I'm not opposed to replacing the context sensitive grammar with an EBNF if the test suite, implementations in this repository follow suit. However that is a significant commitment.
As such fixing the context sensitive grammar in such a way that a the EBNF can be logically derived from it should be a reasonable compromise.
Third party implementations will be able to use a "formal" grammar while this project retains the canonical grammar with closely matching implementation and tests.
In the long term we can then look at improving the test suite, grammar and implementations.
@mpkorstanje
To fix the context sensitive grammar in such a way that a the EBNF can be logically derived from it.
I don't understand what EBNF can be logically derived from it
actually means.
We have 2 grammars:
ARCHITECRURE.md
Cucucumber Expressions
Cucucumber Expressions
specWhat do you mean by fix the context sensitive grammar
?
Do you want final grammar to be context-sensitive? With EBNF it couldn't be achieved and it leads to unnecessary performance overhead for implementation to be compliant.
Do you still want resulting grammar to represent superset of Cucumber Expression
? I'm strongly against it, as it won't let us say that particular implementation is correct
. And I don't think this will be abled to achieved with EBNF too. And this also leads to performance overhead I described earlier.
The only other complaint I saw was regarding alternation
not been obviously bounded by whitespace and I already responded that actually it is bounded. And there is no other way to make it more clear to my knowledge.
I still don't understand what do you actually want to fix
.
I'm looking to retain a close similarity between the grammar, tests and implementation in this repo while also make it possible for you to derive a grammar in EBNF form without asking you to also change the implementations and tests in this repo.
Now for our purposes we can say that grammars are equivalent if they accept the same language. So if the language accepted by cucumber expressions is context free you should be able to derive the context free grammar from the context sensitive grammar in a structured manner (or be able to proof equivalency in another way).
Currently I don't believe this is possible because the context sensitive grammar contains several errors and ambiguities. This inability to derive a context free grammar from the context sensitive one is what I'm asking you to fix. Additionally the the fixed grammar should still be context sensitive and accept the superset if Cucumber expressions with optional parameter types, empty optionals, ect.
So once fixed we will have one context sensitive grammar that matches our implementation and tests from which you can logically derive a context free one.
@mpkorstanje
derive
context-free language form context-sensitive in general case. All in all we have different intentions. You just want to fix existing superset grammar and by doing it to force implementations to be suboptimal to be somehow correct
(while still this word can't be applied to language without spec).
While I want to introduce formal specification for Cucumber Expressions language to be able guarantee correctness not only for Rust implementations, but also to every other implementation.
I don't think it is impossible. This isn't the general case of rewriting any arbitrary grammar but rather this the case of rewriting this specific grammar. Assuming your EBNF is correct we know context free form exists.
But if you do think it is impossible then the only mutually acceptable alternative is to rewrite the grammar, tests, and existing implementations.
@mpkorstanje @ilslv
So the least resourceful, but good enough, solution would be to have a programmable way for mapping "formal spec" AST into the current one, or vice versa, or having even another reduced representation, so both "formal spec" AST and the current AST may be translated into for correct comparing. This will allow us to fully reuse exisitng implementation and tests.
@tyranron
programmable way for mapping "formal spec" AST into the current one, or vice versa
Simply mapping from formal spec into superset grammar won't give us much info. It's almost the same as having grammar that accepts only empty string and will always map in superset. Mapping from superset grammar into formal spec isn't possible, as first one is superset of another. Post-validation is key problem here.
The only thing, that will bring us to being somewhat correct is to feed all implementations some input from manual tests and fuzzers and compare resulting Cucumber Expressions
AST or errors produced while parsing.
Another problem is that tests inside this repo aren't suitable. parser folder contains expansion of superset AST as seen by this testcase. I don't also know if current implementations can produce exact Cucumber Expressions
AST.
@mattwynne
I also noticed one interesting implementation detail. Whitespaces are treated as a some sort of special case
---
expression: three hungry/blind mice
expected_ast:
type: EXPRESSION_NODE
start: 0
end: 23
nodes:
- type: TEXT_NODE
start: 0
end: 5
token: three
- type: TEXT_NODE
start: 5
end: 6
token: " "
- type: ALTERNATION_NODE
start: 6
end: 18
nodes:
- type: ALTERNATIVE_NODE
start: 6
end: 12
nodes:
- type: TEXT_NODE
start: 6
end: 12
token: hungry
- type: ALTERNATIVE_NODE
start: 13
end: 18
nodes:
- type: TEXT_NODE
start: 13
end: 18
token: blind
- type: TEXT_NODE
start: 18
end: 19
token: " "
- type: TEXT_NODE
start: 19
end: 23
token: mice
Rust implementation does completely the same thing. So It looks like existing implementation already work around context-sensitive grammar by being context-free. This can mean that we no longer can call them correct
. In that case bringing context-free grammar would only make it closer to existing implementations.
Rust implementation does completely the same thing. So It looks like existing implementation already work around context-sensitive grammar by being context-free.
I'm not sure about the point you are making.
As we've already established, we can assume the language accepted by the grammar to be context-free. The grammar however is written in context sensitive form for clarity. I'm open to updating the grammar provided the implementations match the grammar.
E.g this would have to change:
@mpkorstanje
I'm not sure about the point you are making.
Your previous argument was about not being able to change the grammar into being context-free, as current implementations are based. I'm, on the other hand, saying that argument doesn't make sense to me, if current implementations already don't follow provided grammar.
I've finally looked into the implementation and from I can see ether grammar is more broken, than I thought, or implementation is wrong.
Here we can see call into parseTokensUntil
which should be doing alternative* + ( '/' + alternative* )+ + (?=right-boundary)
parsing part. Let's peek inside
As I understand algoritm is following:
lookingAtAny(tokens, current, endTokens)
Result result = parseToken(expression, parsers, tokens, current);
If so, this is not how lookahead defined at (?=right-boundary)
should work. Lookahead checks after each parsed character. This is what allows us to something like this in regex (stopping greedy parser using lookahead after the first character).
So, if I understand algorithm correctly, we have 3 possible cases:
(?=right-boundary)
doesn't mean lookahead and means something like apply previous parser and see if next element is right-boundary while not consuming it
Which is it?
I'd say #3 is the most accurate, but again, I don't see how this changes any of the conclusions made so far. If we change the grammar, we change the implementations and tests to match.
@mpkorstanje this is significant, because real lookahead requires context-sensitive grammar, while implemented one doesn't. I believe that it's possible to formalise pretty easy-to-understand context-free grammar which fully compliant with implementation.
So to avoid drift, make it easy to verify correctness and provide a reference implementation the grammar and implementations in this repository should closely reflect each other.
So when for example we change:
alternation = (?<=left-boundary), alternative*, ( "/" + alternative* )+, (?=right-boundary)
left-boundary = whitespace | "}" | "^"
right-boundary = whitespace | "{" | "$"
alternative = optional | parameter | text
To:
alternation = single-alternation, ('/', single_alternation)+
single-alternation = ((text-in-alternative+, optional*)
| (optional+, text-in-alternative+))+
text-in-alternative = (- alternative-to-escape) | ('\', alternative-to-escape)
alternative-to-escape = ' ' | '(' | '{' | '/' | '\'
That change should be reflected in the implementation of the alternationParser
. So rather then checking the context before and after parsing a subtree as it does now it should try to parse a single-alternation
followed by ('/', single_alternation)+
.
@mpkorstanje
That change should be reflected in the implementation of the alternationParser.
I didn't mean old proposal when said I believe that it's possible to formalise pretty easy-to-understand context-free grammar which fully compliant with implementation.
It will be different from initial approach
But to introduce new context-free grammar (which I believe is now really possible, when I understand what (?=)
means in current grammar) we need to fix some problems.
First question of mine is about special handling of ^
and $
in alternations. The is no tests for it and Rust implementation actually ignores this case.
alternation = (?<=left-boundary), alternative*, ( "/" + alternative* )+, (?=right-boundary)
left-boundary = whitespace | "}" | "^"
right-boundary = whitespace | "{" | "$"
alternative = optional | parameter | text
text = whitespace | ")" | "}" | .
Is actually equivalent to
alternation = (?<=left-boundary), alternative*, ( "/" + alternative* )+, (?=right-boundary)
left-boundary = whitespace | "}"
right-boundary = whitespace | "{"
alternative = optional | parameter | text
text = whitespace | ")" | "}" | .
text
in alternative
will always consume ^
or $
before (?=right-boundary)
or (?<=left-boundary)
check: example 1 and 2. Or did I miss something?
The ^
and $
are start and end of line. You can tell by looking at the part of the implementation of alternationParser
that checks the boundaries. The boundary tokens are listed there.
I'm not sure how they ended up qouted though that's clearly wrong.
@mpkorstanje thanks for clarifying it.
cucumber-expression = ( alternation | optional | parameter | text )*
alternation = (?<=left-boundary), alternative*, ( "/" + alternative* )+, (?=right-boundary)
left-boundary = whitespace | "}" | ^
right-boundary = whitespace | "{" | $
alternative = optional | parameter | text
optional = "(", option*, ")"
option = optional | parameter | text
parameter = "{", name*, "}"
name = whitespace | .
text = whitespace | ")" | "}" | .
As alternative
may contain parameter
and cucumber-expression
tries to parse alternation
first, input like {word}a/b
should be theoretically parsed into following AST
- type: ALTERNATION_NODE
- type: ALTERNATIVE_NODE
- type: PARAMETER_NODE
- token: word
- type: TEXT_NODE
- token: a
- type: ALTERNATIVE_NODE
- type: TEXT_NODE
- token: b
But following test shows another picture. For some reason parameter
is treated as separate node, not as a part of alternation
. I don't quite understand, why this is the case?
Mmh. I think that might be a development artifact.
For example {a}/{b}
could be a valid cucumber expression. However in practice that makes it cumbersome to use stemming like {n}st/nd/rd/th
because the parameter would have to he repeated in every alternative.
So an alternative never includes a parameter.
So we can change
alternative = optional | parameter | text
To
alternative = optional | text
@mpkorstanje ok, now we have the following grammar
cucumber-expression = ( alternation | optional | parameter | text )*
alternation = (?<=left-boundary), alternative*, ( "/" + alternative* )+, (?=right-boundary)
left-boundary = whitespace | "}" | ^
right-boundary = whitespace | "{" | $
alternative = optional | text
optional = "(", option*, ")"
option = optional | parameter | text
parameter = "{", name*, "}"
name = whitespace | .
text = whitespace | ")" | "}" | .
which is equivalent to
cucumber-expression = ( alternation | optional | parameter | text )*
alternation = (?<=left-boundary), alternative*, ( "/" + alternative* )+
left-boundary = whitespace | "}" | ^
alternative = optional | text
optional = "(", option*, ")"
option = optional | parameter | text
parameter = "{", name*, "}"
name = whitespace | .
text = whitespace | ")" | "}" | .
(?=right-boundary)
is redundant, because text
inside alternative
will consume every character except whitespace | "{" | $
, so this check is always true.
Can you update the implementation and grammar to reflect that?
@mpkorstanje I'm not too comfortable with any languages other implementations are using. But more important thing is that we don't have to update implementation right the way, as provided grammar is equivalent to implemented one.
@mpkorstanje
Even more
cucumber-expression = ( alternation | optional | parameter | text )*
alternation = (?<=left-boundary), alternative*, ( "/" + alternative* )+
left-boundary = whitespace | "}" | ^
alternative = optional | text
optional = "(", option*, ")"
option = optional | parameter | text
parameter = "{", name*, "}"
name = whitespace | .
text = whitespace | ")" | "}" | .
is equivalent to
cucumber-expression = ( alternation | optional | parameter | text )*
alternation = alternative*, ( "/" + alternative* )+
alternative = optional | text
optional = "(", option*, ")"
option = optional | parameter | text
parameter = "{", name*, "}"
name = whitespace | .
text = whitespace | ")" | "}" | .
This happens because alternation
is greedy and comes first in cucumber-expression
. So previous characters in the start of alternation
parsing are always ^
or something previously parsed by optional | parameter | text
which wasn't accepted in alternation
. This happens to be only exactly whitespace | "}" | ^
so (?<=left-boundary)
is redundant too, as always returns true
.
Final grammar which is equivalent to the existing one is just relaxed version of my original proposal. By relaxed
I mean that it simply accepts invalid Cucumber Expressions
too, while mine doesn't.
Cheers! We can use this info to update the grammar and implementations sometime in the future then.
Fixing the grammar / parser to use the (- X-to-escape) | ('\', X-to-escape)
doesn't look so hard either.
How do you "span" the invalid subtrees in error messages without a fully parsed AST?
Example:
Hello (optional {parameter}) world
^--------^
The parser would not fail at the first {
, how do you know to span the pointers to the next element and not also include other syntax errors like Hello (optional {parameter {} ) world
?
@mpkorstanje
Hello (optional {parameter}) world
^---------^
This one is pretty easy to tackle: If I'm started to parse optional
there can't be unescaped {
. Once I encounter one, I try to parse parameter
. If successful, that means I have an entire parameter
inside of optional. Otherwise this is an error too, just another one: unescaped reserved character.
So in case of Hello (optional {parameter {} ) world
this will error about parameter
containing {
, because we've started to parse parameter
inside an optional
, which errored.
Also helpful idea of splitting errors into recoverable once, and irreconcilable. As the name suggests, recoverable allows us to continue parsing with another parser. For example in abc
we first try to parse alternative
, recoverably fail, then with option
and so on, until encounter text
. While in case of abc/
we'll try to parse alternative
, which will encounter /
, after that we know, that all subsequent errors aren't recoverable, as only alternative
s can have unescaped /
inside. So having empty alternation is immediate irrecoverable error.
Can you show an example of that?
@mpkorstanje Rust syntax is quite alien-looking, but here you go
Mmh. This error message doesn't look quite right:
I also don't see where you would construct the ^---------^
span.
@mpkorstanje
Mmh. This error message doesn't look quite right
I've united all IlligalIn..
into UnescapedReservedCharacter
as final suggestion of escaping this character is the same
I also don't see where you would construct the ^---------^ span.
Actually I don't do it. I allow users to supply string wrapped in a LocatedSpan
which will track where the captured slice is coming from. In this case exact error message can be constructed, but there is possibility to skip this tracking by supplying raw string.
@mpkorstanje
Cheers! We can use this info to update the grammar
So, do you want update grammar to exact Cucumber Expressions
or still use superset AST
?
So far I don't see how I can generate the error messages with the ^---------^
span without a superset.
@mpkorstanje I've already described solution with LocatedSpan.
Codesample:
let err = Expression::regex("I have {int cucumbers in my belly").expect_err();
match err {
Error::UnescapedReservedCharacter(e) => {
assert_eq!(*e, "{");
assert_eq!(
repeat(' ')
.take(e.offset())
.chain(once('^'))
.chain(repeat('-').take(e.len()))
.chain(once('^')),
" ^-^".chars(),
)
}
e => panic!("wrong err: {}", e),
}
let err = Expression::regex("I have ({int}) cucumbers in my belly").expect_err();
match err {
Error::ParameterInOptional(e) => {
assert_eq!(*e, "int");
assert_eq!(
repeat(' ')
.take(e.offset())
.chain(once('^'))
.chain(repeat('-').take(e.len()))
.chain(once('^')),
" ^---^".chars(),
)
}
e => panic!("wrong err: {}", e),
}
More code examples are available here
Unfortunately that doesn't show how to compute the bounds that go into the span.
I'm implementing
Cucumber Expressions
parser in Rust: WIP. And grammar described in theARCHITECTURE.md
really bothers me for couple of reasons.alternation
definition EBNF describes context-free grammars and (correct me if I'm wrong) shouldn't have lookaheads and lookbehinds. This also means that described grammar may have non-linear complexity.Cucumber Expressions
and I don't really see reasons to do it.So implementing
Cucumber Expression
parser according to the docs leads to unnecessary performance overhead for no obvious reasons.Can't we just provide exact grammar for
Cucumber Expressions
withoutNote:
section?If I didn't miss anything provided grammar should be exact
Cucumber Expressions