diff --git a/lib/parslet.rb b/lib/parslet.rb index cba3b2b..a88c8be 100644 --- a/lib/parslet.rb +++ b/lib/parslet.rb @@ -1,24 +1,24 @@ -# A simple parser generator library. Typical usage would look like this: +# A simple parser generator library. Typical usage would look like this: # # require 'parslet' -# +# # class MyParser < Parslet::Parser # rule(:a) { str('a').repeat } -# root(:a) +# root(:a) # end -# +# # pp MyParser.new.parse('aaaa') # => 'aaaa'@0 -# pp MyParser.new.parse('bbbb') # => Parslet::Atoms::ParseFailed: +# pp MyParser.new.parse('bbbb') # => Parslet::Atoms::ParseFailed: # # Don't know what to do with bbbb at line 1 char 1. # # The simple DSL allows you to define grammars in PEG-style. This kind of # grammar construction does away with the ambiguities that usually comes with # parsers; instead, it allows you to construct grammars that are easier to -# debug, since less magic is involved. +# debug, since less magic is involved. +# +# Parslet is typically used in stages: # -# Parslet is typically used in stages: # -# # * Parsing the input string; this yields an intermediary tree, see # Parslet.any, Parslet.match, Parslet.str, Parslet::ClassMethods#rule and # Parslet::ClassMethods#root. @@ -26,14 +26,14 @@ # Parslet::Transform, Parslet.simple, Parslet.sequence and Parslet.subtree. # # The first stage is traditionally intermingled with the second stage; output -# from the second stage is usually called the 'Abstract Syntax Tree' or AST. +# from the second stage is usually called the 'Abstract Syntax Tree' or AST. # # The stages are completely decoupled; You can change your grammar around and # use the second stage to isolate the rest of your code from the changes -# you've effected. +# you've effected. # # == Further reading -# +# # All parslet atoms are subclasses of {Parslet::Atoms::Base}. You might want to # look at all of those: {Parslet::Atoms::Re}, {Parslet::Atoms::Str}, # {Parslet::Atoms::Repetition}, {Parslet::Atoms::Sequence}, @@ -42,7 +42,7 @@ # == When things go wrong # # A parse that fails will raise {Parslet::ParseFailed}. This exception contains -# all the details of what went wrong, including a detailed error trace that +# all the details of what went wrong, including a detailed error trace that # can be printed out as an ascii tree. ({Parslet::Cause}) # module Parslet @@ -52,11 +52,11 @@ module Parslet def self.included(base) base.extend(ClassMethods) end - + # Raised when the parse failed to match. It contains the message that should # be presented to the user. More details can be extracted from the # exceptions #parse_failure_cause member: It contains an instance of {Parslet::Cause} that - # stores all the details of your failed parse in a tree structure. + # stores all the details of your failed parse in a tree structure. # # begin # parslet.parse(str) @@ -76,18 +76,18 @@ def initialize(message, parse_failure_cause=nil) super(message) @parse_failure_cause = parse_failure_cause end - - # Why the parse failed. + + # Why the parse failed. # # @return [Parslet::Cause] attr_reader :parse_failure_cause end - + module ClassMethods # Define an entity for the parser. This generates a method of the same # name that can be used as part of other patterns. Those methods can be # freely mixed in your parser class with real ruby methods. - # + # # class MyParser # include Parslet # @@ -104,12 +104,12 @@ def rule(name, opts={}, &definition) define_method(name) do @rules ||= {} # memoization return @rules[name] if @rules.has_key?(name) - + # Capture the self of the parser class along with the definition. definition_closure = proc { self.instance_eval(&definition) } - + @rules[name] = Atoms::Entity.new(name, opts[:label], &definition_closure) end end @@ -119,18 +119,22 @@ def rule(name, opts={}, &definition) # # @api private class DelayedMatchConstructor + def initialize(re_option) + @re_option = re_option + end + def [](str) - Atoms::Re.new("[" + str + "]") + Atoms::Re.new("[" + str + "]", @re_option) end end - + # Returns an atom matching a character class. All regular expressions can be - # used, as long as they match only a single character at a time. + # used, as long as they match only a single character at a time. # # match('[ab]') # will match either 'a' or 'b' # match('[\n\s]') # will match newlines and spaces # - # There is also another (convenience) form of this method: + # There is also another (convenience) form of this method: # # match['a-z'] # synonymous to match('[a-z]') # match['\n'] # synonymous to match('[\n]') @@ -140,24 +144,50 @@ def [](str) # @return [Parslet::Atoms::Re] a parslet atom # def match(str=nil) - return DelayedMatchConstructor.new unless str - - return Atoms::Re.new(str) + return DelayedMatchConstructor.new(0) unless str + + return Atoms::Re.new(str, 0) end module_function :match - + + # Case-insensitive version of #match atom + # + # imatch('[a]') # will match either 'a' or 'A' + # + # @param str [String] character class to match (regexp syntax) + # @return [Parslet::Atoms::Re] a parslet atom + # + def imatch(str=nil) + return DelayedMatchConstructor.new(Regexp::IGNORECASE) unless str + + return Atoms::Re.new(str, Regexp::IGNORECASE) + end + module_function :imatch + # Returns an atom matching the +str+ given: # - # str('class') # will match 'class' + # str('class') # will match 'class' # # @param str [String] string to match verbatim # @return [Parslet::Atoms::Str] a parslet atom - # + # def str(str) - Atoms::Str.new(str) + Atoms::Str.new(str, false) end module_function :str - + + # Case-insensitive version of #str atom + # + # istr('a') # will match either 'a' or 'A' + # + # @param str [String] string to match verbatim + # @return [Parslet::Atoms::Str] a parslet atom + # + def istr(str) + Atoms::Str.new(str, true) + end + module_function :istr + # Returns an atom matching any character. It acts like the '.' (dot) # character in regular expressions. # @@ -166,57 +196,57 @@ def str(str) # @return [Parslet::Atoms::Re] a parslet atom # def any - Atoms::Re.new('.') + Atoms::Re.new('.', 0) end module_function :any - + # Introduces a new capture scope. This means that all old captures stay # accessible, but new values stored will only be available during the block - # given and the old values will be restored after the block. + # given and the old values will be restored after the block. # - # Example: - # # :a will be available until the end of the block. Afterwards, - # # :a from the outer scope will be available again, if such a thing - # # exists. + # Example: + # # :a will be available until the end of the block. Afterwards, + # # :a from the outer scope will be available again, if such a thing + # # exists. # scope { str('a').capture(:a) } # def scope(&block) Parslet::Atoms::Scope.new(block) end module_function :scope - + # Designates a piece of the parser as being dynamic. Dynamic parsers can # either return a parser at runtime, which will be applied on the input, or - # return a result from a parse. - # + # return a result from a parse. + # # Dynamic parse pieces are never cached and can introduce performance - # abnormalitites - use sparingly where other constructs fail. - # - # Example: + # abnormalitites - use sparingly where other constructs fail. + # + # Example: # # Parses either 'a' or 'b', depending on the weather # dynamic { rand() < 0.5 ? str('a') : str('b') } - # + # def dynamic(&block) Parslet::Atoms::Dynamic.new(block) end module_function :dynamic - # Returns a parslet atom that parses infix expressions. Operations are - # specified as a list of tuples, where - # atom is simply the parslet atom that matches an operator, precedence is - # a number and associativity is either :left or :right. - # + # Returns a parslet atom that parses infix expressions. Operations are + # specified as a list of tuples, where + # atom is simply the parslet atom that matches an operator, precedence is + # a number and associativity is either :left or :right. + # # Higher precedence indicates that the operation should bind tighter than - # other operations with lower precedence. In common algebra, '+' has + # other operations with lower precedence. In common algebra, '+' has # lower precedence than '*'. So you would have a precedence of 1 for '+' and - # a precedence of 2 for '*'. Only the order relation between these two - # counts, so any number would work. + # a precedence of 2 for '*'. Only the order relation between these two + # counts, so any number would work. # # Associativity is what decides what interpretation to take for strings that - # are ambiguous like '1 + 2 + 3'. If '+' is specified as left associative, - # the expression would be interpreted as '(1 + 2) + 3'. If right - # associativity is chosen, it would be interpreted as '1 + (2 + 3)'. Note - # that the hash trees output reflect that choice as well. + # are ambiguous like '1 + 2 + 3'. If '+' is specified as left associative, + # the expression would be interpreted as '(1 + 2) + 3'. If right + # associativity is chosen, it would be interpreted as '1 + (2 + 3)'. Note + # that the hash trees output reflect that choice as well. # # An optional block can be provided in order to manipulate the generated tree. # The block will be called on each operator and passed 3 arguments: the left @@ -233,19 +263,19 @@ def dynamic(&block) # @param element [Parslet::Atoms::Base] elements that take the NUMBER position # in the expression # @param operations [Array<(Parslet::Atoms::Base, Integer, {:left, :right})>] - # + # # @see Parslet::Atoms::Infix # def infix_expression(element, *operations, &reducer) Parslet::Atoms::Infix.new(element, operations, &reducer) end module_function :infix_expression - + # A special kind of atom that allows embedding whole treetop expressions - # into parslet construction. + # into parslet construction. # # # the same as str('a') >> str('b').maybe - # exp(%Q("a" "b"?)) + # exp(%Q("a" "b"?)) # # @param str [String] a treetop expression # @return [Parslet::Atoms::Base] the corresponding parslet parser @@ -254,7 +284,7 @@ def exp(str) Parslet::Expression.new(str).to_parslet end module_function :exp - + # Returns a placeholder for a tree transformation that will only match a # sequence of elements. The +symbol+ you specify will be the key for the # matched sequence in the returned dictionary. @@ -263,7 +293,7 @@ def exp(str) # { :body => sequence(:declarations) } # # The above example would match :body => ['a', 'b'], but not - # :body => 'a'. + # :body => 'a'. # # see {Parslet::Transform} # @@ -271,12 +301,12 @@ def sequence(symbol) Pattern::SequenceBind.new(symbol) end module_function :sequence - + # Returns a placeholder for a tree transformation that will only match # simple elements. This matches everything that #sequence # doesn't match. # - # # Matches a single header. + # # Matches a single header. # { :header => simple(:header) } # # see {Parslet::Transform} @@ -285,9 +315,9 @@ def simple(symbol) Pattern::SimpleBind.new(symbol) end module_function :simple - - # Returns a placeholder for tree transformation patterns that will match - # any kind of subtree. + + # Returns a placeholder for tree transformation patterns that will match + # any kind of subtree. # # { :expression => subtree(:exp) } # diff --git a/lib/parslet/atoms/re.rb b/lib/parslet/atoms/re.rb index 90f7dd1..f771efa 100644 --- a/lib/parslet/atoms/re.rb +++ b/lib/parslet/atoms/re.rb @@ -2,18 +2,18 @@ # character at a time. Useful members of this family are: character # ranges, \\w, \\d, \\r, \\n, ... # -# Example: +# Example: # # match('[a-z]') # matches a-z # match('\s') # like regexps: matches space characters # class Parslet::Atoms::Re < Parslet::Atoms::Base attr_reader :match, :re - def initialize(match) + def initialize(match, re_option = 0) super() @match = match.to_s - @re = Regexp.new(self.match, Regexp::MULTILINE) + @re = Regexp.new(self.match, Regexp::MULTILINE | re_option) end def error_msgs @@ -25,11 +25,11 @@ def error_msgs def try(source, context, consume_all) return succ(source.consume(1)) if source.matches?(@re) - + # No string could be read return context.err(self, source, error_msgs[:premature]) \ if source.chars_left < 1 - + # No match return context.err(self, source, error_msgs[:failed]) end @@ -38,4 +38,3 @@ def to_s_inner(prec) match.inspect[1..-2] end end - diff --git a/lib/parslet/atoms/str.rb b/lib/parslet/atoms/str.rb index 38e8188..bf84d8c 100644 --- a/lib/parslet/atoms/str.rb +++ b/lib/parslet/atoms/str.rb @@ -1,17 +1,22 @@ -# Matches a string of characters. +# Matches a string of characters. +# +# Example: # -# Example: -# # str('foo') # matches 'foo' # class Parslet::Atoms::Str < Parslet::Atoms::Base attr_reader :str - def initialize(str) + def initialize(str, ignore_case = false) super() @str = str.to_s - @pat = Regexp.new(Regexp.escape(str)) @len = str.size + @pat = + if ignore_case + Regexp.new(Regexp.escape(str), Regexp::IGNORECASE) + else + Regexp.new(Regexp.escape(str)) + end end def error_msgs @@ -20,23 +25,22 @@ def error_msgs failed: "Expected #{str.inspect}, but got " } end - + def try(source, context, consume_all) return succ(source.consume(@len)) if source.matches?(@pat) - + # Input ending early: return context.err(self, source, error_msgs[:premature]) \ if source.chars_left<@len - - # Expected something, but got something else instead: - error_pos = source.pos + + # Expected something, but got something else instead: + error_pos = source.pos return context.err_at( - self, source, - [error_msgs[:failed], source.consume(@len)], error_pos) + self, source, + [error_msgs[:failed], source.consume(@len)], error_pos) end - + def to_s_inner(prec) "'#{str}'" end end - diff --git a/spec/parslet/atoms_spec.rb b/spec/parslet/atoms_spec.rb index 1d131b9..e2cf9af 100644 --- a/spec/parslet/atoms_spec.rb +++ b/spec/parslet/atoms_spec.rb @@ -7,78 +7,92 @@ def not_parse raise_error(Parslet::ParseFailed) end - + include Parslet extend Parslet def src(str); Parslet::Source.new str; end let(:context) { Parslet::Atoms::Context.new } - + describe "match('[abc]')" do attr_reader :parslet before(:each) do @parslet = match('[abc]') end - + it "should parse {a,b,c}" do parslet.parse('a') parslet.parse('b') parslet.parse('c') - end + end it "should not parse d" do cause = catch_failed_parse { parslet.parse('d') } cause.to_s.should == "Failed to match [abc] at line 1 char 1." - end + end it "should print as [abc]" do parslet.inspect.should == "[abc]" - end + end end describe "match(['[a]').repeat(3)" do attr_reader :parslet before(:each) do @parslet = match('[a]').repeat(3) end - + context "when failing on input 'aa'" do let!(:cause) { catch_failed_parse { parslet.parse('aa') } } it "should have a relevant cause" do cause.to_s.should == "Expected at least 3 of [a] at line 1 char 1." - end + end it "should have a tree with 2 nodes" do cause.children.size.should == 1 - end + end end it "should succeed on 'aaa'" do parslet.parse('aaa') - end + end it "should succeed on many 'a'" do parslet.parse('a'*100) - end + end it "should inspect as [a]{3, }" do parslet.inspect.should == "[a]{3, }" end end + describe "imatch('[abc]')" do + let(:parslet) do + imatch('[abc]') + end + + it "should parse {a, b, c, A, B, C}" do + parslet.parse('a') + parslet.parse('A') + parslet.parse('b') + parslet.parse('B') + parslet.parse('c') + parslet.parse('C') + end + end describe "str('foo')" do attr_reader :parslet before(:each) do @parslet = str('foo') end - + it "should parse 'foo'" do parslet.parse('foo') end it "should not parse 'bar'" do cause = catch_failed_parse { parslet.parse('bar') } - cause.to_s.should == + cause.to_s.should == "Expected \"foo\", but got \"bar\" at line 1 char 1." end it "should inspect as 'foo'" do parslet.inspect.should == "'foo'" - end + end end describe "str('foo').maybe" do let(:parslet) { str('foo').maybe } @@ -93,21 +107,21 @@ def src(str); Parslet::Source.new str; end end it "should inspect as 'foo'?" do parslet.inspect.should == "'foo'?" - end + end context "when parsing 'foo'" do subject { parslet.parse('foo') } - + it { should == 'foo' } end context "when parsing ''" do - subject { parslet.parse('') } - - it { should == '' } + subject { parslet.parse('') } + + it { should == '' } end end describe "str('foo') >> str('bar')" do let(:parslet) { str('foo') >> str('bar') } - + context "when it fails on input 'foobaz'" do let!(:cause) { catch_failed_parse { parslet.parse('foobaz') } @@ -118,21 +132,21 @@ def src(str); Parslet::Source.new str; end end it "should have 2 nodes in error tree" do cause.children.size.should == 1 - end + end end it "should parse 'foobar'" do parslet.parse('foobar') end it "should inspect as ('foo' 'bar')" do parslet.inspect.should == "'foo' 'bar'" - end + end end describe "str('foo') | str('bar')" do attr_reader :parslet before(:each) do @parslet = str('foo') | str('bar') end - + context "when failing on input 'baz'" do let!(:cause) { catch_failed_parse { parslet.parse('baz') } @@ -140,12 +154,12 @@ def src(str); Parslet::Source.new str; end it "should have a sensible cause" do cause.to_s.should == "Expected one of ['foo', 'bar'] at line 1 char 1." - end + end it "should have an error tree with 3 nodes" do cause.children.size.should == 2 - end + end end - + it "should accept 'foo'" do parslet.parse('foo') end @@ -154,17 +168,17 @@ def src(str); Parslet::Source.new str; end end it "should inspect as ('foo' / 'bar')" do parslet.inspect.should == "'foo' / 'bar'" - end + end end describe "str('foo').present? (positive lookahead)" do attr_reader :parslet before(:each) do @parslet = str('foo').present? end - + it "should inspect as &'foo'" do parslet.inspect.should == "&'foo'" - end + end context "when fed 'foo'" do it "should parse" do success, _ = parslet.apply(src('foo'), context) @@ -184,7 +198,7 @@ def src(str); Parslet::Source.new str; end describe "<- #parse" do it "should return nil" do parslet.apply(src('foo'), context).should == [true, nil] - end + end end end describe "str('foo').absent? (negative lookahead)" do @@ -192,10 +206,10 @@ def src(str); Parslet::Source.new str; end before(:each) do @parslet = str('foo').absent? end - + it "should inspect as !'foo'" do parslet.inspect.should == "!'foo'" - end + end context "when fed 'bar'" do it "should parse" do parslet.apply(src('bar'), context).should == [true, nil] @@ -212,37 +226,47 @@ def src(str); Parslet::Source.new str; end end end end + describe "istr('a')" do + let(:parslet) do + istr('a') + end + + it "should parse either 'a' or 'A'" do + parslet.parse('a') + parslet.parse('A') + end + end describe "non greedy matcher combined with greedy matcher (possible loop)" do attr_reader :parslet before(:each) do # repeat will always succeed, since it has a minimum of 0. It will not # modify input position in that case. absent? will, depending on # implementation, match as much as possible and call its inner element - # again. This leads to an infinite loop. This example tests for the - # absence of that loop. + # again. This leads to an infinite loop. This example tests for the + # absence of that loop. @parslet = str('foo').repeat.maybe end - + it "should not loop infinitely" do lambda { Timeout.timeout(1) { parslet.parse('bar') } }.should raise_error(Parslet::ParseFailed) - end + end end describe "any" do attr_reader :parslet before(:each) do @parslet = any end - + it "should match" do parslet.parse('.') - end + end it "should consume one char" do source = src('foo') parslet.apply(source, context) source.pos.charpos.should == 1 - end + end end describe "eof behaviour" do context "when the pattern just doesn't consume the input" do @@ -251,39 +275,39 @@ def src(str); Parslet::Source.new str; end it "should fail the parse" do cause = catch_failed_parse { parslet.parse('..') } cause.to_s.should == "Don't know what to do with \".\" at line 1 char 2." - end + end end context "when the pattern doesn't match the input" do let (:parslet) { (str('a')).repeat(1) } attr_reader :exception before(:each) do - begin + begin parslet.parse('a.') rescue => @exception end end it "raises Parslet::ParseFailed" do - # ParseFailed here, because the input doesn't match the parser grammar. + # ParseFailed here, because the input doesn't match the parser grammar. exception.should be_kind_of(Parslet::ParseFailed) - end + end it "has the correct error message" do exception.message.should == \ "Extra input after last repetition at line 1 char 2." - end + end end end - + describe "<- #as(name)" do context "str('foo').as(:bar)" do it "should return :bar => 'foo'" do str('foo').as(:bar).parse('foo').should == { :bar => 'foo' } - end + end end context "match('[abc]').as(:name)" do it "should return :name => 'b'" do match('[abc]').as(:name).parse('b').should == { :name => 'b' } - end + end end context "match('[abc]').repeat.as(:name)" do it "should return collated result ('abc')" do @@ -296,35 +320,35 @@ def src(str); Parslet::Source.new str; end (str('a').as(:a) >> str('b').as(:b)).as(:c). parse('ab').should == { :c => { - :a => 'a', + :a => 'a', :b => 'b' } } - end + end end context "(str('a').as(:a) >> str('ignore') >> str('b').as(:b))" do it "should correctly flatten (leaving out 'ignore')" do (str('a').as(:a) >> str('ignore') >> str('b').as(:b)). - parse('aignoreb').should == + parse('aignoreb').should == { - :a => 'a', + :a => 'a', :b => 'b' } end end - + context "(str('a') >> str('ignore') >> str('b')) (no .as(...))" do it "should return simply the original string" do (str('a') >> str('ignore') >> str('b')). parse('aignoreb').should == 'aignoreb' - end + end end context "str('a').as(:a) >> str('b').as(:a)" do attr_reader :parslet before(:each) do @parslet = str('a').as(:a) >> str('b').as(:a) end - + it "should issue a warning that a key is being overwritten in merge" do flexmock(parslet). should_receive(:warn).once @@ -333,9 +357,9 @@ def src(str); Parslet::Source.new str; end it "should return :a => 'b'" do flexmock(parslet). should_receive(:warn) - + parslet.parse('ab').should == { :a => 'b' } - end + end end context "str('a').absent?" do it "should return something in merge, even though it is nil" do @@ -347,7 +371,7 @@ def src(str); Parslet::Source.new str; end it "should return an array of subtrees" do str('a').as(:a).repeat. parse('aa').should == [{:a=>'a'}, {:a=>'a'}] - end + end end end describe "<- #flatten(val)" do @@ -356,18 +380,18 @@ def call(val) flexmock(dummy, :warn => nil) dummy.flatten(val) end - + [ # In absence of named subtrees: ---------------------------------------- # Sequence or Repetition - [ [:sequence, 'a', 'b'], 'ab' ], + [ [:sequence, 'a', 'b'], 'ab' ], [ [:repetition, 'a', 'a'], 'aa' ], - + # Nested inside another node [ [:sequence, [:sequence, 'a', 'b']], 'ab' ], # Combined with lookahead (nil) [ [:sequence, nil, 'a'], 'a' ], - + # Including named subtrees --------------------------------------------- # Atom: A named subtree [ {:a=>'a'}, {:a=>'a'} ], @@ -378,18 +402,18 @@ def call(val) [ [:sequence, {:a => 'a'},[:repetition, {:a => 'a'}] ], [{:a=>'a'}, {:a=>'a'}]], [ [:sequence, [:repetition, {:a => 'a'}],[:repetition, {:a => 'a'}] ], [{:a=>'a'}, {:a=>'a'}]], # Repetition - [ [:repetition, [:repetition, {:a=>'a'}], [:repetition, {:a=>'a'}]], + [ [:repetition, [:repetition, {:a=>'a'}], [:repetition, {:a=>'a'}]], [{:a => 'a'}, {:a => 'a'}]], [ [:repetition, {:a=>'a'}, 'a', {:a=>'a'}], [{:a=>'a'}, {:a=>'a'}]], [ [:repetition, {:a=>'a'}, [:repetition, {:b=>'b'}]], [{:a=>'a'}] ], - + # Some random samples -------------------------------------------------- - [ [:sequence, {:a => :b, :b => :c}], {:a=>:b, :b=>:c} ], - [ [:sequence, {:a => :b}, 'a', {:c=>:d}], {:a => :b, :c=>:d} ], - [ [:repetition, {:a => :b}, 'a', {:c=>:d}], [{:a => :b}, {:c=>:d}] ], - [ [:sequence, {:a => :b}, {:a=>:d}], {:a => :d} ], - [ [:sequence, {:a=>:b}, [:sequence, [:sequence, "\n", nil]]], {:a=>:b} ], - [ [:sequence, nil, " "], ' ' ], + [ [:sequence, {:a => :b, :b => :c}], {:a=>:b, :b=>:c} ], + [ [:sequence, {:a => :b}, 'a', {:c=>:d}], {:a => :b, :c=>:d} ], + [ [:repetition, {:a => :b}, 'a', {:c=>:d}], [{:a => :b}, {:c=>:d}] ], + [ [:sequence, {:a => :b}, {:a=>:d}], {:a => :d} ], + [ [:sequence, {:a=>:b}, [:sequence, [:sequence, "\n", nil]]], {:a=>:b} ], + [ [:sequence, nil, " "], ' ' ], ].each do |input, output| it "should transform #{input.inspect} to #{output.inspect}" do call(input).should == output @@ -399,31 +423,31 @@ def call(val) describe "combinations thereof (regression)" do [ - [(str('a').repeat >> str('b').repeat), 'aaabbb'] + [(str('a').repeat >> str('b').repeat), 'aaabbb'] ].each do |(parslet, input)| describe "#{parslet.inspect} applied to #{input.inspect}" do it "should parse successfully" do parslet.parse(input) end - end + end end [ - [str('a'), "'a'" ], - [(str('a') | str('b')).maybe, "('a' / 'b')?" ], - [(str('a') >> str('b')).maybe, "('a' 'b')?" ], - [str('a').maybe.maybe, "'a'??" ], - [(str('a')>>str('b')).maybe.maybe, "('a' 'b')??" ], - [(str('a') >> (str('b') | str('c'))), "'a' ('b' / 'c')"], - - [str('a') >> str('b').repeat, "'a' 'b'{0, }" ], - [(str('a')>>str('b')).repeat, "('a' 'b'){0, }" ] + [str('a'), "'a'" ], + [(str('a') | str('b')).maybe, "('a' / 'b')?" ], + [(str('a') >> str('b')).maybe, "('a' 'b')?" ], + [str('a').maybe.maybe, "'a'??" ], + [(str('a')>>str('b')).maybe.maybe, "('a' 'b')??" ], + [(str('a') >> (str('b') | str('c'))), "'a' ('b' / 'c')"], + + [str('a') >> str('b').repeat, "'a' 'b'{0, }" ], + [(str('a')>>str('b')).repeat, "('a' 'b'){0, }" ] ].each do |(parslet, inspect_output)| context "regression for #{parslet.inspect}" do it "should inspect correctly as #{inspect_output}" do parslet.inspect.should == inspect_output - end + end end end end -end \ No newline at end of file +end