Nanny7's picture
Initial deploy with custom mobile UI
6efa67a
import { chevrotain } from '../../../lib.js';
import { MacroLexer } from './MacroLexer.js';
const { CstParser } = chevrotain;
/** @typedef {import('chevrotain').TokenType} TokenType */
/** @typedef {import('chevrotain').CstNode} CstNode */
/** @typedef {import('chevrotain').ILexingError} ILexingError */
/** @typedef {import('chevrotain').IRecognitionException} IRecognitionException */
/**
* The singleton instance of the MacroParser.
*
* @type {MacroParser}
*/
let instance;
export { instance as MacroParser };
class MacroParser extends CstParser {
/** @type {MacroParser} */ static #instance;
/** @type {MacroParser} */ static get instance() { return MacroParser.#instance ?? (MacroParser.#instance = new MacroParser()); }
/** @private */
constructor() {
super(MacroLexer.def, {
traceInitPerf: false,
nodeLocationTracking: 'full',
recoveryEnabled: true,
});
const Tokens = MacroLexer.tokens;
const $ = this;
// Top-level document rule that can handle both plaintext and macros
$.document = $.RULE('document', () => {
$.MANY(() => {
$.OR([
{ ALT: () => $.CONSUME(Tokens.Plaintext, { LABEL: 'plaintext' }) },
{ ALT: () => $.CONSUME(Tokens.PlaintextOpenBrace, { LABEL: 'plaintext' }) },
{ ALT: () => $.SUBRULE($.macro) },
{ ALT: () => $.CONSUME(Tokens.Macro.Start, { LABEL: 'plaintext' }) },
]);
});
});
// Basic Macro Structure
$.macro = $.RULE('macro', () => {
$.CONSUME(Tokens.Macro.Start);
$.OR([
{ ALT: () => $.CONSUME(Tokens.Macro.DoubleSlash, { LABEL: 'Macro.identifier' }) },
{ ALT: () => $.CONSUME(Tokens.Macro.Identifier, { LABEL: 'Macro.identifier' }) },
]);
$.OPTION(() => $.SUBRULE($.arguments));
$.CONSUME(Tokens.Macro.End);
});
// Arguments Parsing
$.arguments = $.RULE('arguments', () => {
$.OR([
{
ALT: () => {
$.CONSUME(Tokens.Args.DoubleColon, { LABEL: 'separator' });
$.AT_LEAST_ONE_SEP({
SEP: Tokens.Args.DoubleColon,
DEF: () => $.SUBRULE($.argument, { LABEL: 'argument' }),
});
},
},
{
ALT: () => {
$.OPTION(() => {
$.CONSUME(Tokens.Args.Colon, { LABEL: 'separator' });
});
$.SUBRULE($.argumentAllowingColons, { LABEL: 'argument' });
},
// So, this is a bit hacky. But implemented below, the argument capture does explicitly exclude double colons
// from being captured as the first token. The potential ambiguity chevrotain claims here is not possible.
// It says stuff like <Args.DoubleColon, Identifier/Macro/Unknown> is possible in both branches, but it is not.
IGNORE_AMBIGUITIES: true,
},
]);
});
// List the argument tokens here, as we need two rules, one to be able to parse with double colons and one without
const validArgumentTokens = [
{ ALT: () => $.SUBRULE($.macro) }, // Nested Macros
{ ALT: () => $.CONSUME(Tokens.Identifier) },
{ ALT: () => $.CONSUME(Tokens.Unknown) },
{ ALT: () => $.CONSUME(Tokens.Args.Colon) },
{ ALT: () => $.CONSUME(Tokens.Args.Equals) },
{ ALT: () => $.CONSUME(Tokens.Args.Quote) },
];
$.argument = $.RULE('argument', () => {
$.MANY(() => {
$.OR([...validArgumentTokens]);
});
});
$.argumentAllowingColons = $.RULE('argumentAllowingColons', () => {
$.AT_LEAST_ONE(() => {
$.OR([
...validArgumentTokens,
{ ALT: () => $.CONSUME(Tokens.Args.DoubleColon) },
]);
});
});
this.performSelfAnalysis();
}
/**
* Parses a document into a CST.
*
* @param {string} input
* @returns {{ cst: CstNode|null, errors: ({ message: string }|ILexingError|IRecognitionException)[] , lexingErrors: ILexingError[], parserErrors: IRecognitionException[] }}
*/
parseDocument(input) {
if (!input) {
return { cst: null, errors: [{ message: 'Input is empty' }], lexingErrors: [], parserErrors: [] };
}
const lexingResult = MacroLexer.tokenize(input);
this.input = lexingResult.tokens;
const cst = this.document();
const errors = [
...lexingResult.errors,
...this.errors,
];
return { cst, errors, lexingErrors: lexingResult.errors, parserErrors: this.errors };
}
test(input) {
const lexingResult = MacroLexer.tokenize(input);
// "input" is a setter which will reset the parser's state.
this.input = lexingResult.tokens;
const cst = this.macro();
// For testing purposes we need to actually persist the error messages in the object,
// otherwise the test cases cannot read those, as they don't have access to the exception object type.
const errors = this.errors.map(x => ({ message: x.message, ...x, stack: x.stack }));
return { cst, errors: errors };
}
}
instance = MacroParser.instance;