Fixed the signed truncation of 36-bit values (so that "-34,359,738,368. - 1" correctly produces "34359738367. aka 0o377777777777"), added the proper the right-to-left parsing of unary operators (all two of them: negate and complement), changed the PDP-10 disassembler to honor the selected base when displaying opcodes, and updated all the number-to-string conversions to select an appropriate number of digits when rendering values without a specified number of digits.

This commit is contained in:
Jeff 2017-03-20 13:34:25 -07:00 committed by Jeff Parsons
commit a6021441c3
13 changed files with 665 additions and 637 deletions

View file

@ -459,16 +459,13 @@ class Debugger extends Component
if (nBits <= 32) {
vNew = v | 0;
} else {
/*
* For negative values, we require them to fit within nBits - 1, reserving the left-most bit
* for the sign bit, but for positive values, we can't really be sure if the caller is treating
* the left-most bit as a sign bit or not, so the upper range is based on nBits.
*/
limit = Math.pow(2, nBits - 1);
if (v < -limit) {
vNew = v % limit;
} else if (v >= limit * 2) {
vNew = v % (limit * 2);
if (v < -limit * 2) vNew = v % (limit * 2);
vNew += (limit * 2);
} else if (vNew >= limit) {
if (v >= limit * 2) vNew = v % (limit * 2);
vNew -= (limit * 2); // the sign bit was set in this overly large value, so make it negative
}
}
}
@ -632,7 +629,7 @@ class Debugger extends Component
var value;
var sValue, sOp;
var fError = false;
var iNegate = 0;
var nUnary = 0;
var aVals = [], aOps = [];
var nBasePrev = this.nBase;
@ -643,7 +640,9 @@ class Debugger extends Component
sValue = asValues[iValue++].trim();
sOp = (iValue < iLimit? asValues[iValue++] : "");
if (!sValue) {
if (sValue) {
v = this.parseValue(sValue, null, fQuiet, nUnary);
} else {
if (sOp == '{') {
var cOpen = 1;
var iStart = iValue;
@ -661,21 +660,32 @@ class Debugger extends Component
sOp = (iValue < iLimit? asValues[iValue++] : "");
}
else {
if (sOp == '~' || sOp == '^-') {
iNegate = 1;
if (sOp == '^B') {
this.nBase = 2;
continue;
}
if (sOp == '-') {
iNegate = -1;
if (sOp == '^O') {
this.nBase = 8;
continue;
}
if (sOp == '^D') {
this.nBase = 10;
continue;
}
if (!(nUnary & (0xC0000000|0))) {
if (sOp == '-') {
nUnary = (nUnary << 2) | 1;
continue;
}
if (sOp == '~' || sOp == '^-') {
nUnary = (nUnary << 2) | 2;
continue;
}
}
fError = true;
break;
}
}
else {
v = this.parseValue(sValue, null, fQuiet, iNegate);
}
if (v === undefined) {
if (this.sUndefined == null && fQuiet) {
@ -703,10 +713,10 @@ class Debugger extends Component
* base, so we must override the current base to ensure the count is parsed correctly.
*/
this.nBase = (sOp == '^_')? 10 : nBase;
iNegate = 0;
nUnary = 0;
}
if (!this.evalOps(aVals, aOps) || aVals.length != 1) {
if (fError || !this.evalOps(aVals, aOps) || aVals.length != 1) {
fError = true;
}
@ -848,7 +858,7 @@ class Debugger extends Component
*
* WARNING: Whenever you make changes to this RegExp, make sure you update aBinOpPrecedence as needed, too.
*/
var regExp = /(\{|}|\|\||&&|\||\^!|\^-|~|\^_|_|&|!=|!|==|>=|>>>|>>|>|<=|<<|<|-|\+|%|\/|\*)/;
var regExp = /(\{|}|\|\||&&|\||\^!|\^B|\^O|\^D|\^-|~|\^_|_|&|!=|!|==|>=|>>>|>>|>|<=|<<|<|-|\+|%|\/|\*)/;
sExp = sExp.replace(/(^|[^A-Z0-9$%.])([0-9]+)B/, "$1$2^_");
var asValues = sExp.split(regExp);
value = this.parseArray(asValues, 0, asValues.length, this.nBase, fQuiet);
@ -936,16 +946,26 @@ class Debugger extends Component
}
/**
* parseValue(sValue, sName, fQuiet, iNegate)
* parseValue(sValue, sName, fQuiet, nUnary)
*
* nUnary is actually a small "stack" of unary operations encoded in successive pairs of bits.
* As parseExpression() encounters each unary operator, nUnary is shifted left 2 bits, and the
* new unary operator is encoded in bits 0 and 1 (0b00 is none, 0b01 is negate, 0b10 is complement,
* and 0b11 is reserved). Here, we process the bits in reverse order (hence the stack-like nature),
* ensuring that we process the unary operators associated with this value right-to-left.
*
* Since bitwise operators see only 32 bits, more than 16 unary operators cannot be supported
* using this method. We'll let parseExpression() worry about that; if it ever happens in practice,
* then we'll have to switch to a more "expensive" approach (eg, an actual array of unary operators).
*
* @this {Debugger}
* @param {string|undefined} sValue
* @param {string|null} [sName] is the name of the value, if any
* @param {boolean} [fQuiet]
* @param {number} [iNegate] (-1 to negate , 1 to complement)
* @param {number} [nUnary] (0 for none, 1 for negate, 2 for complement)
* @return {number|undefined} numeric value, or undefined if sValue is either undefined or invalid
*/
parseValue(sValue, sName, fQuiet, iNegate = 0)
parseValue(sValue, sName, fQuiet, nUnary = 0)
{
var value;
if (sValue != null) {
@ -958,23 +978,20 @@ class Debugger extends Component
/*
* A feature of MACRO-10 is that any single-digit number is automatically interpreted as base-10.
*/
var nBase = sValue.length > 1? this.nBase : 10;
if (iNegate < 0) {
sValue = '-' + sValue;
iNegate = 0;
}
value = Str.parseInt(sValue, nBase);
value = Str.parseInt(sValue, sValue.length > 1? this.nBase : 10);
}
}
if (value != null) {
if (iNegate < 0) {
value = -value;
} else if (iNegate > 0) {
/*
* This is easier than adding an evalNOT()....
*/
value = this.evalXOR(value, -1);
while (nUnary) {
if (nUnary & 1) {
value = -this.truncate(value);
}
else if (nUnary & 2) {
value = this.evalXOR(value, -1); // this is easier than adding an evalNOT()...
}
nUnary >>>= 2;
}
value = this.truncate(value);
} else {
if (!fQuiet) {
this.println("invalid " + (sName? sName : "value") + ": " + sValue);

View file

@ -68,6 +68,8 @@ class Str {
* More recently, we've added support for "^D", "^O", and "^B" prefixes to accommodate the base overrides
* that the PDP-10's MACRO-10 assembly language supports (decimal, octal, and binary, respectively).
* If this support turns out to adversely affect other debuggers, then it will have to be "conditionalized".
* Similarly, we've added support for "K", "M", and "G" MACRO-10-style suffixes that add 3, 6, or 9 zeros
* to the value to be parsed, respectively.
*
* 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.
@ -84,71 +86,66 @@ class Str {
if (s) {
if (!base) base = 10;
var chPrefix = s.charAt(0);
var ch, chPrefix, chSuffix;
var fCommas = (s.indexOf(',') > 0);
if (fCommas) s = s.replace(/,/g, '');
ch = chPrefix = s.charAt(0);
if (chPrefix == '#') {
base = 8;
chPrefix = null;
chPrefix = '';
}
else if (chPrefix == '$') {
base = 16;
chPrefix = null;
chPrefix = '';
}
if (chPrefix == null) {
if (ch != chPrefix) {
s = s.substr(1);
}
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;
}
ch = chPrefix = s.substr(0, 2);
if (chPrefix == '0b' && fCommas || chPrefix == '^B') {
base = 2;
chPrefix = '';
}
else if (chPrefix == '^') {
chPrefix = s.charAt(1);
if (chPrefix == 'D') {
base = 10;
chPrefix = null;
}
else if (chPrefix == 'O') {
base = 8;
chPrefix = null;
}
else if (chPrefix == 'B') {
base = 2;
chPrefix = null;
}
else if (chPrefix == '0o' || chPrefix == '^O') {
base = 8;
chPrefix = '';
}
if (chPrefix == null) {
s = s.substr(2);
else if (chPrefix == '^D') {
base = 10;
chPrefix = '';
}
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);
else if (chPrefix == '0x') {
base = 16;
chPrefix = '';
}
if (ch != chPrefix) s = s.substr(2);
}
ch = chSuffix = s.slice(-1);
if (chSuffix == 'Y' || chSuffix == 'y') {
base = 2;
chSuffix = '';
}
else if (chSuffix == '.') {
base = 10;
chSuffix = '';
}
else if (chSuffix == 'H' || chSuffix == 'h') {
base = 16;
chSuffix = '';
}
else if (chSuffix == 'K') {
chSuffix = '000';
}
else if (chSuffix == 'M') {
chSuffix = '000000';
}
else if (chSuffix == 'G') {
chSuffix = '000000000';
}
if (ch != chSuffix) s = s.slice(0, -1) + chSuffix;
/*
* This adds support for the MACRO-10 binary shifting (Bn) suffix, which must be stripped from the
* number before parsing, and then applied to the value after parsing. If n is omitted, 35 is assumed,
@ -187,7 +184,7 @@ class Str {
}
/**
* toBase(n, radix, cch, sPrefix)
* toBase(n, radix, cch, sPrefix, grouping)
*
* Displays the given number as an unsigned integer using the specified radix and number of digits.
*
@ -195,9 +192,10 @@ class Str {
* @param {number} radix (ie, the base)
* @param {number} cch (the desired number of digits)
* @param {string} [sPrefix] (default is none)
* @param {number} [grouping]
* @return {string}
*/
static toBase(n, radix, cch, sPrefix = "")
static toBase(n, radix, cch, sPrefix = "", grouping = -1)
{
/*
* An initial "falsey" check for null takes care of both null and undefined;
@ -229,15 +227,21 @@ class Str {
cch = Math.ceil(Math.log(n) / Math.log(radix));
}
}
if (n == null) {
while (cch-- > 0) s = '?' + s;
} else {
while (cch-- > 0) {
var g = grouping;
while (cch-- > 0) {
if (!g) {
s = ',' + s;
g = grouping;
}
if (n == null) {
s = '?' + s;
} else {
var d = n % radix;
d += (d >= 0 && d <= 9? 0x30 : 0x41 - 10);
s = String.fromCharCode(d) + s;
n = Math.trunc(n / radix);
}
g--;
}
return sPrefix + s;
}
@ -245,41 +249,27 @@ class Str {
/**
* toBin(n, cch, grouping)
*
* Converts an integer to binary, with the specified number of digits (up to the default of 32).
* Converts an integer to binary, with the specified number of digits (up to a maximum of 36).
*
* @param {number|null|undefined} n (interpreted as a 32-bit value)
* @param {number} [cch] is the desired number of binary digits (32 is both the default and the maximum)
* @param {number|null|undefined} n (supports integers up to 36 bits now)
* @param {number} [cch] is the desired number of binary digits (0 or undefined for default of either 8, 18, or 36)
* @param {number} [grouping]
* @return {string} the binary representation of n
*/
static toBin(n, cch, grouping)
{
var s = "";
if (!cch) {
cch = 32;
} else {
if (cch > 32) cch = 32;
}
/*
* 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.
*/
var fInvalid = (n == null || isNaN(n));
var group = (grouping = grouping || cch);
while (cch-- > 0) {
if (!group) {
s = "," + s;
group = grouping;
// cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.LN2) || 1;
var v = Math.abs(n);
if (v <= 0b11111111) {
cch = 8;
} else if (v <= 0b111111111111111111) {
cch = 18;
} else {
cch = 36;
}
s = (fInvalid? '?' : ((n & 0x1)? '1' : '0')) + s;
n >>= 1;
group--;
}
return s;
} else if (cch > 36) cch = 36;
return Str.toBase(n, 2, cch, "", grouping);
}
/**
@ -314,17 +304,23 @@ class Str {
* an exception, whereas this function will return '?' characters.
*
* @param {number|null|undefined} n (supports integers up to 36 bits now)
* @param {number} [cch] is the desired number of octal digits (0 or undefined for default of either 6 or 11)
* @param {number} [cch] is the desired number of octal digits (0 or undefined for default of either 6, 8, or 12)
* @param {boolean} [fPrefix]
* @return {string} the octal representation of n
*/
static toOct(n, cch, fPrefix)
{
if (cch) {
if (cch > 12) cch = 12;
} else {
cch = (n & ~0xffffff)? 12 : ((n & ~0xffff)? 8 : 6);
}
if (!cch) {
// cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.log(8)) || 1;
var v = Math.abs(n);
if (v <= 0o777777) {
cch = 6;
} else if (v <= 0o77777777) {
cch = 8;
} else {
cch = 12;
}
} else if (cch > 12) cch = 12;
return Str.toBase(n, 8, cch, fPrefix? "0o" : "");
}
@ -338,16 +334,20 @@ class Str {
* an exception, whereas this function will return '?' characters.
*
* @param {number|null|undefined} n (supports integers up to 36 bits now)
* @param {number} [cch] is the desired number of decimal digits (0 or undefined for default of either 5 or 10)
* @param {number} [cch] is the desired number of decimal digits (0 or undefined for default of either 5 or 11)
* @return {string} the decimal representation of n
*/
static toDec(n, cch)
{
if (cch) {
if (cch > 11) cch = 11;
} else {
cch = (n & ~0xffff)? 10 : 5;
}
if (!cch) {
// cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.LN10) || 1;
var v = Math.abs(n);
if (v <= 99999) {
cch = 5;
} else {
cch = 11;
}
} else if (cch > 11) cch = 11;
return Str.toBase(n, 10, cch);
}
@ -369,17 +369,23 @@ class Str {
* s = s.substr(0, cch).toUpperCase();
*
* @param {number|null|undefined} n (supports integers up to 36 bits now)
* @param {number} [cch] is the desired number of hex digits (0 or undefined for default of either 4 or 8)
* @param {number} [cch] is the desired number of hex digits (0 or undefined for default of either 4, 8, or 9)
* @param {boolean} [fPrefix]
* @return {string} the hex representation of n
*/
static toHex(n, cch, fPrefix)
{
if (cch) {
if (cch > 9) cch = 9;
} else {
cch = (n & ~0xffff)? 8 : 4;
}
if (!cch) {
// cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.log(16)) || 1;
var v = Math.abs(n);
if (v <= 0xffff) {
cch = 4;
} else if (v <= 0xffffffff) {
cch = 8;
} else {
cch = 9;
}
} else if (cch > 9) cch = 9;
return Str.toBase(n, 16, cch, fPrefix? "0x" : "");
}