Files
SillyTavern/public/scripts/macros/MacroParser.js
Wolfsblvt f9d4deb583 Improve macro argument parsing to allow colons in values
Enhances separator handling by fixing separator type detection and enabling colon characters within argument values
Updates validation to require at least one argument component and adds error cases for empty arguments
Includes expanded test coverage for mixed separator scenarios and edge cases
2025-03-20 02:25:07 +01:00

77 lines
2.5 KiB
JavaScript

import { CstParser } from '../../lib/chevrotain.js';
import { MacroLexer } from './MacroLexer.js';
/** @typedef {import('../../lib/chevrotain.js').TokenType} TokenType */
/**
* 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: true,
nodeLocationTracking: 'full',
});
const Tokens = MacroLexer.tokens;
const $ = this;
// Basic Macro Structure
$.macro = $.RULE('macro', () => {
$.CONSUME(Tokens.Macro.Start);
$.CONSUME(Tokens.Macro.Identifier);
$.OPTION(() => $.SUBRULE($.arguments));
$.CONSUME(Tokens.Macro.End);
});
// Arguments Parsing
$.arguments = $.RULE('arguments', () => {
$.OR([
{ ALT: () => $.CONSUME(Tokens.Args.DoubleColon, { LABEL: 'separator' }) },
{ ALT: () => $.CONSUME(Tokens.Args.Colon, { LABEL: 'separator' }) },
]);
$.AT_LEAST_ONE_SEP({
SEP: Tokens.Args.DoubleColon,
DEF: () => $.SUBRULE($.argument),
});
});
$.argument = $.RULE('argument', () => {
$.AT_LEAST_ONE(() => {
$.OR([
{ ALT: () => $.SUBRULE($.macro) }, // Nested Macros
{ ALT: () => $.CONSUME(Tokens.Identifier) },
{ ALT: () => $.CONSUME(Tokens.Unknown) },
{ ALT: () => $.CONSUME(Tokens.Args.Colon) },
]);
});
});
this.performSelfAnalysis();
}
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;