[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

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
when String
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