mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
escapes only for symbols
This commit is contained in:
@@ -192,18 +192,66 @@ export class SlashCommandParser {
|
|||||||
return executor;
|
return executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the index <length> number of characters forward and returns the last character taken.
|
||||||
|
* @param {number} length Number of characters to take.
|
||||||
|
* @param {boolean} keep Whether to add the characters to the kept text.
|
||||||
|
* @returns The last character taken.
|
||||||
|
*/
|
||||||
take(length = 1, keep = false) {
|
take(length = 1, keep = false) {
|
||||||
let content = '';
|
let content = this.char;
|
||||||
while (length-- > 0) {
|
this.index++;
|
||||||
content += this.char;
|
|
||||||
this.index++;
|
|
||||||
}
|
|
||||||
if (keep) this.keptText += content;
|
if (keep) this.keptText += content;
|
||||||
|
if (length > 1) {
|
||||||
|
content = this.take(length - 1, keep);
|
||||||
|
}
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
discardWhitespace() {
|
discardWhitespace() {
|
||||||
while (/\s/.test(this.char)) this.take(); // discard whitespace
|
while (/\s/.test(this.char)) this.take(); // discard whitespace
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Tests if the next characters match a symbol.
|
||||||
|
* Moves the index forward if the next characters are backslashes directly followed by the symbol.
|
||||||
|
* Expects that the current char is taken after testing.
|
||||||
|
* @param {string|RegExp} sequence Sequence of chars or regex character group that is the symbol.
|
||||||
|
* @param {number} offset Offset from the current index (won't move the index if offset != 0).
|
||||||
|
* @returns Whether the next characters are the indicated symbol.
|
||||||
|
*/
|
||||||
|
testSymbol(sequence, offset = 0) {
|
||||||
|
// /echo abc | /echo def
|
||||||
|
// -> TOAST: abc
|
||||||
|
// -> TOAST: def
|
||||||
|
// /echo abc \| /echo def
|
||||||
|
// -> TOAST: abc | /echo def
|
||||||
|
// /echo abc \\| /echo def
|
||||||
|
// -> TOAST: abc \
|
||||||
|
// -> TOAST: def
|
||||||
|
// /echo abc \\\| /echo def
|
||||||
|
// -> TOAST: abc \| /echo def
|
||||||
|
// /echo abc \\\\| /echo def
|
||||||
|
// -> TOAST: abc \\
|
||||||
|
// -> TOAST: def
|
||||||
|
const escapes = this.text.slice(this.index + offset).replace(/^(\\*).*$/s, '$1').length;
|
||||||
|
const test = (sequence instanceof RegExp) ?
|
||||||
|
(text) => new RegExp(`^${sequence.source}`).test(text) :
|
||||||
|
(text) => text.startsWith(sequence)
|
||||||
|
;
|
||||||
|
if (test(this.text.slice(this.index + offset + escapes))) {
|
||||||
|
// no backslashes before sequence
|
||||||
|
// -> sequence found
|
||||||
|
if (escapes == 0) return true;
|
||||||
|
// uneven number of backslashes before sequence
|
||||||
|
// = the final backslash escapes the sequence
|
||||||
|
// = every preceding pair is one literal backslash
|
||||||
|
// -> move index forward to skip the backslash escaping the first backslash or the symbol
|
||||||
|
// even number of backslashes before sequence
|
||||||
|
// = every pair is one literal backslash
|
||||||
|
// -> move index forward to skip the backslash escaping the first backslash
|
||||||
|
if (offset == 0) this.index++;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
parse(text, verifyCommandNames = true) {
|
parse(text, verifyCommandNames = true) {
|
||||||
@@ -220,11 +268,11 @@ export class SlashCommandParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testClosure() {
|
testClosure() {
|
||||||
return this.ahead.length > 0 && this.char == '{' && this.ahead[0] == ':' && this.behind.slice(-1) != '\\';
|
return this.testSymbol('{:');
|
||||||
}
|
}
|
||||||
testClosureEnd() {
|
testClosureEnd() {
|
||||||
if (this.ahead.length < 1) throw new SlashCommandParserError(`Unclosed closure at position ${this.index - 2}`, this.text, this.index);
|
if (this.ahead.length < 1) throw new SlashCommandParserError(`Unclosed closure at position ${this.index - 2}`, this.text, this.index);
|
||||||
return this.char == ':' && this.ahead[0] == '}' && this.behind.slice(-1) != '\\';
|
return this.testSymbol(':}');
|
||||||
}
|
}
|
||||||
parseClosure() {
|
parseClosure() {
|
||||||
let injectPipe = true;
|
let injectPipe = true;
|
||||||
@@ -253,17 +301,18 @@ export class SlashCommandParser {
|
|||||||
}
|
}
|
||||||
this.discardWhitespace();
|
this.discardWhitespace();
|
||||||
// first pipe marks end of command
|
// first pipe marks end of command
|
||||||
if (this.char == '|') {
|
if (this.testSymbol('|')) {
|
||||||
this.take(); // discard first pipe
|
this.take(); // discard first pipe
|
||||||
// second pipe indicates no pipe injection for the next command
|
// second pipe indicates no pipe injection for the next command
|
||||||
if (this.char == '|') {
|
if (this.testSymbol('|')) {
|
||||||
injectPipe = false;
|
injectPipe = false;
|
||||||
|
this.take(); // discard second pipe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (/\s|\|/.test(this.char)) this.take(); // discard whitespace and further pipes (command separator)
|
this.discardWhitespace(); // discard further whitespace
|
||||||
}
|
}
|
||||||
this.take(2); // discard closing :}
|
this.take(2); // discard closing :}
|
||||||
if (this.char == '(' && this.ahead[0] == ')') {
|
if (this.testSymbol('()')) {
|
||||||
this.take(2); // discard ()
|
this.take(2); // discard ()
|
||||||
closure.executeNow = true;
|
closure.executeNow = true;
|
||||||
}
|
}
|
||||||
@@ -273,7 +322,7 @@ export class SlashCommandParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testRunShorthand() {
|
testRunShorthand() {
|
||||||
return this.ahead.length > 1 && this.char == '/' && this.behind.slice(-1) != '\\' && this.ahead[0] == ':' && this.ahead[1] != '}';
|
return this.testSymbol('/:') && !this.testSymbol(':}', 1);
|
||||||
}
|
}
|
||||||
testRunShorthandEnd() {
|
testRunShorthandEnd() {
|
||||||
return this.testCommandEnd();
|
return this.testCommandEnd();
|
||||||
@@ -308,10 +357,10 @@ export class SlashCommandParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testCommand() {
|
testCommand() {
|
||||||
return this.char == '/' && this.behind.slice(-1) != '\\' && !['/', '#'].includes(this.ahead[0]) && !(this.ahead[0] == ':' && this.ahead[1] != '}');
|
return this.testSymbol('/') && !this.testSymbol('//') && !this.testSymbol('/#') && !this.testSymbol(':}', 1);
|
||||||
}
|
}
|
||||||
testCommandEnd() {
|
testCommandEnd() {
|
||||||
return this.testClosureEnd() || this.endOfText || (this.char == '|' && this.behind.slice(-1) != '\\');
|
return this.testClosureEnd() || this.testSymbol('|');
|
||||||
}
|
}
|
||||||
parseCommand() {
|
parseCommand() {
|
||||||
const start = this.index + 1;
|
const start = this.index + 1;
|
||||||
@@ -401,52 +450,52 @@ export class SlashCommandParser {
|
|||||||
if (listValues.length == 1) return listValues[0];
|
if (listValues.length == 1) return listValues[0];
|
||||||
return listValues;
|
return listValues;
|
||||||
}
|
}
|
||||||
return value.trim().replace(/\\([\s{:])/g, '$1');
|
return value.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
testQuotedValue() {
|
testQuotedValue() {
|
||||||
return this.char == '"' && this.behind.slice(-1) != '\\';
|
return this.testSymbol('"');
|
||||||
}
|
}
|
||||||
testQuotedValueEnd() {
|
testQuotedValueEnd() {
|
||||||
if (this.endOfText) {
|
if (this.endOfText) {
|
||||||
if (this.verifyCommandNames) throw new SlashCommandParserError(`Unexpected end of quoted value at position ${this.index}`, this.text, this.index);
|
if (this.verifyCommandNames) throw new SlashCommandParserError(`Unexpected end of quoted value at position ${this.index}`, this.text, this.index);
|
||||||
else return true;
|
else return true;
|
||||||
}
|
}
|
||||||
if (!this.verifyCommandNames && this.char == ':' && this.ahead == '}') return true;
|
if (!this.verifyCommandNames && this.testClosureEnd()) return true;
|
||||||
return this.char == '"' && this.behind.slice(-1) != '\\';
|
return this.testSymbol('"');
|
||||||
}
|
}
|
||||||
parseQuotedValue() {
|
parseQuotedValue() {
|
||||||
this.take(); // discard opening quote
|
this.take(); // discard opening quote
|
||||||
let value = '';
|
let value = '';
|
||||||
while (!this.testQuotedValueEnd()) value += this.take(); // take all chars until closing quote
|
while (!this.testQuotedValueEnd()) value += this.take(); // take all chars until closing quote
|
||||||
this.take(); // discard closing quote
|
this.take(); // discard closing quote
|
||||||
return value.replace(/\\(")/g, '$1');
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
testListValue() {
|
testListValue() {
|
||||||
return this.char == '[' && this.behind.slice(-1) != '\\';
|
return this.testSymbol('[');
|
||||||
}
|
}
|
||||||
testListValueEnd() {
|
testListValueEnd() {
|
||||||
if (this.endOfText) throw new SlashCommandParserError(`Unexpected end of list value at position ${this.index}`, this.text, this.index);
|
if (this.endOfText) throw new SlashCommandParserError(`Unexpected end of list value at position ${this.index}`, this.text, this.index);
|
||||||
return this.char == ']' && this.behind.slice(-1) != '\\';
|
return this.testSymbol(']');
|
||||||
}
|
}
|
||||||
parseListValue() {
|
parseListValue() {
|
||||||
let value = '';
|
let value = '';
|
||||||
while (!this.testListValueEnd()) value += this.take(); // take all chars until closing bracket
|
while (!this.testListValueEnd()) value += this.take(); // take all chars until closing bracket
|
||||||
value += this.take(); // take closing bracket
|
value += this.take(); // take closing bracket
|
||||||
return value.replace(/\\([[\]])/g, '$1');
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
testValue() {
|
testValue() {
|
||||||
return !/\s/.test(this.char);
|
return !this.testSymbol(/\s/);
|
||||||
}
|
}
|
||||||
testValueEnd() {
|
testValueEnd() {
|
||||||
if (/\s/.test(this.char) && this.behind.slice(-1) != '\\') return true;
|
if (this.testSymbol(/\s/)) return true;
|
||||||
return this.testCommandEnd();
|
return this.testCommandEnd();
|
||||||
}
|
}
|
||||||
parseValue() {
|
parseValue() {
|
||||||
let value = '';
|
let value = '';
|
||||||
while (!this.testValueEnd()) value += this.take(); // take all chars until value end
|
while (!this.testValueEnd()) value += this.take(); // take all chars until value end
|
||||||
return value.replace(/\\([\s{:])/g, '$1');
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user