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, Token

Constant Summary collapse

VERSION =
"0.1.0"

Instance Method Summary collapse

Methods included from Combinators

#splicer, #token

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.



635
636
637
638
# File 'lib/parsby.rb', line 635

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

Instance Method Details

#%(name) ⇒ Object

Set the label and return self.



736
737
738
739
# File 'lib/parsby.rb', line 736

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

#*(n) ⇒ Object

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



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

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.



719
720
721
722
723
# File 'lib/parsby.rb', line 719

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.



688
689
690
# File 'lib/parsby.rb', line 688

def <(p)
  self.then {|r| p.then { pure r } } % "(#{label} < #{p.label})"
end

#<<(p) ⇒ Object

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



726
727
728
729
730
731
732
733
# File 'lib/parsby.rb', line 726

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.



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

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


748
749
750
751
752
# File 'lib/parsby.rb', line 748

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

#labelObject

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



622
623
624
# File 'lib/parsby.rb', line 622

def label
  @label || Token.new("unknown")
end

#label=(name) ⇒ Object

Assign label to parser. If given a symbol, it’ll be turned into a Parsby::Token.



628
629
630
# File 'lib/parsby.rb', line 628

def label=(name)
  @label = name.is_a?(Symbol) ? Token.new(name) : name
end

#parse(src) ⇒ Object

Parse a String or IO object.



641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
# File 'lib/parsby.rb', line 641

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.



666
667
668
669
670
671
672
673
674
# File 'lib/parsby.rb', line 666

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


789
790
791
792
793
794
795
796
797
798
799
800
801
# File 'lib/parsby.rb', line 789

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


771
772
773
774
775
# File 'lib/parsby.rb', line 771

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.



677
678
679
680
681
682
683
684
685
# File 'lib/parsby.rb', line 677

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

#~Object



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

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