Added lots of MACRO-10 parsing improvements

This commit is contained in:
Jeff 2017-03-13 15:41:07 -07:00 committed by Jeff Parsons
commit d591b08aea
3 changed files with 527 additions and 323 deletions

View file

@ -1547,11 +1547,15 @@ class DebuggerPDP10 extends Debugger {
} else {
n = (opCode >> PDP10.OPCODE.A_SHIFT) & PDP10.OPCODE.A_MASK;
sOperand = this.toStrBase(n, -1);
if (opNum == DebuggerPDP10.OPS.JFCL) {
var opAlt = DebuggerPDP10.JFCL[n];
if (opAlt) {
sOperation = DebuggerPDP10.OPNAMES[opAlt];
sOperand = "";
if (n) {
for (var m = 0; m < DebuggerPDP10.ALTOPS.length; m++) {
if (opNum == DebuggerPDP10.ALTOPS[m][0]) {
var opAlt = DebuggerPDP10.ALTOPS[m][n];
if (opAlt) {
sOperation = DebuggerPDP10.OPNAMES[opAlt];
sOperand = "";
}
}
}
}
}
@ -1582,17 +1586,17 @@ class DebuggerPDP10 extends Debugger {
var opCode = this.getWord(dbgAddr, 1);
var sOperation = this.findInstruction(opCode);
var sOpCodes = "";
var sOpcodes = "";
var sLine = this.toStrAddr(dbgAddrOp) + ":";
if (dbgAddrOp.addr !== PDP10.ADDR_INVALID && dbgAddr.addr !== PDP10.ADDR_INVALID) {
do {
var w = this.getWord(dbgAddrOp, 1);
sOpCodes += ' ' + this.toStrWord(w);
sOpcodes += ' ' + this.toStrWord(w);
if (dbgAddrOp.addr == null) break;
} while (dbgAddrOp.addr != dbgAddr.addr);
}
sLine += Str.pad(sOpCodes, 16) + sOperation;
sLine += Str.pad(sOpcodes, 16) + sOperation;
if (sComment) {
sLine = Str.pad(sLine, 48) + ';' + (sComment || "");
@ -1607,28 +1611,31 @@ class DebuggerPDP10 extends Debugger {
}
/**
* parseInstruction(sOpCode, sOperands, dbgAddr)
* parseInstruction(sOpcode, sOperands, addr)
*
* @this {DebuggerPDP10}
* @param {string} sOpCode
* @param {string|undefined} sOperands
* @param {DbgAddrPDP10} dbgAddr of memory where this instruction is being assembled
* @param {string} sOpcode
* @param {string} [sOperands]
* @param {number} [addr] of memory where this instruction is being assembled
* @return {number} (opcode, or -1 if unrecognized instruction)
*/
parseInstruction(sOpCode, sOperands, dbgAddr)
parseInstruction(sOpcode, sOperands, addr)
{
var opCode = -1, opMask, opNum;
var sMnemonic = sOpCode.toUpperCase();
var sMnemonic = sOpcode.toUpperCase();
/*
* Perform any alternate mnemonic substitutions first
*/
for (var n in DebuggerPDP10.JFCL) {
opNum = DebuggerPDP10.JFCL[n];
if (sMnemonic == DebuggerPDP10.OPNAMES[opNum]) {
sMnemonic = DebuggerPDP10.OPNAMES[DebuggerPDP10.OPS.JFCL];
sOperands = this.toStrBase(+n) + ',' + sOperands;
break;
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;
}
}
}
@ -1679,11 +1686,7 @@ class DebuggerPDP10 extends Debugger {
}
if (opCode >= 0) {
if (!sOperands) {
this.println("missing operand(s)");
opCode = -1;
}
else {
if (sOperands) {
var aOperands = sOperands.split(',');
for (var i = 0; i < aOperands.length; i++) {
var sOperand = aOperands[i].trim();
@ -1705,7 +1708,7 @@ class DebuggerPDP10 extends Debugger {
* If this is NOT the first operand, then replace all periods NOT preceded
* by a digit with the current address.
*/
sOperand = sOperand.replace(/(^|[^0-9])\./g, "$1" + this.toStrAddr(dbgAddr));
sOperand = sOperand.replace(/(^|[^0-9])\./g, "$1" + this.toStrOffset(addr));
}
var operand = this.parseExpression(sOperand);
if (operand == undefined) {
@ -1756,9 +1759,16 @@ class DebuggerPDP10 extends Debugger {
}
}
}
//
// TODO: Complain about missing operands only if we know the instruction requires them.
//
// else {
// this.println("missing operand(s)");
// opCode = -1;
// }
}
else {
this.println("unknown instruction: " + sOpCode);
else if (sOperands != null) {
this.println("unknown instruction: " + sOpcode + ' ' + sOperands);
}
return opCode;
}
@ -2533,23 +2543,23 @@ class DebuggerPDP10 extends Debugger {
var dbgAddr = this.parseAddr(asArgs[1], this.dbgAddrAssemble);
if (!dbgAddr) return;
var sOpCode = asArgs[2];
var sOpcode = asArgs[2];
if (sOpCode == undefined) {
if (sOpcode == undefined) {
this.println("begin assemble at " + this.toStrAddr(dbgAddr));
this.fAssemble = true;
this.cmp.updateDisplays();
return;
}
if (sOpCode.indexOf(':') >= 0) {
if (sOpcode.indexOf(':') >= 0) {
var dbg = this;
if (this.macro10) {
dbg.println("assembly already in progress");
}
else {
var addrLoad = dbgAddr.addr;
this.macro10 = new Macro10(sOpCode, addrLoad, dbg, function(macro10, sURL, nErrorCode) {
this.macro10 = new Macro10(sOpcode, addrLoad, dbg, function(macro10, sURL, nErrorCode) {
if (!nErrorCode) {
dbg.loadAssembly(macro10, addrLoad);
} else {
@ -2565,7 +2575,7 @@ class DebuggerPDP10 extends Debugger {
asArgs.shift();
asArgs.shift();
var sOperands = asArgs.join("");
var opCode = this.parseInstruction(sOpCode, sOperands, dbgAddr);
var opCode = this.parseInstruction(sOpcode, sOperands, dbgAddr.addr || 0);
if (opCode >= 0) {
this.setWord(dbgAddr, opCode);
@ -3921,7 +3931,8 @@ if (DEBUGGER) {
BLKI: 93, DATAI: 94, BLKO: 95, DATAO: 96,
CONO: 97, CONI: 98, CONSZ: 99, CONSO: 100,
UUO: 101, JOV: 102, JCRY0: 103, JCRY1: 104,
JCRY: 105, JFOV: 106
JCRY: 105, JFOV: 106, HALT: 107, JRSTF: 108,
JEN: 109
};
/*
@ -3955,7 +3966,8 @@ if (DEBUGGER) {
"BLKI", "DATAI", "BLKO", "DATAO",
"CONO", "CONI", "CONSZ", "CONSO",
"UUO", "JOV", "JCRY0", "JCRY1",
"JCRY", "JFOV"
"JCRY", "JFOV", "HALT", 'JRSTF',
"JEN"
];
DebuggerPDP10.REGS = {
@ -4101,11 +4113,10 @@ if (DEBUGGER) {
/*
* Apparently, DEC's MACRO program permits "JFCL xxx" (with a single argument) as an alternate for "JFCL 0,xxx"
* (which in turn is long-hand for "No-op", since JFCL with 0 does nothing). If we allowed JFCL to be both a
* primary and a alternate mnemonic, that would complicate our simplistic parseInstruction() logic, so we don't.
* (which in turn is long-hand for "No-op", since JFCL with 0 does nothing).
*/
DebuggerPDP10.JFCL = {
// 0o00: DebuggerPDP10.OPS.JFCL,
0o00: DebuggerPDP10.OPS.JFCL,
0o10: DebuggerPDP10.OPS.JOV,
0o04: DebuggerPDP10.OPS.JCRY0,
0o02: DebuggerPDP10.OPS.JCRY1,
@ -4113,6 +4124,17 @@ if (DEBUGGER) {
0o01: DebuggerPDP10.OPS.JFOV
};
DebuggerPDP10.JRST = {
0o00: DebuggerPDP10.OPS.JRST,
0o04: DebuggerPDP10.OPS.HALT,
0o02: DebuggerPDP10.OPS.JRSTF,
0o12: DebuggerPDP10.OPS.JEN
};
DebuggerPDP10.ALTOPS = [
DebuggerPDP10.JFCL, DebuggerPDP10.JRST
];
DebuggerPDP10.HISTORY_LIMIT = DEBUG? 100000 : 1000;
DebuggerPDP10.PROMPT = ">> ";

View file

@ -40,7 +40,8 @@ if (NODE) {
* @typedef {{
* name:(string),
* nOperand:(number),
* aParms:(string),
* aParms:(Array.<string>),
* aDefaults:(Array.<string>),
* sText:(string)
* }}
*/
@ -57,6 +58,15 @@ var Mac;
*/
var Sym;
/**
* @typedef {{
* nLocation:(number),
* aValues:(Array.<string>),
* nBits:(number)
* }}
*/
var Fixup;
/**
* @class Macro10
* @property {string} sURL
@ -114,13 +124,37 @@ class Macro10 {
* 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).
*/
/**
* @type {Object.<Mac>}
*/
this.tblMacros = {};
/**
* @type {Object.<Sym>}
*/
this.tblSymbols = {};
this.nLine = 0;
this.nError = 0;
/**
* @type {Array.<number>}
*/
this.aWords = []; // filled in by the various genXXX() functions
/**
* @type {Array.<string>}
*/
this.aLiterals = [];
/**
* @type {Array.<Fixup>}
*/
this.aFixups = [];
this.nLocation = this.nAddr; // advanced by the various genXXX() functions
this.sOperator = null; // the active operator, if any
this.nMacroDef = 0; // the active MACRO definition state
this.sMacroDef = null; // the active MACRO definition name
@ -179,12 +213,57 @@ class Macro10 {
match = sText.match(/&[a-z]+;/i);
if (match) this.warning("unrecognized HTML entity: " + match[0]);
}
var i;
var asLines = sText.split(/\r?\n/);
for (i = 0; i < asLines.length; i++) {
this.nLine++;
if (!this.parseLine(asLines[i] + '\r\n')) break;
}
for (i = 0; i < this.aLiterals.length; i++) {
var macro = this.tblMacros[this.aLiterals[i]];
if (!macro) {
this.error("missing definition for literal: " + this.aLiterals[i]);
continue;
}
this.parseText(macro.sText);
}
for (var f in this.aFixups) {
var w = 0, nBits = 0;
var fixup = this.aFixups[+f];
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;
}
this.aWords[nLocation++] += w;
continue;
}
for (i = 0; i < fixup.aValues.length; i++) {
var sValue = fixup.aValues[i];
var value = this.parseExpression(sValue);
if (value === undefined) {
this.error("unable to parse expression: " + sValue);
break;
}
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;
}
}
}
} catch(err) {
this.println(err.message);
this.nError = -1;
@ -247,6 +326,16 @@ class Macro10 {
this.sOperator = sOperator;
sOperands = sOperands.trim();
/*
* 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.
*/
if (sOperands[0] == '=') {
sLabel = sOperator;
sOperands = sOperands.substr(1);
sOperator = '=';
}
/*
* Check the operands for a literal.
*/
@ -255,6 +344,14 @@ class Macro10 {
sOperands = sOperands.replace(sLiteral, this.addMacro(Macro10.PSEUDO_OP.LITERAL, sLiteral));
}
/*
* Check the operands for a reserved symbol.
*/
var sReserved = this.getReserved(sOperands);
if (sReserved) {
sOperands = sOperands.replace(sReserved, sReserved.slice(0, -1));
}
if (!this.parseMacro(sOperator, sOperands)) {
switch (sOperator) {
@ -271,6 +368,10 @@ class Macro10 {
this.addASCII(sOperands);
break;
case Macro10.PSEUDO_OP.XWD:
this.addXWD(sOperands);
break;
case Macro10.PSEUDO_OP.DEFINE:
case Macro10.PSEUDO_OP.IFE:
case Macro10.PSEUDO_OP.REPEAT:
@ -282,16 +383,13 @@ class Macro10 {
break;
default:
if (DEBUG) this.println(Str.toDec(this.nLine, 5) + ": label(" + sLabel + ") operator(" + sOperator + ") operands(" + sOperands + ") comment(" + sComment + ")");
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 + ")");
}
break;
}
}
if (this.nLine >= 820) {
this.dbg.printVariable();
this.error("temporary line limit reached");
return false;
}
return true;
}
@ -312,19 +410,12 @@ class Macro10 {
/*
* Process this macro call; start by extracting macro argument values from sOperands.
*/
var aValues = [];
var sDelims = ',';
var sDelim = ',';
if (sOperands[0] == '(') {
sDelims += ')';
sDelim += ')';
sOperands = sOperands.substr(1);
}
while (sOperands) {
sOperands = sOperands.trim();
var sExp = this.getExpression(sOperands, sDelims);
if (!sExp) break;
aValues.push(sExp);
sOperands = sOperands.substr(sExp.length + 1);
}
var aValues = this.getValues(sOperands, sDelim);
this.parseText(macro.sText, macro.aParms, aValues);
return true;
}
@ -350,6 +441,24 @@ 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], -1);
return true;
}
return false;
}
/**
* isSymbolChar(ch)
*
@ -419,21 +528,21 @@ class Macro10 {
}
/**
* getExpression(sOperands, sDelims)
* getExpression(sOperands, sDelim)
*
* @this {Macro10}
* @param {string} sOperands
* @param {string} [sDelims] (eg, comma, closing parenthesis)
* @param {string} [sDelim] (eg, comma, closing parenthesis)
* @return {string|null} (if the operands begin with an expression, return it)
*/
getExpression(sOperands, sDelims = ",")
getExpression(sOperands, sDelim = ",")
{
var i = 0;
var sExp = null;
var sOperand = null;
var cNesting = 0;
while (i < sOperands.length) {
var ch = sOperands[i];
if (sDelims.indexOf(ch) >= 0) {
if (sDelim.indexOf(ch) >= 0) {
break;
}
if (ch == '<') {
@ -447,12 +556,35 @@ class Macro10 {
i++;
}
if (!cNesting) {
sExp = sOperands.substr(0, i);
sOperand = sOperands.substr(0, i);
}
else if (cNesting > 0) {
this.error("extra bracket(s): " + sOperands);
}
return sExp;
return sOperand;
}
/**
* parseExpression(sOperand)
*
* 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.
*
* @this {Macro10}
* @param {string} sOperand
* @return {number|undefined}
*/
parseExpression(sOperand)
{
/*
* 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.
*/
sOperand = sOperand.replace(/\.([^0-9]|$)/g, "$1" + this.dbg.toStrBase(this.nLocation));
return this.dbg.parseExpression(sOperand);
}
/**
@ -464,8 +596,8 @@ class Macro10 {
*/
getLiteral(sOperands)
{
var sLit = null;
var cNesting = 0;
var sLiteral = null;
var i = 0, iLiteral = -1;
while (i < sOperands.length) {
var ch = sOperands[i];
@ -479,9 +611,50 @@ class Macro10 {
this.error("missing bracket(s): " + sOperands);
}
if (iLiteral >= 0) {
sLit = sOperands.substr(iLiteral, i - iLiteral);
sLiteral = sOperands.substr(iLiteral, i - iLiteral);
}
return sLit;
return sLiteral;
}
/**
* getReserved(sOperands)
*
* @this {Macro10}
* @param {string} sOperands
* @return {string|null} (if the operands contain a reserved symbol, return it)
*/
getReserved(sOperands)
{
var match, sReserved = null;
if (match = sOperands.match(/([A-Z$%.][0-9A-Z$%.]*)#/i)) {
var sLabel = match[1];
var name = '@' + sLabel;
this.tblMacros[name] = {name: name, nOperand: 0, aParms: [], aDefaults: [], sText: sLabel + ": 0"};
this.aLiterals.push(name);
sReserved = match[0];
}
return sReserved;
}
/**
* getValues(sOperands, sDelim)
*
* @this {Macro10}
* @param {string} sOperands
* @param {string} [sDelim]
* @return {Array.<string>}
*/
getValues(sOperands, sDelim)
{
var aValues = [];
while (sOperands) {
sOperands = sOperands.trim();
var sOperand = this.getExpression(sOperands, sDelim);
if (!sOperand) break;
aValues.push(sOperand);
sOperands = sOperands.substr(sOperand.length + 1);
}
return aValues;
}
/**
@ -519,17 +692,17 @@ class Macro10 {
}
/**
* addAssign(sName, sExp)
* addAssign(sName, sOperand)
*
* @this {Macro10}
* @param {string} sName
* @param {string} sExp
* @param {string} sOperand
*/
addAssign(sName, sExp)
addAssign(sName, sOperand)
{
var value = this.dbg.parseExpression(sExp);
var value = this.parseExpression(sOperand);
if (value === undefined) {
this.error("parseExpression(" + sExp + ")");
this.error("parseExpression(" + sOperand + ")");
return;
}
this.addSymbol(sName, value);
@ -543,7 +716,7 @@ class Macro10 {
*/
addLabel(sLabel)
{
this.addSymbol(sLabel, this.nAddr, true);
this.addSymbol(sLabel, this.nLocation, true);
}
/**
@ -568,7 +741,7 @@ class Macro10 {
*/
addMacro(sOperator, sOperands)
{
var match, name, aParms, nOperand, iBracket;
var match, name, nOperand, aParms, aDefaults = [], iBracket;
this.chMacroOpen = '<';
this.chMacroClose = '>';
@ -592,24 +765,25 @@ class Macro10 {
else if (sOperator == Macro10.PSEUDO_OP.LITERAL) {
this.chMacroOpen = '[';
this.chMacroClose = ']';
name = '@' + Str.toDec(this.nLine, 5);
name = '@' + Str.toDec(this.nLocation, 5);
this.aLiterals.push(name);
match = [sOperands[0], sOperands.substr(1)];
aParms = [];
nOperand = this.nLine;
iBracket = 0;
}
else {
var sExp = this.getExpression(sOperands);
if (!sExp) {
var sOperand = this.getExpression(sOperands);
if (!sOperand) {
this.error("missing " + sOperator + " expression: " + sOperands);
return "";
}
sOperands = sOperands.substr(sExp.length + 1);
sExp = sExp.trim();
sOperands = sOperands.substr(sOperand.length + 1);
sOperand = sOperand.trim();
match = sOperands.match(/\s*(<|)(.*)/i);
name = '@' + sOperator;
aParms = [];
nOperand = this.dbg.parseExpression(sExp);
nOperand = this.parseExpression(sOperand) || 0;
iBracket = 1;
}
@ -631,12 +805,7 @@ class Macro10 {
}
}
this.tblMacros[name] = {
name: name,
aParms: aParms,
nOperand: nOperand,
sText: sText
};
this.tblMacros[name] = {name, nOperand, aParms, aDefaults, sText};
if (!this.nMacroDef) {
this.parseMacro(name);
@ -695,16 +864,21 @@ class Macro10 {
this.error("label " + name + " redefined");
return;
}
this.tblSymbols[name] = {
name: name,
value: value,
fLabel: fLabel,
fGlobal: fGlobal,
fPrivate: fPrivate
};
this.tblSymbols[name] = {name, value, fLabel, fGlobal, fPrivate};
this.dbg.setVariable(name, value);
}
/**
* addXWD()
*
* @this {Macro10}
* @param {string} sOperands
*/
addXWD(sOperands)
{
this.genWord(0, this.getValues(sOperands), 18);
}
/**
* genASCII()
*
@ -751,25 +925,30 @@ class Macro10 {
}
/**
* genWord(w)
* genWord(w, aValues, nBits)
*
* @this {Macro10}
* @param {number} w
* @param {Array.<string>} [aValues]
* @param {number} [nBits] (-1 for opcode, otherwise the size of the data; default is 36)
*/
genWord(w)
genWord(w, aValues, nBits = 36)
{
this.aWords[this.nAddr++] = w;
this.aWords[this.nLocation] = w;
if (aValues) this.aFixups.push({nLocation: this.nLocation, aValues, nBits});
this.nLocation++;
}
}
Macro10.PSEUDO_OP = {
ASCII: "ASCII",
ASCIZ: "ASCIZ",
SIXBIT: "SIXBIT",
DEFINE: "DEFINE",
LITERAL:"LITERAL",
IFE: "IFE",
REPEAT: "REPEAT",
LITERAL:"LITERAL",
PAGE: "PAGE",
REPEAT: "REPEAT",
SIXBIT: "SIXBIT",
SUBTTL: "SUBTTL",
XWD: "XWD"
};