bloodyroots
Version:
Recursive descent parser
232 lines (208 loc) • 10 kB
text/coffeescript
re_quote = require('regexp-quote')
inspect_orig = require('util').inspect
inspect = (x) -> inspect_orig(x, false, null)
require('sprintf.js')
typeIsArray = Array.isArray || ( value ) -> return {}.toString.call( value ) is '[object Array]'
class Parser
: (alpha_s, beta) ->
[alpha_s] = (vdata, idx) ->
beta.op.call this, vdata, idx
: (name, op_f) ->
this[name] = (args...) ->
{ name: name, op: if op_f? then op_f.apply this, args else this['match_' + name].apply this, args }
'at_least_one', (beta, suffix) ->
'alternation'
'range'
're', (re_str, match_name) ->
'seq'
'transform', (f, beta) ->
'v'
'var_re'
'zero_or_more', (beta, suffix) ->
'zero_or_one', (beta, suffix) ->
: (ref) -> (vdata) ->
m = /^([^\[]*)\[([0-9]*)\]/.exec(ref)
[ (vdata[m[1]] || [ ])[m[2]], ]
debug_log: (f) ->
if .debug
[ name, idx, outcome, data ] = f.call(this)
'%-15s %3s %-25s %-8s %s\n'.printf name, idx, , outcome || '', data || ''
: (args...) ->
if typeIsArray args[0]
[ beta_seq, suffix ] = args
else
beta_seq = args
(vdata, idx) ->
-> [ 'alternation', idx, 'begin', 'alternation=%s%s'.sprintf (beta.name for beta in beta_seq), (if suffix? then ' suffix='+suffix.name else ' no-suffix') ]
i = 0
for beta in beta_seq
-> [ 'alternation', idx, 'i='+i, beta.name ]
m = beta.op.call this, vdata, idx
if m?
if suffix?
m2 = suffix.op.call this, vdata, idx + m[0]
if m2?
-> [ 'alternation', idx + m[0] + m2[0], 'success', 'count='+(i+1) ]
return [ m[0] + m2[0], { pos: idx, length: m[0] + m2[0], type: 'seq', seq: [ m[1], m2[1] ] } ]
else
-> [ 'alternation', idx + m[0], 'success', 'count=%d'.sprintf (i+1) ]
return m
i++
-> [ 'alternation', idx, 'fail' ]
return
: (beta, min=0, max, greedy=true, suffix) ->
if greedy
else
: (beta, min, max, suffix) -> (vdata, idx) ->
return unless state =
match_indices = [ state.progress ]
beta, max, vdata, idx, state, =>
match_indices.push(state.progress)
false
while match_indices.length
state.progress = match_indices.pop()
-> [ 'range', idx + state.progress, 'i='+state.count, 'greedy backtracking' ]
return result if result =
state.work.pop()
state.count--
-> [ 'range', idx + state.progress, 'fail', 'greedy backtracking' ]
return
: (beta, min, max, suffix) -> (vdata, idx) ->
return unless state =
or or (; undefined)
_match_range_to_min: (beta, min, max, greedy, vdata, idx) ->
-> [ 'range', idx, 'begin',
'%s min=%s max=%s %s %s'.sprintf(beta.name, min, (if max? then max else ''),
(if greedy then 'greedy' else 'non-greedy'),
(if suffix? then 'suffix='+suffix.name else 'no-suffix')) ]
if max? and min > max
-> [ 're', idx, 'fail', 'min > max' ]
return
state = { count: 0, progress: 0, work: [], greedy: greedy }
while state.count < min
-> [ 'range', idx + state.progress, 'i='+state.count, '<min' ]
m = beta.op.call this, vdata, idx + state.progress
unless m?
-> [ 'range', idx + state.progress, 'fail', '<min matches' ]
return
state.progress += m[0]
state.work.push m[1]
state.count++
state
_match_range_from_min: (beta, max, vdata, idx, state, func) ->
while not max? or state.count < max
-> [ 'range', idx + state.progress, 'i='+state.count,
'>=min %s'.sprintf(if state.greedy then 'greedy' else 'non-greedy') ]
m = beta.op.call this, vdata, idx + state.progress
break unless m?
state.progress += m[0]
state.work.push m[1]
state.count++
if output = func()
return output
return
_match_range_suffix: (suffix, vdata, idx, state) ->
if suffix?
if (m = suffix.op.call this, vdata, idx + state.progress)?
state.progress += m[0]
state.work.push m[1]
-> [ 'range', idx + state.progress, 'success',
'count=%d %s'.sprintf(state.count, (if state.greedy then 'greedy' else 'non-greedy')) ]
return [ state.progress, { pos: idx, length: state.progress, type: 'seq', seq: state.work } ]
else
return
else
-> [ 'range', idx + state.progress, 'success',
'count=%d %s'.sprintf(state.count, (if state.greedy then 'greedy' else 'non-greedy trivial')) ]
return [ state.progress, { pos: idx, length: state.progress, type: 'seq', seq: state.work } ]
: (rre, match_name) -> (vdata, idx) ->
m = rre.exec .substr idx
if m
-> [ 're', idx, 'success', inspect rre.source ]
vdata[match_name] = m[0..-1] if match_name?
[ m[0].length, { pos: idx, length: m[0].length, type: 're', match: m[0], groups: m[0..-1] } ]
else
-> [ 're', idx, 'fail', inspect rre.source ]
return
: (beta_seq...) ->
(vdata, idx) ->
-> [ 'seq', idx, 'begin', (beta.name for beta in beta_seq) ]
progress = 0
work = [ ]
i = 0
for beta in beta_seq
-> [ 'seq', idx + progress, 'i='+i, beta.name ]
m = beta.op.call this, vdata, idx + progress
unless m?
-> [ 'seq', idx + progress, 'fail' ]
return
progress += m[0]
work.push m[1]
i++
-> [ 'seq', idx + progress, 'success' ]
[ progress, { pos: idx, length: progress, type: 'seq', seq: work } ]
: (alpha_s, argf) -> (vdata, idx) ->
-> [ 'v', idx, 'begin', alpha_s ]
new_vdata = { }
new_vdata.arg = argf.call this, vdata if argf?
m =
-> [ 'v', idx + (if m? then m[0] else 0), (if m? then 'success' else 'fail'), alpha_s ]
m
: (re_str, match_name) ->
self = this
(vdata, idx) ->
self.match_re(RegExp('^(?:' + + ')'), match_name).call this, vdata, idx
: (f, beta) -> (vdata, idx) ->
-> [ 'transform', idx, 'begin', beta.name ]
m = beta.op.call this, vdata, idx
unless m?
-> [ 'transform', idx, 'fail', beta.name ]
return
tm = f.call this, m[1], vdata, idx
unless tm?
-> [ 'transform', idx + m[0], 'fail', 'transform' ]
return
-> [ 'transform', idx + m[0], 'success' ]
[ m[0], tm ]
parse: (str) ->
= str
= {}
-> [ 'parse', 0, 'begin' ]
doc = { }, 0
unless doc?
-> [ 'parse', 0, 'fail' ]
return
-> [ 'parse', doc[0], 'success' ]
doc[1]
replace_backreferences: (re_str, vdata) ->
work = re_str
while m = (/\\=([^\[]+)\[([0-9]+)\]/.exec(work))
mstr = (vdata[m[1]] || [ ])[m[2]]
mstr ?= ''
work = work.substr(0, m.index) + re_quote(mstr) + work.substr(m.index + m[0].length)
work
string_abbrev: (start, n) ->
istr = .substr(start)
istr = inspect istr
if istr.length > n
istr.substr(0, n - 3) + '...'
else
istr
strip_quotes: (str) ->
m = /^'(.*)'$/.exec(str)
if m
m[1]
else
str
vcache: (alpha_s, idx, vdata) ->
cache_key = [ alpha_s, idx, JSON.stringify(vdata) ].join('#')
if .hasOwnProperty(cache_key)
-> [ 'vcache', idx, 'cached' ]
return [cache_key]
else
[cache_key] = this[alpha_s] vdata, idx
exports.Parser = Parser