apg
Version:
(Deprecated: use apg-js instead.) An ABNF Parser Generator - generates recursive-descent parsers from grammars written in a superset of Augmented Backus-Naur Form (ABNF)
1,518 lines (1,473 loc) • 57.6 kB
JavaScript
window.apgweb = (function() {
// This function generates the object with all of the event handlers for the web interface events.
/* Global HTML page information */
var GRAMMAR_AREA = 'grammar-area';
var PARSER_AREA = 'parser-area';
var INPUT_AREA = 'input-area';
var GRAMMAR_PAGE = 'grammar-page';
var RULES_PAGE = 'rules-page';
var ATTRS_PAGE = 'attrs-page';
var STATE_PAGE = 'state-page';
var STATS_PAGE = 'stats-page';
var TRACE_FRAME = 'trace-frame';
var PHRASES_FRAME = 'phrases-frame';
var MODAL_DIALOG = 'modal';
var MODAL_TOP = 'modal-top';
var MODAL_CONTENT = 'modal-content';
var MODAL_INFO = {
elements: [MODAL_DIALOG, MODAL_TOP, MODAL_CONTENT]
}
var TYPE_TEXTAREA = 1;
var TYPE_PAGE = 2;
var TYPE_FRAME = 3;
var PAGE_INFO = [];
var INPUT_MODE = {
name : 'input-mode',
elements : []
};
var TRACE = {};
TRACE.ON = {
name : 'traceon',
elements : []
};
TRACE.DISPLAY = {
name : 'trace-display',
elements : []
};
TRACE.RECORDS = {
name : 'trace-records',
elements : []
};
TRACE.OPERATORS_ON = {
name : 'operatorson',
elements : []
};
TRACE.OPERATORS = {
name : 'operators',
elements : []
};
TRACE.RULES_ON = {
name : 'ruleson',
elements : []
};
TRACE.RULES_TABLE = {
name: 'rules-table',
element: null
};
TRACE.RULES = {
name : 'rules',
elements : []
};
TRACE.TRACE_CONFIG = {
name: 'trace-config',
element: null
}
// `apgInput` is the constructor for the object which processes the input to a generated parser.
// It takes the input string from the parser input area, performs validation
// checks and, if no errors, produces an array of characters for the parser to parse.
//````
// input: the input string
// mode: if "escaped", input is decoded from escaped format
// otherwise, input is treated as pure ASCII.
//````
var apgInput = function(input, mode) {
var apglib = require("apg-lib");
var strings = [];
var pmap = [];
/* private functions */
function ctrlChars(str) {
exp = /(\r\n)|(\n)|(\r)|(\t)/g;
var out = "";
var beg = 0;
exp.lastIndex = 0;
while ((rx = exp.exec(str)) !== null) {
if (beg < rx.index) {
out += str.slice(beg, rx.index);
}
if (rx[1]) {
out += '<span class="apg-ctrl-char">CRLF</span><br>';
} else if (rx[2]) {
out += '<span class="apg-ctrl-char">LF</span><br>';
} else if (rx[3]) {
out += '<span class="apg-ctrl-char">CR</span><br>';
} else if (rx[4]) {
out += '<span class="apg-ctrl-char">TAB</span>';
}
beg = rx.index + rx[0].length;
}
if (beg < str.length) {
out += str.slice(beg);
}
return out;
}
function findInvalidChars(str) {
function validRange(beg, end, chars, display) {
display.push(ctrlChars(str.slice(beg, end)));
}
function invalidRange(beg, end, chars, display) {
var str = '<span class="apg-error">\\x' + chars[beg].toString(16);
for (var i = beg + 1; i < end; i += 1) {
str += '\\x' + chars[i].toString(16);
}
str += '</span>';
display.push(str);
}
var exp = /[ -~\t\r\n]+/g;
var rx = exp.exec(str);
if (rx === null) {
/* entire string is invalid characters */
var chars = apglib.utils.stringToChars(str);
var display = [];
invalidRange(0, str.length, chars, display);
return false;
}
if (rx[0].length === str.length) {
/* entire string is valid */
return [];
}
var chars = apglib.utils.stringToChars(str);
var display = [];
var beg = 0;
while (rx !== null) {
if (beg < rx.index) {
invalidRange(beg, rx.index, chars, display);
}
validRange(rx.index, rx.index + rx[0].length, chars, display);
beg = exp.lastIndex;
if (exp.lastIndex === 0) {
break;
}
rx = exp.exec(str);
}
if (beg < str.length) {
invalidRange(beg, str.length, chars, display);
}
return display;
}
function findInvalidEscapes(escapedStr, originalStr) {
function validRange(beg, end, chars, display) {
display.push(ctrlChars(originalStr.slice(beg, end)));
}
function invalidRange(beg, end, chars, display) {
display.push('<span class="apg-error">`</span>');
}
var display = [];
var exp = /`/g;
var rx = exp.exec(escapedStr);
if (rx === null) {
/* no invalid escapes */
return [];
}
var chars = apglib.utils.stringToChars(escapedStr);
var display = [];
var beg = 0;
while (rx !== null) {
if (beg < rx.index) {
validRange(beg, rx.index, chars, display);
}
invalidRange(rx.index, rx.index + rx[0].length, chars, display);
beg = exp.lastIndex;
if (exp.lastIndex === 0) {
break;
}
rx = exp.exec(escapedStr);
}
if (beg < escapedStr.length) {
validRange(beg, escapedStr.length, chars, display);
}
return display;
}
function markupEscapes(str) {
var expd = /``/g
var expx = /`x[0-9a-fA-F]{2}/g;
var expu = /`u[0-9a-fA-F]{4}/g;
var expuu = /`u\{[0-9a-fA-F]{5,8}\}/g;
var ret = str.replace(expd, '\x01\x01');
ret = ret.replace(expx, function(match) {
return match.replace('`x', '\x02\x02');
});
ret = ret.replace(expu, function(match) {
return match.replace('`u', '\x03\x03');
});
ret = ret.replace(expuu, function(match) {
return match.replace('`u', '\x04' + String.fromCharCode(match.length - 4));
});
return ret;
}
function translateEscapes(str, pchars, pmap) {
var tmp, ii;
var chars = apglib.utils.stringToChars(str);
var pi = 0;
var i = 0;
pchars.length = 0;
pmap.length = 0;
while (true) {
var char = chars[i];
switch (char) {
case 1:
ii = i + 2;
pchars[pi] = 96;
pmap[pi] = '`';
chars[i] = 96;
chars[i + 1] = 96;
break;
case 2:
ii = i + 4;
tmp = str.slice(i + 2, ii);
pchars[pi] = parseInt(tmp, 16);
pmap[pi] = '`x' + tmp;
chars[i] = 96;
chars[i + 1] = 120;
break;
case 3:
ii = i + 6;
tmp = str.slice(i + 2, ii);
pchars[pi] = parseInt(tmp, 16);
pmap[pi] = '`u' + tmp;
chars[i] = 96;
chars[i + 1] = 88;
break;
case 4:
ii = i + chars[i+1] + 4;
tmp = str.slice(i + 3, i + 3 + chars[i+1]);
pchars[pi] = parseInt(tmp, 16);
pmap[pi] = '`u{' + tmp + "}";
chars[i] = 96;
chars[i + 1] = 88;
break;
case 9:
ii = i + 1;
pchars[pi] = char;
pmap[pi] = '<span class="apg-ctrl-char">TAB</span>';
break;
case 10:
ii = i + 1;
pchars[pi] = char;
pmap[pi] = '<span class="apg-ctrl-char">LF</span><br>';
break;
case 13:
ii = i + 1;
if (ii < chars.length && chars[ii] === 10) {
pchars[pi] = 13;
pchars[pi + 1] = 10;
pmap[pi] = '<span class="apg-ctrl-char">CR</span>';
pmap[pi + 1] = '<span class="apg-ctrl-char">LF</span><br>';
ii += 1;
pi += 1;
} else {
pchars[pi] = char;
pmap[pi] = '<span class="apg-ctrl-char">CR</span><br>';
}
break;
case 32:
ii = i + 1;
pchars[pi] = char;
pmap[pi] = ' ';
break;
case 38:
ii = i + 1;
pchars[pi] = char;
pmap[pi] = '&';
break;
case 60:
ii = i + 1;
pchars[pi] = char;
pmap[pi] = '<';
break;
default:
ii = i + 1;
pchars[pi] = char;
pmap[pi] = String.fromCharCode(char);
break;
}
i = ii;
pi += 1;
if (i >= chars.length) {
break;
}
}
return chars;
}
function translateCtrl(str, pchars, pmap) {
var tmp, ii;
var chars = apglib.utils.stringToChars(str);
var pi = 0;
var i = 0;
pchars.length = 0;
pmap.length = 0;
while (true) {
var char = chars[i];
switch (char) {
case 9:
ii = i + 1;
pchars[pi] = char;
pmap[pi] = '<span class="apg-ctrl-char">TAB</span>';
break;
case 10:
ii = i + 1;
pchars[pi] = char;
pmap[pi] = '<span class="apg-ctrl-char">LF</span><br>';
break;
case 13:
ii = i + 1;
if (ii < chars.length && chars[ii] === 10) {
pchars[pi] = 13;
pchars[pi + 1] = 10;
pmap[pi] = '<span class="apg-ctrl-char">CR</span>';
pmap[pi + 1] = '<span class="apg-ctrl-char">LF</span><br>';
ii += 1;
pi += 1;
} else {
pchars[pi] = char;
pmap[pi] = '<span class="apg-ctrl-char">CR</span><br>';
}
break;
case 32:
ii = i + 1;
pchars[pi] = char;
pmap[pi] = ' ';
break;
case 38:
ii = i + 1;
pchars[pi] = char;
pmap[pi] = '&';
break;
case 60:
ii = i + 1;
pchars[pi] = char;
pmap[pi] = '<';
break;
default:
ii = i + 1;
pchars[pi] = char;
pmap[pi] = String.fromCharCode(char);
break;
}
i = ii;
pi += 1;
if (i >= chars.length) {
break;
}
}
return chars;
}
function toHtml(arrayOfStrings) {
var html = '<span class="apg-mono">';
arrayOfStrings.forEach(function(str) {
html += str;
});
return html + '</span>';
}
function isEscaped(input){
var expd = /``/;
var expx = /`x/;
var expu = /`u/;
if(expd.exec(input)){
return true;
}
if(expx.exec(input)){
return true;
}
if(expu.exec(input)){
return true;
}
return false;
}
/* public functions */
this.invalidChars = false;
this.invalidEscapes = false;
this.pchars = [];
this.displayInput = function() {
if (this.invalidChars) {
var html = '<span class="apg-error"><h4>Input has invalid characters.</h4></span>';
return html + toHtml(strings);
}
if (this.invalidEscapes) {
var html = '<span class="apg-error"><h4>Input has invalid escaped characters.</h4></span>';
return html + toHtml(strings);
}
return toHtml(pmap);
}
this.displayPhrases = function(phrases) {
var i = 0;
var html = '';
for (ii = 0; ii < phrases.length; ii += 1) {
var phrase = phrases[ii];
if (phrase.beg >= i) {
while (i < phrase.beg) {
html += pmap[i];
i += 1;
}
if (phrase.beg === phrase.end) {
html += '<span class="apg-empty-phrase">𝜀</span>';
} else {
html += '<span class="apg-phrase">';
while (i < phrase.end) {
html += pmap[i];
i += 1;
}
html += "</span>";
}
}
}
while (i < pmap.length) {
html += pmap[i];
i += 1;
}
return html;
}
/* constructor */
strings = findInvalidChars(input);
if (strings.length > 0) {
/* handle invalid character errors */
this.invalidChars = true;
return;
}
if (mode === "escaped") {
/* escaped character mode */
var str = markupEscapes(input);
strings = findInvalidEscapes(str, input);
if (strings.length > 0) {
/* handle invalid escapes errors */
this.invalidEscapes = true;
return;
}
translateEscapes(str, this.pchars, pmap);
} else {
/* ASCII only mode */
translateCtrl(input, this.pchars, pmap);
}
}
// Constructor function for the generated parser.
var apgParser = function() {
var TAB_PHRASES = 3;
var TAB_STATE = 4;
var DISPLAY_FIRST = 1;
var DISPLAY_PREV = 2;
var DISPLAY_ALL = 3;
var DISPLAY_NEXT = 4;
var DISPLAY_LAST = 5;
var apglib = require("apg-lib");
var converter = require("apg-conv-api").converter;
var currentTab = 0;
var parser = null;
var rules = [];
var selectedRule;
var input = null;
var grammar = null;
var result = null;
var trace = null;
var startRule = 0;
/* private functions */
function iframeWrite(pageInfo, body) {
var win = pageInfo.element.contentWindow
var doc = win.document;
var lineHeight = 14;
var fontSize = 12;
var css = 'body{';
css += 'color: black;';
css += 'background-color: white;';
css += 'font-family: monospace;';
css += 'font-size: '+fontSize+'px;';
css += 'line-height: '+lineHeight+'px;';
css += 'margin: 3px;';
css += 'width: 100%;}\n';
css += apglib.emitcss();
css += 'p{margin: 0;}';
doc.open();
doc.write('<html><head>');
doc.write('<title>');
doc.write(pageInfo.title);
doc.write('</title>');
doc.write('<style>');
doc.write(css);
doc.write('</style></head><body>');
doc.write(body);
doc.write('</body></html>');
doc.close();
var elPhrase = doc.getElementsByClassName('apg-phrase')[0];
if(elPhrase){
var elBody = doc.getElementsByTagName('body')[0];
var winHeight = win.innerHeight;
var winWidth = win.innerWidth;
var top = elBody.scrollTop;
var left = elBody.scrollLeft;
var viewHeight = winHeight - (3 * lineHeight);
viewHeight = viewHeight > 0 ? viewHeight : winHeight;
var viewWidth = winWidth - (3 * fontSize);
viewWidth = viewWidth > 0 ? viewWidth : winWidth;
var bot = top + viewHeight;
var right = left + viewWidth;
if(elPhrase.offsetTop > bot || elPhrase.offsetTop < top){
elBody.scrollTop = elPhrase.offsetTop;
}
if(elPhrase.offsetLeft > right || elPhrase.offsetLeft < left){
elBody.scrollLeft = elPhrase.offsetLeft;
}
}
}
function ruleDropDown(obj) {
function alpha(lhs, rhs) {
if (lhs.lower < rhs.lower) {
return -1;
}
if (lhs.lower > rhs.lower) {
return 1;
}
return 0;
}
rules.length = 0;
var list = document.getElementById("ast-phrases");
if (!obj) {
list.innerHTML = "";
return;
}
for ( var key in obj) {
var phrases = obj[key];
var rule = {};
rule.name = key;
rule.lower = key.toLowerCase();
rule.listName = key + '(' + phrases.length + ')';
rule.selectedPhrase = 0;
rule.phrases = [];
for (var i = 0; i < phrases.length; i += 1) {
var phrase = {}
phrase.beg = phrases[i].index;
phrase.end = phrases[i].index + phrases[i].length;
rule.phrases.push(phrase);
}
rules.push(rule);
}
rules.sort(alpha);
var html = '';
html += '<option selected>' + rules[0].listName + '</option>';
selectedRule = 0;
for (var i = 1; i < rules.length; i += 1) {
html += '<option>' + rules[i].listName + '</option>';
}
list.innerHTML = html;
}
function initRules(rules, udts) {
var html = '';
return html;
}
function checkedValue(elements) {
for (var i = 0; i < elements.length; i += 1) {
if (elements[i].checked) {
return elements[i].value;
}
}
return null;
}
// Initializes the generated parser.
//````
// src: is the the generated grammar text file, null if none
// obj: is the constructed grammar object, null if none
// msg: is the error message, displayed only of src & obj are null
//````
this.init = function(src, obj, msg) {
PAGE_INFO[PARSER_AREA].element.value = '<h4>No parser available.</h4>';
PAGE_INFO[STATE_PAGE].element.innerHTML = '<h4>No parser state available.</h4>';
PAGE_INFO[STATS_PAGE].element.innerHTML = '<h4>No parser statistics available.</h4>';
iframeWrite(PAGE_INFO[TRACE_FRAME], '<h4>No parser trace available.</h4>');
/* show/hide the trace config */
var traceOnOff = checkedValue(TRACE.ON.elements);
TRACE.TRACE_CONFIG.element.className = TRACE.TRACE_CONFIG.element.className.replace(" show", "");
TRACE.TRACE_CONFIG.element.className = TRACE.TRACE_CONFIG.element.className.replace(" hide", "");
TRACE.TRACE_CONFIG.element.className += (traceOnOff === "on") ? " show": " hide";
if (src && obj) {
grammar = obj;
PAGE_INFO[PARSER_AREA].element.value = src;
function rulerows(rules, checked){
var cols = 5;
var rows = rules.length;
var remainder = rows % cols;
var groups = (rows - remainder) /cols;
var i = 0;
var html = '';
for(var j = 0; j < groups; j += 1){
html += "<tr>";
for(var k = 0; k < cols; k += 1){
html += '<td><label><input type="checkbox" name="rules" value="'+rules[i].name+'" '+checked+'> '+rules[i].name+'</label></td>';
i += 1;
}
html += "</tr>";
}
if(remainder > 0){
html += "<tr>";
for(var k = 0; k < remainder; k += 1){
html += '<td><label><input type="checkbox" name="rules" value="'+rules[i].name+'" '+checked+'> '+rules[i].name+'</label></td>';
i += 1;
}
html += "</tr>";
}
return html;
}
function alpha(lhs, rhs){
if(lhs.lower < rhs.lower){
return -1;
}
if(lhs.lower > rhs.lower){
return 1;
}
return 0;
}
/* initialize the config rules/UDT items */
var checked = checkedValue(TRACE.RULES_ON.elements);
checked = (checked === "on") ? "checked" : "";
var list = grammar.rules.slice();
list.sort(alpha);
var html = rulerows(list, checked);
list = grammar.udts.slice();
list.sort(alpha);
html += rulerows(list, checked);
TRACE.RULES_TABLE.element.innerHTML = html;
return;
}
if (msg && typeof (msg) === "string") {
PAGE_INFO[PARSER_AREA].element.value = msg;
grammar = null;
return;
}
}
this.setOperators = function(button){
var checked = (button.value === "on") ? true : false;
TRACE.OPERATORS.elements.forEach(function(element) {
element.checked = checked;
});
}
this.setRules= function(button){
var checked = (button.value === "on") ? true : false;
var elements = document.getElementsByName('rules');
elements.forEach(function(element) {
element.checked = checked;
});
}
this.traceOnOff = function(button){
TRACE.TRACE_CONFIG.element.className = TRACE.TRACE_CONFIG.element.className.replace(" show", "");
TRACE.TRACE_CONFIG.element.className = TRACE.TRACE_CONFIG.element.className.replace(" hide", "");
TRACE.TRACE_CONFIG.element.className += (button.value === "on") ? " show": " hide";
}
this.openModal = function(){
MODAL_INFO.elements[MODAL_DIALOG].style.display = "block";
MODAL_INFO.elements[MODAL_TOP].innerHTML = "Looks like your data is base 64.<br>Which data format does it represent?";
var sel = "";
sel += '<a href="javascript:void(0)" onclick="apgweb.parser.closeModal(\'uint7\')">🞇</a> UINT7 (ASCII)<br>';
sel += '<a href="javascript:void(0)" onclick="apgweb.parser.closeModal(\'uint8\')">🞇</a> UINT8 (BINARY)<br>';
sel += '<a href="javascript:void(0)" onclick="apgweb.parser.closeModal(\'uint16be\')">🞇</a> UINT16BE<br>';
sel += '<a href="javascript:void(0)" onclick="apgweb.parser.closeModal(\'uint16le\')">🞇</a> UINT16LE<br>';
sel += '<a href="javascript:void(0)" onclick="apgweb.parser.closeModal(\'uint32be\')">🞇</a> UINT32BE<br>';
sel += '<a href="javascript:void(0)" onclick="apgweb.parser.closeModal(\'uint32le\')">🞇</a> UINT32LE<br>';
sel += '<a href="javascript:void(0)" onclick="apgweb.parser.closeModal(\'utf8\')">🞇</a> UTF-8<br>';
sel += '<a href="javascript:void(0)" onclick="apgweb.parser.closeModal(\'utf16\')">🞇</a> UTF-16<br>';
sel += '<a href="javascript:void(0)" onclick="apgweb.parser.closeModal(\'utf16be\')">🞇</a> UTF-16BE<br>';
sel += '<a href="javascript:void(0)" onclick="apgweb.parser.closeModal(\'utf16le\')">🞇</a> UTF-16LE<br>';
sel += '<a href="javascript:void(0)" onclick="apgweb.parser.closeModal(\'utf32\')">🞇</a> UTF-32<br>';
sel += '<a href="javascript:void(0)" onclick="apgweb.parser.closeModal(\'utf32be\')">🞇</a> UTF-32BE<br>';
sel += '<a href="javascript:void(0)" onclick="apgweb.parser.closeModal(\'utf32le\')">🞇</a> UTF-32LE<br>';
sel += '<a href="javascript:void(0)" onclick="apgweb.parser.closeModal(\'none\')">🞇</a> none - just parse it as is<br>';
sel += '<a href="javascript:void(0)" onclick="apgweb.parser.closeModal(\'cancel\')">🞇 cancel</a>';
MODAL_INFO.elements[MODAL_CONTENT].innerHTML = sel;
}
this.closeModal = function(format){
MODAL_INFO.elements[MODAL_DIALOG].style.display = "none";
if(!format || format === "cancel"){
return;
}
if(format === "none"){
this.parseInput('ascii');
return;
}
try{
var type = "base64:" + format;
var dst = converter.convert(type, PAGE_INFO[INPUT_AREA].element.value, "ESCAPED");
PAGE_INFO[INPUT_AREA].element.value = dst.toString("ascii");
this.parseInput('escaped');
}catch(e){
var msg = '<p class="apg-error">Unable to perform requested base 64 conversion.</p>';
msg += '<p>' + e.message + '</p>';
iframeWrite(PAGE_INFO[PHRASES_FRAME], msg);
this.openTab(TAB_PHRASES);
}
}
this.parseInput = function(mode) {
function getConfig(){
var config = {};
config.inputMode = checkedValue(INPUT_MODE.elements);
config.trace = (checkedValue(TRACE.ON.elements) === "on");
config.rules = {};
if(config.trace){
config.traceMode = checkedValue(TRACE.DISPLAY.elements);
config.traceRecords = {
max : parseInt(document.getElementById("trace-max-records").value, 10),
last : parseInt(document.getElementById("trace-last-record").value, 10),
}
if(isNaN(config.traceRecords.max) || config.traceRecords.max <= 0){
throw new Error("apgParser: configuration error: max trace records must be integer > 0");
}
if(isNaN(config.traceRecords.last)){
throw new Error("apgParser: configuration error: last trace record must be integer >= -1");
}
if(config.traceRecords.last <= 0 ){
config.traceRecords.last = -1;
}
config.operatorsOnOff = checkedValue(TRACE.OPERATORS_ON.elements);
config.operators = {};
TRACE.OPERATORS.elements.forEach(function(element) {
config.operators[element.value] = element.checked;
});
config.rulesOnOff = checkedValue(TRACE.RULES_ON.elements);
var elements = document.getElementsByName('rules');
elements.forEach(function(element){
config.rules[element.value] = element.checked;
});
}
return config;
}
function isBase64(str){
function isChar(char){
if (char >= 65 && char <= 90) {
return true;
}
if (char >= 97 && char <= 122) {
return true;
}
if (char >= 48 && char <= 57) {
return true;
}
if (char === 43) {
return true;
}
if (char === 47) {
return true;
}
return false;
}
var TAIL = 61;
if(str.length >= 4){
for(var i = 0; i < str.length - 2; i += 1){
if(!isChar(str.charCodeAt(i))){
return false;
}
}
var ct1 = str.charCodeAt(str.length-2);
var ct2 = str.charCodeAt(str.length-1);
if(isChar(ct1)){
if(isChar(ct2)){
return true;
}
if(ct2 === TAIL){
return true;
}
}
if(ct1 === TAIL && ct2 === TAIL){
return true;
}
}
return false;
}
function isEscaped(str){
for(var i = 0; i < str.length - 1; i += 1){
var ii = i + 1;
if(str.charCodeAt(i) === 96){
if(str.charCodeAt(i) === 96){
return true;
}
if(str.charCodeAt(i) === 120){
return true;
}
if(str.charCodeAt(i) === 117){
return true;
}
}
}
return false;
}
rules.length = 0;
input = null;
try {
/* validate the grammar */
if (grammar === null) {
throw new Error("apgParser: Parser has not been initialized.")
}
if (grammar.grammarObject !== "grammarObject") {
throw new Error("apgParser: Parser object not recognized.")
}
if (grammar.udts.length > 0) {
throw new Error("apgParser: Grammar has User Defined Terminals. Unable to parse input.")
}
/* read fixed config item values */
var config = getConfig();
/* validate the input */
var useMode = "auto";
if(!mode || mode === "init"){
useMode = config.inputMode;
}else if(mode === "cancel"){
return;
}else{
useMode = mode;
}
if(useMode === "auto"){
if(isBase64(PAGE_INFO[INPUT_AREA].element.value)){
this.openModal();
return;
}
if(isEscaped(PAGE_INFO[INPUT_AREA].element.value)){
useMode = "escaped";
}
}
input = new apgInput(PAGE_INFO[INPUT_AREA].element.value, useMode);
if (input.invalidChars || input.invalidEscapes) {
iframeWrite(PAGE_INFO[PHRASES_FRAME], input.displayInput());
this.openTab(TAB_PHRASES);
return;
}
/* set up the parser */
parser = new apglib.parser();
/* add statistics gathering */
parser.stats = new apglib.stats();
/* AST for display of matched phrases */
parser.ast = new apglib.ast();
/* configure trace, if any */
if (config.trace) {
parser.trace = new apglib.trace();
for(var key in config.operators){
parser.trace.filter.operators[key] = config.operators[key];
}
for(var key in config.rules){
parser.trace.filter.rules[key] = config.rules[key];
}
parser.trace.setMaxRecords(config.traceRecords.max, config.traceRecords.last);
for(var key in config.rules){
parser.ast.callbacks[key] = config.rules[key];
}
}else{
grammar.rules.forEach(function(rule){
parser.ast.callbacks[rule.name] = true;
});
}
/* configure AST for all rule/UDT names */
startRule = 0;
result = parser.parse(grammar, startRule, input.pchars);
PAGE_INFO[STATE_PAGE].element.innerHTML = apglib.utils.parserResultToHtml(result, "Parser State");
PAGE_INFO[STATS_PAGE].element.innerHTML = parser.stats.toHtml("hits", "Parser Statistics");
if (config.trace) {
iframeWrite(PAGE_INFO[TRACE_FRAME], parser.trace.toHtml(config.traceMode));
}
if (result.success) {
/* populate phrase names */
ruleDropDown(parser.ast.phrases());
this.displayPhrase(null, 1);
this.openTab(TAB_PHRASES);
} else {
ruleDropDown();
iframeWrite(PAGE_INFO[PHRASES_FRAME], input.displayInput());
this.openTab(TAB_STATE);
}
} catch (e) {
var msg = '<span class="apg-error"><h4>Parser Exception:</h4>' + e.message;
if (input !== null) {
msg += "<br><br>";
msg += input.displayInput();
}
iframeWrite(PAGE_INFO[PHRASES_FRAME], msg);
this.openTab(TAB_PHRASES);
}
}
this.phraseChange = function(event) {
if (input === null || rules.length === 0) {
return;
}
var options = document.getElementById("ast-phrases").options;
for (var i = 0; i < options.length; i += 1) {
if (options[i].selected) {
selectedRule = i;
rules[selectedRule].selectedPhrase = 0;
break;
}
}
this.displayPhrase(null, DISPLAY_FIRST);
}
this.displayPhrase = function(event, type) {
if(event){
event.currentTarget.blur();
}
if (input === null || rules.length === 0) {
return;
}
var rule = rules[selectedRule];
var phrases = [];
switch (type) {
case DISPLAY_FIRST:
rule.selectedPhrase = 0;
phrases[0] = rule.phrases[rule.selectedPhrase];
break;
case DISPLAY_PREV:
if (rule.selectedPhrase > 0) {
rule.selectedPhrase -= 1;
}
phrases[0] = rule.phrases[rule.selectedPhrase];
break;
case DISPLAY_NEXT:
if (rule.phrases.length === 0) {
rule.selectedPhrase = 0;
} else if (rule.selectedPhrase < rule.phrases.length - 1) {
rule.selectedPhrase += 1;
}
phrases[0] = rule.phrases[rule.selectedPhrase];
break;
case DISPLAY_LAST:
rule.selectedPhrase = rule.phrases.length > 0 ? rule.phrases.length - 1 : 0;
phrases[0] = rule.phrases[rule.selectedPhrase];
break;
default:
case DISPLAY_ALL:
rule.selectedPhrase = 0;
phrases = rule.phrases.slice();
break;
}
iframeWrite(PAGE_INFO[PHRASES_FRAME], input.displayPhrases(phrases));
}
this.openTab = function(tabnumber) {
var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("parser-tabcontent");
tablinks = document.getElementsByClassName("parser-tablinks");
/* hide all content */
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].className = tabcontent[i].className.replace(" show", "");
tabcontent[i].className = tabcontent[i].className.replace(" hide", "");
tabcontent[i].className += " hide";
}
/* deactivate all menu links */
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
/* activate menu item (changes its color) */
var tab = tablinks[tabnumber];
if (tab) {
tab.className += " active";
}
/* show the activated content */
var content = tabcontent[tabnumber];
if (content) {
content.className = content.className.replace(" hide", "");
content.className += " show";
}
currentTab = tabnumber;
return false;
}
}
// Constructor for the attributes object. Handles display and sorting of the rule attributes.
var apgAttrs = function() {
var DOWN = 1;
var UP = 2;
var CHECKED_ID = "attr-errors-top";
var COL_NAMES = [ {
DOWN : "index↓",
UP : "index↑",
NONE : "index "
}, {
DOWN : "rule↓",
UP : "rule↑",
NONE : "rule "
}, {
DOWN : "type↓",
UP : "type↑",
NONE : "type "
}, {
DOWN : "left↓",
UP : "left↑",
NONE : "left "
}, {
DOWN : "nested↓",
UP : "nested↑",
NONE : "nested "
}, {
DOWN : "right↓",
UP : "right↑",
NONE : "right "
}, {
DOWN : "cyclic↓",
UP : "cyclic↑",
NONE : "cyclic "
}, {
DOWN : "finite↓",
UP : "finite↑",
NONE : "finite "
}, {
DOWN : "empty↓",
UP : "empty↑",
NONE : "empty "
}, {
DOWN : "not empty↓",
UP : "not empty↑",
NONE : "not empty "
} ];
var style = require('apg-lib').style;
var sortState = {};
var colNames = [];
var noDataMsg = "<h4>Attributes not initialized.</h4>";
var self = this;
self.rulesData = null;
self.udtsData = null;
self.msg = noDataMsg;
function sortIndex(sortDir, errors, others, udts) {
function up(lhs, rhs) {
return rhs.index - lhs.index;
}
function down(lhs, rhs) {
return lhs.index - rhs.index;
}
if (sortDir === DOWN) {
errors.sort(down);
others.sort(down);
udts.sort(down);
} else {
errors.sort(up);
others.sort(up);
udts.sort(up);
}
}
function sortRules(sortDir, errors, others, udts) {
function up(lhs, rhs) {
if (lhs.lower > rhs.lower) {
return -1;
}
if (lhs.lower < rhs.lower) {
return 1;
}
return 0;
}
function down(lhs, rhs) {
if (lhs.lower < rhs.lower) {
return -1;
}
if (lhs.lower > rhs.lower) {
return 1;
}
return 0;
}
if (sortDir === DOWN) {
errors.sort(down);
others.sort(down);
udts.sort(down);
} else {
errors.sort(up);
others.sort(up);
udts.sort(up);
}
}
function sortTypes(sortDir, errors, others, udts) {
function up(lhs, rhs) {
return rhs.type - lhs.type;
}
function down(lhs, rhs) {
return lhs.type - rhs.type;
}
if (sortDir === DOWN) {
errors.sort(down);
others.sort(down);
udts.sort(down);
} else {
errors.sort(up);
others.sort(up);
udts.sort(up);
}
}
function sortAttrs(sortDir, errors, others, udts, col) {
function up(lhs, rhs) {
if ((lhs[col] === true) && (rhs[col] === false)) {
return -1;
}
if ((lhs[col] === false) && (rhs[col] === true)) {
return 1;
}
return 0;
}
function down(lhs, rhs) {
if ((lhs[col] === true) && (rhs[col] === false)) {
return 1;
}
if ((lhs[col] === false) && (rhs[col] === true)) {
return -1;
}
return 0;
}
if (sortDir === DOWN) {
errors.sort(down);
others.sort(down);
udts.sort(down);
} else {
errors.sort(up);
others.sort(up);
udts.sort(up);
}
}
/* Generates and displays the attribute table. */
var tableGen = function(errors, others) {
function yes(val) {
return val ? "yes" : "no";
}
function ruleGen(row) {
var html = "";
var left, cyclic, finite;
var left = row.left ? '<span class="apg-error">' + yes(row.left) + '</span>' : yes(row.left);
var cyclic = row.cyclic ? '<span class="apg-error">' + yes(row.cyclic) + '</span>' : yes(row.cyclic);
var finite = row.finite ? yes(row.finite) : '<span class="apg-error">' + yes(row.finite) + '</span>';
html += '<tr>';
html += '<td>' + row.index + '</td>';
html += '<td>' + row.name + '</td>';
html += '<td>' + row.typename + '</td>';
html += '<td>' + left + '</td>';
html += '<td>' + yes(row.nested) + '</td>';
html += '<td>' + yes(row.right) + '</td>';
html += '<td>' + cyclic + '</td>';
html += '<td>' + finite + '</td>';
html += '<td>' + yes(row.empty) + '</td>';
html += '<td>' + yes(row.notempty) + '</td>';
html += '</tr>';
return html;
}
var html = "";
if (sortState.hasErrors) {
var checked = sortState.topErrors ? "checked" : "";
html += '<input type="checkbox" class="align-middle" id="' + CHECKED_ID + '" ' + checked + '> keep rules with attribute errors at top';
}
html += '<table class="' + style.CLASS_ATTRIBUTES + '">';
html += ' <caption>Attributes</caption>'
html += '<tr>';
html += '<th><a class="' + style.CLASS_RULESLINK + '" href="javascript:void(0)" onclick="apgweb.attrs.sort(0)">' + colNames[0] + '</a></th>';
html += '<th><a class="' + style.CLASS_RULESLINK + '" href="javascript:void(0)" onclick="apgweb.attrs.sort(1)">' + colNames[1] + '</a></th>';
html += '<th><a class="' + style.CLASS_RULESLINK + '" href="javascript:void(0)" onclick="apgweb.attrs.sort(2)">' + colNames[2] + '</a></th>';
html += '<th><a class="' + style.CLASS_RULESLINK + '" href="javascript:void(0)" onclick="apgweb.attrs.sort(3)">' + colNames[3] + '</a></th>';
html += '<th><a class="' + style.CLASS_RULESLINK + '" href="javascript:void(0)" onclick="apgweb.attrs.sort(4)">' + colNames[4] + '</a></th>';
html += '<th><a class="' + style.CLASS_RULESLINK + '" href="javascript:void(0)" onclick="apgweb.attrs.sort(5)">' + colNames[5] + '</a></th>';
html += '<th><a class="' + style.CLASS_RULESLINK + '" href="javascript:void(0)" onclick="apgweb.attrs.sort(6)">' + colNames[6] + '</a></th>';
html += '<th><a class="' + style.CLASS_RULESLINK + '" href="javascript:void(0)" onclick="apgweb.attrs.sort(7)">' + colNames[7] + '</a></th>';
html += '<th><a class="' + style.CLASS_RULESLINK + '" href="javascript:void(0)" onclick="apgweb.attrs.sort(8)">' + colNames[8] + '</a></th>';
html += '<th><a class="' + style.CLASS_RULESLINK + '" href="javascript:void(0)" onclick="apgweb.attrs.sort(9)">' + colNames[9] + '</a></th>';
html += '</tr>';
errors.forEach(function(row) {
html += ruleGen(row);
});
others.forEach(function(row) {
html += ruleGen(row);
});
self.udtsData.forEach(function(row) {
html += ruleGen(row);
});
html += "</table>";
return html;
}
/* Initializes the attributes table state data for a descending sort on the index column. */
function initSort(col) {
if (self.rulesData === null) {
return;
}
for (var i = 0; i < COL_NAMES.length; i += 1) {
colNames[i] = COL_NAMES[i].NONE;
}
/* initialize to default sort state */
sortState.hasErrors = false;
for (var i = 0; i < self.rulesData.length; i += 1) {
if (self.rulesData[i].error) {
sortState.hasErrors = true;
break;
}
}
sortState.topErrors = true;
sortState.col = 0;
sortState.dir = UP;
colNames[sortState.col] = COL_NAMES[sortState.col].UP;
}
/* Called by the parser generator to initialize the attributes table, if any. */
/* Displays either the table or an error message on the attributes page. */
this.init = function(rules, udts, msg) {
self.rulesData = rules;
self.udtsData = udts;
if (msg && typeof (msg) === "string") {
self.msg = msg
} else if (self.rulesData === null) {
self.msg = noDataMsg;
} else {
self.msg = "";
}
initSort(0);
self.sort(0);
}
/* Event handler for the attribute table column header sorting anchors. */
this.sort = function(col) {
if (self.rulesData === null) {
PAGE_INFO[ATTRS_PAGE].element.innerHTML = self.msg;
return false;
}
var errorRules = [];
var otherRules = [];
for (var i = 0; i < COL_NAMES.length; i += 1) {
colNames[i] = COL_NAMES[i].NONE;
}
var el = document.getElementById(CHECKED_ID);
if (el !== null) {
sortState.topErrors = el.checked;
}
if (col === sortState.col) {
sortState.dir = sortState.dir === DOWN ? UP : DOWN;
} else {
sortState.col = col;
sortState.dir = DOWN;
}
colNames[col] = sortState.dir === DOWN ? COL_NAMES[col].DOWN : COL_NAMES[col].UP;
for (var i = 0; i < self.rulesData.length; i += 1) {
if (sortState.topErrors && self.rulesData[i].error) {
errorRules.push(self.rulesData[i]);
} else {
otherRules.push(self.rulesData[i]);
}
}
switch (sortState.col) {
case 0:
sortIndex(sortState.dir, errorRules, otherRules, self.udtsData);
break;
case 1:
sortRules(sortState.dir, errorRules, otherRules, self.udtsData);
break;
case 2:
sortTypes(sortState.dir, errorRules, otherRules, self.udtsData);
break;
case 3:
sortAttrs(sortState.dir, errorRules, otherRules, self.udtsData, "left");
break;
case 4:
sortAttrs(sortState.dir, errorRules, otherRules, self.udtsData, "nested");
break;
case 5:
sortAttrs(sortState.dir, errorRules, otherRules, self.udtsData, "right");
break;
case 6:
sortAttrs(sortState.dir, errorRules, otherRules, self.udtsData, "cyclic");
break;
case 7:
sortAttrs(sortState.dir, errorRules, otherRules, self.udtsData, "finite");
break;
case 8:
sortAttrs(sortState.dir, errorRules, otherRules, self.udtsData, "empty");
break;
case 9:
sortAttrs(sortState.dir, errorRules, otherRules, self.udtsData, "notempty");
break;
}
PAGE_INFO[ATTRS_PAGE].element.innerHTML = tableGen(errorRules, otherRules, self.udtsData);
return false;
}
/* initialize the attributes page message */
this.init(null, null);
}
// Constuctor for the display and sorting of the rules/UDT information.
var apgRules = function() {
var INDEX_DOWN = "index\u2193";
var INDEX_UP = "index\u2191";
var INDEX_NONE = "index ";
var RULES_DOWN = "rule\u2193";
var RULES_UP = "rule\u2191";
var RULES_NONE = "rule ";
var ROW_SHOW = "show";
var ROW_HIDE = "hide";
var style = require('apg-lib').style;
var self = this;
self.rulesData = null;
self.udtsData = null;
self.msg = "";
self.dir = {
index : INDEX_DOWN,
rules : RULES_NONE
}
this.showAll = function(show) {
setRowShow(show);
return tableGen();
}
this.dependentsShow = function(event, index) {
var rule = self.rulesData.rows[index];
rule.show = (event.currentTarget.innerHTML === ROW_SHOW)
return tableGen();
}
this.sortIndex = function(event) {
self.dir.rules = RULES_NONE;
function up(lhs, rhs) {
return rhs.index - lhs.index;
}
function down(lhs, rhs) {
return lhs.index - rhs.index;
}
if (event.currentTarget.innerHTML === INDEX_DOWN) {
/* sort up */
self.rulesData.rows.sort(up);
self.udtsData.sort(up);
self.dir.index = INDEX_UP;
} else {
/* sort down */
self.rulesData.rows.sort(down);
self.udtsData.sort(down);
self.dir.index = INDEX_DOWN;
}
return tableGen();
}
this.sortName = function(event) {
self.dir.index = INDEX_NONE;
function up(lhs, rhs) {
if (lhs.lower > rhs.lower) {
return -1;
}
if (lhs.lower < rhs.lower) {
return 1;
}
return 0;
}
function down(lhs, rhs) {
if (lhs.lower < rhs.lower) {
return -1;
}
if (lhs.lower > rhs.lower) {
return 1;
}
return 0;
}
if (event.currentTarget.innerHTML === RULES_DOWN) {
/* sort up */
self.rulesData.rows.sort(up);
self.udtsData.sort(up);
self.dir.rules = RULES_UP;
} else {
/* sort down */
self.rulesData.rows.sort(down);
self.udtsData.sort(down);
self.dir.rules = RULES_DOWN;
}
return tableGen();
}
this.init = function(rules, udts, msg) {
self.rulesData = rules;
self.udtsData = udts;
self.msg = (msg && typeof (msg) === "string") ? msg : "";
if (self.rulesData !== null) {
self.dir.index = INDEX_DOWN;
self.dir.rules = RULES_NONE;
setRowShow(true);
}
tableGen();
}
/* Generate the rules table HTML. */
var tableGen = function() {
if (self.rulesData === null) {
PAGE_INFO[RULES_PAGE].element.innerHTML = self.msg;
return false;
}
var html = "";
html += '<table class="' + style.CLASS_RULES + '">';
html += ' <caption>Rules</caption>'
html += '<tr><th><a class="' + style.CLASS_RULESLINK + '" href="javascript:void(0)" onclick="apgweb.rules.sortIndex(event)">' + self.dir.index
+ '</a></th>';
html += '<th><a class="' + style.CLASS_RULESLINK + '" href="javascript:void(0)" onclick="apgweb.rules.sortName(event)">' + self.dir.rules + '</a></th>';
html += '<th>refers to</th></tr>';
for (var i = 0; i < self.rulesData.rows.length; i += 1) {
var rule = self.rulesData.rows[i];
if (rule.dependents.length > 0) {
var link = rule.show ? ROW_HIDE : ROW_SHOW;
html += '<tr><td>' + rule.index + '</td><td>' + rule.name + '</td>';
html += '<td><a class="' + style.CLASS_RULESLINK + '" href="javascript:void(0)" onclick="apgweb.rules.dependentsShow(event,' + i + ')">' + link
+ '</a></td></tr>';
if (rule.show) {
for (var j = 0; j < rule.dependents.length; j += 1) {
var obj = rule.dependents[j];
html += '<tr><td></td><td>' + obj.index + '</td><td>' + obj.name + '</td></tr>';
}
}
} else {
html += '<tr><td>' + rule.index + '</td><td>' + rule.name + '</td><td></td></tr>';
}
}
for (var i = 0; i < self.udtsData.length; i += 1) {
html += '<tr><td>' + self.udtsData[i].index + '</td><td>' + self.udtsData[i].name + '</td><td></td></tr>';
}
html += '</table>';
PAGE_INFO[RULES_PAGE].element.innerHTML = html;
return false;
}
function setRowShow(show) {
if (self.rulesData !== null) {
self.rulesData.rows.forEach(function(rule) {
if (rule.dependents.length > 0) {
for (var i = 0; i < rule.dependents.length; i += 1) {
rule.show = show;
}
}
});
}
}
self.init(null, null, "<h4>Rules not initialized.</h4>");
}
// Constructor for the generator function. Handles the main logic for parser generation.
var apgWeb = function() {
var api = require("apg-api");
/* handle the main menu tabs */
this.openTab = function(tabnumber) {
var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("tabcontent");
tablinks = document.getElementsByClassName("tablinks");
/* hide all content */
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].className = tabcontent[i].className.replace(" show", "");
tabcontent[i].className = tabcontent[i].className.replace(" hide", "");
tabcontent[i].className += " hide";
}
/* deactivate all menu links */
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
/* activate menu item (changes its color) */
var tab = tablinks[tabnumber];
if (tab) {
tab.className += " active";
}
/* show the activated content */
var content = tabcontent[tabnumber];
if (content) {
content.className = content.className.replace(" hide", "");
content.className += " show";
}
return false;
}
this.select = function(name) {
var el = PAGE_INFO[name].element;
if (el && PAGE_INFO[name].type === TYPE_TEXTAREA) {
el.select();
try {
document.execCommand('copy');
el.blur()
} catch (e) {
alert("please press Ctrl/Cmd+C to copy");
}
}
}
this.fullScreen = function(event, name) {
event.currentTarget.blur();
var options = "";
options += "width=" + screen.availWidth + ",height=" + screen.availHeight + "";
options += ",top=0";
options += ",left=0";
options += ",location=no";
options += ",menubar=no";
options += ",scrollbars=yes";
options += ",status=no";
options += ",titlebar=no";
options += ",toolbar=no";
options += ",resizable=yes";
var css = require('apg-lib').emitcss();
var button = '<button style="color:black;background:#B3C1FF;" type="button" onclick="window.close()">Close</button>';
var html = '';
var el = PAGE_INFO[name].element;
if (el) {
html += '<html><head>';
switch (PAGE_INFO[name].type) {
case TYPE_TEXTAREA:
html += '<title>';
html += PAGE_INFO[nam