Skip to content

Commit

Permalink
Sinatra 2.0 syntax: allow | outside of parens, see #31
Browse files Browse the repository at this point in the history
  • Loading branch information
rkh committed Nov 26, 2014
1 parent 3fdcc45 commit fb20261
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 37 deletions.
10 changes: 10 additions & 0 deletions mustermann-visualizer/spec/visualizer_spec.rb
Expand Up @@ -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))' }
Expand Down
2 changes: 1 addition & 1 deletion mustermann/README.md
Expand Up @@ -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>
Expand Down
4 changes: 4 additions & 0 deletions mustermann/lib/mustermann/ast/node.rb
Expand Up @@ -152,6 +152,10 @@ class Union < Composition
class Optional < Node
end

# @!visibility private
class Or < Node
end

# @!visibility private
class Root < Node
# @!visibility private
Expand Down
21 changes: 1 addition & 20 deletions mustermann/lib/mustermann/ast/parser.rb
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
56 changes: 52 additions & 4 deletions mustermann/lib/mustermann/ast/transformer.rb
Expand Up @@ -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
Expand Down
10 changes: 3 additions & 7 deletions mustermann/lib/mustermann/sinatra.rb
Expand Up @@ -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
Expand Down
17 changes: 12 additions & 5 deletions mustermann/spec/sinatra_spec.rb
Expand Up @@ -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' }
Expand Down Expand Up @@ -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\\\\"')
Expand Down

0 comments on commit fb20261

Please sign in to comment.