/** This module implements the model for the practice page which is
shared by [batch](script.js.html) and [graphical user interface](index.js.html) scripting.
@module Practice
@author © 2023 Axel T. Schreiner <axel@schreiner-family.net>
@version 2024-6-20
*/
import * as iBase from './base.js';
import * as iEBNF from './ebnf.js';
import * as iBNF from './bnf.js';
import * as iFive from './05.js';
import * as iSix from './06.js';
import * as iSeven from './07.js';
import * as iEight from './08.js';
import * as iTen from './10.js';
import * as iEleven from './11.js';
/** Model of the practice page.
* The following global variables and global modules
* will exist once the model has been constructed.
* Pre-existing global variables `newOutput`, `prompt`, and `puts` will not be overwritten.
| name | type | content |
| ------ | ---- | ------- |
| `actions` | `string` | Defines the class and {@link module:Base~Action action methods} to be called by a parser. |
| `g` | `Grammar` | Represents `grammar`. |
| `grammar` | `string` | Rules of a grammar, argument for the construction of `g`. |
| `newOutput` | `function` | Displays it's arguments, blank-separated and marked as a new section. |
| `program` | `string` | Should be a sentence conforming to `grammar`, argument for recognition. |
| `prompt` | `function` | Displays a prompt string, returns input or a default string, else error. |
| `puts` | `function` | Displays it's arguments, blank-separated. |
| `run` | `function` | `null` or an executable compiled from `program` by the `actions`.<br>A stack machine has two arguments, other executables have none. |
| `tokens` | `string` | Defines an object with pattern properties defining the tokens used in `grammar`. |
| module | purpose |
| ------ | ------- |
| {@linkcode module:Base Base} | Base classes shared by the parser generators |
| {@linkcode module:EBNF EBNF} | LL(1) parser generator |
| {@linkcode module:BNF BNF} | SLR(1) parser generator |
| {@linkcode module:Six Six} | Classes for the examples in chapter six |
| {@linkcode module:Seven Seven} | Classes for the examples in chapter seven |
| {@linkcode module:Eight Eight} | Classes for the examples in chapter eight |
| {@linkcode module:Ten Ten} | Classes for the examples in chapter ten |
| {@linkcode module:Eleven Eleven} | Classes for the examples in chapter eleven |
* @property {string} grammar - global `grammar`; setter clears global `g` and global `run`.
* @property {string} tokens - global `tokens`; setter clears global `g` and global `run`.
* @property {RegExp} skip - set by `doNew` from an empty key in `tokens`;
* overwrites default for scanner and parser.
* @property {string} actions - global `actions`; setter clears global `run`.
* @property {string} program - global `program`; setter clears global `run`.
* @property {string} mode - `ebnf`|`stack`|`bnf`; setter clears global `g` and global `run`.
* @property {boolean} greedy - use `expect()` rather than `check()`.
* @property {boolean} error - insert `$error` when translating EBNF.
* @property {boolean} tShallow - trace algorithm and display sets.
* @property {boolean} tDeep - trace algorithm and display sets.
* @property {boolean} tFollow - trace algorithm and display sets.
* @property {boolean} dSets - display sets.
* @property {boolean} dStates - display states.
* @property {boolean} tLookahead - trace lookahead during parse.
* @property {boolean} tParser - trace parse.
* @property {boolean} tActions - trace actions.
* @property {boolean} tNoargs - do not check argument count for actions.
* @property {boolean} build - build lists.
*
* @property {?Array} memory - for stepping a stack machine.
*
@example
// Use in scripting in a Windows environment
new Model(windows);
@example
// Use in scripting in a node.js environment
new Model(globalThis);
*/
class Model {
get grammar () {
return grammar;
} // global
set grammar (value) {
if (value == grammar) return;
grammar = value.trim();
g = run = null;
} // global, if different clears g and run
get tokens () {
return tokens;
} // global
set tokens (value) {
if (value == tokens) return;
tokens = value.trim();
g = run = null;
} // global, if different clears g and run
get skip () { return this.#skip; }
#skip = undefined;
get actions () {
return actions;
} // global
set actions (value) {
if (value == actions) return;
actions = value.trim();
run = null;
} // global, if different clears run
get program () {
return program;
} // global
set program (value) {
if (value == program) return;
program = value.trim();
run = null;
} // global, if different clears run
#mode = 'ebnf';
get mode () {
return this.#mode;
}
set mode (value) {
if (value == this.#mode) return;
this.greedy = this.error = this.tShallow = this.tDeep = this.tFollow =
this.dSets = this.dStates = this.tLookAhead = this.tParser = this.tActions = this.tNoargs =
this.build = false;
g = run = null;
this.#mode = value;
} // if different clears all flags, g, run
greedy = false;
error = false;
tShallow = false;
tDeep = false;
tFollow = false;
dSets = false;
dStates = false;
tLookahead = false;
tParser = false;
tActions = false;
tNoargs = false;
build = false;
#memory = null;
get memory () { return this.#memory; }
/** Create a model.
@param {object} global - either `windows` or `globalThis`.
*/
constructor (global) {
// create and initialize "global" variables
global.actions = '';
global.g = null;
global.grammar = '';
global.program = '';
global.run = null;
global.tokens = '';
if (!('newOutput' in global))
global.newOutput = (...arg) => {
console.log('> newOutput');
if (arg.length > 1 || arg[0].length) console.log(...arg);
};
if (!('prompt' in global))
global.prompt = (text, dflt) => { console.log(text + ' > ' + dflt); return dflt; };
if (!('puts' in global))
global.puts = console.log.bind(console);
global.Base = iBase;
global.EBNF = iEBNF;
global.BNF = iBNF;
global.Five = iFive;
global.Six = iSix;
global.Seven = iSeven;
global.Eight = iEight;
global.Ten = iTen;
global.Eleven = iEleven;
}
/** Event: `eval?.(tokens)`, if any, and represent and check the grammar;
modifies `g`, clears `run`, and removes `memory`.
*/
doNew () {
// clear globals and remove memory
const clearAll = () => g = run = this.#memory = null;
clearAll();
// compile this.tokens into tokens, set this.skip
let tokens = null;
try {
this.#skip = undefined;
if (this.tokens.length) {
tokens = eval?.('(' + this.tokens + '\n)');
if (tokens[''] instanceof RegExp) this.#skip = tokens[''];
}
} catch (e) {
clearAll();
newOutput('Error in tokens: ' + (e instanceof Error ? e.message : e));
return;
}
// represent this.grammar as g
try {
let cmd;
switch (this.mode) {
case 'ebnf':
const flags = [];
if (this.tShallow) flags.push('shallow: true');
if (this.tDeep) flags.push('deep: true');
if (this.tFollow) flags.push('follow: true');
cmd = '> g = new EBNF.Grammar(grammar, tokens';
if (flags.length) cmd += ', { ' + flags.join(', ') + ' }';
newOutput(cmd + ')');
g = new EBNF.Grammar(this.grammar, tokens, {
shallow: this.tShallow,
deep: this.tDeep,
follow: this.tFollow,
log: puts
});
if (g && !g.errors) {
if (this.greedy)
puts('> g.expect()'), g.expect();
else
puts('> g.check()'), g.check();
puts(this.tShallow|this.tDeep|this.tFollow ? g.dump() : g.toString());
}
break;
case 'stack':
cmd = '> g = BNF.Grammar.fromEBNF(new EBNF.Grammar(grammar, tokens)';
if (this.error) cmd += ', { error: true }';
newOutput(cmd + ')');
g = BNF.Grammar.fromEBNF(
new EBNF.Grammar(this.grammar, tokens, {
log: puts
}), {
error: this.error,
log: puts
});
if (g && !g.errors)
puts(this.dSets ? g.dump(undefined, this.dStates) : g.toString(this.dStates));
break;
case 'bnf':
newOutput('> g = new BNF.Grammar(grammar, tokens)');
g = new BNF.Grammar(this.grammar, tokens, {
log: puts
});
if (g && !g.errors)
puts(this.dSets ? g.dump() : g.toString(this.dStates));
}
} catch (e) {
newOutput('Error in new grammar: ' + (e instanceof Error ? e.message : e));
clearAll();
}
if (g && g.errors)
clearAll();
}
/** Event: create a scanner and apply it to this.program.
*/
doScan () {
puts('do scan', g == null)
if (!g) return;
const s = g.scanner(this.skip);
newOutput(`> g.scanner(${this.skip ?? ''}).pattern =`, s.pattern.toString());
puts(`> g.scanner(${this.skip ?? ''}).scan(program)`);
puts(s.scan(this.program.trim()).join('\n'));
}
/** Event: `eval?.(this.actions)`, if any, create a parser, and parse the program;
modifies `run` and removes `memory`.
*/
doParse () {
if (!g) return;
// clear run and remove memory
run = this.#memory = null;
// compile actions, if any
let actions = null;
if (this.actions.length)
try {
actions = eval?.('(' + this.actions + '\n)');
if (typeof actions != 'function' && typeof actions != 'object')
throw 'actions must define a class or an object';
} catch (e) {
newOutput('Error in actions: ' + (e instanceof Error ? e.message : e));
return;
}
// parse/compile this.program
newOutput('');
switch (this.mode) {
case 'ebnf':
if (g.config.lookahead = this.tLookahead) puts('> g.config.lookahead = true');
if (g.config.parse = this.tParser) puts('> g.config.parse = true');
if (g.config.actions = this.tActions) puts('> g.config.actions = true');
if (g.config.noargs = this.tNoargs) puts('> g.config.noargs = true');
break;
case 'stack':
if (g.config.error = this.error) puts('> g.config.error = true');
if (g.config.lookahead = this.tLookahead) puts('> g.config.lookahead = true');
if (this.tParser) puts(`> g.config.trace = ${g.config.trace = /./}`);
else if (this.tActions) puts(`> g.config.trace = ${g.config.trace = /reduce/}`);
else g.config.trace = null;
if (g.config.noargs = this.tNoargs) puts('> g.config.noargs = true');
break;
case 'bnf':
if (g.config.build = this.build) puts('> g.config.build = true');
if (this.tParser) puts(`> g.config.trace = ${g.config.trace = /./}`);
else if (this.tActions) puts(`> g.config.trace = ${g.config.trace = /reduce/}`);
else g.config.trace = null;
if (g.config.noargs = this.tNoargs) puts('> g.config.noargs = true');
}
try {
if (actions) puts(`> run = g.parser(${this.skip ?? ''}).parse(program, actions)`);
else puts(`> g.parser(${this.skip ?? ''}).parse(program)`);
puts(g.dump(run = g.parser(this.skip).parse(this.program, actions)));
if (g.errors) run = null; // don't allow execution
} catch (e) {
if (this.mode != 'ebnf') puts(e instanceof Error ? e.message : e);
run = null; // don't allow execution
} finally {
if (typeof run != 'function') run = null; // don't allow execution
}
}
/** Event: run the executable, if any.
*/
doRun () {
if (typeof run != 'function') return;
try {
this.#memory = null; // no stepping
newOutput('> run()');
const result = run();
puts(run.length == 2 ? result.toString() : g.dump(result));
} catch (e) { puts(e instanceof Error ? e.message : e); }
}
/** Event: step the stack machine, if any.
@param {number} n - number of steps to execute.
*/
doStep (n) {
if (!typeof run == 'function' || run.length != 2) return;
try {
if (!this.memory || !this.memory.continue) {
newOutput(`> memory = run(null, ${n})`);
this.#memory = run(null, n);
} else {
puts(`> memory = run(memory, ${n})`);
this.#memory = run(this.memory, n);
}
if (!this.memory.continue) {
puts(this.memory.toString());
this.#memory = null;
}
} catch (e) { puts(e instanceof Error ? e.message : e); this.#memory = null; }
}
}
export { Model };