From c4c3218424ee80a161ad982510b372b7b678a8ff Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Mon, 24 Jun 2024 08:36:39 -0400 Subject: [PATCH] add /break to break out of loops --- .../slash-commands/SlashCommandBreak.js | 3 ++ .../SlashCommandBreakController.js | 7 +++++ .../slash-commands/SlashCommandClosure.js | 30 ++++++++++++++----- .../SlashCommandClosureResult.js | 1 + .../slash-commands/SlashCommandParser.js | 21 +++++++++++++ 5 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 public/scripts/slash-commands/SlashCommandBreak.js create mode 100644 public/scripts/slash-commands/SlashCommandBreakController.js diff --git a/public/scripts/slash-commands/SlashCommandBreak.js b/public/scripts/slash-commands/SlashCommandBreak.js new file mode 100644 index 000000000..bb3586c2f --- /dev/null +++ b/public/scripts/slash-commands/SlashCommandBreak.js @@ -0,0 +1,3 @@ +import { SlashCommandExecutor } from './SlashCommandExecutor.js'; + +export class SlashCommandBreak extends SlashCommandExecutor {} diff --git a/public/scripts/slash-commands/SlashCommandBreakController.js b/public/scripts/slash-commands/SlashCommandBreakController.js new file mode 100644 index 000000000..c63cf491f --- /dev/null +++ b/public/scripts/slash-commands/SlashCommandBreakController.js @@ -0,0 +1,7 @@ +export class SlashCommandBreakController { + /**@type {boolean} */ isBreak = false; + + break() { + this.isBreak = true; + } +} diff --git a/public/scripts/slash-commands/SlashCommandClosure.js b/public/scripts/slash-commands/SlashCommandClosure.js index abfee8ff9..ee3d72df6 100644 --- a/public/scripts/slash-commands/SlashCommandClosure.js +++ b/public/scripts/slash-commands/SlashCommandClosure.js @@ -2,6 +2,8 @@ import { substituteParams } from '../../script.js'; import { delay, escapeRegex } from '../utils.js'; import { SlashCommand } from './SlashCommand.js'; import { SlashCommandAbortController } from './SlashCommandAbortController.js'; +import { SlashCommandBreak } from './SlashCommandBreak.js'; +import { SlashCommandBreakController } from './SlashCommandBreakController.js'; import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js'; import { SlashCommandClosureResult } from './SlashCommandClosureResult.js'; import { SlashCommandDebugController } from './SlashCommandDebugController.js'; @@ -18,6 +20,7 @@ export class SlashCommandClosure { /**@type {SlashCommandNamedArgumentAssignment[]}*/ providedArgumentList = []; /**@type {SlashCommandExecutor[]}*/ executorList = []; /**@type {SlashCommandAbortController}*/ abortController; + /**@type {SlashCommandBreakController}*/ breakController; /**@type {SlashCommandDebugController}*/ debugController; /**@type {(done:number, total:number)=>void}*/ onProgress; /**@type {string}*/ rawText; @@ -89,6 +92,7 @@ export class SlashCommandClosure { closure.providedArgumentList = this.providedArgumentList; closure.executorList = this.executorList; closure.abortController = this.abortController; + closure.breakController = this.breakController; closure.debugController = this.debugController; closure.onProgress = this.onProgress; return closure; @@ -131,6 +135,7 @@ export class SlashCommandClosure { /**@type {SlashCommandClosure}*/ const closure = v; closure.scope.parent = this.scope; + closure.breakController = this.breakController; if (closure.executeNow) { v = (await closure.execute())?.pipe; } else { @@ -154,6 +159,7 @@ export class SlashCommandClosure { /**@type {SlashCommandClosure}*/ const closure = v; closure.scope.parent = this.scope; + closure.breakController = this.breakController; if (closure.executeNow) { v = (await closure.execute())?.pipe; } else { @@ -172,13 +178,12 @@ export class SlashCommandClosure { this.scope.setVariable(arg.name, v); } - let done = 0; if (this.executorList.length == 0) { this.scope.pipe = ''; } const stepper = this.executeStep(); let step; - while (!step?.done) { + while (!step?.done && !this.breakController?.isBreak) { // get executor before execution step = await stepper.next(); if (step.value instanceof SlashCommandBreakPoint) { @@ -189,8 +194,8 @@ export class SlashCommandClosure { step = await stepper.next(); // get next executor step = await stepper.next(); - const hasImmediateClosureInNamedArgs = step.value.namedArgumentList.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow); - const hasImmediateClosureInUnnamedArgs = step.value.unnamedArgumentList.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow); + const hasImmediateClosureInNamedArgs = step.value?.namedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow); + const hasImmediateClosureInUnnamedArgs = step.value?.unnamedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow); if (hasImmediateClosureInNamedArgs || hasImmediateClosureInUnnamedArgs) { this.debugController.isStepping = yield { closure:this, executor:step.value }; } else { @@ -198,10 +203,16 @@ export class SlashCommandClosure { this.debugController.stepStack[this.debugController.stepStack.length - 1] = true; } } + } else if (step.value instanceof SlashCommandBreak) { + console.log('encountered SlashCommandBreak'); + if (this.breakController) { + this.breakController?.break(); + break; + } } else if (!step.done && this.debugController?.testStepping(this)) { this.debugController.isSteppingInto = false; - const hasImmediateClosureInNamedArgs = step.value.namedArgumentList.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow); - const hasImmediateClosureInUnnamedArgs = step.value.unnamedArgumentList.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow); + const hasImmediateClosureInNamedArgs = step.value?.namedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow); + const hasImmediateClosureInUnnamedArgs = step.value?.unnamedArgumentList?.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow); if (hasImmediateClosureInNamedArgs || hasImmediateClosureInUnnamedArgs) { this.debugController.isStepping = yield { closure:this, executor:step.value }; } @@ -222,7 +233,7 @@ export class SlashCommandClosure { return step.value; } /**@type {SlashCommandClosureResult} */ - const result = Object.assign(new SlashCommandClosureResult(), { pipe: this.scope.pipe }); + const result = Object.assign(new SlashCommandClosureResult(), { pipe: this.scope.pipe, isBreak: this.breakController?.isBreak ?? false }); this.debugController?.up(); return result; } @@ -236,6 +247,9 @@ export class SlashCommandClosure { // no execution for breakpoints, just raise counter done++; yield executor; + } else if (executor instanceof SlashCommandBreak) { + done += this.executorList.length - this.executorList.indexOf(executor); + yield executor; } else { /**@type {import('./SlashCommand.js').NamedArguments} */ let args = { @@ -251,6 +265,7 @@ export class SlashCommandClosure { /**@type {SlashCommandClosure}*/ const closure = arg.value; closure.scope.parent = this.scope; + closure.breakController = this.breakController; if (closure.executeNow) { args[arg.name] = (await closure.execute())?.pipe; } else { @@ -282,6 +297,7 @@ export class SlashCommandClosure { /**@type {SlashCommandClosure}*/ const closure = v; closure.scope.parent = this.scope; + closure.breakController = this.breakController; if (closure.executeNow) { v = (await closure.execute())?.pipe; } else { diff --git a/public/scripts/slash-commands/SlashCommandClosureResult.js b/public/scripts/slash-commands/SlashCommandClosureResult.js index 740d09a9d..4ea0a6ed6 100644 --- a/public/scripts/slash-commands/SlashCommandClosureResult.js +++ b/public/scripts/slash-commands/SlashCommandClosureResult.js @@ -1,6 +1,7 @@ export class SlashCommandClosureResult { /**@type {boolean}*/ interrupt = false; /**@type {string}*/ pipe; + /**@type {boolean}*/ isBreak = false; /**@type {boolean}*/ isAborted = false; /**@type {boolean}*/ isQuietlyAborted = false; /**@type {string}*/ abortReason; diff --git a/public/scripts/slash-commands/SlashCommandParser.js b/public/scripts/slash-commands/SlashCommandParser.js index 84a37b914..a3688554b 100644 --- a/public/scripts/slash-commands/SlashCommandParser.js +++ b/public/scripts/slash-commands/SlashCommandParser.js @@ -20,6 +20,7 @@ import { MacroAutoCompleteOption } from '../autocomplete/MacroAutoCompleteOption import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js'; import { SlashCommandDebugController } from './SlashCommandDebugController.js'; import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js'; +import { SlashCommandBreak } from './SlashCommandBreak.js'; /** @typedef {import('./SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture */ /** @typedef {import('./SlashCommand.js').NamedArguments} NamedArguments */ @@ -162,6 +163,11 @@ export class SlashCommandParser { helpString: 'Set a breakpoint for debugging in the QR Editor.', })); } + if (!Object.keys(this.commands).includes('break')) { + SlashCommandParser.addCommandObjectUnsafe(SlashCommand.fromProps({ name: 'break', + helpString: 'Break out of a loop.', + })); + } //TODO should not be re-registered from every instance this.registerLanguage(); @@ -644,6 +650,9 @@ export class SlashCommandParser { if (this.debugController) { closure.executorList.push(bp); } + } else if (this.testBreak()) { + const b = this.parseBreak(); + closure.executorList.push(b); } else if (this.testCommand()) { const cmd = this.parseCommand(); cmd.injectPipe = injectPipe; @@ -687,6 +696,18 @@ export class SlashCommandParser { return bp; } + testBreak() { + return this.testSymbol(/\/break(\s|\||$)/); + } + parseBreak() { + const b = new SlashCommandBreak(); + b.start = this.index + 1; + this.take('/break'.length); + b.end = this.index; + this.commandIndex.push(b); + return b; + } + testComment() { return this.testSymbol(/\/[/#]/); }