yaap
Version:
164 lines (156 loc) • 6.13 kB
JavaScript
/* Utility functions on PanPG parse trees.
* PanPG version 0.0.10
* built on Thu, 18 Aug 2011 18:51:30 GMT
* http://boshi.inimino.org/3box/PanPG/about.html
* MIT Licensed
*/
"use strict";
(function(define) {
define([],function() {
// (event array (can be partial), [name array], [input string], [state]) → [ascii-art tree, state]
// -or-
// (complete event array, [name array], [input string]) → ascii-art
// if the event array doesn't describe a complete, finished tree, or if the state value argument is provided, then the ascii-art and the state value will be returned as an array
// this is for examining partial tree fragments as they are generated by a streaming parser
function showTree(res,opts,state){var names,str,a,i,l,indent,name,x,y,out=[],output_positions=[],node,out_pos,state_was_passed
if(!res[0])return showError(res)
res=res[1]
names=res.names
a=res.tree
str=res.input
opts=opts||{}
opts.elide=opts.elide||['anonymous']
opts.drop=opts.drop||[]
state_was_passed=!!state
state=state||{stack:[],indent:'',pos:0,drop_depth:0}
for(i=0,l=a.length;i<l;i++){x=a[i]
if(x>0){
if(names){
name=names[x]
if(!name) return err('no such rule index in name array: '+x)}
else name=''+x
output_positions[state.stack.length]=out.length
node={index:x,name:name,start:state.pos}
if(opts.drop.indexOf(name)>-1)state.drop_depth++
out.push(show(state,node))
state.indent+=' '
state.stack.push(node)}
else if(x==-1){
i++
if(i==l){i--;return}
node={name:'anonymous',start:state.pos,end:state.pos+a[i]}
state.pos=node.end
out.push(show(state,node))
}
else if(x==-2){
i++
if(i==l)return err('incomplete close event, expected length at position '+i+' but found end of input array')
y=state.stack.pop()
state.pos=y.end=y.start+a[i]
out_pos=output_positions[state.stack.length]
state.indent=state.indent.slice(0,-1)
if(out_pos!=undefined){
out[out_pos]=show(state,y)}
if(opts.drop.indexOf(y.name)>-1)state.drop_depth--}
else return err('invalid event '+x+' at position '+i)}
if(state_was_passed || state.stack.length) return [out.join(''),state]
else return out.join('')
function err(s){return ['showTree: '+s]}
function show(state,node){var text='',main,indent,l
if(opts.elide.indexOf(node.name)>-1)return ''
if(state.drop_depth)return ''
if(node.end!=undefined && str){
text=show_str(str.slice(node.start,node.end))}
main=state.indent+node.name+' '+node.start+'-'+(node.end==undefined?'?':node.end)
l=main.length
indent=Array(32*Math.ceil((l+2)/32)-l).join(' ')
return main+indent+text+'\n'}
function show_str(s){
return '»'+s.replace(/\n/g,'\\n').replace(/\r/g,'\\r').replace(/(.{16}).{8,}/,"$1…")+'«'}}
// inspired by: http://gist.github.com/312863
function showError(res){var line_number,col,lines,line,start,end,prefix,suffix,arrow,pos,msg,str
pos=res[1];msg=res[2];str=res[3]
msg=msg||'Parse error'
if(str==undefined)return msg+' at position '+pos
prefix=str.slice(0,pos)
suffix=str.slice(pos)
line_number=prefix.split('\n').length
start=prefix.lastIndexOf('\n')+1
end=suffix.indexOf('\n')
if(end==-1) end=str.length
else end=prefix.length+end
line=str.slice(start,end)
line=line.replace(/\t/g,' ')
col=pos-start
arrow=Array(col).join('-')+'^'
return msg+' at line '+line_number+' column '+col+'\n'+line+'\n'+arrow}
function showResult(r,opts){
if(r[0])return showTree(r,opts)
return showError(r)}
function treeWalker(dict,result){var p,any,anon,other,fail,except,index,cb=[],stack=[],frame,pos=0,i,l,x,retval,names,events,begin=[],match,target,msg
fail=dict.fail
except=dict.exception
if(!result[0]){
msg='parse failed: '+result[1]+' '+(result[2]||'')
if(fail)return fail(result)||msg
return err(msg)}
result=result[1]
names=result.names
events=result.tree
for(p in dict) if(dict.hasOwnProperty(p)){
if(p=='any'){any=dict[p];throw new Error('unimplemented, use `other` instead')}
if(p=='anonymous'||p=='anon'){anon=dict[p];continue}
if(p=='other'){other=dict[p];continue}
if(p=='fail'){fail=dict[p];continue}
if(p=='exception'){except=dict[p];continue}
if(p=='warn'){continue}
target=cb
if(match=/(.*) start/.exec(p)){p=m[1];target=begin}
index=names.indexOf(p)
if(index==-1)return err('rule not found in rule names: '+p)
target[index]=dict[p]}
frame={cn:[]}
for(i=0,l=events.length;i<l;i++){x=events[i]
if(x>0){ // named rule start
stack.push(frame)
frame={index:x,start:pos}
if(begin[x]){
try{retval=begin[x](pos)}
// here we call err() but continue iff `except` returns true
catch(e){if(!err('exception in '+names[x]+' start:'+e))return}}
if(cb[x]||any||other) frame.cn=[]}
else if(x==-1){ // anonymous node
i++
if(i==l)return err('incomplete anonymous node')
if(anon)anon(m(pos,pos+events[i]))
pos+=events[i]}
else if(x==-2){ // node close
i++
if(i==l)return err('incomplete rule close')
pos=frame.start+events[i]
x=frame.index
match=m(frame.start,pos)
try{
if(cb[x]) retval=cb[x](match,frame.cn)
else if(other)retval=cb[x](match,frame.cn,names[x])}
catch(e){return err('exception in '+names[x]+': '+e+' (on node at char '+match.start+'-'+match.end+')')}
frame=stack.pop() // the parent node
if(cb[x] && retval!==undefined)
if(frame.cn)frame.cn.push(retval)
else warn('ignored return value of '+names[x]+' in '+names[frame.index])}
else return err('invalid event stream (saw '+x+' at position '+i+')')}
if(frame.cn)return frame.cn[0]
function m(s,e){
return {start:s
,end:e
,text:function(){return result.input.slice(s,e)}}}
function err(s){
if(except)return except(s)
throw new Error('treeWalker: '+s)}
function warn(s){
if(dict.warn)dict.warn(s)}}
return {
showTree:showTree,
treeWalker:treeWalker
}
});})(typeof define == 'function'? define: function(deps, factory) {module.exports = factory.apply(this, deps.map(function(x) {return require(x);}));});