stepOver(), rearranged dbg buttons, fixed basic debugging and saveState()

This commit is contained in:
Steven Hugg 2020-08-13 12:32:47 -05:00
parent 45ab88611e
commit e96f7e8b49
5 changed files with 87 additions and 58 deletions

View File

@ -104,6 +104,7 @@ export interface Platform {
stepBack?() : void;
runEval?(evalfunc : DebugEvalCondition) : void;
runToFrameClock?(clock : number) : void;
stepOver?() : void;
getOpcodeMetadata?(opcode:number, offset:number) : OpcodeMetadata; //TODO
getSP?() : number;
@ -199,7 +200,7 @@ export abstract class BasePlatform {
inspect(sym: string) : string {
return inspectSymbol((this as any) as Platform, sym);
}
getDebugTree() {
getDebugTree() : {} {
return this.saveState();
}
}

View File

@ -955,7 +955,7 @@ export const ECMA55_MINIMAL : BASICOptions = {
export const BASICODE : BASICOptions = {
dialectName: "BASICODE",
asciiOnly : true,
uppercaseOnly : true,
uppercaseOnly : false,
optionalLabels : false,
optionalWhitespace : true,
varNaming : "AA",

View File

@ -80,9 +80,9 @@ export class BASICRuntime {
vars : {};
arrays : {};
defs : {};
forLoops : { [varname:string] : { $next:(name:string) => void, inner:string } };
forLoops : { [varname:string] : { $next:(name:string) => void } };
forLoopStack: string[];
whileLoops : number[];
topForLoopName : string;
returnStack : number[];
column : number;
rng : RNG;
@ -145,7 +145,7 @@ export class BASICRuntime {
this.arrays = {};
this.defs = {}; // TODO? only in interpreters
this.forLoops = {};
this.topForLoopName = null;
this.forLoopStack = [];
this.whileLoops = [];
this.rng = new RNG();
// initialize arrays?
@ -157,6 +157,13 @@ export class BASICRuntime {
}
// TODO: saveState(), loadState()
saveState() {
// TODO: linked list loop?
return $.extend(true, {}, this);
}
loadState(state) {
$.extend(true, this, state);
}
getBuiltinFunctions() {
var fnames = this.program && this.opts.validFunctions;
@ -455,20 +462,20 @@ export class BASICRuntime {
// skip entire for loop before first iteration? (Minimal BASIC)
if (this.opts.testInitialFor && loopdone())
return this.skipToAfterNext(forname);
// save for var name on top of linked-list stack
var inner = this.topForLoopName;
this.topForLoopName = forname;
// save for var name on stack, remove existing entry
if (this.forLoopStack[forname] != null)
this.forLoopStack = this.forLoopStack.filter((n) => n == forname);
this.forLoopStack.push(forname);
// create for loop record
this.forLoops[forname] = {
inner: inner,
$next: (nextname:string) => {
if (nextname && forname != nextname)
this.runtimeError(`I executed NEXT "${nextname}", but the last FOR was for "${forname}".`)
this.vars[forname] += step;
var done = loopdone();
if (done) {
// pop FOR off the stack and continue
this.topForLoopName = inner;
// delete entry, pop FOR off the stack and continue
this.forLoopStack.pop();
delete this.forLoops[forname];
} else {
this.curpc = pc; // go back to FOR location
@ -479,7 +486,8 @@ export class BASICRuntime {
}
nextForLoop(name) {
var fl = this.forLoops[name || (this.opts.optionalNextVar && this.topForLoopName)];
// get FOR loop entry, or get top of stack if NEXT var is optional
var fl = this.forLoops[name || (this.opts.optionalNextVar && this.forLoopStack[this.forLoopStack.length-1])];
if (!fl) this.runtimeError(`I couldn't find a matching FOR for this NEXT.`)
fl.$next(name);
}

View File

@ -1316,6 +1316,12 @@ function singleStep() {
platform.step();
}
function stepOver() {
if (!checkRunReady()) return;
setupBreakpoint("stepover");
platform.stepOver();
}
function singleFrameStep() {
if (!checkRunReady()) return;
setupBreakpoint("tovsync");
@ -1374,17 +1380,15 @@ function resetAndDebug() {
if (!checkRunReady()) return;
var wasRecording = recorderActive;
_disableRecording();
if (platform.setupDebug && platform.readAddress) { // TODO??
if (platform.setupDebug && platform.runEval) { // TODO??
clearBreakpoint();
_resume();
platform.reset();
setupBreakpoint("restart");
if (platform.runEval)
platform.runEval((c) => { return true; }); // break immediately
else
; // TODO???
platform.runEval((c) => { return true; }); // break immediately
} else {
platform.reset();
_resume();
}
if (wasRecording) _enableRecording();
}
@ -1423,19 +1427,6 @@ function breakExpression(exprs : string) {
lastBreakExpr = exprs;
}
function getSymbolAtAddress(a : number) {
var addr2symbol = platform.debugSymbols && platform.debugSymbols.addr2symbol;
if (addr2symbol) {
if (addr2symbol[a]) return addr2symbol[a];
var i=0;
while (--a >= 0) {
i++;
if (addr2symbol[a]) return addr2symbol[a] + '+' + i;
}
}
return '';
}
function updateDebugWindows() {
if (platform.isRunning()) {
projectWindows.tick();
@ -1657,23 +1648,26 @@ function setupDebugControls() {
uitoolbar.newGroup();
uitoolbar.grp.prop('id','debug_bar');
if (platform.runEval) {
uitoolbar.add('ctrl+alt+e', 'Restart Debugging', 'glyphicon-fast-backward', resetAndDebug).prop('id','dbg_restart');
uitoolbar.add('ctrl+alt+e', 'Restart Debugging', 'glyphicon-repeat', resetAndDebug).prop('id','dbg_restart');
}
if (platform.stepBack) {
uitoolbar.add('ctrl+alt+b', 'Step Backwards', 'glyphicon-step-backward', runStepBackwards).prop('id','dbg_stepback');
}
if (platform.step) {
uitoolbar.add('ctrl+alt+s', 'Single Step', 'glyphicon-step-forward', singleStep).prop('id','dbg_step');
}
if (platform.stepOver) {
uitoolbar.add('ctrl+alt+t', 'Step Over', 'glyphicon-hand-right', stepOver).prop('id','dbg_stepover');
}
if (platform.runUntilReturn) {
uitoolbar.add('ctrl+alt+o', 'Step Out of Subroutine', 'glyphicon-hand-up', runUntilReturn).prop('id','dbg_stepout');
}
if (platform.runToVsync) {
uitoolbar.add('ctrl+alt+n', 'Next Frame/Interrupt', 'glyphicon-forward', singleFrameStep).prop('id','dbg_tovsync');
}
if ((platform.runEval || platform.runToPC) && !platform_id.startsWith('verilog')) {
uitoolbar.add('ctrl+alt+l', 'Run To Line', 'glyphicon-save', runToCursor).prop('id','dbg_toline');
}
if (platform.runUntilReturn) {
uitoolbar.add('ctrl+alt+o', 'Step Out of Subroutine', 'glyphicon-hand-up', runUntilReturn).prop('id','dbg_stepout');
}
if (platform.stepBack) {
uitoolbar.add('ctrl+alt+b', 'Step Backwards', 'glyphicon-step-backward', runStepBackwards).prop('id','dbg_stepback');
}
uitoolbar.newGroup();
// add menu clicks
$(".dropdown-menu").collapse({toggle: false});

View File

@ -1,5 +1,5 @@
import { Platform, BreakpointCallback } from "../common/baseplatform";
import { Platform, BreakpointCallback, DebugCondition, DebugEvalCondition } from "../common/baseplatform";
import { PLATFORMS, AnimationTimer, EmuHalt } from "../common/emu";
import { loadScript } from "../ide/ui";
import * as views from "../ide/views";
@ -19,11 +19,11 @@ class BASICPlatform implements Platform {
mainElement: HTMLElement;
program: BASICProgram;
runtime: BASICRuntime;
clock: number = 0;
timer: AnimationTimer;
tty: TeleTypeWithKeyboard;
clock: number = 0;
hotReload: boolean = false;
debugstop: boolean = false; // TODO: should be higher-level support
animcount: number = 0;
constructor(mainElement: HTMLElement) {
//super();
@ -69,7 +69,7 @@ class BASICPlatform implements Platform {
this.resize();
this.runtime.print = (s:string) => {
// TODO: why null sometimes?
this.clock = 0; // exit advance loop when printing
this.animcount = 0; // exit advance loop when printing
this.tty.print(s);
}
this.runtime.resume = this.resume.bind(this);
@ -78,8 +78,8 @@ class BASICPlatform implements Platform {
animate() {
if (this.tty.isBusy()) return;
var ips = this.program.opts.commandsPerSec || 1000;
this.clock += ips / 60;
while (!this.runtime.exited && this.clock-- > 0) {
this.animcount += ips / 60;
while (!this.runtime.exited && this.animcount-- > 0) {
this.advance();
}
}
@ -87,6 +87,8 @@ class BASICPlatform implements Platform {
// should not depend on tty state
advance(novideo?: boolean) : number {
if (this.runtime.running) {
if (this.checkDebugTrap())
return 0;
var more = this.runtime.step();
if (!more) {
this.pause();
@ -94,7 +96,7 @@ class BASICPlatform implements Platform {
this.exitmsg();
}
}
// TODO: break() when EmuHalt at location?
this.clock++;
return 1;
} else {
return 0;
@ -127,10 +129,7 @@ class BASICPlatform implements Platform {
reset(): void {
this.tty.clear();
this.runtime.reset();
if (this.debugstop)
this.break();
else
this.resume();
this.clock = 0;
}
pause(): void {
@ -138,11 +137,12 @@ class BASICPlatform implements Platform {
}
resume(): void {
this.clock = 0;
this.debugstop = false;
if (this.isBlocked()) return;
this.animcount = 0;
this.timer.start();
}
isBlocked() { return this.tty.waitingfor != null; } // is blocked for input?
isRunning() { return this.timer.isRunning(); }
getDefaultExtension() { return ".bas"; }
getToolForFilename() { return "basic"; }
@ -156,19 +156,18 @@ class BASICPlatform implements Platform {
}
isStable() {
return true;
}
}
getCPUState() {
return { PC: this.getPC(), SP: this.getSP() }
}
saveState() {
return {
c: this.getCPUState(),
rt: $.extend(true, {}, this.runtime) // TODO: don't take all
rt: this.runtime.saveState(),
}
}
loadState(state) {
$.extend(true, this.runtime, state);
this.runtime.loadState(state);
}
getDebugTree() {
return {
@ -180,6 +179,7 @@ class BASICPlatform implements Platform {
WhileLoops: this.runtime.whileLoops,
ReturnStack: this.runtime.returnStack,
NextDatum: this.runtime.datums[this.runtime.dataptr],
Clock: this.clock,
Options: this.runtime.opts,
Internals: this.runtime,
}
@ -194,27 +194,53 @@ class BASICPlatform implements Platform {
// TODO: debugging (get running state, etc)
onBreakpointHit : BreakpointCallback;
debugTrap : DebugCondition;
setupDebug(callback : BreakpointCallback) : void {
this.onBreakpointHit = callback;
}
clearDebug() {
this.onBreakpointHit = null;
this.debugTrap = null;
}
step() {
if (this.tty.waitingfor == null) {
checkDebugTrap() : boolean {
if (this.debugTrap && this.debugTrap()) {
this.pause();
this.advance();
this.break();
return true;
}
return false;
}
break() {
// TODO: why doesn't highlight go away on resume?
if (this.onBreakpointHit) {
this.onBreakpointHit(this.saveState());
this.debugstop = true;
}
}
step() {
var prevClock = this.clock;
this.debugTrap = () => {
return this.clock > prevClock;
};
this.resume();
}
stepOver() {
var stmt = this.runtime.getStatement();
if (stmt && (stmt.command == 'GOSUB' || stmt.command == 'ONGOSUB')) {
var nextPC = this.getPC() + 1;
this.runEval(() => this.getPC() == nextPC);
} else {
this.step();
}
}
runUntilReturn() {
var prevSP = this.getSP();
this.runEval(() => this.getSP() > prevSP);
}
runEval(evalfunc : DebugEvalCondition) {
this.debugTrap = () => evalfunc(this.getCPUState());
this.resume();
}
}
//