peasy
Version:
an easy but powerful parser
236 lines (206 loc) • 8.77 kB
text/coffeescript
### an extended parser with lineno and row support ###
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 = @
lineparser = = {}
= (data, root=self.root, cur=0, lineno=0, row=0) ->
self.data = data
self.cur = cur; self.lineno = lineno; self.row = row
self.ruleStack = {}
self.cache = {}
root()
# make rule left recursive
lineparser.rec = = (rule) ->
tag = self.ruleIndex++
->
ruleStack = self.ruleStack
cache = self.cache[tag] ?= {}
start = self.cur; lineno = self.lineno; row = self.row
callStack = ruleStack[start] ?= []
if tag not in callStack
callStack.push(tag)
m = cache[start] ?= {result:undefined, cur:start, lineno: lineno, row:row}
while 1
self.cur = start; self.lineno = lineno; self.row = row
result = rule()
if not result then result = m.result; self.cur = m.cur; self.lineno = m.lineno; self.row = m.row; break
else if m.cur==self.cur then m.result = result; break
else m.result = result; m.cur = self.cur; m.lineno = self.lineno; m.row = self.row
callStack.pop()
result
else
m = cache[start]
self.cur = m.cur; self.lineno = m.lineno; self.row = m.row
m.result
lineparser.memo = = (rule) ->
tag = self.ruleIndex++
=>
cache = self.cache[tag] ?= {}
start = self.cur
m = cache[start]
if m then self.cur = m.cur; self.lineno = lineno; self.row = row; m.result
else
result = rule()
self.cache[tag][start] = {result: result, cur: self.cur, lineno: self.lineno, row: self.row}
result
# combinator *orp* <br/>
lineparser.orp = = (items...) ->
items = for item in items
if (typeof item)=='string' then self.literal(item) else item
=>
start = self.cur; lineno = self.lineno; row = self.row
for item in items
self.cur = start; self.lineno = lineno; self.row = row
if result = item() then return result
# #### matchers and combinators<br/>
lineparser.andp = = (items...) ->
items = for item in items
if (typeof item)=='string' then self.literal(item) else item
->
for item in items
if not (result = item()) then return
result
lineparser.notp = = (item) ->
if (typeof item)=='string' then item = self.literal(item)
-> not item()
lineparser.may = = (item) ->
if (typeof item)=='string' then item = self.literal(item)
=>
start = self.cur; lineno = self.lineno; row = self.row
if x = item() then x
else self.cur = start; self.lineno = lineno; self.row = row; true
# combinator *any*: zero or more times of `item()`
lineparser.any =
# combinator *some*: one or more times of `item()`
lineparser.some =
# combinator *times*: match *self.n* times item(), n>=1
lineparser.times =
# combinator *list*: some times item(), separated by self.separator
lineparser.list = # = (item, separator=self.spaces) ->
# combinator *listn*: given self.n times self.item separated by self.separator, n>=1
lineparser.listn = # = (item, n, separator=self.spaces) ->
# combinator *follow* <br/>
lineparser.follow = = (item) ->
if (typeof item)=='string' then item = self.literal(item)
=>
start = self.cur; lineno = self.lineno; row = self.row
x = item(); self.cur = start; self.lineno = lineno; self.row = row; x
# matcher *literal*<br/>
# match a text string.<br/>
# `notice = some combinators like andp, orp, notp, any, some, etc. use literal to wrap a object which is not a matcher.
lineparser.literal = = (string) ->
len = string.length
->
cur = self.cur; lineno = self.lineno; row = self.row; data = self.data
i = 0
while i<len
c = string[i]
if data[i]==c
i++; cur++
if c=='\n' then lineno++; row = 0
else row++
else return
self.cur = cur; self.lineno = lineno; self.row = row
true
# matcher *char*: match one character<br/>
lineparser['char'] = @['char'] = (c) -> ->
if self.data[self.cur]==c
self.cur++;
if c=='\n' then self.lineno++; self.row = 0
else self.row++
c
# matcher *wrap*<br/>
# match left, then match item, match right at last
lineparser.wrap = = (item, left=self.spaces, right=self.spaces) ->
if (typeof item)=='string' then item = self.literal(item)
-> if left() and result = item() and right() then result
# matcher *spaces*: zero or more whitespaces, ie. space or tab.<br/>
lineparser.spaces = = ->
data = self.data
len = 0
cur = self.cur
while 1
if ((c=data[cur++]) and (c==' ' or c=='\t')) then len++ else break
self.cur += len; self.row += len
len+1
# matcher *spaces1*<br/>
# one or more whitespaces, ie. space or tab.<br/>
lineparser.spaces1 = = ->
data = self.data
cur = self.cur
len = 0
while 1
if ((c=data[cur++]) and (c==' ' or c=='\t')) then lent++ else break
self.cur += len; self.row += len
len
lineparser.eoi = = -> self.cur>=self.data.length
# matcher *identifierLetter* = normal version<br/>
lineparser.identifierLetter = = ->
c = self.data[self.cur]
if c is '$' or c is '_' or 'a'<=c<'z' or 'A'<=c<='Z' or '0'<=c<='9'
self.cur++; self.row++; true
lineparser.followIdentifierLetter = = ->
c = self.data[self.cur]
(c is '$' or c is '_' or 'a'<=c<'z' or 'A'<=c<='Z' or '0'<=c<='9') and c
lineparser.digit = = -> c = self.data[self.cur]; if '0'<=c<='9' then self.cur++; self.row++; c
lineparser.letter = = -> c = self.data[self.cur]; if 'a'<=c<='z' or 'A'<=c<='Z' then self.cur++; self.row++; c
lineparser.lower = = -> c = self.data[self.cur]; if 'a'<=c<='z' then self.cur++; self.row++; c
lineparser.upper = = -> c = self.data[self.cur]; if 'A'<=c<='Z' then self.cur++; self.row++; c
lineparser.identifier = = ->
data = self.data
start = cur = self.cur
c = data[cur]
if 'a'<=c<='z' or 'A'<=c<='Z' or c=='$' or c=='_' then cur++
else return
while 1
c = data[cur]
if 'a'<=c<='z' or 'A'<=c<='Z' or '0'<=c<='9' or c=='$' or c=='_' then cur++
else break
self.row += cur-self.cur
self.cur = cur
data[start...cur]
lineparser.number = = ->
data = self.data
cur = self.cur
c = data[cur]
if '0'<=c<='9' then cur++
else return
while 1
c = data[cur]
if '0'<=c<='9' then cur++
else break
self.row += cur-self.cur
self.cur = cur
data[start...cur]
# 'string' or "string", single line string
lineparser.string = = ->
text = self.data
start = cur = self.cur; row = self.row
# lineno = self.lineno; # lineno should not be changed in single line string
c = text[cur]
if c=='"'then quote = c; wrap = '"'; row++
else if c=="'" then quote = c; wrap="'"; row++
else return
cur++
while 1
c = text[cur]
if c=='\n' or c=='\r' then self.error('new line is forbidden in single line string.')
else if c=='\\'
c1 = text[cur+1]
if c1=='\n' or c1=='\r' then self.error('new line is forbidden in single line string.')
else if not c1 then self.error('unexpect end of input, string is not closed')
else cur += 2; row+=2
else if c==quote
self.cur = cur+1
self.row = row+1
return eval(wrap+text[start..cur]+wrap)
else if not c then self.error('new line is forbidden in string.')
else cur++; row++;
# select one action from actions based on item
lineparser.select = # = (item, actions) ->
# generate a matcher, which select one action from actions based on item()
lineparser.selectp = # = (item, actions) ->