mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
restore old escape handling and parser flag for strict escaping
This commit is contained in:
@ -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) {
|
||||
|
@ -153,6 +153,7 @@ export class SlashCommandClosure {
|
||||
interrupt = executor.command.interruptsGeneration;
|
||||
let args = {
|
||||
_scope: this.scope,
|
||||
_parserFlags: executor.parserFlags,
|
||||
};
|
||||
let value;
|
||||
// substitute named arguments
|
||||
|
@ -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;
|
||||
|
@ -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 "/"
|
||||
|
@ -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 '';
|
||||
|
Reference in New Issue
Block a user