UNPKG

peasy

Version:

an easy but powerful parser

292 lines (248 loc) 8.82 kB
### 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