Permalink
Browse files

Sinatra 2.0 syntax: allow | outside of parens, see #31

  • Loading branch information...
rkh committed Nov 26, 2014
1 parent 3fdcc45 commit fb2026197b013828e90a53169c0379bb1d30a66d
@@ -54,6 +54,16 @@
its(:to_sexp) { should be == '(root (char a) (char " ") (char b))' }
end
context 'a|b' do
let(:pattern) { Mustermann.new('a|b') }
its(:to_sexp) { should be == '(root (union (char a) | (char b)))' }
end
context '(a|b)' do
let(:pattern) { Mustermann.new('(a|b)c') }
its(:to_sexp) { should be == '(root (union "(" (char a) | (char b) ")") (char c))' }
end
context '\:a' do
let(:pattern) { Mustermann.new('\:a') }
its(:to_sexp) { should be == '(root (escaped "\\\\" (escaped_char :)) (char a))' }
View
@@ -811,7 +811,7 @@ This comes with a few trade-offs:
</td>
</tr>
<tr>
<td><b>(</b><i>expression</i><b>|</b><i>expression</i><b>|</b><i>...</i><b>)</b></td>
<td><i>expression</i><b>|</b><i>expression</i><b>|</b><i>...</i></td>
<td>
Will match anything matching the nested expressions. May contain any other syntax element, including captures.
</td>
@@ -152,6 +152,10 @@ class Union < Composition
class Optional < Node
end
# @!visibility private
class Or < Node
end
# @!visibility private
class Root < Node
# @!visibility private
@@ -124,25 +124,8 @@ def read_suffix(element)
# @return [String, MatchData, nil]
# @!visibility private
def scan(regexp)
match_buffer(:scan, regexp)
end
# Wrapper around {StringScanner#check} that turns strings into escaped
# regular expressions and returns a MatchData if the regexp has any
# named captures.
#
# @param [Regexp, String] regexp
# @see StringScanner#check
# @return [String, MatchData, nil]
# @!visibility private
def check(regexp)
match_buffer(:check, regexp)
end
# @!visibility private
def match_buffer(method, regexp)
regexp = Regexp.new(Regexp.escape(regexp)) unless regexp.is_a? Regexp
string = buffer.public_send(method, regexp)
string = buffer.scan(regexp)
regexp.names.any? ? regexp.match(string) : string
end
@@ -245,8 +228,6 @@ def unexpected(char = nil, exception: ParseError)
char = "space" if char == " "
raise exception, "unexpected #{char || "end of string"} while parsing #{string.inspect}"
end
private :match_buffer
end
private_constant :Parser
@@ -15,10 +15,58 @@ def self.transform(tree)
new.translate(tree)
end
translate(:node) { self }
translate(:group, :root) do
self.payload = t(payload)
self
# recursive descent
translate(:node) do
node.payload = t(payload)
node
end
# ignore unknown objects on the tree
translate(Object) { node }
# turn a group containing or nodes into a union
# @!visibility private
class GroupTransformer < NodeTranslator
register :group
# @!visibility private
def translate
return union if payload.any? { |e| e.is_a? :or }
self.payload = t(payload)
self
end
# @!visibility private
def union
groups = split_payload.map { |g| group(g) }
Node[:union].new(groups, start: node.start, stop: node.stop)
end
# @!visibility private
def group(elements)
return t(elements.first) if elements.size == 1
start, stop = elements.first.start, elements.last.stop if elements.any?
Node[:group].new(t(elements), start: start, stop: stop)
end
# @!visibility private
def split_payload
groups = [[]]
payload.each { |e| e.is_a?(:or) ? groups << [] : groups.last << e }
groups.map!
end
end
# inject a union node right inside the root node if it contains or nodes
# @!visibility private
class RootTransformer < GroupTransformer
register :root
# @!visibility private
def union
self.payload = [super]
self
end
end
# URI expression transformations depending on operator
@@ -12,17 +12,13 @@ module Mustermann
class Sinatra < AST::Pattern
register :sinatra
on(nil, ??, ?), ?|) { |c| unexpected(c) }
on(nil, ??, ?)) { |c| unexpected(c) }
on(?*) { |c| scan(/\w+/) ? node(:named_splat, buffer.matched) : node(:splat) }
on(?:) { |c| node(:capture) { scan(/\w+/) } }
on(?\\) { |c| node(:char, expect(/./)) }
on ?( do |char|
groups = []
groups << node(:group) { read unless check(?)) or scan(?|) } until scan(?))
groups.size == 1 ? groups.first : node(:union, groups)
end
on(?() { |c| node(:group) { read unless scan(?)) } }
on(?|) { |c| node(:or) }
on ?{ do |char|
type = scan(?+) ? :named_splat : :capture
@@ -463,6 +463,18 @@
it { should_not expand(a: 'foo', b: 'bar', c: 'baz') }
end
pattern "/:a/:b|:c" do
it { should match("foo") .capturing c: 'foo' }
it { should match("/foo/bar") .capturing a: 'foo', b: 'bar' }
it { should generate_template('/{a}/{b}') }
it { should generate_template('{c}') }
it { should expand(a: 'foo', b: 'bar') .to('/foo/bar') }
it { should expand(c: 'foo') .to('foo') }
it { should_not expand(a: 'foo', b: 'bar', c: 'baz') }
end
pattern '/:foo', capture: /\d+/ do
it { should match('/1') .capturing foo: '1' }
it { should match('/123') .capturing foo: '123' }
@@ -648,11 +660,6 @@
to raise_error(Mustermann::ParseError, 'unexpected ? while parsing "foo??bar"')
end
example '| outside of group' do
expect { Mustermann::Sinatra.new('foo|bar') }.
to raise_error(Mustermann::ParseError, 'unexpected | while parsing "foo|bar"')
end
example 'dangling escape' do
expect { Mustermann::Sinatra.new('foo\\') }.
to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo\\\\"')

0 comments on commit fb20261

Please sign in to comment.