buffer-apg-js
Version:
JavaScript APG, an ABNF Parser Generator
781 lines (726 loc) • 21.2 kB
JavaScript
/* eslint-disable no-underscore-dangle */
/* eslint-disable guard-for-in */
/* eslint-disable no-restricted-syntax */
/* *************************************************************************************
* copyright: Copyright (c) 2021 Lowell D. Thomas, all rights reserved
* license: BSD-2-Clause (https://opensource.org/licenses/BSD-2-Clause)
* ********************************************************************************* */
// This module defines all of the display functions. Text and HTML displays of
// the grammar source, the result object and the `apg-exp` "last match" object.
'use strict;';
const apglib = require('../apg-lib/node-exports');
// const utils = apglib.utils;
// const style = apglib.style;
const { utils, style } = apglib;
const MODE_HEX = 16;
const MODE_DEC = 10;
const MODE_ASCII = 8;
const MODE_UNICODE = 32;
/* add style to HTML phrases */
const phraseStyle = function phraseStyle(phrase, styleArg) {
if (phrase === '') {
return `<span class="${style.CLASS_EMPTY}">𝜺</span>`;
}
if (phrase === undefined) {
return `<span class="${style.CLASS_REMAINDER}">undefined</span>`;
}
let classStyle = style.CLASS_REMAINDER;
if (typeof styleArg === 'string') {
if (styleArg.toLowerCase() === 'match') {
classStyle = style.CLASS_MATCH;
} else if (styleArg.toLowerCase() === 'nomatch') {
classStyle = style.CLASS_NOMATCH;
}
}
const chars = apglib.utils.stringToChars(phrase);
let html = `<span class="${classStyle}">`;
html += apglib.utils.charsToAsciiHtml(chars);
return `${html}</span>`;
};
/* result object - string phrases to ASCII text */
const sResultToText = function sResultToText(result) {
let txt = '';
txt += ' result:\n';
txt += ' [0]: ';
txt += result[0];
txt += '\n';
txt += ` input: ${result.input}`;
txt += '\n';
txt += ` index: ${result.index}`;
txt += '\n';
txt += ` length: ${result.length}`;
txt += '\n';
txt += `tree depth: ${result.treeDepth}`;
txt += '\n';
txt += ` node hits: ${result.nodeHits}`;
txt += '\n';
txt += ' rules: ';
let prefix = '';
const indent = ' : ';
const { rules } = result;
// eslint-disable-next-line no-restricted-syntax
// eslint-disable-next-line guard-for-in
for (const name in rules) {
const rule = rules[name];
if (rule) {
for (let i = 0; i < rule.length; i += 1) {
const ruleobj = rule[i];
txt += `${prefix + name} : ${ruleobj.index}: `;
txt += ruleobj.phrase;
txt += '\n';
prefix = indent;
}
} else {
txt += `${prefix + name}: `;
txt += 'undefined';
txt += '\n';
}
prefix = indent;
}
return txt;
};
/* result object - string to HTML text */
const sResultToHtml = function sResultToHtml(result) {
let html = '';
const caption = 'result:';
html += `<table class="${style.CLASS_STATE}">\n`;
html += `<caption>${caption}</caption>\n`;
html += '<tr>';
html += '<th>item</th><th>value</th><th>phrase</th>';
html += '</tr>\n';
html += '<tr>';
html += '<td>[0]</td>';
html += `<td>${result.index}</td>`;
html += `<td>${phraseStyle(result[0], 'match')}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>input</td>';
html += '<td>0</td>';
html += `<td>${phraseStyle(result.input)}</td>`;
html += '</tr>\n';
html += '<tr>';
html += `<td>index</td><td>${result.index}</td>`;
html += '<td></td>';
html += '</tr>\n';
html += '<tr>';
html += `<td>length</td><td>${result.length}</td>`;
html += '<td></td>';
html += '</tr>\n';
html += '<tr>';
html += `<td>tree depth</td><td>${result.treeDepth}</td>`;
html += '<td></td>';
html += '</tr>\n';
html += '<tr>';
html += `<td>node hits</td><td>${result.nodeHits}</td>`;
html += '<td></td>';
html += '</tr>\n';
html += '<tr>';
html += '<th>rules</th><th>index</th><th>phrase</th>';
html += '</tr>\n';
const { rules } = result;
for (const name in rules) {
const rule = rules[name];
if (rule) {
for (let i = 0; i < rule.length; i += 1) {
const ruleobj = rule[i];
html += '<tr>';
html += `<td>${name}</td>`;
html += `<td>${ruleobj.index}</td>`;
html += `<td>${phraseStyle(ruleobj.phrase, 'match')}</td>`;
html += '\n';
}
} else {
html += '<tr>';
html += `<td>${name}</td>`;
html += '<td></td>';
html += `<td>${phraseStyle(undefined)}</td>`;
html += '\n';
}
}
html += '</table>\n';
return html;
};
/* result object - string to HTML page */
const sResultToHtmlPage = function sResultToHtmlPage(result) {
return utils.htmlToPage(sResultToHtml(result), 'apg-exp result');
};
/* apg-exp object - string to ASCII text */
const sLastMatchToText = function sLastMatchToText(exp) {
let txt = '';
txt += ' last match:\n';
txt += ' lastIndex: ';
txt += exp.lastIndex;
txt += '\n';
txt += ' flags: "';
txt += `${exp.flags}"`;
txt += '\n';
txt += ' global: ';
txt += exp.global;
txt += '\n';
txt += ' sticky: ';
txt += exp.sticky;
txt += '\n';
txt += ' unicode: ';
txt += exp.unicode;
txt += '\n';
txt += ' debug: ';
txt += exp.debug;
txt += '\n';
if (exp['$&'] === undefined) {
txt += ' last match: undefined';
txt += '\n';
return txt;
}
txt += ' input: ';
txt += exp.input;
txt += '\n';
txt += ' leftContext: ';
txt += exp.leftContext;
txt += '\n';
txt += ' lastMatch: ';
txt += exp.lastMatch;
txt += '\n';
txt += 'rightContext: ';
txt += exp.rightContext;
txt += '\n';
txt += ' rules: ';
let prefix = '';
const indent = ' : ';
for (const name in exp.rules) {
txt += `${prefix + name} : `;
txt += exp.rules[name];
txt += '\n';
prefix = indent;
}
txt += '\n';
txt += 'alias:\n';
txt += ' ["$_"]: ';
// eslint-disable-next-line no-underscore-dangle
txt += exp.$_;
txt += '\n';
txt += ' ["$`"]: ';
txt += exp['$`'];
txt += '\n';
txt += ' ["$&"]: ';
txt += exp['$&'];
txt += '\n';
txt += ' ["$\'"]: ';
txt += exp["$'"];
txt += '\n';
for (const name in exp.rules) {
txt += ` ["\${${name}}"]: `;
txt += exp[`\${${name}}`];
txt += '\n';
}
return txt;
};
/* apg-exp object - string to HTML text */
const sLastMatchToHtml = function sLastMatchToHtml(exp) {
let html = '';
const caption = 'last match:';
html += `<table class="${style.CLASS_STATE}">\n`;
html += `<caption>${caption}</caption>\n`;
html += '<tr>';
html += '<th>item</th><th>value</th>';
html += '</tr>\n';
html += '<tr>';
html += '<td>lastIndex</td>';
html += `<td>${exp.lastIndex}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>flags</td>';
html += `<td>"${exp.flags}"</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>global</td>';
html += `<td>${exp.global}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>sticky</td>';
html += `<td>${exp.sticky}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>unicode</td>';
html += `<td>${exp.unicode}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>debug</td>';
html += `<td>${exp.debug}</td>`;
html += '</tr>\n';
if (exp['$&'] === undefined) {
html += '<tr>';
html += '<td>lastMatch</td>';
html += `<td>${phraseStyle(undefined)}</td>`;
html += '</tr>\n';
html += '</table>\n';
return html;
}
html += '<th>item</th><th>phrase</th>';
html += '</tr>\n';
html += '<tr>';
html += '<td>input</td>';
html += `<td>${phraseStyle(exp.input)}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>leftContext</td>';
html += `<td>${phraseStyle(exp.leftContext)}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>lastMatch</td>';
html += `<td>${phraseStyle(exp.lastMatch, 'match')}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>rightContext</td>';
html += `<td>${phraseStyle(exp.rightContext)}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<th>rule</th><th>phrase</th>';
html += '</tr>\n';
for (const name in exp.rules) {
html += '<tr>';
html += `<td>${name}</td>`;
html += `<td>${phraseStyle(exp.rules[name])}</td>`;
html += '</tr>\n';
}
html += '<tr>';
html += '<th>alias</th><th>phrase</th>';
html += '</tr>\n';
html += '<tr>';
html += '<td>["$_"]</td>';
// eslint-disable-next-line no-underscore-dangle
html += `<td>${phraseStyle(exp.$_)}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>["$`"]</td>';
html += `<td>${phraseStyle(exp['$`'])}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>["$&"]</td>';
html += `<td>${phraseStyle(exp['$&'], 'match')}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>["$\'"]</td>';
html += `<td>${phraseStyle(exp["$'"])}</td>`;
html += '</tr>\n';
for (const name in exp.rules) {
html += '<tr>';
html += `<td>["\${${name}}"]</td>`;
html += `<td>${phraseStyle(exp[`\${${name}}`])}</td>`;
html += '</tr>\n';
}
html += '</table>\n';
return html;
};
/* apg-exp object - string to HTML page */
const sLastMatchToHtmlPage = function sLastMatchToHtmlPage(exp) {
return utils.htmlToPage(sLastMatchToHtml(exp), 'apg-exp last result');
};
/* translates ASCII string to integer mode identifier - defaults to ASCII */
const getMode = function getMode(modearg) {
let mode = MODE_ASCII;
if (typeof modearg === 'string' && modearg.length >= 3) {
const modein = modearg.toLowerCase().slice(0, 3);
if (modein === 'hex') {
mode = MODE_HEX;
} else if (modein === 'dec') {
mode = MODE_DEC;
} else if (modein === 'uni') {
mode = MODE_UNICODE;
}
}
return mode;
};
/* translate integer mode identifier to standard text string */
const modeToText = function modeToText(mode) {
let txt;
switch (mode) {
case MODE_ASCII:
txt = 'ascii';
break;
case MODE_HEX:
txt = 'hexadecimal';
break;
case MODE_DEC:
txt = 'decimal';
break;
case MODE_UNICODE:
txt = 'Unicode';
break;
default:
throw new Error('recognized mode');
}
return txt;
};
/* convert integer to hex with leading 0 if necessary */
const charToHex = function charToHex(char) {
let ch = char.toString(16);
if (ch.length % 2 !== 0) {
ch = `0${ch}`;
}
return ch;
};
/* convert integer character code array to formatted text string */
const charsToMode = function charsToMode(chars, mode) {
let txt = '';
if (mode === MODE_ASCII) {
txt += apglib.utils.charsToString(chars);
} else if (mode === MODE_DEC) {
txt += '[';
if (chars.length > 0) {
txt += chars[0];
for (let i = 1; i < chars.length; i += 1) {
txt += `,${chars[i]}`;
}
}
txt += ']';
} else if (mode === MODE_HEX) {
txt += '[';
if (chars.length > 0) {
txt += `\\x${charToHex(chars[0])}`;
for (let i = 1; i < chars.length; i += 1) {
txt += `,\\x${charToHex(chars[i])}`;
}
}
txt += ']';
} else if (mode === MODE_UNICODE) {
txt += '[';
if (chars.length > 0) {
txt += `\\u${charToHex(chars[0])}`;
for (let i = 1; i < chars.length; i += 1) {
txt += `,\\u${charToHex(chars[i])}`;
}
}
txt += ']';
}
return txt;
};
/* result object - Unicode mode to ASCII text */
const uResultToText = function uResultToText(result, modeArg) {
const mode = getMode(modeArg);
let txt = '';
txt += ` result(${modeToText(mode)})\n`;
txt += ' [0]: ';
txt += charsToMode(result[0], mode);
txt += '\n';
txt += ` input: ${charsToMode(result.input, mode)}`;
txt += '\n';
txt += ` index: ${result.index}`;
txt += '\n';
txt += ` length: ${result.length}`;
txt += '\n';
txt += `tree depth: ${result.treeDepth}`;
txt += '\n';
txt += ` node hits: ${result.nodeHits}`;
txt += '\n';
txt += ' rules: ';
txt += '\n';
const { rules } = result;
for (const name in rules) {
const rule = rules[name];
if (rule) {
for (let i = 0; i < rule.length; i += 1) {
const ruleobj = rule[i];
txt += ` :${name} : ${ruleobj.index}: `;
txt += charsToMode(ruleobj.phrase, mode);
txt += '\n';
}
} else {
txt += ` :${name}: `;
txt += 'undefined';
txt += '\n';
}
}
return txt;
};
/* result object - Unicode mode to HTML text */
const uResultToHtml = function uResultToHtml(result, modeArg) {
const mode = getMode(modeArg);
let html = '';
let caption = 'result:';
caption += `(${modeToText(mode)})`;
html += `<table class="${style.CLASS_STATE}">\n`;
html += `<caption>${caption}</caption>\n`;
html += '<tr>';
html += '<th>item</th><th>value</th><th>phrase</th>';
html += '</tr>\n';
html += '<tr>';
html += '<td>[0]</td>';
html += `<td>${result.index}</td>`;
html += `<td>${phraseStyle(charsToMode(result[0], mode), 'match')}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>input</td>';
html += '<td>0</td>';
html += `<td>${phraseStyle(charsToMode(result.input, mode))}</td>`;
html += '</tr>\n';
html += '<tr>';
html += `<td>index</td><td>${result.index}</td>`;
html += '<td></td>';
html += '</tr>\n';
html += '<tr>';
html += `<td>length</td><td>${result.length}</td>`;
html += '<td></td>';
html += '</tr>\n';
html += '<tr>';
html += `<td>tree depth</td><td>${result.treeDepth}</td>`;
html += '<td></td>';
html += '</tr>\n';
html += '<tr>';
html += `<td>node hits</td><td>${result.nodeHits}</td>`;
html += '<td></td>';
html += '</tr>\n';
html += '<tr>';
html += '<th>rules</th><th>index</th><th>phrase</th>';
html += '</tr>\n';
const { rules } = result;
for (const name in rules) {
const rule = rules[name];
if (rule) {
for (let i = 0; i < rule.length; i += 1) {
const ruleobj = rule[i];
html += '<tr>';
html += `<td>${name}</td>`;
html += `<td>${ruleobj.index}</td>`;
html += `<td>${phraseStyle(charsToMode(ruleobj.phrase, mode), 'match')}</td>`;
html += '\n';
}
} else {
html += '<tr>';
html += `<td>${name}</td>`;
html += '<td></td>';
html += `<td>${phraseStyle(undefined)}</td>`;
html += '\n';
}
}
html += '</table>\n';
return html;
};
/* result object - Unicode mode to HTML page */
const uResultToHtmlPage = function uResultToHtmlPage(result, mode) {
return utils.htmlToPage(uResultToHtml(result, mode));
};
/* apg-exp object - Unicode mode to ASCII text */
const uLastMatchToText = function uLastMatchToText(exp, modeArg) {
const mode = getMode(modeArg);
let txt = '';
txt += ` last match(${modeToText(mode)})\n`;
txt += ` lastIndex: ${exp.lastIndex}`;
txt += '\n';
txt += ` flags: "${exp.flags}"`;
txt += '\n';
txt += ` global: ${exp.global}`;
txt += '\n';
txt += ` sticky: ${exp.sticky}`;
txt += '\n';
txt += ` unicode: ${exp.unicode}`;
txt += '\n';
txt += ` debug: ${exp.debug}`;
txt += '\n';
if (exp['$&'] === undefined) {
txt += ' lastMatch: undefined';
txt += '\n';
return txt;
}
txt += ' input: ';
txt += charsToMode(exp.input, mode);
txt += '\n';
txt += ' leftContext: ';
txt += charsToMode(exp.leftContext, mode);
txt += '\n';
txt += ' lastMatch: ';
txt += charsToMode(exp.lastMatch, mode);
txt += '\n';
txt += 'rightContext: ';
txt += charsToMode(exp.rightContext, mode);
txt += '\n';
txt += ' rules:';
let prefix = '';
const indent = ' :';
for (const name in exp.rules) {
txt += `${prefix + name} : `;
txt += exp.rules[name] ? charsToMode(exp.rules[name], mode) : 'undefined';
txt += '\n';
prefix = indent;
}
txt += '\n';
txt += ' alias:\n';
txt += ' ["$_"]: ';
txt += charsToMode(exp.$_, mode);
txt += '\n';
txt += ' ["$`"]: ';
txt += charsToMode(exp['$`'], mode);
txt += '\n';
txt += ' ["$&"]: ';
txt += charsToMode(exp['$&'], mode);
txt += '\n';
txt += ' ["$\'"]: ';
txt += charsToMode(exp["$'"], mode);
txt += '\n';
for (const name in exp.rules) {
txt += ` ["\${${name}}"]: `;
txt += exp[`\${${name}}`] ? charsToMode(exp[`\${${name}}`], mode) : 'undefined';
txt += '\n';
}
return txt;
};
/* apg-exp object - Unicode mode to HTML text */
const uLastMatchToHtml = function uLastMatchToHtml(exp, modeArg) {
const mode = getMode(modeArg);
let html = '';
let caption = 'last match:';
caption += `(${modeToText(mode)})`;
html += `<table class="${style.CLASS_STATE}">\n`;
html += `<caption>${caption}</caption>\n`;
html += '<tr>';
html += '<th>item</th><th>value</th>';
html += '</tr>\n';
html += '<tr>';
html += '<td>lastIndex</td>';
html += `<td>${exp.lastIndex}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>flags</td>';
html += `<td>"${exp.flags}"</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>global</td>';
html += `<td>${exp.global}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>sticky</td>';
html += `<td>${exp.sticky}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>unicode</td>';
html += `<td>${exp.unicode}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>debug</td>';
html += `<td>${exp.debug}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<th>item</th><th>phrase</th>';
html += '</tr>\n';
if (exp['$&'] === undefined) {
html += '<tr>';
html += '<td>lastMatch</td>';
html += `<td>${phraseStyle(undefined)}</td>`;
html += '</tr>\n';
html += '</table>\n';
return html;
}
html += '<tr>';
html += '<td>input</td>';
html += `<td>${phraseStyle(charsToMode(exp.input, mode))}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>leftContext</td>';
html += `<td>${phraseStyle(charsToMode(exp.leftContext, mode))}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>lastMatch</td>';
html += `<td>${phraseStyle(charsToMode(exp.lastMatch, mode), 'match')}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>rightContext</td>';
html += `<td>${phraseStyle(charsToMode(exp.rightContext, mode))}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<th>rules</th><th>phrase</th>';
html += '</tr>\n';
for (const name in exp.rules) {
html += '<tr>';
html += `<td>${name}</td>`;
if (exp.rules[name]) {
html += `<td>${phraseStyle(charsToMode(exp.rules[name], mode))}</td>`;
} else {
html += `<td>${phraseStyle(undefined)}</td>`;
}
html += '</tr>\n';
}
html += '<tr>';
html += '<th>alias</th><th>phrase</th>';
html += '</tr>\n';
html += '<tr>';
html += '<td>["$_"]</td>';
html += `<td>${phraseStyle(charsToMode(exp.$_, mode))}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>["$`"]</td>';
html += `<td>${phraseStyle(charsToMode(exp['$`'], mode))}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>["$&"]</td>';
html += `<td>${phraseStyle(charsToMode(exp['$&'], mode), 'match')}</td>`;
html += '</tr>\n';
html += '<tr>';
html += '<td>["$\'"]</td>';
html += `<td>${phraseStyle(charsToMode(exp["$'"], mode))}</td>`;
html += '</tr>\n';
for (const name in exp.rules) {
html += '<tr>';
html += `<td>["\${${name}}"]</td>`;
if (exp[`\${${name}}`]) {
html += `<td>${phraseStyle(charsToMode(exp[`\${${name}}`], mode))}</td>`;
} else {
html += `<td>${phraseStyle(undefined)}</td>`;
}
html += '</tr>\n';
}
html += '</table>\n';
return html;
};
/* apg-exp object - Unicode mode to HTML page */
const uLastMatchToHtmlPage = function uLastMatchToHtmlPage(exp, mode) {
return utils.htmlToPage(uLastMatchToHtml(exp, mode));
};
/* SABNF grammar souce to ASCII text */
const sourceToText = function sourceToText(exp) {
return exp.source;
};
/* SABNF grammar souce to HTML */
const sourceToHtml = function sourceToHtml(exp) {
const rx = /.*(\r\n|\n|\r)/g;
let result;
let chars;
let html;
html = '<pre>\n';
const TRUE = true;
while (TRUE) {
result = rx.exec(exp.source);
if (result === null || result[0] === '') {
break;
}
chars = apglib.utils.stringToChars(result[0]);
html += apglib.utils.charsToAsciiHtml(chars);
html += '\n';
}
html += '</pre>\n';
return html;
};
/* SABNF grammar souce to HTML page */
const sourceToHtmlPage = function sourceToHtmlPage(exp) {
return apglib.utils.htmlToPage(sourceToHtml(exp), 'apg-exp source');
};
/* export modules needed by the apg-exp and result objects to display their values */
module.exports = {
s: {
resultToText: sResultToText,
resultToHtml: sResultToHtml,
resultToHtmlPage: sResultToHtmlPage,
expToText: sLastMatchToText,
expToHtml: sLastMatchToHtml,
expToHtmlPage: sLastMatchToHtmlPage,
sourceToText,
sourceToHtml,
sourceToHtmlPage,
},
u: {
resultToText: uResultToText,
resultToHtml: uResultToHtml,
resultToHtmlPage: uResultToHtmlPage,
expToText: uLastMatchToText,
expToHtml: uLastMatchToHtml,
expToHtmlPage: uLastMatchToHtmlPage,
},
};