Successfully assembled my first MACRO-10 source file (DAKAD.MAC)
This commit is contained in:
parent
e1f37c8275
commit
3f8705ad98
9 changed files with 631 additions and 476 deletions
|
|
@ -1621,68 +1621,76 @@ class DebuggerPDP10 extends Debugger {
|
|||
*/
|
||||
parseInstruction(sOpcode, sOperands, addr)
|
||||
{
|
||||
var opCode = -1, opMask, opNum;
|
||||
var sMnemonic = sOpcode.toUpperCase();
|
||||
var opCode = -1;
|
||||
var opMask, opNum;
|
||||
|
||||
/*
|
||||
* Perform any alternate mnemonic substitutions first
|
||||
*/
|
||||
for (var m = 0; m < DebuggerPDP10.ALTOPS.length; m++) {
|
||||
for (var n in DebuggerPDP10.ALTOPS[m]) {
|
||||
if (!+n) continue;
|
||||
opNum = DebuggerPDP10.ALTOPS[m][n];
|
||||
if (sMnemonic == DebuggerPDP10.OPNAMES[opNum]) {
|
||||
sMnemonic = DebuggerPDP10.OPNAMES[DebuggerPDP10.ALTOPS[m][0]];
|
||||
if (sOperands) sOperands = this.toStrBase(+n) + ',' + sOperands;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!sOpcode) {
|
||||
/*
|
||||
* MACRO-10 permits instructions to be assembled without an explicit opcode;
|
||||
* an address expression is sufficient. This is done to generate UUO opcodes,
|
||||
* for example.
|
||||
*/
|
||||
if (sOperands) opCode = opMask = 0;
|
||||
}
|
||||
|
||||
for (var mask in DebuggerPDP10.OPTABLE) {
|
||||
|
||||
var aModes;
|
||||
opMask = +mask;
|
||||
var opMasks = DebuggerPDP10.OPTABLE[mask];
|
||||
|
||||
switch(opMask) {
|
||||
case PDP10.OPCODE.OPMODE:
|
||||
aModes = DebuggerPDP10.OPMODES;
|
||||
break;
|
||||
case PDP10.OPCODE.OPCOMP:
|
||||
aModes = DebuggerPDP10.OPCOMPS;
|
||||
break;
|
||||
case PDP10.OPCODE.OPTEST:
|
||||
aModes = DebuggerPDP10.OPTESTS;
|
||||
break;
|
||||
default:
|
||||
aModes = [""];
|
||||
break;
|
||||
}
|
||||
|
||||
var opMode = 0;
|
||||
for (var op in opMasks) {
|
||||
|
||||
opNum = opMasks[op];
|
||||
for (var iMode = 0; iMode < aModes.length; iMode++) {
|
||||
|
||||
var sMode = aModes[iMode];
|
||||
if (sMode == "S" && opNum > DebuggerPDP10.OPS.MOVM) sMode = "B";
|
||||
var sCandidate = DebuggerPDP10.OPNAMES[opNum] + sMode;
|
||||
|
||||
if (sMnemonic == sCandidate) {
|
||||
if (opMask != PDP10.OPCODE.OPTEST) {
|
||||
opMode = iMode;
|
||||
} else {
|
||||
opMode = ((iMode & 0o3) << 1) | ((iMode & 0o14) << 2);
|
||||
}
|
||||
opCode = (op | (opMode << 6)) * PDP10.OPCODE.OP_SCALE;
|
||||
else {
|
||||
var sMnemonic = sOpcode.toUpperCase();
|
||||
/*
|
||||
* Perform any alternate mnemonic substitutions first
|
||||
*/
|
||||
for (var m = 0; m < DebuggerPDP10.ALTOPS.length; m++) {
|
||||
for (var n in DebuggerPDP10.ALTOPS[m]) {
|
||||
if (!+n) continue;
|
||||
opNum = DebuggerPDP10.ALTOPS[m][n];
|
||||
if (sMnemonic == DebuggerPDP10.OPNAMES[opNum]) {
|
||||
sMnemonic = DebuggerPDP10.OPNAMES[DebuggerPDP10.ALTOPS[m][0]];
|
||||
if (sOperands) sOperands = this.toStrBase(+n) + ',' + sOperands;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var mask in DebuggerPDP10.OPTABLE) {
|
||||
|
||||
var aModes;
|
||||
opMask = +mask;
|
||||
var opMasks = DebuggerPDP10.OPTABLE[mask];
|
||||
|
||||
switch (opMask) {
|
||||
case PDP10.OPCODE.OPMODE:
|
||||
aModes = DebuggerPDP10.OPMODES;
|
||||
break;
|
||||
case PDP10.OPCODE.OPCOMP:
|
||||
aModes = DebuggerPDP10.OPCOMPS;
|
||||
break;
|
||||
case PDP10.OPCODE.OPTEST:
|
||||
aModes = DebuggerPDP10.OPTESTS;
|
||||
break;
|
||||
default:
|
||||
aModes = [""];
|
||||
break;
|
||||
}
|
||||
var opMode = 0;
|
||||
for (var op in opMasks) {
|
||||
opNum = opMasks[op];
|
||||
for (var iMode = 0; iMode < aModes.length; iMode++) {
|
||||
|
||||
var sMode = aModes[iMode];
|
||||
if (sMode == "S" && opNum > DebuggerPDP10.OPS.MOVM) sMode = "B";
|
||||
var sCandidate = DebuggerPDP10.OPNAMES[opNum] + sMode;
|
||||
|
||||
if (sMnemonic == sCandidate) {
|
||||
if (opMask != PDP10.OPCODE.OPTEST) {
|
||||
opMode = iMode;
|
||||
} else {
|
||||
opMode = ((iMode & 0o3) << 1) | ((iMode & 0o14) << 2);
|
||||
}
|
||||
opCode = (op | (opMode << 6)) * PDP10.OPCODE.OP_SCALE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (opCode >= 0) break;
|
||||
}
|
||||
if (opCode >= 0) break;
|
||||
}
|
||||
if (opCode >= 0) break;
|
||||
}
|
||||
|
||||
if (opCode >= 0) {
|
||||
|
|
@ -1765,9 +1773,11 @@ class DebuggerPDP10 extends Debugger {
|
|||
// opCode = -1;
|
||||
// }
|
||||
}
|
||||
else if (sOperands != null) {
|
||||
|
||||
if (opCode < 0 && sOperands != null) {
|
||||
this.println("unknown instruction: " + sOpcode + ' ' + sOperands);
|
||||
}
|
||||
|
||||
return opCode;
|
||||
}
|
||||
|
||||
|
|
@ -2451,15 +2461,21 @@ class DebuggerPDP10 extends Debugger {
|
|||
}
|
||||
|
||||
/**
|
||||
* loadAssembly(macro10, addrLoad)
|
||||
* loadBin(aWords, addr)
|
||||
*
|
||||
* @this {DebuggerPDP10}
|
||||
* @param {Macro10} macro10
|
||||
* @param {number|null} addrLoad
|
||||
* @param {Array.<number>} aWords
|
||||
* @param {number|null} addr
|
||||
*/
|
||||
loadAssembly(macro10, addrLoad)
|
||||
loadBin(aWords, addr)
|
||||
{
|
||||
this.println("loadAssembly()");
|
||||
/*
|
||||
* TODO: Decide what to do about the load address (addr); either drop it or use it.
|
||||
*/
|
||||
var bus = this.bus;
|
||||
aWords.forEach(function(w, addr) {
|
||||
bus.setWordDirect(addr, w);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2559,7 +2575,7 @@ class DebuggerPDP10 extends Debugger {
|
|||
var addrLoad = dbgAddr.addr;
|
||||
this.macro10 = new Macro10(sOpcode, addrLoad, dbg, function(macro10, sURL, nErrorCode) {
|
||||
if (!nErrorCode) {
|
||||
dbg.loadAssembly(macro10, addrLoad);
|
||||
dbg.loadBin(macro10.getBin(), addrLoad);
|
||||
} else {
|
||||
dbg.println("error assembling " + sURL + ": (" + nErrorCode + ")");
|
||||
}
|
||||
|
|
@ -2816,10 +2832,15 @@ class DebuggerPDP10 extends Debugger {
|
|||
sData += this.toStrWord(w);
|
||||
sData += ' ';
|
||||
}
|
||||
for (var i = 0; size == 1 && i < 6; i++) {
|
||||
var c = ((w % 64)|0) + 32;
|
||||
sChars += String.fromCharCode(c);
|
||||
w /= 64;
|
||||
/*
|
||||
* TODO: Provide some UI for choosing whether to dump SIXBIT or ASCII data.
|
||||
*/
|
||||
var nBits = 7;
|
||||
var shift = 36 - nBits;
|
||||
for (var i = 0; size == 1 && shift >= 0; i++) {
|
||||
var c = ((w / Math.pow(2, shift)) % Math.pow(2, nBits)) + (nBits == 6? 0x20 : 0);
|
||||
sChars += (c < 0x20? '.' : String.fromCharCode(c));
|
||||
shift -= nBits;
|
||||
}
|
||||
}
|
||||
if (sDump) sDump += "\n";
|
||||
|
|
|
|||
|
|
@ -37,20 +37,26 @@ if (NODE) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Elements of tblMacros.
|
||||
*
|
||||
* @typedef {{
|
||||
* name:(string),
|
||||
* nOperand:(number),
|
||||
* aParms:(Array.<string>),
|
||||
* aDefaults:(Array.<string>),
|
||||
* sText:(string)
|
||||
* sText:(string),
|
||||
* nLine:(number)
|
||||
* }}
|
||||
*/
|
||||
var Mac;
|
||||
|
||||
/**
|
||||
* Elements of tblSymbols.
|
||||
*
|
||||
* @typedef {{
|
||||
* name:(string),
|
||||
* value:(number),
|
||||
* nLine:(number),
|
||||
* fLabel:(boolean),
|
||||
* fGlobal:(boolean),
|
||||
* fPrivate:(boolean)
|
||||
|
|
@ -62,7 +68,6 @@ var Sym;
|
|||
* @typedef {{
|
||||
* nLocation:(number),
|
||||
* aValues:(Array.<string>),
|
||||
* nBits:(number)
|
||||
* }}
|
||||
*/
|
||||
var Fixup;
|
||||
|
|
@ -111,21 +116,23 @@ class Macro10 {
|
|||
/*
|
||||
* Initialize all the tables that MACRO-10 uses.
|
||||
*
|
||||
* The Macros and Symbols tables are fairly straightforward: they are indexed by a macro or symbol
|
||||
* name, and each index points to a Mac or Sym object, respectively.
|
||||
* The Macros (tblMacros) and Symbols (tblSymbols) tables are fairly straightforward: they are
|
||||
* indexed by a macro or symbol name, and each element is a Mac or Sym object, respectively.
|
||||
*
|
||||
* We also treat REPEAT blocks and conditional (eg, IFE) blocks like macros, except that they are
|
||||
* anonymous and immediately invoked as appropriate. What does that mean? Well, REPEAT blocks are
|
||||
* always immediately invoked (repeatedly, based on the repeat count), whereas conditional blocks are
|
||||
* either immediately invoked if the associated condition is true or skipped if the condition is false.
|
||||
* We also treat REPEAT blocks and CONDITIONAL (eg, IFE) blocks like macros, except that they are
|
||||
* anonymous, parameter-less, and immediately invoked. REPEAT blocks are always immediately invoked
|
||||
* (repeatedly, based on the repeat count), whereas CONDITIONAL blocks are either immediately
|
||||
* invoked if the associated condition is true or skipped if the condition is false.
|
||||
*
|
||||
* Finally, we have LITERAL blocks, which are also semi-anonymous (because we give each one an
|
||||
* auto-generated label based on the current line number), but they are never immediately invoked;
|
||||
* instead, after we've finished processing all the lines in the original input file, we run through
|
||||
* all the LITERAL entries in the Macros table and process the associated statement(s).
|
||||
* Finally, we have LITERAL blocks, which are semi-anonymous (we give each one an auto-generated
|
||||
* name based on the current location) and are automatically but not immediately invoked. Instead,
|
||||
* after we've finished processing all the lines in the original input file, we run through all
|
||||
* the LITERAL blocks in tblMacros and process the associated statement(s) at that time.
|
||||
*
|
||||
* REPEAT and LITERAL blocks are assigned internal labels, using a leading underscore ('_') so that
|
||||
* they don't conflict with normal MACRO-10 labels.
|
||||
* Macros have the name that was assigned to them, REPEAT and conditional blocks have generated names
|
||||
* that match the pseudo-op (eg, "_REPEAT", "_IFE"), and LITERAL blocks have generated location-based
|
||||
* names. All generated names use a leading underscore ('_') so that they don't conflict with
|
||||
* normal MACRO-10 labels.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -138,16 +145,12 @@ class Macro10 {
|
|||
*/
|
||||
this.tblSymbols = {};
|
||||
|
||||
this.nLine = 0;
|
||||
this.nError = 0;
|
||||
|
||||
/**
|
||||
* @type {Array.<number>}
|
||||
*/
|
||||
this.aWords = []; // filled in by the various genXXX() functions
|
||||
|
||||
/**
|
||||
* @type {Array.<string>}
|
||||
*
|
||||
* This array is mostly a convenience; we could also enumerate all the literals by walking tblMacros
|
||||
* and looking for elements with MACRO_OP.LITERAL, but the ordering wouldn't necessarily match the order
|
||||
* in which the literals were defined.
|
||||
*/
|
||||
this.aLiterals = [];
|
||||
|
||||
|
|
@ -156,6 +159,13 @@ class Macro10 {
|
|||
*/
|
||||
this.aFixups = [];
|
||||
|
||||
/**
|
||||
* @type {Array.<number|undefined>}
|
||||
*/
|
||||
this.aWords = []; // filled in by the various genXXX() functions
|
||||
|
||||
this.nLine = 0;
|
||||
this.nError = 0;
|
||||
this.nLocation = this.nAddr; // advanced by the various genXXX() functions
|
||||
|
||||
this.sOperator = null; // the active operator, if any
|
||||
|
|
@ -163,12 +173,10 @@ class Macro10 {
|
|||
this.sMacroDef = null; // the active MACRO definition name
|
||||
this.chMacroOpen = this.chMacroClose = '';
|
||||
|
||||
/*
|
||||
/**
|
||||
* If an ASCII/ASCIZ/SIXBIT pseudo-op is active, chASCII is set to the separator and sASCII collects
|
||||
* the intervening character(s).
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {null|string}
|
||||
*/
|
||||
this.chASCII = null;
|
||||
|
|
@ -179,7 +187,7 @@ class Macro10 {
|
|||
this.sASCII = "";
|
||||
|
||||
var macro10 = this;
|
||||
Web.getResource(sURL, null, true, function(sURL, sResource, nErrorCode) {
|
||||
Web.getResource(sURL, null, true, function processMacro10(sURL, sResource, nErrorCode) {
|
||||
if (!nErrorCode) {
|
||||
nErrorCode = macro10.parseFile(sURL, sResource);
|
||||
}
|
||||
|
|
@ -187,6 +195,17 @@ class Macro10 {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* getBin()
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @return {Array.<number>}
|
||||
*/
|
||||
getBin()
|
||||
{
|
||||
return this.aWords;
|
||||
}
|
||||
|
||||
/**
|
||||
* parseFile(sPath, sContents)
|
||||
*
|
||||
|
|
@ -199,6 +218,7 @@ class Macro10 {
|
|||
*/
|
||||
parseFile(sPath, sContents)
|
||||
{
|
||||
var macro10 = this;
|
||||
var a = this.dbg.resetVariables();
|
||||
try {
|
||||
var sText = sContents;
|
||||
|
|
@ -224,50 +244,70 @@ class Macro10 {
|
|||
if (!this.parseLine(asLines[i] + '\r\n')) break;
|
||||
}
|
||||
|
||||
if (this.nMacroDef) {
|
||||
this.error("open block from line " + this.tblMacros[this.sMacroDef].nLine);
|
||||
}
|
||||
|
||||
for (i = 0; i < this.aLiterals.length; i++) {
|
||||
var name = this.aLiterals[i];
|
||||
var macro = this.tblMacros[name];
|
||||
if (!macro) {
|
||||
/*
|
||||
* This is more of an assert(), because it should never happen, regardless of input.
|
||||
*/
|
||||
this.error("missing definition for literal: " + name);
|
||||
continue;
|
||||
}
|
||||
this.parseText(macro.sText);
|
||||
}
|
||||
|
||||
for (i = 0; i < this.aFixups.length; i++) {
|
||||
var w = 0, nBits = 0;
|
||||
var fixup = this.aFixups[i];
|
||||
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.nBits < 0) {
|
||||
w = this.dbg.parseInstruction(fixup.aValues[0], fixup.aValues[1], nLocation);
|
||||
if (w < 0) {
|
||||
this.error("unable to parse instruction: " + fixup.aValues[0] + ' ' + fixup.aValues[1]);
|
||||
break;
|
||||
if (fixup.aValues.length == 1) {
|
||||
if (sValue.indexOf('@') >= 0 || sValue.indexOf('(') >= 0) {
|
||||
sOperand = sValue;
|
||||
sValue = "";
|
||||
}
|
||||
this.aWords[nLocation++] += w;
|
||||
continue;
|
||||
}
|
||||
for (var j = 0; j < fixup.aValues.length; j++) {
|
||||
var sValue = fixup.aValues[j];
|
||||
var value = this.parseExpression(sValue, nLocation);
|
||||
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) {
|
||||
this.error("unable to parse expression: " + sValue);
|
||||
break;
|
||||
macro10.error("unable to parse expression: " + sValue);
|
||||
}
|
||||
w += value;
|
||||
nBits += fixup.nBits;
|
||||
if (nBits < 36) {
|
||||
w *= Math.pow(2, 36 - nBits);
|
||||
} else {
|
||||
if (this.aWords[nLocation] === undefined) {
|
||||
this.error("undefined fixup location: " + Str.toOct(nLocation, 0, true));
|
||||
break;
|
||||
}
|
||||
this.aWords[nLocation++] += w;
|
||||
w = nBits = 0;
|
||||
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;
|
||||
});
|
||||
|
||||
} catch(err) {
|
||||
this.println(err.message);
|
||||
this.nError = -1;
|
||||
|
|
@ -341,7 +381,9 @@ class Macro10 {
|
|||
}
|
||||
|
||||
/*
|
||||
* Check the operands for a literal.
|
||||
* Check the operands for a literal. If the line contains and/or ends with a literal
|
||||
* we record it and replace it with an internal symbol. We assume only one literal per line,
|
||||
* especially since they can be open-ended (ie, continue for multiple lines).
|
||||
*/
|
||||
var sLiteral = this.getLiteral(sOperands);
|
||||
if (sLiteral) {
|
||||
|
|
@ -349,18 +391,21 @@ class Macro10 {
|
|||
}
|
||||
|
||||
/*
|
||||
* Check the operands for a reserved symbol.
|
||||
* Check the operands for any reserved symbols (ie, symbols with a trailing '#', such as "USER#").
|
||||
*/
|
||||
var sReserved = this.getReserved(sOperands);
|
||||
if (sReserved) {
|
||||
sOperands = sOperands.replace(sReserved, sReserved.slice(0, -1));
|
||||
var sSymbol;
|
||||
while (sSymbol = this.getReserved(sOperands)) {
|
||||
sOperands = sOperands.replace(sSymbol, sSymbol.slice(0, -1));
|
||||
}
|
||||
|
||||
if (!this.parseMacro(sOperator, sOperands)) {
|
||||
|
||||
switch (sOperator) {
|
||||
case "":
|
||||
break; // eg, a blank line or a line that contains only a label and/or a comment
|
||||
if (sOperands) {
|
||||
this.genWord(0, [sOperands]);
|
||||
}
|
||||
break;
|
||||
|
||||
case "=":
|
||||
this.addAssign(sLabel, sOperands);
|
||||
|
|
@ -457,7 +502,7 @@ class Macro10 {
|
|||
{
|
||||
var opCode = this.dbg.parseInstruction(sOpcode);
|
||||
if (opCode >= 0) {
|
||||
this.genWord(opCode, [sOpcode, sOperands], -1);
|
||||
this.genWord(opCode, [sOpcode, sOperands]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -572,7 +617,9 @@ class Macro10 {
|
|||
* parseExpression(sOperand, nLocation)
|
||||
*
|
||||
* This is a wrapper around the Debugger's parseExpression() function to take care of some
|
||||
* additional requirements we have, such as interpreting '.' as the current location counter.
|
||||
* additional requirements we have, such as interpreting a period as the current location and
|
||||
* interpreting two expressions separated by two commas as the left and right 18-bit halves
|
||||
* of a 36-bit value.
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sOperand
|
||||
|
|
@ -581,21 +628,49 @@ class Macro10 {
|
|||
*/
|
||||
parseExpression(sOperand, nLocation)
|
||||
{
|
||||
var result;
|
||||
|
||||
if (nLocation === undefined) nLocation = this.nLocation;
|
||||
|
||||
/*
|
||||
* 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 as
|
||||
* the decimal point within a floating-point number, so here we only replace periods that are
|
||||
* not FOLLOWED by a decimal digit.
|
||||
* 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");
|
||||
return this.dbg.parseExpression(sOperand);
|
||||
|
||||
/*
|
||||
* Check for the "double comma" syntax that MACRO-10 uses to express a 36-bit value as two 18-bit halves.
|
||||
*/
|
||||
var match = sOperand.match(/^([^,]*),,([^,]*)$/);
|
||||
if (!match) {
|
||||
result = this.dbg.parseExpression(sOperand);
|
||||
} else {
|
||||
var wLeft = match[1]? this.dbg.parseExpression(match[1]) : 0;
|
||||
if (wLeft !== undefined) {
|
||||
var wRight = match[2]? this.dbg.parseExpression(match[2]) : 0;
|
||||
if (wRight !== undefined) {
|
||||
/*
|
||||
* NOTE: These must be combined as UNSIGNED values, so that's what we tell truncate().
|
||||
*/
|
||||
result = this.dbg.truncate(wLeft, 18, true) * Math.pow(2, 18) + this.dbg.truncate(wRight, 18, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* getLiteral(sOperands)
|
||||
*
|
||||
* Check the operands for a literal (ie, an expression starting with a square bracket).
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sOperands
|
||||
* @return {string|null} (if the operands contain a literal, return it)
|
||||
|
|
@ -625,6 +700,8 @@ class Macro10 {
|
|||
/**
|
||||
* getReserved(sOperands)
|
||||
*
|
||||
* Check the operands for any reserved symbols (ie, symbols with a trailing '#', such as "USER#").
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sOperands
|
||||
* @return {string|null} (if the operands contain a reserved symbol, return it)
|
||||
|
|
@ -633,11 +710,21 @@ class Macro10 {
|
|||
{
|
||||
var match, sReserved = null;
|
||||
if (match = sOperands.match(/([A-Z$%.][0-9A-Z$%.]*)#/i)) {
|
||||
sReserved = match[0];
|
||||
var sLabel = match[1];
|
||||
var name = '_' + sLabel;
|
||||
this.tblMacros[name] = {name: name, nOperand: 0, aParms: [], aDefaults: [], sText: sLabel + ": 0"};
|
||||
if (this.tblMacros[name] !== undefined) {
|
||||
this.error("reserved symbol redefined: " + sReserved);
|
||||
}
|
||||
this.tblMacros[name] = {
|
||||
name: name,
|
||||
nOperand: Macro10.MACRO_OP.RESERVED,
|
||||
aParms: [],
|
||||
aDefaults: [],
|
||||
sText: sLabel + ": 0",
|
||||
nLine: this.nLine
|
||||
};
|
||||
this.aLiterals.push(name);
|
||||
sReserved = match[0];
|
||||
}
|
||||
return sReserved;
|
||||
}
|
||||
|
|
@ -733,7 +820,7 @@ class Macro10 {
|
|||
*
|
||||
* REPEAT blocks piggy-back on this code because they're essentially anonymous immediately-invoked macros;
|
||||
* we use an illegal MACRO-10 symbol ('_REPEAT') to name the anonymous macro while it's being defined, and the
|
||||
* macro's nOperand field will contain the repeat count (-1 for regular macros).
|
||||
* macro's nOperand field will contain the repeat count.
|
||||
*
|
||||
* The piggy-backing continues with other pseudo-ops like IFE, which again contain an anonymous block of text
|
||||
* that is immediately invoked if the criteria associated with the expression stored in the nOperand field is
|
||||
|
|
@ -747,10 +834,11 @@ class Macro10 {
|
|||
*/
|
||||
addMacro(sOperator, sOperands)
|
||||
{
|
||||
var match, name, nOperand, aParms, aDefaults = [], iBracket;
|
||||
var match, name, nOperand, aParms, aDefaults, iMatch;
|
||||
|
||||
this.chMacroOpen = '<';
|
||||
this.chMacroClose = '>';
|
||||
aParms = aDefaults = [];
|
||||
|
||||
if (sOperator == Macro10.PSEUDO_OP.DEFINE) {
|
||||
match = sOperands.match(/([A-Z$%.][0-9A-Z$%.]*)\s*(\([^)]*\)|)\s*(<|)(.*)/i);
|
||||
|
|
@ -758,25 +846,33 @@ class Macro10 {
|
|||
this.error("unrecognized " + sOperator + " definition: " + sOperands);
|
||||
return "";
|
||||
}
|
||||
name = match[1];
|
||||
/*
|
||||
* TODO: This may not actually be an error, but I'd like to take a look if/when it happens.
|
||||
*/
|
||||
if (this.tblMacros[name] !== undefined) {
|
||||
this.error("macro redefined: " + name);
|
||||
}
|
||||
/*
|
||||
* TODO: Tighten up this parsing at some point. All this is doing is extracting entire symbols
|
||||
* from within the parentheses, if any; it's NOT ensuring that those symbols are comma-separated
|
||||
* with no other intervening characters.
|
||||
*/
|
||||
name = match[1];
|
||||
aParms = match[2].match(/[A-Z$%.][0-9A-Z$%.]*/g);
|
||||
nOperand = -1;
|
||||
iBracket = 3;
|
||||
nOperand = Macro10.MACRO_OP.DEFINE;
|
||||
iMatch = 3;
|
||||
}
|
||||
else if (sOperator == Macro10.PSEUDO_OP.LITERAL) {
|
||||
this.chMacroOpen = '[';
|
||||
this.chMacroClose = ']';
|
||||
name = '_' + Str.toDec(this.nLocation, 5);
|
||||
this.aLiterals.push(name);
|
||||
if (this.tblMacros[name] !== undefined) {
|
||||
this.error("literal symbol redefined: " + name);
|
||||
}
|
||||
match = [sOperands[0], name + ": " + sOperands.substr(1)];
|
||||
aParms = [];
|
||||
nOperand = this.nLine;
|
||||
iBracket = 0;
|
||||
nOperand = Macro10.MACRO_OP.LITERAL;
|
||||
this.aLiterals.push(name);
|
||||
iMatch = 0;
|
||||
}
|
||||
else {
|
||||
var sOperand = this.getExpression(sOperands);
|
||||
|
|
@ -788,9 +884,8 @@ class Macro10 {
|
|||
sOperand = sOperand.trim();
|
||||
match = sOperands.match(/\s*(<|)(.*)/i);
|
||||
name = '_' + sOperator;
|
||||
aParms = [];
|
||||
nOperand = this.parseExpression(sOperand) || 0;
|
||||
iBracket = 1;
|
||||
iMatch = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -802,20 +897,16 @@ class Macro10 {
|
|||
this.nMacroDef = 1;
|
||||
|
||||
var sText = "";
|
||||
if (match[iBracket]) { // if there IS also an opening bracket...
|
||||
if (match[iMatch]) { // if there IS also an opening bracket...
|
||||
this.nMacroDef = 2; // then the macro definition has begun
|
||||
sText = match[iBracket + 1];
|
||||
sText = match[iMatch + 1];
|
||||
if (sText.slice(-1) == this.chMacroClose) { // and if there is ALSO a closing bracket...
|
||||
this.nMacroDef = 0; // the macro definition has ended as well
|
||||
sText = sText.slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
// if (this.tblMacros[name] !== undefined) {
|
||||
// this.warning("macro redefined: " + name);
|
||||
// }
|
||||
|
||||
this.tblMacros[name] = {name, nOperand, aParms, aDefaults, sText};
|
||||
this.tblMacros[name] = {name, nOperand, aParms, aDefaults, sText, nLine: this.nLine};
|
||||
|
||||
if (!this.nMacroDef) {
|
||||
this.parseMacro(name);
|
||||
|
|
@ -874,19 +965,22 @@ class Macro10 {
|
|||
this.error("label " + name + " redefined");
|
||||
return;
|
||||
}
|
||||
this.tblSymbols[name] = {name, value, fLabel, fGlobal, fPrivate};
|
||||
this.tblSymbols[name] = {name, value, nLine: this.nLine, fLabel, fGlobal, fPrivate};
|
||||
this.dbg.setVariable(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* addXWD()
|
||||
*
|
||||
* The XWD pseudo-op appears to be equivalent to two values separated by two commas, which our fixup code
|
||||
* must also support, so we simply treat the XWD operands as a fixup expression.
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sOperands
|
||||
*/
|
||||
addXWD(sOperands)
|
||||
{
|
||||
this.genWord(0, this.getValues(sOperands), 18);
|
||||
this.genWord(0, [sOperands.replace(",", ",,")]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -935,17 +1029,16 @@ class Macro10 {
|
|||
}
|
||||
|
||||
/**
|
||||
* genWord(w, aValues, nBits)
|
||||
* genWord(w, aValues)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {number} w
|
||||
* @param {Array.<string>} [aValues]
|
||||
* @param {number} [nBits] (-1 for opcode, otherwise the size of the data; default is 36)
|
||||
* @param {number} w (default value for the current location)
|
||||
* @param {Array.<string>} [aValues] (optional fixup value(s) to evaluate later)
|
||||
*/
|
||||
genWord(w, aValues, nBits = 36)
|
||||
genWord(w, aValues)
|
||||
{
|
||||
this.aWords[this.nLocation] = w;
|
||||
if (aValues) this.aFixups.push({nLocation: this.nLocation, aValues, nBits});
|
||||
if (aValues) this.aFixups.push({nLocation: this.nLocation, aValues});
|
||||
this.nLocation++;
|
||||
}
|
||||
}
|
||||
|
|
@ -962,3 +1055,14 @@ Macro10.PSEUDO_OP = {
|
|||
SUBTTL: "SUBTTL",
|
||||
XWD: "XWD"
|
||||
};
|
||||
|
||||
/*
|
||||
* This enumerates the kinds of macros stored in tblMacros. The nOperand field should contain
|
||||
* one of these values, unless it's a REPEAT or CONDITIONAL block, in which case it will contain
|
||||
* either a repeat count or conditional value.
|
||||
*/
|
||||
Macro10.MACRO_OP = {
|
||||
DEFINE: -1,
|
||||
LITERAL: -2,
|
||||
RESERVED: -3,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -362,23 +362,47 @@ class Debugger extends Component {
|
|||
}
|
||||
|
||||
/**
|
||||
* truncate(v)
|
||||
* truncate(v, nBits, fUnsigned)
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {number} v
|
||||
* @param {number} [nBits]
|
||||
* @param {boolean} [fUnsigned]
|
||||
* @return {number}
|
||||
*/
|
||||
truncate(v)
|
||||
truncate(v, nBits, fUnsigned)
|
||||
{
|
||||
if (this.nBits <= 32) {
|
||||
v = v|0;
|
||||
} else {
|
||||
var limit = Math.pow(2, this.nBits-1);
|
||||
if (v < -limit || v >= limit) {
|
||||
var vNew = v % limit;
|
||||
if (DEBUG) this.println("warning: value " + v + " truncated to " + vNew);
|
||||
v = vNew;
|
||||
var limit, vNew = v;
|
||||
nBits = nBits || this.nBits;
|
||||
|
||||
if (fUnsigned) {
|
||||
if (nBits == 32) {
|
||||
vNew = v >>> 0;
|
||||
}
|
||||
else if (nBits < 32) {
|
||||
vNew = v & ((1 << nBits) - 1);
|
||||
}
|
||||
else {
|
||||
limit = Math.pow(2, nBits);
|
||||
if (v < 0 || v >= limit) {
|
||||
vNew = v % limit;
|
||||
if (vNew < 0) vNew += limit;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (nBits <= 32) {
|
||||
vNew = v | 0;
|
||||
} else {
|
||||
limit = Math.pow(2, nBits - 1);
|
||||
if (v < -limit || v >= limit) {
|
||||
vNew = v % limit;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (v != vNew) {
|
||||
if (MAXDEBUG) this.println("warning: value " + v + " truncated to " + vNew);
|
||||
v = vNew;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
|
@ -481,7 +505,7 @@ class Debugger extends Component {
|
|||
default:
|
||||
return false;
|
||||
}
|
||||
aVals.push(this.truncate(valNew));
|
||||
aVals.push(this.truncate(valNew, this.nBits));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -571,7 +595,7 @@ class Debugger extends Component {
|
|||
fPrint = false;
|
||||
break;
|
||||
}
|
||||
aVals.push(v);
|
||||
aVals.push(this.truncate(v, this.nBits));
|
||||
if (i == asValues.length) break;
|
||||
var sOp = asValues[i++], cchOp = sOp.length;
|
||||
this.assert(Debugger.aBinOpPrecedence[sOp] != null);
|
||||
|
|
|
|||
Loading…
Reference in a new issue