apg-js-examples
Version:
Examples of using the suite of apg-js applications and libraries.
167 lines (165 loc) • 6.55 kB
JavaScript
/* *************************************************************************************
* copyright: Copyright (c) 2021 Lowell D. Thomas, all rights reserved
* license: BSD-2-Clause (https://opensource.org/licenses/BSD-2-Clause)
* ********************************************************************************* */
// Sometimes the academic question comes up as to exactly where in the Chomsky hierarchy of things, modern `regex` engines fall.
// An interesting discussion thread of that question can be found
// [here](http://cstheory.stackexchange.com/questions/1047/where-do-most-regex-implementations-fall-on-the-complexity-scale).
// As noted there, `"regexes that can contain arbitrary program code"` are considered Turing complete.
// Allowing arbitrary code is probably not an academically satisfying way of getting completeness,
// but nonetheless, being able to match a tough phrase by simply writing
// a special routine to do it can get you over the hump.
//
// `apg-exp` allows for including arbitrary code snippets for phrase matching through `apg`'s
// User-Defined Terminal (`UDT`) feature. This example will give a simple demonstration of how that is done.
//
// It is well knows that the grammar for
// [a<sup>n</sup>b<sup>n</sup>](https://en.wikipedia.org/wiki/Context-free_language) (n >= 1)is Context Free and that
// [a<sup>n</sup>b<sup>n</sup>c<sup>n</sup>](https://en.wikipedia.org/wiki/Parsing_expression_grammar)
// can be matched with the addition of look ahead.
// Let's ramp that up a little with (a/A)<sup>n</sup>(b/B)<sup>n</sup>(c/C)<sup>n</sup>
// and add the requirement that the sequences of "a"s, "b"s and "c"s must match in case.
// I'm not a mathematician and that may not be a Turing-complete problem, per se,
// but I'm pretty sure that falls outside the range of "context-free + plus look around + plus back referencing."
// True or not, here is how to do it with `apg-exp` and `UDT`'s.
(function udt() {
const { apgExp: ApgExp, apgLib } = require('apg-js');
// let apgJs = require("apg-js");
// let apgExp = apgJs.apgExp;
// let apgLib = apgJs.apgLib;
const id = apgLib.ids;
// const { charsToString } = apgLib.utils;
// Define the `UDT` callback function.
// `data` is not used here. With a normal `apg` parser, the user has the option of passing
// in a data object for use by the user-written functions.
// But there is no facility for this in `apg-exp`.
const udtPhrase = function udtPhrase(sysData, chars, phraseIndex) {
let i;
let j;
let an;
/* default to failure - any early return is a failure to find a match */
sysData.state = id.NOMATCH;
sysData.phraseLength = 0;
const ai = phraseIndex;
an = 0;
for (i = ai; i < chars.length; i += 1) {
if (chars[i] === 65 || chars[i] === 97) {
an += 1;
} else {
break;
}
}
if (an === 0) {
return;
}
const bi = ai + an;
const bend = bi + an;
if (bend > chars.length) {
/* not enough characters left in the string for a match */
return;
}
for (i = ai, j = bi; j < bend; i += 1, j += 1) {
if (chars[j] === 66) {
if (chars[i] !== 65) {
/* doesn't match upper case */
return;
}
} else if (chars[j] === 98) {
if (chars[i] !== 97) {
/* doesn't match lower case */
return;
}
} else {
/* doesn't match - period */
return;
}
}
const ci = bi + an;
const cend = ci + an;
if (cend > chars.length) {
/* not enough characters left in the string for a match */
return;
}
for (i = ai, j = ci; j < cend; i += 1, j += 1) {
if (chars[j] === 67) {
if (chars[i] !== 65) {
/* doesn't match upper case */
return;
}
} else if (chars[j] === 99) {
if (chars[i] !== 97) {
/* doesn't match lower case */
return;
}
} else {
/* doesn't match - period */
return;
}
}
/* if we made it all the way to here, it's a match */
sysData.state = id.MATCH;
sysData.phraseLength = 3 * an;
};
try {
let grammar;
let exp;
let result;
let str;
/* simple grammar to just match the phrase */
grammar = 'anbncn = u_phrase\n';
str = 'aaAAaabbBBbbccCCcc';
exp = new ApgExp(grammar);
// The user must write the `UDT` *and* tell the `apg-exp` object about it.
exp.defineUdt('u_phrase', udtPhrase);
console.log();
console.log('Demonstrate the use of User-Defined Terminals (UDTs).');
console.log('Handwritten code snippets for matching phrases.');
console.log();
console.log('grammar:');
console.log(exp.source);
result = exp.exec(str);
console.log();
console.log('cases match:');
console.log(`input string: ${str}`);
if (result) {
console.log(result.toText());
} else {
console.log('result: null');
throw new Error('cases should match for this input string');
}
str = 'aaAAaabbBBbbcccCcc';
result = exp.exec(str);
console.log();
console.log("cases don't match:");
console.log(`input string: ${str}`);
if (result) {
console.log(result.toText());
throw new Error('cases should not match for this input string');
} else {
console.log('result: null');
}
// Just to make the example a little more interesting, let's combine this with the
// matching parentheses grammar.
grammar = 'R = (open text R text close) / (open u_phrase close)\n';
grammar += 'open = %d40 ; open paren "("\n';
grammar += 'close = %d41 ; close paren ")"\n';
grammar += 'text = *(%d32-39/%d42-126); any characters but "()"\n';
exp = new ApgExp(grammar);
exp.defineUdt('u_phrase', udtPhrase);
str = 'find anbncn in within parentheses (down 1(down 2(down 3(aAabBbcCc)up 3)up 2)up 1)';
console.log();
console.log('grammar:');
console.log(exp.source);
console.log('within parentheses:');
console.log(`input string: ${str}`);
result = exp.exec(str);
if (result) {
console.log(result.toText());
} else {
console.log('result: null');
throw new Error('cases should match for this input string');
}
} catch (e) {
console.log(`EXCEPTION: ${e.message}`);
}
})();