Improved fixup handling for better MACRO-10 support

This commit is contained in:
Jeff 2017-03-16 00:32:30 -07:00 committed by Jeff Parsons
commit 0e8e2ed4a2
12 changed files with 975 additions and 916 deletions

View file

@ -502,7 +502,7 @@ class DebuggerPDP10 extends Debugger {
}
/**
* parseAddr(sAddr, dbgAddr, fPrint)
* parseAddr(sAddr, dbgAddr)
*
* Address evaluation and validation (eg, range checks) are no longer performed at this stage. That's
* done later, by getAddr(), which returns PDP10.ADDR_INVALID for invalid segments, out-of-range offsets,
@ -514,10 +514,9 @@ class DebuggerPDP10 extends Debugger {
* @this {DebuggerPDP10}
* @param {string|undefined} sAddr
* @param {DbgAddrPDP10} [dbgAddr]
* @param {boolean} [fPrint]
* @return {DbgAddrPDP10|null|undefined}
*/
parseAddr(sAddr, dbgAddr, fPrint)
parseAddr(sAddr, dbgAddr)
{
var fPhysical, nBase;
if (!dbgAddr) dbgAddr = this.newAddr();
@ -538,7 +537,7 @@ class DebuggerPDP10 extends Debugger {
} else if (sAddr.indexOf('.') >= 0) {
nBase = 10;
}
addr = this.parseExpression(sAddr, fPrint);
addr = this.parseExpression(sAddr);
}
if (addr != null) {
addr = this.validateWord(addr, this.nBusWidth);
@ -1611,19 +1610,27 @@ class DebuggerPDP10 extends Debugger {
}
/**
* parseInstruction(sOpcode, sOperands, addr)
* parseInstruction(sOpcode, sOperands, addr, fQuiet)
*
* @this {DebuggerPDP10}
* @param {string} sOpcode
* @param {string} [sOperands]
* @param {number} [addr] of memory where this instruction is being assembled
* @param {boolean} [fQuiet]
* @return {number} (opcode, or -1 if unrecognized instruction)
*/
parseInstruction(sOpcode, sOperands, addr)
parseInstruction(sOpcode, sOperands, addr, fQuiet)
{
var opCode = -1;
var opMask, opNum;
/*
* It's best to always clear sUndefined up front, because the caller won't
* necessarily know whether or not we had to call parseExpression() for this
* instruction.
*/
this.sUndefined = null;
if (!sOpcode) {
/*
* MACRO-10 permits instructions to be assembled without an explicit opcode;
@ -1700,7 +1707,7 @@ class DebuggerPDP10 extends Debugger {
var sOperand = aOperands[i].trim();
if (!sOperand) continue;
if (i > 1) {
this.println("too many operands: " + sOperand);
this.println("too many operands: " + sOperands);
opCode = -1;
break;
}
@ -1722,7 +1729,7 @@ class DebuggerPDP10 extends Debugger {
sOperand = sOperand.replace(/(^|[^0-9])\./g, "$1" + this.toStrOffset(addr));
}
}
var operand = this.parseExpression(sOperand);
var operand = this.parseExpression(sOperand, fQuiet);
if (operand == undefined) {
opCode = -1;
break;
@ -1751,7 +1758,7 @@ class DebuggerPDP10 extends Debugger {
opCode += operand;
sOperand = match[3];
if (sOperand) {
operand = this.parseExpression(sOperand);
operand = this.parseExpression(sOperand, fQuiet);
if (operand == undefined) {
opCode = -1;
break;
@ -1774,7 +1781,7 @@ class DebuggerPDP10 extends Debugger {
// }
}
if (opCode < 0 && sOperands != null) {
if (opCode < 0 && !fQuiet) {
this.println("unknown instruction: " + sOpcode + ' ' + sOperands);
}
@ -2591,12 +2598,22 @@ class DebuggerPDP10 extends Debugger {
dbg.println("assembly already in progress");
}
else {
var sFile = match[2];
var addrLoad = dbgAddr.addr;
this.macro10 = new Macro10(match[2], addrLoad, sOptions, dbg, function doneMacro10(nErrorCode, sURL) {
this.macro10 = new Macro10(sFile, addrLoad, sOptions, dbg, function doneMacro10(nErrorCode, sURL) {
if (!nErrorCode) {
dbg.loadBin(dbg.macro10.getBin(), addrLoad);
/*
* NOTE: Most Debugger operations run in the context of doCommand(), which catches any exceptions;
* however, this callback may be running in a different context (eg, a network request callback), so
* better safe than sorry.
*/
try {
dbg.loadBin(dbg.macro10.getBin(), addrLoad);
} catch(e) {
dbg.println(e.message);
}
} else {
dbg.println("error (" + nErrorCode + ") processing " + sURL);
dbg.println("error (" + nErrorCode + ") processing " + (sURL || sFile));
}
dbg.macro10 = null;
});
@ -3318,7 +3335,7 @@ class DebuggerPDP10 extends Debugger {
sCmd = Str.trim(sCmd);
var a = sCmd.match(/^(['"])(.*?)\1$/);
if (!a) {
this.parseExpression(sCmd, true);
this.parseExpression(sCmd, false);
} else {
if (a[2].length > 1) {
this.println(this.replaceRegs(a[2]));

View file

@ -67,7 +67,7 @@ var Sym;
/**
* @typedef {{
* nLocation:(number),
* aValues:(Array.<string>),
* sValue:(string)
* }}
*/
var Fixup;
@ -301,50 +301,14 @@ class Macro10 {
}
this.aFixups.forEach(function processFixup(fixup){
var value = 0;
var sValue = fixup.aValues[0];
var sOperand = fixup.aValues[1];
var nLocation = fixup.nLocation;
if (fixup.aValues.length == 1) {
if (sValue.indexOf('@') >= 0 || sValue.indexOf('(') >= 0) {
sOperand = sValue;
sValue = "";
}
var value = macro10.parseExpression(fixup.sValue, undefined, nLocation);
if (value === undefined) {
macro10.error("unable to parse expression: " + fixup.sValue);
return;
}
if (sOperand != null) {
value = macro10.dbg.parseInstruction(sValue, sOperand, nLocation);
if (value < 0) {
macro10.error("unable to parse instruction: " + fixup.aValues[0] + ' ' + fixup.aValues[1]);
}
} else {
value = macro10.parseExpression(sValue, nLocation);
if (value === undefined) {
macro10.error("unable to parse expression: " + sValue);
}
else if (macro10.aWords[nLocation] === undefined) {
macro10.error("undefined fixup location: " + Str.toOct(nLocation, 0, true));
}
else {
value += macro10.aWords[nLocation];
}
}
/*
* At the end of the day, we only want unsigned 36-bit values, and some of our internal calculations
* already adhere to that (eg, the parseExpression() concatenation of two unsigned 18-bit values).
* But in general, the expression parser is allowed to return negative values, so that signed arithmetic
* works naturally. Perhaps that decision should be revisited, but for now, this particular mix of
* behaviors means that when checking for out-of-range values, the upper bound must be the unsigned
* WORD_LIMIT, while the lower bound must be the signed INT_LIMIT.
*
* In a perfect world, the bounds would either be -INT_LIMIT,INT_LIMIT-1 or 0,WORD_LIMIT-1. In any
* event, the truncate() call will ensure everything is an unsigned 36-bit value. The warning should
* trigger only if the original value contained more than 36 significant bits.
*/
var w = macro10.dbg.truncate(value || 0, 36, true);
if (value < -PDP10.INT_LIMIT || value >= PDP10.WORD_LIMIT) {
macro10.warning("truncated value " + Str.toOct(value) + " at location " + Str.toOct(nLocation) + " to " + Str.toOct(w));
}
macro10.aWords[nLocation] = w;
value += macro10.aWords[nLocation];
macro10.aWords[nLocation] = macro10.truncate(value, nLocation);
});
} catch(err) {
@ -406,9 +370,10 @@ class Macro10 {
}
}
this.sOperator = sOperator;
sOperands = sOperands.trim();
if (!sOperator && !sOperands) return true;
/*
* My initial read of the MACRO-10 specification suggested that lines may begin with EITHER
* "symbol:" (for a label) or "symbol=" (for an assignment), but apparently they can have BOTH.
@ -418,6 +383,7 @@ class Macro10 {
sOperands = sOperands.substr(1);
sOperator = '=';
}
this.sOperator = sOperator;
/*
* Check the operands for a literal. If the line contains and/or ends with a literal
@ -440,12 +406,6 @@ class Macro10 {
if (!this.parseMacro(sOperator, sOperands)) {
switch (sOperator) {
case "":
if (sOperands) {
this.genWord(0, [sOperands]);
}
break;
case "=":
this.addAssign(sLabel, sOperands);
break;
@ -471,10 +431,7 @@ class Macro10 {
break;
default:
if (!this.parseOpcode(sOperator, sOperands)) {
this.genWord(0, [sOperator + sOperands]);
// if (DEBUG) this.println(Str.toDec(this.nLine, 5) + ": label(" + sLabel + ") operator(" + sOperator + ") operands(" + sOperands + ") comment(" + sComment + ")");
}
this.addWord(sOperator, sOperands);
break;
}
}
@ -529,24 +486,6 @@ class Macro10 {
return true;
}
/**
* parseOpcode(sOpcode, sOperands)
*
* @this {Macro10}
* @param {string} sOpcode
* @param {string} sOperands
* @return {boolean}
*/
parseOpcode(sOpcode, sOperands)
{
var opCode = this.dbg.parseInstruction(sOpcode);
if (opCode >= 0) {
this.genWord(opCode, [sOpcode, sOperands]);
return true;
}
return false;
}
/**
* isSymbolChar(ch)
*
@ -653,7 +592,7 @@ class Macro10 {
}
/**
* parseExpression(sOperand, nLocation)
* parseExpression(sOperand, fPass1, nLocation)
*
* This is a wrapper around the Debugger's parseExpression() function to take care of some
* additional requirements we have, such as interpreting a period as the current location and
@ -662,38 +601,44 @@ class Macro10 {
*
* @this {Macro10}
* @param {string} sOperand
* @param {number} [nLocation]
* @param {boolean|undefined} [fPass1]
* @param {number|undefined} [nLocation]
* @return {number|undefined}
*/
parseExpression(sOperand, nLocation)
parseExpression(sOperand, fPass1, nLocation)
{
var result;
if (nLocation === undefined) nLocation = this.nLocation;
/*
* Check for the "period" syntax that MACRO-10 uses to represent the value of the current
* location. The Debugger's parseInstruction() method understands that syntax, but its
* parseExpression() method does not.
*
* Note that the Debugger's parseInstruction() replaces any period not PRECEDED by a decimal
* digit with the current address, because our Debuggers' only other interpretation of a period
* is as the suffix of a decimal integer, whereas MACRO-10's only other interpretation of a period
* is (I think) as the decimal point within a floating-point number, so here we only replace
* periods that are not FOLLOWED by a decimal digit.
*/
sOperand = sOperand.replace(/\.([^0-9]|$)/g, this.dbg.toStrBase(nLocation, -1) + "$1");
/*
* Check for the "double comma" syntax that MACRO-10 uses to express a 36-bit value as two 18-bit halves.
* Check for the "double comma" syntax that MACRO-10 uses to express a 36-bit value as two 18-bit halves,
* and invoke ourselves recursively for each half.
*/
var match = sOperand.match(/^([^,]*),,([^,]*)$/);
if (!match) {
result = this.dbg.parseExpression(sOperand);
if (nLocation === undefined) nLocation = this.nLocation;
/*
* Check for the "period" syntax that MACRO-10 uses to represent the value of the current
* location. The Debugger's parseInstruction() method understands that syntax, but its
* parseExpression() method does not.
*
* Note that the Debugger's parseInstruction() replaces any period not PRECEDED by a decimal
* digit with the current address, because our Debuggers' only other interpretation of a period
* is as the suffix of a decimal integer, whereas MACRO-10's only other interpretation of a period
* is (I think) as the decimal point within a floating-point number, so here we only replace
* periods that are not FOLLOWED by a decimal digit.
*/
result = this.dbg.parseExpression(sOperand.replace(/\.([^0-9]|$)/g, this.dbg.toStrBase(nLocation, -1) + "$1"), fPass1);
if (result === undefined) {
this.error("error parsing expression: " + sOperand);
}
// else if (this.dbg.sUndefined != null) {
// /*
// * If a valid result was returned but sUndefined is also set, then this must be a pass1 evaluation.
// */
// }
} else {
var wLeft = match[1]? this.dbg.parseExpression(match[1]) : 0;
var wLeft = match[1]? this.parseExpression(match[1]) : 0;
if (wLeft !== undefined) {
var wRight = match[2]? this.dbg.parseExpression(match[2]) : 0;
var wRight = match[2]? this.parseExpression(match[2]) : 0;
if (wRight !== undefined) {
/*
* NOTE: These must be combined as UNSIGNED values, so that's what we tell truncate().
@ -880,6 +825,9 @@ class Macro10 {
aParms = aDefaults = [];
if (sOperator == Macro10.PSEUDO_OP.DEFINE) {
/*
* This is a DEFINE (macro) block.
*/
match = sOperands.match(/([A-Z$%.][0-9A-Z$%.]*)\s*(\([^)]*\)|)\s*(<|)(.*)/i);
if (!match) {
this.error("unrecognized " + sOperator + " definition: " + sOperands);
@ -902,6 +850,9 @@ class Macro10 {
iMatch = 3;
}
else if (sOperator == Macro10.PSEUDO_OP.LITERAL) {
/*
* This is a LITERAL block.
*/
this.chMacroOpen = '[';
this.chMacroClose = ']';
name = '_' + Str.toDec(this.nLocation, 5);
@ -914,6 +865,9 @@ class Macro10 {
iMatch = 0;
}
else {
/*
* This must be a REPEAT or CONDITIONAL block.
*/
var sOperand = this.getExpression(sOperands);
if (!sOperand) {
this.error("missing " + sOperator + " expression: " + sOperands);
@ -923,6 +877,10 @@ class Macro10 {
sOperand = sOperand.trim();
match = sOperands.match(/\s*(<|)(.*)/i);
name = '_' + sOperator;
/*
* The expression is either a repeat count or a condition. Either way, we must be able to
* resolve it now, so we don't set fPass1 (but that doesn't mean it's the second pass, either).
*/
nOperand = this.parseExpression(sOperand) || 0;
iMatch = 1;
}
@ -1008,6 +966,36 @@ class Macro10 {
this.dbg.setVariable(name, value);
}
/**
* addWord(sOperator, sOperands)
*
* @this {Macro10}
* @param {string} sOperator
* @param {string} sOperands
*/
addWord(sOperator, sOperands)
{
var w;
var sExp = sOperator + sOperands;
if (sExp.indexOf(",,") >= 0 || !sOperator && sExp.indexOf('@') < 0 && sExp.indexOf('(') < 0) {
w = this.parseExpression(sExp, true);
} else {
w = this.dbg.parseInstruction(sOperator, sOperands, this.nLocation, true);
if (w < 0) {
/*
* MACRO-10 also allows instructions to be assembled without an opcode (ie, just an address reference).
*/
w = this.dbg.parseInstruction("", sExp, this.nLocation, true);
if (w < 0) w = undefined;
}
}
if (w !== undefined) {
this.genWord(w, this.dbg.sUndefined);
} else {
this.error("unrecognized expression: " + sExp);
}
}
/**
* addXWD()
*
@ -1019,7 +1007,7 @@ class Macro10 {
*/
addXWD(sOperands)
{
this.genWord(0, [sOperands.replace(",", ",,")]);
this.genWord(0, sOperands.replace(",", ",,"));
}
/**
@ -1068,18 +1056,35 @@ class Macro10 {
}
/**
* genWord(w, aValues)
* genWord(value, sFixup)
*
* @this {Macro10}
* @param {number} w (default value for the current location)
* @param {Array.<string>} [aValues] (optional fixup value(s) to evaluate later)
* @param {number} value (default value for the current location)
* @param {string|null} [sFixup] (optional fixup value to evaluate later)
*/
genWord(w, aValues)
genWord(value, sFixup)
{
this.aWords[this.nLocation] = w;
if (aValues) this.aFixups.push({nLocation: this.nLocation, aValues});
this.aWords[this.nLocation] = this.truncate(value);
if (sFixup != null) this.aFixups.push({nLocation: this.nLocation, sValue: sFixup});
this.nLocation++;
}
/**
* truncate(value, nLocation)
*
* @this {Macro10}
* @param {number} value
* @param {number} [nLocation]
* @return {number}
*/
truncate(value, nLocation = this.nLocation)
{
var w = this.dbg.truncate(value || 0, 36, true);
if (value < -PDP10.INT_LIMIT || value >= PDP10.WORD_LIMIT) {
this.warning("truncated value " + Str.toOct(value) + " at location " + Str.toOct(nLocation) + " to " + Str.toOct(w));
}
return w;
}
}
Macro10.PSEUDO_OP = {