[ann] rsec - parsec for ruby1.9

Hi dear everyone,

I just made a small parser combinator library for ruby1.9 (sadly it’s
1.9 only).
It’s simple(buggy) and fast(ha, it beats treetop on arithmetics!).
Please have a look if you are interested in.

install:
gem install rsec -s http://gemcutter.org

source code (about 500 LOCs):

the terrible document:
http://wiki.github.com/luikore/rsec/

Yours, sincerely, and many best regards.

Helloworld: a simple arithmetic calculator
#---------------------------------------------------------

require ‘rubygems’
require ‘rsec’
include Rsec::Helpers

def arithmetic
calculate = proc do |(p, *ps)|
ps.each_slice(2).inject(p) do |left, (op, right)|
left.send op.strip, right
end
end

num = /[±]?[1-9]\d*(.\d+)?/.r.map &:to_f
bra = ‘(’.r.skip
ket = ‘)’.r.skip
paren = (bra << lazy{@expr} << ket).map &:first
factor = paren | num
term = factor.join(/\s*[*/]\s*/).map &calculate
@expr = term.join(/\s*[+-]\s*/).map &calculate
end

puts arithmetic.parse ‘1 + 2.3 / 4 - 5 + 6 * 7.8 / (9 +10)’

Just added another example: a Scheme interpreter.

require “rsec”

class Scheme
include Rsec::Helpers

def initialize
bra = /(\s*/.r.skip
ket = /\s*)/.r.skip
boolean = /#[tf]/. r.map {|n| ValueNode[n==’#t’] }
integer = /0|[1-9]\d*/.r.map {|n| ValueNode[n.to_i] }
id = /[^\s()[]]+/.r.on {|n|
def n.eval bind, *xs
bind[self]
end
}
atom = boolean | integer | id
list = nil # declare for lazy
cell = atom | lazy{list}
list = ( bra < cell.join_(spacee) < ket ).map {|(n)|
ListNode[*n] }
@parser = (spacee < cell.join_(spacee) < spacee).map {|(n)|
ListNode[*n] }
end

def run source
@ctx = Rsec::ParseContext.new source, ‘scm’
res = @parser._parse @ctx
if !res or !@ctx.eos?
raise Rsec::ParseError[‘syntax error’, @ctx]
end
res.eval Runtime.new
end

ValueNode = Struct.new :val
class ValueNode
def eval *xs
val
end
def pretty_print q
q.text “<#{val}>”
end
end

class ListNode < Array
def eval bind
head, *tail = self
case head
when String
pr = bind[head]
pr.is_a?(Proc) ? pr[bind, tail] : pr
when ListNode
map{|n| n.eval bind }.last
end
end
end

class Bind < Hash
def initialize parent = {}
@parent = parent
end

def [] key
  super(key) || @parent[key]
end

# define a function
def define id, &p
  self[id] = proc do |bind, xs|
    p[* xs.map{|x| x.eval bind }]
  end
end

end

class Runtime < Bind
def initialize
super()

  self['define'] = proc do |bind, (param, body)|
    case param
    # (define (name plist[0]) body)
    when ListNode
      func, *xs = param
      self[func] = self['lambda'][bind, [xs, body]]
    # (define param body)
    when String
      self[param] = body.eval bind
    end
  end

  # declare:
  #   (lambda (xs[0] xs[1]) body)
  self['lambda'] = proc do |bind_def, (xs, body)|
    xs = [xs] if xs.is_a?(String)
    new_bind = Bind.new bind_def
    # calling:
    #   (some vs[0] vs[1])
    proc do |bind_call, vs|
      vs = vs.map{|v| v.eval bind_call}
      new_bind.merge! Hash[xs.zip vs]
      body.eval new_bind
    end
  end

  # lazy (short cut)
  self['if'] = proc do |bind, (p, left, right)|
    p.eval(bind) ? left.eval(bind) : right.eval(bind)
  end

  # misc
  %w|+ - * / ** % > <|.each do |s|
    define s, &s.to_sym
  end
  define '=', &:==
  define 'display' do |x|
    puts x
  end
end

end
end

s = Scheme.new
s.run File.read ARGV[0]