apg-js-examples
Version:
Examples of using the suite of apg-js applications and libraries.
152 lines (150 loc) • 7.21 kB
JavaScript
/* eslint-disable guard-for-in */
/* eslint-disable no-restricted-syntax */
/* eslint-disable new-cap */
/* *************************************************************************************
* copyright: Copyright (c) 2021 Lowell D. Thomas, all rights reserved
* license: BSD-2-Clause (https://opensource.org/licenses/BSD-2-Clause)
* ********************************************************************************* */
// This module demonstrates how to deal with the phrases matched to the SABNF syntax rule names.
// Rule names are similar to named groupings in `regex` expressions. They associate a name with a phrase.
// `apg-exp` provides a little more information about the rule phrases than does the JavaScript `RegExp` object.
// Firstly, the JavaScript `RegExp` object does not provide for naming the grouped phrases, although other flavors
// of `regex` engines do. Secondly, the JavaScript `RegExp` object only gives the last match to the group and doesn't
// provide the character index where it was found. `apg-exp` retains all phrases matched by any rule name and provides
// character index where it was found.
//
// The `result` object retains an array of all of the phrases found for each rule name.
// It is an array of phrase objects, each object having the phrase and index in the form
// {phrase: string, index : number}.
// By default, all rule names in the grammar are retained in the result object
// but, as will be demonstrated here, uninteresting rules or phrases can be ignored.
//
// The [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp)
// description of the `RegExp` object also indicates that it retains "last match" information.
// Although most of that information seems to be missing in the `node.js` implementation, all of it,
// including the aliases, are defined and retained by the `apg-exp` object.
// This will also be demonstrated here.
//
// We will pick a grammar and string that will have several named-rules, some with zero, one and more matching phrases.
// This is a simplified [`ini file`](https://en.wikipedia.org/wiki/INI_file) format.
// It simply consists of a single, optional section name line and one required key/pair line.
// Because of the many line end characters, we will use HTML display of the results.
// The line end characters leave many confusing gaps in the console output.
(function rules() {
try {
const apgJs = require('apg-js');
const writeHtml = require('../writeHtml');
const grammar = new (require('./grammars/ini'))();
const { apgExp } = apgJs;
const { apgLib } = apgJs;
let exp;
let result;
let str;
let html;
let page;
let htmlName;
const flags = '';
exp = new apgExp(grammar, flags);
console.log();
console.log('Demonstrate named matched phrases.');
console.log();
console.log('SABNF grammar:');
console.log(exp.sourceToText());
str = '';
str += '; comment\n';
str += 'input = 1000\n';
result = exp.exec(str);
// A few things to note about the output.
// <ul>
// <li>
// The first rule name in the grammar, called the start rule (ini in this case,) is always the same as result[0].
// result[0] is simply an alias to match the familiar JavaScript RegExp result.
// Often, the matched phrase is all that you want and result[0] is a convenient handle to it.
// </li>
// <li>
// Not all rules match a phrase. In this case, there is no section name.
// </li>
// <li>
// Finally, some rules are matched to multiple phrases. In fact, the alpha and digit phrases are matched
// so often that they are a nuisance. We will demonstrate shortly how to get rid of the clutter.
// </li>
// </ul>
page = result.toHtmlPage();
htmlName = 'ini-first-result';
writeHtml(page, htmlName);
// Let's say we want to see everything except the `alpha`, `digit` and `owsp` phrases.
// We can exclude those with a call to the `exp.exclude()` function.
exp.exclude(['alpha', 'digit', 'owsp']);
result = exp.exec(str);
page = result.toHtmlPage();
htmlName = 'ini-exclude-result';
writeHtml(page, htmlName);
// Or we can do it the other way around with the `exp.include()` function.
// Say that we only want to see the `section-name`, `key` and `value`.
exp.include(['section-name', 'key', 'value']);
result = exp.exec(str);
page = result.toHtmlPage();
htmlName = 'ini-include-result';
writeHtml(page, htmlName);
// Now that we've show the display of the rules (see display.js for more on display functions)
// let's take a look at how to handle them programmatically.
// Here is a general loop that will give specific access to all of the included rule phrases.
str = '';
str += '; comment\n';
str += 'input = 1000\n';
exp = new apgExp(grammar, flags);
exp.exclude(['alpha', 'digit']);
result = exp.exec(str);
html = '';
html += exp.sourceToHtml();
html += '<h3>input string</h3>\n';
html += apgLib.utils.stringToAsciiHtml(str);
html += '<h3>result</h3>\n';
html += '<pre>\n';
// Enumerate all of the named rules.
for (const name in result.rules) {
if (result.rules[name]) {
// The named rule is defined. Therefore, it is an array of phrase objects.
// Each phrase and index is specifically identified and displayed in this loop.
for (let i = 0; i < result.rules[name].length; i += 1) {
const { phrase } = result.rules[name][i];
const { index } = result.rules[name][i];
html += `result.rules[${name}][${i}].phrase(${index}) = '`;
html += apgLib.utils.stringToAsciiHtml(phrase);
html += "'\n";
}
} else {
// Otherwise, no phrase was matched for this rule
html += `result.rules[${name}] = undefined\n`;
}
}
html += '</pre>\n';
html = apgLib.utils.htmlToPage(html);
htmlName = 'ini-enumeration';
writeHtml(html, htmlName);
// Briefly, we will also show here the `last match` information retained in the `apg-exp` object.
html = exp.toHtml();
html = apgLib.utils.htmlToPage(html);
htmlName = 'ini-last-match';
writeHtml(html, htmlName);
// Programmatically, we can access the `last match` data similarly. A partial display is given here.
html = '<h3>last match</h3>\n';
html += 'exp.leftContext: ';
html += apgLib.utils.stringToAsciiHtml(exp.leftContext);
// Enumerate the last match to each rule
html += '<pre>\n';
for (const name in exp.rules) {
html += `exp.rules[${name}]: ${exp.rules[name]}`;
html += '\n';
const lastMatchPhraseName = `\${${name}}`;
html += `exp[${lastMatchPhraseName}]: ${exp[lastMatchPhraseName]}`;
html += '\n';
}
html += '</pre>\n';
html = apgLib.utils.htmlToPage(html);
htmlName = 'ini-last-match-program';
writeHtml(html, htmlName);
} catch (e) {
console.log(`EXCEPTION: ${e.message}`);
}
})();