Class: Parsby

Inherits:
Object
  • Object
show all
Includes:
Combinators
Defined in:
lib/parsby.rb,
lib/parsby/version.rb,
lib/parsby/combinators.rb

Defined Under Namespace

Modules: Combinators, Example, Tree Classes: BackedIO, Backup, Context, Error, ExpectationFailed, ParsedRange, PosRange, Splicer

Constant Summary collapse

VERSION =
"1.1.2"

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Combinators

#splicer

Methods included from Combinators::ModuleMethods

#define_combinator

Constructor Details

#initialize(label = nil, &b) ⇒ Parsby

Initialize parser with optional label argument, and parsing block. The parsing block is given an IO as argument, and its result is the result when parsing.



607
608
609
610
# File 'lib/parsby.rb', line 607

def initialize(label = nil, &b)
  self.label = label if label
  @parser = b
end

Instance Attribute Details

#labelObject

The parser’s label. It’s an “unknown” token by default.



598
599
600
# File 'lib/parsby.rb', line 598

def label
  @label || "unknown"
end

Instance Method Details

#%(name) ⇒ Object

Set the label and return self.



710
711
712
713
# File 'lib/parsby.rb', line 710

def %(name)
  self.label = name
  self
end

#*(n) ⇒ Object

p * n, runs parser p n times, grouping results in an array.



685
686
687
688
689
# File 'lib/parsby.rb', line 685

def *(n)
  Parsby.new "(#{label} * #{n})" do |c|
    n.times.map { parse c }
  end
end

#+(p) ⇒ Object

x + y does + on the results of x and y. This is mostly meant to be used with arrays, but it would work with numbers and strings too.



693
694
695
696
697
# File 'lib/parsby.rb', line 693

def +(p)
  group(self, p)
    .fmap {|(x, y)| x + y }
    .tap {|r| r.label = "(#{label} + #{p.label})" }
end

#<(p) ⇒ Object

x < y runs parser x then y and returns x.



660
661
662
663
664
# File 'lib/parsby.rb', line 660

def <(p)
  ~splicer.start do |m|
    m.end(self).then {|r| m.end(p).then { pure r } }
  end % "(#{label} < #{p.label})"
end

#<<(p) ⇒ Object

xs << x appends result of parser x to list result of parser xs.



700
701
702
703
704
705
706
707
# File 'lib/parsby.rb', line 700

def <<(p)
  Parsby.new "(#{label} << #{p.label})" do |c|
    x = parse c
    y = p.parse c
    # like x << y, but without modifying x.
    x + [y]
  end
end

#>(p) ⇒ Object

x > y runs parser x then y and returns y.



667
668
669
# File 'lib/parsby.rb', line 667

def >(p)
  self.then { p } % "(#{label} > #{p.label})"
end

#fmap(&b) ⇒ Object

Like map for arrays, this lets you work with the value “inside” the parser, i.e. the result.

Example:

decimal.fmap {|x| x + 1}.parse("2")
=> 3


722
723
724
725
726
# File 'lib/parsby.rb', line 722

def fmap(&b)
  Parsby.new "#{label}.fmap" do |c|
    b.call parse c
  end
end

#parse(src) ⇒ Object

Parse a String or IO object.



613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
# File 'lib/parsby.rb', line 613

def parse(src)
  ctx = src.is_a?(Context) ? src : Context.new(src)
  parsed_range = ParsedRange.new(ctx.bio.pos, ctx.bio.pos, label)
  ctx.parsed_ranges << parsed_range if ctx.parsed_ranges
  parent_parsed_range = ctx.parsed_ranges
  ctx.parsed_ranges = parsed_range
  begin
    r = @parser.call ctx
  rescue ExpectationFailed => e
    ctx.parsed_ranges.end = ctx.bio.pos
    ctx.parsed_ranges.failed = true
    ctx.bio.restore_to ctx.parsed_ranges.start
    raise
  else
    ctx.parsed_ranges.end = ctx.bio.pos
    r
  ensure
    # Keep the root one for use in ExceptionFailed#message
    if parent_parsed_range
      ctx.parsed_ranges = parent_parsed_range
    end
  end
end

#peek(src) ⇒ Object

Parses without consuming input.



638
639
640
641
642
643
644
645
646
# File 'lib/parsby.rb', line 638

def peek(src)
  ctx = src.is_a?(Context) ? src : Context.new(src)
  starting_pos = ctx.bio.pos
  begin
    parse ctx
  ensure
    ctx.bio.restore_to starting_pos
  end
end

#that_fails(p) ⇒ Object Also known as: that_fail

x.that_fails(y) will try y, fail if y succeeds, or parse with x if y fails.

Example:

decimal.that_fails(string("10")).parse "3"
=> 3
decimal.that_fails(string("10")).parse "10"
Parsby::ExpectationFailed: line 1:
  10
  \/ expected: (not "10")


763
764
765
766
767
768
769
770
771
772
773
774
775
# File 'lib/parsby.rb', line 763

def that_fails(p)
  Parsby.new "#{label}.that_fails(#{p.label})" do |c|
    orig_pos = c.bio.pos
    begin
      r = p.parse c.bio
    rescue Error
      c.bio.restore_to orig_pos
      parse c.bio
    else
      raise ExpectationFailed.new c
    end
  end
end

#then(&b) ⇒ Object

Pass result of self parser to block to construct the next parser.

For example, instead of writing:

Parsby.new do |c|
  x = foo.parse c
  bar(x).parse c
end

you can write:

foo.then {|x| bar x }

This is analogous to Parsec’s >>= operator in Haskell, where you could write:

foo >>= bar


745
746
747
748
749
# File 'lib/parsby.rb', line 745

def then(&b)
  Parsby.new "#{label}.then" do |c|
    b.call(parse(c)).parse(c)
  end
end

#|(p) ⇒ Object

x | y tries y if x fails.



649
650
651
652
653
654
655
656
657
# File 'lib/parsby.rb', line 649

def |(p)
  Parsby.new "(#{self.label} | #{p.label})" do |c|
    begin
      parse c
    rescue Error
      p.parse c
    end
  end
end

#~Object



671
672
673
674
675
676
677
678
679
680
681
682
# File 'lib/parsby.rb', line 671

def ~
  Parsby.new "(~ #{label})" do |c|
    begin
      parse c
    ensure
      c.parsed_ranges.children[0].splice_self!
      if c.parsed_ranges.parent
        c.parsed_ranges.splice_self!
      end
    end
  end
end