peasy
Version:
an easy but powerful parser
292 lines (248 loc) • 8.82 kB
text/coffeescript
### an extended parser with logic features ###
peasy = require "./peasy"
exports = module.exports = {}
if typeof window=='object' then peasy.extend exports, peasy
exports.Parser = class Parser extends peasy.BaseParser
constructor: ->
super
self = @
@parse = (data, root=self.root, cur=0) ->
self.data = data
self.cur = cur
self.trail = new Trail
self.ruleStack = {}
self.cache = {}
root()
@bind = (vari, term) ->
vari.bind(self.trail.deref(term))
true
@unify = (x, y, equal=((x, y) -> x==y)) ->
self.trail.unify(x, y, equal)
@unifyList = (xs, ys, equal=((x, y) -> x==y)) ->
xlen = xs.length
if ys.length isnt xlen then return false
else
_unify = self.trail.unify
for i in [0...xlen]
if not _unify(xs[i], ys[i], equal) then return false
true
# combinator *orp* <br/>
@orp = (items...) ->
items = for item in items then (if (typeof item)=='string' then self.literal(item) else item)
->
start = self.cur
length = items.length
for i in [0...length]
self.cur = start
self.trail = new Trail
if result = items[i]() then return result
if i!= length-1 then self.trail.undo()
result
# matcher *char*: match one character<br/>
@unifyChar = (x) -> ->
x = self.trail.deref(x)
if x instanceof Var then c = self.data[self.cur++]; x.bind(c); c
else if self.data[self.cur]==x then self.cur++; x
@unifyDigit = (x) -> ->
c = self.data[self.cur]
if '0'<=c<='9'
x = self.trail.deref(x)
if x instanceof Var then self.cur++; x.bind(c); c
else if x==c then self.cur++; c
@unifyLetter = (x) -> ->
c = self.data[self.cur]
if 'a'<=x<='z' or 'A'<=x<='Z'
x = self.trail.deref(x)
if x instanceof Va then x.bind(c); self.cur++; c
else if x==c then self.cur++; c
@unifyLower = (x) -> ->
c = self.data[self.cur];
if 'a'<=x<='z'
x = self.trail.deref(x)
if x instanceof Var then x.bind(c); self.cur++; c
else if x==c then self.cur++; c
@unifyUpper = (x) -> ->
c = self.data[self.cur]
if 'A'<=x<='Z'
x = self.trail.deref(x)
if x instanceof Var then x.bind(c); self.cur++; c
else if x==c then self.cur++; c
@unifyIdentifier = (x) -> ->
if n = self.identifier() and self.unify(x, n) then n
exports.Error = class Error
constructor: (@exp, @message='', @stack = @) -> # @stack: to make webstorm nodeunit happy.
toString: () -> "#{@constructor.name}: #{@exp} >>> #{@message}"
exports.BindingError = class BindingError extends Error
# record the trail for variable binding <br/>
# when multiple choices exist, a new Trail for current branch is constructored, <br/>
# when backtracking, undo the trail to restore the previous variable binding
exports.Trail = class Trail
constructor: (@data={}) ->
copy: () -> new Trail(peasy.extend({},@data))
set: (vari, value) ->
data = @data
if not data.hasOwnProperty(vari.name)
data[vari.name] = [vari, value]
undo: () -> for nam, pair of @data then pair[0].binding = pair[1]
deref: (x) -> x?.deref?(@) or x
getvalue: (x, memo={}) ->
getvalue = x?.getvalue
if getvalue then getvalue.call(x, @, memo)
else x
unify: (x, y, equal) ->
x = @deref(x); y = @deref(y)
if x instanceof Var then @set(x, x.binding); x.binding = y; true;
else if y instanceof Var then @set(y, y.binding); y.binding = x; true;
else x?.unify?(y, @) or y?.unify?(x, @) or equal(x, y)
# ####class Var
# Var for logic bindings, used in unify, lisp.assign, inc/dec, parser operation, etc.
exports.Var = class Var
constructor: (@name='', @binding = @) ->
deref: (trail) ->
v = @
next = @binding
if next is @ or next not instanceof Var then next
else
chains = [v]
length = 1
while 1
chains.push(next)
v = next; next = v.binding
length++
if next is v
for i in [0...chains.length-2]
x = chains[i]
x.binding = next
trail.set(x, chains[i+1])
return next
else if next not instanceof Var
for i in [0...chains.length-1]
x = chains[i]
x.binding = next
trail.set(x, chains[i+1])
return next
bind: (value, trail) ->
trail.set(@, @binding)
@binding = trail.deref(value)
getvalue: (trail, memo={}) ->
name = @name
if memo.hasOwnProperty(name) then return memo[name]
result = @deref(trail)
if result instanceof Var
memo[name] = result
result
else
result = trail.getvalue(result, memo)
memo[name] = result
result
toString:() -> "vari(#{@name})"
reElements = /\s*,\s*|\s+/
# utilities for new variables
# sometiems, say in macro, we need unique var to avoid name conflict
nameToIndexMap = {}
exports.vari = (name='') ->
index = nameToIndexMap[name] or 1
nameToIndexMap[name] = index+1
new Var(name+index)
exports.vars = (names) -> vari(name) for name in split names, reElements
# DummyVar never fail when it unify. see tests on any/some/times in test_parser for examples
exports.DummyVar = class DummyVar extends Var
constructor: (name) -> @name = '_$'+name
deref: (trail) -> @
getvalue: (trail, memo={}) ->
name = @name
if memo.hasOwnProperty(name) then return memo[name]
result = @binding
if result is @
memo[name] = result
result
else
result = trail.getvalue(result, memo)
memo[name] = result
result
# nottodo: variable's applyCont:: canceled. lisp1 should be good.
exports.dummy = dummy = (name) ->
index = nameToIndexMap[name] or 1
nameToIndexMap[name] = index+1
new exports.DummyVar(name+index)
exports.dummies = (names) -> new dummy(name) for name in split names, reElements
exports.UObject = class UObject
constructor: (@data) ->
getvalue: (trail, memo) ->
result = {}
changed = false
for key, value of @data
v = trail.getvalue(value, memo)
if v isnt value then changed = true
result[key] = v
if changed then new UObject(result)
else @
unify: (y, trail, equal=(x, y) -> x==y) ->
xdata = @data
ydata = if y instanceof UObject then y.data else y
ykeys = Object.keys(y)
for key of xdata
index = ykeys.indexOf(key)
if index==-1 then return false
if not trail.unify(xdata[key], ydata[key], equal) then return false
ykeys.splice(index, 1);
if ykeys.length isnt 0 then return false
true
# make unifable object
exports.uobject = (x) -> new UObject(x)
exports.UArray = class UArray
constructor: (@data) ->
getvalue: (trail, memo={}) ->
result = []
changed = false
for x in @data
v = trail.getvalue(x, memo)
if v isnt x then changed = true
result.push(v)
if changed then new UArray(result)
else @
unify: (y, trail, equal=(x, y) -> x==y) ->
xdata = @data; ydata = y.data or y
length = @data.length
if length!=y.length then return false
for i in [0...length]
if not trail.unify(xdata[i], ydata[i], equal) then return false
true
toString: () -> @data.toString()
# make unifable array
exports.uarray = uarray = (x) -> new UArray(x)
exports.Cons = class Cons
constructor: (@head, @tail) ->
getvalue: (trail, memo={}) ->
head = @head; tail = @tail
head1 = trail.getvalue(head, memo)
tail1 = trail.getvalue(tail, memo)
if head1 is head and tail1 is tail then @
else new Cons(head1, tail1)
unify: (y, trail, equal=(x, y) -> x==y) ->
if y not instanceof Cons then false
else if not trail.unify(@head, y.head, equal) then false
else trail.unify(@tail, y.tail, equal)
flatString: () ->
result = "#{@head}"
tail = @tail
if tail is null then result += ''
else if tail instanceof Cons
result += ','
result += tail.flatString()
else result += tail.toString()
result
toString: () -> "cons(#{@head}, #{@tail})"
# cons, like pair in lisp
exports.cons = (x, y) -> new Cons(x, y)
# conslist, like list in lisp
exports.conslist = (args...) ->
result = null
for i in [args.length-1..0] by -1
result = new Cons([args[i], result])
result
# make unifiable array or unifiable object
exports.unifiable = (x) ->
if peasy.isArray(x) then new UArray(x)
else if peasy.isObject(x) then new UObject(x)
else x