-
Notifications
You must be signed in to change notification settings - Fork 49
Add support for quantification kinds to the DSL #150
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
Add support for quantification kinds to the DSL #150
Conversation
This adds support for specifying eager / reluctant / possessive quantification from within the DSL, like the literal quantification operators with an appended `?`. --- ```swift let regex = Regex { oneOrMore(.word, .reluctant) CharacterClass.digit.capture() } "aaa1bbb2".match(regex).match // ("aaa1", "1") ```
_ component: Component, | ||
_ kind: QuantificationKind = .eager |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should kind
come before component
? This would make it consistent with the builder API. For the builder API we are kinda out of options since we'd have to support a trailing closure.
So it's
oneOrMore("x", .eager)
oneOrMore(.eager) { "x" }
vs.
oneOrMore(.eager, "x")
oneOrMore(.eager) { "x" }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't decide about this. On the one hand, it's nice to have them in a consistent order. On the other, I think it reads better to have the thing you're matching come first, and only shift it to the end when it's in the trailing closure position. IIUC, this is how SwiftUI handles it pretty much across the board — for example, when creating a Button
with just a label, the string is the first parameter, otherwise, the builder closure comes at the end.
Let's punt on this for now to see how many different calls like this we have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. If there's a precedent in SwiftUI I agree we should follow that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if the fact we keep using the term "kind" clues us in a bit. An eager oneOrMore is a different kind of operation than a reluctant or possessive one, and both spelling it as passing a ternary enum or as 3 separate overloads could make sense. Under that interpretation, I think the kind should come first.
We have a default (.eager
for regex default semantics), so I would expect the Button(label)
analogy for declaring the construction of a button with a label to be more analogous to oneOrMore(pattern)
when you just want the default.
@@ -62,6 +62,44 @@ extension CharacterClass: RegexProtocol { | |||
|
|||
// Note: Quantifiers are currently gyb'd. | |||
|
|||
/// Specifies how much to attempt to match when using a quantifier. | |||
public struct QuantificationKind { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- "Kind" feels a bit too general. Kind could also refer to one-or-more/zero-or-more/zero-or-one. Is there a more specific word for this? Maybe
QuantificationBehavior
? - For the DSL, should quantifier kinds read like an adverb for fluency?
oneOrMore("x", .eagerly) oneOrMore("x", .reluctantly)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed on both these counts — good call!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would these instead be properties returning Self
if we're going that route?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
_ component: Component | ||
public func \(kind.rawValue)<\(genericParams)>( | ||
_ component: Component, | ||
_ kind: QuantificationBehavior = .eagerly |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit
_ kind: QuantificationBehavior = .eagerly | |
_ behavior: QuantificationBehavior = .eagerly |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, missed these!
} | ||
|
||
\(arity == 0 ? "@_disfavoredOverload" : "") | ||
public func \(kind.rawValue)<\(genericParameters(withConstraints: true))>( | ||
public func \(kind.rawValue)<\(genericParams)>( | ||
_ kind: QuantificationBehavior = .eagerly, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_ kind: QuantificationBehavior = .eagerly, | |
_ behavior: QuantificationBehavior = .eagerly, |
@swift-ci Please test |
Sorry for the merge conflicts! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@@ -62,6 +62,44 @@ extension CharacterClass: RegexProtocol { | |||
|
|||
// Note: Quantifiers are currently gyb'd. | |||
|
|||
/// Specifies how much to attempt to match when using a quantifier. | |||
public struct QuantificationBehavior { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts on this being a "thing" in API vs a set of operations on quantifiable patterns? E.g. there could be a sub-protocol for quantifiables with a var eagerly: Self
requirement.
(Not arguing one way or another)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it makes the most sense as a parameter to the quantification methods — making it an operation on a oneOrMore
that uses a builder puts the adverb too far from what it describes. The .eagerly
in this example doesn't read clearly as modifying oneOrMore
to me:
let regex = Regex {
oneOrMore {
"a"
optionally(.digit)
.word
}.eagerly
}
Tests/RegexTests/RegexDSLTests.swift
Outdated
{ | ||
oneOrMore { | ||
oneOrMore(.word, .reluctantly) | ||
CharacterClass.digit.capture() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we drop CharacterClass
?
At some point I had to add a RegexWithComponent protocol and some overloads to make the static vars discoverable, but @rxwei was able to remove that and keep the lookup. I don't recall the issues there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the compiler able to distinguish between .digit
being a method of oneOrMore(...)
's return type and it being a static member call on its own? I haven't been able to get that kind of lookup to work for static protocol methods.
@swift-ci Please test Linux platform |
This adds support for specifying eager / reluctant / possessive quantification from within the DSL, like the literal quantification operators with an appended
?
.For example, with the default eager capturing, the following simple regex matches the entire string and capture the
"2"
: