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:
Jeff 2017-03-24 00:09:30 -07:00 • committed by Jeff Parsons
commit 471a93353e
14 changed files with 447 additions and 299 deletions

View file

@ -32,7 +32,7 @@ The Debugger's assemble ("a") command can be used to test the new built-in
of the [MACRO-10](http://archive.pcjs.org/pubs/dec/pdp10/tops10/02_1973AsmRef_macro.pdf) assembly language.
This command:
a 30724 /apps/pdp10/diags/klad/dakaa/TEST.MAC
a 30724 /apps/pdp10/diags/klad/dakaa/MYDAKAA.MAC
will automatically read the [MYDAKAA.MAC](MYDAKAA.MAC.txt) source file (a slightly modified copy of [DAKAA.MAC](DAKAA.MAC.txt)),
assemble it, and then load the binary output at the specified address.

View file

@ -32,7 +32,7 @@ The Debugger's assemble ("a") command can be used to test the new built-in
of the [MACRO-10](http://archive.pcjs.org/pubs/dec/pdp10/tops10/02_1973AsmRef_macro.pdf) assembly language.
This command:
a 30724 /apps/pdp10/diags/klad/dakad/TEST.MAC
a 30724 /apps/pdp10/diags/klad/dakad/MYDAKAD.MAC
will automatically read the [MYDAKAD.MAC](MYDAKAD.MAC.txt) source file (a slightly modified copy of [DAKAD.MAC](DAKAD.MAC.txt)),
assemble it, and then load the binary output at the specified address. Use the command `db 30724` to dump the first few words

View file

@ -0,0 +1,16 @@
; From the MACRO-10 ASSEMBLER PROGRAMMER'S REFERENCE MANUAL (April 1978), p. 2-8:
P=0
T1=1
T2=2
GETCHR: ILDB T2,T1 ;Get a character
CAIN T2,0 ;Is it a null?
JRST [MOVE T1,TXTPTR# ;Yes, retrieve pointer
ILDB T2,T1 ;Get a new character
CAIN T2,"?" ;Is it a question mark?
JRST [MOVE T1,TXTPT1# ;Yes, get alternate pointer
ILDB T2,T1 ;Get the message character
JRST GETHLP#] ;Go to help routine
POPJ P,] ;Not question mark, return
POPJ P, ;Not a null, return

View file

@ -7,7 +7,7 @@ machines:
type: pdp10
config: /devices/pdp10/machine/ka10/test/debugger/machine.xml
debugger: true
commands: a 100 /apps/pdp10/tests/macro10/TEXT.MAC
commands: a 100 /apps/pdp10/tests/macro10/LITNEST.MAC
---
PDPjs MACRO-10 Mini-Assembler Tests

View file

@ -1,4 +1,4 @@
; From the MACR0-10 ASSEMBLER PROGRAMMER'S REFERENCE MANUAL (June 1972):
; From the MACRO-10 ASSEMBLER PROGRAMMER'S REFERENCE MANUAL (June 1972), p. 7-9:
;
; Example 4 (Figure 7-4) shows nested macros which use IRPC. The desired operation
; is to take an ASCII text string and store the characters four per word, left-justified,

File diff suppressed because one or more lines are too long

View file

@ -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);

View file

@ -1756,10 +1756,12 @@ class DebuggerPDP10 extends Debugger {
}
continue;
}
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];
if (sOperand) {

View file

@ -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",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long