apg-lite
Version:
lightweight JavaScript APG parser
1 lines • 16.8 kB
JavaScript
const Parser=function(){const e=identifiers,t=utilities,r=this,n="parser.js: Parser(): ";r.ast=void 0,r.stats=void 0,r.trace=void 0,r.callbacks=[];let a,s,i,o,h,c,l,d=0,T=0,p=0,g=0,u=0,m=new function(){this.state=e.ACTIVE,this.phraseLength=0,this.refresh=()=>{this.state=e.ACTIVE,this.phraseLength=0}};r.parse=(f,b,M,E)=>{const L=`${n}parse(): `;d=0,T=0,p=0,g=0,u=0,a=void 0,s=void 0,i=void 0,o=void 0,m.refresh(),h=void 0,c=void 0,l=void 0,o=t.stringToChars(M),a=f.rules,s=f.udts;const w=b.toLowerCase();let $;for(const e in a)if(a.hasOwnProperty(e)&&w===a[e].lower){$=a[e].index;break}if(void 0===$)throw new Error(`${L}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<s.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<s.length;t+=1)o.push(s[t].lower);for(const n in r.callbacks)if(r.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=r.callbacks[n]?r.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}})(),r.trace&&r.trace.init(a,s,o),r.stats&&r.stats.init(a,s),r.ast&&r.ast.init(a,s,o),l=E,i=[{type:e.RNM,index:$}],A(0,0),i=void 0;let N=!1;switch(m.state){case e.ACTIVE:throw new Error(`${L}final state should never be 'ACTIVE'`);case e.NOMATCH:N=!1;break;case e.EMPTY:case e.MATCH:N=m.phraseLength===o.length;break;default:throw new Error("unrecognized state")}return{success:N,state:m.state,stateName:e.idName(m.state),length:o.length,matched:m.phraseLength,maxMatched:u,maxTreeDepth:p,nodeHits:g}};const f=(t,r,a,s)=>{if(r.phraseLength>a){let e=`${n}opRNM(${t.name}): callback function error: `;throw e+=`sysData.phraseLength: ${r.phraseLength}`,e+=` must be <= remaining chars: ${a}`,new Error(e)}switch(r.state){case e.ACTIVE:if(!s)throw new Error(`${n}opRNM(${t.name}): callback function return error. ACTIVE state not allowed.`);break;case e.EMPTY:r.phraseLength=0;break;case e.MATCH:0===r.phraseLength&&(r.state=e.EMPTY);break;case e.NOMATCH:r.phraseLength=0;break;default:throw new Error(`${n}opRNM(${t.name}): callback function return error. Unrecognized return state: ${r.state}`)}},b=(t,h)=>{let T,p,g;const u=i[t],f=s[u.index];m.UdtIndex=f.index,d||(g=r.ast&&r.ast.udtDefined(u.index),g&&(p=a.length+u.index,T=r.ast.getLength(),r.ast.down(p,f.name)));const b=o.length-h;c[u.index](m,o,h,l),((t,r,a)=>{if(r.phraseLength>a){let e=`${n}opUDT(${t.name}): callback function error: `;throw e+=`sysData.phraseLength: ${r.phraseLength}`,e+=` must be <= remaining chars: ${a}`,new Error(e)}switch(r.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.`);r.phraseLength=0;break;case e.MATCH:if(0===r.phraseLength){if(!t.empty)throw new Error(`${n}opUDT(${t.name}) may not return EMPTY.`);r.state=e.EMPTY}break;case e.NOMATCH:r.phraseLength=0;break;default:throw new Error(`${n}opUDT(${t.name}): callback function return error. Unrecognized return state: ${r.state}`)}})(f,m,b),d||g&&(m.state===e.NOMATCH?r.ast.setLength(T):r.ast.up(p,f.name,h,m.phraseLength))},A=(t,s)=>{const c=`${n}opExecute(): `,M=i[t];switch(g+=1,T>p&&(p=T),T+=1,m.refresh(),r.trace&&r.trace.down(M,s),M.type){case e.ALT:((t,r)=>{const n=i[t];for(let t=0;t<n.children.length&&(A(n.children[t],r),m.state===e.NOMATCH);t+=1);})(t,s);break;case e.CAT:((t,n)=>{let a,s,o,h;const c=i[t];r.ast&&(s=r.ast.getLength()),a=!0,o=n,h=0;for(let t=0;t<c.children.length;t+=1){if(A(c.children[t],o),m.state===e.NOMATCH){a=!1;break}o+=m.phraseLength,h+=m.phraseLength}a?(m.state=0===h?e.EMPTY:e.MATCH,m.phraseLength=h):(m.state=e.NOMATCH,m.phraseLength=0,r.ast&&r.ast.setLength(s))})(t,s);break;case e.REP:((t,n)=>{let a,s,h,c;const l=i[t];if(0===l.max)return m.state=e.EMPTY,void(m.phraseLength=0);for(s=n,h=0,c=0,r.ast&&(a=r.ast.getLength());!(s>=o.length)&&(A(t+1,s),m.state!==e.NOMATCH)&&m.state!==e.EMPTY&&(c+=1,h+=m.phraseLength,s+=m.phraseLength,c!==l.max););m.state===e.EMPTY||c>=l.min?(m.state=0===h?e.EMPTY:e.MATCH,m.phraseLength=h):(m.state=e.NOMATCH,m.phraseLength=0,r.ast&&r.ast.setLength(a))})(t,s);break;case e.RNM:((t,n)=>{let s,c,T;const p=i[t],g=a[p.index],u=h[g.index];if(d||(c=r.ast&&r.ast.ruleDefined(p.index),c&&(s=r.ast.getLength(),r.ast.down(p.index,a[p.index].name))),u){const t=o.length-n;u(m,o,n,l),f(g,m,t,!0),m.state===e.ACTIVE&&(T=i,i=g.opcodes,A(0,n),i=T,u(m,o,n,l),f(g,m,t,!1))}else T=i,i=g.opcodes,A(0,n,m),i=T;d||c&&(m.state===e.NOMATCH?r.ast.setLength(s):r.ast.up(p.index,g.name,n,m.phraseLength))})(t,s);break;case e.TRG:((t,r)=>{const n=i[t];m.state=e.NOMATCH,r<o.length&&n.min<=o[r]&&o[r]<=n.max&&(m.state=e.MATCH,m.phraseLength=1)})(t,s);break;case e.TBS:((t,r)=>{const n=i[t],a=n.string.length;if(m.state=e.NOMATCH,r+a<=o.length){for(let e=0;e<a;e+=1)if(o[r+e]!==n.string[e])return;m.state=e.MATCH,m.phraseLength=a}})(t,s);break;case e.TLS:((t,r)=>{let n;const a=i[t];m.state=e.NOMATCH;const s=a.string.length;if(0!==s){if(r+s<=o.length){for(let e=0;e<s;e+=1)if(n=o[r+e],n>=65&&n<=90&&(n+=32),n!==a.string[e])return;m.state=e.MATCH,m.phraseLength=s}}else m.state=e.EMPTY})(t,s);break;case e.UDT:b(t,s);break;case e.AND:((t,r)=>{switch(d+=1,A(t+1,r),d-=1,m.phraseLength=0,m.state){case e.EMPTY:case e.MATCH:m.state=e.EMPTY;break;case e.NOMATCH:m.state=e.NOMATCH;break;default:throw new Error(`opAND: invalid state ${m.state}`)}})(t,s);break;case e.NOT:((t,r)=>{switch(d+=1,A(t+1,r),d-=1,m.phraseLength=0,m.state){case e.EMPTY:case e.MATCH:m.state=e.NOMATCH;break;case e.NOMATCH:m.state=e.EMPTY;break;default:throw new Error(`opNOT: invalid state ${m.state}`)}})(t,s);break;default:throw new Error(`${c}unrecognized operator`)}d||s+m.phraseLength>u&&(u=s+m.phraseLength),r.stats&&r.stats.collect(M,m),r.trace&&r.trace.up(M,m.state,s,m.phraseLength),T-=1}},Ast=function(){const e=identifiers,t=utilities,r=this;let n,a,s,i=0;const o=[],h=[],c=[];function l(e){let t="";for(;e-- >0;)t+=" ";return t}r.callbacks=[],r.init=(e,t,l)=>{let d;h.length=0,c.length=0,i=0,n=e,a=t,s=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 r.callbacks)if(r.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]=r.callbacks[e]}},r.ruleDefined=e=>!!o[e],r.udtDefined=e=>!!o[n.length+e],r.down=(t,r)=>{const n=c.length;return h.push(n),c.push({name:r,thisIndex:n,thatIndex:void 0,state:e.SEM_PRE,callbackIndex:t,phraseIndex:void 0,phraseLength:void 0,stack:h.length}),n},r.up=(t,r,n,a)=>{const s=c.length,i=h.pop();return c.push({name:r,thisIndex:s,thatIndex:i,state:e.SEM_POST,callbackIndex:t,phraseIndex:n,phraseLength:a,stack:h.length}),c[i].thatIndex=s,c[i].phraseIndex=n,c[i].phraseLength=a,s},r.translate=t=>{let r,n;for(let a=0;a<c.length;a+=1)n=c[a],r=o[n.callbackIndex],r&&(n.state===e.SEM_PRE?r(e.SEM_PRE,s,n.phraseIndex,n.phraseLength,t):r&&r(e.SEM_POST,s,n.phraseIndex,n.phraseLength,t))},r.setLength=e=>{c.length=e,h.length=e>0?c[e-1].stack:0},r.getLength=()=>c.length,r.toXml=()=>{let r="",n=0;return r+='<?xml version="1.0" encoding="utf-8"?>\n',r+=`<root nodes="${c.length/2}" characters="${s.length}">\n`,r+="\x3c!-- input string --\x3e\n",r+=l(n+2),r+=t.charsToString(s),r+="\n",c.forEach((a=>{a.state===e.SEM_PRE?(n+=1,r+=l(n),r+=`<node name="${a.name}" index="${a.phraseIndex}" length="${a.phraseLength}">\n`,r+=l(n+2),r+=t.charsToString(s,a.phraseIndex,a.phraseLength),r+="\n"):(r+=l(n),r+=`</node>\x3c!-- name="${a.name}" --\x3e\n`,n-=1)})),r+="</root>\n",r}},Trace=function(){const e=identifiers,t=utilities,r="parser.js: Trace(): ";let n,a,s,i="",o=0;const h=this,c=e=>{let t="",r=0;if(e>=0)for(;e--;)r+=1,5===r?(t+="|",r=0):t+=".";return t};h.init=(e,t,r)=>{a=e,s=t,n=r};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(${s[n.index].name})`;break;case e.AND:i="AND";break;case e.NOT:i="NOT";break;default:throw new Error(`${r}Trace: opName: unrecognized opcode`)}return i};h.down=(e,r)=>{const a=c(o),s=Math.min(100,n.length-r);let h=t.charsToString(n,r,s);s<n.length-r&&(h+="..."),h=`${a}|-|[${l(e)}]${h}\n`,i+=h,o+=1},h.up=(a,s,h,d)=>{const T=`${r}trace.up: `;o-=1;const p=c(o);let g,u,m;switch(s){case e.EMPTY:m="|E|",u="''";break;case e.MATCH:m="|M|",g=Math.min(100,d),u=g<d?`'${t.charsToString(n,h,g)}...'`:`'${t.charsToString(n,h,g)}'`;break;case e.NOMATCH:m="|N|",u="";break;default:throw new Error(`${T} unrecognized state`)}u=`${p}${m}[${l(a)}]${u}\n`,i+=u},h.displayTrace=()=>i},Stats=function(){const e=identifiers;let t,r,n;const a=[],s=[],i=[];this.init=(e,n)=>{t=e,r=n,T()},this.collect=(t,r)=>{p(n,r.state,r.phraseLength),p(a[t.type],r.state,r.phraseLength),t.type===e.RNM&&p(s[t.index],r.state,r.phraseLength),t.type===e.UDT&&p(i[t.index],r.state,r.phraseLength)},this.displayStats=()=>{let t="";const r={match:0,empty:0,nomatch:0,total:0},n=(e,t,n,a,s)=>{r.match+=t,r.empty+=n,r.nomatch+=a,r.total+=s;return`${e} | ${o(t)} | ${o(n)} | ${o(a)} | ${o(s)} |\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",r.match,r.empty,r.nomatch,r.total),t},this.displayHits=e=>{let t="";const r=(e,t,r,a,s)=>{n.match+=e,n.empty+=t,n.nomatch+=r,n.total+=a;return`| ${o(e)} | ${o(t)} | ${o(r)} | ${o(a)} | ${s}\n`};"string"==typeof e&&"a"===e.toLowerCase()[0]?(s.sort(h),i.sort(h),t+=" RULES/UDTS ALPHABETICALLY\n"):"string"==typeof e&&"i"===e.toLowerCase()[0]?(s.sort(l),i.sort(l),t+=" RULES/UDTS BY INDEX\n"):(s.sort(c),i.sort(c),t+=" RULES/UDTS BY HIT COUNT\n"),t+="| MATCH | EMPTY | NOMATCH | TOTAL | NAME\n";for(let e=0;e<s.length;e+=1){let n=s[e];n.total&&(t+=r(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+=r(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,s.length=0;for(let e=0;e<t.length;e+=1)s.push({empty:0,match:0,nomatch:0,total:0,name:t[e].name,lower:t[e].lower,index:t[e].index});if(r.length>0){i.length=0;for(let e=0;e<r.length;e+=1)i.push({empty:0,match:0,nomatch:0,total:0,name:r[e].name,lower:r[e].lower,index:r[e].index})}},p=(t,r)=>{switch(t.total+=1,r){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: ${r}`)}}},utilities={stringToChars:e=>[...e].map((e=>e.codePointAt(0))),charsToString:(e,t,r)=>{let n=e;for(;!(void 0===t||t<0);){if(void 0===r){n=e.slice(t);break}if(r<=0)return"";n=e.slice(t,t+r);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:"phone-number",lower:"phone-number",index:0,isBkr:!1},this.rules[1]={name:"area-code",lower:"area-code",index:1,isBkr:!1},this.rules[2]={name:"subscriber",lower:"subscriber",index:2,isBkr:!1},this.rules[3]={name:"gt-2",lower:"gt-2",index:3,isBkr:!1},this.rules[4]={name:"not-9",lower:"not-9",index:4,isBkr:!1},this.rules[5]={name:"digit",lower:"digit",index:5,isBkr:!1},this.udts=[],this.udts[0]={name:"u_office",lower:"u_office",index:0,empty:!1,isBkr:!1},this.rules[0].opcodes=[],this.rules[0].opcodes[0]={type:2,children:[1,2,3,4,5,6]},this.rules[0].opcodes[1]={type:7,string:[40]},this.rules[0].opcodes[2]={type:4,index:1},this.rules[0].opcodes[3]={type:7,string:[41]},this.rules[0].opcodes[4]={type:11,empty:!1,index:0},this.rules[0].opcodes[5]={type:7,string:[45]},this.rules[0].opcodes[6]={type:4,index:2},this.rules[1].opcodes=[],this.rules[1].opcodes[0]={type:2,children:[1,2,3]},this.rules[1].opcodes[1]={type:4,index:3},this.rules[1].opcodes[2]={type:4,index:4},this.rules[1].opcodes[3]={type:4,index:5},this.rules[2].opcodes=[],this.rules[2].opcodes[0]={type:3,min:4,max:4},this.rules[2].opcodes[1]={type:4,index:5},this.rules[3].opcodes=[],this.rules[3].opcodes[0]={type:5,min:50,max:57},this.rules[4].opcodes=[],this.rules[4].opcodes[0]={type:5,min:48,max:56},this.rules[5].opcodes=[],this.rules[5].opcodes[0]={type:5,min:48,max:57},this.toString=function(){let e="";return e+=";\n",e+="; Ref: Wikipedia, North American Numbering Plan\n",e+=";\n",e+='phone-number = "(" area-code ")" u_office "-" subscriber\n',e+="area-code = gt-2 not-9 digit\n",e+="subscriber = 4digit\n",e+="gt-2 = %d50-57\n",e+="not-9 = %d48-56\n",e+="digit = %d48-57\n",';\n; Ref: Wikipedia, North American Numbering Plan\n;\nphone-number = "(" area-code ")" u_office "-" subscriber\narea-code = gt-2 not-9 digit\nsubscriber = 4digit\ngt-2 = %d50-57\nnot-9 = %d48-56\ndigit = %d48-57\n'}},parser=new Parser,grammar=new Grammar,id=identifiers,utils=utilities;$("#grammar-bnf").html(grammar.toString()),parser.callbacks["phone-number"]=(e,t,r,n)=>{switch(e.state){case id.ACTIVE:n["phone-number"]="invalid",n.sign=void 0,n.decimal=void 0,n.exponent=void 0;break;case id.MATCH:n["phone-number"]=utils.charsToString(t,r,e.phraseLength)}},parser.callbacks.u_office=(e,t,r,n)=>{let a=!1;for(;!(t+r+3<=t.length);){const e=t[r],n=t[r+1],s=t[r+2];if(e<50||e>57)break;if(n<48||n>57||s<48||s>57)break;if(49===n&&49===s)break;a=!0;break}!0===a?(e.state=id.MATCH,e.phraseLength=3,null!==n&&(n.u_office=utils.charsToString(t,r,e.phraseLength))):(e.state=id.NOMATCH,e.phraseLength=0)},parser.callbacks["area-code"]=(e,t,r,n)=>{e.state===id.MATCH&&(n["area-code"]=utils.charsToString(t,r,e.phraseLength))},parser.callbacks.subscriber=(e,t,r,n)=>{e.state===id.MATCH&&(n.subscriber=utils.charsToString(t,r,e.phraseLength))};const data={},parseNumber=()=>{const e=$("#string").val(),t=parser.parse(grammar,"phone-number",e,data);$("#result").html("Parser Result<br>"+JSON.stringify(t));let r="";t.success?(r+="Parsed Phone Number Parts\n",r+=` pnone number: ${data["phone-number"]}\n`,r+=` area code: ${data["area-code"]}\n`,r+=` office code: ${data.u_office}\n`,r+=`subscriber number: ${data.subscriber}\n`):r+="Invalid Phone Number\n",$("#values").html(r)};$(document).ready((()=>{$("#parse").click(parseNumber),$("#string").val("(339)501-5555")}));