apg-js-examples
Version:
Examples of using the suite of apg-js applications and libraries.
137 lines (129 loc) • 5.39 kB
JavaScript
/* eslint-disable new-cap */
/* 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 provides an example of how a more "real world" parser might be built.
// It parses the [INI file format](https://en.wikipedia.org/wiki/INI_file), commonly
// used as a configuration file for many types of applications.
// The grammar (ini-file.bnf) presented here includes "error productions".
// That is, rules are defined to catch errors in the configuration file format.
// This way, instead of simply having the parser fail on bad input (a very frustrating user experience)
// the application can provide callback functions to catch the errors and report them to the user.
//
// To keep things modular the syntax and `AST` callback functions are defined in separate modules,
// `parser-callbacks.js` and `ast-callbacks.js`, resectively,
// and "require()"ed by the application as needed.
//
// - *input* - the input string to parse
// - *trace* - if `true` display the trace
// - *stats* - if `true` display the stats
module.exports = function setup(input, traceArg, statsArg) {
let trace = traceArg;
let stats = statsArg;
const thisFileName = 'setup.js: ';
const nodeUtil = require('node:util');
const writeHtml = require('../writeHtml');
const inspectOptions = {
showHidden: true,
depth: null,
};
/* build an array of names (strings) and sort them alphabetically */
const sortNames = function sortNames(obj) {
const names = [];
for (const name in obj) {
names.push(name);
}
return names.sort();
};
try {
if (typeof input !== 'string') {
throw new Error(`${thisFileName}invalid input string`);
}
if (trace === null || typeof trace !== 'object' || trace.traceObject !== 'traceObject') {
trace = null;
}
if (stats === null || typeof stats !== 'object' || stats.statsObject !== 'statsObject') {
stats = null;
}
/* Get the required parser components (see [`simple/setup.js`](../simple/setup.html) for the basics of setting up the parser */
const { apgLib } = require('apg-js');
const parser = new apgLib.parser();
const grammar = new (require('./ini-file'))();
const parserCallbacks = new (require('./parser-callbacks'))();
const astCallbacks = new (require('./ast-callbacks'))();
parser.ast = new apgLib.ast();
/* Define the syntax callback functions to the parser. */
parser.callbacks = parserCallbacks.callbacks;
/* Define the semantic (`AST`) callback functions to the parser. */
parser.ast.callbacks = astCallbacks.callbacks;
parser.trace = trace;
parser.stats = stats;
const inputCharacterCodes = apgLib.utils.stringToChars(input);
/* object to hold error messages from the syntax callback functions */
const syntaxData = {};
/*
Parse the input string. The syntax callback functions simply
monitor the parser, reporting any syntax errors it finds in the input string.
*/
const result = parser.parse(grammar, 0, inputCharacterCodes, syntaxData);
console.log();
console.log("the parser's results");
console.dir(result, inspectOptions);
if (result.success !== true) {
throw new Error(`${thisFileName}parse failed`);
}
if (syntaxData.errors.length > 0) {
console.log();
console.log('syntax errors found:');
syntaxData.errors.forEach((error) => {
console.log(error);
});
}
let html;
if (parser.stats !== null) {
html = parser.stats.toHtmlPage('hits', 'rules ordered by hit count', 'IniFile Stats');
writeHtml(html, 'ini-file-stats');
}
if (parser.trace !== null) {
/* Display the trace, if requested */
html = parser.trace.toHtmlPage('ascii', 'IniFile Trace', 'IniFile Trace');
writeHtml(html, 'ini-file-trace');
}
/* object to hold the parsed data */
const data = {};
/*
The syntax is correct, so translate the ini file format data into a JSON-like object.
Alphabetize the section names and the key names within each section and display on the console.
*/
parser.ast.translate(data);
console.log();
console.log('alphabetized AST translation data:');
console.log();
const sectionNames = sortNames(data);
sectionNames.forEach((sectionName) => {
if (sectionName !== '0') {
console.log();
console.log(`[${sectionName}]`);
}
const keys = sortNames(data[sectionName]);
keys.forEach((keyName) => {
console.log(`${keyName}: ${data[sectionName][keyName]}`);
});
});
} catch (e) {
let msg = '\nEXCEPTION THROWN: \n';
if (e instanceof Error) {
msg += `${e.name}: ${e.message}`;
} else if (typeof e === 'string') {
msg += e;
} else {
msg += nodeUtil.inspect(e, inspectOptions);
}
process.exitCode = 1;
console.log(msg);
throw e;
}
};