parslet-1.8.2/0000755000004100000410000000000013242043720013221 5ustar www-datawww-dataparslet-1.8.2/Rakefile0000644000004100000410000000121113242043720014661 0ustar www-datawww-datarequire 'rdoc/task' require 'sdoc' require 'rspec/core/rake_task' require "rubygems/package_task" desc "Run all tests: Exhaustive." RSpec::Core::RakeTask.new namespace :spec do desc "Only run unit tests: Fast. " RSpec::Core::RakeTask.new(:unit) do |task| task.pattern = "spec/parslet/**/*_spec.rb" end end task :default => :spec # This task actually builds the gem. task :gem => :spec spec = eval(File.read('parslet.gemspec')) desc "Prints LOC stats" task :stat do %w(lib spec example).each do |dir| loc = %x(find #{dir} -name "*.rb" | xargs wc -l | grep 'total').split.first.to_i printf("%20s %d\n", dir, loc) end end parslet-1.8.2/README0000644000004100000410000000360313242043720014103 0ustar www-datawww-dataINTRODUCTION Parslet makes developing complex parsers easy. It does so by * providing the best error reporting possible * not generating reams of code for you to debug Parslet takes the long way around to make your job easier. It allows for incremental language construction. Often, you start out small, implementing the atoms of your language first; _parslet_ takes pride in making this possible. Eager to try this out? Please see the associated web site: http://kschiess.github.io/parslet SYNOPSIS require 'parslet' include Parslet # parslet parses strings str('foo'). parse('foo') # => "foo"@0 # it matches character sets match['abc'].parse('a') # => "a"@0 match['abc'].parse('b') # => "b"@0 match['abc'].parse('c') # => "c"@0 # and it annotates its output str('foo').as(:important_bit). parse('foo') # => {:important_bit=>"foo"@0} # you can construct parsers with just a few lines quote = str('"') simple_string = quote >> (quote.absent? >> any).repeat >> quote simple_string. parse('"Simple Simple Simple"') # => "\"Simple Simple Simple\""@0 # or by making a fuss about it class Smalltalk < Parslet::Parser root :smalltalk rule(:smalltalk) { statements } rule(:statements) { # insert smalltalk parser here (outside of the scope of this readme) } end # and then Smalltalk.new.parse('smalltalk') FEATURES * Tools for every part of the parser chain * Transformers generate Abstract Syntax Trees * Accelerators transform parsers, making them quite a bit faster * Pluggable error reporters * Graphviz export for your parser * Rspec testing support rig * Simply Ruby, composable and hackable COMPATIBILITY This library is intended to work with Ruby variants >= 1.9. I've tested it on MRI 1.9, rbx-head, jruby. Please report as a bug if you encounter issues. STATUS Production worthy. (c) 2010-2017 Kaspar Schiess parslet-1.8.2/spec/0000755000004100000410000000000013242043720014153 5ustar www-datawww-dataparslet-1.8.2/spec/spec_helper.rb0000644000004100000410000000146213242043720016774 0ustar www-datawww-data require 'parslet' require 'parslet/rig/rspec' require 'parslet/atoms/visitor' require 'parslet/export' require 'ae' RSpec.configure do |config| config.mock_with :flexmock begin # Here's to the worst idea ever, rspec. This is why we'll be leaving you soon. config.expect_with :rspec do |c| c.syntax = [:should, :expect] end rescue NoMethodError # If the feature is missing, ignore it. end # Exclude other ruby versions by giving :ruby => 1.8 or :ruby => 1.9 # config.filter_run_excluding :ruby => lambda { |version| RUBY_VERSION.to_s !~ /^#{Regexp.escape(version.to_s)}/ } end def catch_failed_parse begin yield rescue Parslet::ParseFailed => exception end exception.parse_failure_cause end def slet name, &block let(name, &block) subject(&block) endparslet-1.8.2/spec/parslet/0000755000004100000410000000000013242043720015625 5ustar www-datawww-dataparslet-1.8.2/spec/parslet/source_spec.rb0000644000004100000410000001074613242043720020474 0ustar www-datawww-data# Encoding: UTF-8 require 'spec_helper' describe Parslet::Source do describe "using simple input" do let(:str) { "a"*100 + "\n" + "a"*100 + "\n" } let(:source) { described_class.new(str) } describe "<- #read(n)" do it "should not raise error when the return value is nil" do described_class.new('').consume(1) end it "should return 100 'a's when reading 100 chars" do source.consume(100).should == 'a'*100 end end describe "<- #chars_left" do subject { source.chars_left } it { should == 202 } context "after depleting the source" do before(:each) { source.consume(10000) } it { should == 0 } end end describe "<- #pos" do subject { source.pos.charpos } it { should == 0 } context "after reading a few bytes" do it "should still be correct" do pos = 0 10.times do pos += (n = rand(10)+1) source.consume(n) source.pos.charpos.should == pos end end end end describe "<- #pos=(n)" do subject { source.pos.charpos } 10.times do pos = rand(200) context "setting position #{pos}" do before(:each) { source.bytepos = pos } it { should == pos } end end end describe '#chars_until' do it 'should return 100 chars before line end' do source.chars_until("\n").should == 100 end end describe "<- #column & #line" do subject { source.line_and_column } it { should == [1,1] } context "on the first line" do it "should increase column with every read" do 10.times do |i| source.line_and_column.last.should == 1+i source.consume(1) end end end context "on the second line" do before(:each) { source.consume(101) } it { should == [2, 1]} end context "after reading everything" do before(:each) { source.consume(10000) } context "when seeking to 9" do before(:each) { source.bytepos = 9 } it { should == [1, 10] } end context "when seeking to 100" do before(:each) { source.bytepos = 100 } it { should == [1, 101] } end context "when seeking to 101" do before(:each) { source.bytepos = 101 } it { should == [2, 1] } end context "when seeking to 102" do before(:each) { source.bytepos = 102 } it { should == [2, 2] } end context "when seeking beyond eof" do it "should not throw an error" do source.bytepos = 1000 end end end context "reading char by char, storing the results" do attr_reader :results before(:each) { @results = {} while source.chars_left>0 pos = source.pos.charpos @results[pos] = source.line_and_column source.consume(1) end @results.entries.size.should == 202 @results } context "when using pos argument" do it "should return the same results" do results.each do |pos, result| source.line_and_column(pos).should == result end end end it "should give the same results when seeking" do results.each do |pos, result| source.bytepos = pos source.line_and_column.should == result end end it "should give the same results when reading" do cur = source.bytepos = 0 while source.chars_left>0 source.line_and_column.should == results[cur] cur += 1 source.consume(1) end end end end end describe "reading encoded input" do let(:source) { described_class.new("éö変わる") } def r str Regexp.new(Regexp.escape(str)) end it "should read characters, not bytes" do source.should match(r("é")) source.consume(1) source.pos.charpos.should == 1 source.bytepos.should == 2 source.should match(r("ö")) source.consume(1) source.pos.charpos.should == 2 source.bytepos.should == 4 source.should match(r("変")) source.consume(1) source.consume(2) source.chars_left.should == 0 source.chars_left.should == 0 end end end parslet-1.8.2/spec/parslet/atoms_spec.rb0000644000004100000410000003146713242043720020322 0ustar www-datawww-datarequire 'spec_helper' require 'timeout' require 'parslet' describe Parslet do 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 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 it "should print as [abc]" do parslet.inspect.should == "[abc]" 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 it "should have a tree with 2 nodes" do cause.children.size.should == 1 end end it "should succeed on 'aaa'" do parslet.parse('aaa') end it "should succeed on many 'a'" do parslet.parse('a'*100) end it "should inspect as [a]{3, }" do parslet.inspect.should == "[a]{3, }" 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 == "Expected \"foo\", but got \"bar\" at line 1 char 1." end it "should inspect as 'foo'" do parslet.inspect.should == "'foo'" end end describe "str('foo').maybe" do let(:parslet) { str('foo').maybe } it "should parse a foo" do parslet.parse('foo') end it "should leave pos untouched if there is no foo" do source = src('bar') parslet.apply(source, context) source.pos.charpos.should == 0 end it "should inspect as 'foo'?" do parslet.inspect.should == "'foo'?" end context "when parsing 'foo'" do subject { parslet.parse('foo') } it { should == 'foo' } end context "when parsing ''" do 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') } } it "should not parse 'foobaz'" do cause.to_s.should == "Failed to match sequence ('foo' 'bar') at line 1 char 4." end it "should have 2 nodes in error tree" do cause.children.size.should == 1 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 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') } } it "should have a sensible cause" do cause.to_s.should == "Expected one of ['foo', 'bar'] at line 1 char 1." end it "should have an error tree with 3 nodes" do cause.children.size.should == 2 end end it "should accept 'foo'" do parslet.parse('foo') end it "should accept 'bar'" do parslet.parse('bar') end it "should inspect as ('foo' / 'bar')" do parslet.inspect.should == "'foo' / 'bar'" 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 context "when fed 'foo'" do it "should parse" do success, _ = parslet.apply(src('foo'), context) success.should == true end it "should not change input position" do source = src('foo') parslet.apply(source, context) source.pos.charpos.should == 0 end end context "when fed 'bar'" do it "should not parse" do lambda { parslet.parse('bar') }.should not_parse end end describe "<- #parse" do it "should return nil" do parslet.apply(src('foo'), context).should == [true, nil] end end end describe "str('foo').absent? (negative lookahead)" do attr_reader :parslet before(:each) do @parslet = str('foo').absent? end it "should inspect as !'foo'" do parslet.inspect.should == "!'foo'" end context "when fed 'bar'" do it "should parse" do parslet.apply(src('bar'), context).should == [true, nil] end it "should not change input position" do source = src('bar') parslet.apply(source, context) source.pos.charpos.should == 0 end end context "when fed 'foo'" do it "should not parse" do lambda { parslet.parse('foo') }.should not_parse end 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. @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 describe "any" do attr_reader :parslet before(:each) do @parslet = any end it "should match" do parslet.parse('.') end it "should consume one char" do source = src('foo') parslet.apply(source, context) source.pos.charpos.should == 1 end end describe "eof behaviour" do context "when the pattern just doesn't consume the input" do let (:parslet) { any } 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 context "when the pattern doesn't match the input" do let (:parslet) { (str('a')).repeat(1) } attr_reader :exception before(:each) do begin parslet.parse('a.') rescue => @exception end end it "raises Parslet::ParseFailed" do # ParseFailed here, because the input doesn't match the parser grammar. exception.should be_kind_of(Parslet::ParseFailed) end it "has the correct error message" do exception.message.should == \ "Extra input after last repetition at line 1 char 2." 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 context "match('[abc]').as(:name)" do it "should return :name => 'b'" do match('[abc]').as(:name).parse('b').should == { :name => 'b' } end end context "match('[abc]').repeat.as(:name)" do it "should return collated result ('abc')" do match('[abc]').repeat.as(:name). parse('abc').should == { :name => 'abc' } end end context "(str('a').as(:a) >> str('b').as(:b)).as(:c)" do it "should return a hash of hashes" do (str('a').as(:a) >> str('b').as(:b)).as(:c). parse('ab').should == { :c => { :a => 'a', :b => 'b' } } 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 == { :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 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 parslet.parse('ab').should == { :a => 'b' } end it "should return :a => 'b'" do flexmock(parslet). should_receive(:warn) parslet.parse('ab').should == { :a => 'b' } end end context "str('a').absent?" do it "should return something in merge, even though it is nil" do (str('a').absent? >> str('b').as(:b)). parse('b').should == {:b => 'b'} end end context "str('a').as(:a).repeat" do it "should return an array of subtrees" do str('a').as(:a).repeat. parse('aa').should == [{:a=>'a'}, {:a=>'a'}] end end end describe "<- #flatten(val)" do def call(val) dummy = str('a') flexmock(dummy, :warn => nil) dummy.flatten(val) end [ # In absence of named subtrees: ---------------------------------------- # Sequence or Repetition [ [: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'} ], # Composition of subtrees [ [:sequence, {:a=>'a'},{:b=>'b'}], {:a=>'a',:b=>'b'} ], # Mixed subtrees :sequence of :repetition yields [] [ [:sequence, [:repetition, {:a => 'a'}], {:a => 'a'} ], [{:a=>'a'}, {:a=>'a'}]], [ [: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'}]], [{: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, " "], ' ' ], ].each do |input, output| it "should transform #{input.inspect} to #{output.inspect}" do call(input).should == output end end end describe "combinations thereof (regression)" do [ [(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 [ [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 endparslet-1.8.2/spec/parslet/parser_spec.rb0000644000004100000410000000141513242043720020461 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Parser do include Parslet class FooParser < Parslet::Parser rule(:foo) { str('foo') } root(:foo) end describe "<- .root" do parser = Class.new(Parslet::Parser) parser.root :root_parslet it "should have defined a 'root' method, returning the root" do parser_instance = parser.new flexmock(parser_instance).should_receive(:root_parslet => :answer) parser_instance.root.should == :answer end end it "should parse 'foo'" do FooParser.new.parse('foo').should == 'foo' end context "composition" do let(:parser) { FooParser.new } it "should allow concatenation" do composite = parser >> str('bar') composite.should parse('foobar') end end endparslet-1.8.2/spec/parslet/expression/0000755000004100000410000000000013242043720020024 5ustar www-datawww-dataparslet-1.8.2/spec/parslet/expression/treetop_spec.rb0000644000004100000410000000346113242043720023051 0ustar www-datawww-datarequire 'spec_helper' require 'parslet' describe Parslet::Expression::Treetop do include Parslet describe "positive samples" do [ # pattern # input "'abc'", 'abc', "...", 'abc', "[1-4]", '3', "'abc'?", 'abc', "'abc'?", '', "('abc')", 'abc', "'a' 'b'", 'ab', "'a' ('b')", 'ab', "'a' / 'b'", 'a', "'a' / 'b'", 'b', "'a'*", 'aaa', "'a'*", '', "'a'+", 'aa', "'a'+", 'a', "'a'{1,2}", 'a', "'a'{1,2}", 'aa', "'a'{1,}", 'a', "'a'{1,}", 'aa', "'a'{,2}", '', "'a'{,2}", 'a', "'a'{,2}", 'aa', ].each_slice(2) do |pattern, input| context "exp(#{pattern.inspect})" do let(:parslet) { exp(pattern) } subject { parslet } it { should parse(input) } context "string representation" do subject { exp(parslet.to_s) } it { should parse(input, :trace => true) } end end end end describe "negative samples" do [ # pattern # input "'abc'", 'cba', "[1-4]", '5', "'a' / 'b'", 'c', "'a'+", '', "'a'{1,2}", '', "'a'{1,2}", 'aaa', "'a'{1,}", '', "'a'{,2}", 'aaa', ].each_slice(2) do |pattern, input| context "exp(#{pattern.inspect})" do subject { exp(pattern) } it { should_not parse(input) } end end end endparslet-1.8.2/spec/parslet/position_spec.rb0000644000004100000410000000043513242043720021032 0ustar www-datawww-data# Encoding: UTF-8 require 'spec_helper' describe Parslet::Position do slet(:position) { described_class.new('öäüö', 4) } it 'should have a charpos of 2' do position.charpos.should == 2 end it 'should have a bytepos of 4' do position.bytepos.should == 4 end endparslet-1.8.2/spec/parslet/atoms/0000755000004100000410000000000013242043720016750 5ustar www-datawww-dataparslet-1.8.2/spec/parslet/atoms/repetition_spec.rb0000644000004100000410000000076113242043720022475 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Atoms::Repetition do include Parslet describe "repeat" do let(:parslet) { str('a') } describe "(min, max)" do subject { parslet.repeat(1,2) } it { should_not parse("") } it { should parse("a") } it { should parse("aa") } end describe "0 times" do it "raises an ArgumentError" do expect { parslet.repeat(0,0) }.to raise_error(ArgumentError) end end end endparslet-1.8.2/spec/parslet/atoms/infix_spec.rb0000644000004100000410000000007513242043720021426 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Atoms::Infix do endparslet-1.8.2/spec/parslet/atoms/alternative_spec.rb0000644000004100000410000000120013242043720022616 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Atoms::Alternative do include Parslet describe '| shortcut' do let(:alternative) { str('a') | str('b') } context "when chained with different atoms" do before(:each) { # Chain something else to the alternative parslet. If it modifies the # parslet atom in place, we'll notice: alternative | str('d') } let!(:chained) { alternative | str('c') } it "is side-effect free" do chained.should parse('c') chained.should parse('a') chained.should_not parse('d') end end end endparslet-1.8.2/spec/parslet/atoms/re_spec.rb0000644000004100000410000000047513242043720020723 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Atoms::Re do describe "construction" do include Parslet it "should allow match(str) form" do match('[a]').should be_a(Parslet::Atoms::Re) end it "should allow match[str] form" do match['a'].should be_a(Parslet::Atoms::Re) end end endparslet-1.8.2/spec/parslet/atoms/named_spec.rb0000644000004100000410000000007413242043720021374 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Atoms::Named do endparslet-1.8.2/spec/parslet/atoms/entity_spec.rb0000644000004100000410000000404013242043720021621 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Atoms::Entity do context "when constructed with str('bar') inside" do let(:named) { Parslet::Atoms::Entity.new('name', &proc { Parslet.str('bar') }) } it "should parse 'bar' without raising exceptions" do named.parse('bar') end it "should raise when applied to 'foo'" do lambda { named.parse('foo') }.should raise_error(Parslet::ParseFailed) end describe "#inspect" do it "should return the name of the entity" do named.inspect.should == 'NAME' end end end context "when constructed with empty block" do let(:entity) { Parslet::Atoms::Entity.new('name', &proc { }) } it "should raise NotImplementedError" do lambda { entity.parse('some_string') }.should raise_error(NotImplementedError) end end context "recursive definition parser" do class RecDefParser include Parslet rule :recdef do str('(') >> atom >> str(')') end rule :atom do str('a') | str('b') | recdef end end let(:parser) { RecDefParser.new } it "should parse balanced parens" do parser.recdef.parse("(((a)))") end it "should not throw 'stack level too deep' when printing errors" do cause = catch_failed_parse { parser.recdef.parse('(((a))') } cause.ascii_tree end end context "when constructed with a label" do let(:named) { Parslet::Atoms::Entity.new('name', 'label', &proc { Parslet.str('bar') }) } it "should parse 'bar' without raising exceptions" do named.parse('bar') end it "should raise when applied to 'foo'" do lambda { named.parse('foo') }.should raise_error(Parslet::ParseFailed) end describe "#inspect" do it "should return the label of the entity" do named.inspect.should == 'label' end end describe "#parslet" do it "should set the label on the cached parslet" do named.parslet.label.should == 'label' end end end end parslet-1.8.2/spec/parslet/atoms/visitor_spec.rb0000644000004100000410000000440413242043720022010 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Atoms do include Parslet let(:visitor) { flexmock(:visitor) } describe Parslet::Atoms::Str do let(:parslet) { str('foo') } it "should call back visitor" do visitor.should_receive(:visit_str).with('foo').once parslet.accept(visitor) end end describe Parslet::Atoms::Re do let(:parslet) { match['abc'] } it "should call back visitor" do visitor.should_receive(:visit_re).with('[abc]').once parslet.accept(visitor) end end describe Parslet::Atoms::Sequence do let(:parslet) { str('a') >> str('b') } it "should call back visitor" do visitor.should_receive(:visit_sequence).with(Array).once parslet.accept(visitor) end end describe Parslet::Atoms::Repetition do let(:parslet) { str('a').repeat(1,2) } it "should call back visitor" do visitor.should_receive(:visit_repetition).with(:repetition, 1, 2, Parslet::Atoms::Base).once parslet.accept(visitor) end end describe Parslet::Atoms::Alternative do let(:parslet) { str('a') | str('b') } it "should call back visitor" do visitor.should_receive(:visit_alternative).with(Array).once parslet.accept(visitor) end end describe Parslet::Atoms::Named do let(:parslet) { str('a').as(:a) } it "should call back visitor" do visitor.should_receive(:visit_named).with(:a, Parslet::Atoms::Base).once parslet.accept(visitor) end end describe Parslet::Atoms::Entity do let(:parslet) { Parslet::Atoms::Entity.new('foo', &lambda {}) } it "should call back visitor" do visitor.should_receive(:visit_entity).with('foo', Proc).once parslet.accept(visitor) end end describe Parslet::Atoms::Lookahead do let(:parslet) { str('a').absent? } it "should call back visitor" do visitor.should_receive(:visit_lookahead).with(false, Parslet::Atoms::Base).once parslet.accept(visitor) end end describe "< Parslet::Parser" do let(:parslet) { Parslet::Parser.new } it "calls back to visitor" do visitor.should_receive(:visit_parser).with(:root).once flexmock(parslet, :root => :root) parslet.accept(visitor) end end endparslet-1.8.2/spec/parslet/atoms/str_spec.rb0000644000004100000410000000044713242043720021124 0ustar www-datawww-data# Encoding: UTF-8 require 'spec_helper' describe Parslet::Atoms::Str do def str(s) described_class.new(s) end describe 'regression #1: multibyte characters' do it "parses successfully (length check works)" do str('あああ').should parse('あああ') end end endparslet-1.8.2/spec/parslet/atoms/ignored_spec.rb0000644000004100000410000000102113242043720021730 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Atoms::Ignored do include Parslet describe "ignore" do it "ignores parts of the input" do str('a').ignore.parse('a').should == nil (str('a') >> str('b').ignore >> str('c')).parse('abc').should == 'ac' (str('a') >> str('b').as(:name).ignore >> str('c')).parse('abc').should == 'ac' (str('a') >> str('b').maybe.ignore >> str('c')).parse('abc').should == 'ac' (str('a') >> str('b').maybe.ignore >> str('c')).parse('ac').should == 'ac' end end endparslet-1.8.2/spec/parslet/atoms/dsl_spec.rb0000644000004100000410000000120713242043720021071 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Atoms::DSL do describe "deprecated methods" do let(:parslet) { Parslet.str('foo') } describe "<- #absnt?" do slet(:absnt) { parslet.absnt? } it '#bound_parslet' do absnt.bound_parslet.should == parslet end it 'should be a negative lookahead' do absnt.positive.should == false end end describe "<- #prsnt?" do slet(:prsnt) { parslet.prsnt? } it '#bound_parslet' do prsnt.bound_parslet.should == parslet end it 'should be a positive lookahead' do prsnt.positive.should == true end end end endparslet-1.8.2/spec/parslet/atoms/capture_spec.rb0000644000004100000410000000100713242043720021750 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Atoms::Capture do include Parslet let(:context) { Parslet::Atoms::Context.new(nil) } def inject string, parser source = Parslet::Source.new(string) parser.apply(source, context, true) end it "should capture simple results" do inject 'a', str('a').capture(:a) context.captures[:a].should == 'a' end it "should capture complex results" do inject 'a', str('a').as(:b).capture(:a) context.captures[:a].should == {:b => 'a'} end endparslet-1.8.2/spec/parslet/atoms/base_spec.rb0000644000004100000410000000753213242043720021230 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Atoms::Base do let(:parslet) { Parslet::Atoms::Base.new } let(:context) { Parslet::Atoms::Context.new } describe "<- #try(io)" do it "should raise NotImplementedError" do lambda { parslet.try(flexmock(:io), context, false) }.should raise_error(NotImplementedError) end end describe "<- #flatten_sequence" do [ # 9 possibilities for making a word of 2 letters from the alphabeth of # A(rray), H(ash) and S(tring). Make sure that all results are valid. # ['a', 'b'], 'ab', # S S [['a'], ['b']], ['a', 'b'], # A A [{:a=>'a'}, {:b=>'b'}], {:a=>'a',:b=>'b'}, # H H [{:a=>'a'}, ['a']], [{:a=>'a'}, 'a'], # H A [{:a=>'a'}, 's'], {:a=>'a'}, # H S [['a'], {:a=>'a'}], ['a', {:a=>'a'}], # A H (symmetric to H A) [['a'], 'b'], ['a'], # A S ['a', {:b=>'b'}], {:b=>'b'}, # S H (symmetric to H S) ['a', ['b']], ['b'], # S A (symmetric to A S) [nil, ['a']], ['a'], # handling of lhs nil [nil, {:a=>'a'}], {:a=>'a'}, [['a'], nil], ['a'], # handling of rhs nil [{:a=>'a'}, nil], {:a=>'a'} ].each_slice(2) do |sequence, result| context "for " + sequence.inspect do it "should equal #{result.inspect}" do parslet.flatten_sequence(sequence).should == result end end end end describe "<- #flatten_repetition" do def unnamed(obj) parslet.flatten_repetition(obj, false) end it "should give subtrees precedence" do unnamed([[{:a=>"a"}, {:m=>"m"}], {:a=>"a"}]).should == [{:a=>"a"}] end end describe '#parse(source)' do context "when given something that looks like a source" do let(:source) { flexmock("source lookalike", :line_and_column => [1,2], :bytepos => 1, :chars_left => 0) } it "should not rewrap in a source" do flexmock(Parslet::Source). should_receive(:new => :source_created).never begin parslet.parse(source) rescue NotImplementedError end end end end context "when the parse fails, the exception" do it "should contain a string" do begin Parslet.str('foo').parse('bar') rescue Parslet::ParseFailed => ex ex.message.should be_kind_of(String) end end end context "when not all input is consumed" do let(:parslet) { Parslet.str('foo') } it "should raise with a proper error message" do error = catch_failed_parse { parslet.parse('foobar') } error.to_s.should == "Don't know what to do with \"bar\" at line 1 char 4." end end context "when only parsing string prefix" do let(:parslet) { Parslet.str('foo') >> Parslet.str('bar') } it "returns the first half on a prefix parse" do parslet.parse('foobarbaz', :prefix => true).should == 'foobar' end end describe ':reporter option' do let(:parslet) { Parslet.str('test') >> Parslet.str('ing') } let(:reporter) { flexmock(:reporter) } it "replaces the default reporter" do cause = flexmock(:cause) # Two levels of the parse, calling two different error reporting # methods. reporter. should_receive(:err_at).once reporter. should_receive(:err => cause).once reporter. should_receive(:succ).once # The final cause will be sent the #raise method. cause. should_receive(:raise).once.and_throw(:raise) catch(:raise) { parslet.parse('testung', :reporter => reporter) fail "NEVER REACHED" } end end endparslet-1.8.2/spec/parslet/atoms/sequence_spec.rb0000644000004100000410000000121213242043720022113 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Atoms::Sequence do include Parslet let(:sequence) { described_class.new } describe '>> shortcut' do let(:sequence) { str('a') >> str('b') } context "when chained with different atoms" do before(:each) { # Chain something else to the sequence parslet. If it modifies the # parslet atom in place, we'll notice: sequence >> str('d') } let!(:chained) { sequence >> str('c') } it "is side-effect free" do chained.should parse('abc') chained.should_not parse('abdc') end end end end parslet-1.8.2/spec/parslet/atoms/scope_spec.rb0000644000004100000410000000110113242043720021411 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Atoms::Scope do include Parslet include Parslet::Atoms::DSL let(:context) { Parslet::Atoms::Context.new(nil) } let(:captures) { context.captures } def inject string, parser source = Parslet::Source.new(string) parser.apply(source, context, true) end let(:aabb) { scope { match['ab'].capture(:f) >> dynamic { |s,c| str(c.captures[:f]) } } } it "keeps values of captures outside" do captures[:f] = 'old_value' inject 'aa', aabb captures[:f].should == 'old_value' end endparslet-1.8.2/spec/parslet/atoms/lookahead_spec.rb0000644000004100000410000000137013242043720022237 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Atoms::Lookahead do include Parslet describe 'negative lookahead' do it "influences the error tree" do parser = str('f').absent? >> str('b') cause = catch_failed_parse { parser.parse('f') } cause.ascii_tree.should == "Failed to match sequence (!'f' 'b') at line 1 char 1.\n`- Input should not start with 'f' at line 1 char 1.\n" end end describe 'positive lookahead' do it "influences the error tree" do parser = str('f').present? >> str('b') cause = catch_failed_parse { parser.parse('b') } cause.ascii_tree.should == "Failed to match sequence (&'f' 'b') at line 1 char 1.\n`- Input should start with 'f' at line 1 char 1.\n" end end endparslet-1.8.2/spec/parslet/atoms/combinations_spec.rb0000644000004100000410000000011713242043720022773 0ustar www-datawww-datarequire 'spec_helper' describe "Parslet combinations" do include Parslet endparslet-1.8.2/spec/parslet/transform_spec.rb0000644000004100000410000001240413242043720021200 0ustar www-datawww-datarequire 'spec_helper' require 'parslet' describe Parslet::Transform do include Parslet let(:transform) { Parslet::Transform.new } class A < Struct.new(:elt); end class B < Struct.new(:elt); end class C < Struct.new(:elt); end class Bi < Struct.new(:a, :b); end describe "delayed construction" do context "given simple(:x) => A.new(x)" do before(:each) do transform.rule(simple(:x)) { |d| A.new(d[:x]) } end it "should transform 'a' into A.new('a')" do transform.apply('a').should == A.new('a') end it "should transform ['a', 'b'] into [A.new('a'), A.new('b')]" do transform.apply(['a', 'b']).should == [A.new('a'), A.new('b')] end end context "given rules on {:a => simple(:x)} and {:b => :_x}" do before(:each) do transform.rule(:a => simple(:x)) { |d| A.new(d[:x]) } transform.rule(:b => simple(:x)) { |d| B.new(d[:x]) } end it "should transform {:d=>{:b=>'c'}} into d => B('c')" do transform.apply({:d=>{:b=>'c'}}).should == {:d => B.new('c')} end it "should transform {:a=>{:b=>'c'}} into A(B('c'))" do transform.apply({:a=>{:b=>'c'}}).should == A.new(B.new('c')) end end describe "pulling out subbranches" do before(:each) do transform.rule(:a => {:b => simple(:x)}, :d => {:e => simple(:y)}) { |d| Bi.new(*d.values_at(:x, :y)) } end it "should yield Bi.new('c', 'f')" do transform.apply(:a => {:b => 'c'}, :d => {:e => 'f'}).should == Bi.new('c', 'f') end end end describe "dsl construction" do let(:transform) { Parslet::Transform.new do rule(simple(:x)) { A.new(x) } end } it "should still evaluate rules correctly" do transform.apply('a').should == A.new('a') end end describe "class construction" do class OptimusPrime < Parslet::Transform rule(:a => simple(:x)) { A.new(x) } rule(:b => simple(:x)) { B.new(x) } end let(:transform) { OptimusPrime.new } it "should evaluate rules" do transform.apply(:a => 'a').should == A.new('a') end context "optionally raise when no match found" do class BumbleBee < Parslet::Transform def initialize(&block) super(raise_on_unmatch: true, &block) end rule(:a => simple(:x)) { A.new(x) } end let(:transform) { BumbleBee.new } it "should evaluate rules" do transform.apply(:a => 'a').should == A.new('a') end it "should raise when no rules are matched" do lambda { transform.apply(:z => 'z') }.should raise_error(NotImplementedError, /Failed to match/) end end context "with inheritance" do class OptimusPrimeJunior < OptimusPrime rule(:b => simple(:x)) { B.new(x.upcase) } rule(:c => simple(:x)) { C.new(x) } end let(:transform) { OptimusPrimeJunior.new } it "should inherit rules from its parent" do transform.apply(:a => 'a').should == A.new('a') end it "should be able to override rules from its parent" do transform.apply(:b => 'b').should == B.new('B') end it "should be able to define new rules" do transform.apply(:c => 'c').should == C.new('c') end end end describe "<- #call_on_match" do let(:bindings) { { :foo => 'test' } } context "when given a block of arity 1" do it "should call the block" do called = false transform.call_on_match(bindings, lambda do |dict| called = true end) called.should == true end it "should yield the bindings" do transform.call_on_match(bindings, lambda do |dict| dict.should == bindings end) end it "should execute in the current context" do foo = 'test' transform.call_on_match(bindings, lambda do |dict| foo.should == 'test' end) end end context "when given a block of arity 0" do it "should call the block" do called = false transform.call_on_match(bindings, proc do called = true end) called.should == true end it "should have bindings as local variables" do transform.call_on_match(bindings, proc do foo.should == 'test' end) end it "should execute in its own context" do @bar = 'test' transform.call_on_match(bindings, proc do if instance_variable_defined?("@bar") instance_variable_get("@bar").should_not == 'test' end end) end end end context "various transformations (regression)" do context "hashes" do it "are matched completely" do transform.rule(:a => simple(:x)) { fail } transform.apply(:a => 'a', :b => 'b') end end end context "when not using the bindings as hash, but as local variables" do it "should access the variables" do transform.rule(simple(:x)) { A.new(x) } transform.apply('a').should == A.new('a') end it "should allow context as local variable" do transform.rule(simple(:x)) { foo } transform.apply('a', :foo => 'bar').should == 'bar' end end endparslet-1.8.2/spec/parslet/slice_spec.rb0000644000004100000410000001017313242043720020265 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Slice do def cslice string, offset, cache=nil described_class.new( Parslet::Position.new(string, offset), string, cache) end describe "construction" do it "should construct from an offset and a string" do cslice('foobar', 40) end end context "('foobar', 40, 'foobar')" do let(:slice) { cslice('foobar', 40) } describe "comparison" do it "should be equal to other slices with the same attributes" do other = cslice('foobar', 40) slice.should == other other.should == slice end it "should be equal to other slices (offset is irrelevant for comparison)" do other = cslice('foobar', 41) slice.should == other other.should == slice end it "should be equal to a string with the same content" do slice.should == 'foobar' end it "should be equal to a string (inversed operands)" do 'foobar'.should == slice end it "should not be equal to a string" do slice.should_not equal('foobar') end it "should not be eql to a string" do slice.should_not eql('foobar') end it "should not hash to the same number" do slice.hash.should_not == 'foobar'.hash end end describe "offset" do it "should return the associated offset" do slice.offset.should == 6 end it "should fail to return a line and column" do lambda { slice.line_and_column }.should raise_error(ArgumentError) end context "when constructed with a source" do let(:slice) { cslice( 'foobar', 40, flexmock(:cache, :line_and_column => [13, 14])) } it "should return proper line and column" do slice.line_and_column.should == [13, 14] end end end describe "string methods" do describe "matching" do it "should match as a string would" do slice.should match(/bar/) slice.should match(/foo/) md = slice.match(/f(o)o/) md.captures.first.should == 'o' end end describe "<- #size" do subject { slice.size } it { should == 6 } end describe "<- #length" do subject { slice.length } it { should == 6 } end describe "<- #+" do let(:other) { cslice('baz', 10) } subject { slice + other } it "should concat like string does" do subject.size.should == 9 subject.should == 'foobarbaz' subject.offset.should == 6 end end end describe "conversion" do describe "<- #to_slice" do it "should return self" do slice.to_slice.should eq(slice) end end describe "<- #to_sym" do it "should return :foobar" do slice.to_sym.should == :foobar end end describe "cast to Float" do it "should return a float" do Float(cslice('1.345', 11)).should == 1.345 end end describe "cast to Integer" do it "should cast to integer as a string would" do s = cslice('1234', 40) Integer(s).should == 1234 s.to_i.should == 1234 end it "should fail when Integer would fail on a string" do lambda { Integer(slice) }.should raise_error(ArgumentError, /invalid value/) end it "should turn into zero when a string would" do slice.to_i.should == 0 end end end describe "inspection and string conversion" do describe "#inspect" do subject { slice.inspect } it { should == '"foobar"@6' } end describe "#to_s" do subject { slice.to_s } it { should == 'foobar' } end end describe "serializability" do it "should serialize" do Marshal.dump(slice) end context "when storing a line cache" do let(:slice) { cslice('foobar', 40, Parslet::Source::LineCache.new()) } it "should serialize" do Marshal.dump(slice) end end end end end parslet-1.8.2/spec/parslet/transform/0000755000004100000410000000000013242043720017640 5ustar www-datawww-dataparslet-1.8.2/spec/parslet/transform/context_spec.rb0000644000004100000410000000237513242043720022672 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Context do def context(*args) described_class.new(*args) end it "binds hash keys as variable like things" do context(:a => 'value').instance_eval { a }. should == 'value' end it "one contexts variables aren't the next ones" do ca = context(:a => 'b') cb = context(:b => 'c') ca.methods.should_not include(:b) cb.methods.should_not include(:a) end describe 'works as a Ruby object should' do let(:obj) { context(a: 1) } it 'responds_to? :a' do assert obj.respond_to?(:a) end it 'includes :a in #methods' do obj.methods.assert.include?(:a) end it 'allows inspection' do obj.inspect.assert.match(/@a=1/) end it 'allows conversion to string' do obj.to_s.assert.match(/Parslet::Context:0x/) end context 'when the context is enhanced' do before(:each) do class << obj def foo 'foo' end end end it 'responds_to correctly' do assert obj.respond_to?(:foo) end it 'includes :foo also in methods' do obj.methods.assert.include?(:foo) end it 'allows calling #foo' do obj.foo.assert == 'foo' end end end endparslet-1.8.2/spec/parslet/parslet_spec.rb0000644000004100000410000000156313242043720020643 0ustar www-datawww-datarequire 'spec_helper' describe Parslet do include Parslet describe Parslet::ParseFailed do it "should be caught by an empty rescue" do begin raise Parslet::ParseFailed rescue # Success! Ignore this. end end end describe "<- .rule" do # Rules define methods. This can be easily tested by defining them right # here. context "empty rule" do rule(:empty) { } it "should raise a NotImplementedError" do lambda { empty.parslet }.should raise_error(NotImplementedError) end end context "containing 'any'" do rule(:any_rule) { any } subject { any_rule } it { should be_a Parslet::Atoms::Entity } it "should memoize the returned instance" do any_rule.object_id.should == any_rule.object_id end end end endparslet-1.8.2/spec/parslet/source/0000755000004100000410000000000013242043720017125 5ustar www-datawww-dataparslet-1.8.2/spec/parslet/source/line_cache_spec.rb0000644000004100000410000000442013242043720022536 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Source::RangeSearch do describe "<- #lbound" do context "for a simple array" do let(:ary) { [10, 20, 30, 40, 50] } before(:each) { ary.extend Parslet::Source::RangeSearch } it "should return correct answers for numbers not in the array" do ary.lbound(5).should == 0 ary.lbound(15).should == 1 ary.lbound(25).should == 2 ary.lbound(35).should == 3 ary.lbound(45).should == 4 end it "should return correct answers for numbers in the array" do ary.lbound(10).should == 1 ary.lbound(20).should == 2 ary.lbound(30).should == 3 ary.lbound(40).should == 4 end it "should cover right edge case" do ary.lbound(50).should be_nil ary.lbound(51).should be_nil end it "should cover left edge case" do ary.lbound(0).should == 0 end end context "for an empty array" do let(:ary) { [] } before(:each) { ary.extend Parslet::Source::RangeSearch } it "should return nil" do ary.lbound(1).should be_nil end end end end describe Parslet::Source::LineCache do describe "<- scan_for_line_endings" do context "calculating the line_and_columns" do let(:str) { "foo\nbar\nbazd" } it "should return the first line if we have no line ends" do subject.scan_for_line_endings(0, nil) subject.line_and_column(3).should == [1, 4] subject.scan_for_line_endings(0, "") subject.line_and_column(5).should == [1, 6] end it "should find the right line starting from pos 0" do subject.scan_for_line_endings(0, str) subject.line_and_column(5).should == [2, 2] subject.line_and_column(9).should == [3, 2] end it "should find the right line starting from pos 5" do subject.scan_for_line_endings(5, str) subject.line_and_column(11).should == [2, 3] end it "should find the right line if scannning the string multiple times" do subject.scan_for_line_endings(0, str) subject.scan_for_line_endings(0, "#{str}\nthe quick\nbrown fox") subject.line_and_column(10).should == [3,3] subject.line_and_column(24).should == [5,2] end end end end parslet-1.8.2/spec/parslet/atom_results_spec.rb0000644000004100000410000000232413242043720021706 0ustar www-datawww-datarequire 'spec_helper' describe 'Result of a Parslet#parse' do include Parslet; extend Parslet describe "regression" do [ # Behaviour with maybe-nil [str('foo').maybe >> str('bar'), "bar", "bar"], [str('bar') >> str('foo').maybe, "bar", 'bar'], # These might be hard to understand; look at the result of # str.maybe >> str # and # str.maybe >> str first. [(str('f').maybe >> str('b')).repeat, "bb", "bb"], [(str('b') >> str('f').maybe).repeat, "bb", 'bb'], [str('a').as(:a) >> (str('b') >> str('c').as(:a)).repeat, 'abc', [{:a=>'a'}, {:a=>'c'}]], [str('a').as(:a).repeat >> str('b').as(:b).repeat, 'ab', [{:a=>'a'}, {:b=>'b'}]], # Repetition behaviour / named vs. unnamed [str('f').repeat, '', ''], [str('f').repeat.as(:f), '', {:f => []}], # Maybe behaviour / named vs. unnamed [str('f').maybe, '', ''], [str('f').maybe.as(:f), '', {:f => nil}], ].each do |parslet, input, result| context "#{parslet.inspect}" do it "should parse \"#{input}\" into \"#{result}\"" do parslet.parse(input).should == result end end end end endparslet-1.8.2/spec/parslet/pattern_spec.rb0000644000004100000410000002022313242043720020640 0ustar www-datawww-datarequire 'spec_helper' require 'parslet' describe Parslet::Pattern do include Parslet # These two factory methods help make the specs more robust to interface # changes. They also help to label trees (t) and patterns (p). def p(pattern) Parslet::Pattern.new(pattern) end def t(obj) obj end # Tries to match pattern to the tree, and verifies the bindings hash. Don't # use this for new examples. # RSpec::Matchers.define :match_with_bind do |pattern, exp_bindings| unless respond_to?(:failure_message) alias_method :failure_message_for_should, :failure_message end failure_message do |tree| "expected #{pattern.inspect} to match #{tree.inspect}, but didn't. (block wasn't called or not correctly)" end match do |tree| bindings = Parslet::Pattern.new(pattern).match(tree) bindings && bindings == exp_bindings end end # This is the more modern version of verifying a match: (uses 'exp' # implicitly). Checks for a match of pattern in +exp+ and yields the # matched variables. # def with_match_locals(pattern, &block) bindings = p(pattern).match(exp) bindings.should_not be_nil block.call(bindings) if block end # Can't use #match here, so I went to the Thesaurus. # RSpec::Matchers.define :detect do |pattern| match do |tree| bindings = Parslet::Pattern.new(pattern).match(tree) bindings ? true : false end end describe "<- #match" do context "injecting bindings" do let(:pattern) { p(simple(:x)) } it "should not modify the original bindings hash" do h = {} b=pattern.match('a', h) h.size.should == 0 b.size.should == 1 end it "should return nil when no match succeeds" do pattern.match([], :foo => :bar).should be_nil end context "when matching simple(:x) against 'a'" do let(:bindings) { pattern.match(t('a'), :foo => :bar) } before(:each) { bindings.should_not be_nil } it "should return the injected bindings" do bindings[:foo].should == :bar end it "should return the new bindings" do bindings[:x].should == 'a' end end end context "simple strings" do let(:exp) { 'aaaa' } it "should match simple strings" do exp.should match_with_bind(simple(:x), :x => 'aaaa') end end context "simple hash {:a => 'b'}" do attr_reader :exp before(:each) do @exp = t(:a => 'b') end it "should not match {:a => simple(:x), :b => simple(:y)}" do exp.should_not detect(:a => simple(:x), :b => simple(:y)) end it "should match {:a => simple(:x)}, binding 'x' to the first argument" do exp.should match_with_bind({:a => simple(:x)}, :x => 'b') end it "should match {:a => 'b'} with no binds" do exp.should match_with_bind({:a => 'b'}, {}) end end context "a more complex hash {:a => {:b => 'c'}}" do attr_reader :exp before(:each) do @exp = t(:a => {:b => 'c'}) end it "should match wholly with {:a => {:b => simple(:x)}}" do exp.should match_with_bind({:a => {:b => simple(:x)}}, :x => 'c') end it "should match wholly with {:a => subtree(:t)}" do with_match_locals(:a => subtree(:t)) do |dict| dict[:t].should == {:b => 'c'} end end it "should not bind subtrees to variables in {:a => simple(:x)}" do p(:a => simple(:x)).should_not detect(exp) end end context "a more complex hash {:a => 'a', :b => 'b'}" do attr_reader :exp before(:each) do @exp = t({:a => 'a', :b => 'b'}) end it "should not match partially" do Parslet::Pattern.new(:a => simple(:x)).match(exp).should be_nil end it "should match completely" do exp.should match_with_bind({:a => simple(:x), :b => simple(:y)}, :x => 'a', :y => 'b') end end context "an array of 'a', 'b', 'c'" do let(:exp) { ['a', 'b', 'c'] } it "should match all elements at once" do exp.should match_with_bind( [simple(:x), simple(:y), simple(:z)], :x => 'a', :y => 'b', :z => 'c') end end context "{:a => 'a', :b => 'b'}" do attr_reader :exp before(:each) do @exp = t(:a => 'a', :b => 'b') end it "should match both elements simple(:x), simple(:y)" do exp.should match_with_bind( {:a => simple(:x), :b => simple(:y)}, :x => 'a', :y => 'b') end it "should not match a constrained match (simple(:x) != simple(:y))" do exp.should_not detect({:a => simple(:x), :b => simple(:x)}) end end context "{:a => 'a', :b => 'a'}" do attr_reader :exp before(:each) do @exp = t(:a => 'a', :b => 'a') end it "should match constrained pattern" do exp.should match_with_bind( {:a => simple(:x), :b => simple(:x)}, :x => 'a') end end context "{:sub1 => {:a => 'a'}, :sub2 => {:a => 'a'}}" do attr_reader :exp before(:each) do @exp = t({ :sub1 => {:a => 'a'}, :sub2 => {:a => 'a'} }) end it "should verify constraints over several subtrees" do exp.should match_with_bind({ :sub1 => {:a => simple(:x)}, :sub2 => {:a => simple(:x)} }, :x => 'a') end it "should return both bind variables simple(:x), simple(:y)" do exp.should match_with_bind({ :sub1 => {:a => simple(:x)}, :sub2 => {:a => simple(:y)} }, :x => 'a', :y => 'a') end end context "{:sub1 => {:a => 'a'}, :sub2 => {:a => 'b'}}" do attr_reader :exp before(:each) do @exp = t({ :sub1 => {:a => 'a'}, :sub2 => {:a => 'b'} }) end it "should verify constraints over several subtrees" do exp.should_not match_with_bind({ :sub1 => {:a => simple(:x)}, :sub2 => {:a => simple(:x)} }, :x => 'a') end it "should return both bind variables simple(:x), simple(:y)" do exp.should match_with_bind({ :sub1 => {:a => simple(:x)}, :sub2 => {:a => simple(:y)} }, :x => 'a', :y => 'b') end end context "[{:a => 'x'}, {:a => 'y'}]" do attr_reader :exp before(:each) do @exp = t([{:a => 'x'}, {:a => 'y'}]) end it "should not match sequence(:x) (as a whole)" do exp.should_not detect(sequence(:x)) end end context "['x', 'y', 'z']" do attr_reader :exp before(:each) do @exp = t(['x', 'y', 'z']) end it "should match [simple(:x), simple(:y), simple(:z)]" do with_match_locals([simple(:x), simple(:y), simple(:z)]) do |dict| dict[:x].should == 'x' dict[:y].should == 'y' dict[:z].should == 'z' end end it "should match %w(x y z)" do exp.should match_with_bind(%w(x y z), { }) end it "should not match [simple(:x), simple(:y), simple(:x)]" do exp.should_not detect([simple(:x), simple(:y), simple(:x)]) end it "should not match [simple(:x), simple(:y)]" do exp.should_not detect([simple(:x), simple(:y), simple(:x)]) end it "should match sequence(:x) (as array)" do exp.should match_with_bind(sequence(:x), :x => ['x', 'y', 'z']) end end context "{:a => [1,2,3]}" do attr_reader :exp before(:each) do @exp = t(:a => [1,2,3]) end it "should match :a => sequence(:x) (binding x to the whole array)" do exp.should match_with_bind({:a => sequence(:x)}, {:x => [1,2,3]}) end end context "with differently ordered hashes" do it "should still match" do t(:a => 'a', :b => 'b').should detect(:a => 'a', :b => 'b') t(:a => 'a', :b => 'b').should detect(:b => 'b', :a => 'a') t(:b => 'b', :a => 'a').should detect(:b => 'b', :a => 'a') t(:b => 'b', :a => 'a').should detect(:a => 'a', :b => 'b') end end end endparslet-1.8.2/spec/parslet/minilisp.tt0000644000004100000410000000106013242043720020017 0ustar www-datawww-datagrammar MiniLisp rule root (expression) end rule expression ((space_p) "(" (space_p) (body) ")") end rule space_p (space)0..1 end rule body ((expression) / (identifier) / (float) / (integer) / (string))0.. end rule space \s1.. end rule identifier (([a-zA-Z=*] [a-zA-Z=*_]0..) (space_p)) end rule float (((integer) (("." [0-9]1..) / ("e" [0-9]1..))) (space_p)) end rule integer ((("+" / "-")0..1 [0-9]1..) (space_p)) end rule string ("\"" (("\\" .) / (!"\"" .))0.. "\"" (space_p)) end end parslet-1.8.2/spec/parslet/rig/0000755000004100000410000000000013242043720016406 5ustar www-datawww-dataparslet-1.8.2/spec/parslet/rig/rspec_spec.rb0000644000004100000410000000354313242043720021066 0ustar www-datawww-datarequire 'spec_helper' require 'parslet/rig/rspec' describe 'rspec integration' do include Parslet subject { str('example') } it { should parse('example') } it { should_not parse('foo') } it { should parse('example').as('example') } it { should_not parse('foo').as('example') } it { should_not parse('example').as('foo') } it { str('foo').as(:bar).should parse('foo').as({:bar => 'foo'}) } it { str('foo').as(:bar).should_not parse('foo').as({:b => 'f'}) } it 'accepts a block to assert more specific details about the parsing output' do str('foo').as(:bar).should(parse('foo').as { |output| output.should have_key(:bar) output.values.first.should == 'foo' }) end # Uncomment to test error messages manually: # it { str('foo').should parse('foo', :trace => true).as('bar') } # it { str('foo').should parse('food', :trace => true) } # it { str('foo').should_not parse('foo', :trace => true).as('foo') } # it { str('foo').should_not parse('foo', :trace => true) } # it 'accepts a block to assert more specific details about the parsing output' do # str('foo').as(:bar).should(parse('foo', :trace => true).as { |output| # output.should_not have_key(:bar) # }) # end end describe 'rspec3 syntax' do include Parslet let(:s) { str('example') } it { expect(s).to parse('example') } it { expect(s).not_to parse('foo') } it { expect(s).to parse('example').as('example') } it { expect(s).not_to parse('foo').as('example') } it { expect(s).not_to parse('example').as('foo') } # Uncomment to test error messages manually: # it { expect(str('foo')).to parse('foo', :trace => true).as('bar') } # it { expect(str('foo')).to parse('food', :trace => true) } # it { expect(str('foo')).not_to parse('foo', :trace => true).as('foo') } # it { expect(str('foo')).not_to parse('foo', :trace => true) } end parslet-1.8.2/spec/parslet/export_spec.rb0000644000004100000410000000327613242043720020515 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Parser, "exporting to other lingos" do class MiniLisp < Parslet::Parser root :expression rule(:expression) { space? >> str('(') >> space? >> body >> str(')') } rule(:body) { (expression | identifier | float | integer | string).repeat.as(:exp) } rule(:space) { match('\s').repeat(1) } rule(:space?) { space.maybe } rule(:identifier) { (match('[a-zA-Z=*]') >> match('[a-zA-Z=*_]').repeat).as(:identifier) >> space? } rule(:float) { ( integer >> ( str('.') >> match('[0-9]').repeat(1) | str('e') >> match('[0-9]').repeat(1) ).as(:e) ).as(:float) >> space? } rule(:integer) { ((str('+') | str('-')).maybe >> match("[0-9]").repeat(1)).as(:integer) >> space? } rule(:string) { str('"') >> ( str('\\') >> any | str('"').absent? >> any ).repeat.as(:string) >> str('"') >> space? } end # I only update the files once I've verified the new syntax to work with # the respective tools. This is more an acceptance test than a real spec. describe "<- #to_citrus" do let(:citrus) { File.read( File.join(File.dirname(__FILE__), 'minilisp.citrus')) } it "should be valid citrus syntax" do # puts MiniLisp.new.to_citrus MiniLisp.new.to_citrus.should == citrus end end describe "<- #to_treetop" do let(:treetop) { File.read( File.join(File.dirname(__FILE__), 'minilisp.tt')) } it "should be valid treetop syntax" do # puts MiniLisp.new.to_treetop MiniLisp.new.to_treetop.should == treetop end end endparslet-1.8.2/spec/parslet/scope_spec.rb0000644000004100000410000000176113242043720020302 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::Scope do let(:scope) { described_class.new } describe 'simple store/retrieve' do before(:each) { scope[:foo] = :bar } it "allows storing objects" do scope[:obj] = 42 end it "raises on access of empty slots" do expect { scope[:empty] }.to raise_error(Parslet::Scope::NotFound) end it "allows retrieval of stored values" do scope[:foo].should == :bar end end describe 'scoping' do before(:each) { scope[:depth] = 1 } before(:each) { scope.push } let(:depth) { scope[:depth] } subject { depth } it { should == 1 } describe 'after a push' do before(:each) { scope.push } it { should == 1 } describe 'and reassign' do before(:each) { scope[:depth] = 2 } it { should == 2 } describe 'and a pop' do before(:each) { scope.pop } it { should == 1 } end end end end endparslet-1.8.2/spec/parslet/minilisp.citrus0000644000004100000410000000104713242043720020706 0ustar www-datawww-datagrammar MiniLisp rule root (expression) end rule expression ((space_p) "(" (space_p) (body) ")") end rule space_p (space)0*1 end rule body ((expression) | (identifier) | (float) | (integer) | (string))0* end rule space \s1* end rule identifier (([a-zA-Z=*] [a-zA-Z=*_]0*) (space_p)) end rule float (((integer) (("." [0-9]1*) | ("e" [0-9]1*))) (space_p)) end rule integer ((("+" | "-")0*1 [0-9]1*) (space_p)) end rule string ("\"" (("\\" .) | (!"\"" .))0* "\"" (space_p)) end end parslet-1.8.2/spec/parslet/convenience_spec.rb0000644000004100000410000000241713242043720021464 0ustar www-datawww-datarequire 'spec_helper' describe 'parslet/convenience' do require 'parslet/convenience' include Parslet class FooParser < Parslet::Parser rule(:foo) { str('foo') } root(:foo) end describe 'parse_with_debug' do let(:parser) { flexmock FooParser.new } context 'internal' do before(:each) do # Suppress output. # parser.should_receive(:puts) end it 'should exist' do lambda { parser.parse_with_debug('anything') }.should_not raise_error end it 'should catch ParseFailed exceptions' do lambda { parser.parse_with_debug('bar') }.should_not raise_error end it 'should parse correct input like #parse' do lambda { parser.parse_with_debug('foo') }.should_not raise_error end end context 'output' do it 'should puts once for tree output' do parser.should_receive(:puts).once parser.parse_with_debug('incorrect') end it "should puts once for the error on unconsumed input" do parser.should_receive(:puts).once parser.parse_with_debug('foobar') end end it "should work for all parslets" do str('foo').parse_with_debug('foo') match['bar'].parse_with_debug('a') end end end parslet-1.8.2/spec/parslet/error_reporter/0000755000004100000410000000000013242043720020700 5ustar www-datawww-dataparslet-1.8.2/spec/parslet/error_reporter/contextual_spec.rb0000644000004100000410000000653513242043720024436 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::ErrorReporter::Contextual do let(:reporter) { described_class.new } let(:fake_source) { flexmock('source') } let(:fake_atom) { flexmock('atom') } let(:fake_cause) { flexmock('cause') } describe '#err' do before(:each) { fake_source.should_receive( :pos => 13, :line_and_column => [1,1]) } it "returns the deepest cause" do flexmock(reporter). should_receive(:deepest).and_return(:deepest) reporter.err('parslet', fake_source, 'message'). should == :deepest end end describe '#err_at' do before(:each) { fake_source.should_receive( :pos => 13, :line_and_column => [1,1]) } it "returns the deepest cause" do flexmock(reporter). should_receive(:deepest).and_return(:deepest) reporter.err('parslet', fake_source, 'message', 13). should == :deepest end end describe '#deepest(cause)' do def fake_cause(pos=13, children=nil) flexmock('cause' + pos.to_s, :pos => pos, :children => children) end context "when there is no deepest cause yet" do let(:cause) { fake_cause } it "returns the given cause" do reporter.deepest(cause).should == cause end end context "when the previous cause is deeper (no relationship)" do let(:previous) { fake_cause } before(:each) { reporter.deepest(previous) } it "returns the previous cause" do reporter.deepest(fake_cause(12)). should == previous end end context "when the previous cause is deeper (child)" do let(:previous) { fake_cause } before(:each) { reporter.deepest(previous) } it "returns the given cause" do given = fake_cause(12, [previous]) reporter.deepest(given).should == given end end context "when the previous cause is shallower" do before(:each) { reporter.deepest(fake_cause) } it "stores the cause as deepest" do deeper = fake_cause(14) reporter.deepest(deeper) reporter.deepest_cause.should == deeper end end end describe '#reset' do before(:each) { fake_source.should_receive( :pos => Parslet::Position.new('source', 13), :line_and_column => [1,1]) } it "resets deepest cause on success of sibling expression" do flexmock(reporter). should_receive(:deepest).and_return(:deepest) reporter.err('parslet', fake_source, 'message'). should == :deepest flexmock(reporter). should_receive(:reset).once reporter.succ(fake_source) end end describe 'label' do before(:each) { fake_source.should_receive( :pos => Parslet::Position.new('source', 13), :line_and_column => [1,1]) } it "sets label if atom has one" do fake_atom.should_receive(:label).once.and_return('label') fake_cause.should_receive(:set_label).once flexmock(reporter). should_receive(:deepest).and_return(fake_cause) reporter.err(fake_atom, fake_source, 'message'). should == fake_cause end it 'does not set label if atom does not have one' do flexmock(reporter). should_receive(:deepest).and_return(:deepest) fake_atom.should_receive(:update_label).never reporter.err(fake_atom, fake_source, 'message'). should == :deepest end end end parslet-1.8.2/spec/parslet/error_reporter/tree_spec.rb0000644000004100000410000000015013242043720023172 0ustar www-datawww-datarequire 'spec_helper' require 'parslet/error_reporter' describe Parslet::ErrorReporter::Tree do endparslet-1.8.2/spec/parslet/error_reporter/deepest_spec.rb0000644000004100000410000000407513242043720023676 0ustar www-datawww-datarequire 'spec_helper' describe Parslet::ErrorReporter::Deepest do let(:reporter) { described_class.new } let(:fake_source) { flexmock('source') } describe '#err' do before(:each) { fake_source.should_receive( :pos => 13, :line_and_column => [1,1]) } it "returns the deepest cause" do flexmock(reporter). should_receive(:deepest).and_return(:deepest) reporter.err('parslet', fake_source, 'message'). should == :deepest end end describe '#err_at' do before(:each) { fake_source.should_receive( :pos => 13, :line_and_column => [1,1]) } it "returns the deepest cause" do flexmock(reporter). should_receive(:deepest).and_return(:deepest) reporter.err('parslet', fake_source, 'message', 13). should == :deepest end end describe '#deepest(cause)' do def fake_cause(pos=13, children=nil) flexmock('cause' + pos.to_s, :pos => pos, :children => children) end context "when there is no deepest cause yet" do let(:cause) { fake_cause } it "returns the given cause" do reporter.deepest(cause).should == cause end end context "when the previous cause is deeper (no relationship)" do let(:previous) { fake_cause } before(:each) { reporter.deepest(previous) } it "returns the previous cause" do reporter.deepest(fake_cause(12)). should == previous end end context "when the previous cause is deeper (child)" do let(:previous) { fake_cause } before(:each) { reporter.deepest(previous) } it "returns the given cause" do given = fake_cause(12, [previous]) reporter.deepest(given).should == given end end context "when the previous cause is shallower" do before(:each) { reporter.deepest(fake_cause) } it "stores the cause as deepest" do deeper = fake_cause(14) reporter.deepest(deeper) reporter.deepest_cause.should == deeper end end end end parslet-1.8.2/spec/acceptance/0000755000004100000410000000000013242043720016241 5ustar www-datawww-dataparslet-1.8.2/spec/acceptance/examples_spec.rb0000644000004100000410000000170413242043720021420 0ustar www-datawww-datarequire 'spec_helper' require 'open3' describe "Regression on" do Dir["example/*.rb"].each do |example| context example do # Generates a product path for a given example file. def product_path(str, ext) str. gsub('.rb', ".#{ext}"). gsub('example/','example/output/') end it "runs successfully" do _, stdout, stderr = Open3.popen3("ruby #{example}") handle_map = { stdout => :out, stderr => :err } expectation_found = handle_map.any? do |io, ext| name = product_path(example, ext) if File.exist?(name) io.read.strip.should == File.read(name).strip true end end unless expectation_found fail "Example doesn't have either an .err or an .out file. "+ "Please create in examples/output!" end end end end end parslet-1.8.2/spec/acceptance/unconsumed_input_spec.rb0000644000004100000410000000114513242043720023200 0ustar www-datawww-datarequire 'spec_helper' describe "Unconsumed input:" do class RepeatingBlockParser < Parslet::Parser root :expressions rule(:expressions) { expression.repeat } rule(:expression) { str('(') >> aab >> str(')') } rule(:aab) { str('a').repeat(1) >> str('b') } end describe RepeatingBlockParser do let(:parser) { described_class.new } it "throws annotated error" do error = catch_failed_parse { parser.parse('(aaac)') } end it "doesn't error out if prefix is true" do expect { parser.parse('(aaac)', :prefix => true) }.not_to raise_error end end endparslet-1.8.2/spec/acceptance/regression_spec.rb0000644000004100000410000002300213242043720021755 0ustar www-datawww-data# Encoding: UTF-8 require 'spec_helper' require 'parslet' describe "Regressions from real examples" do # This parser piece produces on the left a subtree that is keyed (a hash) # and on the right a subtree that is a repetition of such subtrees. I've # for now decided that these would merge into the repetition such that the # return value is an array. This avoids maybe loosing keys/values in a # hash merge. # class ArgumentListParser include Parslet rule :argument_list do expression.as(:argument) >> (comma >> expression.as(:argument)).repeat end rule :expression do string end rule :string do str('"') >> ( str('\\') >> any | str('"').absent? >> any ).repeat.as(:string) >> str('"') >> space? end rule :comma do str(',') >> space? end rule :space? do space.maybe end rule :space do match("[ \t]").repeat(1) end def parse(str) argument_list.parse(str) end end describe ArgumentListParser do let(:instance) { ArgumentListParser.new } it "should have method expression" do instance.should respond_to(:expression) end it 'should parse "arg1", "arg2"' do result = ArgumentListParser.new.parse('"arg1", "arg2"') result.size.should == 2 result.each do |r| r[:argument] end end it 'should parse "arg1", "arg2", "arg3"' do result = ArgumentListParser.new.parse('"arg1", "arg2", "arg3"') result.size.should == 3 result.each do |r| r[:argument] end end end class ParensParser < Parslet::Parser rule(:balanced) { str('(').as(:l) >> balanced.maybe.as(:m) >> str(')').as(:r) } root(:balanced) end describe ParensParser do let(:instance) { ParensParser.new } context "statefulness: trying several expressions in sequence" do it "should not be stateful" do # NOTE: Since you've come here to read this, I'll explain why # this is broken and not fixed: You're looking at the tuning branch, # which rewrites a bunch of stuff - so I have failing tests to # remind me of what is left to be done. And to remind you not to # trust this code. instance.parse('(())') lambda { instance.parse('((()))') instance.parse('(((())))') }.should_not raise_error end end context "expression '(())'" do let(:result) { instance.parse('(())') } it "should yield a doubly nested hash" do result.should be_a(Hash) result.should have_key(:m) result[:m].should be_a(Hash) # This was an array earlier end context "inner hash" do let(:inner) { result[:m] } it "should have nil as :m" do inner[:m].should be_nil end end end end class ALanguage < Parslet::Parser root(:expressions) rule(:expressions) { (line >> eol).repeat(1) | line } rule(:line) { space? >> an_expression.as(:exp).repeat } rule(:an_expression) { str('a').as(:a) >> space? } rule(:eol) { space? >> match["\n\r"].repeat(1) >> space? } rule(:space?) { space.repeat } rule(:space) { multiline_comment.as(:multi) | line_comment.as(:line) | str(' ') } rule(:line_comment) { str('//') >> (match["\n\r"].absent? >> any).repeat } rule(:multiline_comment) { str('/*') >> (str('*/').absent? >> any).repeat >> str('*/') } end describe ALanguage do def remove_indent(s) s.to_s.lines.map { |l| l.chomp.strip }.join("\n") end it "should count lines correctly" do cause = catch_failed_parse { subject.parse('a a a a aaa // ff /* a */ b ') } remove_indent(cause.ascii_tree).should == remove_indent(%q( Expected one of [(LINE EOL){1, }, LINE] at line 1 char 1. |- Extra input after last repetition at line 7 char 11. | `- Failed to match sequence (LINE EOL) at line 7 char 11. | `- Failed to match sequence (SPACE? [\n\r]{1, } SPACE?) at line 7 char 11. | `- Expected at least 1 of [\n\r] at line 7 char 11. | `- Failed to match [\n\r] at line 7 char 11. `- Don't know what to do with "\n " at line 1 char 2.).strip) end end class BLanguage < Parslet::Parser root :expression rule(:expression) { b.as(:one) >> b.as(:two) } rule(:b) { str('b') } end describe BLanguage do it "should parse 'bb'" do subject.should parse('bb').as(:one => 'b', :two => 'b') end it "should transform with binding constraint" do transform = Parslet::Transform.new do |t| t.rule(:one => simple(:b), :two => simple(:b)) { :ok } end transform.apply(subject.parse('bb')).should == :ok end end class UnicodeLanguage < Parslet::Parser root :gobble rule(:gobble) { any.repeat } end describe UnicodeLanguage do it "should parse UTF-8 strings" do subject.should parse('éèäöü').as('éèäöü') subject.should parse('RubyKaigi2009のテーマは、「変わる/変える」です。 前回の').as('RubyKaigi2009のテーマは、「変わる/変える」です。 前回の') end end class UnicodeSentenceLanguage < Parslet::Parser rule(:sentence) { (match('[^。]').repeat(1) >> str("。")).as(:sentence) } rule(:sentences) { sentence.repeat } root(:sentences) end describe UnicodeSentenceLanguage do let(:string) { "RubyKaigi2009のテーマは、「変わる/変える」です。 前回の" + "RubyKaigi2008のテーマであった「多様性」の言葉の通り、 " + "2008年はRubyそのものに関しても、またRubyの活躍する舞台に関しても、 " + "ますます多様化が進みつつあります。RubyKaigi2008は、そのような " + "Rubyの生態系をあらためて認識する場となりました。 しかし、" + "こうした多様化が進む中、異なる者同士が単純に距離を 置いたままでは、" + "その違いを認識したところであまり意味がありません。 異なる実装、" + "異なる思想、異なる背景といった、様々な多様性を理解しつつ、 " + "すり合わせるべきものをすり合わせ、変えていくべきところを " + "変えていくことが、豊かな未来へとつながる道に違いありません。" } it "should parse sentences" do subject.should parse(string) end end class TwoCharLanguage < Parslet::Parser root :twochar rule(:twochar) { any >> str('2') } end describe TwoCharLanguage do def di(s) s.strip.to_s.lines.map { |l| l.chomp.strip }.join("\n") end it "should raise an error" do error = catch_failed_parse { subject.parse('123') } di(error.ascii_tree).should == di(%q( Failed to match sequence (. '2') at line 1 char 2. `- Don't know what to do with "3" at line 1 char 3. )) end end # Issue #68: Extra input reporting, written by jmettraux class RepetitionParser < Parslet::Parser rule(:nl) { match('[\s]').repeat(1) } rule(:nl?) { nl.maybe } rule(:sp) { str(' ').repeat(1) } rule(:sp?) { str(' ').repeat(0) } rule(:line) { sp >> str('line') } rule(:body) { ((line | block) >> nl).repeat(0) } rule(:block) { sp? >> str('begin') >> sp >> match('[a-z]') >> nl >> body >> sp? >> str('end') } rule(:blocks) { nl? >> block >> (nl >> block).repeat(0) >> nl? } root(:blocks) end describe RepetitionParser do def di(s) s.strip.to_s.lines.map { |l| l.chomp.strip }.join("\n") end it 'parses a block' do subject.parse(%q{ begin a end }) end it 'parses nested blocks' do subject.parse(%q{ begin a begin b end end }) end it 'parses successive blocks' do subject.parse(%q{ begin a end begin b end }) end it 'fails gracefully on a missing end' do error = catch_failed_parse { subject.parse(%q{ begin a begin b end }) } di(error.ascii_tree).should == di(%q( Failed to match sequence (NL? BLOCK (NL BLOCK){0, } NL?) at line 2 char 11. `- Failed to match sequence (SP? 'begin' SP [a-z] NL BODY SP? 'end') at line 5 char 9. `- Premature end of input at line 5 char 9. )) end it 'fails gracefully on a missing end (2)' do error = catch_failed_parse { subject.parse(%q{ begin a end begin b begin c end }) } di(error.ascii_tree).should == di(%q( Failed to match sequence (NL? BLOCK (NL BLOCK){0, } NL?) at line 3 char 14. `- Don't know what to do with "begin b\n " at line 4 char 11. )) end it 'fails gracefully on a missing end (deepest reporter)' do error = catch_failed_parse { subject.parse(%q{ begin a end begin b begin c li end end }, :reporter => Parslet::ErrorReporter::Deepest.new) } di(error.ascii_tree).should == di(%q( Failed to match sequence (NL? BLOCK (NL BLOCK){0, } NL?) at line 3 char 16. `- Expected "end", but got "li\n" at line 6 char 17. )) end end end parslet-1.8.2/spec/acceptance/repetition_and_maybe_spec.rb0000644000004100000410000000227313242043720023765 0ustar www-datawww-datarequire 'spec_helper' describe "Tree output" do extend Parslet def self.hash_examples(h) h.each do |atom, expected| it "should convert #{atom} to #{expected.inspect}" do atom.parse(input).should == expected end end end context "when parsing the empty string" do let(:input) { '' } hash_examples( # No naming str('a').maybe => '', str('a').repeat => '', # Named contents: maybe yields nil str('a').maybe.as(:f) => {:f => nil}, str('a').repeat.as(:f) => {:f => []}, # Contents that aren't simple strings (str('a') >> str('b')).maybe.as(:f) => {:f => nil}, (str('a') >> str('b')).repeat.as(:f) => {:f => []}, # The other way around: Contents would be tagged, but nil result isn't (str('a') >> str('b')).as(:f).maybe => '', (str('a') >> str('b')).as(:f).repeat => '' ) end context "when parsing 'aa'" do let(:input) { 'aa' } hash_examples( # since they're not named, repetitions get merged together. str('a').as(:a).repeat >> str('a').as(:a).repeat => [{:a=>'a'},{:a=>'a'}] ) end endparslet-1.8.2/spec/acceptance/infix_parser_spec.rb0000644000004100000410000000642713242043720022302 0ustar www-datawww-datarequire 'spec_helper' describe 'Infix expression parsing' do class InfixExpressionParser < Parslet::Parser rule(:space) { match['\s'] } def cts atom atom >> space.repeat end def infix *args Infix.new(*args) end rule(:mul_op) { match['*/'] >> str(' ').maybe } rule(:add_op) { match['+-'] >> str(' ').maybe } rule(:digit) { match['0-9'] } rule(:integer) { cts digit.repeat(1) } rule(:expression) { infix_expression(integer, [mul_op, 2, :left], [add_op, 1, :right]) } end let(:p) { InfixExpressionParser.new } describe '#integer' do let(:i) { p.integer } it "parses integers" do i.should parse('1') i.should parse('123') end it "consumes trailing white space" do i.should parse('1 ') i.should parse('134 ') end it "doesn't parse floats" do i.should_not parse('1.3') end end describe '#multiplication' do let(:m) { p.expression } it "parses simple multiplication" do m.should parse('1*2').as(l: '1', o: '*', r: '2') end it "parses simple multiplication with spaces" do m.should parse('1 * 2').as(l: '1 ', o: '* ', r: '2') end it "parses division" do m.should parse('1/2') end end describe '#addition' do let(:a) { p.expression } it "parses simple addition" do a.should parse('1+2') end it "parses complex addition" do a.should parse('1+2+3-4') end it "parses a single element" do a.should parse('1') end end describe 'mixed operations' do let(:mo) { p.expression } describe 'inspection' do it 'produces useful expressions' do p.expression.parslet.inspect.should == "infix_expression(INTEGER, [MUL_OP, ADD_OP])" end end describe 'right associativity' do it 'produces trees that lean right' do mo.should parse('1+2+3').as( l: '1', o: '+', r: {l: '2', o: '+', r: '3'}) end end describe 'left associativity' do it 'produces trees that lean left' do mo.should parse('1*2*3').as( l: {l:'1', o:'*', r:'2'}, o:'*', r:'3') end end describe 'error handling' do describe 'incomplete expression' do it 'produces the right error' do cause = catch_failed_parse { mo.parse('1+') } cause.ascii_tree.to_s.should == <<-ERROR INTEGER was expected at line 1 char 3. `- Failed to match sequence (DIGIT{1, } SPACE{0, }) at line 1 char 3. `- Expected at least 1 of DIGIT at line 1 char 3. `- Premature end of input at line 1 char 3. ERROR end end describe 'invalid operator' do it 'produces the right error' do cause = catch_failed_parse { mo.parse('1%') } cause.ascii_tree.to_s.should == <<-ERROR Don't know what to do with "%" at line 1 char 2. ERROR end end end end describe "providing a reducer block" do class InfixExpressionReducerParser < Parslet::Parser rule(:top) { infix_expression(str('a'), [str('-'), 1, :right]) { |l,o,r| {:and=>[l,r]} } } end it "applies the reducer" do InfixExpressionReducerParser.new.top.parse("a-a-a").should == {:and=>["a", {:and=>["a", "a"]}]} end end endparslet-1.8.2/parslet.gemspec0000644000004100000410000000103013242043720016232 0ustar www-datawww-data# -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = 'parslet' s.version = '1.8.2' s.authors = ['Kaspar Schiess'] s.email = 'kaspar.schiess@absurd.li' s.extra_rdoc_files = ['README'] s.files = %w(HISTORY.txt LICENSE Rakefile README parslet.gemspec) + Dir.glob("{lib,spec,example}/**/*") s.homepage = 'http://kschiess.github.io/parslet' s.license = 'MIT' s.rdoc_options = ['--main', 'README'] s.require_paths = ['lib'] s.summary = 'Parser construction library with great error reporting in Ruby.' end parslet-1.8.2/lib/0000755000004100000410000000000013242043720013767 5ustar www-datawww-dataparslet-1.8.2/lib/parslet/0000755000004100000410000000000013242043720015441 5ustar www-datawww-dataparslet-1.8.2/lib/parslet/export.rb0000644000004100000410000000764713242043720017325 0ustar www-datawww-data# Allows exporting parslet grammars to other lingos. require 'set' require 'parslet/atoms/visitor' class Parslet::Parser module Visitors class Citrus attr_reader :context, :output def initialize(context) @context = context end def visit_str(str) "\"#{str.inspect[1..-2]}\"" end def visit_re(match) match.to_s end def visit_entity(name, block) context.deferred(name, block) "(#{context.mangle_name(name)})" end def visit_named(name, parslet) parslet.accept(self) end def visit_sequence(parslets) '(' << parslets. map { |el| el.accept(self) }. join(' ') << ')' end def visit_repetition(tag, min, max, parslet) parslet.accept(self) << "#{min}*#{max}" end def visit_alternative(alternatives) '(' << alternatives. map { |el| el.accept(self) }. join(' | ') << ')' end def visit_lookahead(positive, bound_parslet) (positive ? '&' : '!') << bound_parslet.accept(self) end end class Treetop < Citrus def visit_repetition(tag, min, max, parslet) parslet.accept(self) << "#{min}..#{max}" end def visit_alternative(alternatives) '(' << alternatives. map { |el| el.accept(self) }. join(' / ') << ')' end end end # A helper class that formats Citrus and Treetop grammars as a string. # class PrettyPrinter attr_reader :visitor def initialize(visitor_klass) @visitor = visitor_klass.new(self) end # Pretty prints the given parslet using the visitor that has been # configured in initialize. Returns the string representation of the # Citrus or Treetop grammar. # def pretty_print(name, parslet) output = "grammar #{name}\n" output << rule('root', parslet) seen = Set.new loop do # @todo is constantly filled by the visitor (see #deferred). We # keep going until it is empty. break if @todo.empty? name, block = @todo.shift # Track what rules we've already seen. This breaks loops. next if seen.include?(name) seen << name output << rule(name, block.call) end output << "end\n" end # Formats a rule in either dialect. # def rule(name, parslet) " rule #{mangle_name name}\n" << " " << parslet.accept(visitor) << "\n" << " end\n" end # Whenever the visitor encounters an rule in a parslet, it defers the # pretty printing of the rule by calling this method. # def deferred(name, content) @todo ||= [] @todo << [name, content] end # Mangles names so that Citrus and Treetop can live with it. This mostly # transforms some of the things that Ruby allows into other patterns. If # there is collision, we will not detect it for now. # def mangle_name(str) str.to_s.sub(/\?$/, '_p') end end # Exports the current parser instance as a string in the Citrus dialect. # # Example: # # require 'parslet/export' # class MyParser < Parslet::Parser # root(:expression) # rule(:expression) { str('foo') } # end # # MyParser.new.to_citrus # => a citrus grammar as a string # def to_citrus PrettyPrinter.new(Visitors::Citrus). pretty_print(self.class.name, root) end # Exports the current parser instance as a string in the Treetop dialect. # # Example: # # require 'parslet/export' # class MyParser < Parslet::Parser # root(:expression) # rule(:expression) { str('foo') } # end # # MyParser.new.to_treetop # => a treetop grammar as a string # def to_treetop PrettyPrinter.new(Visitors::Treetop). pretty_print(self.class.name, root) end end parslet-1.8.2/lib/parslet/transform.rb0000644000004100000410000001735513242043720020014 0ustar www-datawww-data require 'parslet/pattern' # Transforms an expression tree into something else. The transformation # performs a depth-first, post-order traversal of the expression tree. During # that traversal, each time a rule matches a node, the node is replaced by the # result of the block associated to the rule. Otherwise the node is accepted # as is into the result tree. # # This is almost what you would generally do with a tree visitor, except that # you can match several levels of the tree at once. # # As a consequence of this, the resulting tree will contain pieces of the # original tree and new pieces. Most likely, you will want to transform the # original tree wholly, so this isn't a problem. # # You will not be able to create a loop, given that each node will be replaced # only once and then left alone. This means that the results of a replacement # will not be acted upon. # # Example: # # class Example < Parslet::Transform # rule(:string => simple(:x)) { # (1) # StringLiteral.new(x) # } # end # # A tree transform (Parslet::Transform) is defined by a set of rules. Each # rule can be defined by calling #rule with the pattern as argument. The block # given will be called every time the rule matches somewhere in the tree given # to #apply. It is passed a Hash containing all the variable bindings of this # pattern match. # # In the above example, (1) illustrates a simple matching rule. # # Let's say you want to parse matching parentheses and distill a maximum nest # depth. You would probably write a parser like the one in example/parens.rb; # here's the relevant part: # # rule(:balanced) { # str('(').as(:l) >> balanced.maybe.as(:m) >> str(')').as(:r) # } # # If you now apply this to a string like '(())', you get a intermediate parse # tree that looks like this: # # { # l: '(', # m: { # l: '(', # m: nil, # r: ')' # }, # r: ')' # } # # This parse tree is good for debugging, but what we would really like to have # is just the nesting depth. This transformation rule will produce that: # # rule(:l => '(', :m => simple(:x), :r => ')') { # # innermost :m will contain nil # x.nil? ? 1 : x+1 # } # # = Usage patterns # # There are four ways of using this class. The first one is very much # recommended, followed by the second one for generality. The other ones are # omitted here. # # Recommended usage is as follows: # # class MyTransformator < Parslet::Transform # rule(...) { ... } # rule(...) { ... } # # ... # end # MyTransformator.new.apply(tree) # # Alternatively, you can use the Transform class as follows: # # transform = Parslet::Transform.new do # rule(...) { ... } # end # transform.apply(tree) # # = Execution context # # The execution context of action blocks differs depending on the arity of # said blocks. This can be confusing. It is however somewhat intentional. You # should not create fat Transform descendants containing a lot of helper methods, # instead keep your AST class construction in global scope or make it available # through a factory. The following piece of code illustrates usage of global # scope: # # transform = Parslet::Transform.new do # rule(...) { AstNode.new(a_variable) } # rule(...) { Ast.node(a_variable) } # modules are nice # end # transform.apply(tree) # # And here's how you would use a class builder (a factory): # # transform = Parslet::Transform.new do # rule(...) { builder.add_node(a_variable) } # rule(...) { |d| d[:builder].add_node(d[:a_variable]) } # end # transform.apply(tree, :builder => Builder.new) # # As you can see, Transform allows you to inject local context for your rule # action blocks to use. # class Parslet::Transform # FIXME: Maybe only part of it? Or maybe only include into constructor # context? include Parslet class << self # FIXME: Only do this for subclasses? include Parslet # Define a rule for the transform subclass. # def rule(expression, &block) @__transform_rules ||= [] # Prepend new rules so they have higher precedence than older rules @__transform_rules.unshift([Parslet::Pattern.new(expression), block]) end # Allows accessing the class' rules # def rules @__transform_rules ||= [] end def inherited(subclass) super subclass.instance_variable_set(:@__transform_rules, rules.dup) end end def initialize(raise_on_unmatch=false, &block) @raise_on_unmatch = raise_on_unmatch @rules = [] if block instance_eval(&block) end end # Defines a rule to be applied whenever apply is called on a tree. A rule # is composed of two parts: # # * an *expression pattern* # * a *transformation block* # def rule(expression, &block) # Prepend new rules so they have higher precedence than older rules @rules.unshift([Parslet::Pattern.new(expression), block]) end # Applies the transformation to a tree that is generated by Parslet::Parser # or a simple parslet. Transformation will proceed down the tree, replacing # parts/all of it with new objects. The resulting object will be returned. # # Using the context parameter, you can inject bindings for the transformation. # This can be used to allow access to the outside world from transform blocks, # like so: # # document = # some class that you act on # transform.apply(tree, document: document) # # The above will make document available to all your action blocks: # # # Variant A # rule(...) { document.foo(bar) } # # Variant B # rule(...) { |d| d[:document].foo(d[:bar]) } # # @param obj PORO ast to transform # @param context start context to inject into the bindings. # def apply(obj, context=nil) transform_elt( case obj when Hash recurse_hash(obj, context) when Array recurse_array(obj, context) else obj end, context ) end # Executes the block on the bindings obtained by Pattern#match, if such a match # can be made. Depending on the arity of the given block, it is called in # one of two environments: the current one or a clean toplevel environment. # # If you would like the current environment preserved, please use the # arity 1 variant of the block. Alternatively, you can inject a context object # and call methods on it (think :ctx => self). # # # the local variable a is simulated # t.call_on_match(:a => :b) { a } # # no change of environment here # t.call_on_match(:a => :b) { |d| d[:a] } # def call_on_match(bindings, block) if block if block.arity == 1 return block.call(bindings) else context = Context.new(bindings) return context.instance_eval(&block) end end end # Allow easy access to all rules, the ones defined in the instance and the # ones predefined in a subclass definition. # def rules self.class.rules + @rules end # @api private # def transform_elt(elt, context) rules.each do |pattern, block| if bindings=pattern.match(elt, context) # Produces transformed value return call_on_match(bindings, block) end end # No rule matched - element is not transformed if @raise_on_unmatch && elt.is_a?(Hash) elt_types = elt.map do |key, value| [ key, value.class ] end.to_h raise NotImplementedError, "Failed to match `#{elt_types.inspect}`" else return elt end end # @api private # def recurse_hash(hsh, ctx) hsh.inject({}) do |new_hsh, (k,v)| new_hsh[k] = apply(v, ctx) new_hsh end end # @api private # def recurse_array(ary, ctx) ary.map { |elt| apply(elt, ctx) } end end require 'parslet/context'parslet-1.8.2/lib/parslet/source.rb0000644000004100000410000000457113242043720017275 0ustar www-datawww-data require 'stringio' require 'strscan' require 'parslet/position' require 'parslet/source/line_cache' module Parslet # Wraps the input string for parslet. # class Source def initialize(str) raise( ArgumentError, "Must construct Source with a string like object." ) unless str.respond_to?(:to_str) @str = StringScanner.new(str) # maps 1 => /./m, 2 => /../m, etc... @re_cache = Hash.new { |h,k| h[k] = /(.|$){#{k}}/m } @line_cache = LineCache.new @line_cache.scan_for_line_endings(0, str) end # Checks if the given pattern matches at the current input position. # # @param pattern [Regexp] pattern to check for # @return [Boolean] true if the pattern matches at #pos # def matches?(pattern) @str.match?(pattern) end alias match matches? # Consumes n characters from the input, returning them as a slice of the # input. # def consume(n) position = self.pos slice_str = @str.scan(@re_cache[n]) slice = Parslet::Slice.new( position, slice_str, @line_cache) return slice end # Returns how many chars remain in the input. # def chars_left @str.rest_size end # Returns how many chars there are between current position and the # string given. If the string given doesn't occur in the source, then # the remaining chars (#chars_left) are returned. # # @return [Fixnum] count of chars until str or #chars_left # def chars_until str slice_str = @str.check_until(Regexp.new(Regexp.escape(str))) return chars_left unless slice_str return slice_str.size - str.size end # Position of the parse as a character offset into the original string. # # @note Please be aware of encodings at this point. # def pos Position.new(@str.string, @str.pos) end def bytepos @str.pos end # @note Please be aware of encodings at this point. # def bytepos=(n) @str.pos = n rescue RangeError end # Returns a tuple for the given position. If no position is # given, line/column information is returned for the current position # given by #pos. # def line_and_column(position=nil) @line_cache.line_and_column(position || self.bytepos) end end end parslet-1.8.2/lib/parslet/accelerator.rb0000644000004100000410000001064113242043720020254 0ustar www-datawww-data # Optimizes the parsers by pattern matching on the parser atoms and replacing # matches with better versions. See the file qed/accelerators.md for a more # in-depth description. # # Example: # quote = str('"') # parser = quote >> (quote.absent? >> any).repeat >> quote # # A = Accelerator # for making what follows a bit shorter # optimized_parser = A.apply(parser, # A.rule( (A.str(:x).absent? >> A.any).repeat ) { GobbleUp.new(x) }) # # optimized_parser.parse('"Parsing is now fully optimized! (tm)"') # module Parslet::Accelerator # An expression to match against a tree of parser atoms. Normally, an # expression is produced by Parslet::Accelerator.any, # Parslet::Accelerator.str or Parslet::Accelerator.re. # # Expressions can be chained much like parslet atoms can be: # # expr.repeat(1) # matching repetition # expr.absent? # matching absent? # expr.present? # matching present? # expr1 >> expr2 # matching a sequence # expr1 | expr2 # matching an alternation # # @see Parslet::Accelerator.str # @see Parslet::Accelerator.re # @see Parslet::Accelerator.any # # @see Parslet::Accelerator # class Expression attr_reader :type attr_reader :args def initialize(type, *args) @type = type @args = args end # @return [Expression] def >> other_expr join_or_new :seq, other_expr end # @return [Expression] def | other_expr join_or_new :alt, other_expr end # @return [Expression] def absent? Expression.new(:absent, self) end # @return [Expression] def present? Expression.new(:present, self) end # @return [Expression] def repeat min=0, max=nil Expression.new(:rep, min, max, self) end # @return [Expression] def as name Expression.new(:as, name) end # @api private # @return [Expression] def join_or_new tag, other_expr if type == tag @args << other_expr self else Expression.new(tag, self, other_expr) end end end module_function # Returns a match expression that will match `str` parslet atoms. # # @return [Parslet::Accelerator::Expression] # def str variable, *constraints Expression.new(:str, variable, *constraints) end # Returns a match expression that will match `match` parslet atoms. # # @return [Parslet::Accelerator::Expression] # def re variable, *constraints Expression.new(:re, variable, *constraints) end # Returns a match expression that will match `any` parslet atoms. # # @return [Parslet::Accelerator::Expression] # def any Expression.new(:re, ".") end # Given a parslet atom and an expression, will determine if the expression # matches the atom. If successful, returns the bindings into the pattern # that were made. If no bindings had to be made to make the match successful, # the empty hash is returned. # # @param atom [Parslet::Atoms::Base] parslet atom to match against # @param expr [Parslet::Accelerator::Expression] expression to match # @return [nil, Hash] bindings for the match, nil on failure # def match atom, expr engine = Engine.new return engine.bindings if engine.match(atom, expr) end # Constructs an accelerator rule. A rule is a matching expression and the # code that should be executed once the expression could be bound to a # parser. # # Example: # Accelerator.rule(Accelerator.any) { Parslet.match('.') } # def rule expression, &action [expression, action] end # Given a parslet atom and a set of rules, tries to match the rules # recursively through the parslet atom. Once a rule could be matched, # its action block will be called. # # Example: # quote = str('"') # parser = quote >> (quote.absent? >> any).repeat >> quote # # A = Accelerator # for making what follows a bit shorter # optimized_parser = A.apply(parser, # A.rule( (A.str(:x).absent? >> A.any).repeat ) { GobbleUp.new(x) }) # # optimized_parser.parse('"Parsing is now fully optimized! (tm)"') # # @param atom [Parslet::Atoms::Base] a parser to optimize # @param *rules [Parslet::Accelerator::Rule] rules produced by .rule # @return [Parslet::Atoms::Base] optimized parser # def apply atom, *rules Application.new(atom, rules).call end end require 'parslet/accelerator/engine' require 'parslet/accelerator/application'parslet-1.8.2/lib/parslet/expression/0000755000004100000410000000000013242043720017640 5ustar www-datawww-dataparslet-1.8.2/lib/parslet/expression/treetop.rb0000644000004100000410000000475513242043720021662 0ustar www-datawww-dataclass Parslet::Expression::Treetop class Parser < Parslet::Parser root(:expression) rule(:expression) { alternatives } # alternative 'a' / 'b' rule(:alternatives) { (simple >> (spaced('/') >> simple).repeat).as(:alt) } # sequence by simple concatenation 'a' 'b' rule(:simple) { occurrence.repeat(1).as(:seq) } # occurrence modifiers rule(:occurrence) { atom.as(:repetition) >> spaced('*').as(:sign) | atom.as(:repetition) >> spaced('+').as(:sign) | atom.as(:repetition) >> repetition_spec | atom.as(:maybe) >> spaced('?') | atom } rule(:atom) { spaced('(') >> expression.as(:unwrap) >> spaced(')') | dot | string | char_class } # a character class rule(:char_class) { (str('[') >> (str('\\') >> any | str(']').absent? >> any).repeat(1) >> str(']')).as(:match) >> space? } # anything at all rule(:dot) { spaced('.').as(:any) } # recognizing strings rule(:string) { str('\'') >> ( (str('\\') >> any) | (str("'").absent? >> any) ).repeat.as(:string) >> str('\'') >> space? } # repetition specification like {1, 2} rule(:repetition_spec) { spaced('{') >> integer.maybe.as(:min) >> spaced(',') >> integer.maybe.as(:max) >> spaced('}') } rule(:integer) { match['0-9'].repeat(1) } # whitespace handling rule(:space) { match("\s").repeat(1) } rule(:space?) { space.maybe } def spaced(str) str(str) >> space? end end class Transform < Parslet::Transform rule(:repetition => simple(:rep), :sign => simple(:sign)) { min = sign=='+' ? 1 : 0 Parslet::Atoms::Repetition.new(rep, min, nil) } rule(:repetition => simple(:rep), :min => simple(:min), :max => simple(:max)) { Parslet::Atoms::Repetition.new(rep, Integer(min || 0), max && Integer(max) || nil) } rule(:alt => subtree(:alt)) { Parslet::Atoms::Alternative.new(*alt) } rule(:seq => sequence(:s)) { Parslet::Atoms::Sequence.new(*s) } rule(:unwrap => simple(:u)) { u } rule(:maybe => simple(:m)) { |d| d[:m].maybe } rule(:string => simple(:s)) { Parslet::Atoms::Str.new(s) } rule(:match => simple(:m)) { Parslet::Atoms::Re.new(m) } rule(:any => simple(:a)) { Parslet::Atoms::Re.new('.') } end end parslet-1.8.2/lib/parslet/pattern.rb0000644000004100000410000000676413242043720017460 0ustar www-datawww-data# Matches trees against expressions. Trees are formed by arrays and hashes # for expressing membership and sequence. The leafs of the tree are other # classes. # # A tree issued by the parslet library might look like this: # # { # :function_call => { # :name => 'foobar', # :args => [1, 2, 3] # } # } # # A pattern that would match against this tree would be: # # { :function_call => { :name => simple(:name), :args => sequence(:args) }} # # Note that Parslet::Pattern only matches at a given subtree; it wont try # to match recursively. To do that, please use Parslet::Transform. # class Parslet::Pattern def initialize(pattern) @pattern = pattern end # Decides if the given subtree matches this pattern. Returns the bindings # made on a successful match or nil if the match fails. If you specify # bindings to be a hash, the mappings in it will be treated like bindings # made during an attempted match. # # Pattern.new('a').match('a', :foo => 'bar') # => { :foo => 'bar' } # # @param subtree [String, Hash, Array] poro subtree returned by a parse # @param bindings [Hash] variable bindings to be verified # @return [Hash, nil] On success: variable bindings that allow a match. On # failure: nil # def match(subtree, bindings=nil) bindings = bindings && bindings.dup || Hash.new return bindings if element_match(subtree, @pattern, bindings) end # Returns true if the tree element given by +tree+ matches the expression # given by +exp+. This match must respect bindings already made in # +bindings+. Note that bindings is carried along and modified. # # @api private # def element_match(tree, exp, bindings) # p [:elm, tree, exp] if tree.is_a?(Hash) && exp.is_a?(Hash) return element_match_hash(tree, exp, bindings) elsif tree.is_a?(Array) && exp.is_a?(Array) return element_match_ary_single(tree, exp, bindings) else # If elements match exactly, then that is good enough in all cases return true if exp === tree # If exp is a bind variable: Check if the binding matches if exp.respond_to?(:can_bind?) && exp.can_bind?(tree) return element_match_binding(tree, exp, bindings) end # Otherwise: No match (we don't know anything about the element # combination) return false end end # @api private # def element_match_binding(tree, exp, bindings) var_name = exp.variable_name # TODO test for the hidden :_ feature. if var_name && bound_value = bindings[var_name] return bound_value == tree end # New binding: bindings.store var_name, tree return true end # @api private # def element_match_ary_single(sequence, exp, bindings) return false if sequence.size != exp.size return sequence.zip(exp).all? { |elt, subexp| element_match(elt, subexp, bindings) } end # @api private # def element_match_hash(tree, exp, bindings) # Early failure when one hash is bigger than the other return false unless exp.size == tree.size # We iterate over expected pattern, since we demand that the keys that # are there should be in tree as well. exp.each do |expected_key, expected_value| return false unless tree.has_key? expected_key # Recurse into the value and stop early on failure value = tree[expected_key] return false unless element_match(value, expected_value, bindings) end return true end endparslet-1.8.2/lib/parslet/atoms/0000755000004100000410000000000013242043720016564 5ustar www-datawww-dataparslet-1.8.2/lib/parslet/atoms/ignored.rb0000644000004100000410000000101513242043720020535 0ustar www-datawww-data# Ignores the result of a match. # # Example: # # str('foo') # will return 'foo', # str('foo').ignore # will return nil # class Parslet::Atoms::Ignored < Parslet::Atoms::Base attr_reader :parslet def initialize(parslet) super() @parslet = parslet end def apply(source, context, consume_all) success, _ = result = parslet.apply(source, context, consume_all) return result unless success succ(nil) end def to_s_inner(prec) "ignored(#{parslet.to_s(prec)})" end end parslet-1.8.2/lib/parslet/atoms/entity.rb0000644000004100000410000000246113242043720020430 0ustar www-datawww-data# This wraps pieces of parslet definition and gives them a name. The wrapped # piece is lazily evaluated and cached. This has two purposes: # # * Avoid infinite recursion during evaluation of the definition # * Be able to print things by their name, not by their sometimes # complicated content. # # You don't normally use this directly, instead you should generate it by # using the structuring method Parslet.rule. # class Parslet::Atoms::Entity < Parslet::Atoms::Base attr_reader :name, :block def initialize(name, label=nil, &block) super() @name = name @label = label @block = block @parslet = nil end def try(source, context, consume_all) parslet.apply(source, context, consume_all) end def parslet return @parslet unless @parslet.nil? @parslet = @block.call raise_not_implemented if @parslet.nil? @parslet.label = @label @parslet end def to_s_inner(prec) name.to_s.upcase end private def raise_not_implemented trace = caller.reject {|l| l =~ %r{#{Regexp.escape(__FILE__)}}} # blatantly stolen from dependencies.rb in activesupport exception = NotImplementedError.new("rule(#{name.inspect}) { ... } returns nil. Still not implemented, but already used?") exception.set_backtrace(trace) raise exception end end parslet-1.8.2/lib/parslet/atoms/dynamic.rb0000644000004100000410000000130413242043720020533 0ustar www-datawww-data# Evaluates a block at parse time. The result from the block must be a parser # (something which implements #apply). In the first case, the parser will then # be applied to the input, creating the result. # # Dynamic parses are never cached. # # Example: # dynamic { rand < 0.5 ? str('a') : str('b') } # class Parslet::Atoms::Dynamic < Parslet::Atoms::Base attr_reader :block def initialize(block) @block = block end def cached? false end def try(source, context, consume_all) result = block.call(source, context) # Result is a parslet atom. return result.apply(source, context, consume_all) end def to_s_inner(prec) "dynamic { ... }" end end parslet-1.8.2/lib/parslet/atoms/can_flatten.rb0000644000004100000410000001102313242043720021364 0ustar www-datawww-data module Parslet::Atoms # A series of helper functions that have the common topic of flattening # result values into the intermediary tree that consists of Ruby Hashes and # Arrays. # # This module has one main function, #flatten, that takes an annotated # structure as input and returns the reduced form that users expect from # Atom#parse. # # NOTE: Since all of these functions are just that, functions without # side effects, they are in a module and not in a class. Its hard to draw # the line sometimes, but this is beyond. # module CanFlatten # Takes a mixed value coming out of a parslet and converts it to a return # value for the user by dropping things and merging hashes. # # Named is set to true if this result will be embedded in a Hash result from # naming something using .as(...). It changes the folding # semantics of repetition. # def flatten(value, named=false) # Passes through everything that isn't an array of things return value unless value.instance_of? Array # Extracts the s-expression tag tag, *tail = value # Merges arrays: result = tail. map { |e| flatten(e) } # first flatten each element case tag when :sequence return flatten_sequence(result) when :maybe return named ? result.first : result.first || '' when :repetition return flatten_repetition(result, named) end fail "BUG: Unknown tag #{tag.inspect}." end # Lisp style fold left where the first element builds the basis for # an inject. # def foldl(list, &block) return '' if list.empty? list[1..-1].inject(list.first, &block) end # Flatten results from a sequence of parslets. # # @api private # def flatten_sequence(list) foldl(list.compact) { |r, e| # and then merge flat elements merge_fold(r, e) } end # @api private def merge_fold(l, r) # equal pairs: merge. ---------------------------------------------------- if l.class == r.class if l.is_a?(Hash) warn_about_duplicate_keys(l, r) return l.merge(r) else return l + r end end # unequal pairs: hoist to same level. ------------------------------------ # Maybe classes are not equal, but both are stringlike? if l.respond_to?(:to_str) && r.respond_to?(:to_str) # if we're merging a String with a Slice, the slice wins. return r if r.respond_to? :to_slice return l if l.respond_to? :to_slice fail "NOTREACHED: What other stringlike classes are there?" end # special case: If one of them is a string/slice, the other is more important return l if r.respond_to? :to_str return r if l.respond_to? :to_str # otherwise just create an array for one of them to live in return l + [r] if r.class == Hash return [l] + r if l.class == Hash fail "Unhandled case when foldr'ing sequence." end # Flatten results from a repetition of a single parslet. named indicates # whether the user has named the result or not. If the user has named # the results, we want to leave an empty list alone - otherwise it is # turned into an empty string. # # @api private # def flatten_repetition(list, named) if list.any? { |e| e.instance_of?(Hash) } # If keyed subtrees are in the array, we'll want to discard all # strings inbetween. To keep them, name them. return list.select { |e| e.instance_of?(Hash) } end if list.any? { |e| e.instance_of?(Array) } # If any arrays are nested in this array, flatten all arrays to this # level. return list. select { |e| e.instance_of?(Array) }. flatten(1) end # Consistent handling of empty lists, when we act on a named result return [] if named && list.empty? # If there are only strings, concatenate them and return that. foldl(list.compact) { |s,e| s+e } end # That annoying warning 'Duplicate subtrees while merging result' comes # from here. You should add more '.as(...)' names to your intermediary tree. # def warn_about_duplicate_keys(h1, h2) d = h1.keys & h2.keys unless d.empty? warn "Duplicate subtrees while merging result of \n #{self.inspect}\nonly the values"+ " of the latter will be kept. (keys: #{d.inspect})" end end end endparslet-1.8.2/lib/parslet/atoms/capture.rb0000644000004100000410000000170013242043720020552 0ustar www-datawww-data # Stores the result of matching an atom against input in the #captures in # parse context. Doing so will allow you to pull parts of the ongoing parse # out later and use them to match other pieces of input. # # Example: # # After this, context.captures[:an_a] returns 'a' # str('a').capture(:an_a) # # # Capture and use of the capture: (matches either 'aa' or 'bb') # match['ab'].capture(:first) >> # dynamic { |src, ctx| str(ctx.captures[:first]) } # class Parslet::Atoms::Capture < Parslet::Atoms::Base attr_reader :parslet, :name def initialize(parslet, name) super() @parslet, @name = parslet, name end def apply(source, context, consume_all) success, value = result = parslet.apply(source, context, consume_all) if success context.captures[name.to_sym] = flatten(value) end return result end def to_s_inner(prec) "(#{name.inspect} = #{parslet.to_s(prec)})" end end parslet-1.8.2/lib/parslet/atoms/context.rb0000644000004100000410000000576013242043720020605 0ustar www-datawww-datamodule Parslet::Atoms # Helper class that implements a transient cache that maps position and # parslet object to results. This is used for memoization in the packrat # style. # # Also, error reporter is stored here and error reporting happens through # this class. This makes the reporting pluggable. # class Context # @param reporter [#err, #err_at] Error reporter (leave empty for default # reporter) def initialize(reporter=Parslet::ErrorReporter::Tree.new) @cache = Hash.new { |h, k| h[k] = {} } @reporter = reporter @captures = Parslet::Scope.new end # Caches a parse answer for obj at source.pos. Applying the same parslet # at one position of input always yields the same result, unless the input # has changed. # # We need the entire source here so we can ask for how many characters # were consumed by a successful parse. Imitation of such a parse must # advance the input pos by the same amount of bytes. # def try_with_cache(obj, source, consume_all) beg = source.bytepos # Not in cache yet? Return early. unless entry = lookup(obj, beg) result = obj.try(source, self, consume_all) if obj.cached? set obj, beg, [result, source.bytepos-beg] end return result end # the condition in unless has returned true, so entry is not nil. result, advance = entry # The data we're skipping here has been read before. (since it is in # the cache) PLUS the actual contents are not interesting anymore since # we know obj matches at beg. So skip reading. source.bytepos = beg + advance return result end # Report an error at a given position. # @see ErrorReporter # def err_at(*args) return [false, @reporter.err_at(*args)] if @reporter return [false, nil] end # Report an error. # @see ErrorReporter # def err(*args) return [false, @reporter.err(*args)] if @reporter return [false, nil] end # Report a successful parse. # @see ErrorReporter::Contextual # def succ(*args) return [true, @reporter.succ(*args)] if @reporter return [true, nil] end # Returns the current captures made on the input (see # Parslet::Atoms::Base#capture). Use as follows: # # context.captures[:foobar] # => returns capture :foobar # attr_reader :captures # Starts a new scope. Use the #scope method of Parslet::Atoms::DSL # to call this. # def scope captures.push yield ensure captures.pop end private # NOTE These methods use #object_id directly, since that seems to bring the # most performance benefit. This is a hot spot; going through # Atoms::Base#hash doesn't yield as much. # def lookup(obj, pos) @cache[pos][obj.object_id] end def set(obj, pos, val) @cache[pos][obj.object_id] = val end end endparslet-1.8.2/lib/parslet/atoms/alternative.rb0000644000004100000410000000250513242043720021431 0ustar www-datawww-data # Alternative during matching. Contains a list of parslets that is tried each # one in turn. Only fails if all alternatives fail. # # Example: # # str('a') | str('b') # matches either 'a' or 'b' # class Parslet::Atoms::Alternative < Parslet::Atoms::Base attr_reader :alternatives # Constructs an Alternative instance using all given parslets in the order # given. This is what happens if you call '|' on existing parslets, like # this: # # str('a') | str('b') # def initialize(*alternatives) super() @alternatives = alternatives end #--- # Don't construct a hanging tree of Alternative parslets, instead store them # all here. This reduces the number of objects created. #+++ def |(parslet) self.class.new(*@alternatives + [parslet]) end def error_msg @error_msg ||= "Expected one of #{alternatives.inspect}" end def try(source, context, consume_all) errors = alternatives.map { |a| success, value = result = a.apply(source, context, consume_all) return result if success # Aggregate all errors value } # If we reach this point, all alternatives have failed. context.err(self, source, error_msg, errors) end precedence ALTERNATE def to_s_inner(prec) alternatives.map { |a| a.to_s(prec) }.join(' / ') end end parslet-1.8.2/lib/parslet/atoms/repetition.rb0000644000004100000410000000443413242043720021300 0ustar www-datawww-data # Matches a parslet repeatedly. # # Example: # # str('a').repeat(1,3) # matches 'a' at least once, but at most three times # str('a').maybe # matches 'a' if it is present in the input (repeat(0,1)) # class Parslet::Atoms::Repetition < Parslet::Atoms::Base attr_reader :min, :max, :parslet def initialize(parslet, min, max, tag=:repetition) super() raise ArgumentError, "Asking for zero repetitions of a parslet. (#{parslet.inspect} repeating #{min},#{max})" \ if max == 0 @parslet = parslet @min = min @max = max @tag = tag end def error_msgs @error_msgs ||= { minrep: "Expected at least #{min} of #{parslet.inspect}", unconsumed: 'Extra input after last repetition' } end def try(source, context, consume_all) occ = 0 accum = [@tag] # initialize the result array with the tag (for flattening) start_pos = source.pos break_on = nil loop do success, value = parslet.apply(source, context, false) break_on = value break unless success occ += 1 accum << value # If we're not greedy (max is defined), check if that has been reached. return succ(accum) if max && occ>=max end # Last attempt to match parslet was a failure, failure reason in break_on. # Greedy matcher has produced a failure. Check if occ (which will # contain the number of successes) is >= min. return context.err_at( self, source, error_msgs[:minrep], start_pos, [break_on]) if occ < min # consume_all is true, that means that we're inside the part of the parser # that should consume the input completely. Repetition failing here means # probably that we didn't. # # We have a special clause to create an error here because otherwise # break_on would get thrown away. It turns out, that contains very # interesting information in a lot of cases. # return context.err( self, source, error_msgs[:unconsumed], [break_on]) if consume_all && source.chars_left>0 return succ(accum) end precedence REPETITION def to_s_inner(prec) minmax = "{#{min}, #{max}}" minmax = '?' if min == 0 && max == 1 parslet.to_s(prec) + minmax end end parslet-1.8.2/lib/parslet/atoms/named.rb0000644000004100000410000000127713242043720020204 0ustar www-datawww-data# Names a match to influence tree construction. # # Example: # # str('foo') # will return 'foo', # str('foo').as(:foo) # will return :foo => 'foo' # class Parslet::Atoms::Named < Parslet::Atoms::Base attr_reader :parslet, :name def initialize(parslet, name) super() @parslet, @name = parslet, name end def apply(source, context, consume_all) success, value = result = parslet.apply(source, context, consume_all) return result unless success succ( produce_return_value( value)) end def to_s_inner(prec) "#{name}:#{parslet.to_s(prec)}" end private def produce_return_value(val) { name => flatten(val, true) } end end parslet-1.8.2/lib/parslet/atoms/re.rb0000644000004100000410000000200213242043720017511 0ustar www-datawww-data# Matches a special kind of regular expression that only ever matches one # character at a time. Useful members of this family are: character # ranges, \\w, \\d, \\r, \\n, ... # # 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) super() @match = match.to_s @re = Regexp.new(self.match, Regexp::MULTILINE) end def error_msgs @error_msgs ||= { premature: 'Premature end of input', failed: "Failed to match #{match.inspect[1..-2]}" } end 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 def to_s_inner(prec) match.inspect[1..-2] end end parslet-1.8.2/lib/parslet/atoms/lookahead.rb0000644000004100000410000000261513242043720021044 0ustar www-datawww-data# Either positive or negative lookahead, doesn't consume its input. # # Example: # # str('foo').present? # matches when the input contains 'foo', but leaves it # class Parslet::Atoms::Lookahead < Parslet::Atoms::Base attr_reader :positive attr_reader :bound_parslet def initialize(bound_parslet, positive=true) super() # Model positive and negative lookahead by testing this flag. @positive = positive @bound_parslet = bound_parslet end def error_msgs @error_msgs ||= { :positive => ["Input should start with ", bound_parslet], :negative => ["Input should not start with ", bound_parslet] } end def try(source, context, consume_all) rewind_pos = source.bytepos error_pos = source.pos success, _ = bound_parslet.apply(source, context, consume_all) if positive return succ(nil) if success return context.err_at(self, source, error_msgs[:positive], error_pos) else return succ(nil) unless success return context.err_at(self, source, error_msgs[:negative], error_pos) end # This is probably the only parslet that rewinds its input in #try. # Lookaheads NEVER consume their input, even on success, that's why. ensure source.bytepos = rewind_pos end precedence LOOKAHEAD def to_s_inner(prec) @char = positive ? '&' : '!' "#{@char}#{bound_parslet.to_s(prec)}" end end parslet-1.8.2/lib/parslet/atoms/str.rb0000644000004100000410000000162413242043720017724 0ustar www-datawww-data# Matches a string of characters. # # Example: # # str('foo') # matches 'foo' # class Parslet::Atoms::Str < Parslet::Atoms::Base attr_reader :str def initialize(str) super() @str = str.to_s @pat = Regexp.new(Regexp.escape(str)) @len = str.size end def error_msgs @error_msgs ||= { premature: 'Premature end of input', 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 return context.err_at( self, source, [error_msgs[:failed], source.consume(@len)], error_pos) end def to_s_inner(prec) "'#{str}'" end end parslet-1.8.2/lib/parslet/atoms/sequence.rb0000644000004100000410000000204613242043720020723 0ustar www-datawww-data# A sequence of parslets, matched from left to right. Denoted by '>>' # # Example: # # str('a') >> str('b') # matches 'a', then 'b' # class Parslet::Atoms::Sequence < Parslet::Atoms::Base attr_reader :parslets def initialize(*parslets) super() @parslets = parslets end def error_msgs @error_msgs ||= { failed: "Failed to match sequence (#{inspect})" } end def >>(parslet) self.class.new(* @parslets+[parslet]) end def try(source, context, consume_all) # Presize an array result = Array.new(parslets.size + 1) result[0] = :sequence parslets.each_with_index do |p, idx| child_consume_all = consume_all && (idx == parslets.size-1) success, value = p.apply(source, context, child_consume_all) unless success return context.err(self, source, error_msgs[:failed], [value]) end result[idx+1] = value end return succ(result) end precedence SEQUENCE def to_s_inner(prec) parslets.map { |p| p.to_s(prec) }.join(' ') end end parslet-1.8.2/lib/parslet/atoms/visitor.rb0000644000004100000410000000361413242043720020614 0ustar www-datawww-data# Augments all parslet atoms with an accept method that will call back # to the visitor given. # module Parslet::Atoms class Base def accept(visitor) raise NotImplementedError, "No #accept method on #{self.class.name}." end end class Str # Call back visitors #visit_str method. See parslet/export for an example. # def accept(visitor) visitor.visit_str(str) end end class Entity # Call back visitors #visit_entity method. See parslet/export for an # example. # def accept(visitor) visitor.visit_entity(name, block) end end class Named # Call back visitors #visit_named method. See parslet/export for an # example. # def accept(visitor) visitor.visit_named(name, parslet) end end class Sequence # Call back visitors #visit_sequence method. See parslet/export for an # example. # def accept(visitor) visitor.visit_sequence(parslets) end end class Repetition # Call back visitors #visit_repetition method. See parslet/export for an # example. # def accept(visitor) visitor.visit_repetition(@tag, min, max, parslet) end end class Alternative # Call back visitors #visit_alternative method. See parslet/export for an # example. # def accept(visitor) visitor.visit_alternative(alternatives) end end class Lookahead # Call back visitors #visit_lookahead method. See parslet/export for an # example. # def accept(visitor) visitor.visit_lookahead(positive, bound_parslet) end end class Re # Call back visitors #visit_re method. See parslet/export for an example. # def accept(visitor) visitor.visit_re(match) end end end class Parslet::Parser # Call back visitors #visit_parser method. # def accept(visitor) visitor.visit_parser(root) end end parslet-1.8.2/lib/parslet/atoms/base.rb0000644000004100000410000001170013242043720020022 0ustar www-datawww-data# Base class for all parslets, handles orchestration of calls and implements # a lot of the operator and chaining methods. # # Also see Parslet::Atoms::DSL chaining parslet atoms together. # class Parslet::Atoms::Base include Parslet::Atoms::Precedence include Parslet::Atoms::DSL include Parslet::Atoms::CanFlatten # Parslet label as provided in grammar attr_accessor :label # Given a string or an IO object, this will attempt a parse of its contents # and return a result. If the parse fails, a Parslet::ParseFailed exception # will be thrown. # # @param io [String, Source] input for the parse process # @option options [Parslet::ErrorReporter] :reporter error reporter to use, # defaults to Parslet::ErrorReporter::Tree # @option options [Boolean] :prefix Should a prefix match be accepted? # (default: false) # @return [Hash, Array, Parslet::Slice] PORO (Plain old Ruby object) result # tree # def parse(io, options={}) source = io.respond_to?(:line_and_column) ? io : Parslet::Source.new(io) # Try to cheat. Assuming that we'll be able to parse the input, don't # run error reporting code. success, value = setup_and_apply(source, nil, !options[:prefix]) # If we didn't succeed the parse, raise an exception for the user. # Stack trace will be off, but the error tree should explain the reason # it failed. unless success # Cheating has not paid off. Now pay the cost: Rerun the parse, # gathering error information in the process. reporter = options[:reporter] || Parslet::ErrorReporter::Tree.new source.bytepos = 0 success, value = setup_and_apply(source, reporter, !options[:prefix]) fail "Assertion failed: success was true when parsing with reporter" \ if success # Value is a Parslet::Cause, which can be turned into an exception: value.raise fail "NEVER REACHED" end # assert: success is true # Extra input is now handled inline with the rest of the parsing. If # really we have success == true, prefix: false and still some input # is left dangling, that is a BUG. if !options[:prefix] && source.chars_left > 0 fail "BUG: New error strategy should not reach this point." end return flatten(value) end # Creates a context for parsing and applies the current atom to the input. # Returns the parse result. # # @return [] Result of the parse. If the first member is # true, the parse has succeeded. def setup_and_apply(source, error_reporter, consume_all) context = Parslet::Atoms::Context.new(error_reporter) apply(source, context, consume_all) end # Calls the #try method of this parslet. Success consumes input, error will # rewind the input. # # @param source [Parslet::Source] source to read input from # @param context [Parslet::Atoms::Context] context to use for the parsing # @param consume_all [Boolean] true if the current parse must consume # all input by itself. def apply(source, context, consume_all=false) old_pos = source.bytepos success, _ = result = context.try_with_cache(self, source, consume_all) if success # Notify context context.succ(source) # If a consume_all parse was made and doesn't result in the consumption # of all the input, that is considered an error. if consume_all && source.chars_left>0 # Read 10 characters ahead. Why ten? I don't know. offending_pos = source.pos offending_input = source.consume(10) # Rewind input (as happens always in error case) source.bytepos = old_pos return context.err_at( self, source, "Don't know what to do with #{offending_input.to_s.inspect}", offending_pos ) end # Looks like the parse was successful after all. Don't rewind the input. return result end # We only reach this point if the parse has failed. Rewind the input. source.bytepos = old_pos return result end # Override this in your Atoms::Base subclasses to implement parsing # behaviour. # def try(source, context, consume_all) raise NotImplementedError, \ "Atoms::Base doesn't have behaviour, please implement #try(source, context)." end # Returns true if this atom can be cached in the packrat cache. Most parslet # atoms are cached, so this always returns true, unless overridden. # def cached? true end # Debug printing - in Treetop syntax. # def self.precedence(prec) define_method(:precedence) { prec } end precedence BASE def to_s(outer_prec=OUTER) str = label || to_s_inner(precedence) if outer_prec < precedence "(#{str})" else str end end def inspect to_s(OUTER) end private # Produces an instance of Success and returns it. # def succ(result) [true, result] end end parslet-1.8.2/lib/parslet/atoms/dsl.rb0000644000004100000410000000625013242043720017676 0ustar www-datawww-data # A mixin module that defines operations that can be called on any subclass # of Parslet::Atoms::Base. These operations make parslets atoms chainable and # allow combination of parslet atoms to form bigger parsers. # # Example: # # str('foo') >> str('bar') # str('f').repeat # any.absent? # also called The Epsilon # module Parslet::Atoms::DSL # Construct a new atom that repeats the current atom min times at least and # at most max times. max can be nil to indicate that no maximum is present. # # Example: # # match any number of 'a's # str('a').repeat # # # match between 1 and 3 'a's # str('a').repeat(1,3) # def repeat(min=0, max=nil) Parslet::Atoms::Repetition.new(self, min, max) end # Returns a new parslet atom that is only maybe present in the input. This # is synonymous to calling #repeat(0,1). Generated tree value will be # either nil (if atom is not present in the input) or the matched subtree. # # Example: # str('foo').maybe # def maybe Parslet::Atoms::Repetition.new(self, 0, 1, :maybe) end # Returns a new parslet atom that will not show up in the output. This # is synonymous to calling #repeat(0,1). Generated tree value will always be # nil. # # Example: # str('foo').ignore # def ignore Parslet::Atoms::Ignored.new(self) end # Chains two parslet atoms together as a sequence. # # Example: # str('a') >> str('b') # def >>(parslet) Parslet::Atoms::Sequence.new(self, parslet) end # Chains two parslet atoms together to express alternation. A match will # always be attempted with the parslet on the left side first. If it doesn't # match, the right side will be tried. # # Example: # # matches either 'a' OR 'b' # str('a') | str('b') # def |(parslet) Parslet::Atoms::Alternative.new(self, parslet) end # Tests for absence of a parslet atom in the input stream without consuming # it. # # Example: # # Only proceed the parse if 'a' is absent. # str('a').absent? # def absent? Parslet::Atoms::Lookahead.new(self, false) end # Tests for presence of a parslet atom in the input stream without consuming # it. # # Example: # # Only proceed the parse if 'a' is present. # str('a').present? # def present? Parslet::Atoms::Lookahead.new(self, true) end # Alias for present? that will disappear in 2.0 (deprecated) # alias prsnt? present? # Alias for absent? that will disappear in 2.0 (deprecated) # alias absnt? absent? # Marks a parslet atom as important for the tree output. This must be used # to achieve meaningful output from the #parse method. # # Example: # str('a').as(:b) # will produce {:b => 'a'} # def as(name) Parslet::Atoms::Named.new(self, name) end # Captures a part of the input and stores it under the name given. This # is very useful to create self-referential parses. A capture stores # the result of its parse (may be complex) on a successful parse action. # # Example: # str('a').capture(:b) # will store captures[:b] == 'a' # def capture(name) Parslet::Atoms::Capture.new(self, name) end endparslet-1.8.2/lib/parslet/atoms/scope.rb0000644000004100000410000000076013242043720020225 0ustar www-datawww-data# Starts a new scope in the parsing process. Please also see the #captures # method. # class Parslet::Atoms::Scope < Parslet::Atoms::Base attr_reader :block def initialize(block) super() @block = block end def cached? false end def apply(source, context, consume_all) context.scope do parslet = block.call return parslet.apply(source, context, consume_all) end end def to_s_inner(prec) "scope { #{block.call.to_s(prec)} }" end end parslet-1.8.2/lib/parslet/atoms/infix.rb0000644000004100000410000000621613242043720020233 0ustar www-datawww-dataclass Parslet::Atoms::Infix < Parslet::Atoms::Base attr_reader :element, :operations, :reducer def initialize(element, operations, &reducer) super() @element = element @operations = operations @reducer = reducer || lambda { |left, op, right| {l: left, o: op, r: right} } end def try(source, context, consume_all) return catch_error { return succ( produce_tree( precedence_climb(source, context, consume_all))) } end # Turns an array of the form ['1', '+', ['2', '*', '3']] into a hash that # reflects the same structure. # def produce_tree(ary) return ary unless ary.kind_of? Array left = ary.shift until ary.empty? op, right = ary.shift(2) # p [left, op, right] if right.kind_of? Array # Subexpression -> Subhash left = reducer.call(left, op, produce_tree(right)) else left = reducer.call(left, op, right) end end left end # A precedence climbing algorithm married to parslet, as described here # http://eli.thegreenplace.net/2012/08/02/parsing-expressions-by-precedence-climbing/ # # @note Error handling in this routine is done by throwing :error and # as a value the error to return to parslet. This avoids cluttering # the recursion logic here with parslet error handling. # def precedence_climb(source, context, consume_all, current_prec=1, needs_element=false) result = [] # To even begin parsing an arithmetic expression, there needs to be # at least one @element. success, value = @element.apply(source, context, false) unless success abort context.err(self, source, "#{@element.inspect} was expected", [value]) end result << flatten(value, true) # Loop until we fail on operator matching or until input runs out. loop do op_pos = source.bytepos op_match, prec, assoc = match_operation(source, context, false) # If no operator could be matched here, one of several cases # applies: # # - end of file # - end of expression # - syntax error # # We abort matching the expression here. break unless op_match if prec >= current_prec next_prec = (assoc == :left) ? prec+1 : prec result << op_match result << precedence_climb( source, context, consume_all, next_prec, true) else source.bytepos = op_pos return unwrap(result) end end return unwrap(result) end def unwrap expr expr.size == 1 ? expr.first : expr end def match_operation(source, context, consume_all) errors = [] @operations.each do |op_atom, prec, assoc| success, value = op_atom.apply(source, context, consume_all) return flatten(value, true), prec, assoc if success # assert: this was in fact an error, accumulate errors << value end return nil end def abort(error) throw :error, error end def catch_error catch(:error) { yield } end def to_s_inner(prec) ops = @operations.map { |o, _, _| o.inspect }.join(', ') "infix_expression(#{@element.inspect}, [#{ops}])" end endparslet-1.8.2/lib/parslet/context.rb0000644000004100000410000000074613242043720017461 0ustar www-datawww-data# Provides a context for tree transformations to run in. The context allows # accessing each of the bindings in the bindings hash as local method. # # Example: # # ctx = Context.new(:a => :b) # ctx.instance_eval do # a # => :b # end # # @api private class Parslet::Context include Parslet def initialize(bindings) bindings.each do |key, value| singleton_class.send(:define_method, key) { value } instance_variable_set("@#{key}", value) end end endparslet-1.8.2/lib/parslet/parser.rb0000644000004100000410000000354413242043720017270 0ustar www-datawww-data # The base class for all your parsers. Use as follows: # # require 'parslet' # # class MyParser < Parslet::Parser # rule(:a) { str('a').repeat } # root(:a) # end # # pp MyParser.new.parse('aaaa') # => 'aaaa' # pp MyParser.new.parse('bbbb') # => Parslet::Atoms::ParseFailed: # # Don't know what to do with bbbb at line 1 char 1. # # Parslet::Parser is also a grammar atom. This means that you can mix full # fledged parsers freely with small parts of a different parser. # # Example: # class ParserA < Parslet::Parser # root :aaa # rule(:aaa) { str('a').repeat(3,3) } # end # class ParserB < Parslet::Parser # root :expression # rule(:expression) { str('b') >> ParserA.new >> str('b') } # end # # In the above example, ParserB would parse something like 'baaab'. # class Parslet::Parser < Parslet::Atoms::Base include Parslet class << self # class methods # Define the parsers #root function. This is the place where you start # parsing; if you have a rule for 'file' that describes what should be # in a file, this would be your root declaration: # # class Parser # root :file # rule(:file) { ... } # end # # #root declares a 'parse' function that works just like the parse # function that you can call on a simple parslet, taking a string as input # and producing parse output. # # In a way, #root is a shorthand for: # # def parse(str) # your_parser_root.parse(str) # end # def root(name) undef_method :root if method_defined? :root define_method(:root) do self.send(name) end end end def try(source, context, consume_all) root.try(source, context, consume_all) end def to_s_inner(prec) root.to_s(prec) end end parslet-1.8.2/lib/parslet/convenience.rb0000644000004100000410000000127613242043720020270 0ustar www-datawww-dataclass Parslet::Atoms::Base # Packages the common idiom # # begin # tree = parser.parse('something') # rescue Parslet::ParseFailed => error # puts parser.parse_failure_cause.ascii_tree # end # # into a convenient method. # # Usage: # # require 'parslet' # require 'parslet/convenience' # # class FooParser < Parslet::Parser # rule(:foo) { str('foo') } # root(:foo) # end # # FooParser.new.parse_with_debug('bar') # # @see Parslet::Atoms::Base#parse # def parse_with_debug str, opts={} parse str, opts rescue Parslet::ParseFailed => error puts error.parse_failure_cause.ascii_tree end endparslet-1.8.2/lib/parslet/atoms.rb0000644000004100000410000000204313242043720017110 0ustar www-datawww-data # This is where parslets name comes from: Small parser atoms. # module Parslet::Atoms # The precedence module controls parenthesis during the #inspect printing # of parslets. It is not relevant to other aspects of the parsing. # module Precedence BASE = 1 # everything else LOOKAHEAD = 2 # &SOMETHING REPETITION = 3 # 'a'+, 'a'? SEQUENCE = 4 # 'a' 'b' ALTERNATE = 5 # 'a' | 'b' OUTER = 6 # printing is done here. end require 'parslet/atoms/can_flatten' require 'parslet/atoms/context' require 'parslet/atoms/dsl' require 'parslet/atoms/base' require 'parslet/atoms/ignored' require 'parslet/atoms/named' require 'parslet/atoms/lookahead' require 'parslet/atoms/alternative' require 'parslet/atoms/sequence' require 'parslet/atoms/repetition' require 'parslet/atoms/re' require 'parslet/atoms/str' require 'parslet/atoms/entity' require 'parslet/atoms/capture' require 'parslet/atoms/dynamic' require 'parslet/atoms/scope' require 'parslet/atoms/infix' end parslet-1.8.2/lib/parslet/position.rb0000644000004100000410000000050713242043720017634 0ustar www-datawww-data # Encapsules the concept of a position inside a string. # class Parslet::Position attr_reader :bytepos include Comparable def initialize string, bytepos @string = string @bytepos = bytepos end def charpos @string.byteslice(0, @bytepos).size end def <=> b self.bytepos <=> b.bytepos end endparslet-1.8.2/lib/parslet/slice.rb0000644000004100000410000000475313242043720017076 0ustar www-datawww-data # A slice is a small part from the parse input. A slice mainly behaves like # any other string, except that it remembers where it came from (offset in # original input). # # == Extracting line and column # # Using the #line_and_column method, you can extract the line and column in # the original input where this slice starts. # # Example: # slice.line_and_column # => [1, 13] # slice.offset # => 12 # # == Likeness to strings # # Parslet::Slice behaves in many ways like a Ruby String. This likeness # however is not complete - many of the myriad of operations String supports # are not yet in Slice. You can always extract the internal string instance by # calling #to_s. # # These omissions are somewhat intentional. Rather than maintaining a full # delegation, we opt for a partial emulation that gets the job done. # class Parslet::Slice attr_reader :str attr_reader :position attr_reader :line_cache # Construct a slice using a string, an offset and an optional line cache. # The line cache should be able to answer to the #line_and_column message. # def initialize(position, string, line_cache=nil) @position = position @str = string @line_cache = line_cache end def offset @position.charpos end # Compares slices to other slices or strings. # def == other str == other end # Match regular expressions. # def match(regexp) str.match(regexp) end # Returns the slices size in characters. # def size str.size end alias length size # Concatenate two slices; it is assumed that the second slice begins # where the first one ends. The offset of the resulting slice is the same # as the one of this slice. # def +(other) self.class.new(@position, str + other.to_s, line_cache) end # Returns a tuple referring to the original input. # def line_and_column raise ArgumentError, "No line cache was given, cannot infer line and column." \ unless line_cache line_cache.line_and_column(@position.bytepos) end # Conversion operators ----------------------------------------------------- def to_str str end alias to_s to_str def to_slice self end def to_sym str.to_sym end def to_int Integer(str) end def to_i str.to_i end def to_f str.to_f end # Inspection & Debugging --------------------------------------------------- # Prints the slice as "string"@offset. def inspect str.inspect + "@#{offset}" end end parslet-1.8.2/lib/parslet/source/0000755000004100000410000000000013242043720016741 5ustar www-datawww-dataparslet-1.8.2/lib/parslet/source/line_cache.rb0000644000004100000410000000553113242043720021344 0ustar www-datawww-data class Parslet::Source # A cache for line start positions. # class LineCache def initialize # Stores line endings as a simple position number. The first line always # starts at 0; numbers beyond the biggest entry are on any line > size, # but probably make a scan to that position neccessary. @line_ends = [] @line_ends.extend RangeSearch @last_line_end = nil end # Returns a tuple for the given input position. Input # position must be given as byte offset into original string. # def line_and_column(pos) pos = pos.bytepos if pos.respond_to? :bytepos eol_idx = @line_ends.lbound(pos) if eol_idx # eol_idx points to the offset that ends the current line. # Let's try to find the offset that starts it: offset = eol_idx>0 && @line_ends[eol_idx-1] || 0 return [eol_idx+1, pos-offset+1] else # eol_idx is nil, that means that we're beyond the last line end that # we know about. Pretend for now that we're just on the last line. offset = @line_ends.last || 0 return [@line_ends.size+1, pos-offset+1] end end def scan_for_line_endings(start_pos, buf) return unless buf buf = StringScanner.new(buf) return unless buf.exist?(/\n/) ## If we have already read part or all of buf, we already know about ## line ends in that portion. remove it and correct cur (search index) if @last_line_end && start_pos < @last_line_end # Let's not search the range from start_pos to last_line_end again. buf.pos = @last_line_end - start_pos end ## Scan the string for line endings; store the positions of all endings ## in @line_ends. while buf.skip_until(/\n/) @last_line_end = start_pos + buf.pos @line_ends << @last_line_end end end end # Mixin for arrays that implicitly give a number of ranges, where one range # begins where the other one ends. # # Example: # # [10, 20, 30] # # would describe [0, 10], (10, 20], (20, 30] # module RangeSearch def find_mid(left, right) # NOTE: Jonathan Hinkle reported that when mathn is required, just # dividing and relying on the integer truncation is not enough. left + ((right - left) / 2).floor end # Scans the array for the first number that is > than bound. Returns the # index of that number. # def lbound(bound) return nil if empty? return nil unless last > bound left = 0 right = size - 1 loop do mid = find_mid(left, right) if self[mid] > bound right = mid else # assert: self[mid] <= bound left = mid+1 end if right <= left return right end end end end end parslet-1.8.2/lib/parslet/expression.rb0000644000004100000410000000223113242043720020163 0ustar www-datawww-data # Allows specifying rules as strings using the exact same grammar that treetop # does, minus the actions. This is on one hand a good example of a fully # fledged parser and on the other hand might even turn out really useful. # # This can be viewed as an extension to parslet and might even be hosted in # its own gem one fine day. # class Parslet::Expression include Parslet autoload :Treetop, 'parslet/expression/treetop' # Creates a parslet from a foreign language expression. # # Example: # # Parslet::Expression.new("'a' 'b'") # def initialize(str, opts={}, context=self) @type = opts[:type] || :treetop @exp = str @parslet = transform( parse(str)) end # Transforms the parse tree into a parslet expression. # def transform(tree) transform = Treetop::Transform.new # pp tree transform.apply(tree) rescue warn "Could not transform: " + tree.inspect raise end # Parses the string and returns a parse tree. # def parse(str) parser = Treetop::Parser.new parser.parse(str) end # Turns this expression into a parslet. # def to_parslet @parslet end endparslet-1.8.2/lib/parslet/pattern/0000755000004100000410000000000013242043720017116 5ustar www-datawww-dataparslet-1.8.2/lib/parslet/pattern/binding.rb0000644000004100000410000000235213242043720021057 0ustar www-datawww-data # Used internally for representing a bind placeholder in a Parslet::Transform # pattern. This is the superclass for all bindings. # # It defines the most permissive kind of bind, the one that matches any subtree # whatever it looks like. # class Parslet::Pattern::SubtreeBind < Struct.new(:symbol) def variable_name symbol end def inspect "#{bind_type_name}(#{symbol.inspect})" end def can_bind?(subtree) true end private def bind_type_name if md=self.class.name.match(/(\w+)Bind/) md.captures.first.downcase else # This path should never be used, but since this is for inspection only, # let's not raise. 'unknown_bind' end end end # Binds a symbol to a simple subtree, one that is not either a sequence of # elements or a collection of attributes. # class Parslet::Pattern::SimpleBind < Parslet::Pattern::SubtreeBind def can_bind?(subtree) not [Hash, Array].include?(subtree.class) end end # Binds a symbol to a sequence of simple leafs ([element1, element2, ...]) # class Parslet::Pattern::SequenceBind < Parslet::Pattern::SubtreeBind def can_bind?(subtree) subtree.kind_of?(Array) && (not subtree.any? { |el| [Hash, Array].include?(el.class) }) end endparslet-1.8.2/lib/parslet/accelerator/0000755000004100000410000000000013242043720017725 5ustar www-datawww-dataparslet-1.8.2/lib/parslet/accelerator/application.rb0000644000004100000410000000331213242043720022554 0ustar www-datawww-data # @api private module Parslet::Accelerator class Application def initialize atom, rules @atom = atom @rules = rules end def call @atom.accept(self) end def visit_parser(root) transform root.accept(self) end def visit_entity(name, block) transform Parslet::Atoms::Entity.new(name) { block.call.accept(self) } end def visit_named(name, atom) transform Parslet::Atoms::Named.new(atom.accept(self), name) end def visit_repetition(tag, min, max, atom) transform Parslet::Atoms::Repetition.new(atom.accept(self), min, max, tag) end def visit_alternative(alternatives) transform Parslet::Atoms::Alternative.new( *alternatives.map { |atom| atom.accept(self) }) end def visit_sequence(sequence) transform Parslet::Atoms::Sequence.new( *sequence.map { |atom| atom.accept(self) }) end def visit_lookahead(positive, atom) transform Parslet::Atoms::Lookahead.new(atom, positive) end def visit_re(regexp) transform Parslet::Atoms::Re.new(regexp) end def visit_str(str) transform Parslet::Atoms::Str.new(str) end def transform atom @rules.each do |expr, action| # Try and match each rule in turn binding = Parslet::Accelerator.match(atom, expr) if binding # On a successful match, allow the rule action to transform the # parslet into something new. ctx = Parslet::Context.new(binding) return ctx.instance_eval(&action) end end # rules.each # If no rule matches, this is the fallback - a clean new parslet atom. return atom end end end require 'parslet/context'parslet-1.8.2/lib/parslet/accelerator/engine.rb0000644000004100000410000000475313242043720021530 0ustar www-datawww-data require 'parslet/atoms/visitor' module Parslet::Accelerator # @api private class Apply def initialize(engine, expr) @engine = engine @expr = expr end def visit_parser(root) false end def visit_entity(name, block) false end def visit_named(name, atom) match(:as) do |key| @engine.try_bind(key, name) end end def visit_repetition(tag, min, max, atom) match(:rep) do |e_min, e_max, expr| e_min == min && e_max == max && @engine.match(atom, expr) end end def visit_alternative(alternatives) match(:alt) do |*expressions| return false if alternatives.size != expressions.size alternatives.zip(expressions).all? do |atom, expr| @engine.match(atom, expr) end end end def visit_sequence(sequence) match(:seq) do |*expressions| return false if sequence.size != expressions.size sequence.zip(expressions).all? do |atom, expr| @engine.match(atom, expr) end end end def visit_lookahead(positive, atom) match(:absent) do |expr| return positive == false && @engine.match(atom, expr) end match(:present) do |expr| return positive == true && @engine.match(atom, expr) end end def visit_re(regexp) match(:re) do |*bind_conditions| bind_conditions.all? { |bind_cond| @engine.try_bind(bind_cond, regexp) } end end def visit_str(str) match(:str) do |*bind_conditions| bind_conditions.all? { |bind_cond| @engine.try_bind(bind_cond, str) } end end def match(type_tag) expr_tag = @expr.type if expr_tag == type_tag yield *@expr.args end end end # @api private class Engine attr_reader :bindings def initialize @bindings = {} end def match(atom, expr) atom.accept( Apply.new(self, expr)) end def try_bind(variable, value) if bound? variable return value == lookup(variable) else case variable when Symbol bind(variable, value) else # This does not look like a variable - let's try matching it against # the value: variable === value end end end def bound? var @bindings.has_key? var end def lookup var @bindings[var] end def bind var, val @bindings[var] = val end end endparslet-1.8.2/lib/parslet/cause.rb0000644000004100000410000000644113242043720017073 0ustar www-datawww-datamodule Parslet # Represents a cause why a parse did fail. A lot of these objects are # constructed - not all of the causes turn out to be failures for the whole # parse. # class Cause def initialize(message, source, pos, children) @message, @source, @pos, @children, @context = message, source, pos, children, nil end # @return [String, Array] A string or an array of message pieces that # provide failure information. Use #to_s to get a formatted string. attr_reader :message # @return [Parslet::Source] Source that was parsed when this error # happend. Mainly used for line number information. attr_reader :source # Location of the error. # # @return [Fixnum] Position where the error happened. (character offset) attr_reader :pos # When this cause is part of a tree of error causes: child nodes for this # node. Very often carries the reasons for this cause. # # @return [Array] A list of reasons for this cause. def children @children ||= [] end # Appends 'at line LINE char CHAR' to the string given. Use +pos+ to # override the position of the +source+. This method returns an object # that can be turned into a string using #to_s. # # @param source [Parslet::Source] source that was parsed when this error # happened # @param pos [Fixnum] position of error # @param str [String, Array] message parts # @param children [Array] child nodes for this error tree # @return [Parslet::Cause] a new instance of {Parslet::Cause} # def self.format(source, pos, str, children=[]) self.new(str, source, pos, children) end # Update error message to include context provided by label # Update all child causes too (the same context applies to all causes) def set_label(l) @context = " when parsing #{l}" children.each { |c| c.set_label(l) } end def to_s line, column = source.line_and_column(pos) # Allow message to be a list of objects. Join them here, since we now # really need it. Array(message).map { |o| o.respond_to?(:to_slice) ? o.str.inspect : o.to_s }.join + " at line #{line} char #{column}#{@context}." end # Signals to the outside that the parse has failed. Use this in # conjunction with .format for nice error messages. # def raise(exception_klass=Parslet::ParseFailed) exception = exception_klass.new(self.to_s, self) Kernel.raise exception end # Returns an ascii tree representation of the causes of this node and its # children. # def ascii_tree StringIO.new.tap { |io| recursive_ascii_tree(self, io, [true]) }. string end private def recursive_ascii_tree(node, stream, curved) append_prefix(stream, curved) stream.puts node.to_s node.children.each do |child| last_child = (node.children.last == child) recursive_ascii_tree(child, stream, curved + [last_child]) end end def append_prefix(stream, curved) return if curved.size < 2 curved[1..-2].each do |c| stream.print c ? " " : "| " end stream.print curved.last ? "`- " : "|- " end end endparslet-1.8.2/lib/parslet/error_reporter.rb0000644000004100000410000000030713242043720021041 0ustar www-datawww-data# A namespace for all error reporters. # module Parslet::ErrorReporter end require 'parslet/error_reporter/tree' require 'parslet/error_reporter/deepest' require 'parslet/error_reporter/contextual' parslet-1.8.2/lib/parslet/rig/0000755000004100000410000000000013242043720016222 5ustar www-datawww-dataparslet-1.8.2/lib/parslet/rig/rspec.rb0000644000004100000410000000316413242043720017667 0ustar www-datawww-dataRSpec::Matchers.define(:parse) do |input, opts| as = block = nil result = trace = nil match do |parser| begin result = parser.parse(input) block ? block.call(result) : (as == result || as.nil?) rescue Parslet::ParseFailed => ex trace = ex.parse_failure_cause.ascii_tree if opts && opts[:trace] false end end public_send(respond_to?(:failure_message) ? :failure_message : :failure_message_for_should) do |is| if block "expected output of parsing #{input.inspect}" << " with #{is.inspect} to meet block conditions, but it didn't" else "expected " << (as ? "output of parsing #{input.inspect}"<< " with #{is.inspect} to equal #{as.inspect}, but was #{result.inspect}" : "#{is.inspect} to be able to parse #{input.inspect}") << (trace ? "\n"+trace : '') end end public_send(respond_to?(:failure_message_when_negated) ? :failure_message_when_negated : :failure_message_for_should_not) do |is| if block "expected output of parsing #{input.inspect} with #{is.inspect} not to meet block conditions, but it did" else "expected " << (as ? "output of parsing #{input.inspect}"<< " with #{is.inspect} not to equal #{as.inspect}" : "#{is.inspect} to not parse #{input.inspect}, but it did") end end # NOTE: This has a nodoc tag since the rdoc parser puts this into # Object, a thing I would never allow. chain :as do |expected_output=nil, &my_block| as = expected_output block = my_block end end parslet-1.8.2/lib/parslet/graphviz.rb0000644000004100000410000000370313242043720017623 0ustar www-datawww-data # Paints a graphviz graph of your parser. begin require 'ruby-graphviz' rescue LoadError puts "Please install the 'ruby-graphviz' gem first." fail end require 'set' require 'parslet/atoms/visitor' module Parslet class GraphvizVisitor def initialize g @graph = g @known_links = Set.new @visited = Set.new end attr_reader :parent def visit_parser(root) recurse root, node('parser') end def visit_entity(name, block) s = node(name) downwards s return if @visited.include?(name) @visited << name recurse block.call, s end def visit_named(name, atom) recurse atom, parent end def visit_repetition(tag, min, max, atom) recurse atom, parent end def visit_alternative(alternatives) p = parent alternatives.each do |atom| recurse atom, p end end def visit_sequence(sequence) p = parent sequence.each do |atom| recurse atom, p end end def visit_lookahead(positive, atom) recurse atom, parent end def visit_re(regexp) # downwards node(regexp.object_id, label: escape("re(#{regexp.inspect})")) end def visit_str(str) # downwards node(str.object_id, label: escape("#{str.inspect}")) end def escape str str.gsub('"', "'") end def node name, opts={} @graph.add_nodes name.to_s, opts end def downwards child if @parent && !@known_links.include?([@parent, child]) @graph.add_edges(@parent, child) @known_links << [@parent, child] end end def recurse node, current @parent = current node.accept(self) end end module Graphable def graph opts g = GraphViz.new(:G, type: :digraph) visitor = GraphvizVisitor.new(g) new.accept(visitor) g.output opts end end class Parser # reopen for introducing the .graph method extend Graphable end endparslet-1.8.2/lib/parslet/error_reporter/0000755000004100000410000000000013242043720020514 5ustar www-datawww-dataparslet-1.8.2/lib/parslet/error_reporter/deepest.rb0000644000004100000410000000724213242043720022477 0ustar www-datawww-datamodule Parslet module ErrorReporter # Instead of reporting the latest error that happens like {Tree} does, # this class reports the deepest error. Depth is defined here as how # advanced into the input an error happens. The errors close to the # greatest depth tend to be more relevant to the end user, since they # specify what could be done to make them go away. # # More specifically, errors produced by this reporter won't be related to # the structure of the grammar at all. The positions of the errors will # be advanced and convey at every grammar level what the deepest rule # was to fail. # class Deepest def initialize @deepest_cause = nil end # Produces an error cause that combines the message at the current level # with the errors that happened at a level below (children). # # @param atom [Parslet::Atoms::Base] parslet that failed # @param source [Source] Source that we're using for this parse. (line # number information...) # @param message [String, Array] Error message at this level. # @param children [Array] A list of errors from a deeper level (or nil). # @return [Cause] An error tree combining children with message. # def err(atom, source, message, children=nil) position = source.pos cause = Cause.format(source, position, message, children) return deepest(cause) end # Produces an error cause that combines the message at the current level # with the errors that happened at a level below (children). # # @param atom [Parslet::Atoms::Base] parslet that failed # @param source [Source] Source that we're using for this parse. (line # number information...) # @param message [String, Array] Error message at this level. # @param pos [Fixnum] The real position of the error. # @param children [Array] A list of errors from a deeper level (or nil). # @return [Cause] An error tree combining children with message. # def err_at(atom, source, message, pos, children=nil) position = pos cause = Cause.format(source, position, message, children) return deepest(cause) end # Returns the cause that is currently deepest. Mainly for specs. # Notification that an expression successfully parsed # not used, see ErrorReporter::Contextual def succ(source) end # attr_reader :deepest_cause # Checks to see if the lineage of the cause given includes a cause with # an error position deeper than the current deepest cause stored. If # yes, it passes the cause through to the caller. If no, it returns the # current deepest error that was saved as a reference. # def deepest(cause) _, leaf = deepest_child(cause) if !deepest_cause || leaf.pos >= deepest_cause.pos # This error reaches deeper into the input, save it as reference. @deepest_cause = leaf return cause end return deepest_cause end private # Returns the leaf from a given error tree with the biggest rank. # def deepest_child(cause, rank=0) max_child = cause max_rank = rank if cause.children && !cause.children.empty? cause.children.each do |child| c_rank, c_cause = deepest_child(child, rank+1) if c_rank > max_rank max_rank = c_rank max_child = c_cause end end end return max_rank, max_child end end end endparslet-1.8.2/lib/parslet/error_reporter/tree.rb0000644000004100000410000000543013242043720022002 0ustar www-datawww-datamodule Parslet module ErrorReporter # An error reporter has two central methods, one for reporting errors at # the current parse position (#err) and one for reporting errors at a # given parse position (#err_at). The reporter can return an object (a # 'cause') that will be returned to the caller along with the information # that the parse failed. # # When reporting errors on the outer levels of your parser, these methods # get passed a list of error objects ('causes') from the inner levels. In # this default implementation, the inner levels are considered error # subtrees and are appended to the generated tree node at each level, # thereby constructing an error tree. # # This error tree will report in parallel with the grammar structure that # failed. A one-to-one correspondence exists between each error in the # tree and the parslet atom that produced that error. # # The implementor is really free to use these return values as he sees # fit. One example would be to return an error state object from these # methods that is then updated as errors cascade up the parse derivation # tree. # class Tree # Produces an error cause that combines the message at the current level # with the errors that happened at a level below (children). # # @param atom [Parslet::Atoms::Base] parslet that failed # @param source [Source] Source that we're using for this parse. (line # number information...) # @param message [String, Array] Error message at this level. # @param children [Array] A list of errors from a deeper level (or nil). # @return [Cause] An error tree combining children with message. # def err(atom, source, message, children=nil) position = source.pos Cause.format(source, position, message, children) end # Produces an error cause that combines the message at the current level # with the errors that happened at a level below (children). # # @param atom [Parslet::Atoms::Base] parslet that failed # @param source [Source] Source that we're using for this parse. (line # number information...) # @param message [String, Array] Error message at this level. # @param pos [Fixnum] The real position of the error. # @param children [Array] A list of errors from a deeper level (or nil). # @return [Cause] An error tree combining children with message. # def err_at(atom, source, message, pos, children=nil) position = pos Cause.format(source, position, message, children) end # Notification that an expression successfully parsed # not used, see ErrorReporter::Contextual def succ(source) end end end end parslet-1.8.2/lib/parslet/error_reporter/contextual.rb0000644000004100000410000000777313242043720023245 0ustar www-datawww-datamodule Parslet module ErrorReporter # A reporter that tries to improve on the deepest error reporter by # using heuristics to find the most relevant error and provide more # context. # The heuristic chooses the deepest error when parsing a sequence for which # no alternative parsed successfully. # # Given the following parser: # # root(:call) # # rule(:call, label: 'call') { # identifier >> str('.') >> method # } # # rule(:method, label: 'method call') { # identifier >> str('(') >> arguments.maybe >> str(')') # } # # rule(:identifier, label: 'identifier') { # match['[:alnum:]'].repeat(1) # } # # rule(:arguments, label: 'method call arguments') { # argument >> str(',') >> arguments | argument # } # # rule(:argument) { # call | identifier # } # # and the following source: # # foo.bar(a,goo.baz(),c,) # # The contextual reporter returns the following causes: # # 0: Failed to match sequence (identifier '.' method call) at line 1 char 5 # when parsing method call arguments. # 1: Failed to match sequence (identifier '(' method call arguments? ')') at # line 1 char 22 when parsing method call arguments. # 2: Failed to match [[:alnum:]] at line 1 char 23 when parsing method call # arguments. # # (where 2 is a child cause of 1 and 1 a child cause of 0) # # The last piece used by the reporter is the (newly introduced) ability # to attach a label to rules that describe a sequence in the grammar. The # labels are used in two places: # - In the "to_s" of Atom::Base so that any error message uses labels to # refer to atoms # - In the cause error messages to give information about which expression # failed to parse # class Contextual < Deepest def initialize @last_reset_pos = 0 reset end # A sequence expression successfully parsed, reset all errors reported # for previous expressions in the sequence (an alternative matched) # Only reset errors if the position of the source that matched is higher # than the position of the source that was last successful (so we keep # errors that are the "deepest" but for which no alternative succeeded) # def succ(source) source_pos = source.pos.bytepos return if source_pos < @last_reset_pos @last_reset_pos = source_pos reset end # Reset deepest error and its position and sequence index # def reset @deepest_cause = nil @label_pos = -1 end # Produces an error cause that combines the message at the current level # with the errors that happened at a level below (children). # Compute and set label used by Cause to produce error message. # # @param atom [Parslet::Atoms::Base] parslet that failed # @param source [Source] Source that we're using for this parse. (line # number information...) # @param message [String, Array] Error message at this level. # @param children [Array] A list of errors from a deeper level (or nil). # @return [Cause] An error tree combining children with message. # def err(atom, source, message, children=nil) cause = super(atom, source, message, children) if (label = atom.respond_to?(:label) && atom.label) update_label(label, source.pos.bytepos) cause.set_label(@label) end cause end # Update error message label if given label is more relevant. # A label is more relevant if the position of the matched source is # bigger. # # @param label [String] label to apply if more relevant # @param bytepos [Integer] position in source code of matched source # def update_label(label, bytepos) if bytepos >= @label_pos @label_pos = bytepos @label = label end end end end end parslet-1.8.2/lib/parslet/scope.rb0000644000004100000410000000124613242043720017102 0ustar www-datawww-dataclass Parslet::Scope # Raised when the accessed slot has never been assigned a value. # class NotFound < StandardError end class Binding attr_reader :parent def initialize(parent=nil) @parent = parent @hash = Hash.new end def [](k) @hash.has_key?(k) && @hash[k] || parent && parent[k] or raise NotFound end def []=(k,v) @hash.store(k,v) end end def [](k) @current[k] end def []=(k,v) @current[k] = v end def initialize @current = Binding.new end def push @current = Binding.new(@current) end def pop @current = @current.parent end endparslet-1.8.2/lib/parslet.rb0000644000004100000410000002423413242043720015773 0ustar www-datawww-data# A simple parser generator library. Typical usage would look like this: # # require 'parslet' # # class MyParser < Parslet::Parser # rule(:a) { str('a').repeat } # root(:a) # end # # pp MyParser.new.parse('aaaa') # => 'aaaa'@0 # 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. # # 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. # * Transformation of the tree into something useful to you, see # 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. # # 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. # # == 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}, # {Parslet::Atoms::Alternative}. # # == 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 # can be printed out as an ascii tree. ({Parslet::Cause}) # module Parslet # Extends classes that include Parslet with the module # {Parslet::ClassMethods}. # 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. # # begin # parslet.parse(str) # rescue Parslet::ParseFailed => failure # puts failure.parse_failure_cause.ascii_tree # end # # Alternatively, you can just require 'parslet/convenience' and call the # method #parse_with_debug instead of #parse. This method will never raise # and print error trees to stdout. # # require 'parslet/convenience' # parslet.parse_with_debug(str) # class ParseFailed < StandardError def initialize(message, parse_failure_cause=nil) super(message) @parse_failure_cause = parse_failure_cause end # 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 # # rule(:bar) { str('bar') } # rule(:twobar) do # bar >> bar # end # # root :twobar # end # def rule(name, opts={}, &definition) undef_method name if method_defined? name 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 end # Allows for delayed construction of #match. See also Parslet.match. # # @api private class DelayedMatchConstructor def [](str) Atoms::Re.new("[" + str + "]") 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. # # 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: # # match['a-z'] # synonymous to match('[a-z]') # match['\n'] # synonymous to match('[\n]') # # @overload match(str) # @param str [String] character class to match (regexp syntax) # @return [Parslet::Atoms::Re] a parslet atom # def match(str=nil) return DelayedMatchConstructor.new unless str return Atoms::Re.new(str) end module_function :match # Returns an atom matching the +str+ given: # # 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) end module_function :str # Returns an atom matching any character. It acts like the '.' (dot) # character in regular expressions. # # any.parse('a') # => 'a' # # @return [Parslet::Atoms::Re] a parslet atom # def any Atoms::Re.new('.') 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. # # 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. # # Dynamic parse pieces are never cached and can introduce performance # 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. # # Higher precedence indicates that the operation should bind tighter than # 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. # # 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. # # 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 # operand, the operator, and the right operand. # # Examples: # infix_expression(integer, [add_op, 1, :left]) # # would parse things like '1 + 2' # # infix_expression(integer, [add_op, 1, :left]) { |l,o,r| { :plus => [l, r] } } # # would parse '1 + 2 + 3' as: # # { :plus => [1, { :plus => [2, 3] }] } # # @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. # # # the same as str('a') >> str('b').maybe # exp(%Q("a" "b"?)) # # @param str [String] a treetop expression # @return [Parslet::Atoms::Base] the corresponding parslet parser # 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. # # # This would match a body element that contains several declarations. # { :body => sequence(:declarations) } # # The above example would match :body => ['a', 'b'], but not # :body => 'a'. # # see {Parslet::Transform} # 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. # { :header => simple(:header) } # # see {Parslet::Transform} # 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. # # { :expression => subtree(:exp) } # def subtree(symbol) Pattern::SubtreeBind.new(symbol) end module_function :subtree autoload :Expression, 'parslet/expression' end require 'parslet/slice' require 'parslet/cause' require 'parslet/source' require 'parslet/atoms' require 'parslet/pattern' require 'parslet/pattern/binding' require 'parslet/transform' require 'parslet/parser' require 'parslet/error_reporter' require 'parslet/scope' parslet-1.8.2/HISTORY.txt0000644000004100000410000002304213242043720015124 0ustar www-datawww-data = 2.0 / ?? (future release changes, like a reminder to self) - prsnt? and absnt? are now finally banned into oblivion. Wasting vocals for the win. = 1.8.2 / 13Feb2018 ! Improvements to performance in cases where atoms are dynamically generated (Kevin Olbrich). = 1.8.1 / 19Nov2017 - Minor fixes for language compatibility. = 1.8 / 3Apr2017 + The `ignored` atom that allows to ignore a part of the matched text. `str('foo').ignore` will match 'foo', but not yield any parse output. Thanks to chrismwendt (Chris Wendt). + Infix expression parser (arithmetics, anyone?) now supports custom reducers in block form. Thanks to chrismwendt (Chris Wendt). ! Small patches to memory footprint (Christophe Bliard). - blankslate dependency removed. You should be good - but if things break, please let us know (Nikita Shilnikov). ! Parslet now has `parse_failure_cause`, replaces the earlier `cause`. ! Fixes all these interpreter warnings on modern rubies. = 1.7 / 12Mar2015 ! Small speed gains from improvements on the hot spots. + Contextual error reporter, a flavor of error reporting that emphasizes context. = 1.6 / 1May2014, 13Okt14 + EXPERIMENTAL: Parslet accelerators permit replacing parts of your parser with optimized atoms using pattern matching. Look at examples/optimized_erb.rb or the introduction to the feature in qed/accelerators.md. + infix_expression permits to declare an infix expression parser (think calculator) directly. This will solve many of the problems we have more elegantly. + Rspec 3 syntax, though hideous, should now work. - Drops 1.8.7 compatibility. ! A performance anomaly when parsing multibyte characters has been detected and fixed with the help of Zach Moazeni (@zmoazeni). ! A few small bug fixes and optimisations have been introduced. API should remain unchanged. + More lenient on the blankslate version. + Modernizes the test suite to run with rspec again. (!) = 1.5 / 27Dec2012 + Handles unconsumed input at end of parse completely differently. Instead of generating a toplevel error, it now raises an error in every branch of the parse. More information in the resulting exception ensues! Thanks again to John Mettraux for inspiration & acceptance specs. NOTE that this means that the UnconsumedInput exception is gone, since the unconsumed input case is nothing special anymore. * This history now finally reads like the Changelog of the linux kernel. Meaning that probably no one ever reads this. + Captures and parsing subsequent input based on captured values. This has been long overdue - finally you can parse HEREdocs with parslet! = 1.4.0 / 25May2012 + Revised documentation. A few new API features have finally made it into the documentation. Examples in the documentation are now curated and run against the current code so that they really really work. Also, the website generation tools have been replaced with 2012-style tools. Much less pain to update now. + Parslet::Source now doesn't hold a StringIO, it directly holds the buffer to be parsed. The api of Source has changed a tiny bit. This change has been made for speed optimisation reasons. + :reporter argument to parse, allowing to customize error reporting within wide boundaries. See issue #64 for a discussion. Included are two error reporters, one (default) with the existing error tree functionality, one reporting deepest errors as defined by the above ticket. + Optimistic parse: Parsing is two phase, with the first phase assuming there will be no errors. This yields ~ 20% speed improvement in the case where the parse succeeds. Also, internal error handling is now using tuples. This and other optimizations have yielded ~ 30% overall improvement. ! #error_tree and #cause removed from all of parslet. The Parslet::ParseFailed exception now contains a #cause field that can be asked for an #ascii_tree as before. Cleaner internal error handling, not stateful in atoms anymore. Some parsers will see correct error reporting for the first time. (issue #65) + Made it possible to pass a custom Parslet::Source implementor to #parse. (see #63) + #parse has now a second argument that is an options hash. See Parslet::Atoms::Base#parse for documentation. - VM engine on the way out. No benefit except for the intellectual challenge. = 1.3.0 / 5Mar2012 ! Parslet::Transform::Context is now much more well-behaved. It has #respond_to? and #method_missing; it now looks like a plain old Ruby object with instance variables and attribute readers. - Grammar transforms turned out to be a dead end and have been removed. ! A few problems in error message generation have been fixed. This will improve diagnostics further. + A VM driven parser engine: Removes the limitation that parsing needs a lot of stack space, something dearly missing from Ruby 1.9.3 fibers. This engine is experimental and might be removed in the future. ! Interaction with mathn fixed - Line number generation will terminate. . Internal reorganisation, removing cruft and bit rot. = 1.2.3 / 22Sep2011 + Transform#apply can now be called with a hash as second argument. This provides bindings and a way to inject context. ! Fixes a bug thar modified parslet atoms in place, defeating oop chaining. (#50) = 1.2.1 / 6Jun2011 ! FIX: Input at the end of a parse raises Parslet::UnconsumedInput. (see issue 18) ! FIX: Unicode parsing should now work as expected. (see issue 38) ! FIX: Slice#slice returned wrong bits at times (see issue 36). = 1.2.0 / 4Feb2011 + Parslet::Parser is now also a grammar atom, it can be composed freely with other atoms. (str('f') >> MiniLispParser.new >> str('b')) + No strings, only slices are returned as part of the parser result. Parslet::Slice is almost a string class, but one that remembers the source offset. This has also bought us a slight speedup. + require 'parslet/convenience' now brings #parse_with_debug to all parslets. This is a consequence of the above change. + Deprecates prsnt? and absnt? in favor of the more readable absent? and prsnt?. Uses 3 bytes more RAM. The old variants will exist until we release 2.0. INTERNALLY + Visitors now should have methods that all begin with 'visit_*'. #str becomes #visit_str. + Parslet::Atoms::Entity now takes only a block argument instead of context and block. = 1.1.1 / 4Feb2011 ! FIX: Line counting was broken by performance optimisations. + Squeezed out another few drops of performance. = 1.1.0 / 2Feb2011 + Uses return (fail/success), cached line counts, memoizing of parse results and other tricks internally for at least an order of magnitude increase in execution speed. + str('foo').maybe will now return an empty string again. Use .as(...) to name things and get back [] from #repeat and nil from #maybe. + If you require 'parslet/atoms/visitor', you'll get an accept method on all known Parslet::Atoms. + If you require 'parslet/export', you can call #to_citrus and #to_treetop to produce string versions of your grammar in those dialects. + Requiring 'parslet/convenience' will given you a parse_with_debug on your Parslet::Parser class. This prints some diagnostics on parse failure. (Thanks to Florian Hanke) = 1.0.1 / 17Jan2011 A happy new year! ! FIX: Parslet::Transform was wrongly fixed earlier - it now wont mangle hashes anymore. (Blake Sweeney) + parslet/rig/rspec.rb contains useful rspec matchers. (R. Konstantin Haase) = 1.0.0 / 29Dez2010 - #each_match was removed. There was some duplication of code that even confused me - and we should not have 2 methods of achieving the same goal. + Full documentation. Fixed sdoc. = 0.11.0 / 25Nov2010 ! Bugfixes to tree handling. Let's hope that was the last such significant change to the core. = 0.10.1 / 22Nov2010 + Allow match['a-z'], shortcut for match('[a-z]') ! Fixed output inconsistencies (behaviour in connection to 'maybe') = 0.10.0 / 22Nov2010 + Parslet::Transform now takes a block on initialisation, wherein you can define all the rules directly. + Parslet::Transform now only passes a hash to the block during transform when its arity is 1. Otherwise all hash contents as bound as local variables. + Both inline and other documentation have been improved. + You can now use 'subtree(:x)' to bind any subtree to x during tree pattern matching. + Transform classes can now include rules into class definition. This makes Parser and Transformer behave the same. = 0.9.0 / 28Oct2010 * More of everything: Examples, documentation, etc... * Breaking change: Ruby's binary or ('|') is now used for alternatives, instead of the division sign ('/') - this reduces the amount of parenthesis needed for a grammar overall. * parslet.maybe now yields the result or nil in case of parse failure. This is probably better than the array it did before; the jury is still out on that. * parslet.repeat(min, max) is now valid syntax = 0.1.0 / not released. * Initial version. Classes for parsing, matching in the resulting trees and transforming the trees into something more useful. * Parses and outputs intermediary trees * Matching of single elements and sequencesparslet-1.8.2/LICENSE0000644000004100000410000000207213242043720014227 0ustar www-datawww-data Copyright (c) 2010-2018 Kaspar Schiess Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. parslet-1.8.2/example/0000755000004100000410000000000013242043720014654 5ustar www-datawww-dataparslet-1.8.2/example/big.erb0000644000004100000410000001240313242043720016107 0ustar www-datawww-dataLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <%= erb tag %> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. parslet-1.8.2/example/minilisp.rb0000644000004100000410000000420513242043720017026 0ustar www-datawww-data# Reproduces [1] using parslet. # [1] http://thingsaaronmade.com/blog/a-quick-intro-to-writing-a-parser-using-treetop.html $:.unshift File.dirname(__FILE__) + "/../lib" require 'pp' require 'parslet' require 'parslet/convenience' module MiniLisp class Parser < Parslet::Parser root :expression rule(:expression) { space? >> str('(') >> space? >> body >> str(')') >> space? } rule(:body) { (expression | identifier | float | integer | string).repeat.as(:exp) } rule(:space) { match('\s').repeat(1) } rule(:space?) { space.maybe } rule(:identifier) { (match('[a-zA-Z=*]') >> match('[a-zA-Z=*_]').repeat).as(:identifier) >> space? } rule(:float) { ( integer >> ( str('.') >> match('[0-9]').repeat(1) | str('e') >> match('[0-9]').repeat(1) ).as(:e) ).as(:float) >> space? } rule(:integer) { ((str('+') | str('-')).maybe >> match("[0-9]").repeat(1)).as(:integer) >> space? } rule(:string) { str('"') >> ( str('\\') >> any | str('"').absent? >> any ).repeat.as(:string) >> str('"') >> space? } end class Transform include Parslet attr_reader :t def initialize @t = Parslet::Transform.new # To understand these, take a look at what comes out of the parser. t.rule(:identifier => simple(:ident)) { ident.to_sym } t.rule(:string => simple(:str)) { str } t.rule(:integer => simple(:int)) { Integer(int) } t.rule(:float=>{:integer=> simple(:a), :e=> simple(:b)}) { Float(a + b) } t.rule(:exp => subtree(:exp)) { exp } end def do(tree) t.apply(tree) end end end parser = MiniLisp::Parser.new transform = MiniLisp::Transform.new result = parser.parse_with_debug %Q{ (define test (lambda () (begin (display "something") (display 1) (display 3.08)))) } # Transform the result pp transform.do(result) if result # Thereby reducing it to the earlier problem: # http://github.com/kschiess/toylisp parslet-1.8.2/example/documentation.rb0000644000004100000410000000052513242043720020054 0ustar www-datawww-data# A small example that shows a really small parser and what happens on parser # errors. $:.unshift File.dirname(__FILE__) + "/../lib" require 'pp' require 'parslet' class MyParser < Parslet::Parser rule(:a) { str('a').repeat } def parse(str) a.parse(str) end end pp MyParser.new.parse('aaaa') pp MyParser.new.parse('bbbb') parslet-1.8.2/example/ip_address.rb0000644000004100000410000000614413242043720017323 0ustar www-datawww-data# This example is heavily inspired by citrus' ip.citrus. Have a look at both # of these to get some choice! # The grammars in this file conform to the ABNF given in Appendix A of RFC 3986 # Uniform Resource Identifier (URI): Generic Syntax. # # See http://tools.ietf.org/html/rfc3986#appendix-A for more information. $:.unshift File.dirname(__FILE__) + "/../lib" require 'pp' require 'parslet' module IPv4 include Parslet # A host identified by an IPv4 literal address is represented in # dotted-decimal notation (a sequence of four decimal numbers in the range 0 # to 255, separated by "."), as described in [RFC1123] by reference to # [RFC0952]. Note that other forms of dotted notation may be interpreted on # some platforms, as described in Section 7.4, but only the dotted-decimal # form of four octets is allowed by this grammar. rule(:ipv4) { (dec_octet >> str('.') >> dec_octet >> str('.') >> dec_octet >> str('.') >> dec_octet).as(:ipv4) } rule(:dec_octet) { str('25') >> match("[0-5]") | str('2') >> match("[0-4]") >> digit | str('1') >> digit >> digit | match('[1-9]') >> digit | digit } rule(:digit) { match('[0-9]') } end # Must be used in concert with IPv4 module IPv6 include Parslet rule(:colon) { str(':') } rule(:dcolon) { colon >> colon } # h16 : def h16r(times) (h16 >> colon).repeat(times, times) end # : h16 def h16l(times) (colon >> h16).repeat(0,times) end # A 128-bit IPv6 address is divided into eight 16-bit pieces. Each piece is # represented numerically in case-insensitive hexadecimal, using one to four # hexadecimal digits (leading zeroes are permitted). The eight encoded # pieces are given most-significant first, separated by colon characters. # Optionally, the least-significant two pieces may instead be represented in # IPv4 address textual format. A sequence of one or more consecutive # zero-valued 16-bit pieces within the address may be elided, omitting all # their digits and leaving exactly two consecutive colons in their place to # mark the elision. rule(:ipv6) { ( ( h16r(6) | dcolon >> h16r(5) | h16.maybe >> dcolon >> h16r(4) | (h16 >> h16l(1)).maybe >> dcolon >> h16r(3) | (h16 >> h16l(2)).maybe >> dcolon >> h16r(2) | (h16 >> h16l(3)).maybe >> dcolon >> h16r(1) | (h16 >> h16l(4)).maybe >> dcolon ) >> ls32 | (h16 >> h16l(5)).maybe >> dcolon >> h16 | (h16 >> h16l(6)).maybe >> dcolon ).as(:ipv6) } rule(:h16) { hexdigit.repeat(1,4) } rule(:ls32) { (h16 >> colon >> h16) | ipv4 } rule(:hexdigit) { digit | match("[a-fA-F]") } end class Parser include IPv4 include IPv6 def parse(str) (ipv4 | ipv6).parse(str) end end %W( 0.0.0.0 255.255.255.255 255.255.255 1:2:3:4:5:6:7:8 12AD:34FC:A453:1922:: 12AD::34FC 12AD:: :: 1:2 ).each do |address| parser = Parser.new printf "%30s -> ", address begin result = parser.parse(address) puts result.inspect rescue Parslet::ParseFailed => m puts "Failed: #{m}" end end parslet-1.8.2/example/calc.rb0000644000004100000410000000664513242043720016116 0ustar www-datawww-data# A simple integer calculator to answer the question about how to do # left and right associativity in parslet (PEG) once and for all. $:.unshift File.dirname(__FILE__) + "/../lib" require 'rspec' require 'parslet' require 'parslet/rig/rspec' # This is the parsing stage. It expresses left associativity by compiling # list of things that have the same associativity. class CalcParser < Parslet::Parser root :addition rule(:addition) { multiplication.as(:l) >> (add_op >> multiplication.as(:r)).repeat(1) | multiplication } rule(:multiplication) { integer.as(:l) >> (mult_op >> integer.as(:r)).repeat(1) | integer } rule(:integer) { digit.repeat(1).as(:i) >> space? } rule(:mult_op) { match['*/'].as(:o) >> space? } rule(:add_op) { match['+-'].as(:o) >> space? } rule(:digit) { match['0-9'] } rule(:space?) { match['\s'].repeat } end # Classes for the abstract syntax tree. Int = Struct.new(:int) { def eval; self end def op(operation, other) left = int right = other.int Int.new( case operation when '+' left + right when '-' left - right when '*' left * right when '/' left / right end) end def to_i int end } Seq = Struct.new(:sequence) { def eval sequence.reduce { |accum, operation| operation.call(accum) } end } LeftOp = Struct.new(:operation, :right) { def call(left) left = left.eval right = self.right.eval left.op(operation, right) end } # Transforming intermediary syntax tree into a real AST. class CalcTransform < Parslet::Transform rule(i: simple(:i)) { Int.new(Integer(i)) } rule(o: simple(:o), r: simple(:i)) { LeftOp.new(o, i) } rule(l: simple(:i)) { i } rule(sequence(:seq)) { Seq.new(seq) } end # And this calls everything in the right order. def calculate(str) intermediary_tree = CalcParser.new.parse(str) abstract_tree = CalcTransform.new.apply(intermediary_tree) result = abstract_tree.eval result.to_i end # A test suite for the above parser describe CalcParser do let(:p) { described_class.new } describe '#integer' do let(:i) { p.integer } it "parses integers" do i.should parse('1') i.should parse('123') end it "consumes trailing white space" do i.should parse('123 ') end it "doesn't parse floats" do i.should_not parse('1.3') end end describe '#multiplication' do let(:m) { p.multiplication } it "parses simple multiplication" do m.should parse('1*2') end it "parses division" do m.should parse('1/2') end end describe '#addition' do let(:a) { p.addition } it "parses simple addition" do a.should parse('1+2') a.should parse('1+2+3-4') end end end describe CalcTransform do def t(obj) described_class.new.apply(obj) end it "transforms integers" do t(i: '1').should == Int.new(1) end it "unwraps left operand" do t(l: :obj).should == :obj end end describe 'whole computation specs' do def self.result_of(str, int) it(str) { calculate(str).should == int } end result_of '1+1', 2 result_of '1-1-1', -1 result_of '1+1+3*5/2', 9 result_of '123*2', 246 end # Enable these if you want to change the code. # RSpec::Core::Runner.run([], $stderr, $stdout) str = ARGV.join str = '123*2' if str.match(/^\s*$/) print "#{str} (command line): -> " puts calculate(str) parslet-1.8.2/example/deepest_errors.rb0000644000004100000410000000440013242043720020224 0ustar www-datawww-data$:.unshift File.dirname(__FILE__) + "/../lib" # This example demonstrates how to do deepest error reporting, as invented # by John Mettraux (issue #64). require 'parslet' require 'parslet/convenience' def prettify(str) puts " "*3 + " "*4 + "." + " "*4 + "10" + " "*3 + "." + " "*4 + "20" str.lines.each_with_index do |line, index| printf "%02d %s\n", index+1, line.chomp end end class Parser < Parslet::Parser # commons rule(:space) { match('[ \t]').repeat(1) } rule(:space?) { space.maybe } rule(:newline) { match('[\r\n]') } rule(:comment) { str('#') >> match('[^\r\n]').repeat } rule(:line_separator) { (space? >> ((comment.maybe >> newline) | str(';')) >> space?).repeat(1) } rule(:blank) { line_separator | space } rule(:blank?) { blank.maybe } rule(:identifier) { match('[a-zA-Z0-9_]').repeat(1) } # res_statement rule(:reference) { (str('@').repeat(1,2) >> identifier).as(:reference) } rule(:res_action_or_link) { str('.').as(:dot) >> (identifier >> str('?').maybe ).as(:name) >> str('()') } rule(:res_actions) { ( reference ).as(:resources) >> ( res_action_or_link.as(:res_action) ).repeat(0).as(:res_actions) } rule(:res_statement) { res_actions >> (str(':') >> identifier.as(:name)).maybe.as(:res_field) } # expression rule(:expression) { res_statement } # body rule(:body) { (line_separator >> (block | expression)).repeat(1).as(:body) >> line_separator } # blocks rule(:begin_block) { (str('concurrent').as(:type) >> space).maybe.as(:pre) >> str('begin').as(:begin) >> body >> str('end') } rule(:define_block) { str('define').as(:define) >> space >> identifier.as(:name) >> str('()') >> body >> str('end') } rule(:block) { define_block | begin_block } # root rule(:radix) { line_separator.maybe >> block >> line_separator.maybe } root(:radix) end ds = [ %{ define f() @res.name end }, %{ define f() begin @res.name end end } ] ds.each do |d| puts '-' * 80 prettify(d) parser = Parser.new begin parser.parse_with_debug(d, :reporter => Parslet::ErrorReporter::Deepest.new) end end puts '-' * 80parslet-1.8.2/example/json.rb0000644000004100000410000000520713242043720016156 0ustar www-datawww-data$:.unshift File.dirname(__FILE__) + "/../lib" # # MIT License - (c) 2011 John Mettraux # require 'rubygems' require 'parslet' # gem install parslet module MyJson class Parser < Parslet::Parser rule(:spaces) { match('\s').repeat(1) } rule(:spaces?) { spaces.maybe } rule(:comma) { spaces? >> str(',') >> spaces? } rule(:digit) { match('[0-9]') } rule(:number) { ( str('-').maybe >> ( str('0') | (match('[1-9]') >> digit.repeat) ) >> ( str('.') >> digit.repeat(1) ).maybe >> ( match('[eE]') >> (str('+') | str('-')).maybe >> digit.repeat(1) ).maybe ).as(:number) } rule(:string) { str('"') >> ( str('\\') >> any | str('"').absent? >> any ).repeat.as(:string) >> str('"') } rule(:array) { str('[') >> spaces? >> (value >> (comma >> value).repeat).maybe.as(:array) >> spaces? >> str(']') } rule(:object) { str('{') >> spaces? >> (entry >> (comma >> entry).repeat).maybe.as(:object) >> spaces? >> str('}') } rule(:value) { string | number | object | array | str('true').as(:true) | str('false').as(:false) | str('null').as(:null) } rule(:entry) { ( string.as(:key) >> spaces? >> str(':') >> spaces? >> value.as(:val) ).as(:entry) } rule(:attribute) { (entry | value).as(:attribute) } rule(:top) { spaces? >> value >> spaces? } root(:top) end class Transformer < Parslet::Transform class Entry < Struct.new(:key, :val); end rule(:array => subtree(:ar)) { ar.is_a?(Array) ? ar : [ ar ] } rule(:object => subtree(:ob)) { (ob.is_a?(Array) ? ob : [ ob ]).inject({}) { |h, e| h[e.key] = e.val; h } } rule(:entry => { :key => simple(:ke), :val => simple(:va) }) { Entry.new(ke, va) } rule(:string => simple(:st)) { st.to_s } rule(:number => simple(:nb)) { nb.match(/[eE\.]/) ? Float(nb) : Integer(nb) } rule(:null => simple(:nu)) { nil } rule(:true => simple(:tr)) { true } rule(:false => simple(:fa)) { false } end def self.parse(s) parser = Parser.new transformer = Transformer.new tree = parser.parse(s) puts; p tree; puts out = transformer.apply(tree) out end end s = %{ [ 1, 2, 3, null, "asdfasdf asdfds", { "a": -1.2 }, { "b": true, "c": false }, 0.1e24, true, false, [ 1 ] ] } out = MyJson.parse(s) p out; puts out == [ 1, 2, 3, nil, "asdfasdf asdfds", { "a" => -1.2 }, { "b" => true, "c" => false }, 0.1e24, true, false, [ 1 ] ] || raise("MyJson is a failure") parslet-1.8.2/example/capture.rb0000644000004100000410000000262413242043720016650 0ustar www-datawww-data # This example demonstrates how pieces of input can be captured and matched # against later on. Without this, you cannot match here-documents and other # self-dependent grammars. $:.unshift File.dirname(__FILE__) + "/../lib" require 'parslet' require 'parslet/convenience' require 'pp' class CapturingParser < Parslet::Parser root :document # Introduce a scope for each document. This ensures that documents can be # nested. rule(:document) { scope { doc_start >> text >> doc_end } } # Start of a document is a heredoc marker. This is captured in :marker rule(:doc_start) { str('<') >> marker >> newline } rule(:marker) { match['A-Z'].repeat(1).capture(:marker) } # The content of a document can be either lines of text or another # document, introduced by > any >> (newline.absent? >> any).repeat >> newline } # The end of the document is marked by the marker that was at the beginning # of the document, by itself on a line. rule(:doc_end) { captured_marker } rule(:captured_marker) { dynamic { |source, context| str(context.captures[:marker]) } } rule(:newline) { match["\n"] } end parser = CapturingParser.new pp parser.parse_with_debug %Q(').absent? >> any).repeat.as(:ruby) } rule(:expression) { (str('=') >> ruby).as(:expression) } rule(:comment) { (str('#') >> ruby).as(:comment) } rule(:code) { ruby.as(:code) } rule(:erb) { expression | comment | code } rule(:erb_with_tags) { str('<%') >> erb >> str('%>') } rule(:text) { (str('<%').absent? >> any).repeat(1) } rule(:text_with_ruby) { (text.as(:text) | erb_with_tags).repeat.as(:text) } root(:text_with_ruby) end parser = ErbParser.new A = Parslet::Accelerator optimized = A.apply(parser, A.rule((A.str(:x).absent? >> A.any).repeat(1)) { GobbleUp.new(x, 1) }, A.rule((A.str(:x).absent? >> A.any).repeat(0)) { GobbleUp.new(x, 0) }) input = File.read(File.dirname(__FILE__) + "/big.erb") # Remove the comment marks here to see what difference the optimisation makes. # Commented out for the acceptance tests to run. # # require 'benchmark' # Benchmark.bm(7) do |bm| # bm.report('original') { parser.parse(input) } # bm.report('gobble') { optimized.parse(input) } # end p optimized.parse(input)parslet-1.8.2/example/parens.rb0000644000004100000410000000166113242043720016475 0ustar www-datawww-data# A small example that demonstrates the power of tree pattern matching. Also # uses '.as(:name)' to construct a tree that can reliably be matched # afterwards. $:.unshift File.dirname(__FILE__) + "/../lib" require 'pp' require 'parslet' module LISP # as in 'lots of insipid and stupid parenthesis' class Parser < Parslet::Parser rule(:balanced) { str('(').as(:l) >> balanced.maybe.as(:m) >> str(')').as(:r) } root(:balanced) end class Transform < Parslet::Transform rule(:l => '(', :m => simple(:x), :r => ')') { # innermost :m will contain nil x.nil? ? 1 : x+1 } end end parser = LISP::Parser.new transform = LISP::Transform.new %w! () (()) ((((())))) ((()) !.each do |pexp| begin result = parser.parse(pexp) puts "#{"%20s"%pexp}: #{result.inspect} (#{transform.apply(result)} parens)" rescue Parslet::ParseFailed => m puts "#{"%20s"%pexp}: #{m}" end puts end parslet-1.8.2/example/seasons.rb0000644000004100000410000000167413242043720016664 0ustar www-datawww-data$:.unshift File.dirname(__FILE__) + "/../lib" require 'parslet' require 'pp' tree = {:bud => {:stem => []}} class Spring < Parslet::Transform rule(:stem => sequence(:branches)) { {:stem => (branches + [{:branch => :leaf}])} } end class Summer < Parslet::Transform rule(:stem => subtree(:branches)) { new_branches = branches.map { |b| {:branch => [:leaf, :flower]} } {:stem => new_branches} } end class Fall < Parslet::Transform rule(:branch => sequence(:x)) { x.each { |e| puts "Fruit!" if e==:flower } x.each { |e| puts "Falling Leaves!" if e==:leaf } {:branch => []} } end class Winter < Parslet::Transform rule(:stem => subtree(:x)) { {:stem => []} } end def do_seasons(tree) [Spring, Summer, Fall, Winter].each do |season| p "And when #{season} comes" tree = season.new.apply(tree) pp tree puts end tree end # What marvel of life! tree = do_seasons(tree) tree = do_seasons(tree) parslet-1.8.2/example/nested_errors.rb0000644000004100000410000000434013242043720020060 0ustar www-datawww-data$:.unshift File.dirname(__FILE__) + "/../lib" require 'parslet' require 'parslet/convenience' # This example demonstrates tree error reporting in a real life example. # The parser code has been contributed by John Mettraux. def prettify(str) puts " "*3 + " "*4 + "." + " "*4 + "10" + " "*3 + "." + " "*4 + "20" str.lines.each_with_index do |line, index| printf "%02d %s\n", index+1, line.chomp end end class Parser < Parslet::Parser # commons rule(:space) { match('[ \t]').repeat(1) } rule(:space?) { space.maybe } rule(:newline) { match('[\r\n]') } rule(:comment) { str('#') >> match('[^\r\n]').repeat } rule(:line_separator) { (space? >> ((comment.maybe >> newline) | str(';')) >> space?).repeat(1) } rule(:blank) { line_separator | space } rule(:blank?) { blank.maybe } rule(:identifier) { match('[a-zA-Z0-9_]').repeat(1) } # res_statement rule(:reference) { (str('@').repeat(1,2) >> identifier).as(:reference) } rule(:res_action_or_link) { str('.').as(:dot) >> (identifier >> str('?').maybe ).as(:name) >> str('()') } rule(:res_actions) { ( reference ).as(:resources) >> ( res_action_or_link.as(:res_action) ).repeat(0).as(:res_actions) } rule(:res_statement) { res_actions >> (str(':') >> identifier.as(:name)).maybe.as(:res_field) } # expression rule(:expression) { res_statement } # body rule(:body) { (line_separator >> (block | expression)).repeat(1).as(:body) >> line_separator } # blocks rule(:begin_block) { (str('concurrent').as(:type) >> space).maybe.as(:pre) >> str('begin').as(:begin) >> body >> str('end') } rule(:define_block) { str('define').as(:define) >> space >> identifier.as(:name) >> str('()') >> body >> str('end') } rule(:block) { define_block | begin_block } # root rule(:radix) { line_separator.maybe >> block >> line_separator.maybe } root(:radix) end ds = [ %{ define f() @res.name end }, %{ define f() begin @res.name end end } ] ds.each do |d| puts '-' * 80 prettify(d) parser = Parser.new begin parser.parse_with_debug(d) end end puts '-' * 80parslet-1.8.2/example/comments.rb0000644000004100000410000000165213242043720017032 0ustar www-datawww-data# A small example on how to parse common types of comments. The example # started out with parser code from Stephen Waits. $:.unshift File.dirname(__FILE__) + "/../lib" require 'pp' require 'parslet' require 'parslet/convenience' class ALanguage < Parslet::Parser root(:lines) rule(:lines) { line.repeat } rule(:line) { spaces >> expression.repeat >> newline } rule(:newline) { str("\n") >> str("\r").maybe } rule(:expression) { (str('a').as(:a) >> spaces).as(:exp) } rule(:spaces) { space.repeat } rule(:space) { multiline_comment | line_comment | str(' ') } rule(:line_comment) { (str('//') >> (newline.absent? >> any).repeat).as(:line) } rule(:multiline_comment) { (str('/*') >> (str('*/').absent? >> any).repeat >> str('*/')).as(:multi) } end code = %q( a // line comment a a a // line comment a /* inline comment */ a /* multiline comment */ ) pp ALanguage.new.parse_with_debug(code) parslet-1.8.2/example/readme.rb0000644000004100000410000000162713242043720016444 0ustar www-datawww-data# The example from the readme. With this, I am making sure that the readme # 'works'. Is this too messy? $:.unshift File.dirname(__FILE__) + "/../lib" # cut here ------------------------------------------------------------------- require 'parslet' include Parslet # Constructs a parser using a Parser Expression Grammar like DSL: parser = str('"') >> ( str('\\') >> any | str('"').absent? >> any ).repeat.as(:string) >> str('"') # Parse the string and capture parts of the interpretation (:string above) tree = parser.parse('"This is a \\"String\\" in which you can escape stuff"') tree # => {:string=>"This is a \\\"String\\\" in which you can escape stuff"} # Here's how you can grab results from that tree: transform = Parslet::Transform.new do rule(:string => simple(:x)) { puts "String contents: #{x}" } end transform.apply(tree) parslet-1.8.2/example/local.rb0000644000004100000410000000165613242043720016303 0ustar www-datawww-data # An exploration of two ideas: # a) Constructing a whole parser inline, without the artificial class around # it. # and: # b) Constructing non-greedy or non-blind parsers by transforming the # grammar. $:.unshift File.dirname(__FILE__) + "/../lib" require 'parslet' include Parslet a = str('a').repeat >> str('aa') # E1% E2 # # S = E2 | E1 S def this(name, &block); return Parslet::Atoms::Entity.new(name, &block) end def epsilon; any.absent? end # Traditional repetition will try as long as the pattern can be matched and # then give up. This is greedy and blind. a = str('a').as(:e) >> this('a') { a }.as(:rec) | epsilon # Here's a pattern match that is greedy and non-blind. The first pattern # 'a'* will be tried as many times as possible, while still matching the # end pattern 'aa'. b = str('aa').as(:e2) >> epsilon | str('a').as(:e1) >> this('b') { b }.as(:rec) p a.parse('aaaa') p b p b.parse('aaaa') parslet-1.8.2/example/erb.rb0000644000004100000410000000264613242043720015761 0ustar www-datawww-data# Example that demonstrates how a simple erb-like parser could be constructed. $:.unshift File.dirname(__FILE__) + "/../lib" require 'parslet' class ErbParser < Parslet::Parser rule(:ruby) { (str('%>').absent? >> any).repeat.as(:ruby) } rule(:expression) { (str('=') >> ruby).as(:expression) } rule(:comment) { (str('#') >> ruby).as(:comment) } rule(:code) { ruby.as(:code) } rule(:erb) { expression | comment | code } rule(:erb_with_tags) { str('<%') >> erb >> str('%>') } rule(:text) { (str('<%').absent? >> any).repeat(1) } rule(:text_with_ruby) { (text.as(:text) | erb_with_tags).repeat.as(:text) } root(:text_with_ruby) end parser = ErbParser.new p parser.parse "The value of x is <%= x %>." p parser.parse "<% 1 + 2 %>" p parser.parse "<%# commented %>" evaluator = Parslet::Transform.new do erb_binding = binding rule(:code => { :ruby => simple(:ruby) }) { eval(ruby, erb_binding); '' } rule(:expression => { :ruby => simple(:ruby) }) { eval(ruby, erb_binding) } rule(:comment => { :ruby => simple(:ruby) }) { '' } rule(:text => simple(:text)) { text } rule(:text => sequence(:texts)) { texts.join } end puts evaluator.apply(parser.parse(<<-ERB The <% a = 2 %>not printed result of "a = 2". The <%# a = 1 %>not printed non-evaluated comment "a = 1", see the value of a below. The <%= 'nicely' %> printed result. The <% b = 3 %>value of a is <%= a %>, and b is <%= b %>. ERB )) parslet-1.8.2/example/modularity.rb0000644000004100000410000000216113242043720017372 0ustar www-datawww-data$:.unshift File.dirname(__FILE__) + "/../lib" require 'pp' require "parslet" # Demonstrates modular parsers, split out over many classes. Please look at # ip_address.rb as well. module ALanguage include Parslet # Parslet rules are really a special kind of method. Mix them into your # classes! rule(:a_language) { str('aaa') } end # Parslet parsers are parslet atoms as well. Create an instance and chain them # to your other rules. # class BLanguage < Parslet::Parser root :blang rule(:blang) { str('bbb') } end # Parslet atoms are really Ruby values, pass them around. c_language = Parslet.str('ccc') class Language < Parslet::Parser def initialize(c_language) @c_language = c_language super() end root :root include ALanguage rule(:root) { str('a(') >> a_language >> str(')') >> space | str('b(') >> BLanguage.new >> str(')') >> space | str('c(') >> @c_language >> str(')') >> space } rule(:space) { str(' ').maybe } end Language.new(c_language).parse('a(aaa)') Language.new(c_language).parse('b(bbb)') Language.new(c_language).parse('c(ccc)')parslet-1.8.2/example/boolean_algebra.rb0000644000004100000410000000357613242043720020310 0ustar www-datawww-data$:.unshift File.dirname(__FILE__) + "/../lib" require "parslet" require "pp" # Parses strings like "var1 and (var2 or var3)" respecting operator precedence # and parentheses. After that transforms the parse tree into an array of # arrays like this: # # [["1", "2"], ["1", "3"]] # # The array represents a DNF (disjunctive normal form). Elements of outer # array are connected with "or" operator, while elements of inner arrays are # joined with "and". # class Parser < Parslet::Parser rule(:space) { match[" "].repeat(1) } rule(:space?) { space.maybe } rule(:lparen) { str("(") >> space? } rule(:rparen) { str(")") >> space? } rule(:and_operator) { str("and") >> space? } rule(:or_operator) { str("or") >> space? } rule(:var) { str("var") >> match["0-9"].repeat(1).as(:var) >> space? } # The primary rule deals with parentheses. rule(:primary) { lparen >> or_operation >> rparen | var } # Note that following rules are both right-recursive. rule(:and_operation) { (primary.as(:left) >> and_operator >> and_operation.as(:right)).as(:and) | primary } rule(:or_operation) { (and_operation.as(:left) >> or_operator >> or_operation.as(:right)).as(:or) | and_operation } # We start at the lowest precedence rule. root(:or_operation) end class Transformer < Parslet::Transform rule(:var => simple(:var)) { [[String(var)]] } rule(:or => { :left => subtree(:left), :right => subtree(:right) }) do (left + right) end rule(:and => { :left => subtree(:left), :right => subtree(:right) }) do res = [] left.each do |l| right.each do |r| res << (l + r) end end res end end pp tree = Parser.new.parse("var1 and (var2 or var3)") # {:and=> # {:left=>{:var=>"1"@3}, # :right=>{:or=>{:left=>{:var=>"2"@13}, :right=>{:var=>"3"@21}}}}} pp Transformer.new.apply(tree) # [["1", "2"], ["1", "3"]] parslet-1.8.2/example/sentence.rb0000644000004100000410000000277513242043720017020 0ustar www-datawww-data# encoding: UTF-8 # A small example contributed by John Mettraux (jmettraux) that demonstrates # working with Unicode. This only works on Ruby 1.9. $:.unshift File.dirname(__FILE__) + "/../lib" require 'parslet' class Parser < Parslet::Parser rule(:sentence) { (match('[^。]').repeat(1) >> str("。")).as(:sentence) } rule(:sentences) { sentence.repeat } root(:sentences) end class Transformer < Parslet::Transform rule(:sentence => simple(:sen)) { sen.to_s } end string = "RubyKaigi2009のテーマは、「変わる/変える」です。 前回の" + "RubyKaigi2008のテーマであった「多様性」の言葉の通り、 " + "2008年はRubyそのものに関しても、またRubyの活躍する舞台に関しても、 " + "ますます多様化が進みつつあります。RubyKaigi2008は、そのような " + "Rubyの生態系をあらためて認識する場となりました。 しかし、" + "こうした多様化が進む中、異なる者同士が単純に距離を 置いたままでは、" + "その違いを認識したところであまり意味がありません。 異なる実装、" + "異なる思想、異なる背景といった、様々な多様性を理解しつつ、 " + "すり合わせるべきものをすり合わせ、変えていくべきところを " + "変えていくことが、豊かな未来へとつながる道に違いありません。" parser = Parser.new transformer = Transformer.new tree = parser.parse(string) p transformer.apply(tree) parslet-1.8.2/example/empty.rb0000644000004100000410000000044413242043720016341 0ustar www-datawww-data# Basically just demonstrates that you can leave rules empty and get a nice # NotImplementedError. A way to quickly spec out your parser rules? $:.unshift File.dirname(__FILE__) + "/../lib" require 'parslet' class Parser < Parslet::Parser rule(:empty) { } end Parser.new.empty.parslet parslet-1.8.2/example/simple.lit0000644000004100000410000000005213242043720016654 0ustar www-datawww-data123 12345 " Some String with \"escapes\"" parslet-1.8.2/example/scopes.rb0000644000004100000410000000042213242043720016473 0ustar www-datawww-data $:.unshift File.dirname(__FILE__) + "/../lib" require 'parslet' include Parslet parser = str('a').capture(:a) >> scope { str('b').capture(:a) } >> dynamic { |s,c| str(c.captures[:a]) } begin parser.parse('aba') puts "parses 'aba'" rescue puts "exception!" endparslet-1.8.2/example/test.lit0000644000004100000410000000021013242043720016336 0ustar www-datawww-data"THis is a string" "This is another string" "This string is escaped \"embedded quoted stuff \" " 12 // an integer literal and a comment parslet-1.8.2/example/simple_xml.rb0000644000004100000410000000234713242043720017360 0ustar www-datawww-data# A simple xml parser. It is simple in the respect as that it doesn't address # any of the complexities of XML. This is ruby 1.9. $:.unshift File.dirname(__FILE__) + "/../lib" require 'pp' require 'parslet' class XML < Parslet::Parser root :document rule(:document) { tag(close: false).as(:o) >> document.as(:i) >> tag(close: true).as(:c) | text } # Perhaps we could have some syntax sugar to make this more easy? # def tag(opts={}) close = opts[:close] || false parslet = str('<') parslet = parslet >> str('/') if close parslet = parslet >> (str('>').absent? >> match("[a-zA-Z]")).repeat(1).as(:name) parslet = parslet >> str('>') parslet end rule(:text) { match('[^<>]').repeat(0) } end def check(xml) r = XML.new.parse(xml) # We'll validate the tree by reducing valid pairs of tags into simply the # string "verified". If the transformation ends on a string, then the # document was 'valid'. # t = Parslet::Transform.new do rule( o: {name: simple(:tag)}, c: {name: simple(:tag)}, i: simple(:t) ) { 'verified' } end t.apply(r) end pp check("some text in the tags") pp check("some text in the tags") parslet-1.8.2/example/output/0000755000004100000410000000000013242043720016214 5ustar www-datawww-dataparslet-1.8.2/example/output/deepest_errors.out0000644000004100000410000000753113242043720022000 0ustar www-datawww-data-------------------------------------------------------------------------------- . 10 . 20 01 02 define f() 03 @res.name 04 end 05 Failed to match sequence (LINE_SEPARATOR? BLOCK LINE_SEPARATOR?) at line 2 char 5. `- Expected one of [DEFINE_BLOCK, BEGIN_BLOCK] at line 2 char 5. |- Failed to match sequence (define:'define' SPACE name:IDENTIFIER '()' BODY 'end') at line 2 char 15. | `- Failed to match sequence (body:((LINE_SEPARATOR (BLOCK / EXPRESSION)){1, }) LINE_SEPARATOR) at line 3 char 11. | `- Expected at least 1 of SPACE? (COMMENT? NEWLINE / ';') SPACE? at line 3 char 11. | `- Failed to match sequence (SPACE? (COMMENT? NEWLINE / ';') SPACE?) at line 3 char 11. | `- Expected one of [COMMENT? NEWLINE, ';'] at line 3 char 11. | |- Failed to match sequence (COMMENT? NEWLINE) at line 3 char 11. | | `- Expected "()", but got "\n " at line 3 char 16. | `- Expected "()", but got "\n " at line 3 char 16. `- Failed to match sequence (pre:((type:'concurrent' SPACE)?) begin:'begin' BODY 'end') at line 2 char 5. `- Expected "()", but got "\n " at line 3 char 16. -------------------------------------------------------------------------------- . 10 . 20 01 02 define f() 03 begin 04 @res.name 05 end 06 end 07 Failed to match sequence (LINE_SEPARATOR? BLOCK LINE_SEPARATOR?) at line 2 char 5. `- Expected one of [DEFINE_BLOCK, BEGIN_BLOCK] at line 2 char 5. |- Failed to match sequence (define:'define' SPACE name:IDENTIFIER '()' BODY 'end') at line 2 char 15. | `- Failed to match sequence (body:((LINE_SEPARATOR (BLOCK / EXPRESSION)){1, }) LINE_SEPARATOR) at line 2 char 15. | `- Expected at least 1 of LINE_SEPARATOR (BLOCK / EXPRESSION) at line 2 char 15. | `- Failed to match sequence (LINE_SEPARATOR (BLOCK / EXPRESSION)) at line 3 char 7. | `- Expected one of [BLOCK, EXPRESSION] at line 3 char 7. | |- Expected one of [DEFINE_BLOCK, BEGIN_BLOCK] at line 3 char 7. | | |- Failed to match sequence (define:'define' SPACE name:IDENTIFIER '()' BODY 'end') at line 3 char 7. | | | `- Expected "define", but got "begin\n" at line 3 char 7. | | `- Failed to match sequence (pre:((type:'concurrent' SPACE)?) begin:'begin' BODY 'end') at line 3 char 12. | | `- Failed to match sequence (body:((LINE_SEPARATOR (BLOCK / EXPRESSION)){1, }) LINE_SEPARATOR) at line 4 char 13. | | `- Expected at least 1 of SPACE? (COMMENT? NEWLINE / ';') SPACE? at line 4 char 13. | | `- Failed to match sequence (SPACE? (COMMENT? NEWLINE / ';') SPACE?) at line 4 char 13. | | `- Expected one of [COMMENT? NEWLINE, ';'] at line 4 char 13. | | |- Failed to match sequence (COMMENT? NEWLINE) at line 4 char 13. | | | `- Expected "()", but got "\n " at line 4 char 18. | | `- Expected "()", but got "\n " at line 4 char 18. | `- Failed to match sequence (RES_ACTIONS res_field:((':' name:IDENTIFIER)?)) at line 3 char 7. | `- Failed to match sequence (resources:REFERENCE res_actions:(res_action:RES_ACTION_OR_LINK{0, })) at line 3 char 7. | `- Failed to match sequence ('@'{1, 2} IDENTIFIER) at line 3 char 7. | `- Expected at least 1 of '@' at line 3 char 7. | `- Expected "()", but got "\n " at line 4 char 18. `- Failed to match sequence (pre:((type:'concurrent' SPACE)?) begin:'begin' BODY 'end') at line 2 char 5. `- Expected "()", but got "\n " at line 4 char 18. -------------------------------------------------------------------------------- parslet-1.8.2/example/output/boolean_algebra.out0000644000004100000410000000017613242043720022045 0ustar www-datawww-data{:and=> {:left=>{:var=>"1"@3}, :right=>{:or=>{:left=>{:var=>"2"@13}, :right=>{:var=>"3"@21}}}}} [["1", "2"], ["1", "3"]] parslet-1.8.2/example/output/prec_calc.out0000644000004100000410000000010513242043720020654 0ustar www-datawww-dataa = 1 b = 2 c = 3 * 25 d = 100 + 3*4 {:a=>1, :b=>2, :c=>75, :d=>112} parslet-1.8.2/example/output/documentation.out0000644000004100000410000000001113242043720021606 0ustar www-datawww-data"aaaa"@0 parslet-1.8.2/example/output/parens.out0000644000004100000410000000066013242043720020237 0ustar www-datawww-data (): {:l=>"("@0, :m=>nil, :r=>")"@1} (1 parens) (()): {:l=>"("@0, :m=>{:l=>"("@1, :m=>nil, :r=>")"@2}, :r=>")"@3} (2 parens) ((((())))): {:l=>"("@0, :m=>{:l=>"("@1, :m=>{:l=>"("@2, :m=>{:l=>"("@3, :m=>{:l=>"("@4, :m=>nil, :r=>")"@5}, :r=>")"@6}, :r=>")"@7}, :r=>")"@8}, :r=>")"@9} (5 parens) ((()): Failed to match sequence (l:'(' m:(BALANCED?) r:')') at line 1 char 6. parslet-1.8.2/example/output/minilisp.out0000644000004100000410000000015513242043720020572 0ustar www-datawww-data[:define, :test, [:lambda, [], [:begin, [:display, "something"@54], [:display, 1], [:display, 3.08]]]] parslet-1.8.2/example/output/modularity.out0000644000004100000410000000000013242043720021124 0ustar www-datawww-dataparslet-1.8.2/example/output/seasons.out0000644000004100000410000000100213242043720020411 0ustar www-datawww-data"And when Spring comes" {:bud=>{:stem=>[{:branch=>:leaf}]}} "And when Summer comes" {:bud=>{:stem=>[{:branch=>[:leaf, :flower]}]}} "And when Fall comes" Fruit! Falling Leaves! {:bud=>{:stem=>[{:branch=>[]}]}} "And when Winter comes" {:bud=>{:stem=>[]}} "And when Spring comes" {:bud=>{:stem=>[{:branch=>:leaf}]}} "And when Summer comes" {:bud=>{:stem=>[{:branch=>[:leaf, :flower]}]}} "And when Fall comes" Fruit! Falling Leaves! {:bud=>{:stem=>[{:branch=>[]}]}} "And when Winter comes" {:bud=>{:stem=>[]}} parslet-1.8.2/example/output/local.out0000644000004100000410000000024013242043720020033 0ustar www-datawww-data{:e=>"a"@0, :rec=>{:e=>"a"@1, :rec=>{:e=>"a"@2, :rec=>{:e=>"a"@3, :rec=>nil}}}} e2:'aa' !. / e1:'a' rec:B {:e1=>"a"@0, :rec=>{:e1=>"a"@1, :rec=>{:e2=>"aa"@2}}} parslet-1.8.2/example/output/ignore_whitespace.out0000644000004100000410000000006513242043720022445 0ustar www-datawww-data[{:a=>"a"@0}, {:a=>"a"@1}, {:a=>"a"@5}, {:a=>"a"@7}] parslet-1.8.2/example/output/calc.out0000644000004100000410000000003513242043720017645 0ustar www-datawww-data123*2 (command line): -> 246 parslet-1.8.2/example/output/ignore.out0000644000004100000410000000000713242043720020225 0ustar www-datawww-data"ac"@0 parslet-1.8.2/example/output/erb.out0000644000004100000410000000053713242043720017522 0ustar www-datawww-data{:text=>[{:text=>"The value of x is "@0}, {:expression=>{:ruby=>" x "@21}}, {:text=>"."@26}]} {:text=>[{:code=>{:ruby=>" 1 + 2 "@2}}]} {:text=>[{:comment=>{:ruby=>" commented "@3}}]} The not printed result of "a = 2". The not printed non-evaluated comment "a = 1", see the value of a below. The nicely printed result. The value of a is 2, and b is 3. parslet-1.8.2/example/output/string_parser.out0000644000004100000410000000020413242043720021623 0ustar www-datawww-data[#, #, #] parslet-1.8.2/example/output/ip_address.out0000644000004100000410000000111613242043720021061 0ustar www-datawww-data 0.0.0.0 -> {:ipv4=>"0.0.0.0"@0} 255.255.255.255 -> {:ipv4=>"255.255.255.255"@0} 255.255.255 -> Failed: Expected one of [IPV4, IPV6] at line 1 char 1. 1:2:3:4:5:6:7:8 -> {:ipv6=>"1:2:3:4:5:6:7:8"@0} 12AD:34FC:A453:1922:: -> {:ipv6=>"12AD:34FC:A453:1922::"@0} 12AD::34FC -> {:ipv6=>"12AD::34FC"@0} 12AD:: -> {:ipv6=>"12AD::"@0} :: -> {:ipv6=>"::"@0} 1:2 -> Failed: Expected one of [IPV4, IPV6] at line 1 char 1. parslet-1.8.2/example/output/capture.out0000644000004100000410000000015213242043720020406 0ustar www-datawww-data[{:line=>"Text1\n"@9}, {:doc=>[{:line=>"Text3\n"@23}, {:line=>"Text4\n"@29}]}, {:line=>"\nText2\n"@41}] parslet-1.8.2/example/output/empty.err0000644000004100000410000000020313242043720020057 0ustar www-datawww-dataexample/empty.rb:13:in `
': rule(:empty) { ... } returns nil. Still not implemented, but already used? (NotImplementedError) parslet-1.8.2/example/output/sentence.out0000644000004100000410000000157413242043720020560 0ustar www-datawww-data["RubyKaigi2009のテーマは、「変わる/変える」です。", " 前回のRubyKaigi2008のテーマであった「多様性」の言葉の通り、 2008年はRubyそのものに関しても、またRubyの活躍する舞台に関しても、 ますます多様化が進みつつあります。", "RubyKaigi2008は、そのような Rubyの生態系をあらためて認識する場となりました。", " しかし、こうした多様化が進む中、異なる者同士が単純に距離を 置いたままでは、その違いを認識したところであまり意味がありません。", " 異なる実装、異なる思想、異なる背景といった、様々な多様性を理解しつつ、 すり合わせるべきものをすり合わせ、変えていくべきところを 変えていくことが、豊かな未来へとつながる道に違いありません。"] parslet-1.8.2/example/output/comments.out0000644000004100000410000000043413242043720020573 0ustar www-datawww-data[{:exp=>{:a=>"a"@3}}, {:line=>"// line comment"@7}, {:exp=>{:a=>"a"@25}}, {:exp=>{:a=>"a"@27}}, {:exp=>[{:a=>"a"@29}, {:line=>"// line comment"@31}]}, {:exp=>[{:a=>"a"@49}, {:multi=>"/* inline comment */"@51}]}, {:exp=>{:a=>"a"@72}}, {:multi=>"/* multiline\n comment */"@77}] parslet-1.8.2/example/output/optimized_erb.out0000644000004100000410000001262313242043720021605 0ustar www-datawww-data{:text=>[{:text=>"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\nquis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\nconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\ncillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\nproident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\nquis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\nconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\ncillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\nproident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\nquis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\nconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\ncillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\nproident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\nquis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\nconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\ncillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\nproident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\nquis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\nconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\ncillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\nproident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\nquis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\nconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\ncillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\nproident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n\n"@0}, {:expression=>{:ruby=>" erb tag "@2685}}, {:text=>"\n\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\nquis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\nconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\ncillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\nproident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\nquis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\nconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\ncillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\nproident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\nquis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\nconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\ncillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\nproident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\nquis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\nconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\ncillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\nproident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\nquis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\nconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\ncillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\nproident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\nquis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\nconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\ncillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\nproident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n"@2696}]} parslet-1.8.2/example/output/documentation.err0000644000004100000410000000051113242043720021574 0ustar www-datawww-data/Users/kaspar/git_work/own/parslet/lib/parslet/atoms/base.rb:326:in `parse_failed': Don't know what to do with bbbb at line 1 char 1. (Parslet::ParseFailed) from /Users/kaspar/git_work/own/parslet/lib/parslet/atoms/base.rb:55:in `parse' from example/documentation.rb:13:in `parse' from example/documentation.rb:18:in `
' parslet-1.8.2/example/output/scopes.out0000644000004100000410000000001513242043720020235 0ustar www-datawww-dataparses 'aba' parslet-1.8.2/example/output/json.out0000644000004100000410000000102713242043720017716 0ustar www-datawww-data {:array=>[{:number=>"1"@5}, {:number=>"2"@8}, {:number=>"3"@11}, {:null=>"null"@14}, {:string=>"asdfasdf asdfds"@25}, {:object=>{:entry=>{:key=>{:string=>"a"@46}, :val=>{:number=>"-1.2"@50}}}}, {:object=>[{:entry=>{:key=>{:string=>"b"@61}, :val=>{:true=>"true"@65}}}, {:entry=>{:key=>{:string=>"c"@72}, :val=>{:false=>"false"@76}}}]}, {:number=>"0.1e24"@89}, {:true=>"true"@97}, {:false=>"false"@103}, {:array=>{:number=>"1"@112}}]} [1, 2, 3, nil, "asdfasdf asdfds", {"a"=>-1.2}, {"b"=>true, "c"=>false}, 1.0e+23, true, false, [1]] parslet-1.8.2/example/output/mathn.out0000644000004100000410000000015513242043720020055 0ustar www-datawww-datait terminates before we require mathn requiring mathn now and trying again (will hang without the fix) okay! parslet-1.8.2/example/output/email_parser.out0000644000004100000410000000016313242043720021410 0ustar www-datawww-datasince you haven't specified any EMAIL_ADDR, for testing purposes we're using a.b.c.d@gmail.com "a.b.c.d@gmail.com" parslet-1.8.2/example/output/readme.out0000644000004100000410000000010413242043720020175 0ustar www-datawww-dataString contents: This is a \"String\" in which you can escape stuff parslet-1.8.2/example/output/nested_errors.out0000644000004100000410000000751713242043720021635 0ustar www-datawww-data-------------------------------------------------------------------------------- . 10 . 20 01 02 define f() 03 @res.name 04 end 05 Failed to match sequence (LINE_SEPARATOR? BLOCK LINE_SEPARATOR?) at line 2 char 5. `- Expected one of [DEFINE_BLOCK, BEGIN_BLOCK] at line 2 char 5. |- Failed to match sequence (define:'define' SPACE name:IDENTIFIER '()' BODY 'end') at line 2 char 15. | `- Failed to match sequence (body:((LINE_SEPARATOR (BLOCK / EXPRESSION)){1, }) LINE_SEPARATOR) at line 3 char 11. | `- Expected at least 1 of SPACE? (COMMENT? NEWLINE / ';') SPACE? at line 3 char 11. | `- Failed to match sequence (SPACE? (COMMENT? NEWLINE / ';') SPACE?) at line 3 char 11. | `- Expected one of [COMMENT? NEWLINE, ';'] at line 3 char 11. | |- Failed to match sequence (COMMENT? NEWLINE) at line 3 char 11. | | `- Failed to match [\\r\\n] at line 3 char 11. | `- Expected ";", but got "." at line 3 char 11. `- Failed to match sequence (pre:((type:'concurrent' SPACE)?) begin:'begin' BODY 'end') at line 2 char 5. `- Expected "begin", but got "defin" at line 2 char 5. -------------------------------------------------------------------------------- . 10 . 20 01 02 define f() 03 begin 04 @res.name 05 end 06 end 07 Failed to match sequence (LINE_SEPARATOR? BLOCK LINE_SEPARATOR?) at line 2 char 5. `- Expected one of [DEFINE_BLOCK, BEGIN_BLOCK] at line 2 char 5. |- Failed to match sequence (define:'define' SPACE name:IDENTIFIER '()' BODY 'end') at line 2 char 15. | `- Failed to match sequence (body:((LINE_SEPARATOR (BLOCK / EXPRESSION)){1, }) LINE_SEPARATOR) at line 2 char 15. | `- Expected at least 1 of LINE_SEPARATOR (BLOCK / EXPRESSION) at line 2 char 15. | `- Failed to match sequence (LINE_SEPARATOR (BLOCK / EXPRESSION)) at line 3 char 7. | `- Expected one of [BLOCK, EXPRESSION] at line 3 char 7. | |- Expected one of [DEFINE_BLOCK, BEGIN_BLOCK] at line 3 char 7. | | |- Failed to match sequence (define:'define' SPACE name:IDENTIFIER '()' BODY 'end') at line 3 char 7. | | | `- Expected "define", but got "begin\n" at line 3 char 7. | | `- Failed to match sequence (pre:((type:'concurrent' SPACE)?) begin:'begin' BODY 'end') at line 3 char 12. | | `- Failed to match sequence (body:((LINE_SEPARATOR (BLOCK / EXPRESSION)){1, }) LINE_SEPARATOR) at line 4 char 13. | | `- Expected at least 1 of SPACE? (COMMENT? NEWLINE / ';') SPACE? at line 4 char 13. | | `- Failed to match sequence (SPACE? (COMMENT? NEWLINE / ';') SPACE?) at line 4 char 13. | | `- Expected one of [COMMENT? NEWLINE, ';'] at line 4 char 13. | | |- Failed to match sequence (COMMENT? NEWLINE) at line 4 char 13. | | | `- Failed to match [\\r\\n] at line 4 char 13. | | `- Expected ";", but got "." at line 4 char 13. | `- Failed to match sequence (RES_ACTIONS res_field:((':' name:IDENTIFIER)?)) at line 3 char 7. | `- Failed to match sequence (resources:REFERENCE res_actions:(res_action:RES_ACTION_OR_LINK{0, })) at line 3 char 7. | `- Failed to match sequence ('@'{1, 2} IDENTIFIER) at line 3 char 7. | `- Expected at least 1 of '@' at line 3 char 7. | `- Expected "@", but got "b" at line 3 char 7. `- Failed to match sequence (pre:((type:'concurrent' SPACE)?) begin:'begin' BODY 'end') at line 2 char 5. `- Expected "begin", but got "defin" at line 2 char 5. -------------------------------------------------------------------------------- parslet-1.8.2/example/output/simple_xml.out0000644000004100000410000000010513242043720021112 0ustar www-datawww-data"verified" {:o=>{:name=>"b"@1}, :i=>"verified", :c=>{:name=>"a"@33}} parslet-1.8.2/example/prec_calc.rb0000644000004100000410000000347613242043720017126 0ustar www-datawww-data # A demonstration of the new precedence climbing infix expression parser. $:.unshift File.dirname(__FILE__) + "/../lib" require 'pp' require 'rspec' require 'parslet' require 'parslet/rig/rspec' require 'parslet/convenience' class InfixExpressionParser < Parslet::Parser root :variable_assignment_list rule(:space) { match[' '] } def cts atom atom >> space.repeat end def infix *args Infix.new(*args) end # This is the heart of the infix expression parser: real simple definitions # for all the pieces we need. rule(:mul_op) { cts match['*/'] } rule(:add_op) { cts match['+-'] } rule(:digit) { match['0-9'] } rule(:integer) { cts digit.repeat(1).as(:int) } rule(:expression) { infix_expression(integer, [mul_op, 2, :left], [add_op, 1, :right]) } # And now adding variable assignments to that, just to a) demonstrate this # embedded in a bigger parser, and b) make the example interesting. rule(:variable_assignment_list) { variable_assignment.repeat(1) } rule(:variable_assignment) { identifier.as(:ident) >> equal_sign >> expression.as(:exp) >> eol } rule(:identifier) { cts (match['a-z'] >> match['a-zA-Z0-9'].repeat) } rule(:equal_sign) { cts str('=') } rule(:eol) { cts(str("\n")) | any.absent? } end class InfixInterpreter < Parslet::Transform rule(int: simple(:int)) { Integer(int) } rule(ident: simple(:ident), exp: simple(:result)) { |d| d[:doc][d[:ident].to_s.strip.to_sym] = d[:result] } rule(l: simple(:l), o: /^\*/, r: simple(:r)) { l * r } rule(l: simple(:l), o: /^\+/, r: simple(:r)) { l + r } end input = <> possible_whitespace >> cephalopod >> possible_whitespace >> str(')') parser = possible_whitespace >> parenthesized_cephalopod >> possible_whitespace # This parse fails, but that is not the point. When mathn is in the current # ruby environment, it modifies integer division in a way that makes # parslet loop indefinitely. parser.parse %{(\nsqeed)\n} rescue Parslet::ParseFailed end attempt_parse puts 'it terminates before we require mathn' puts "requiring mathn now" require 'mathn' puts "and trying again (will hang without the fix)" attempt_parse # but it doesn't terminate after requiring mathn puts "okay!"parslet-1.8.2/example/email_parser.rb0000644000004100000410000000264113242043720017647 0ustar www-datawww-data#!/usr/bin/env ruby # Example contributed by Hal Brodigan (postmodern). Thanks! $:.unshift File.dirname(__FILE__) + "/../lib" require 'parslet' require 'parslet/convenience' class EmailParser < Parslet::Parser rule(:space) { match('\s').repeat(1) } rule(:space?) { space.maybe } rule(:dash?) { match['_-'].maybe } rule(:at) { str('@') | (dash? >> (str('at') | str('AT')) >> dash?) } rule(:dot) { str('.') | (dash? >> (str('dot') | str('DOT')) >> dash?) } rule(:word) { match('[a-z0-9]').repeat(1).as(:word) >> space? } rule(:separator) { dot.as(:dot) >> space? | space } rule(:words) { word >> (separator >> word).repeat } rule(:email) { (words.as(:username) >> space? >> at >> space? >> words).as(:email) } root(:email) end class EmailSanitizer < Parslet::Transform rule(:dot => simple(:dot), :word => simple(:word)) { ".#{word}" } rule(:word => simple(:word)) { word } rule(:username => sequence(:username)) { username.join + "@" } rule(:username => simple(:username)) { username.to_s + "@" } rule(:email => sequence(:email)) { email.join } end parser = EmailParser.new sanitizer = EmailSanitizer.new input = ARGV[0] || begin default = "a.b.c.d@gmail.com" STDERR.puts "usage: #{$0} \"EMAIL_ADDR\"" STDOUT.puts "since you haven't specified any EMAIL_ADDR, for testing purposes we're using #{default}" default end p sanitizer.apply(parser.parse_with_debug(input)) parslet-1.8.2/example/string_parser.rb0000644000004100000410000000260113242043720020062 0ustar www-datawww-data# A more complex parser that illustrates how a compiler might be constructed. # The parser recognizes strings and integer literals and constructs almost a # useful AST from the file contents. require 'pp' $:.unshift File.dirname(__FILE__) + "/../lib" require 'parslet' include Parslet class LiteralsParser < Parslet::Parser rule :space do (match '[ ]').repeat(1) end rule :literals do (literal >> eol).repeat end rule :literal do (integer | string).as(:literal) >> space.maybe end rule :string do str('"') >> ( (str('\\') >> any) | (str('"').absent? >> any) ).repeat.as(:string) >> str('"') end rule :integer do match('[0-9]').repeat(1).as(:integer) end rule :eol do line_end.repeat(1) end rule :line_end do crlf >> space.maybe end rule :crlf do match('[\r\n]').repeat(1) end root :literals end input_name = File.join(File.dirname(__FILE__), 'simple.lit') file = File.read(input_name) parsetree = LiteralsParser.new.parse(file) class Lit < Struct.new(:text) def to_s text.inspect end end class StringLit < Lit end class IntLit < Lit def to_s text end end transform = Parslet::Transform.new do rule(:literal => {:integer => simple(:x)}) { IntLit.new(x) } rule(:literal => {:string => simple(:s)}) { StringLit.new(s) } end ast = transform.apply(parsetree) pp ast