d-parse 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +104 -0
- data/Guardfile +3 -0
- data/LICENSE +19 -0
- data/NEWS.md +0 -0
- data/README.md +137 -0
- data/Rakefile +14 -0
- data/d-parse.gemspec +26 -0
- data/lib/d-parse.rb +10 -0
- data/lib/d-parse/dsl.rb +71 -0
- data/lib/d-parse/failure.rb +46 -0
- data/lib/d-parse/parser.rb +102 -0
- data/lib/d-parse/parsers.rb +26 -0
- data/lib/d-parse/parsers/combinators/alt.rb +32 -0
- data/lib/d-parse/parsers/combinators/repeat.rb +42 -0
- data/lib/d-parse/parsers/combinators/seq.rb +49 -0
- data/lib/d-parse/parsers/highlevel/char_in.rb +13 -0
- data/lib/d-parse/parsers/highlevel/intersperse.rb +18 -0
- data/lib/d-parse/parsers/highlevel/json.rb +237 -0
- data/lib/d-parse/parsers/highlevel/opt.rb +16 -0
- data/lib/d-parse/parsers/highlevel/string.rb +13 -0
- data/lib/d-parse/parsers/highlevel/whitespace_char.rb +15 -0
- data/lib/d-parse/parsers/modifiers/capturing.rb +13 -0
- data/lib/d-parse/parsers/modifiers/describe.rb +28 -0
- data/lib/d-parse/parsers/modifiers/ignore.rb +17 -0
- data/lib/d-parse/parsers/modifiers/lazy.rb +18 -0
- data/lib/d-parse/parsers/modifiers/map.rb +24 -0
- data/lib/d-parse/parsers/primitives/any.rb +22 -0
- data/lib/d-parse/parsers/primitives/bind.rb +25 -0
- data/lib/d-parse/parsers/primitives/char.rb +27 -0
- data/lib/d-parse/parsers/primitives/char_not.rb +27 -0
- data/lib/d-parse/parsers/primitives/char_not_in.rb +30 -0
- data/lib/d-parse/parsers/primitives/eof.rb +21 -0
- data/lib/d-parse/parsers/primitives/except.rb +33 -0
- data/lib/d-parse/parsers/primitives/fail.rb +17 -0
- data/lib/d-parse/parsers/primitives/succeed.rb +13 -0
- data/lib/d-parse/position.rb +31 -0
- data/lib/d-parse/success.rb +35 -0
- data/lib/d-parse/version.rb +3 -0
- data/samples/parse-bind +25 -0
- data/samples/parse-csv +19 -0
- data/samples/parse-errortest +45 -0
- data/samples/parse-fun +61 -0
- data/samples/parse-json +18 -0
- data/samples/parse-readme +27 -0
- data/spec/d-parse/failure_spec.rb +36 -0
- data/spec/d-parse/parser_spec.rb +77 -0
- data/spec/d-parse/parsers/alt_spec.rb +48 -0
- data/spec/d-parse/parsers/any_spec.rb +15 -0
- data/spec/d-parse/parsers/bind_spec.rb +31 -0
- data/spec/d-parse/parsers/capture_spec.rb +11 -0
- data/spec/d-parse/parsers/char_in_spec.rb +22 -0
- data/spec/d-parse/parsers/char_not_in_spec.rb +23 -0
- data/spec/d-parse/parsers/char_not_spec.rb +16 -0
- data/spec/d-parse/parsers/char_spec.rb +22 -0
- data/spec/d-parse/parsers/describe_spec.rb +22 -0
- data/spec/d-parse/parsers/end_of_input_spec.rb +20 -0
- data/spec/d-parse/parsers/except_spec.rb +20 -0
- data/spec/d-parse/parsers/fail_spec.rb +12 -0
- data/spec/d-parse/parsers/intersperse_spec.rb +18 -0
- data/spec/d-parse/parsers/json_spec.rb +69 -0
- data/spec/d-parse/parsers/lazy_spec.rb +16 -0
- data/spec/d-parse/parsers/map_spec.rb +54 -0
- data/spec/d-parse/parsers/optional_spec.rb +16 -0
- data/spec/d-parse/parsers/or_spec.rb +26 -0
- data/spec/d-parse/parsers/repeat_spec.rb +40 -0
- data/spec/d-parse/parsers/sequence_spec.rb +52 -0
- data/spec/d-parse/parsers/string_spec.rb +19 -0
- data/spec/d-parse/parsers/succeed_spec.rb +12 -0
- data/spec/d-parse/parsers/whitespace_char_spec.rb +14 -0
- data/spec/spec_helper.rb +97 -0
- metadata +140 -0
    
        data/samples/parse-bind
    ADDED
    
    | @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'd-parse'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            include DParse::DSL
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            digit = chars(*('0'..'9')).capture
         | 
| 8 | 
            +
            letter = chars(*('a'..'z')).capture
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            init = alt(char('d'), char('l')).capture
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            parser_map = {
         | 
| 13 | 
            +
              'd' => digit,
         | 
| 14 | 
            +
              'l' => letter,
         | 
| 15 | 
            +
            }
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            pair =
         | 
| 18 | 
            +
              init.bind do |data|
         | 
| 19 | 
            +
                new_parser = parser_map[data]
         | 
| 20 | 
            +
                new_parser.map { |d| [data, d] }
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            parser = repeat(pair)
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            p parser.apply('lad1d2d3lblc')
         | 
    
        data/samples/parse-csv
    ADDED
    
    | @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'd-parse'
         | 
| 4 | 
            +
            require 'pp'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            data = <<EOS
         | 
| 7 | 
            +
            first_name,last_name,age
         | 
| 8 | 
            +
            Denis,Defreyne,29
         | 
| 9 | 
            +
            EOS
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            module Grammar
         | 
| 12 | 
            +
              extend DParse::DSL
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              FIELD = repeat(char_not_in([',', "\n"])).capture
         | 
| 15 | 
            +
              LINE = intersperse(FIELD, char(',')).select_even
         | 
| 16 | 
            +
              FILE = seq(intersperse(LINE, char("\n")).select_even, eof).first
         | 
| 17 | 
            +
            end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            pp Grammar::FILE.apply(data)
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'd-parse'
         | 
| 4 | 
            +
            require 'pp'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module MyGrammar
         | 
| 7 | 
            +
              extend DParse::DSL
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              LETTER =
         | 
| 10 | 
            +
                describe(alt(*('a'..'z').map { |c| char(c) }), 'letter')
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              IDENTIFIER =
         | 
| 13 | 
            +
                describe(
         | 
| 14 | 
            +
                  repeat(LETTER).capture,
         | 
| 15 | 
            +
                  'identifier',
         | 
| 16 | 
            +
                )
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              FUNDEF =
         | 
| 19 | 
            +
                seq(
         | 
| 20 | 
            +
                  describe(IDENTIFIER, 'identifier'),
         | 
| 21 | 
            +
                  eof,
         | 
| 22 | 
            +
                )
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              PARSER =
         | 
| 25 | 
            +
                alt(
         | 
| 26 | 
            +
                  string('Hello!'),
         | 
| 27 | 
            +
                  string('Hello, world!'),
         | 
| 28 | 
            +
                  string('Hello… ?'),
         | 
| 29 | 
            +
                )
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              TESTB =
         | 
| 32 | 
            +
                seq(
         | 
| 33 | 
            +
                  alt(
         | 
| 34 | 
            +
                    seq(string('a+a').capture),
         | 
| 35 | 
            +
                    seq(string('a').capture),
         | 
| 36 | 
            +
                  ),
         | 
| 37 | 
            +
                  eof,
         | 
| 38 | 
            +
                )
         | 
| 39 | 
            +
            end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            puts MyGrammar::PARSER.apply('Hello, what?').pretty_message
         | 
| 42 | 
            +
            puts
         | 
| 43 | 
            +
            puts MyGrammar::FUNDEF.apply('foo3a').pretty_message
         | 
| 44 | 
            +
            puts
         | 
| 45 | 
            +
            puts MyGrammar::TESTB.apply('a+').pretty_message
         | 
    
        data/samples/parse-fun
    ADDED
    
    | @@ -0,0 +1,61 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'd-parse'
         | 
| 4 | 
            +
            require 'pp'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            data = <<EOS
         | 
| 7 | 
            +
            add(1, mul(2, 3))
         | 
| 8 | 
            +
            mul(2,3)
         | 
| 9 | 
            +
            EOS
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            include DParse::DSL
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            # primitives
         | 
| 14 | 
            +
            digit = describe(alt(*('0'..'9').map { |c| char(c) }), 'digit')
         | 
| 15 | 
            +
            letter = describe(alt(*('a'..'z').map { |c| char(c) }), 'letter')
         | 
| 16 | 
            +
            lparen = char('(')
         | 
| 17 | 
            +
            rparen = char(')')
         | 
| 18 | 
            +
            comma = char(',')
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            # basic
         | 
| 21 | 
            +
            identifier = describe(seq(letter, repeat(letter)).capture, 'identifier')
         | 
| 22 | 
            +
            lit = seq(digit, repeat(digit)).capture.map { |d| d.to_i(10) }
         | 
| 23 | 
            +
            eof_with_opt_nl = seq(opt(char("\n")), eof)
         | 
| 24 | 
            +
            whitespace = repeat(whitespace_char)
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            # complex
         | 
| 27 | 
            +
            funcall = nil
         | 
| 28 | 
            +
            expression = alt(lazy { funcall }, lit)
         | 
| 29 | 
            +
            arglist_tail = repeat(seq(comma, whitespace, expression).map { |d| d[2] })
         | 
| 30 | 
            +
            arglist = seq(expression, arglist_tail).map { |d| [d[0]] + d[1] }
         | 
| 31 | 
            +
            funcall = seq(identifier, lparen, arglist, rparen).map { |d| [d[0]] + d[2] }
         | 
| 32 | 
            +
            program = seq(intersperse(expression, char("\n")).select_even, eof_with_opt_nl).first
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            def eval_expr(expr)
         | 
| 35 | 
            +
              case expr
         | 
| 36 | 
            +
              when Array
         | 
| 37 | 
            +
                case expr[0]
         | 
| 38 | 
            +
                when 'add'
         | 
| 39 | 
            +
                  eval_expr(expr[1]) + eval_expr(expr[2])
         | 
| 40 | 
            +
                when 'mul'
         | 
| 41 | 
            +
                  eval_expr(expr[1]) * eval_expr(expr[2])
         | 
| 42 | 
            +
                else
         | 
| 43 | 
            +
                  raise '???'
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              when Integer
         | 
| 46 | 
            +
                expr
         | 
| 47 | 
            +
              else
         | 
| 48 | 
            +
                raise '???'
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
            end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            res = program.apply(data)
         | 
| 53 | 
            +
            case res
         | 
| 54 | 
            +
            when DParse::Success
         | 
| 55 | 
            +
              exprs = res.data
         | 
| 56 | 
            +
              pp exprs
         | 
| 57 | 
            +
              exprs.each { |e| p eval_expr(e) }
         | 
| 58 | 
            +
            when DParse::Failure
         | 
| 59 | 
            +
              $stderr.puts res.pretty_message
         | 
| 60 | 
            +
              exit 1
         | 
| 61 | 
            +
            end
         | 
    
        data/samples/parse-json
    ADDED
    
    | @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'd-parse'
         | 
| 4 | 
            +
            require 'pp'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            json = DParse::Parsers::JSON.new
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            puts "[#{Time.now}] start"
         | 
| 9 | 
            +
            res = json.apply($stdin.read)
         | 
| 10 | 
            +
            puts "[#{Time.now}] stop"
         | 
| 11 | 
            +
            puts
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            case res
         | 
| 14 | 
            +
            when DParse::Success
         | 
| 15 | 
            +
              pp res.data
         | 
| 16 | 
            +
            when DParse::Failure
         | 
| 17 | 
            +
              puts res.pretty_message
         | 
| 18 | 
            +
            end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'd-parse'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Grammar
         | 
| 6 | 
            +
              extend DParse::DSL
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              DIGIT = char_in('0'..'9')
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              ROOT =
         | 
| 11 | 
            +
                seq(
         | 
| 12 | 
            +
                  intersperse(
         | 
| 13 | 
            +
                    repeat(DIGIT).capture.map { |d| d.to_i(10) },
         | 
| 14 | 
            +
                    char(',').ignore,
         | 
| 15 | 
            +
                  ).compact,
         | 
| 16 | 
            +
                  eof,
         | 
| 17 | 
            +
                ).first
         | 
| 18 | 
            +
            end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            res = Grammar::ROOT.apply('1,2,100,582048,07,09')
         | 
| 21 | 
            +
            case res
         | 
| 22 | 
            +
            when DParse::Success
         | 
| 23 | 
            +
              p res.data
         | 
| 24 | 
            +
            when DParse::Failure
         | 
| 25 | 
            +
              $stderr.puts res.pretty_message
         | 
| 26 | 
            +
              exit 1
         | 
| 27 | 
            +
            end
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            describe DParse::Failure do
         | 
| 2 | 
            +
              let(:failure) { described_class.new("one\ntwo\nthree\nfour\nfive", position, origin: origin) }
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              let(:position) { DParse::Position.new(index: 13, line: 2, column: 3) }
         | 
| 5 | 
            +
              let(:origin) { double(:origin, expectation_message: expectation_message) }
         | 
| 6 | 
            +
              let(:expectation_message) { 'lols' }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              describe '#full_message' do
         | 
| 9 | 
            +
                subject { failure.full_message }
         | 
| 10 | 
            +
                it { is_expected.to eql('expected lols at line 3, column 4') }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                context 'no origin' do
         | 
| 13 | 
            +
                  let(:origin) { nil }
         | 
| 14 | 
            +
                  it { is_expected.to eql('expected ? at line 3, column 4') }
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              describe '#pretty_message' do
         | 
| 19 | 
            +
                subject { failure.pretty_message }
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                context 'column 3' do
         | 
| 22 | 
            +
                  let(:position) { DParse::Position.new(index: 13, line: 2, column: 3) }
         | 
| 23 | 
            +
                  it { is_expected.to eql("expected lols at line 3, column 4\n\nthr\e[31me\e[0me\n\e[31m   ↑\e[0m") }
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                context 'column 4' do
         | 
| 27 | 
            +
                  let(:position) { DParse::Position.new(index: 14, line: 2, column: 4) }
         | 
| 28 | 
            +
                  it { is_expected.to eql("expected lols at line 3, column 5\n\nthre\e[31me\e[0m\n\e[31m    ↑\e[0m") }
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                context 'column 5' do
         | 
| 32 | 
            +
                  let(:position) { DParse::Position.new(index: 15, line: 2, column: 5) }
         | 
| 33 | 
            +
                  it { is_expected.to eql("expected lols at line 3, column 6\n\nthree\n\e[31m     ↑\e[0m") }
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| @@ -0,0 +1,77 @@ | |
| 1 | 
            +
            describe DParse::Parser do
         | 
| 2 | 
            +
              context 'bare parser' do
         | 
| 3 | 
            +
                let(:klass) do
         | 
| 4 | 
            +
                  Class.new(described_class) do
         | 
| 5 | 
            +
                  end
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                let(:parser) { klass.new }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                it 'errors on #read' do
         | 
| 11 | 
            +
                  expect { parser.read('foo', :some_pos) }.to raise_error(NotImplementedError)
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                it 'errors on #inspect' do
         | 
| 15 | 
            +
                  expect { parser.inspect }.to raise_error(NotImplementedError)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              context 'parser returning an array' do
         | 
| 20 | 
            +
                let(:klass) do
         | 
| 21 | 
            +
                  Class.new(described_class) do
         | 
| 22 | 
            +
                    def initialize(result)
         | 
| 23 | 
            +
                      @result = result
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    def read(_input, _pos)
         | 
| 27 | 
            +
                      @result
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    def inspect
         | 
| 31 | 
            +
                      'sample()'
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                let(:result) { DParse::Success.new('…', pos, data: [:a, :b, :c, :d, :e]) }
         | 
| 37 | 
            +
                let(:pos) { DParse::Position.new(index: 0, line: 0, column: 0) }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                let(:parser) { klass.new(result) }
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                describe '#first' do
         | 
| 42 | 
            +
                  subject { parser.first }
         | 
| 43 | 
            +
                  example { expect(subject).to parse('…').and_capture(:a) }
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                describe '#second' do
         | 
| 47 | 
            +
                  subject { parser.second }
         | 
| 48 | 
            +
                  example { expect(subject).to parse('…').and_capture(:b) }
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                describe '#select_even' do
         | 
| 52 | 
            +
                  subject { parser.select_even }
         | 
| 53 | 
            +
                  example { expect(subject).to parse('…').and_capture([:a, :c, :e]) }
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                describe '#select_odd' do
         | 
| 57 | 
            +
                  subject { parser.select_odd }
         | 
| 58 | 
            +
                  example { expect(subject).to parse('…').and_capture([:b, :d]) }
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                describe '#match?' do
         | 
| 62 | 
            +
                  subject { parser.match?('…') }
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  let(:pos) { DParse::Position.new(index: 0, line: 0, column: 0) }
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  context 'failure' do
         | 
| 67 | 
            +
                    let(:result) { DParse::Failure.new('…', pos) }
         | 
| 68 | 
            +
                    it { is_expected.to equal(false) }
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  context 'success' do
         | 
| 72 | 
            +
                    let(:result) { DParse::Success.new('…', pos) }
         | 
| 73 | 
            +
                    it { is_expected.to equal(true) }
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
            end
         | 
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            describe DParse::Parsers::Alt do
         | 
| 2 | 
            +
              let(:a) { DParse::Parsers::String.new('Hello') }
         | 
| 3 | 
            +
              let(:b) { DParse::Parsers::String.new('Goodbye') }
         | 
| 4 | 
            +
              let(:parser) { described_class.new(a, b) }
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              example { expect(parser).to parse('Hello').up_to(5).line(0).column(5) }
         | 
| 7 | 
            +
              example { expect(parser).to parse('Goodbye').up_to(7).line(0).column(7) }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0).with_failure('expected \'H\'') }
         | 
| 10 | 
            +
              example { expect(parser).not_to parse('Wilkommen').and_fail_at(0).line(0).column(0).with_failure('expected \'H\'') }
         | 
| 11 | 
            +
              example { expect(parser).not_to parse('Hallo').and_fail_at(1).line(0).column(1).with_failure('expected \'e\'') }
         | 
| 12 | 
            +
              example { expect(parser).not_to parse('Hellenistic period').and_fail_at(4).line(0).column(4).with_failure('expected \'o\'') }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              describe '#inspect' do
         | 
| 15 | 
            +
                subject { parser.inspect }
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                let(:a) { DParse::Parsers::Char.new('a') }
         | 
| 18 | 
            +
                let(:b) { DParse::Parsers::Char.new('b') }
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                it { is_expected.to eql('alt(char("a"),char("b"))') }
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              context 'ambiguous parsers' do
         | 
| 24 | 
            +
                let(:a) { double('parser') }
         | 
| 25 | 
            +
                let(:b) { double('parser') }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                before do
         | 
| 28 | 
            +
                  expect(a).to receive(:read).and_return(DParse::Failure.new('…', DParse::Position.new(index: 10, line: 0, column: 10)))
         | 
| 29 | 
            +
                  expect(b).to receive(:read).and_return(DParse::Failure.new('…', DParse::Position.new(index: 20, line: 0, column: 20)))
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                context 'shortest first' do
         | 
| 33 | 
            +
                  let(:parser) { described_class.new(a, b) }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  it 'picks the most specific failure' do
         | 
| 36 | 
            +
                    expect(parser).not_to parse('…').and_fail_at(20).line(0).column(20)
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                context 'longest first' do
         | 
| 41 | 
            +
                  let(:parser) { described_class.new(b, a) }
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  it 'picks the most specific failure' do
         | 
| 44 | 
            +
                    expect(parser).not_to parse('…').and_fail_at(20).line(0).column(20)
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            describe DParse::Parsers::Any do
         | 
| 2 | 
            +
              let(:parser) { described_class.new }
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              example { expect(parser).to parse('a').up_to(1).line(0).column(1) }
         | 
| 5 | 
            +
              example { expect(parser).to parse('b').up_to(1).line(0).column(1) }
         | 
| 6 | 
            +
              example { expect(parser).to parse('ab').up_to(1).line(0).column(1) }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0).with_failure('expected any character except end of file') }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              describe '#inspect' do
         | 
| 11 | 
            +
                subject { parser.inspect }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                it { is_expected.to eql('any()') }
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            describe DParse::Parsers::Bind do
         | 
| 2 | 
            +
              let(:a) { DParse::Parsers::Char.new('a') }
         | 
| 3 | 
            +
              let(:b) { DParse::Parsers::Char.new('b') }
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              let(:init) { DParse::Parsers::Alt.new(a, b).capture }
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              let(:parser) do
         | 
| 8 | 
            +
                parser_map = {
         | 
| 9 | 
            +
                  'a' => DParse::Parsers::Char.new('0'),
         | 
| 10 | 
            +
                  'b' => DParse::Parsers::Char.new('1'),
         | 
| 11 | 
            +
                }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                init.bind do |data|
         | 
| 14 | 
            +
                  new_parser = parser_map[data]
         | 
| 15 | 
            +
                  new_parser.map { |d| [data, d] }
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              example { expect(parser).to parse('a0').up_to(2).line(0).column(2) }
         | 
| 20 | 
            +
              example { expect(parser).to parse('b1').up_to(2).line(0).column(2) }
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0) }
         | 
| 23 | 
            +
              example { expect(parser).not_to parse('a').and_fail_at(1).line(0).column(1) }
         | 
| 24 | 
            +
              example { expect(parser).not_to parse('aa').and_fail_at(1).line(0).column(1) }
         | 
| 25 | 
            +
              example { expect(parser).not_to parse('ab').and_fail_at(1).line(0).column(1) }
         | 
| 26 | 
            +
              example { expect(parser).not_to parse('a1').and_fail_at(1).line(0).column(1) }
         | 
| 27 | 
            +
              example { expect(parser).not_to parse('b').and_fail_at(1).line(0).column(1) }
         | 
| 28 | 
            +
              example { expect(parser).not_to parse('ba').and_fail_at(1).line(0).column(1) }
         | 
| 29 | 
            +
              example { expect(parser).not_to parse('bb').and_fail_at(1).line(0).column(1) }
         | 
| 30 | 
            +
              example { expect(parser).not_to parse('b0').and_fail_at(1).line(0).column(1) }
         | 
| 31 | 
            +
            end
         | 
| @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            describe DParse::Parsers::Capturing do
         | 
| 2 | 
            +
              let(:any) { DParse::Parsers::Any.new }
         | 
| 3 | 
            +
              let(:parser) { described_class.new(any) }
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              example { expect(parser).to parse('a').up_to(1).line(0).column(1).and_capture('a') }
         | 
| 6 | 
            +
              example { expect(parser).to parse('aa').up_to(1).line(0).column(1).and_capture('a')  }
         | 
| 7 | 
            +
              example { expect(parser).to parse('ab').up_to(1).line(0).column(1).and_capture('a')  }
         | 
| 8 | 
            +
              example { expect(parser).to parse('ba').up_to(1).line(0).column(1).and_capture('b')  }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0) }
         | 
| 11 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            describe DParse::Parsers::CharIn do
         | 
| 2 | 
            +
              let(:parser) { described_class.new(%w(a b)) }
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              example { expect(parser).to parse('a').up_to(1) }
         | 
| 5 | 
            +
              example { expect(parser).to parse('b').up_to(1) }
         | 
| 6 | 
            +
              example { expect(parser).to parse('ab').up_to(1) }
         | 
| 7 | 
            +
              example { expect(parser).to parse('ac').up_to(1) }
         | 
| 8 | 
            +
              example { expect(parser).to parse('ba').up_to(1) }
         | 
| 9 | 
            +
              example { expect(parser).to parse('bc').up_to(1) }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              example { expect(parser).not_to parse('').and_fail_at(0).line(0).column(0) }
         | 
| 12 | 
            +
              example { expect(parser).not_to parse('c').and_fail_at(0).line(0).column(0) }
         | 
| 13 | 
            +
              example { expect(parser).not_to parse('d').and_fail_at(0).line(0).column(0) }
         | 
| 14 | 
            +
              example { expect(parser).not_to parse('ca').and_fail_at(0).line(0).column(0) }
         | 
| 15 | 
            +
              example { expect(parser).not_to parse('db').and_fail_at(0).line(0).column(0) }
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              describe '#inspect' do
         | 
| 18 | 
            +
                subject { parser.inspect }
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                it { is_expected.to eql('alt(char("a"),char("b"))') }
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         |