diff --git a/public/scripts/slash-commands/SlashCommandClosure.js b/public/scripts/slash-commands/SlashCommandClosure.js index 0480d372c..00ae3cef8 100644 --- a/public/scripts/slash-commands/SlashCommandClosure.js +++ b/public/scripts/slash-commands/SlashCommandClosure.js @@ -1,4 +1,5 @@ import { substituteParams } from '../../script.js'; +import { SlashCommandClosureExecutor } from './SlashCommandClosureExecutor.js'; import { SlashCommandClosureResult } from './SlashCommandClosureResult.js'; import { SlashCommandExecutor } from './SlashCommandExecutor.js'; import { SlashCommandScope } from './SlashCommandScope.js'; @@ -10,7 +11,7 @@ export class SlashCommandClosure { /**@type {Map}*/ arguments = {}; // @ts-ignore /**@type {Map}*/ providedArguments = {}; - /**@type {SlashCommandExecutor[]}*/ executorList = []; + /**@type {(SlashCommandExecutor|SlashCommandClosureExecutor)[]}*/ executorList = []; /**@type {String}*/ keptText; constructor(parent) { @@ -39,6 +40,10 @@ export class SlashCommandClosure { return closure; } + /** + * + * @returns Promise + */ async execute() { const closure = this.getCopy(); return await closure.executeDirect(); @@ -98,82 +103,94 @@ export class SlashCommandClosure { } for (const executor of this.executorList) { - interrupt = executor.command.interruptsGeneration; - let args = { - _scope: this.scope, - }; - let value; - // substitute named arguments - for (const key of Object.keys(executor.args)) { - if (executor.args[key] instanceof SlashCommandClosure) { + if (executor instanceof SlashCommandClosureExecutor) { + const closure = this.scope.getVariable(executor.name); + if (!closure || !(closure instanceof SlashCommandClosure)) throw new Error(`${name} is not a closure.`); + closure.scope.parent = this.scope; + closure.providedArguments = executor.providedArguments; + const result = await closure.execute(); + this.scope.pipe = result.pipe; + interrupt = result.interrupt; + } else { + interrupt = executor.command.interruptsGeneration; + let args = { + _scope: this.scope, + }; + let value; + // substitute named arguments + for (const key of Object.keys(executor.args)) { + if (executor.args[key] instanceof SlashCommandClosure) { + /**@type {SlashCommandClosure}*/ + const closure = executor.args[key]; + closure.scope.parent = this.scope; + if (closure.executeNow) { + args[key] = (await closure.execute())?.pipe; + } else { + args[key] = closure; + } + } else { + args[key] = this.substituteParams(executor.args[key]); + } + // unescape named argument + if (typeof args[key] == 'string') { + args[key] = args[key] + ?.replace(/\\\|/g, '|') + ?.replace(/\\\{/g, '{') + ?.replace(/\\\}/g, '}') + ; + } + } + + // substitute unnamed argument + if (executor.value === undefined) { + value = this.scope.pipe; + } else if (executor.value instanceof SlashCommandClosure) { /**@type {SlashCommandClosure}*/ - const closure = executor.args[key]; + const closure = executor.value; closure.scope.parent = this.scope; if (closure.executeNow) { - args[key] = (await closure.execute())?.pipe; + value = (await closure.execute())?.pipe; } else { - args[key] = closure; + value = closure; + } + } else if (Array.isArray(executor.value)) { + value = []; + for (let i = 0; i < executor.value.length; i++) { + let v = executor.value[i]; + if (v instanceof SlashCommandClosure) { + /**@type {SlashCommandClosure}*/ + const closure = v; + closure.scope.parent = this.scope; + if (closure.executeNow) { + v = (await closure.execute())?.pipe; + } else { + v = closure; + } + } else { + v = this.substituteParams(v); + } + value[i] = v; + } + if (!value.find(it=>it instanceof SlashCommandClosure)) { + value = value.join(' '); } } else { - args[key] = this.substituteParams(executor.args[key]); + value = this.substituteParams(executor.value); } - // unescape named argument - if (typeof args[key] == 'string') { - args[key] = args[key] + // unescape unnamed argument + if (typeof value == 'string') { + value = value ?.replace(/\\\|/g, '|') ?.replace(/\\\{/g, '{') ?.replace(/\\\}/g, '}') ; } - } - // substitute unnamed argument - if (executor.value === undefined) { - value = this.scope.pipe; - } else if (executor.value instanceof SlashCommandClosure) { - /**@type {SlashCommandClosure}*/ - const closure = executor.value; - closure.scope.parent = this.scope; - if (closure.executeNow) { - value = (await closure.execute())?.pipe; - } else { - value = closure; - } - } else if (Array.isArray(executor.value)) { - value = []; - for (let i = 0; i < executor.value.length; i++) { - let v = executor.value[i]; - if (v instanceof SlashCommandClosure) { - /**@type {SlashCommandClosure}*/ - const closure = v; - closure.scope.parent = this.scope; - if (closure.executeNow) { - v = (await closure.execute())?.pipe; - } else { - v = closure; - } - } else { - v = this.substituteParams(v); - } - value[i] = v; - } - if (!value.find(it=>it instanceof SlashCommandClosure)) { - value = value.join(' '); - } - } else { - value = this.substituteParams(executor.value); + this.scope.pipe = await executor.command.callback(args, value); } - // unescape unnamed argument - if (typeof value == 'string') { - value = value - ?.replace(/\\\|/g, '|') - ?.replace(/\\\{/g, '{') - ?.replace(/\\\}/g, '}') - ; - } - - this.scope.pipe = await executor.command.callback(args, value); } - return Object.assign(new SlashCommandClosureResult(), { interrupt, newText: this.keptText, pipe: this.scope.pipe }); + /**@type {SlashCommandClosureResult} */ + const result = Object.assign(new SlashCommandClosureResult(), { interrupt, newText: this.keptText, pipe: this.scope.pipe }); + return result; } } diff --git a/public/scripts/slash-commands/SlashCommandClosureExecutor.js b/public/scripts/slash-commands/SlashCommandClosureExecutor.js new file mode 100644 index 000000000..0f932b174 --- /dev/null +++ b/public/scripts/slash-commands/SlashCommandClosureExecutor.js @@ -0,0 +1,5 @@ +export class SlashCommandClosureExecutor { + /**@type {String}*/ name = ''; + // @ts-ignore + /**@type {Map}*/ providedArguments = {}; +} diff --git a/public/scripts/slash-commands/SlashCommandParser.js b/public/scripts/slash-commands/SlashCommandParser.js index 7d14f3763..c8b07f919 100644 --- a/public/scripts/slash-commands/SlashCommandParser.js +++ b/public/scripts/slash-commands/SlashCommandParser.js @@ -1,5 +1,6 @@ import { SlashCommand } from './SlashCommand.js'; import { SlashCommandClosure } from './SlashCommandClosure.js'; +import { SlashCommandClosureExecutor } from './SlashCommandClosureExecutor.js'; import { SlashCommandExecutor } from './SlashCommandExecutor.js'; import { SlashCommandParserError } from './SlashCommandParserError.js'; // eslint-disable-next-line no-unused-vars @@ -232,6 +233,9 @@ export class SlashCommandParser { this.discardWhitespace(); } while (!this.testClosureEnd()) { + if (this.testClosureCall()) { + closure.executorList.push(this.parseClosureCall()); + } if (this.testCommand()) { closure.executorList.push(this.parseCommand()); } else { @@ -260,6 +264,37 @@ export class SlashCommandParser { return closure; } + testClosureCall() { + return this.char == '/' + && this.behind.slice(-1) != '\\' + && !['/', '#'].includes(this.ahead[0]) + && /^\S+\(/.test(this.ahead) + ; + } + testClosureCallEnd() { + return this.char == ')' && this.behind.slice(-1) != '\\'; + } + parseClosureCall() { + this.take(); // discard / + let name = ''; + while (!/\s|\(/.test(this.char)) name += this.take(); // take chars until opening ( + this.take(); // discard opening ( + const executor = new SlashCommandClosureExecutor(); + executor.name = name; + this.discardWhitespace(); + while (this.testNamedArgument()) { + const arg = this.parseNamedArgument(); + executor.providedArguments[arg.key] = arg.value; + this.discardWhitespace(); + } + if (this.testClosureCallEnd()) { + this.take(); // discard closing ) + return executor; + } else { + throw new SlashCommandParserError(`Unexpected end of closure call at position ${this.index - 2}`, this.text, this.index); + } + } + testCommand() { return this.char == '/' && this.behind.slice(-1) != '\\' && !['/', '#'].includes(this.ahead[0]); }