Fixed MACRO-10 literal processing (literals need to be evaluated as early as possible, but not allocated until the end is reached)
This commit is contained in:
parent
6ad35d2d17
commit
471a93353e
14 changed files with 447 additions and 299 deletions
|
|
@ -922,6 +922,11 @@ class CPUStatePDP10 extends CPUPDP10 {
|
|||
if (op >= 0) {
|
||||
this.opDecode(op);
|
||||
}
|
||||
/*
|
||||
* TODO: This is a temporary cycle charge, required for CPU operational bookkeeping until we add
|
||||
* correct cycle counts for all instructions.
|
||||
*/
|
||||
this.nStepCycles--;
|
||||
|
||||
} while (this.nStepCycles > 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -1756,9 +1756,11 @@ class DebuggerPDP10 extends Debugger {
|
|||
}
|
||||
continue;
|
||||
}
|
||||
if (operand < 0 || operand > PDP10.OPCODE.Y_MASK) {
|
||||
operand &= PDP10.ADDR_MASK;
|
||||
if (MAXDEBUG) this.println("address (" + sOperand + ") truncated to " + this.toStrBase(operand));
|
||||
if (sOpcode) {
|
||||
if (operand < 0 || operand > PDP10.OPCODE.Y_MASK) {
|
||||
operand &= PDP10.ADDR_MASK;
|
||||
if (MAXDEBUG) this.println("address (" + sOperand + ") truncated to " + this.toStrBase(operand));
|
||||
}
|
||||
}
|
||||
opCode += operand;
|
||||
sOperand = match[3];
|
||||
|
|
|
|||
|
|
@ -68,18 +68,29 @@ var Mac;
|
|||
var Sym;
|
||||
|
||||
/**
|
||||
*
|
||||
* Elements of aFixups.
|
||||
*
|
||||
* NOTE: The nLine property is not required; it's added for improved error reporting.
|
||||
* Elements of aLiterals.
|
||||
*
|
||||
* @typedef {{
|
||||
* name:(string),
|
||||
* aWords:(Array.<number>),
|
||||
* aFixups:(Array.<string>)
|
||||
* }}
|
||||
*/
|
||||
var Lit;
|
||||
|
||||
/**
|
||||
* Elements of stackScopes.
|
||||
*
|
||||
* @typedef {{
|
||||
* name:(string),
|
||||
* aWords:(Array.<number>),
|
||||
* aFixups:(Array.<string>),
|
||||
* nLocation:(number),
|
||||
* sValue:(string),
|
||||
* nLocationScope:(number),
|
||||
* nLine:(number)
|
||||
* }}
|
||||
*/
|
||||
var Fixup;
|
||||
var Scope;
|
||||
|
||||
/**
|
||||
* @class Macro10
|
||||
|
|
@ -169,28 +180,52 @@ class Macro10 {
|
|||
this.tblSymbols = {};
|
||||
|
||||
/**
|
||||
* @type {Array.<string>}
|
||||
* @type {Array.<Lit>}
|
||||
*/
|
||||
this.aLiterals = []; // array of literals
|
||||
|
||||
/**
|
||||
* This keeps track of symbols suffixed with '#', which are later assembled into a variable pool,
|
||||
* following the literal pool.
|
||||
*
|
||||
* 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.
|
||||
* @type {Array.<string>}
|
||||
*/
|
||||
this.aLiterals = [];
|
||||
this.aVariables = [];
|
||||
|
||||
/**
|
||||
* @type {Array.<Fixup>}
|
||||
*/
|
||||
this.aFixups = [];
|
||||
|
||||
/**
|
||||
* @type {Array.<number|undefined>}
|
||||
* @type {Array.<number>}
|
||||
*/
|
||||
this.aWords = []; // filled in by the various genXXX() functions
|
||||
|
||||
/**
|
||||
* This sparse array is indexed by location, and each used entry contains an undefined symbol that
|
||||
* must be evaluated to fully resolve the word at the corresponding location. NOTE: There's no requirement
|
||||
* that the array be sparse; we could certainly fill each unused entry with null (ie, for locations that
|
||||
* don't require a fixup).
|
||||
*
|
||||
* @type {Array.<string>}
|
||||
*/
|
||||
this.aFixups = [];
|
||||
|
||||
this.nLine = 0;
|
||||
this.nError = 0;
|
||||
this.nLiteral = 0; // used to uniquely number literals
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this.nLocation = this.nAddr; // advanced by the various genXXX() functions
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this.nLocationScope = -1;
|
||||
|
||||
/**
|
||||
* @type {Array.<Scope>}
|
||||
*/
|
||||
this.stackScopes = [];
|
||||
|
||||
this.sOperator = null; // the active operator, if any
|
||||
this.nMacroDef = 0; // the active macro definition state
|
||||
this.sMacroDef = null; // the active macro definition name
|
||||
|
|
@ -309,8 +344,7 @@ class Macro10 {
|
|||
|
||||
var a = this.dbg.resetVariables();
|
||||
try {
|
||||
var i;
|
||||
for (i = 0; i < this.asLines.length; i += 2) {
|
||||
for (let i = 0; i < this.asLines.length; i += 2) {
|
||||
this.nLine++;
|
||||
/*
|
||||
* Since, at this early stage, I'm not sure whether all the resources I'm interested in
|
||||
|
|
@ -330,24 +364,42 @@ class Macro10 {
|
|||
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 (this.stackScopes.length) {
|
||||
this.error("open scope from line " + this.stackScopes[0].nLine);
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.aLiterals.length; i++) {
|
||||
let lit = this.aLiterals[i];
|
||||
this.addSymbol(lit.name, this.nLocation, Macro10.SYMTYPE.LABEL);
|
||||
lit.aWords.forEach(function(w, nLocation) {
|
||||
macro10.genWord(w, lit.aFixups[nLocation]);
|
||||
});
|
||||
|
||||
/*
|
||||
* TODO: Add support for "literal collapsing"; ie, if two or more literals generate the same
|
||||
* set of values, then all instances after the first should refer back to the first (subject to
|
||||
* exceptions identified by MACRO-10: eg, "literals that contain errors, undefined expressions,
|
||||
* or EXTERNAL symbols.")
|
||||
*/
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.aVariables.length; i++) {
|
||||
let name = this.aVariables[i];
|
||||
let 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);
|
||||
this.error("missing definition for variable: " + name);
|
||||
continue;
|
||||
}
|
||||
this.parseText(macro.sText);
|
||||
}
|
||||
|
||||
this.aFixups.forEach(function processFixup(fixup){
|
||||
var nLocation = fixup.nLocation;
|
||||
var value = macro10.parseExpression(fixup.sValue, undefined, nLocation);
|
||||
this.aFixups.forEach(function processFixup(sValue, nLocation){
|
||||
let value = macro10.parseExpression(sValue, undefined, nLocation);
|
||||
if (value === undefined) {
|
||||
macro10.error("unable to parse expression: " + fixup.sValue);
|
||||
macro10.error("unable to parse expression: " + sValue);
|
||||
return;
|
||||
}
|
||||
value += macro10.aWords[nLocation];
|
||||
|
|
@ -473,7 +525,7 @@ class Macro10 {
|
|||
*/
|
||||
var sLiteral = this.getLiteral(sOperands);
|
||||
if (sLiteral) {
|
||||
sOperands = sOperands.replace(sLiteral, this.addMacro(Macro10.PSEUDO_OP.LITERAL, sLiteral));
|
||||
sOperands = sOperands.replace(sLiteral, this.addMacro(Macro10.PSEUDO_OP.LITERAL, this.getLiteral(sRemainder)));
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -527,6 +579,23 @@ class Macro10 {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* parseLiteral(name, sText)
|
||||
*
|
||||
* This is like parseText() except that we first set up a new scope, which includes new aWords and aFixups
|
||||
* arrays.
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} name
|
||||
* @param {string} sText
|
||||
*/
|
||||
parseLiteral(name, sText)
|
||||
{
|
||||
this.pushScope(name);
|
||||
this.parseText(sText);
|
||||
this.popScope();
|
||||
}
|
||||
|
||||
/**
|
||||
* parseMacro(name, sOperands)
|
||||
*
|
||||
|
|
@ -548,10 +617,10 @@ class Macro10 {
|
|||
this.parseText(macro.sText, macro.aParms, macro.aValues, macro.aDefaults);
|
||||
/*
|
||||
* WARNING: Our simplistic approach to macro expansion and processing means that recursive macros
|
||||
* (such as the SHIFT macro contained in /apps/pdp10/tests/macro10/TEXT.MAC) could blow the stack. Nothing
|
||||
* bad should happen (other than a JavaScript stack limit exception aborting the assembly), but it begs
|
||||
* (such as the SHIFT macro in /apps/pdp10/tests/macro10/TEXT.MAC) could blow the stack. Nothing bad
|
||||
* should happen (other than a JavaScript stack limit exception aborting the assembly), but it begs
|
||||
* the question: did MACRO-10 perform any tail recursion optimizations or other tricks to prevent macros
|
||||
* from running amok?
|
||||
* from running amok, or could they blow MACRO-10's stack just as easily?
|
||||
*/
|
||||
this.macroCall = macroPrev;
|
||||
return true;
|
||||
|
|
@ -559,7 +628,9 @@ class Macro10 {
|
|||
|
||||
if (name[0] != '?') return false;
|
||||
|
||||
switch(name.substr(1)) {
|
||||
var sOperator = name.substr(1);
|
||||
|
||||
switch(sOperator) {
|
||||
case Macro10.PSEUDO_OP.IFE:
|
||||
if (!macro.nOperand) {
|
||||
this.parseText(macro.sText);
|
||||
|
|
@ -586,23 +657,12 @@ class Macro10 {
|
|||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
this.parseLiteral(name, macro.sText);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* isSymbolChar(ch)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} ch
|
||||
* @return {boolean}
|
||||
*/
|
||||
isSymbolChar(ch)
|
||||
{
|
||||
return !!ch.match(/[0-9A-Z$%.]/i);
|
||||
}
|
||||
|
||||
/**
|
||||
* parseText(sText, aParms, aValues, aDefaults)
|
||||
*
|
||||
|
|
@ -621,6 +681,47 @@ class Macro10 {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* pushScope(name)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} name
|
||||
*/
|
||||
pushScope(name)
|
||||
{
|
||||
this.stackScopes.push(
|
||||
{name, aWords: this.aWords, aFixups: this.aFixups, nLocation: this.nLocation, nLocationScope: this.nLocationScope, nLine: this.nLine}
|
||||
);
|
||||
this.aWords = [];
|
||||
this.aFixups = [];
|
||||
this.nLocationScope = this.nLocation;
|
||||
this.nLocation = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* popScope()
|
||||
*
|
||||
* @this {Macro10}
|
||||
*/
|
||||
popScope()
|
||||
{
|
||||
if (!this.stackScopes.length) {
|
||||
this.error("scope nesting error");
|
||||
return;
|
||||
}
|
||||
var name = this.stackScopes[this.stackScopes.length - 1].name;
|
||||
this.aLiterals.push({name, aWords: this.aWords, aFixups: this.aFixups});
|
||||
var scope = this.stackScopes.pop();
|
||||
this.aWords = scope.aWords;
|
||||
this.aFixups = scope.aFixups;
|
||||
this.nLocation = scope.nLocation;
|
||||
this.nLocationScope = scope.nLocationScope;
|
||||
if (!this.stackScopes.length && this.nLocationScope != -1) {
|
||||
this.error("scope restore error");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* error(sError)
|
||||
*
|
||||
|
|
@ -643,6 +744,18 @@ class Macro10 {
|
|||
this.println("warning" + (this.nLine? " at line " + Str.toDec(this.nLine) : "") + ": " + sWarning);
|
||||
}
|
||||
|
||||
/**
|
||||
* isSymbolChar(ch)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} ch
|
||||
* @return {boolean}
|
||||
*/
|
||||
isSymbolChar(ch)
|
||||
{
|
||||
return !!ch.match(/[0-9A-Z$%.]/i);
|
||||
}
|
||||
|
||||
/**
|
||||
* getExpression(sOperands, sDelim)
|
||||
*
|
||||
|
|
@ -709,7 +822,7 @@ class Macro10 {
|
|||
*/
|
||||
var match = sOperand.match(/^([^,]*),,([^,]*)$/);
|
||||
if (!match) {
|
||||
if (nLocation === undefined) nLocation = this.nLocation;
|
||||
if (nLocation === undefined) nLocation = (this.nLocationScope >= 0? this.nLocationScope : 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
|
||||
|
|
@ -747,26 +860,30 @@ class Macro10 {
|
|||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sOperands
|
||||
* @return {string|null} (if the operands contain a literal, return it)
|
||||
* @return {string} (if the operands contain a literal, return it)
|
||||
*/
|
||||
getLiteral(sOperands)
|
||||
{
|
||||
var cNesting = 0;
|
||||
var sLiteral = null;
|
||||
var i = 0, iLiteral = -1;
|
||||
var sLiteral = "";
|
||||
var i = 0, iBegin = -1, iEnd = sOperands.length;
|
||||
while (i < sOperands.length) {
|
||||
var ch = sOperands[i];
|
||||
if (ch == ';') break;
|
||||
if (ch == '[') {
|
||||
if (!cNesting++) iLiteral = i;
|
||||
if (!cNesting++) iBegin = i;
|
||||
}
|
||||
i++;
|
||||
if (ch == ']' && --cNesting <= 0) break;
|
||||
if (ch == ']' && --cNesting <= 0) {
|
||||
iEnd = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cNesting < 0) {
|
||||
this.error("missing bracket(s): " + sOperands);
|
||||
}
|
||||
if (iLiteral >= 0) {
|
||||
sLiteral = sOperands.substr(iLiteral, i - iLiteral);
|
||||
if (iBegin >= 0) {
|
||||
sLiteral = sOperands.substr(iBegin, iEnd - iBegin);
|
||||
}
|
||||
return sLiteral;
|
||||
}
|
||||
|
|
@ -825,17 +942,24 @@ class Macro10 {
|
|||
sText: sLabel + ": 0",
|
||||
nLine: this.nLine
|
||||
};
|
||||
this.aLiterals.push(name);
|
||||
this.aVariables.push(name);
|
||||
}
|
||||
return sReserved;
|
||||
}
|
||||
|
||||
/**
|
||||
* getSymbol(sOperands)
|
||||
*
|
||||
* Check the operands for a symbol. TODO: Use this method?
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sOperands
|
||||
* @return {string|null} (if the operands contain a symbol, return it)
|
||||
*/
|
||||
getSymbol(sOperands)
|
||||
{
|
||||
var match;
|
||||
if (match = sOperands.match(/([A-Z$%.][0-9A-Z$%.]*)/i)) {
|
||||
return match[1];
|
||||
}
|
||||
var match = sOperands.match(/([A-Z$%.][0-9A-Z$%.]*)/i);
|
||||
return match && match[1] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -948,13 +1072,12 @@ class Macro10 {
|
|||
*/
|
||||
this.chMacroOpen = '[';
|
||||
this.chMacroClose = ']';
|
||||
name = '?' + Str.toDec(this.nLocation, 5);
|
||||
name = '?' + Str.toDec(++this.nLiteral, 5);
|
||||
if (this.tblMacros[name] !== undefined) {
|
||||
this.error("literal symbol redefined: " + name);
|
||||
}
|
||||
match = [sOperands[0], name + ": " + sOperands.substr(1)];
|
||||
match = [sOperands[0], sOperands.substr(1)];
|
||||
nOperand = Macro10.MACRO_OP.LITERAL;
|
||||
this.aLiterals.push(name);
|
||||
iMatch = 0;
|
||||
}
|
||||
else if (sOperator == Macro10.PSEUDO_OP.IRP || sOperator == Macro10.PSEUDO_OP.IRPC) {
|
||||
|
|
@ -1141,20 +1264,21 @@ class Macro10 {
|
|||
*/
|
||||
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 {
|
||||
var w = -1;
|
||||
var sExp = (sOperator + ' ' + sOperands).trim();
|
||||
|
||||
if (sOperands.indexOf(",,") < 0) {
|
||||
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 < 0) w = this.parseExpression(sExp, true);
|
||||
|
||||
if (w !== undefined) {
|
||||
this.genWord(w, this.dbg.sUndefined);
|
||||
} else {
|
||||
|
|
@ -1228,12 +1352,12 @@ class Macro10 {
|
|||
*
|
||||
* @this {Macro10}
|
||||
* @param {number} value (default value for the current location)
|
||||
* @param {string|null} [sFixup] (optional fixup value to evaluate later)
|
||||
* @param {string|null|undefined} [sFixup] (optional fixup value to evaluate later)
|
||||
*/
|
||||
genWord(value, sFixup)
|
||||
{
|
||||
this.aWords[this.nLocation] = this.truncate(value);
|
||||
if (sFixup != null) this.aFixups.push({nLocation: this.nLocation, sValue: sFixup, nLine: this.nLine});
|
||||
if (sFixup != null) this.aFixups[this.nLocation] = sFixup;
|
||||
this.nLocation++;
|
||||
}
|
||||
|
||||
|
|
@ -1286,7 +1410,7 @@ Macro10.PSEUDO_OP = {
|
|||
IFN: "IFN",
|
||||
IRP: "IRP",
|
||||
IRPC: "IRPC",
|
||||
LITERAL: "LITERAL",
|
||||
LITERAL: "LITERAL", // this is a pseudo-pseudo-op, used for internal purposes
|
||||
PAGE: "PAGE",
|
||||
REPEAT: "REPEAT",
|
||||
SIXBIT: "SIXBIT",
|
||||
|
|
|
|||
Loading…
Reference in a new issue