apg-lite
Version:
lightweight JavaScript APG parser
1 lines • 16.9 kB
JavaScript
const Parser=function(){const e=identifiers,t=utilities,s=this,n="parser.js: Parser(): ";s.ast=void 0,s.stats=void 0,s.trace=void 0,s.callbacks=[];let a,r,i,o,h,c,l,d=0,T=0,m=0,p=0,g=0,u=new function(){this.state=e.ACTIVE,this.phraseLength=0,this.refresh=()=>{this.state=e.ACTIVE,this.phraseLength=0}};s.parse=(f,A,L,M)=>{const w=`${n}parse(): `;d=0,T=0,m=0,p=0,g=0,a=void 0,r=void 0,i=void 0,o=void 0,u.refresh(),h=void 0,c=void 0,l=void 0,o=t.stringToChars(L),a=f.rules,r=f.udts;const $=A.toLowerCase();let N;for(const e in a)if(a.hasOwnProperty(e)&&$===a[e].lower){N=a[e].index;break}if(void 0===N)throw new Error(`${w}start rule name '${startRule}' not recognized`);(()=>{const e=`${n}initializeCallbacks(): `;let t,i;for(h=[],c=[],t=0;t<a.length;t+=1)h[t]=void 0;for(t=0;t<r.length;t+=1)c[t]=void 0;const o=[];for(t=0;t<a.length;t+=1)o.push(a[t].lower);for(t=0;t<r.length;t+=1)o.push(r[t].lower);for(const n in s.callbacks)if(s.callbacks.hasOwnProperty(n)){if(t=o.indexOf(n.toLowerCase()),t<0)throw new Error(`${e}syntax callback '${n}' not a rule or udt name`);if(i=s.callbacks[n]?s.callbacks[n]:void 0,"function"!=typeof i&&void 0!==i)throw new Error(`${e}syntax callback[${n}] must be function reference or falsy)`);t<a.length?h[t]=i:c[t-a.length]=i}})(),s.trace&&s.trace.init(a,r,o),s.stats&&s.stats.init(a,r),s.ast&&s.ast.init(a,r,o),l=M,i=[{type:e.RNM,index:N}],E(0,0),i=void 0;let C=!1;switch(u.state){case e.ACTIVE:throw new Error(`${w}final state should never be 'ACTIVE'`);case e.NOMATCH:C=!1;break;case e.EMPTY:case e.MATCH:C=u.phraseLength===o.length;break;default:throw new Error("unrecognized state")}return{success:C,state:u.state,stateName:e.idName(u.state),length:o.length,matched:u.phraseLength,maxMatched:g,maxTreeDepth:m,nodeHits:p}};const f=(t,s,a,r)=>{if(s.phraseLength>a){let e=`${n}opRNM(${t.name}): callback function error: `;throw e+=`sysData.phraseLength: ${s.phraseLength}`,e+=` must be <= remaining chars: ${a}`,new Error(e)}switch(s.state){case e.ACTIVE:if(!r)throw new Error(`${n}opRNM(${t.name}): callback function return error. ACTIVE state not allowed.`);break;case e.EMPTY:s.phraseLength=0;break;case e.MATCH:0===s.phraseLength&&(s.state=e.EMPTY);break;case e.NOMATCH:s.phraseLength=0;break;default:throw new Error(`${n}opRNM(${t.name}): callback function return error. Unrecognized return state: ${s.state}`)}},A=(t,h)=>{let T,m,p;const g=i[t],f=r[g.index];u.UdtIndex=f.index,d||(p=s.ast&&s.ast.udtDefined(g.index),p&&(m=a.length+g.index,T=s.ast.getLength(),s.ast.down(m,f.name)));const A=o.length-h;c[g.index](u,o,h,l),((t,s,a)=>{if(s.phraseLength>a){let e=`${n}opUDT(${t.name}): callback function error: `;throw e+=`sysData.phraseLength: ${s.phraseLength}`,e+=` must be <= remaining chars: ${a}`,new Error(e)}switch(s.state){case e.ACTIVE:throw new Error(`${n}opUDT(${t.name}) ACTIVE state return not allowed.`);case e.EMPTY:if(!t.empty)throw new Error(`${n}opUDT(${t.name}) may not return EMPTY.`);s.phraseLength=0;break;case e.MATCH:if(0===s.phraseLength){if(!t.empty)throw new Error(`${n}opUDT(${t.name}) may not return EMPTY.`);s.state=e.EMPTY}break;case e.NOMATCH:s.phraseLength=0;break;default:throw new Error(`${n}opUDT(${t.name}): callback function return error. Unrecognized return state: ${s.state}`)}})(f,u,A),d||p&&(u.state===e.NOMATCH?s.ast.setLength(T):s.ast.up(m,f.name,h,u.phraseLength))},E=(t,r)=>{const c=`${n}opExecute(): `,L=i[t];switch(p+=1,T>m&&(m=T),T+=1,u.refresh(),s.trace&&s.trace.down(L,r),L.type){case e.ALT:((t,s)=>{const n=i[t];for(let t=0;t<n.children.length&&(E(n.children[t],s),u.state===e.NOMATCH);t+=1);})(t,r);break;case e.CAT:((t,n)=>{let a,r,o,h;const c=i[t];s.ast&&(r=s.ast.getLength()),a=!0,o=n,h=0;for(let t=0;t<c.children.length;t+=1){if(E(c.children[t],o),u.state===e.NOMATCH){a=!1;break}o+=u.phraseLength,h+=u.phraseLength}a?(u.state=0===h?e.EMPTY:e.MATCH,u.phraseLength=h):(u.state=e.NOMATCH,u.phraseLength=0,s.ast&&s.ast.setLength(r))})(t,r);break;case e.REP:((t,n)=>{let a,r,h,c;const l=i[t];if(0===l.max)return u.state=e.EMPTY,void(u.phraseLength=0);for(r=n,h=0,c=0,s.ast&&(a=s.ast.getLength());!(r>=o.length)&&(E(t+1,r),u.state!==e.NOMATCH)&&u.state!==e.EMPTY&&(c+=1,h+=u.phraseLength,r+=u.phraseLength,c!==l.max););u.state===e.EMPTY||c>=l.min?(u.state=0===h?e.EMPTY:e.MATCH,u.phraseLength=h):(u.state=e.NOMATCH,u.phraseLength=0,s.ast&&s.ast.setLength(a))})(t,r);break;case e.RNM:((t,n)=>{let r,c,T;const m=i[t],p=a[m.index],g=h[p.index];if(d||(c=s.ast&&s.ast.ruleDefined(m.index),c&&(r=s.ast.getLength(),s.ast.down(m.index,a[m.index].name))),g){const t=o.length-n;g(u,o,n,l),f(p,u,t,!0),u.state===e.ACTIVE&&(T=i,i=p.opcodes,E(0,n),i=T,g(u,o,n,l),f(p,u,t,!1))}else T=i,i=p.opcodes,E(0,n,u),i=T;d||c&&(u.state===e.NOMATCH?s.ast.setLength(r):s.ast.up(m.index,p.name,n,u.phraseLength))})(t,r);break;case e.TRG:((t,s)=>{const n=i[t];u.state=e.NOMATCH,s<o.length&&n.min<=o[s]&&o[s]<=n.max&&(u.state=e.MATCH,u.phraseLength=1)})(t,r);break;case e.TBS:((t,s)=>{const n=i[t],a=n.string.length;if(u.state=e.NOMATCH,s+a<=o.length){for(let e=0;e<a;e+=1)if(o[s+e]!==n.string[e])return;u.state=e.MATCH,u.phraseLength=a}})(t,r);break;case e.TLS:((t,s)=>{let n;const a=i[t];u.state=e.NOMATCH;const r=a.string.length;if(0!==r){if(s+r<=o.length){for(let e=0;e<r;e+=1)if(n=o[s+e],n>=65&&n<=90&&(n+=32),n!==a.string[e])return;u.state=e.MATCH,u.phraseLength=r}}else u.state=e.EMPTY})(t,r);break;case e.UDT:A(t,r);break;case e.AND:((t,s)=>{switch(d+=1,E(t+1,s),d-=1,u.phraseLength=0,u.state){case e.EMPTY:case e.MATCH:u.state=e.EMPTY;break;case e.NOMATCH:u.state=e.NOMATCH;break;default:throw new Error(`opAND: invalid state ${u.state}`)}})(t,r);break;case e.NOT:((t,s)=>{switch(d+=1,E(t+1,s),d-=1,u.phraseLength=0,u.state){case e.EMPTY:case e.MATCH:u.state=e.NOMATCH;break;case e.NOMATCH:u.state=e.EMPTY;break;default:throw new Error(`opNOT: invalid state ${u.state}`)}})(t,r);break;default:throw new Error(`${c}unrecognized operator`)}d||r+u.phraseLength>g&&(g=r+u.phraseLength),s.stats&&s.stats.collect(L,u),s.trace&&s.trace.up(L,u.state,r,u.phraseLength),T-=1}},Ast=function(){const e=identifiers,t=utilities,s=this;let n,a,r,i=0;const o=[],h=[],c=[];function l(e){let t="";for(;e-- >0;)t+=" ";return t}s.callbacks=[],s.init=(e,t,l)=>{let d;h.length=0,c.length=0,i=0,n=e,a=t,r=l;const T=[];for(d=0;d<n.length;d+=1)T.push(n[d].lower);for(d=0;d<a.length;d+=1)T.push(a[d].lower);for(i=n.length+a.length,d=0;d<i;d+=1)o[d]=void 0;for(const e in s.callbacks)if(s.callbacks.hasOwnProperty(e)){const t=e.toLowerCase();if(d=T.indexOf(t),d<0)throw new Error(`parser.js: Ast()): init: node '${e}' not a rule or udt name`);o[d]=s.callbacks[e]}},s.ruleDefined=e=>!!o[e],s.udtDefined=e=>!!o[n.length+e],s.down=(t,s)=>{const n=c.length;return h.push(n),c.push({name:s,thisIndex:n,thatIndex:void 0,state:e.SEM_PRE,callbackIndex:t,phraseIndex:void 0,phraseLength:void 0,stack:h.length}),n},s.up=(t,s,n,a)=>{const r=c.length,i=h.pop();return c.push({name:s,thisIndex:r,thatIndex:i,state:e.SEM_POST,callbackIndex:t,phraseIndex:n,phraseLength:a,stack:h.length}),c[i].thatIndex=r,c[i].phraseIndex=n,c[i].phraseLength=a,r},s.translate=t=>{let s,n;for(let a=0;a<c.length;a+=1)n=c[a],s=o[n.callbackIndex],s&&(n.state===e.SEM_PRE?s(e.SEM_PRE,r,n.phraseIndex,n.phraseLength,t):s&&s(e.SEM_POST,r,n.phraseIndex,n.phraseLength,t))},s.setLength=e=>{c.length=e,h.length=e>0?c[e-1].stack:0},s.getLength=()=>c.length,s.toXml=()=>{let s="",n=0;return s+='<?xml version="1.0" encoding="utf-8"?>\n',s+=`<root nodes="${c.length/2}" characters="${r.length}">\n`,s+="\x3c!-- input string --\x3e\n",s+=l(n+2),s+=t.charsToString(r),s+="\n",c.forEach((a=>{a.state===e.SEM_PRE?(n+=1,s+=l(n),s+=`<node name="${a.name}" index="${a.phraseIndex}" length="${a.phraseLength}">\n`,s+=l(n+2),s+=t.charsToString(r,a.phraseIndex,a.phraseLength),s+="\n"):(s+=l(n),s+=`</node>\x3c!-- name="${a.name}" --\x3e\n`,n-=1)})),s+="</root>\n",s}},Trace=function(){const e=identifiers,t=utilities,s="parser.js: Trace(): ";let n,a,r,i="",o=0;const h=this,c=e=>{let t="",s=0;if(e>=0)for(;e--;)s+=1,5===s?(t+="|",s=0):t+=".";return t};h.init=(e,t,s)=>{a=e,r=t,n=s};const l=n=>{let i;switch(n.type){case e.ALT:i="ALT";break;case e.CAT:i="CAT";break;case e.REP:i=n.max===1/0?`REP(${n.min},inf)`:`REP(${n.min},${n.max})`;break;case e.RNM:i=`RNM(${a[n.index].name})`;break;case e.TRG:i=`TRG(${n.min},${n.max})`;break;case e.TBS:i=n.string.length>6?`TBS(${t.charsToString(n.string,0,3)}...)`:`TBS(${t.charsToString(n.string,0,6)})`;break;case e.TLS:i=n.string.length>6?`TLS(${t.charsToString(n.string,0,3)}...)`:`TLS(${t.charsToString(n.string,0,6)})`;break;case e.UDT:i=`UDT(${r[n.index].name})`;break;case e.AND:i="AND";break;case e.NOT:i="NOT";break;default:throw new Error(`${s}Trace: opName: unrecognized opcode`)}return i};h.down=(e,s)=>{const a=c(o),r=Math.min(100,n.length-s);let h=t.charsToString(n,s,r);r<n.length-s&&(h+="..."),h=`${a}|-|[${l(e)}]${h}\n`,i+=h,o+=1},h.up=(a,r,h,d)=>{const T=`${s}trace.up: `;o-=1;const m=c(o);let p,g,u;switch(r){case e.EMPTY:u="|E|",g="''";break;case e.MATCH:u="|M|",p=Math.min(100,d),g=p<d?`'${t.charsToString(n,h,p)}...'`:`'${t.charsToString(n,h,p)}'`;break;case e.NOMATCH:u="|N|",g="";break;default:throw new Error(`${T} unrecognized state`)}g=`${m}${u}[${l(a)}]${g}\n`,i+=g},h.displayTrace=()=>i},Stats=function(){const e=identifiers;let t,s,n;const a=[],r=[],i=[];this.init=(e,n)=>{t=e,s=n,T()},this.collect=(t,s)=>{m(n,s.state,s.phraseLength),m(a[t.type],s.state,s.phraseLength),t.type===e.RNM&&m(r[t.index],s.state,s.phraseLength),t.type===e.UDT&&m(i[t.index],s.state,s.phraseLength)},this.displayStats=()=>{let t="";const s={match:0,empty:0,nomatch:0,total:0},n=(e,t,n,a,r)=>{s.match+=t,s.empty+=n,s.nomatch+=a,s.total+=r;return`${e} | ${o(t)} | ${o(n)} | ${o(a)} | ${o(r)} |\n`};return t+=" OPERATOR STATS\n",t+=" | MATCH | EMPTY | NOMATCH | TOTAL |\n",t+=n(" ALT",a[e.ALT].match,a[e.ALT].empty,a[e.ALT].nomatch,a[e.ALT].total),t+=n(" CAT",a[e.CAT].match,a[e.CAT].empty,a[e.CAT].nomatch,a[e.CAT].total),t+=n(" REP",a[e.REP].match,a[e.REP].empty,a[e.REP].nomatch,a[e.REP].total),t+=n(" RNM",a[e.RNM].match,a[e.RNM].empty,a[e.RNM].nomatch,a[e.RNM].total),t+=n(" TRG",a[e.TRG].match,a[e.TRG].empty,a[e.TRG].nomatch,a[e.TRG].total),t+=n(" TBS",a[e.TBS].match,a[e.TBS].empty,a[e.TBS].nomatch,a[e.TBS].total),t+=n(" TLS",a[e.TLS].match,a[e.TLS].empty,a[e.TLS].nomatch,a[e.TLS].total),t+=n(" UDT",a[e.UDT].match,a[e.UDT].empty,a[e.UDT].nomatch,a[e.UDT].total),t+=n(" AND",a[e.AND].match,a[e.AND].empty,a[e.AND].nomatch,a[e.AND].total),t+=n(" NOT",a[e.NOT].match,a[e.NOT].empty,a[e.NOT].nomatch,a[e.NOT].total),t+=n("TOTAL",s.match,s.empty,s.nomatch,s.total),t},this.displayHits=e=>{let t="";const s=(e,t,s,a,r)=>{n.match+=e,n.empty+=t,n.nomatch+=s,n.total+=a;return`| ${o(e)} | ${o(t)} | ${o(s)} | ${o(a)} | ${r}\n`};"string"==typeof e&&"a"===e.toLowerCase()[0]?(r.sort(h),i.sort(h),t+=" RULES/UDTS ALPHABETICALLY\n"):"string"==typeof e&&"i"===e.toLowerCase()[0]?(r.sort(l),i.sort(l),t+=" RULES/UDTS BY INDEX\n"):(r.sort(c),i.sort(c),t+=" RULES/UDTS BY HIT COUNT\n"),t+="| MATCH | EMPTY | NOMATCH | TOTAL | NAME\n";for(let e=0;e<r.length;e+=1){let n=r[e];n.total&&(t+=s(n.match,n.empty,n.nomatch,n.total,n.name))}for(let e=0;e<i.length;e+=1){let n=i[e];n.total&&(t+=s(n.match,n.empty,n.nomatch,n.total,n.name))}return t};const o=e=>e<10?` ${e}`:e<100?` ${e}`:e<1e3?` ${e}`:e<1e4?` ${e}`:e<1e5?` ${e}`:e<1e6?` ${e}`:`${e}`,h=(e,t)=>e.lower<t.lower?-1:e.lower>t.lower?1:0,c=(e,t)=>e.total<t.total?1:e.total>t.total?-1:h(e,t),l=(e,t)=>e.index<t.index?-1:e.index>t.index?1:0,d=function(){this.empty=0,this.match=0,this.nomatch=0,this.total=0},T=()=>{a.length=0,n=new d,a[e.ALT]=new d,a[e.CAT]=new d,a[e.REP]=new d,a[e.RNM]=new d,a[e.TRG]=new d,a[e.TBS]=new d,a[e.TLS]=new d,a[e.UDT]=new d,a[e.AND]=new d,a[e.NOT]=new d,r.length=0;for(let e=0;e<t.length;e+=1)r.push({empty:0,match:0,nomatch:0,total:0,name:t[e].name,lower:t[e].lower,index:t[e].index});if(s.length>0){i.length=0;for(let e=0;e<s.length;e+=1)i.push({empty:0,match:0,nomatch:0,total:0,name:s[e].name,lower:s[e].lower,index:s[e].index})}},m=(t,s)=>{switch(t.total+=1,s){case e.EMPTY:t.empty+=1;break;case e.MATCH:t.match+=1;break;case e.NOMATCH:t.nomatch+=1;break;default:throw new Error(`parser.js: Stats(): collect(): incStat(): unrecognized state: ${s}`)}}},utilities={stringToChars:e=>[...e].map((e=>e.codePointAt(0))),charsToString:(e,t,s)=>{let n=e;for(;!(void 0===t||t<0);){if(void 0===s){n=e.slice(t);break}if(s<=0)return"";n=e.slice(t,t+s);break}return String.fromCodePoint(...n)}},identifiers={ALT:1,CAT:2,REP:3,RNM:4,TRG:5,TBS:6,TLS:7,UDT:11,AND:12,NOT:13,ACTIVE:100,MATCH:101,EMPTY:102,NOMATCH:103,SEM_PRE:200,SEM_POST:201,SEM_OK:300,idName:e=>{switch(e){case identifiers.ALT:return"ALT";case identifiers.CAT:return"CAT";case identifiers.REP:return"REP";case identifiers.RNM:return"RNM";case identifiers.TRG:return"TRG";case identifiers.TBS:return"TBS";case identifiers.TLS:return"TLS";case identifiers.UDT:return"UDT";case identifiers.AND:return"AND";case identifiers.NOT:return"NOT";case identifiers.ACTIVE:return"ACTIVE";case identifiers.EMPTY:return"EMPTY";case identifiers.MATCH:return"MATCH";case identifiers.NOMATCH:return"NOMATCH";case identifiers.SEM_PRE:return"SEM_PRE";case identifiers.SEM_POST:return"SEM_POST";case identifiers.SEM_OK:return"SEM_OK";default:return"UNRECOGNIZED STATE"}}},Grammar=function(){this.grammarObject="grammarObject",this.rules=[],this.rules[0]={name:"start",lower:"start",index:0,isBkr:!1},this.rules[1]={name:"statement",lower:"statement",index:1,isBkr:!1},this.rules[2]={name:"statement1",lower:"statement1",index:2,isBkr:!1},this.rules[3]={name:"statement2",lower:"statement2",index:3,isBkr:!1},this.rules[4]={name:"grin",lower:"grin",index:4,isBkr:!1},this.rules[5]={name:"cool",lower:"cool",index:5,isBkr:!1},this.rules[6]={name:"LF",lower:"lf",index:6,isBkr:!1},this.udts=[],this.rules[0].opcodes=[],this.rules[0].opcodes[0]={type:2,children:[1,2,3,4]},this.rules[0].opcodes[1]={type:4,index:2},this.rules[0].opcodes[2]={type:4,index:6},this.rules[0].opcodes[3]={type:4,index:3},this.rules[0].opcodes[4]={type:3,min:0,max:1},this.rules[0].opcodes[5]={type:4,index:6},this.rules[1].opcodes=[],this.rules[1].opcodes[0]={type:2,children:[1,2]},this.rules[1].opcodes[1]={type:5,min:33,max:126},this.rules[1].opcodes[2]={type:3,min:0,max:1/0},this.rules[1].opcodes[3]={type:5,min:32,max:126},this.rules[2].opcodes=[],this.rules[2].opcodes[0]={type:2,children:[1,2]},this.rules[2].opcodes[1]={type:4,index:1},this.rules[2].opcodes[2]={type:4,index:4},this.rules[3].opcodes=[],this.rules[3].opcodes[0]={type:2,children:[1,2]},this.rules[3].opcodes[1]={type:4,index:1},this.rules[3].opcodes[2]={type:4,index:5},this.rules[4].opcodes=[],this.rules[4].opcodes[0]={type:6,string:[128513]},this.rules[5].opcodes=[],this.rules[5].opcodes[0]={type:6,string:[128526]},this.rules[6].opcodes=[],this.rules[6].opcodes[0]={type:1,children:[1,2,3]},this.rules[6].opcodes[1]={type:6,string:[10]},this.rules[6].opcodes[2]={type:6,string:[13,10]},this.rules[6].opcodes[3]={type:6,string:[13]},this.toString=function(){let e="";return e+=";\n",e+="; ABNF with emoticons\n",e+=";\n",e+="start = statement1 LF statement2 [LF]\n",e+="statement = %d33-126 *%d32-126\n",e+="statement1 = statement grin\n",e+="statement2 = statement cool\n",e+="grin = %d128513 ; grinning face\n",e+="cool = %d128526 ; cool shades, sunglasses\n",e+="LF = %d10 / %d13.10 / %d13\n",";\n; ABNF with emoticons\n;\nstart = statement1 LF statement2 [LF]\nstatement = %d33-126 *%d32-126\nstatement1 = statement grin\nstatement2 = statement cool\ngrin = %d128513 ; grinning face\ncool = %d128526 ; cool shades, sunglasses\nLF = %d10 / %d13.10 / %d13\n"}},parser=new Parser,grammar=new Grammar,id=identifiers,utils=utilities;$("#grammar-bnf").html(grammar.toString()),parser.callbacks.start=(e,t,s,n)=>{switch(e.state){case id.ACTIVE:n.start="invalid statement(s)",n.statement1=void 0,n.statement2=void 0;break;case id.MATCH:n.start=utils.charsToString(t,s,e.phraseLength)}},parser.callbacks.statement1=(e,t,s,n)=>{e.state===id.MATCH&&(n.statement1=utils.charsToString(t,s,e.phraseLength))},parser.callbacks.statement2=(e,t,s,n)=>{e.state===id.MATCH&&(n.statement2=utils.charsToString(t,s,e.phraseLength))};const data={},parseNumber=()=>{const e=$("#string").val(),t=parser.parse(grammar,"start",e,data);$("#result").html("Parser Result<br>"+JSON.stringify(t));let s="";t.success?(s+="Parsed Statments\n",s+=`statement1: ${data.statement1}\n`,s+=`statement2: ${data.statement2}\n`):s+="One or more invalid statements.\n",$("#values").html(s)};$(document).ready((()=>{$("#parse").click(parseNumber);let e="See you later alligator. 😁\n";e+="After while crocodile. 😎\n",$("#string").val("See you later alligator. 😁\nAfter while crocodile. 😎\n")}));