From e96f7e8b49bded41176d33199cffd5a2d781a0b2 Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Thu, 13 Aug 2020 12:32:47 -0500 Subject: [PATCH] stepOver(), rearranged dbg buttons, fixed basic debugging and saveState() --- src/common/baseplatform.ts | 3 +- src/common/basic/compiler.ts | 2 +- src/common/basic/runtime.ts | 28 +++++++++------ src/ide/ui.ts | 44 ++++++++++------------- src/platform/basic.ts | 68 +++++++++++++++++++++++++----------- 5 files changed, 87 insertions(+), 58 deletions(-) diff --git a/src/common/baseplatform.ts b/src/common/baseplatform.ts index 7eefd429..8dcbba88 100644 --- a/src/common/baseplatform.ts +++ b/src/common/baseplatform.ts @@ -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(); } } diff --git a/src/common/basic/compiler.ts b/src/common/basic/compiler.ts index 2b5eefbb..d64757ae 100644 --- a/src/common/basic/compiler.ts +++ b/src/common/basic/compiler.ts @@ -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", diff --git a/src/common/basic/runtime.ts b/src/common/basic/runtime.ts index cbafc309..e701bead 100644 --- a/src/common/basic/runtime.ts +++ b/src/common/basic/runtime.ts @@ -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); } diff --git a/src/ide/ui.ts b/src/ide/ui.ts index ff7e17dd..b43be052 100644 --- a/src/ide/ui.ts +++ b/src/ide/ui.ts @@ -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}); diff --git a/src/platform/basic.ts b/src/platform/basic.ts index ec71cef4..f027aec3 100644 --- a/src/platform/basic.ts +++ b/src/platform/basic.ts @@ -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(); + } } //