More MACRO-10 support
This commit is contained in:
parent
b371f9d649
commit
e2e018af67
8 changed files with 706 additions and 205 deletions
|
|
@ -91,9 +91,11 @@ class DebuggerPDP10 extends Debugger {
|
|||
* Since this Debugger doesn't use replaceRegs(), we can use parentheses instead of braces.
|
||||
*/
|
||||
this.fInit = false;
|
||||
this.fParens = true;
|
||||
this.nBusWidth = 18; // default value, updated by initBus()
|
||||
|
||||
this.achGroup = ['<','>'];
|
||||
this.achAddress = [];
|
||||
|
||||
/*
|
||||
* Most commands that require an address call parseAddr(), and if a dbgAddr parameter is supplied
|
||||
* as as well (eg, dbgAddrCode, dbgAddrData), then that address will be used as the default.
|
||||
|
|
|
|||
|
|
@ -36,8 +36,33 @@ if (NODE) {
|
|||
var DebuggerPDP10 = require("./debugger");
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* name:(string),
|
||||
* nOperand:(number),
|
||||
* aParms:(string),
|
||||
* sText:(string)
|
||||
* }}
|
||||
*/
|
||||
var Macro;
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* name:(string),
|
||||
* value:(number),
|
||||
* fLabel:(boolean),
|
||||
* fGlobal:(boolean),
|
||||
* fPrivate:(boolean)
|
||||
* }}
|
||||
*/
|
||||
var Sym;
|
||||
|
||||
/**
|
||||
* @class Macro10
|
||||
* @property {string} sURL
|
||||
* @property {number} nAddr
|
||||
* @property {DebuggerPDP10} dbg
|
||||
* @property {function(Macro10,string,number)} done
|
||||
*/
|
||||
class Macro10 {
|
||||
/**
|
||||
|
|
@ -64,13 +89,14 @@ class Macro10 {
|
|||
constructor(sURL, nAddr, dbg, done)
|
||||
{
|
||||
this.sURL = sURL;
|
||||
this.nAddr = nAddr;
|
||||
this.nAddr = nAddr || 0;
|
||||
this.dbg = dbg;
|
||||
this.done = done;
|
||||
|
||||
/*
|
||||
* Set up all the services we need to use.
|
||||
*/
|
||||
this.println = dbg && dbg.println || console.log;
|
||||
this.println = dbg.println;
|
||||
|
||||
/*
|
||||
* Initialize all the tables that MACRO-10 uses.
|
||||
|
|
@ -78,6 +104,29 @@ class Macro10 {
|
|||
this.tblMacros = {};
|
||||
this.tblSymbols = {};
|
||||
|
||||
this.nLine = 0;
|
||||
this.nError = 0;
|
||||
this.aWords = []; // filled in 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
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
this.sASCII = "";
|
||||
|
||||
var macro10 = this;
|
||||
Web.getResource(sURL, null, true, function(sURL, sResource, nErrorCode) {
|
||||
if (!nErrorCode) {
|
||||
|
|
@ -99,47 +148,75 @@ class Macro10 {
|
|||
*/
|
||||
parseFile(sPath, sContents)
|
||||
{
|
||||
var sText = sContents;
|
||||
if (Str.endsWith(sPath, ".html")) {
|
||||
/*
|
||||
* We want to parse ONLY the text between <PRE>...</PRE> tags, and eliminate any HTML entities.
|
||||
*/
|
||||
sText = "";
|
||||
var match, re = /<pre>([\s\S]*?)<\/pre>/gi;
|
||||
while (match = re.exec(sContents)) {
|
||||
var s = match[1];
|
||||
if (s.indexOf('&') >= 0) s = s.replace(/</gi, '<').replace(/>/gi, '>').replace(/&/gi, '&');
|
||||
sText += s;
|
||||
var a = this.dbg.resetVariables();
|
||||
try {
|
||||
var sText = sContents;
|
||||
if (Str.endsWith(sPath, ".html")) {
|
||||
/*
|
||||
* We want to parse ONLY the text between <PRE>...</PRE> tags, and eliminate any HTML entities.
|
||||
*/
|
||||
sText = "";
|
||||
var match, re = /<pre>([\s\S]*?)<\/pre>/gi;
|
||||
while (match = re.exec(sContents)) {
|
||||
var s = match[1];
|
||||
if (s.indexOf('&') >= 0) s = s.replace(/</gi, '<').replace(/>/gi, '>').replace(/&/gi, '&');
|
||||
sText += s;
|
||||
}
|
||||
match = sText.match(/&[a-z]+;/i);
|
||||
if (match) this.warning("unrecognized HTML entity: " + match[0]);
|
||||
}
|
||||
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;
|
||||
}
|
||||
} catch(err) {
|
||||
this.println(err.message);
|
||||
this.nError = -1;
|
||||
}
|
||||
var i;
|
||||
var asLines = sText.split(/\r?\n/);
|
||||
for (i = 0; i < asLines.length; i++) {
|
||||
if (!this.parseLine(i + 1, asLines[i])) break;
|
||||
}
|
||||
return 0;
|
||||
this.dbg.restoreVariables(a);
|
||||
return this.nError;
|
||||
}
|
||||
|
||||
/**
|
||||
* parseLine(nLine, sLine)
|
||||
* parseLine(sLine)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {number} nLine (line number)
|
||||
* @param {string} sLine (line contents)
|
||||
* @return {boolean}
|
||||
*/
|
||||
parseLine(nLine, sLine)
|
||||
parseLine(sLine)
|
||||
{
|
||||
var reLine = /([A-Z$%.][0-9A-Z$%.]*[:=]|)\s*([^\s;]+|)\s*([^;]+|)\s*(;?.*)/i;
|
||||
if (this.nMacroDef) {
|
||||
if (this.nMacroDef == 1) {
|
||||
var i = sLine.indexOf('<');
|
||||
if (i >= 0) {
|
||||
this.nMacroDef++;
|
||||
sLine = sLine.substr(i+1);
|
||||
} else {
|
||||
this.error("expected " + this.sOperator + " definition: " + sLine);
|
||||
}
|
||||
}
|
||||
if (this.nMacroDef > 1) {
|
||||
sLine = this.appendMacro(sLine);
|
||||
}
|
||||
if (this.nMacroDef) return true;
|
||||
}
|
||||
|
||||
if (this.chASCII != null) {
|
||||
sLine = this.addASCII(sLine);
|
||||
}
|
||||
|
||||
var reLine = /\s*([A-Z$%.][0-9A-Z$%.]*[:=]|)\s*([A-Z$%.][0-9A-Z$%.]*|)\s*([^;]+|)\s*(;?.*)/i;
|
||||
var match = sLine.match(reLine);
|
||||
if (!match || match[4] && match[4].slice(0, 1) != ';') {
|
||||
this.warning("failed to parse line " + nLine + ": " + sLine);
|
||||
this.error("failed to parse line: " + sLine);
|
||||
return false;
|
||||
}
|
||||
|
||||
var sLabel = match[1];
|
||||
var sOperator = match[2];
|
||||
var sOperator = match[2].toUpperCase();
|
||||
var sOperands = match[3].trim();
|
||||
var sComment = match[4].slice(1);
|
||||
if (sLabel) {
|
||||
|
|
@ -148,14 +225,103 @@ class Macro10 {
|
|||
if (chSep == ':') {
|
||||
this.addLabel(sLabel);
|
||||
} else {
|
||||
this.addVariable(sLabel, sOperands = sOperator + sOperands);
|
||||
sOperands = sOperator + sOperands;
|
||||
sOperator = chSep;
|
||||
}
|
||||
}
|
||||
if (DEBUG) this.println(Str.toDec(nLine, 5) + ": label(" + sLabel + ") operator(" + sOperator + ") operands(" + sOperands + ") comment(" + sComment + ")");
|
||||
|
||||
this.sOperator = sOperator;
|
||||
sOperands = sOperands.trim();
|
||||
|
||||
switch(sOperator) {
|
||||
case "":
|
||||
break; // eg, a blank line or a line that contains only a label and/or a comment
|
||||
|
||||
case "=":
|
||||
this.addAssign(sLabel, sOperands);
|
||||
break;
|
||||
|
||||
case Macro10.PSEUDO_OP.ASCII:
|
||||
case Macro10.PSEUDO_OP.ASCIZ:
|
||||
case Macro10.PSEUDO_OP.SIXBIT:
|
||||
this.addASCII(sOperands);
|
||||
break;
|
||||
|
||||
case Macro10.PSEUDO_OP.DEFINE:
|
||||
case Macro10.PSEUDO_OP.IFE:
|
||||
case Macro10.PSEUDO_OP.REPEAT:
|
||||
this.addMacro(sOperator, sOperands);
|
||||
break;
|
||||
|
||||
case Macro10.PSEUDO_OP.PAGE: // TODO
|
||||
case Macro10.PSEUDO_OP.SUBTTL: // TODO
|
||||
break;
|
||||
|
||||
default:
|
||||
if (DEBUG) this.println(Str.toDec(this.nLine, 5) + ": label(" + sLabel + ") operator(" + sOperator + ") operands(" + sOperands + ") comment(" + sComment + ")");
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.nLine >= 330) {
|
||||
this.dbg.printVariable();
|
||||
this.error("temporary line limit reached");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* parseMacro()
|
||||
*
|
||||
* @this {Macro10}
|
||||
*/
|
||||
parseMacro()
|
||||
{
|
||||
if (!this.sMacroDef) return;
|
||||
|
||||
var macro = this.tblMacros[this.sMacroDef];
|
||||
var sOperator = this.sMacroDef[0] == '@'? this.sMacroDef.substr(1) : this.sOperator;
|
||||
|
||||
switch(sOperator) {
|
||||
case Macro10.PSEUDO_OP.IFE:
|
||||
if (!macro.nOperand) {
|
||||
this.parseText(macro.sText);
|
||||
}
|
||||
break;
|
||||
|
||||
case Macro10.PSEUDO_OP.REPEAT:
|
||||
while (macro.nOperand-- > 0) {
|
||||
this.parseText(macro.sText);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parseText(sText)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sText
|
||||
*/
|
||||
parseText(sText)
|
||||
{
|
||||
var asLines = sText.split(/\r?\n/);
|
||||
for (var i = 0; i < asLines.length; i++) {
|
||||
if (!this.parseLine(asLines[i] + '\r\n')) break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* error(sError)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sError
|
||||
*/
|
||||
error(sError)
|
||||
{
|
||||
throw new Error("error" + (this.nLine? " at line " + Str.toDec(this.nLine) : "") + ": " + sError);
|
||||
}
|
||||
|
||||
/**
|
||||
* warning(sWarning)
|
||||
*
|
||||
|
|
@ -164,7 +330,90 @@ class Macro10 {
|
|||
*/
|
||||
warning(sWarning)
|
||||
{
|
||||
this.println("warning: " + sWarning);
|
||||
this.println("warning" + (this.nLine? " at line " + Str.toDec(this.nLine) : "") + ": " + sWarning);
|
||||
}
|
||||
|
||||
/**
|
||||
* getExpression(sInput, chDelim)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sInput
|
||||
* @param {string} [chDelim]
|
||||
* @return {string|null} (if the input string begins with an expression, return it)
|
||||
*/
|
||||
getExpression(sInput, chDelim)
|
||||
{
|
||||
var sExp = null;
|
||||
var cNesting = 0;
|
||||
for (var i = 0; i < sInput.length; i++) {
|
||||
var ch = sInput[i];
|
||||
if (ch == chDelim) {
|
||||
if (!cNesting) {
|
||||
sExp = sInput.substr(0, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ch == '<') {
|
||||
cNesting++;
|
||||
} else if (ch == '>') {
|
||||
if (--cNesting < 0) {
|
||||
this.error("missing angle bracket(s): " + sInput);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sExp;
|
||||
}
|
||||
|
||||
/**
|
||||
* addASCII(sOperands)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sOperands
|
||||
* @return {string} (returns whatever portion of the string was not part of an ASCII pseudo-op)
|
||||
*/
|
||||
addASCII(sOperands)
|
||||
{
|
||||
var sRemain = sOperands;
|
||||
if (this.chASCII == null) {
|
||||
this.chASCII = this.sASCII = "";
|
||||
if (sOperands) {
|
||||
this.chASCII = sOperands[0];
|
||||
sRemain = sOperands = sOperands.substr(1);
|
||||
}
|
||||
}
|
||||
if (this.chASCII) {
|
||||
var i = sOperands.indexOf(this.chASCII);
|
||||
if (i < 0) {
|
||||
sRemain = "";
|
||||
} else {
|
||||
sRemain = sOperands.substr(i + 1);
|
||||
sOperands = sOperands.substr(0, i);
|
||||
this.chASCII = null;
|
||||
}
|
||||
this.sASCII += sOperands;
|
||||
}
|
||||
if (this.chASCII == null) {
|
||||
this.genASCII();
|
||||
}
|
||||
return sRemain;
|
||||
}
|
||||
|
||||
/**
|
||||
* addAssign(sName, sExp)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sName
|
||||
* @param {string} sExp
|
||||
*/
|
||||
addAssign(sName, sExp)
|
||||
{
|
||||
var value = this.dbg.parseExpression(sExp);
|
||||
if (value === undefined) {
|
||||
this.error("parseExpression(" + sExp + ")");
|
||||
return;
|
||||
}
|
||||
this.addSymbol(sName, value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -175,16 +424,200 @@ class Macro10 {
|
|||
*/
|
||||
addLabel(sLabel)
|
||||
{
|
||||
this.addSymbol(sLabel, this.nAddr, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* addVariable(sVar, sExp)
|
||||
* addMacro(sOperator, sOperands)
|
||||
*
|
||||
* If sOperator is DEFINE, then a macro definition is expected. If it's REPEAT, then we're starting a
|
||||
* REPEAT block instead.
|
||||
*
|
||||
* 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).
|
||||
*
|
||||
* 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
|
||||
* satisfied. That satisfaction occurs (or doesn't occur) when parseMacro() is called, once the macro has
|
||||
* been fully defined.
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sVar
|
||||
* @param {string} sExp
|
||||
* @param {string} sOperator
|
||||
* @param {string} sOperands
|
||||
*/
|
||||
addVariable(sVar, sExp)
|
||||
addMacro(sOperator, sOperands)
|
||||
{
|
||||
var match, name, aParms, nOperand, iDelim;
|
||||
|
||||
if (sOperator == Macro10.PSEUDO_OP.DEFINE) {
|
||||
match = sOperands.match(/([A-Z$%.][0-9A-Z$%.]*)\s*(\([^)]*\)|)\s*(<|)(.*)/i);
|
||||
if (!match) {
|
||||
this.error("unrecognized " + sOperator + " definition: " + sOperands);
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
iDelim = 3;
|
||||
} else {
|
||||
var sExp = this.getExpression(sOperands, ',');
|
||||
if (!sExp) {
|
||||
this.error("missing " + sOperator + " expression: " + sOperands);
|
||||
return;
|
||||
}
|
||||
sOperands = sOperands.substr(sExp.length + 1);
|
||||
sExp = sExp.trim();
|
||||
match = sOperands.match(/\s*(<|)(.*)/i);
|
||||
name = '@' + sOperator;
|
||||
aParms = [];
|
||||
nOperand = this.dbg.parseExpression(sExp);
|
||||
iDelim = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now we need to set a global parsing state: we are either about to receive a macro definition on
|
||||
* subsequent lines (1), the definition has already started on the current line (2), or the definition
|
||||
* started and ended on the current line (0).
|
||||
*/
|
||||
this.nMacroDef = 1;
|
||||
this.sMacroDef = name;
|
||||
|
||||
var sText = "";
|
||||
if (match[iDelim]) { // if there IS an angle bracket...
|
||||
this.nMacroDef = 2; // then the macro definition has begun
|
||||
sText = match[iDelim + 1];
|
||||
if (sText.slice(-1) == '>') { // and if there is ALSO a closing angle bracket...
|
||||
this.nMacroDef = 0; // the macro definition has also ended
|
||||
sText = sText.slice(0, -1);
|
||||
}
|
||||
}
|
||||
this.tblMacros[name] = {
|
||||
name: name,
|
||||
aParms: aParms,
|
||||
nOperand: nOperand,
|
||||
sText: sText
|
||||
};
|
||||
if (!this.nMacroDef) this.parseMacro();
|
||||
}
|
||||
|
||||
/**
|
||||
* appendMacro(sLine)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} sLine
|
||||
* @return {string}
|
||||
*/
|
||||
appendMacro(sLine)
|
||||
{
|
||||
var sRemain = "";
|
||||
for (var i = 0; i < sLine.length; i++) {
|
||||
if (sLine[i] == '<') {
|
||||
this.nMacroDef++;
|
||||
} else if (sLine[i] == '>') {
|
||||
this.nMacroDef--;
|
||||
if (this.nMacroDef == 1) {
|
||||
this.nMacroDef = 0;
|
||||
sRemain = sLine.substr(i + 1);
|
||||
sLine = sLine.substr(0, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
var macro = this.tblMacros[this.sMacroDef];
|
||||
macro.sText += sLine;
|
||||
if (!this.nMacroDef) this.parseMacro();
|
||||
return sRemain;
|
||||
}
|
||||
|
||||
/**
|
||||
* addSymbol(name, value, fLabel, fGlobal, fPrivate)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {string} name
|
||||
* @param {number} value
|
||||
* @param {boolean} [fLabel] (default is false, meaning the symbol is an assignment)
|
||||
* @param {boolean} [fGlobal] (default is false, meaning the symbol is local to the current file)
|
||||
* @param {boolean} [fPrivate] (default is false, meaning the symbol has visibility to the caller)
|
||||
*/
|
||||
addSymbol(name, value, fLabel = false, fGlobal = false, fPrivate = false)
|
||||
{
|
||||
name = name.toUpperCase().substr(0, 6);
|
||||
if (fLabel && this.tblSymbols[name] !== undefined) {
|
||||
this.error("label " + name + " redefined");
|
||||
return;
|
||||
}
|
||||
this.tblSymbols[name] = {
|
||||
name: name,
|
||||
value: value,
|
||||
fLabel: fLabel,
|
||||
fGlobal: fGlobal,
|
||||
fPrivate: fPrivate
|
||||
};
|
||||
this.dbg.setVariable(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* genASCII()
|
||||
*
|
||||
* Based on the last operator, generate the appropriate ASCII data.
|
||||
*
|
||||
* @this {Macro10}
|
||||
*/
|
||||
genASCII()
|
||||
{
|
||||
var n = 0, w = 0; // number of characters in current word, and current word
|
||||
var bits, shift; // bits per character, and bits to left-shift next character
|
||||
var cch = this.sASCII.length;
|
||||
if (this.sOperator == Macro10.PSEUDO_OP.ASCIZ) cch++;
|
||||
for (var i = 0; i < cch; i++) {
|
||||
if (!n) {
|
||||
w = 0; shift = 29; bits = 7;
|
||||
if (this.sOperator == Macro10.PSEUDO_OP.SIXBIT) {
|
||||
bits--; shift++;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* If we're processing an ASCIZ pseudo-op, then yes, we will fetch one character beyond
|
||||
* the end of sASCII, which will return NaN, but when we mask a falsey value like NaN, we
|
||||
* get zero, so it's all good.
|
||||
*/
|
||||
var c = this.sASCII.charCodeAt(i) & 0o177;
|
||||
w += c * Math.pow(2, shift);
|
||||
shift -= bits;
|
||||
n++;
|
||||
if (shift < 0) {
|
||||
this.genWord(w);
|
||||
n = 0;
|
||||
}
|
||||
}
|
||||
if (n) this.genWord(w);
|
||||
}
|
||||
|
||||
/**
|
||||
* genWord(w)
|
||||
*
|
||||
* @this {Macro10}
|
||||
* @param {number} w
|
||||
*/
|
||||
genWord(w)
|
||||
{
|
||||
this.aWords[this.nAddr++] = w;
|
||||
}
|
||||
}
|
||||
|
||||
Macro10.PSEUDO_OP = {
|
||||
ASCII: "ASCII",
|
||||
ASCIZ: "ASCIZ",
|
||||
SIXBIT: "SIXBIT",
|
||||
DEFINE: "DEFINE",
|
||||
IFE: "IFE",
|
||||
REPEAT: "REPEAT",
|
||||
PAGE: "PAGE",
|
||||
SUBTTL: "SUBTTL",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -89,7 +89,9 @@ class DebuggerPDP11 extends Debugger {
|
|||
* Since this Debugger doesn't use replaceRegs(), we can use parentheses instead of braces.
|
||||
*/
|
||||
this.fInit = false;
|
||||
this.fParens = true;
|
||||
|
||||
this.achGroup = ['(',')'];
|
||||
this.achAddress = [];
|
||||
|
||||
/*
|
||||
* Most commands that require an address call parseAddr(), which defaults to dbgAddrNextCode
|
||||
|
|
|
|||
|
|
@ -100,7 +100,9 @@ class Debugger extends Component {
|
|||
* Default base used to display all values; modified with the "s base" command.
|
||||
*/
|
||||
this.nBase = +parmsDbg['base'] || 16;
|
||||
this.fParens = false;
|
||||
|
||||
this.achGroup = ['{','}'];
|
||||
this.achAddress = ['[',']'];
|
||||
|
||||
/*
|
||||
* These keep track of instruction activity, but only when tracing or when Debugger checks
|
||||
|
|
@ -409,19 +411,19 @@ class Debugger extends Component {
|
|||
* ...
|
||||
*
|
||||
* We pop 1 "binop" from aOps and 2 values from aVals whenever a "binop" of lower priority than its
|
||||
* predecessor is encountered, evaluate, and push the result back onto aVals.
|
||||
* predecessor is encountered, evaluate, and push the result back onto aVals. Unary operators like
|
||||
* '~' and ternary operators like '?:' are not supported.
|
||||
*
|
||||
* Unary operators like '~' and ternary operators like '?:' are not supported; neither are parentheses.
|
||||
* parseReference() makes it possible to write parenthetical-style sub-expressions by using whatever
|
||||
* characters achGroup contains (default is braces}. Address references are resolved using the characters
|
||||
* in achAddress (default is brackets).
|
||||
*
|
||||
* However, parseReference() now makes it possible to write parenthetical-style sub-expressions by using
|
||||
* {...} (braces), as well as address references by using [...] (brackets).
|
||||
* Why not always use parentheses for sub-expressions? Because parseReference() serves multiple purposes,
|
||||
* the other being reference replacement in message strings passing through replaceRegs(), and some
|
||||
* Debuggers don't want parentheses taking on a new meaning in message strings.
|
||||
*
|
||||
* Why am I using braces instead of parentheses for sub-expressions? Because parseReference() serves
|
||||
* multiple purposes, the other being reference replacement in message strings passing through replaceRegs(),
|
||||
* and I didn't want parentheses taking on a new meaning in message strings.
|
||||
*
|
||||
* However, a Debugger can override this choice by setting fParens to true, if there's no conflict in its
|
||||
* replaceRegs() implementation.
|
||||
* However, a Debugger can override these choices by modifying achGroup and/or achAddress, if there's no
|
||||
* conflict in its replaceRegs() implementation.
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string|undefined} sExp
|
||||
|
|
@ -494,8 +496,7 @@ class Debugger extends Component {
|
|||
*
|
||||
* Returns the given string with any "{expression}" sequences replaced with the value of the expression,
|
||||
* and any "[address]" references replaced with the contents of the address. Expressions are parsed BEFORE
|
||||
* addresses. Owing to this function's simplistic parsing, nested braces/brackets are not supported
|
||||
* (define intermediate variables if needed).
|
||||
* addresses.
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string} s
|
||||
|
|
@ -503,17 +504,36 @@ class Debugger extends Component {
|
|||
*/
|
||||
parseReference(s) {
|
||||
var a;
|
||||
var chOpen = this.fParens? '(' : '{';
|
||||
var chClose = this.fParens? ')' : '}';
|
||||
var reSubExp = new RegExp(this.fParens? "\\((.*?)\\)" : "\\{(.*?)\\}");
|
||||
var chOpen = this.achGroup[0];
|
||||
var chClose = this.achGroup[1];
|
||||
var chEscape = (chOpen == '(' || chOpen == '{' || chOpen == '[')? '\\' : '';
|
||||
var chInnerEscape = (chOpen == '['? '\\' : '');
|
||||
var reSubExp = new RegExp(chEscape + chOpen + "([^" + chInnerEscape + chOpen + chInnerEscape + chClose + "]+)" + chEscape + chClose);
|
||||
while (a = s.match(reSubExp)) {
|
||||
if (a[1].indexOf(chOpen) >= 0) break; // unsupported nested brace(s)
|
||||
var value = this.parseExpression(a[1]);
|
||||
s = s.replace(chOpen + a[1] + chClose, value != null? this.toStrBase(value) : "undefined");
|
||||
var sSearch = chOpen + a[1] + chClose;
|
||||
var sReplace = value != null? this.toStrBase(value) : "undefined";
|
||||
/*
|
||||
* Note that by default, the String replace() method only replaces the FIRST occurrence,
|
||||
* and there MIGHT be more than one occurrence of the expression we just parsed, so we could
|
||||
* do this instead:
|
||||
*
|
||||
* s = s.split(sSearch).join(sReplace);
|
||||
*
|
||||
* However, that's knd of an expensive (slow) solution, and it's not strictly necessary, since
|
||||
* any additional identical expressions will be picked up on a subsequent iteration through this loop.
|
||||
*/
|
||||
s = s.replace(sSearch, sReplace);
|
||||
}
|
||||
while (a = s.match(/\[(.*?)]/)) {
|
||||
if (a[1].indexOf('[') >= 0) break; // unsupported nested bracket(s)
|
||||
s = this.parseAddrReference(s, a[1]);
|
||||
if (this.achAddress.length) {
|
||||
chOpen = this.achAddress[0];
|
||||
chClose = this.achAddress[1];
|
||||
chEscape = (chOpen == '(' || chOpen == '{' || chOpen == '[')? '\\' : '';
|
||||
chInnerEscape = (chOpen == '['? '\\' : '');
|
||||
reSubExp = new RegExp(chEscape + chOpen + "([^" + chInnerEscape + chOpen + chInnerEscape + chClose + "]+)" + chEscape + chClose);
|
||||
while (a = s.match(reSubExp)) {
|
||||
s = this.parseAddrReference(s, a[1]);
|
||||
}
|
||||
}
|
||||
return this.parseSysVars(s);
|
||||
}
|
||||
|
|
@ -595,6 +615,28 @@ class Debugger extends Component {
|
|||
return fDefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* resetVariables()
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @return {Object}
|
||||
*/
|
||||
resetVariables() {
|
||||
var a = this.aVariables;
|
||||
this.aVariables = {};
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* restoreVariables(a)
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {Object} a (from previous resetVariables() call)
|
||||
*/
|
||||
restoreVariables(a) {
|
||||
this.aVariables = a;
|
||||
}
|
||||
|
||||
/**
|
||||
* printVariable(sVar)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -65,6 +65,10 @@ class Str {
|
|||
* to indicate octal (because such a number could also be decimal or hex). Any number of commas are
|
||||
* allowed; we remove them all before calling the built-in parseInt().
|
||||
*
|
||||
* More recently, we've added support for "^D", "^O", and "^B" prefixes to accommodate the base overrides
|
||||
* that the PDP-10's MACRO-10 assembly language supports. If this support turns out to adversely affect
|
||||
* other debuggers, then it will have to be "conditionalized".
|
||||
*
|
||||
* To summarize our non-standard alternatives: a 'y' suffix indicates binary, a '#' prefix indicates
|
||||
* octal, a '$' prefix indicates hex, and a "0b" prefix indicates binary IF at least one comma is present.
|
||||
* Commas are useful for grouping binary digits, but if you don't want to use them, then you must use a
|
||||
|
|
@ -110,6 +114,21 @@ class Str {
|
|||
chPrefix = null;
|
||||
}
|
||||
}
|
||||
else if (chPrefix == '^') {
|
||||
chPrefix = s.charAt(1);
|
||||
if (chPrefix == 'D') {
|
||||
base = 10;
|
||||
chPrefix = null;
|
||||
}
|
||||
else if (chPrefix == 'O') {
|
||||
base = 8;
|
||||
chPrefix = null;
|
||||
}
|
||||
else if (chPrefix == 'B') {
|
||||
base = 2;
|
||||
chPrefix = null;
|
||||
}
|
||||
}
|
||||
if (chPrefix == null) {
|
||||
s = s.substr(2);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue