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.1"

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.



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

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.



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

def label
  @label || "unknown"
end

Instance Method Details

#%(name) ⇒ Object

Set the label and return self.



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

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

#*(n) ⇒ Object

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



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

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.



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

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.



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

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.



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

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.



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

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


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

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

#parse(src) ⇒ Object

Parse a String or IO object.



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

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.



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

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")


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

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


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

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.



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

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

#~Object



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

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