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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -617,7 +617,7 @@ class DebuggerPDP10 extends Debugger {
*/
toStrOffset(off)
{
return this.toStrBase(off);
return this.toStrBase(off, 18);
}
/**
@ -645,7 +645,7 @@ class DebuggerPDP10 extends Debugger {
* ADDR_LIMIT is not derived from WORD_LIMIT; we're just taking advantage of the fact
* that ADDR_LIMIT happens to be exactly half of WORD_LIMIT, and they are both powers of two.
*/
return Str.toOct(w / PDP10.ADDR_LIMIT, 6) + ' ' + Str.toOct(w % PDP10.ADDR_LIMIT, 6);
return this.toStrBase(w / PDP10.ADDR_LIMIT, 18) + ' ' + this.toStrBase(w % PDP10.ADDR_LIMIT, 18);
}
/**

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" : "");
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long