UNPKG

peasy

Version:

an easy but powerful parser

236 lines (206 loc) 8.77 kB
### 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 = @lineparser = {} @parse = (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 = @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 = @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 = @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 = @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 = @notp = (item) -> if (typeof item)=='string' then item = self.literal(item) -> not item() lineparser.may = @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 = @any # combinator *some*: one or more times of `item()` lineparser.some = @some # combinator *times*: match *self.n* times item(), n>=1 lineparser.times = @times # combinator *list*: some times item(), separated by self.separator lineparser.list = @list # = (item, separator=self.spaces) -> # combinator *listn*: given self.n times self.item separated by self.separator, n>=1 lineparser.listn = @listn # = (item, n, separator=self.spaces) -> # combinator *follow* <br/> lineparser.follow = @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 = @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 = @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 = @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 = @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 = @eoi = -> self.cur>=self.data.length # matcher *identifierLetter* = normal version<br/> lineparser.identifierLetter = @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 = @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 = @digit = -> c = self.data[self.cur]; if '0'<=c<='9' then self.cur++; self.row++; c lineparser.letter = @letter = -> c = self.data[self.cur]; if 'a'<=c<='z' or 'A'<=c<='Z' then self.cur++; self.row++; c lineparser.lower = @lower = -> c = self.data[self.cur]; if 'a'<=c<='z' then self.cur++; self.row++; c lineparser.upper = @upper = -> c = self.data[self.cur]; if 'A'<=c<='Z' then self.cur++; self.row++; c lineparser.identifier = @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 = @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 = @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 = @select # = (item, actions) -> # generate a matcher, which select one action from actions based on item() lineparser.selectp = @selectp # = (item, actions) ->