add /break to break out of loops

This commit is contained in:
LenAnderson 2024-06-24 08:36:39 -04:00
parent 914e8eb4cf
commit c4c3218424
5 changed files with 55 additions and 7 deletions

View File

@ -0,0 +1,3 @@
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
export class SlashCommandBreak extends SlashCommandExecutor {}

View File

@ -0,0 +1,7 @@
export class SlashCommandBreakController {
/**@type {boolean} */ isBreak = false;
break() {
this.isBreak = true;
}
}

View File

@ -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 {

View File

@ -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;

View File

@ -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(/\/[/#]/);
}