🎨 优化扩展模块,完成ai接入和对话功能
This commit is contained in:
149
data/st-core-scripts/scripts/macros/engine/MacroParser.js
Normal file
149
data/st-core-scripts/scripts/macros/engine/MacroParser.js
Normal file
@@ -0,0 +1,149 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user