debugger basics rough

This commit is contained in:
LenAnderson
2024-06-18 14:29:29 -04:00
parent dcbadcb5f9
commit eb02ca95f9
10 changed files with 431 additions and 12 deletions

View File

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

View File

@ -2,8 +2,10 @@ import { substituteParams } from '../../script.js';
import { delay, escapeRegex } from '../utils.js';
import { SlashCommand } from './SlashCommand.js';
import { SlashCommandAbortController } from './SlashCommandAbortController.js';
import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';
import { SlashCommandClosureExecutor } from './SlashCommandClosureExecutor.js';
import { SlashCommandClosureResult } from './SlashCommandClosureResult.js';
import { SlashCommandDebugController } from './SlashCommandDebugController.js';
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
import { SlashCommandScope } from './SlashCommandScope.js';
@ -17,6 +19,7 @@ export class SlashCommandClosure {
/**@type {SlashCommandNamedArgumentAssignment[]}*/ providedArgumentList = [];
/**@type {SlashCommandExecutor[]}*/ executorList = [];
/**@type {SlashCommandAbortController}*/ abortController;
/**@type {SlashCommandDebugController}*/ debugController;
/**@type {(done:number, total:number)=>void}*/ onProgress;
/**@type {string}*/ rawText;
@ -87,6 +90,7 @@ export class SlashCommandClosure {
closure.providedArgumentList = this.providedArgumentList;
closure.executorList = this.executorList;
closure.abortController = this.abortController;
closure.debugController = this.debugController;
closure.onProgress = this.onProgress;
return closure;
}
@ -97,10 +101,29 @@ export class SlashCommandClosure {
*/
async execute() {
const closure = this.getCopy();
return await closure.executeDirect();
const gen = closure.executeDirect();
let step;
while (!step?.done) {
step = await gen.next(this.debugController?.isStepping ?? false);
if (!(step.value instanceof SlashCommandClosureResult) && this.debugController) {
this.debugController.isStepping = await this.debugController.awaitBreakPoint(step.value.closure, step.value.executor);
}
}
return step.value;
}
async executeDirect() {
async * executeGenerator() {
const closure = this.getCopy();
const gen = closure.executeDirect();
let step;
while (!step?.done) {
step = await gen.next(this.debugController?.isStepping);
this.debugController.isStepping = yield step.value;
}
return step.value;
}
async * executeDirect() {
// closure arguments
for (const arg of this.argumentList) {
let v = arg.value;
@ -153,7 +176,7 @@ export class SlashCommandClosure {
if (this.executorList.length == 0) {
this.scope.pipe = '';
}
for (const executor of this.executorList) {
for (const executor of [] ?? this.executorList) {
this.onProgress?.(done, this.commandCount);
if (executor instanceof SlashCommandClosureExecutor) {
const closure = this.scope.getVariable(executor.name);
@ -258,10 +281,156 @@ export class SlashCommandClosure {
}
}
}
const stepper = this.executeStep();
let step;
while (!step?.done) {
// get executor before execution
step = await stepper.next();
if (step.value instanceof SlashCommandBreakPoint) {
console.log('encountered SlashCommandBreakPoint');
if (this.debugController) {
// "execute" breakpoint
step = await stepper.next();
// get next executor
step = await stepper.next();
this.debugController.isStepping = yield { closure:this, executor:step.value };
}
} else if (!step.done && this.debugController?.isStepping) {
this.debugController.isSteppingInto = false;
this.debugController.isStepping = yield { closure:this, executor:step.value };
}
// execute executor
step = await stepper.next();
}
// if execution has returned a closure result, return that (should only happen on abort)
if (step.value instanceof SlashCommandClosureResult) {
return step.value;
}
/**@type {SlashCommandClosureResult} */
const result = Object.assign(new SlashCommandClosureResult(), { pipe: this.scope.pipe });
return result;
}
async * executeStep() {
let done = 0;
for (const executor of this.executorList) {
this.onProgress?.(done, this.commandCount);
yield executor;
if (executor instanceof SlashCommandClosureExecutor) {
const closure = this.scope.getVariable(executor.name);
if (!closure || !(closure instanceof SlashCommandClosure)) throw new Error(`${executor.name} is not a closure.`);
closure.scope.parent = this.scope;
closure.providedArgumentList = executor.providedArgumentList;
const result = await closure.execute();
this.scope.pipe = result.pipe;
} else if (executor instanceof SlashCommandBreakPoint) {
// no execution for breakpoints, just raise counter
done++;
} else {
/**@type {import('./SlashCommand.js').NamedArguments} */
let args = {
_scope: this.scope,
_parserFlags: executor.parserFlags,
_abortController: this.abortController,
_hasUnnamedArgument: executor.unnamedArgumentList.length > 0,
};
let value;
// substitute named arguments
for (const arg of executor.namedArgumentList) {
if (arg.value instanceof SlashCommandClosure) {
/**@type {SlashCommandClosure}*/
const closure = arg.value;
closure.scope.parent = this.scope;
if (closure.executeNow) {
args[arg.name] = (await closure.execute())?.pipe;
} else {
args[arg.name] = closure;
}
} else {
args[arg.name] = this.substituteParams(arg.value);
}
// unescape named argument
if (typeof args[arg.name] == 'string') {
args[arg.name] = args[arg.name]
?.replace(/\\\{/g, '{')
?.replace(/\\\}/g, '}')
;
}
}
// substitute unnamed argument
if (executor.unnamedArgumentList.length == 0) {
if (executor.injectPipe) {
value = this.scope.pipe;
args._hasUnnamedArgument = this.scope.pipe !== null && this.scope.pipe !== undefined;
}
} else {
value = [];
for (let i = 0; i < executor.unnamedArgumentList.length; i++) {
let v = executor.unnamedArgumentList[i].value;
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 (!executor.command.splitUnnamedArgument) {
if (value.length == 1) {
value = value[0];
} else if (!value.find(it=>it instanceof SlashCommandClosure)) {
value = value.join('');
}
}
}
// unescape unnamed argument
if (typeof value == 'string') {
value = value
?.replace(/\\\{/g, '{')
?.replace(/\\\}/g, '}')
;
} else if (Array.isArray(value)) {
value = value.map(v=>{
if (typeof v == 'string') {
return v
?.replace(/\\\{/g, '{')
?.replace(/\\\}/g, '}');
}
return v;
});
}
let abortResult = await this.testAbortController();
if (abortResult) {
return abortResult;
}
executor.onProgress = (subDone, subTotal)=>this.onProgress?.(done + subDone, this.commandCount);
const isStepping = this.debugController?.isStepping;
if (this.debugController) {
this.debugController.isStepping = false || this.debugController.isSteppingInto;
}
this.scope.pipe = await executor.command.callback(args, value ?? '');
if (this.debugController) {
this.debugController.isStepping = isStepping;
}
this.#lintPipe(executor.command);
done += executor.commandCount;
this.onProgress?.(done, this.commandCount);
abortResult = await this.testAbortController();
if (abortResult) {
return abortResult;
}
}
yield executor;
}
}
async testPaused() {
while (!this.abortController?.signal?.aborted && this.abortController?.signal?.paused) {

View File

@ -0,0 +1,41 @@
import { SlashCommandClosure } from './SlashCommandClosure.js';
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
export class SlashCommandDebugController {
/**@type {boolean} */ isStepping = false;
/**@type {boolean} */ isSteppingInto = false;
/**@type {Promise<boolean>} */ continuePromise;
/**@type {(boolean)=>void} */ continueResolver;
/**@type {(closure:SlashCommandClosure, executor:SlashCommandExecutor)=>Promise<boolean>} */ onBreakPoint;
resume() {
this.continueResolver?.(false);
this.continuePromise = null;
}
step() {
this.continueResolver?.(true);
this.continuePromise = null;
}
stepInto() {
this.isSteppingInto = true;
this.continueResolver?.(true);
this.continuePromise = null;
}
async awaitContinue() {
this.continuePromise ??= new Promise(resolve=>{
this.continueResolver = resolve;
});
this.isStepping = await this.continuePromise;
return this.isStepping;
}
async awaitBreakPoint(closure, executor) {
this.isStepping = await this.onBreakPoint(closure, executor);
return this.isStepping;
}
}

View File

@ -17,6 +17,8 @@ import { SlashCommandAutoCompleteNameResult } from './SlashCommandAutoCompleteNa
import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js';
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
import { MacroAutoCompleteOption } from '../autocomplete/MacroAutoCompleteOption.js';
import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';
import { SlashCommandDebugController } from './SlashCommandDebugController.js';
/**@readonly*/
/**@enum {Number}*/
@ -85,6 +87,7 @@ export class SlashCommandParser {
/**@type {string}*/ text;
/**@type {number}*/ index;
/**@type {SlashCommandAbortController}*/ abortController;
/**@type {SlashCommandDebugController}*/ debugController;
/**@type {SlashCommandScope}*/ scope;
/**@type {SlashCommandClosure}*/ closure;
@ -560,12 +563,13 @@ export class SlashCommandParser {
}
parse(text, verifyCommandNames = true, flags = null, abortController = null) {
parse(text, verifyCommandNames = true, flags = null, abortController = null, debugController = null) {
this.verifyCommandNames = verifyCommandNames;
for (const key of Object.keys(PARSER_FLAG)) {
this.flags[PARSER_FLAG[key]] = flags?.[PARSER_FLAG[key]] ?? power_user.stscript.parser.flags[PARSER_FLAG[key]] ?? false;
}
this.abortController = abortController;
this.debugController = debugController;
this.text = text;
this.index = 0;
this.scope = null;
@ -601,6 +605,7 @@ export class SlashCommandParser {
const textStart = this.index;
let closure = new SlashCommandClosure(this.scope);
closure.abortController = this.abortController;
closure.debugController = this.debugController;
this.scope = closure.scope;
this.closure = closure;
this.discardWhitespace();
@ -619,6 +624,11 @@ export class SlashCommandParser {
const cmd = this.parseRunShorthand();
closure.executorList.push(cmd);
injectPipe = true;
} else if (this.testBreakPoint()) {
const bp = this.parseBreakPoint();
if (this.debugController) {
closure.executorList.push(bp);
}
} else if (this.testCommand()) {
const cmd = this.parseCommand();
cmd.injectPipe = injectPipe;
@ -650,6 +660,17 @@ export class SlashCommandParser {
return closure;
}
testBreakPoint() {
return this.testSymbol(/\/breakpoint\s*\|/);
}
parseBreakPoint() {
const bp = new SlashCommandBreakPoint();
bp.start = this.index;
this.take('/breakpoint'.length);
bp.end = this.index;
return bp;
}
testComment() {
return this.testSymbol(/\/[/#]/);
}