restore old escape handling and parser flag for strict escaping

This commit is contained in:
LenAnderson
2024-04-20 07:08:53 -04:00
parent 1de09bf71d
commit bc6ff9756a
5 changed files with 93 additions and 11 deletions

View File

@ -1740,14 +1740,14 @@ function modelCallback(_, model) {
* @param {SlashCommandScope} scope The scope to be used when executing the commands.
* @returns {Promise<SlashCommandClosureResult>}
*/
async function executeSlashCommands(text, handleParserErrors = true, scope = null, handleExecutionErrors = false) {
async function executeSlashCommands(text, handleParserErrors = true, scope = null, handleExecutionErrors = false, parserFlags = null) {
if (!text) {
return null;
}
let closure;
try {
closure = parser.parse(text);
closure = parser.parse(text, true, parserFlags);
closure.scope.parent = scope;
} catch (e) {
if (handleParserErrors && e instanceof SlashCommandParserError) {

View File

@ -153,6 +153,7 @@ export class SlashCommandClosure {
interrupt = executor.command.interruptsGeneration;
let args = {
_scope: this.scope,
_parserFlags: executor.parserFlags,
};
let value;
// substitute named arguments

View File

@ -2,6 +2,7 @@
import { SlashCommand } from './SlashCommand.js';
// eslint-disable-next-line no-unused-vars
import { SlashCommandClosure } from './SlashCommandClosure.js';
import { PARSER_FLAG } from './SlashCommandParser.js';
export class SlashCommandExecutor {
/**@type {Boolean}*/ injectPipe = true;
@ -12,6 +13,7 @@ export class SlashCommandExecutor {
// @ts-ignore
/**@type {Object.<string,String|SlashCommandClosure>}*/ args = {};
/**@type {String|SlashCommandClosure|(String|SlashCommandClosure)[]}*/ value;
/**@type {Object<PARSER_FLAG,boolean>} */ parserFlags;
constructor(start) {
this.start = start;

View File

@ -1,3 +1,4 @@
import { isTrueBoolean } from '../utils.js';
import { SlashCommand } from './SlashCommand.js';
import { OPTION_TYPE, SlashCommandAutoCompleteOption } from './SlashCommandAutoCompleteOption.js';
import { SlashCommandClosure } from './SlashCommandClosure.js';
@ -7,6 +8,12 @@ import { NAME_RESULT_TYPE, SlashCommandParserNameResult } from './SlashCommandPa
// eslint-disable-next-line no-unused-vars
import { SlashCommandScope } from './SlashCommandScope.js';
/**@readonly*/
/**@enum {Number}*/
export const PARSER_FLAG = {
'STRICT_ESCAPING': 1,
};
export class SlashCommandParser {
// @ts-ignore
/**@type {Object.<string, SlashCommand>}*/ commands = {};
@ -18,6 +25,8 @@ export class SlashCommandParser {
/**@type {number}*/ index;
/**@type {SlashCommandScope}*/ scope;
/**@type {Object.<PARSER_FLAG,boolean>}*/ flags = {};
/**@type {boolean}*/ jumpedEscapeSequence = false;
/**@type {{start:number, end:number}[]}*/ closureIndex;
@ -253,6 +262,7 @@ export class SlashCommandParser {
* @returns Whether the next characters are the indicated symbol.
*/
testSymbol(sequence, offset = 0) {
if (!this.flags[PARSER_FLAG.STRICT_ESCAPING]) return this.testSymbolLooseyGoosey(sequence, offset);
// /echo abc | /echo def
// -> TOAST: abc
// -> TOAST: def
@ -294,9 +304,35 @@ export class SlashCommandParser {
}
}
testSymbolLooseyGoosey(sequence, offset = 0) {
const escapeOffset = this.jumpedEscapeSequence ? -1 : 0;
const escapes = this.text[this.index + offset + escapeOffset] == '\\' ? 1 : 0;
const test = (sequence instanceof RegExp) ?
(text) => new RegExp(`^${sequence.source}`).test(text) :
(text) => text.startsWith(sequence)
;
if (test(this.text.slice(this.index + offset + escapeOffset + escapes))) {
// no backslashes before sequence
// -> sequence found
if (escapes == 0) return true;
// otherwise
// -> sequence found
if (!this.jumpedEscapeSequence && offset == 0) {
this.index++;
this.jumpedEscapeSequence = true;
}
return false;
}
}
parse(text, verifyCommandNames = true) {
parse(text, verifyCommandNames = true, flags = null) {
this.verifyCommandNames = verifyCommandNames;
if (flags) {
for (const key of Object.keys(PARSER_FLAG)) {
this.flags[PARSER_FLAG[key]] = flags[PARSER_FLAG[key]] ?? false;
}
}
this.text = `{:${text}:}`;
this.keptText = '';
this.index = 0;
@ -331,7 +367,11 @@ export class SlashCommandParser {
this.discardWhitespace();
}
while (!this.testClosureEnd()) {
if (this.testRunShorthand()) {
if (this.testComment()) {
this.parseComment();
} else if (this.testParserFlag()) {
this.parseParserFlag();
} else if (this.testRunShorthand()) {
const cmd = this.parseRunShorthand();
closure.executorList.push(cmd);
injectPipe = true;
@ -366,6 +406,44 @@ export class SlashCommandParser {
return closure;
}
testComment() {
return this.testSymbol(/\/[/#]/);
}
testCommentEnd() {
return this.testCommandEnd();
}
parseComment() {
const start = this.index + 2;
const cmd = new SlashCommandExecutor(start);
this.commandIndex.push(cmd);
this.scopeIndex.push(this.scope.getCopy());
this.take(); // discard "/"
cmd.name = this.take(); // set second "/" or "#" as name
while (!this.testCommentEnd()) this.take();
cmd.end = this.index;
}
testParserFlag() {
return this.testSymbol('/parser-flag ');
}
testParserFlagEnd() {
return this.testCommandEnd();
}
parseParserFlag() {
const start = this.index + 1;
const cmd = new SlashCommandExecutor(start);
cmd.name = 'parser-flag';
cmd.value = '';
this.commandIndex.push(cmd);
this.scopeIndex.push(this.scope.getCopy());
this.take(13); // discard "/parser-flag "
const [flag, state] = this.parseUnnamedArgument()?.split(/\s+/) ?? [null, null];
if (Object.keys(PARSER_FLAG).includes(flag)) {
this.flags[PARSER_FLAG[flag]] = isTrueBoolean(state);
}
cmd.end = this.index;
}
testRunShorthand() {
return this.testSymbol('/:') && !this.testSymbol(':}', 1);
}
@ -402,7 +480,7 @@ export class SlashCommandParser {
}
testCommand() {
return this.testSymbol('/') && !this.testSymbol('//') && !this.testSymbol('/#');
return this.testSymbol('/');
}
testCommandEnd() {
return this.testClosureEnd() || this.testSymbol('|');
@ -410,6 +488,7 @@ export class SlashCommandParser {
parseCommand() {
const start = this.index + 1;
const cmd = new SlashCommandExecutor(start);
cmd.parserFlags = Object.assign({}, this.flags);
this.commandIndex.push(cmd);
this.scopeIndex.push(this.scope.getCopy());
this.take(); // discard "/"

View File

@ -320,7 +320,7 @@ async function whileCallback(args, command) {
if (result && command) {
if (command instanceof SlashCommandClosure) await command.execute();
else await executeSubCommands(command, args._scope);
else await executeSubCommands(command, args._scope, args._parserFlags);
} else {
break;
}
@ -346,7 +346,7 @@ async function timesCallback(args, value) {
await command.execute();
}
else {
await executeSubCommands(command.replace(/\{\{timesIndex\}\}/g, i), args._scope);
await executeSubCommands(command.replace(/\{\{timesIndex\}\}/g, i), args._scope, args._parserFlags);
}
}
@ -359,10 +359,10 @@ async function ifCallback(args, command) {
if (result && command) {
if (command instanceof SlashCommandClosure) return (await command.execute()).pipe;
return await executeSubCommands(command, args._scope);
return await executeSubCommands(command, args._scope, args._parserFlags);
} else if (!result && args.else && ((typeof args.else === 'string' && args.else !== '') || args.else instanceof SlashCommandClosure)) {
if (args.else instanceof SlashCommandClosure) return (await args.else.execute(args._scope)).pipe;
return await executeSubCommands(args.else, args._scope);
return await executeSubCommands(args.else, args._scope, args._parserFlags);
}
return '';
@ -509,12 +509,12 @@ function evalBoolean(rule, a, b) {
* @param {string} command Command to execute. May contain escaped macro and batch separators.
* @returns {Promise<string>} Pipe result
*/
async function executeSubCommands(command, scope = null) {
async function executeSubCommands(command, scope = null, parserFlags = null) {
if (command.startsWith('"') && command.endsWith('"')) {
command = command.slice(1, -1);
}
const result = await executeSlashCommands(command, true, scope);
const result = await executeSlashCommands(command, true, scope, true, parserFlags);
if (!result || typeof result !== 'object') {
return '';