First stab at factoring some common Debugger functionality into a shared component

This commit is contained in:
Jeff Parsons 2016-09-14 15:36:38 -07:00
commit a8bb0c7841
42 changed files with 3721 additions and 4457 deletions

View file

@ -68,7 +68,7 @@ if (NODE) {
* @extends Component
* @param {Object} parmsBus
* @param {CPUState} cpu
* @param {Debugger} dbg
* @param {Debugger6502} dbg
*/
function Bus(parmsBus, cpu, dbg)
{

View file

@ -152,7 +152,7 @@ function Computer(parmsComputer, parmsMachine, fSuspended) {
Component.error("Unable to find CPU component");
return;
}
this.dbg = /** @type {Debugger} */ (Component.getComponentByType("Debugger", this.id));
this.dbg = /** @type {Debugger6502} */ (Component.getComponentByType("Debugger", this.id));
/*
* Enumerate all Video components for future updateVideo() calls.

View file

@ -160,7 +160,7 @@ CPU.BUTTONS = ["power", "reset"];
* @param {Computer} cmp
* @param {Bus} bus
* @param {CPU} cpu
* @param {Debugger} dbg
* @param {Debugger6502} dbg
*/
CPU.prototype.initBus = function(cmp, bus, cpu, dbg)
{

File diff suppressed because it is too large Load diff

View file

@ -34,7 +34,12 @@
/**
* @define {string}
*/
var APPCLASS = "pc8080"; // this @define is the default application class (formerly APPCLASS) to use
var APPCLASS = "pc6502"; // this @define is the default application class (eg, "pcx86", "c1pjs")
/**
* @define {string}
*/
var APPNAME = "PC6502"; // this @define is the default application name (eg, "PCx86", "C1Pjs")
/**
* @define {boolean}
@ -69,6 +74,26 @@ var BYTEARRAYS = false;
*/
var TYPEDARRAYS = (typeof ArrayBuffer !== 'undefined');
/*
* Combine all the shared globals and machine-specific globals into one machine-specific global object,
* which all machine components should start using; eg: "if (PCX86.DEBUG) ..." instead of "if (DEBUG) ...".
*/
var PC6502 = {
APPCLASS: APPCLASS,
APPNAME: APPNAME,
APPVERSION: APPVERSION, // shared
BYTEARRAYS: BYTEARRAYS,
COMPILED: COMPILED, // shared
CSSCLASS: CSSCLASS, // shared
DEBUG: DEBUG, // shared
DEBUGGER: DEBUGGER,
MAXDEBUG: MAXDEBUG, // shared
PRIVATE: PRIVATE, // shared
TYPEDARRAYS: TYPEDARRAYS,
SITEHOST: SITEHOST, // shared
XMLVERSION: XMLVERSION // shared
};
if (NODE) {
global.APPCLASS = APPCLASS;
global.DEBUGGER = DEBUGGER;

View file

@ -237,7 +237,7 @@ Memory.prototype = {
* @this {Memory}
* @param {Memory} mem
* @param {number} [type]
* @param {Debugger} [dbg]
* @param {Debugger6502} [dbg]
*/
clone: function(mem, type, dbg) {
/*
@ -485,7 +485,7 @@ Memory.prototype = {
* copyBreakpoints(dbg, mem)
*
* @this {Memory}
* @param {Debugger} [dbg]
* @param {Debugger6502} [dbg]
* @param {Memory} [mem] (outgoing Memory block to copy breakpoints from, if any)
*/
copyBreakpoints: function(dbg, mem) {

View file

@ -87,7 +87,7 @@ Panel.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
* @param {Computer} cmp
* @param {Bus} bus
* @param {CPUState} cpu
* @param {Debugger} dbg
* @param {Debugger6502} dbg
*/
Panel.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -74,7 +74,7 @@ Component.subclass(RAM);
* @param {Computer} cmp
* @param {Bus} bus
* @param {CPUState} cpu
* @param {Debugger} dbg
* @param {Debugger6502} dbg
*/
RAM.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -133,7 +133,7 @@ Component.subclass(ROM);
* @param {Computer} cmp
* @param {Bus} bus
* @param {CPUState} cpu
* @param {Debugger} dbg
* @param {Debugger6502} dbg
*/
ROM.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -223,7 +223,7 @@ Video.COLORS = {
* @param {Computer} cmp
* @param {Bus} bus
* @param {CPUState} cpu
* @param {Debugger} dbg
* @param {Debugger6502} dbg
*/
Video.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -37,6 +37,7 @@ if (DEBUGGER) {
var usr = require("../../shared/lib/usrlib");
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var Debugger = require("../../shared/lib/debugger");
var State = require("../../shared/lib/state");
var PC8080 = require("./defines");
var CPUDef8080 = require("./cpudef");
@ -68,7 +69,7 @@ var DbgAddr8080;
* Debugger8080(parmsDbg)
*
* @constructor
* @extends Component
* @extends Debugger
* @param {Object} parmsDbg
*
* The Debugger8080 component supports the following optional (parmsDbg) properties:
@ -89,17 +90,6 @@ function Debugger8080(parmsDbg)
this.style = Debugger8080.STYLE_8080;
/*
* These keep track of instruction activity, but only when tracing or when Debugger8080 checks
* have been enabled (eg, one or more breakpoints have been set).
*
* They are zeroed by the reset() notification handler. cInstructions is advanced by
* stepCPU() and checkInstruction() calls. nCycles is updated by every stepCPU() or stop()
* call and simply represents the number of cycles performed by the last run of instructions.
*/
this.nCycles = 0;
this.cOpcodes = this.cOpcodesStart = 0;
/*
* Most commands that require an address call parseAddr(), which defaults to dbgAddrNextCode
* or dbgAddrNextData when no address has been given. doDump() and doUnassemble(), in turn,
@ -111,13 +101,6 @@ function Debugger8080(parmsDbg)
this.dbgAddrNextCode = this.newAddr();
this.dbgAddrNextData = this.newAddr();
/*
* This maintains command history. New commands are inserted at index 0 of the array.
* When Enter is pressed on an empty input buffer, we default to the command at aPrevCmds[0].
*/
this.iPrevCmd = -1;
this.aPrevCmds = [];
/*
* fAssemble is true when "assemble mode" is active, false when not.
*/
@ -138,21 +121,6 @@ function Debugger8080(parmsDbg)
*/
this.aSymbolTable = [];
/*
* aVariables is an object with properties that grows as setVariable() assigns more variables;
* each property corresponds to one variable, where the property name is the variable name (ie,
* a string beginning with a letter or underscore, followed by zero or more additional letters,
* digits, or underscores) and the property value is the variable's numeric value. See doVar()
* and setVariable() for details.
*
* Note that parseValue(), through its reliance on str.parseInt(), assumes a default base of 16
* if no base is explicitly indicated (eg, a trailing decimal period), and if you define variable
* names containing exclusively hex alpha characters (a-f), those variables will take precedence
* over the corresponding hex values. In other words, if you define variables "a" and "b", you
* will no longer be able to simply type "a" or "b" to specify the decimal values 10 or 11.
*/
this.aVariables = {};
/*
* clearBreakpoints() initializes the breakpoints lists: aBreakExec is a list of addresses
* to halt on whenever attempting to execute an instruction at the corresponding address,
@ -214,7 +182,7 @@ function Debugger8080(parmsDbg)
if (DEBUGGER) {
Component.subclass(Debugger8080);
Component.subclass(Debugger8080, Debugger);
/*
* NOTE: Every Debugger8080 property from here to the first prototype function definition (initBus()) is a
@ -2622,380 +2590,7 @@ if (DEBUGGER) {
return s;
};
Debugger8080.aBinOpPrecedence = {
'||': 0, // logical OR
'&&': 1, // logical AND
'|': 2, // bitwise OR
'^': 3, // bitwise XOR
'&': 4, // bitwise AND
'!=': 5, // inequality
'==': 5, // equality
'>=': 6, // greater than or equal to
'>': 6, // greater than
'<=': 6, // less than or equal to
'<': 6, // less than
'>>>': 7, // unsigned bitwise right shift
'>>': 7, // bitwise right shift
'<<': 7, // bitwise left shift
'-': 8, // subtraction
'+': 8, // addition
'%': 9, // remainder
'/': 9, // division
'*': 9 // multiplication
};
/**
* evalExpression(aVals, aOps, cOps)
*
* In Node, if you set a variable to 0x80000001; ie:
*
* foo=0x80000001|0
*
* and then calculate foo*foo using "(foo*foo).toString(2)", the result is:
*
* '11111111111111111111111111111100000000000000000000000000000000'
*
* which is slightly incorrect because it has overflowed JavaScript's floating-point precision.
*
* 0x80000001 in decimal is -2147483647, so the product is 4611686014132420609, which is 0x3FFFFFFF00000001.
*
* @this {Debugger8080}
* @param {Array.<number>} aVals
* @param {Array.<string>} aOps
* @param {number} [cOps] (default is all)
* @return {boolean} true if successful, false if error
*/
Debugger8080.prototype.evalExpression = function(aVals, aOps, cOps)
{
cOps = cOps || -1;
while (cOps-- && aOps.length) {
var chOp = aOps.pop();
if (aVals.length < 2) return false;
var valNew;
var val2 = aVals.pop();
var val1 = aVals.pop();
switch(chOp) {
case '*':
valNew = val1 * val2;
break;
case '/':
if (!val2) return false;
valNew = val1 / val2;
break;
case '%':
if (!val2) return false;
valNew = val1 % val2;
break;
case '+':
valNew = val1 + val2;
break;
case '-':
valNew = val1 - val2;
break;
case '<<':
valNew = val1 << val2;
break;
case '>>':
valNew = val1 >> val2;
break;
case '>>>':
valNew = val1 >>> val2;
break;
case '<':
valNew = (val1 < val2? 1 : 0);
break;
case '<=':
valNew = (val1 <= val2? 1 : 0);
break;
case '>':
valNew = (val1 > val2? 1 : 0);
break;
case '>=':
valNew = (val1 >= val2? 1 : 0);
break;
case '==':
valNew = (val1 == val2? 1 : 0);
break;
case '!=':
valNew = (val1 != val2? 1 : 0);
break;
case '&':
valNew = val1 & val2;
break;
case '^':
valNew = val1 ^ val2;
break;
case '|':
valNew = val1 | val2;
break;
case '&&':
valNew = (val1 && val2? 1 : 0);
break;
case '||':
valNew = (val1 || val2? 1 : 0);
break;
default:
return false;
}
aVals.push(valNew|0);
}
return true;
};
/**
* parseExpression(sExp, fPrint)
*
* A quick-and-dirty expression parser. It takes an expression like:
*
* EDX+EDX*4+12345678
*
* and builds a value stack in aVals and a "binop" (binary operator) stack in aOps:
*
* aVals aOps
* ----- ----
* EDX +
* EDX *
* 4 +
* ...
*
* We pop 1 "binop" from aOps and 2 values from aVals whenever a "binop" of lower priority than its
* predecessor is encountered, evaluate, and push the result back onto aVals.
*
* Unary operators like '~' and ternary operators like '?:' are not supported; neither are parentheses.
*
* However, parseReference() now makes it possible to write parenthetical-style sub-expressions by using
* {...} (braces), as well as address references by using [...] (brackets).
*
* Why am I using braces instead of parentheses for sub-expressions? Because parseReference() serves
* multiple purposes, the other being reference replacement in message strings passing through replaceRegs(),
* and I didn't want parentheses taking on a new meaning in message strings.
*
* @this {Debugger8080}
* @param {string|undefined} sExp
* @param {boolean} [fPrint] is true to print all resolved values, false for quiet parsing
* @return {number|undefined} numeric value, or undefined if sExp contains any undefined or invalid values
*/
Debugger8080.prototype.parseExpression = function(sExp, fPrint)
{
var value;
if (sExp) {
/*
* First process (and eliminate) any references, aka sub-expressions.
*/
sExp = this.parseReference(sExp);
var i = 0;
var fError = false;
var sExpOrig = sExp;
var aVals = [], aOps = [];
/*
* All browsers (including, I believe, IE9 and up) support the following idiosyncrasy of a regexp split():
* when the regexp uses a capturing pattern, the resulting array will include entries for all the pattern
* matches along with the non-matches. This effectively means that, in the set of expressions that we
* support, all even entries in asValues will contain "values" and all odd entries will contain "operators".
*
* And although I tried to list the supported operators in "precedential" order, bitwise operators must
* be out-of-order so that we don't mistakenly match either '>' or '<' when they're part of '>>' or '<<'.
*/
var regExp = /(\|\||&&|\||^|&|!=|==|>=|>>>|>>|>|<=|<<|<|-|\+|%|\/|\*)/;
var asValues = sExp.split(regExp);
while (i < asValues.length) {
var sValue = asValues[i++];
var cchValue = sValue.length;
var s = str.trim(sValue);
if (!s) {
fError = true;
break;
}
var v = this.parseValue(s, null, fPrint === false);
if (v === undefined) {
fError = true;
fPrint = false;
break;
}
aVals.push(v);
if (i == asValues.length) break;
var sOp = asValues[i++], cchOp = sOp.length;
this.assert(Debugger8080.aBinOpPrecedence[sOp] != null);
if (aOps.length && Debugger8080.aBinOpPrecedence[sOp] < Debugger8080.aBinOpPrecedence[aOps[aOps.length-1]]) {
this.evalExpression(aVals, aOps, 1);
}
aOps.push(sOp);
sExp = sExp.substr(cchValue + cchOp);
}
if (!this.evalExpression(aVals, aOps) || aVals.length != 1) {
fError = true;
}
if (!fError) {
value = aVals.pop();
if (fPrint) this.printValue(null, value);
} else {
if (fPrint) this.println("error parsing '" + sExpOrig + "' at character " + (sExpOrig.length - sExp.length));
}
}
return value;
};
/**
* parseReference(s)
*
* Returns the given string with any "{expression}" sequences replaced with the value of the expression,
* and any "[address]" references replaced with the contents of the address. Expressions are parsed BEFORE
* addresses. Owing to this function's simplistic parsing, nested braces/brackets are not supported
* (define intermediate variables if needed).
*
* @this {Debugger8080}
* @param {string} s
* @return {string}
*/
Debugger8080.prototype.parseReference = function(s)
{
var a;
while (a = s.match(/\{(.*?)}/)) {
if (a[1].indexOf('{') >= 0) break; // unsupported nested brace(s)
var value = this.parseExpression(a[1]);
s = s.replace('{' + a[1] + '}', value != null? str.toHex(value) : "undefined");
}
while (a = s.match(/\[(.*?)]/)) {
if (a[1].indexOf('[') >= 0) break; // unsupported nested bracket(s)
var dbgAddr = this.parseAddr(a[1]);
s = s.replace('[' + a[1] + ']', dbgAddr? str.toHex(this.getWord(dbgAddr), 4) : "undefined");
}
return this.parseSysVars(s);
};
/**
* parseSysVars(s)
*
* Returns the given string with any recognized "$var" replaced with its value; eg:
*
* $ops: the number of opcodes executed since the last time it was displayed (or reset)
*
* @this {Debugger8080}
* @param {string} s
* @return {string}
*/
Debugger8080.prototype.parseSysVars = function(s)
{
var a;
while (a = s.match(/\$([a-z]+)/i)) {
var v = null;
switch(a[1].toLowerCase()) {
case "ops":
v = this.cOpcodes - this.cOpcodesStart;
break;
}
if (v == null) break;
s = s.replace(a[0], v.toString());
}
return s;
};
/**
* parseValue(sValue, sName, fQuiet)
*
* @this {Debugger8080}
* @param {string|undefined} sValue
* @param {string|null} [sName] is the name of the value, if any
* @param {boolean} [fQuiet]
* @return {number|undefined} numeric value, or undefined if sValue is either undefined or invalid
*/
Debugger8080.prototype.parseValue = function(sValue, sName, fQuiet)
{
var value;
if (sValue !== undefined) {
var iReg = this.getRegIndex(sValue);
if (iReg >= 0) {
value = this.getRegValue(iReg);
} else {
value = this.getVariable(sValue);
if (value === undefined) value = str.parseInt(sValue);
}
if (value === undefined && !fQuiet) this.println("invalid " + (sName? sName : "value") + ": " + sValue);
} else {
if (!fQuiet) this.println("missing " + (sName || "value"));
}
return value;
};
/**
* printValue(sVar, value)
*
* @this {Debugger8080}
* @param {string|null} sVar
* @param {number|undefined} value
* @return {boolean} true if value defined, false if not
*/
Debugger8080.prototype.printValue = function(sVar, value)
{
var sValue;
var fDefined = false;
if (value !== undefined) {
fDefined = true;
sValue = str.toHexLong(value) + " " + value + ". (" + str.toBinBytes(value) + ")";
}
sVar = (sVar != null? (sVar + ": ") : "");
this.println(sVar + sValue);
return fDefined;
};
/**
* printVariable(sVar)
*
* @this {Debugger8080}
* @param {string} [sVar]
* @return {boolean} true if all value(s) defined, false if not
*/
Debugger8080.prototype.printVariable = function(sVar)
{
if (sVar) {
return this.printValue(sVar, this.aVariables[sVar]);
}
var cVariables = 0;
for (sVar in this.aVariables) {
this.printValue(sVar, this.aVariables[sVar]);
cVariables++;
}
return cVariables > 0;
};
/**
* delVariable(sVar)
*
* @this {Debugger8080}
* @param {string} sVar
*/
Debugger8080.prototype.delVariable = function(sVar)
{
delete this.aVariables[sVar];
};
/**
* getVariable(sVar)
*
* @this {Debugger8080}
* @param {string} sVar
* @return {number|undefined}
*/
Debugger8080.prototype.getVariable = function(sVar)
{
return this.aVariables[sVar];
};
/**
* setVariable(sVar, value)
*
* @this {Debugger8080}
* @param {string} sVar
* @param {number} value
*/
Debugger8080.prototype.setVariable = function(sVar, value)
{
this.aVariables[sVar] = value;
};
/**
/**
* comparePairs(p1, p2)
*
* @this {Debugger8080}

View file

@ -76,7 +76,8 @@ try {
* Create is the constructor returned by require(). The only bit of fudging we do is
* overriding the constructor for component "cpu" with the constructor for "x86cpu",
* because when a "cpu" definition is encountered, it's the "x86cpu" subclass that we
* actually want to create, not the "cpu" superclass.
* actually want to create, not the "cpu" superclass. See aSubClasses for the complete
* list of subclassed components we manage like that.
*
* TODO: Update the list of ignored (ie, ignorable) components.
*/
@ -84,7 +85,11 @@ var Component;
var dbg;
var aComponents = [];
var asComponentsIgnore = ["embed","save"];
var aSubClasses = {
"pcx86/lib/x86cpu": "pcx86/lib/cpu",
"pcx86/lib/debugger": "shared/lib/debugger"
};
/**
* loadComponents(asFiles)
*
@ -106,9 +111,17 @@ function loadComponents(asFiles)
* we'll want to access later (eg, "component" in getComponentByType()).
*/
var fn = require(lib + "../../../" + sFile);
if (sName == "x86cpu") {
var sSuperClass = null;
for (var s in aSubClasses) {
if (sFile.indexOf(s) >= 0) {
sSuperClass = aSubClasses[s];
break;
}
}
if (sSuperClass) {
for (var j = 0; j < aComponents.length; j++) {
if (aComponents[j].name == "cpu") {
if (aComponents[j].path.indexOf(sSuperClass) >= 0) {
if (fDebug) console.log("updating superclass " + aComponents[j].path + " with subclass " + sFile);
aComponents[j].Create = fn;
sName = null;
break;
@ -121,7 +134,7 @@ function loadComponents(asFiles)
}; // jshint ignore:line
}
if (sName) {
aComponents.push({name: sName, Create: fn, objects: []});
aComponents.push({name: sName, path: sFile, Create: fn, objects: []});
}
if (sName == "defines") {
/*

View file

@ -68,7 +68,7 @@ if (NODE) {
* @extends Component
* @param {Object} parmsBus
* @param {X86CPU} cpu
* @param {Debugger} dbg
* @param {DebuggerX86} dbg
*/
function Bus(parmsBus, cpu, dbg)
{

View file

@ -1294,7 +1294,7 @@ ChipSet.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
* @param {DebuggerX86} dbg
*/
ChipSet.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -182,7 +182,7 @@ function Computer(parmsComputer, parmsMachine, fSuspended) {
Component.error("Unable to find CPU component");
return;
}
this.dbg = /** @type {Debugger} */ (Component.getComponentByType("Debugger", this.id));
this.dbg = /** @type {DebuggerX86} */ (Component.getComponentByType("Debugger", this.id));
/*
* Enumerate all Video components for future updateVideo() calls.

View file

@ -159,7 +159,7 @@ CPU.BUTTONS = ["power", "reset"];
* @param {Computer} cmp
* @param {Bus} bus
* @param {CPU} cpu
* @param {Debugger} dbg
* @param {DebuggerX86} dbg
*/
CPU.prototype.initBus = function(cmp, bus, cpu, dbg)
{

File diff suppressed because it is too large Load diff

View file

@ -34,12 +34,12 @@
/**
* @define {string}
*/
var APPCLASS = "pcx86"; // this @define is the default application class (eg, "pcx86", "c1pjs")
var APPCLASS = "pcx86"; // this @define is the default application class (eg, "pcx86", "c1pjs")
/**
* @define {string}
*/
var APPNAME = "PCx86"; // this @define is the default application name (eg, "PCx86", "C1Pjs")
var APPNAME = "PCx86"; // this @define is the default application name (eg, "PCx86", "C1Pjs")
/**
* @define {boolean}

View file

@ -811,7 +811,7 @@ var SectorInfo;
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
* @param {DebuggerX86} dbg
*/
Disk.prototype.initBus = function(cmp, bus, cpu, dbg) {
this.dbg = dbg;

View file

@ -623,7 +623,7 @@ FDC.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
* @param {DebuggerX86} dbg
*/
FDC.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -594,7 +594,7 @@ HDC.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
* @param {DebuggerX86} dbg
*/
HDC.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -31,22 +31,6 @@
"use strict";
/*
* Components that previously used Debugger interrupt definitions by including:
*
* var Debugger = require("./debugger");
*
* and using:
*
* Debugger.INT.FOO
*
* must now instead include:
*
* var Interrupts = require("./interrupts");
*
* and then replace all occurrences of "Debugger.INT.FOO" with "Interrupts.FOO".
*/
var Interrupts = {
VIDEO: 0x10,
DISK: 0x13,
@ -184,7 +168,7 @@ if (DEBUGGER) {
0x4A8: ["SAVE_PTR",4] // POINTER TO EGA PARAMETER CONTROL BLOCK
},
/*
* See Debugger.prototype.replaceRegs() for the rules governing how register contents are replaced in the strings below.
* See DebuggerX86.prototype.replaceRegs() for the rules governing how register contents are replaced in the strings below.
*
* Replacements occur in the following order:
*
@ -1546,7 +1530,7 @@ if (DEBUGGER) {
* INT 2E Execute command (*) (2.0+)
* Entry: DS:SI-&gt;command string-1 followed by a carriage return
* Exit: All registers except CS and IP are destroyed
* Note: This function is not re-enterant and should not be issued from a
* Note: This function is not re-entrant and should not be issued from a
* batch file.
*
* INT 2F Multiplex (3.0+)
@ -1922,7 +1906,7 @@ if (DEBUGGER) {
*
* Stacks descriptor (8 bytes)
* 00 Flag (0=free,1=in use)
* 02 Savearea for SS:SP
* 02 Save area for SS:SP
* 06 Offset of end of stack
*
* Device driver header (12h bytes)

View file

@ -1324,7 +1324,7 @@ Keyboard.prototype.findBinding = function(simCode, sType, fDown)
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
* @param {DebuggerX86} dbg
*/
Keyboard.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -288,7 +288,7 @@ Memory.prototype = {
* @this {Memory}
* @param {Memory} mem
* @param {number} [type]
* @param {Debugger} [dbg]
* @param {DebuggerX86} [dbg]
*/
clone: function(mem, type, dbg) {
/*
@ -620,7 +620,7 @@ Memory.prototype = {
* copyBreakpoints(dbg, mem)
*
* @this {Memory}
* @param {Debugger} [dbg]
* @param {DebuggerX86} [dbg]
* @param {Memory} [mem] (outgoing Memory block to copy breakpoints from, if any)
*/
copyBreakpoints: function(dbg, mem) {

View file

@ -31,22 +31,6 @@
"use strict";
/*
* Components that previously used Debugger messages definitions by including:
*
* var Debugger = require("./debugger");
*
* and using:
*
* Debugger.MESSAGE.FOO
*
* must now instead include:
*
* var Messages = require("./messages");
*
* and then replace all occurrences of "Debugger.MESSAGE.FOO" with "Messages.FOO".
*/
var Messages = {
CPU: 0x00000001,
SEG: 0x00000002,

View file

@ -181,7 +181,7 @@ Mouse.BUTTON = {
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
* @param {DebuggerX86} dbg
*/
Mouse.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -341,7 +341,7 @@ Panel.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
* @param {DebuggerX86} dbg
*/
Panel.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -230,7 +230,7 @@ ParallelPort.prototype.setBinding = function(sHTMLType, sBinding, control, sValu
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
* @param {DebuggerX86} dbg
*/
ParallelPort.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -77,7 +77,7 @@ Component.subclass(RAM);
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
* @param {DebuggerX86} dbg
*/
RAM.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -162,7 +162,7 @@ ROM.BIOS.RESET_FLAG_WARMBOOT = 0x1234; // value stored at ROM.BIOS.RESET_FLAG t
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
* @param {DebuggerX86} dbg
*/
ROM.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -456,7 +456,7 @@ SerialPort.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
* @param {DebuggerX86} dbg
*/
SerialPort.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -2899,7 +2899,7 @@ Video.TOUCH = {
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
* @param {DebuggerX86} dbg
*/
Video.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -163,7 +163,7 @@ Component.subclass(X86FPU);
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
* @param {DebuggerX86} dbg
*/
X86FPU.prototype.initBus = function(cmp, bus, cpu, dbg)
{

View file

@ -39,6 +39,7 @@ if (DEBUGGER) {
var usr = require("../../shared/lib/usrlib");
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var Debugger = require("../../shared/lib/debugger");
var State = require("../../shared/lib/state");
var PDP11 = require("./defines");
var CPUPDP11 = require("./cpu");
@ -69,7 +70,7 @@ var DbgAddrPDP11;
* DebuggerPDP11(parmsDbg)
*
* @constructor
* @extends Component
* @extends Debugger
* @param {Object} parmsDbg
*
* The DebuggerPDP11 component supports the following optional (parmsDbg) properties:
@ -86,18 +87,7 @@ function DebuggerPDP11(parmsDbg)
{
if (DEBUGGER) {
Component.call(this, "Debugger", parmsDbg, DebuggerPDP11);
/*
* These keep track of instruction activity, but only when tracing or when DebuggerPDP11 checks
* have been enabled (eg, one or more breakpoints have been set).
*
* They are zeroed by the reset() notification handler. cInstructions is advanced by
* stepCPU() and checkInstruction() calls. nCycles is updated by every stepCPU() or stop()
* call and simply represents the number of cycles performed by the last run of instructions.
*/
this.nCycles = 0;
this.cOpcodes = this.cOpcodesStart = 0;
Debugger.call(this, parmsDbg);
/*
* Most commands that require an address call parseAddr(), which defaults to dbgAddrNextCode
@ -110,13 +100,6 @@ function DebuggerPDP11(parmsDbg)
this.dbgAddrNextCode = this.newAddr();
this.dbgAddrNextData = this.newAddr();
/*
* This maintains command history. New commands are inserted at index 0 of the array.
* When Enter is pressed on an empty input buffer, we default to the command at aPrevCmds[0].
*/
this.iPrevCmd = -1;
this.aPrevCmds = [];
/*
* fAssemble is true when "assemble mode" is active, false when not.
*/
@ -137,21 +120,6 @@ function DebuggerPDP11(parmsDbg)
*/
this.aSymbolTable = [];
/*
* aVariables is an object with properties that grows as setVariable() assigns more variables;
* each property corresponds to one variable, where the property name is the variable name (ie,
* a string beginning with a letter or underscore, followed by zero or more additional letters,
* digits, or underscores) and the property value is the variable's numeric value. See doVar()
* and setVariable() for details.
*
* Note that parseValue(), through its reliance on str.parseInt(), assumes a default base of 16
* if no base is explicitly indicated (eg, a trailing decimal period), and if you define variable
* names containing exclusively hex alpha characters (a-f), those variables will take precedence
* over the corresponding hex values. In other words, if you define variables "a" and "b", you
* will no longer be able to simply type "a" or "b" to specify the decimal values 10 or 11.
*/
this.aVariables = {};
/*
* clearBreakpoints() initializes the breakpoints lists: aBreakExec is a list of addresses
* to halt on whenever attempting to execute an instruction at the corresponding address,
@ -213,7 +181,7 @@ function DebuggerPDP11(parmsDbg)
if (DEBUGGER) {
Component.subclass(DebuggerPDP11);
Component.subclass(DebuggerPDP11, Debugger);
/*
* NOTE: Every DebuggerPDP11 property from here to the first prototype function definition (initBus()) is a
@ -2138,379 +2106,6 @@ if (DEBUGGER) {
return "no regs";
};
DebuggerPDP11.aBinOpPrecedence = {
'||': 0, // logical OR
'&&': 1, // logical AND
'|': 2, // bitwise OR
'^': 3, // bitwise XOR
'&': 4, // bitwise AND
'!=': 5, // inequality
'==': 5, // equality
'>=': 6, // greater than or equal to
'>': 6, // greater than
'<=': 6, // less than or equal to
'<': 6, // less than
'>>>': 7, // unsigned bitwise right shift
'>>': 7, // bitwise right shift
'<<': 7, // bitwise left shift
'-': 8, // subtraction
'+': 8, // addition
'%': 9, // remainder
'/': 9, // division
'*': 9 // multiplication
};
/**
* evalExpression(aVals, aOps, cOps)
*
* In Node, if you set a variable to 0x80000001; ie:
*
* foo=0x80000001|0
*
* and then calculate foo*foo using "(foo*foo).toString(2)", the result is:
*
* '11111111111111111111111111111100000000000000000000000000000000'
*
* which is slightly incorrect because it has overflowed JavaScript's floating-point precision.
*
* 0x80000001 in decimal is -2147483647, so the product is 4611686014132420609, which is 0x3FFFFFFF00000001.
*
* @this {DebuggerPDP11}
* @param {Array.<number>} aVals
* @param {Array.<string>} aOps
* @param {number} [cOps] (default is all)
* @return {boolean} true if successful, false if error
*/
DebuggerPDP11.prototype.evalExpression = function(aVals, aOps, cOps)
{
cOps = cOps || -1;
while (cOps-- && aOps.length) {
var chOp = aOps.pop();
if (aVals.length < 2) return false;
var valNew;
var val2 = aVals.pop();
var val1 = aVals.pop();
switch(chOp) {
case '*':
valNew = val1 * val2;
break;
case '/':
if (!val2) return false;
valNew = val1 / val2;
break;
case '%':
if (!val2) return false;
valNew = val1 % val2;
break;
case '+':
valNew = val1 + val2;
break;
case '-':
valNew = val1 - val2;
break;
case '<<':
valNew = val1 << val2;
break;
case '>>':
valNew = val1 >> val2;
break;
case '>>>':
valNew = val1 >>> val2;
break;
case '<':
valNew = (val1 < val2? 1 : 0);
break;
case '<=':
valNew = (val1 <= val2? 1 : 0);
break;
case '>':
valNew = (val1 > val2? 1 : 0);
break;
case '>=':
valNew = (val1 >= val2? 1 : 0);
break;
case '==':
valNew = (val1 == val2? 1 : 0);
break;
case '!=':
valNew = (val1 != val2? 1 : 0);
break;
case '&':
valNew = val1 & val2;
break;
case '^':
valNew = val1 ^ val2;
break;
case '|':
valNew = val1 | val2;
break;
case '&&':
valNew = (val1 && val2? 1 : 0);
break;
case '||':
valNew = (val1 || val2? 1 : 0);
break;
default:
return false;
}
aVals.push(valNew|0);
}
return true;
};
/**
* parseExpression(sExp, fPrint)
*
* A quick-and-dirty expression parser. It takes an expression like:
*
* EDX+EDX*4+12345678
*
* and builds a value stack in aVals and a "binop" (binary operator) stack in aOps:
*
* aVals aOps
* ----- ----
* EDX +
* EDX *
* 4 +
* ...
*
* We pop 1 "binop" from aOps and 2 values from aVals whenever a "binop" of lower priority than its
* predecessor is encountered, evaluate, and push the result back onto aVals.
*
* Unary operators like '~' and ternary operators like '?:' are not supported; neither are parentheses.
*
* However, parseReference() now makes it possible to write parenthetical-style sub-expressions by using
* {...} (braces), as well as address references by using [...] (brackets).
*
* Why am I using braces instead of parentheses for sub-expressions? Because parseReference() serves
* multiple purposes, the other being reference replacement in message strings passing through replaceRegs(),
* and I didn't want parentheses taking on a new meaning in message strings.
*
* @this {DebuggerPDP11}
* @param {string|undefined} sExp
* @param {boolean} [fPrint] is true to print all resolved values, false for quiet parsing
* @return {number|undefined} numeric value, or undefined if sExp contains any undefined or invalid values
*/
DebuggerPDP11.prototype.parseExpression = function(sExp, fPrint)
{
var value;
if (sExp) {
/*
* First process (and eliminate) any references, aka sub-expressions.
*/
sExp = this.parseReference(sExp);
var i = 0;
var fError = false;
var sExpOrig = sExp;
var aVals = [], aOps = [];
/*
* All browsers (including, I believe, IE9 and up) support the following idiosyncrasy of a regexp split():
* when the regexp uses a capturing pattern, the resulting array will include entries for all the pattern
* matches along with the non-matches. This effectively means that, in the set of expressions that we
* support, all even entries in asValues will contain "values" and all odd entries will contain "operators".
*
* And although I tried to list the supported operators in "precedential" order, bitwise operators must
* be out-of-order so that we don't mistakenly match either '>' or '<' when they're part of '>>' or '<<'.
*/
var regExp = /(\|\||&&|\||^|&|!=|==|>=|>>>|>>|>|<=|<<|<|-|\+|%|\/|\*)/;
var asValues = sExp.split(regExp);
while (i < asValues.length) {
var sValue = asValues[i++];
var cchValue = sValue.length;
var s = str.trim(sValue);
if (!s) {
fError = true;
break;
}
var v = this.parseValue(s, null, fPrint === false);
if (v === undefined) {
fError = true;
fPrint = false;
break;
}
aVals.push(v);
if (i == asValues.length) break;
var sOp = asValues[i++], cchOp = sOp.length;
this.assert(DebuggerPDP11.aBinOpPrecedence[sOp] != null);
if (aOps.length && DebuggerPDP11.aBinOpPrecedence[sOp] < DebuggerPDP11.aBinOpPrecedence[aOps[aOps.length-1]]) {
this.evalExpression(aVals, aOps, 1);
}
aOps.push(sOp);
sExp = sExp.substr(cchValue + cchOp);
}
if (!this.evalExpression(aVals, aOps) || aVals.length != 1) {
fError = true;
}
if (!fError) {
value = aVals.pop();
if (fPrint) this.printValue(null, value);
} else {
if (fPrint) this.println("error parsing '" + sExpOrig + "' at character " + (sExpOrig.length - sExp.length));
}
}
return value;
};
/**
* parseReference(s)
*
* Returns the given string with any "{expression}" sequences replaced with the value of the expression,
* and any "[address]" references replaced with the contents of the address. Expressions are parsed BEFORE
* addresses. Owing to this function's simplistic parsing, nested braces/brackets are not supported
* (define intermediate variables if needed).
*
* @this {DebuggerPDP11}
* @param {string} s
* @return {string}
*/
DebuggerPDP11.prototype.parseReference = function(s)
{
var a;
while (a = s.match(/\{(.*?)}/)) {
if (a[1].indexOf('{') >= 0) break; // unsupported nested brace(s)
var value = this.parseExpression(a[1]);
s = s.replace('{' + a[1] + '}', value != null? str.toHex(value) : "undefined");
}
while (a = s.match(/\[(.*?)]/)) {
if (a[1].indexOf('[') >= 0) break; // unsupported nested bracket(s)
var dbgAddr = this.parseAddr(a[1]);
s = s.replace('[' + a[1] + ']', dbgAddr? str.toHex(this.getWord(dbgAddr), 4) : "undefined");
}
return this.parseSysVars(s);
};
/**
* parseSysVars(s)
*
* Returns the given string with any recognized "$var" replaced with its value; eg:
*
* $ops: the number of opcodes executed since the last time it was displayed (or reset)
*
* @this {DebuggerPDP11}
* @param {string} s
* @return {string}
*/
DebuggerPDP11.prototype.parseSysVars = function(s)
{
var a;
while (a = s.match(/\$([a-z]+)/i)) {
var v = null;
switch(a[1].toLowerCase()) {
case "ops":
v = this.cOpcodes - this.cOpcodesStart;
break;
}
if (v == null) break;
s = s.replace(a[0], v.toString());
}
return s;
};
/**
* parseValue(sValue, sName, fQuiet)
*
* @this {DebuggerPDP11}
* @param {string|undefined} sValue
* @param {string|null} [sName] is the name of the value, if any
* @param {boolean} [fQuiet]
* @return {number|undefined} numeric value, or undefined if sValue is either undefined or invalid
*/
DebuggerPDP11.prototype.parseValue = function(sValue, sName, fQuiet)
{
var value;
if (sValue !== undefined) {
var iReg = this.getRegIndex(sValue);
if (iReg >= 0) {
value = this.getRegValue(iReg);
} else {
value = this.getVariable(sValue);
if (value === undefined) value = str.parseInt(sValue);
}
if (value === undefined && !fQuiet) this.println("invalid " + (sName? sName : "value") + ": " + sValue);
} else {
if (!fQuiet) this.println("missing " + (sName || "value"));
}
return value;
};
/**
* printValue(sVar, value)
*
* @this {DebuggerPDP11}
* @param {string|null} sVar
* @param {number|undefined} value
* @return {boolean} true if value defined, false if not
*/
DebuggerPDP11.prototype.printValue = function(sVar, value)
{
var sValue;
var fDefined = false;
if (value !== undefined) {
fDefined = true;
sValue = str.toHexLong(value) + " " + value + ". (" + str.toBinBytes(value) + ")";
}
sVar = (sVar != null? (sVar + ": ") : "");
this.println(sVar + sValue);
return fDefined;
};
/**
* printVariable(sVar)
*
* @this {DebuggerPDP11}
* @param {string} [sVar]
* @return {boolean} true if all value(s) defined, false if not
*/
DebuggerPDP11.prototype.printVariable = function(sVar)
{
if (sVar) {
return this.printValue(sVar, this.aVariables[sVar]);
}
var cVariables = 0;
for (sVar in this.aVariables) {
this.printValue(sVar, this.aVariables[sVar]);
cVariables++;
}
return cVariables > 0;
};
/**
* delVariable(sVar)
*
* @this {DebuggerPDP11}
* @param {string} sVar
*/
DebuggerPDP11.prototype.delVariable = function(sVar)
{
delete this.aVariables[sVar];
};
/**
* getVariable(sVar)
*
* @this {DebuggerPDP11}
* @param {string} sVar
* @return {number|undefined}
*/
DebuggerPDP11.prototype.getVariable = function(sVar)
{
return this.aVariables[sVar];
};
/**
* setVariable(sVar, value)
*
* @this {DebuggerPDP11}
* @param {string} sVar
* @param {number} value
*/
DebuggerPDP11.prototype.setVariable = function(sVar, value)
{
this.aVariables[sVar] = value;
};
/**
* comparePairs(p1, p2)
*

View file

@ -0,0 +1,473 @@
/**
* @fileoverview Common PCjs Debugger support.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2012-Jun-21
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
*
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see COPYRIGHT in /modules/shared/lib/defines.js).
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of PCjs
* for purposes of the GNU General Public License, and the author does not claim any copyright
* as to their contents.
*/
"use strict";
if (DEBUGGER) {
if (NODE) {
var str = require("../../shared/lib/strlib");
var Component = require("../../shared/lib/component");
}
}
/**
* Debugger(parmsDbg)
*
* @constructor
* @extends Component
* @param {Object} parmsDbg
*
* The Debugger component is a shared component containing a subset of functionality used by
* the other CPU-specific Debuggers (eg, DebuggerX86). Over time, the goal is to factor out as
* much common debugging support from those components into this one.
*/
function Debugger(parmsDbg)
{
if (DEBUGGER) {
Component.call(this, "Debugger", parmsDbg, Debugger);
/*
* These keep track of instruction activity, but only when tracing or when Debugger checks
* have been enabled (eg, one or more breakpoints have been set).
*
* They are zeroed by the reset() notification handler. cInstructions is advanced by
* stepCPU() and checkInstruction() calls. nCycles is updated by every stepCPU() or stop()
* call and simply represents the number of cycles performed by the last run of instructions.
*/
this.nCycles = 0;
this.cOpcodes = this.cOpcodesStart = 0;
/*
* This maintains command history. New commands are inserted at index 0 of the array.
* When Enter is pressed on an empty input buffer, we default to the command at aPrevCmds[0].
*/
this.iPrevCmd = -1;
this.aPrevCmds = [];
/*
* aVariables is an object with properties that grows as setVariable() assigns more variables;
* each property corresponds to one variable, where the property name is the variable name (ie,
* a string beginning with a letter or underscore, followed by zero or more additional letters,
* digits, or underscores) and the property value is the variable's numeric value. See doVar()
* and setVariable() for details.
*
* Note that parseValue(), through its reliance on str.parseInt(), assumes a default base of 16
* if no base is explicitly indicated (eg, a trailing decimal period), and if you define variable
* names containing exclusively hex alpha characters (a-f), those variables will take precedence
* over the corresponding hex values. In other words, if you define variables "a" and "b", you
* will no longer be able to simply type "a" or "b" to specify the decimal values 10 or 11.
*/
this.aVariables = {};
} // endif DEBUGGER
}
if (DEBUGGER) {
Component.subclass(Debugger);
Debugger.aBinOpPrecedence = {
'||': 0, // logical OR
'&&': 1, // logical AND
'|': 2, // bitwise OR
'^': 3, // bitwise XOR
'&': 4, // bitwise AND
'!=': 5, // inequality
'==': 5, // equality
'>=': 6, // greater than or equal to
'>': 6, // greater than
'<=': 6, // less than or equal to
'<': 6, // less than
'>>>': 7, // unsigned bitwise right shift
'>>': 7, // bitwise right shift
'<<': 7, // bitwise left shift
'-': 8, // subtraction
'+': 8, // addition
'%': 9, // remainder
'/': 9, // division
'*': 9 // multiplication
};
/**
* evalExpression(aVals, aOps, cOps)
*
* In Node, if you set a variable to 0x80000001; ie:
*
* foo=0x80000001|0
*
* and then calculate foo*foo using "(foo*foo).toString(2)", the result is:
*
* '11111111111111111111111111111100000000000000000000000000000000'
*
* which is slightly incorrect because it has overflowed JavaScript's floating-point precision.
*
* 0x80000001 in decimal is -2147483647, so the product is 4611686014132420609, which is 0x3FFFFFFF00000001.
*
* @this {Debugger}
* @param {Array.<number>} aVals
* @param {Array.<string>} aOps
* @param {number} [cOps] (default is all)
* @return {boolean} true if successful, false if error
*/
Debugger.prototype.evalExpression = function(aVals, aOps, cOps)
{
cOps = cOps || -1;
while (cOps-- && aOps.length) {
var chOp = aOps.pop();
if (aVals.length < 2) return false;
var valNew;
var val2 = aVals.pop();
var val1 = aVals.pop();
switch(chOp) {
case '*':
valNew = val1 * val2;
break;
case '/':
if (!val2) return false;
valNew = val1 / val2;
break;
case '%':
if (!val2) return false;
valNew = val1 % val2;
break;
case '+':
valNew = val1 + val2;
break;
case '-':
valNew = val1 - val2;
break;
case '<<':
valNew = val1 << val2;
break;
case '>>':
valNew = val1 >> val2;
break;
case '>>>':
valNew = val1 >>> val2;
break;
case '<':
valNew = (val1 < val2? 1 : 0);
break;
case '<=':
valNew = (val1 <= val2? 1 : 0);
break;
case '>':
valNew = (val1 > val2? 1 : 0);
break;
case '>=':
valNew = (val1 >= val2? 1 : 0);
break;
case '==':
valNew = (val1 == val2? 1 : 0);
break;
case '!=':
valNew = (val1 != val2? 1 : 0);
break;
case '&':
valNew = val1 & val2;
break;
case '^':
valNew = val1 ^ val2;
break;
case '|':
valNew = val1 | val2;
break;
case '&&':
valNew = (val1 && val2? 1 : 0);
break;
case '||':
valNew = (val1 || val2? 1 : 0);
break;
default:
return false;
}
aVals.push(valNew|0);
}
return true;
};
/**
* parseExpression(sExp, fPrint)
*
* A quick-and-dirty expression parser. It takes an expression like:
*
* EDX+EDX*4+12345678
*
* and builds a value stack in aVals and a "binop" (binary operator) stack in aOps:
*
* aVals aOps
* ----- ----
* EDX +
* EDX *
* 4 +
* ...
*
* We pop 1 "binop" from aOps and 2 values from aVals whenever a "binop" of lower priority than its
* predecessor is encountered, evaluate, and push the result back onto aVals.
*
* Unary operators like '~' and ternary operators like '?:' are not supported; neither are parentheses.
*
* However, parseReference() now makes it possible to write parenthetical-style sub-expressions by using
* {...} (braces), as well as address references by using [...] (brackets).
*
* Why am I using braces instead of parentheses for sub-expressions? Because parseReference() serves
* multiple purposes, the other being reference replacement in message strings passing through replaceRegs(),
* and I didn't want parentheses taking on a new meaning in message strings.
*
* @this {Debugger}
* @param {string|undefined} sExp
* @param {boolean} [fPrint] is true to print all resolved values, false for quiet parsing
* @return {number|undefined} numeric value, or undefined if sExp contains any undefined or invalid values
*/
Debugger.prototype.parseExpression = function(sExp, fPrint)
{
var value;
if (sExp) {
/*
* First process (and eliminate) any references, aka sub-expressions.
*/
sExp = this.parseReference(sExp);
var i = 0;
var fError = false;
var sExpOrig = sExp;
var aVals = [], aOps = [];
/*
* All browsers (including, I believe, IE9 and up) support the following idiosyncrasy of a regexp split():
* when the regexp uses a capturing pattern, the resulting array will include entries for all the pattern
* matches along with the non-matches. This effectively means that, in the set of expressions that we
* support, all even entries in asValues will contain "values" and all odd entries will contain "operators".
*
* And although I tried to list the supported operators in "precedential" order, bitwise operators must
* be out-of-order so that we don't mistakenly match either '>' or '<' when they're part of '>>' or '<<'.
*/
var regExp = /(\|\||&&|\||^|&|!=|==|>=|>>>|>>|>|<=|<<|<|-|\+|%|\/|\*)/;
var asValues = sExp.split(regExp);
while (i < asValues.length) {
var sValue = asValues[i++];
var cchValue = sValue.length;
var s = str.trim(sValue);
if (!s) {
fError = true;
break;
}
var v = this.parseValue(s, null, fPrint === false);
if (v === undefined) {
fError = true;
fPrint = false;
break;
}
aVals.push(v);
if (i == asValues.length) break;
var sOp = asValues[i++], cchOp = sOp.length;
this.assert(Debugger.aBinOpPrecedence[sOp] != null);
if (aOps.length && Debugger.aBinOpPrecedence[sOp] < Debugger.aBinOpPrecedence[aOps[aOps.length-1]]) {
this.evalExpression(aVals, aOps, 1);
}
aOps.push(sOp);
sExp = sExp.substr(cchValue + cchOp);
}
if (!this.evalExpression(aVals, aOps) || aVals.length != 1) {
fError = true;
}
if (!fError) {
value = aVals.pop();
if (fPrint) this.printValue(null, value);
} else {
if (fPrint) this.println("error parsing '" + sExpOrig + "' at character " + (sExpOrig.length - sExp.length));
}
}
return value;
};
/**
* parseReference(s)
*
* Returns the given string with any "{expression}" sequences replaced with the value of the expression,
* and any "[address]" references replaced with the contents of the address. Expressions are parsed BEFORE
* addresses. Owing to this function's simplistic parsing, nested braces/brackets are not supported
* (define intermediate variables if needed).
*
* @this {Debugger}
* @param {string} s
* @return {string}
*/
Debugger.prototype.parseReference = function(s)
{
var a;
while (a = s.match(/\{(.*?)}/)) {
if (a[1].indexOf('{') >= 0) break; // unsupported nested brace(s)
var value = this.parseExpression(a[1]);
s = s.replace('{' + a[1] + '}', value != null? str.toHex(value) : "undefined");
}
while (a = s.match(/\[(.*?)]/)) {
if (a[1].indexOf('[') >= 0) break; // unsupported nested bracket(s)
var dbgAddr = this.parseAddr(a[1]);
s = s.replace('[' + a[1] + ']', dbgAddr? str.toHex(this.getWord(dbgAddr), dbgAddr.fData32? 8 : 4) : "undefined");
}
return this.parseSysVars(s);
};
/**
* parseSysVars(s)
*
* Returns the given string with any recognized "$var" replaced with its value; eg:
*
* $ops: the number of opcodes executed since the last time it was displayed (or reset)
*
* @this {Debugger}
* @param {string} s
* @return {string}
*/
Debugger.prototype.parseSysVars = function(s)
{
var a;
while (a = s.match(/\$([a-z]+)/i)) {
var v = null;
switch(a[1].toLowerCase()) {
case "ops":
v = this.cOpcodes - this.cOpcodesStart;
break;
}
if (v == null) break;
s = s.replace(a[0], v.toString());
}
return s;
};
/**
* parseValue(sValue, sName, fQuiet)
*
* @this {Debugger}
* @param {string|undefined} sValue
* @param {string|null} [sName] is the name of the value, if any
* @param {boolean} [fQuiet]
* @return {number|undefined} numeric value, or undefined if sValue is either undefined or invalid
*/
Debugger.prototype.parseValue = function(sValue, sName, fQuiet)
{
var value;
if (sValue !== undefined) {
var iReg = this.getRegIndex(sValue);
if (iReg >= 0) {
value = this.getRegValue(iReg);
} else {
value = this.getVariable(sValue);
if (value === undefined) value = str.parseInt(sValue);
}
if (value === undefined && !fQuiet) this.println("invalid " + (sName? sName : "value") + ": " + sValue);
} else {
if (!fQuiet) this.println("missing " + (sName || "value"));
}
return value;
};
/**
* printValue(sVar, value)
*
* @this {Debugger}
* @param {string|null} sVar
* @param {number|undefined} value
* @return {boolean} true if value defined, false if not
*/
Debugger.prototype.printValue = function(sVar, value)
{
var sValue;
var fDefined = false;
if (value !== undefined) {
fDefined = true;
sValue = str.toHexLong(value) + " " + value + ". (" + str.toBinBytes(value) + ")";
}
sVar = (sVar != null? (sVar + ": ") : "");
this.println(sVar + sValue);
return fDefined;
};
/**
* printVariable(sVar)
*
* @this {Debugger}
* @param {string} [sVar]
* @return {boolean} true if all value(s) defined, false if not
*/
Debugger.prototype.printVariable = function(sVar)
{
if (sVar) {
return this.printValue(sVar, this.aVariables[sVar]);
}
var cVariables = 0;
for (sVar in this.aVariables) {
this.printValue(sVar, this.aVariables[sVar]);
cVariables++;
}
return cVariables > 0;
};
/**
* delVariable(sVar)
*
* @this {Debugger}
* @param {string} sVar
*/
Debugger.prototype.delVariable = function(sVar)
{
delete this.aVariables[sVar];
};
/**
* getVariable(sVar)
*
* @this {Debugger}
* @param {string} sVar
* @return {number|undefined}
*/
Debugger.prototype.getVariable = function(sVar)
{
return this.aVariables[sVar];
};
/**
* setVariable(sVar, value)
*
* @this {Debugger}
* @param {string} sVar
* @param {number} value
*/
Debugger.prototype.setVariable = function(sVar, value)
{
this.aVariables[sVar] = value;
};
} // endif DEBUGGER
if (NODE) module.exports = Debugger;

View file

@ -35,13 +35,9 @@
* @define {string}
*/
var APPVERSION = "1.x.x"; // this @define is overridden by the Closure Compiler with the version in package.json
var XMLVERSION = null; // this is set in non-COMPILED builds by embedMachine() if a version number was found in the machine XML
var COPYRIGHT = "Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>";
var LICENSE = "License: GPL version 3 or later <http://gnu.org/licenses/gpl.html>";
var CSSCLASS = "pcjs";
/**

View file

@ -41,7 +41,7 @@ var str = {};
* So use this function to validate the entire string.
*
* @param {string} s is the string representation of some number
* @param {number} [base] is the radix of the number represented above (only 2, 10 and 16 are supported)
* @param {number} [base] is the radix of the number represented above; 10 is the default (only 2, 8, 10 and 16 are supported)
* @return {boolean} true if valid, false if invalid (or the specified base isn't supported)
*/
str.isValidInt = function(s, base)