Added support for converting numbers to/from octal, along with improved support for binary numbers

This commit is contained in:
Jeff Parsons 2016-09-15 18:23:09 -07:00
commit 1265104e6d
15 changed files with 888 additions and 789 deletions

View file

@ -98,7 +98,7 @@ function Debugger(parmsDbg)
this.aPrevCmds = [];
/*
* aVariables is an object with properties that grows as setVariable() assigns more variables;
* aVariables is an object with properties that grow 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()
@ -108,7 +108,7 @@ function Debugger(parmsDbg)
* unprefixed hex value (eg, "a5" as opposed to "0xa5") will trump the numeric value. Unprefixed
* hex values are a convenience of parseValue(), which always calls str.parseInt() with a default
* base of 16; however, that default be overridden with a variety of explicit prefixes or suffixes
* (eg, a trailing period to indicate a decimal number).
* (eg, a leading zero to indicate octal, or a trailing period to indicate decimal).
*/
this.aVariables = {};
@ -141,6 +141,162 @@ if (DEBUGGER) {
'*': 9 // multiplication
};
/**
* getRegIndex(sReg, off)
*
* NOTE: This must be implemented by the individual debuggers.
*
* @this {Debugger}
* @param {string} sReg
* @param {number} [off] optional offset into sReg
* @return {number} register index, or -1 if not found
*/
Debugger.prototype.getRegIndex = function(sReg, off)
{
return -1;
};
/**
* getRegValue(iReg)
*
* NOTE: This must be implemented by the individual debuggers.
*
* @this {Debugger}
* @param {number} iReg
* @return {number|undefined}
*/
Debugger.prototype.getRegValue = function(iReg)
{
return undefined;
};
/**
* parseAddrReference(s, sAddr)
*
* Returns the given string with the given address reference replaced with the contents of that address.
*
* NOTE: This must be implemented by the individual debuggers.
*
* @this {Debugger}
* @param {string} s
* @param {string} sAddr
* @return {string}
*/
Debugger.prototype.parseAddrReference = function(s, sAddr)
{
return s.replace('[' + sAddr + ']', "unimplemented");
};
/**
* getNextCommand()
*
* @this {Debugger}
* @return {string}
*/
Debugger.prototype.getNextCommand = function()
{
var sCmd;
if (this.iPrevCmd > 0) {
sCmd = this.aPrevCmds[--this.iPrevCmd];
} else {
sCmd = "";
this.iPrevCmd = -1;
}
return sCmd;
};
/**
* getPrevCommand()
*
* @this {Debugger}
* @return {string|null}
*/
Debugger.prototype.getPrevCommand = function()
{
var sCmd = null;
if (this.iPrevCmd < this.aPrevCmds.length - 1) {
sCmd = this.aPrevCmds[++this.iPrevCmd];
}
return sCmd;
};
/**
* parseCommand(sCmd, fSave, chSep)
*
* @this {Debugger}
* @param {string|undefined} sCmd
* @param {boolean} [fSave] is true to save the command, false if not
* @param {string} [chSep] is the command separator character (default is ';')
* @return {Array.<string>}
*/
Debugger.prototype.parseCommand = function(sCmd, fSave, chSep)
{
if (fSave) {
if (!sCmd) {
if (this.fAssemble) {
sCmd = "end";
} else {
sCmd = this.aPrevCmds[this.iPrevCmd+1];
}
} else {
if (this.iPrevCmd < 0 && this.aPrevCmds.length) {
this.iPrevCmd = 0;
}
if (this.iPrevCmd < 0 || sCmd != this.aPrevCmds[this.iPrevCmd]) {
this.aPrevCmds.splice(0, 0, sCmd);
this.iPrevCmd = 0;
}
this.iPrevCmd--;
}
}
var a = [];
if (sCmd) {
/*
* With the introduction of breakpoint commands (ie, quoted command sequences
* associated with a breakpoint), we can no longer perform simplistic splitting.
*
* a = sCmd.split(chSep || ';');
* for (var i = 0; i < a.length; i++) a[i] = str.trim(a[i]);
*
* We may now split on semi-colons ONLY if they are outside a quoted sequence.
*
* Also, to allow quoted strings *inside* breakpoint commands, we first replace all
* DOUBLE double-quotes with single quotes.
*/
sCmd = sCmd.toLowerCase().replace(/""/g, "'");
var iPrev = 0;
var chQuote = null;
chSep = chSep || ';';
/*
* NOTE: Processing charAt() up to and INCLUDING length is not a typo; we're taking
* advantage of the fact that charAt() with an invalid index returns an empty string,
* allowing us to use the same substring() call to capture the final portion of sCmd.
*
* In a sense, it allows us to pretend that the string ends with a zero terminator.
*/
for (var i = 0; i <= sCmd.length; i++) {
var ch = sCmd.charAt(i);
if (ch == '"' || ch == "'") {
if (!chQuote) {
chQuote = ch;
} else if (ch == chQuote) {
chQuote = null;
}
}
else if (ch == chSep && !chQuote || !ch) {
/*
* Recall that substring() accepts starting (inclusive) and ending (exclusive)
* indexes, whereas substr() accepts a starting index and a length. We need the former.
*/
a.push(str.trim(sCmd.substring(iPrev, i)));
iPrev = i + 1;
}
}
}
return a;
};
/**
* evalExpression(aVals, aOps, cOps)
*
@ -239,145 +395,6 @@ if (DEBUGGER) {
return true;
};
/**
* getNextCommand()
*
* @this {Debugger}
* @return {string}
*/
Debugger.prototype.getNextCommand = function()
{
var sCmd;
if (this.iPrevCmd > 0) {
sCmd = this.aPrevCmds[--this.iPrevCmd];
} else {
sCmd = "";
this.iPrevCmd = -1;
}
return sCmd;
};
/**
* getPrevCommand()
*
* @this {Debugger}
* @return {string|null}
*/
Debugger.prototype.getPrevCommand = function()
{
var sCmd = null;
if (this.iPrevCmd < this.aPrevCmds.length - 1) {
sCmd = this.aPrevCmds[++this.iPrevCmd];
}
return sCmd;
};
/**
* getRegIndex(sReg, off)
*
* NOTE: This must be implemented by the individual debuggers.
*
* @this {Debugger}
* @param {string} sReg
* @param {number} [off] optional offset into sReg
* @return {number} register index, or -1 if not found
*/
Debugger.prototype.getRegIndex = function(sReg, off)
{
return -1;
};
/**
* getRegValue(iReg)
*
* NOTE: This must be implemented by the individual debuggers.
*
* @this {Debugger}
* @param {number} iReg
* @return {number|undefined}
*/
Debugger.prototype.getRegValue = function(iReg)
{
return undefined;
};
/**
* parseCommand(sCmd, fSave, chSep)
*
* @this {Debugger}
* @param {string|undefined} sCmd
* @param {boolean} [fSave] is true to save the command, false if not
* @param {string} [chSep] is the command separator character (default is ';')
* @return {Array.<string>}
*/
Debugger.prototype.parseCommand = function(sCmd, fSave, chSep)
{
if (fSave) {
if (!sCmd) {
if (this.fAssemble) {
sCmd = "end";
} else {
sCmd = this.aPrevCmds[this.iPrevCmd+1];
}
} else {
if (this.iPrevCmd < 0 && this.aPrevCmds.length) {
this.iPrevCmd = 0;
}
if (this.iPrevCmd < 0 || sCmd != this.aPrevCmds[this.iPrevCmd]) {
this.aPrevCmds.splice(0, 0, sCmd);
this.iPrevCmd = 0;
}
this.iPrevCmd--;
}
}
var a = [];
if (sCmd) {
/*
* With the introduction of breakpoint commands (ie, quoted command sequences
* associated with a breakpoint), we can no longer perform simplistic splitting.
*
* a = sCmd.split(chSep || ';');
* for (var i = 0; i < a.length; i++) a[i] = str.trim(a[i]);
*
* We may now split on semi-colons ONLY if they are outside a quoted sequence.
*
* Also, to allow quoted strings *inside* breakpoint commands, we first replace all
* DOUBLE double-quotes with single quotes.
*/
sCmd = sCmd.toLowerCase().replace(/""/g, "'");
var iPrev = 0;
var chQuote = null;
chSep = chSep || ';';
/*
* NOTE: Processing charAt() up to and INCLUDING length is not a typo; we're taking
* advantage of the fact that charAt() with an invalid index returns an empty string,
* allowing us to use the same substring() call to capture the final portion of sCmd.
*
* In a sense, it allows us to pretend that the string ends with a zero terminator.
*/
for (var i = 0; i <= sCmd.length; i++) {
var ch = sCmd.charAt(i);
if (ch == '"' || ch == "'") {
if (!chQuote) {
chQuote = ch;
} else if (ch == chQuote) {
chQuote = null;
}
}
else if (ch == chSep && !chQuote || !ch) {
/*
* Recall that substring() accepts starting (inclusive) and ending (exclusive)
* indexes, whereas substr() accepts a starting index and a length. We need the former.
*/
a.push(str.trim(sCmd.substring(iPrev, i)));
iPrev = i + 1;
}
}
}
return a;
};
/**
* parseExpression(sExp, fPrint)
*
@ -473,23 +490,6 @@ if (DEBUGGER) {
return value;
};
/**
* parseAddrReference(s, sAddr)
*
* Returns the given string with the given address references replaced with the contents of the address.
*
* NOTE: This must be implemented by the individual debuggers.
*
* @this {Debugger}
* @param {string} s
* @param {string} sAddr
* @return {string}
*/
Debugger.prototype.parseAddrReference = function(s, sAddr)
{
return s.replace('[' + sAddr + ']', "unimplemented");
};
/**
* parseReference(s)
*
@ -585,7 +585,7 @@ if (DEBUGGER) {
var fDefined = false;
if (value !== undefined) {
fDefined = true;
sValue = str.toHexLong(value) + " " + value + ". (" + str.toBinBytes(value) + ")";
sValue = str.toHex(value, 0, true) + " " + value + ". " + str.toOctal(value, 0, true) + " " + str.toBinBytes(value, 0, true);
}
sVar = (sVar != null? (sVar + ": ") : "");
this.println(sVar + sValue);

View file

@ -38,56 +38,100 @@ var str = {};
*
* The built-in parseInt() function has the annoying feature of returning a partial value (ie,
* up to the point where it encounters an invalid character); eg, parseInt("foo", 16) returns 0xf.
* So use this function to validate the entire string.
*
* So it's best to use our own str.parseInt() function, which will in turn 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 above number; 10 is the default (only 2, 8, 10 and 16 are supported)
* @param {number} [base] is the radix to use (default is 10); 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)
{
if (!base || base == 10) return s.match(/^[0-9]+$/) !== null;
if (base == 16) return s.match(/^[0-9a-f]+$/i) !== null;
if (base == 2) return s.match(/^[01]+$/i) !== null;
if (base == 8) return s.match(/^[0-7]+$/) !== null;
if (base == 2) return s.match(/^[01]+$/) !== null;
return false;
};
/**
* parseInt(s, base)
*
* This is a wrapper around the built-in parseInt() function, which recognizes certain prefixes (eg,
* '$' or "0x" for hex) and suffixes (eg, 'h' for hex, or '.' for decimal), and then calls isValidInt()
* to ensure we don't convert strings that contain partial values (see isValidInt() for details).
* This is a wrapper around the built-in parseInt() function. Our wrapper recognizes certain prefixes
* ('$' or "0x" for hex, '#' or "0o" for octal) and suffixes ('.' for decimal, 'h' for hex, 'y' for
* binary), and then calls isValidInt() to ensure we don't convert strings that contain partial values;
* see isValidInt() for details.
*
* We don't support multiple prefix/suffix combinations, nor do we support the "0b" prefix (or "b" suffix)
* for binary, because 1) it's not commonly used, and 2) it conflicts with valid hex sequences.
* The use of multiple prefix/suffix combinations is undefined (although for the record, we process
* prefixes first). We do NOT support the "0b" prefix to indicate binary UNLESS one or more commas are
* also present (because "0b" is also a valid hex sequence), and we do NOT support a single leading zero
* to indicate octal (because such a number could also be decimal or hex). Any number of commas are
* allowed; we remove them all before calling the built-in parseInt().
*
* To summarize our non-standard alternatives: a 'y' suffix indicates binary, a '#' prefix indicates
* octal, a '$' prefix indicates hex, and a "0b" prefix indicates binary IF at least one comma is present.
* Commas are useful for grouping binary digits, but if you don't want to use them, then use a 'y' suffix.
*
* @param {string} s is the string representation of some number
* @param {number} [base] is the default radix to use (default is 10); can be overridden by prefixes/suffixes
* @param {number} [base] is the radix to use (default is 10); can be overridden by prefixes/suffixes
* @return {number|undefined} corresponding value, or undefined if invalid
*/
str.parseInt = function(s, base)
{
var value;
if (s) {
if (!base) base = 10;
if (s.charAt(0) == '$') {
var chPrefix = s.charAt(0);
var fCommas = (s.indexOf(',') > 0);
if (fCommas) s = s.replace(/,/g, '');
if (chPrefix == '#') {
base = 8;
chPrefix = null;
}
else if (chPrefix == '$') {
base = 16;
chPrefix = null;
}
if (chPrefix == null) {
s = s.substr(1);
} else if (s.substr(0, 2) == "0x") {
base = 16;
s = s.substr(2);
} else {
var chSuffix = s.charAt(s.length-1).toLowerCase();
if (chSuffix == 'h') {
base = 16;
chSuffix = null;
}
else {
if (chPrefix == '0') {
chPrefix = s.charAt(1);
if (chPrefix == 'b' && fCommas) {
base = 2;
chPrefix = null;
}
if (chPrefix == 'o') {
base = 8;
chPrefix = null;
}
else if (chPrefix == 'x') {
base = 16;
chPrefix = null;
}
}
else if (chSuffix == '.') {
base = 10;
chSuffix = null;
if (chPrefix == null) {
s = s.substr(2);
}
else {
var chSuffix = s.charAt(s.length-1).toLowerCase();
if (chSuffix == 'y') {
base = 2;
chSuffix = null;
}
else if (chSuffix == '.') {
base = 10;
chSuffix = null;
}
else if (chSuffix == 'h') {
base = 16;
chSuffix = null;
}
if (chSuffix == null) s = s.substr(0, s.length-1);
}
if (chSuffix == null) s = s.substr(0, s.length-1);
}
var v;
if (str.isValidInt(s, base) && !isNaN(v = parseInt(s, base))) {
@ -109,7 +153,7 @@ str.parseInt = function(s, base)
str.toBin = function(n, cch)
{
var s = "";
if (cch === undefined) {
if (!cch) {
cch = 32;
} else {
if (cch > 32) cch = 32;
@ -134,30 +178,74 @@ str.toBin = function(n, cch)
};
/**
* toBinBytes(n, cb)
* toBinBytes(n, cb, fPrefix)
*
* Converts an integer to binary, with the specified number of bytes (up to the default of 4).
*
* @param {number|null|undefined} n is a 32-bit value
* @param {number} [cb] is the desired number of binary bytes (4 is both the default and the maximum)
* @param {boolean} [fPrefix]
* @return {string} the binary representation of n
*/
str.toBinBytes = function(n, cb)
str.toBinBytes = function(n, cb, fPrefix)
{
var s = "";
if (!cb || cb > 4) cb = 4;
for (var i = 0; i < cb; i++) {
if (s) s = ',' + s;
s = str.toBin(n & 0xff, 8) + 'b' + s;
s = str.toBin(n & 0xff, 8) + s;
n >>= 8;
}
return s;
return (fPrefix? "0b" : "") + s;
};
/**
* toHex(n, cch)
* toOctal(n, cch, fPrefix)
*
* Converts an integer to hex, with the specified number of digits (up to the default of 8).
* Converts an integer to octal, with the specified number of digits (default of 6; max of 11)
*
* You might be tempted to use the built-in n.toString(8) instead, but it doesn't zero-pad and it
* doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw
* an exception, whereas this function will return '?' characters.
*
* @param {number|null|undefined} n is a 32-bit value
* @param {number} [cch] is the desired number of octal digits (0 or undefined for default of either 6 or 11)
* @param {boolean} [fPrefix]
* @return {string} the octal representation of n
*/
str.toOctal = function(n, cch, fPrefix)
{
var s = "";
if (cch) {
if (cch > 11) cch = 11;
} else {
cch = (n & ~0xffff)? 11 : 6;
}
/*
* An initial "falsey" check for null takes care of both null and undefined;
* we can't rely entirely on isNaN(), because isNaN(null) returns false, oddly enough.
*
* Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN,
* since JavaScript coerces such operands to zero, but I think there's "value" in seeing those
* values displayed differently.
*/
if (n == null || isNaN(n)) {
while (cch-- > 0) s = '?' + s;
} else {
while (cch-- > 0) {
var d = (n & 7) + 0x30;
s = String.fromCharCode(d) + s;
n >>= 3;
}
}
return (fPrefix? "0o" : "") + s;
};
/**
* toHex(n, cch, fPrefix)
*
* Converts an integer to hex, with the specified number of digits (default of 4 or 8, max of 8).
*
* You might be tempted to use the built-in n.toString(16) instead, but it doesn't zero-pad and it
* doesn't properly convert negative values; for example, if n is -2147483647, then n.toString(16)
@ -172,16 +260,18 @@ str.toBinBytes = function(n, cb)
* s = s.substr(0, cch).toUpperCase();
*
* @param {number|null|undefined} n is a 32-bit value
* @param {number} [cch] is the desired number of hex digits (8 is both the default and the maximum)
* @param {number} [cch] is the desired number of hex digits (0 or undefined for default of either 4 or 8)
* @param {boolean} [fPrefix]
* @return {string} the hex representation of n
*/
str.toHex = function(n, cch)
str.toHex = function(n, cch, fPrefix)
{
var s = "";
if (cch === undefined) {
cch = 8;
} else {
if (cch) {
if (cch > 8) cch = 8;
} else {
cch = (n & ~0xffff)? 8 : 4;
}
/*
* An initial "falsey" check for null takes care of both null and undefined;
@ -201,46 +291,46 @@ str.toHex = function(n, cch)
n >>= 4;
}
}
return s;
return (fPrefix? "0x" : "") + s;
};
/**
* toHexByte(b)
*
* Alias for "0x" + str.toHex(b, 2)
* Alias for str.toHex(b, 2, true)
*
* @param {number|null|undefined} b is a byte value
* @return {string} the hex representation of b
*/
str.toHexByte = function(b)
{
return "0x" + str.toHex(b, 2);
return str.toHex(b, 2, true);
};
/**
* toHexWord(w)
*
* Alias for "0x" + str.toHex(w, 4)
* Alias for str.toHex(w, 4, true)
*
* @param {number|null|undefined} w is a word (16-bit) value
* @return {string} the hex representation of w
*/
str.toHexWord = function(w)
{
return "0x" + str.toHex(w, 4);
return str.toHex(w, 4, true);
};
/**
* toHexLong(l)
*
* Alias for "0x" + toHex(l)
* Alias for str.toHex(l, 8, true)
*
* @param {number|null|undefined} l is a dword (32-bit) value
* @return {string} the hex representation of w
*/
str.toHexLong = function(l)
{
return "0x" + str.toHex(l);
return str.toHex(l, 8, true);
};
/**