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:
parent
822d400f27
commit
a6021441c3
13 changed files with 665 additions and 637 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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" : "");
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue