PDP-11 command-line (Node-based) operation is finally working

This commit is contained in:
Jeff Parsons 2016-12-30 14:51:32 -08:00 • committed by Jeff Parsons
commit 4b345bb3d2
19 changed files with 552 additions and 438 deletions

View file

@ -58,10 +58,6 @@ try {
* used in a JSON machine definition file; eg:
*
* [
* {name: "panel",
* Create: Panel,
* objects: []
* },
* {name: "chipset":
* Create: ChipSet,
* objects: []
@ -77,7 +73,7 @@ try {
var Component;
var dbg;
var aComponents = [];
var asComponentsIgnore = ["embed", "save"];
var asComponentsIgnore = ["panel", "embed", "save"];
/*
* A few of the components are subclasses of other classes (eg, "x86cpu" is a subclass

View file

@ -78,7 +78,7 @@ try {
* TODO: Update the list of ignored (ie, ignorable) components.
*/
var Component;
var dbg;
var dbg, serial, fnSendData;
var aComponents = [];
var asComponentsIgnore = ["embed", "save"];
@ -204,6 +204,11 @@ function initMachine(xml)
}
var machine = xml['machine'];
if (fDebug) {
console.log(JSON.stringify(machine, null, 2));
}
var idMachine = machine[idAttrs] && machine[idAttrs]['id'] || "";
for (var iComponent = 0; iComponent < aComponents.length; iComponent++) {
@ -257,7 +262,7 @@ function initMachine(xml)
}
if (sDeviceName == "cpu") {
parmsObj['autoStart'] = false;
parmsObj['autoStart'] = true;
}
try {
@ -267,12 +272,24 @@ function initMachine(xml)
continue;
}
console.log(obj['id'] + " object created");
console.log(obj['type'] + " object created: " + obj['id']);
component.objects.push(obj);
if (obj.type == "Debugger") {
dbg = obj;
}
else if (obj.type == "SerialPort") {
serial = obj;
var exports = serial['exports'];
if (exports) {
var fnSetConnection = exports['setConnection'];
if (fnSetConnection) {
if (fnSetConnection.call(serial, null, receiveData)) {
fnSendData = exports['receiveData'];
}
}
}
}
}
}
}
@ -381,6 +398,9 @@ function doCommand(sCmd)
} catch(err) {
console.log(err.message);
}
if (sCmd == '?') {
console.log(".exit exit REPL and connect console to machine");
}
}
break;
}
@ -423,6 +443,37 @@ function onCommand(cmd, context, filename, callback)
callback(null, result);
}
/**
* receiveData(b)
*
* @param {number} b
*/
function receiveData(b)
{
var s;
if (b != Str.ASCII.CR && b != Str.ASCII.LF) {
s = Str.aASCIICodes[b];
}
if (s) {
s = '<' + s + '>';
} else {
s = String.fromCharCode(b);
}
process.stdout.write(s);
}
/**
* sendData(b)
*
* @param {number} b
*/
function sendData(b)
{
if (serial && fnSendData) {
fnSendData.call(serial, b);
}
}
/**
* startInput()
*
@ -432,7 +483,7 @@ function startInput()
{
var stdin = process.stdin;
if (!stdin.setRawMode) return false;
console.log("switching to raw input (alt-r to launch REPL, alt-x to exit)");
console.log("console connected to machine (alt-r for REPL prompt, alt-x to exit)");
stdin.setRawMode(true);
stdin.resume();
stdin.on('data', function(buf){
@ -440,9 +491,15 @@ function startInput()
stdin.removeAllListeners('data');
stdin.setRawMode(false);
stdin.pause();
if (buf[1] == 0x72) startREPL();
if (buf[1] == 0x72) {
startREPL();
} else {
console.log("exiting...");
process.exit();
}
// process.stdout.write(buf);
return;
}
sendData(buf[0]);
});
return true;
}
@ -452,6 +509,7 @@ function startInput()
*/
function startREPL()
{
console.log("starting REPL...");
replServer = repl.start({
prompt: "PDP11> ",
input: process.stdin,
@ -479,4 +537,4 @@ if (argv['cmd'] !== undefined) {
sCmdPrev = "";
}
if (!startInput()) startREPL();
startInput();

View file

@ -102,7 +102,7 @@ class BusPDP11 extends Component {
* Supported values for nBusWidth are 16 (default), 18, and 22. This represents the maximum size
* of the bus for the life of the machine, regardless what memory management mode the CPU has enabled.
*/
this.nBusWidth = parmsBus['busWidth'] || 16;
this.nBusWidth = +parmsBus['busWidth'] || 16;
/*
* Compute all BusPDP11 memory block parameters now, based on the width of the bus.

View file

@ -113,7 +113,7 @@ class ComputerPDP11 extends Component {
this.parmsMachine = null;
this.setMachineParms(parmsMachine);
this.fAutoPower = this.getMachineParm('autoPower', parmsComputer);
this.fAutoPower = this.getMachineParm('autoPower', parmsComputer, Str.TYPES.BOOLEAN);
/*
* nPowerChange is 0 while the power state is stable, 1 while power is transitioning
@ -124,7 +124,7 @@ class ComputerPDP11 extends Component {
/*
* TODO: Deprecate 'buswidth' (it should have always used camelCase)
*/
this.nBusWidth = parmsComputer['busWidth'] || parmsComputer['buswidth'];
this.nBusWidth = +parmsComputer['busWidth'] || +parmsComputer['buswidth'];
this.resume = ComputerPDP11.RESUME_NONE;
this.sStateData = null;
@ -303,7 +303,7 @@ class ComputerPDP11 extends Component {
}
/**
* getMachineParm(sParm, parmsComponent)
* getMachineParm(sParm, parmsComponent, type)
*
* If the machine parameter doesn't exist, we check for a matching component parameter (if parmsComponent is provided),
* and failing that, we check the bundled resources (if any).
@ -313,10 +313,11 @@ class ComputerPDP11 extends Component {
* resource to obtain the actual state.
*
* @param {string} sParm
* @param {Object} [parmsComponent]
* @param {Object|null} [parmsComponent]
* @param {number} [type] (from Str.TYPES)
* @return {string|undefined}
*/
getMachineParm(sParm, parmsComponent)
getMachineParm(sParm, parmsComponent, type)
{
/*
* When checking parmsURL, the check is allowed be a bit looser, because URL parameters are
@ -336,6 +337,16 @@ class ComputerPDP11 extends Component {
if (value === undefined && typeof resources == 'object' && resources[sParm]) {
value = sParm;
}
if (typeof value == "string" && type) {
switch(type) {
case Str.TYPES.NUMBER:
value = +value;
break;
case Str.TYPES.BOOLEAN:
value = (value == "true");
break;
}
}
return value;
}

View file

@ -98,9 +98,9 @@ class CPUPDP11 extends Component {
{
super("CPU", parmsCPU, CPUPDP11, MessagesPDP11.CPU);
var nCycles = parmsCPU['cycles'] || nCyclesDefault;
var nCycles = +parmsCPU['cycles'] || nCyclesDefault;
var nMultiplier = parmsCPU['multiplier'] || 1;
var nMultiplier = +parmsCPU['multiplier'] || 1;
this.nDisplayCount = 0;
this.nDisplayLimit = 30;
@ -122,6 +122,7 @@ class CPUPDP11 extends Component {
this.flags.running = false;
this.flags.starting = false;
this.flags.autoStart = parmsCPU['autoStart'];
if (typeof this.flags.autoStart == "string") this.flags.autoStart = (this.flags.autoStart == "true");
/*
* Get checksum parameters, if any. runCPU() behavior is not affected until fChecksum
@ -134,9 +135,9 @@ class CPUPDP11 extends Component {
*/
this.flags.checksum = false;
this.nChecksum = this.nCyclesChecksumNext = 0;
this.nCyclesChecksumStart = parmsCPU["csStart"];
this.nCyclesChecksumInterval = parmsCPU["csInterval"];
this.nCyclesChecksumStop = parmsCPU["csStop"];
this.nCyclesChecksumStart = +parmsCPU["csStart"];
this.nCyclesChecksumInterval = +parmsCPU["csInterval"];
this.nCyclesChecksumStop = +parmsCPU["csStop"];
/*
* Array of countdown timers managed by addTimer() and setTimer().
@ -257,8 +258,8 @@ class CPUPDP11 extends Component {
this.dbg.init(this.flags.autoStart);
} else {
/*
* The Computer (this.cmp) knows if there's a Control Panel (this.cmp.panel), and the Control Panel
* knows if there's a "print" control (this.cmp.panel.controlPrint), and if there IS a "print" control
* The Computer (this.cmp) knows if there's a Control Panel (this.panel), and the Control Panel
* knows if there's a "print" control (this.panel.controlPrint), and if there IS a "print" control
* but no debugger, the machine is probably misconfigured (most likely, the page simply neglected to
* load the Debugger component).
*
@ -268,7 +269,7 @@ class CPUPDP11 extends Component {
this.println("No debugger detected");
}
if (!this.flags.autoStart) {
this.println("CPU will not be auto-started, click Run to start");
this.println("CPU will not be auto-started " + (this.panel? "(click Run to start)" : "(type 'go' to start)"));
}
}
/*

View file

@ -109,7 +109,7 @@ class CPUStatePDP11 extends CPUPDP11 {
super(parmsCPU, nCyclesDefault);
this.model = model;
this.addrReset = parmsCPU['addrReset'] || 0;
this.addrReset = +parmsCPU['addrReset'] || 0;
/*
* These properties will be initialized by initCPU()

View file

@ -226,9 +226,9 @@ class DebuggerPDP11 extends Debugger {
* @param {number} [nBase]
* @return {DbgAddrPDP11}
*/
newAddr(addr, fPhysical, nBase)
newAddr(addr = null, fPhysical = false, nBase)
{
return {addr: addr || null, fPhysical: fPhysical || false, fTemporary: false, nBase: nBase};
return {addr: addr, fPhysical: fPhysical, fTemporary: false, nBase: nBase};
}
/**

View file

@ -382,7 +382,7 @@ class DiskPDP11 extends Component {
*
* @this {DiskPDP11}
* @param {string} sURL
* @param {string} sDiskData
* @param {string|null} sDiskData
* @param {number} nErrorCode (response from server if anything other than 200)
*/
doneLoad(sURL, sDiskData, nErrorCode)

View file

@ -46,8 +46,9 @@ class PanelPDP11 extends Component {
* The PanelPDP11 component has no required (parmsPanel) properties.
*
* @param {Object} parmsPanel
* @param {boolean} fBindings (true if panel may have bindings, otherwise not)
*/
constructor(parmsPanel)
constructor(parmsPanel, fBindings)
{
super("Panel", parmsPanel, PanelPDP11, MessagesPDP11.PANEL);
@ -59,6 +60,7 @@ class PanelPDP11 extends Component {
this.nDisplayCount = 0;
this.nDisplayLimit = 60;
this.fDisplayLiveRegs = true;
this.fBindings = fBindings;
/*
* regSwitches contains the Front Panel (aka Console) SWITCH register, which is also available
@ -145,6 +147,8 @@ class PanelPDP11 extends Component {
/** @type {DebuggerPDP11} */
this.dbg = null;
this.setReady();
}
/**
@ -386,9 +390,10 @@ class PanelPDP11 extends Component {
/*
* As noted in init(), our powerUp() method gives us a second opportunity to notify any
* components that that might care (eg, CPU, Keyboard, and Debugger) that we have some controls
* they might want to use.
* (ie, bindings) they might want to use.
*/
PanelPDP11.init();
if (this.fBindings) PanelPDP11.init();
/*
* TODO: Until we implement a restore() function, all we can do is reset()
*/
@ -1089,18 +1094,13 @@ class PanelPDP11 extends Component {
*/
static init()
{
var fReady = false;
var aePanels = Component.getElementsByClass(document, PDP11.APPCLASS, "panel");
for (var iPanel=0; iPanel < aePanels.length; iPanel++) {
var ePanel = aePanels[iPanel];
var parmsPanel = Component.getComponentParms(ePanel);
var panel = Component.getComponentByID(parmsPanel['id']);
if (!panel) {
fReady = true;
panel = new PanelPDP11(parmsPanel);
}
if (!panel) panel = new PanelPDP11(parmsPanel, true);
Component.bindComponentControls(panel, ePanel, PDP11.APPCLASS);
if (fReady) panel.setReady();
}
}
}

View file

@ -72,7 +72,7 @@ class PC11 extends Component {
*/
this.configMount = this.parseConfig(parms['autoMount']);
this.cAutoMount = 0;
this.nBaudReceive = parms['baudReceive'] || PDP11.PC11.PRS.BAUD;
this.nBaudReceive = +parms['baudReceive'] || PDP11.PC11.PRS.BAUD;
this.regPRS = 0; // PRS register
this.regPRB = 0; // PRB register

View file

@ -69,10 +69,13 @@ class RAMPDP11 extends Component {
this.abInit = null;
this.aSymbols = null;
this.addrRAM = parmsRAM['addr'];
this.sizeRAM = parmsRAM['size'];
this.addrRAM = +parmsRAM['addr'];
this.sizeRAM = +parmsRAM['size'];
this.addrLoad = parmsRAM['load'];
this.addrExec = parmsRAM['exec'];
if (this.addrLoad != null) this.addrLoad = +this.addrLoad;
if (this.addrExec != null) this.addrExec = +this.addrExec;
this.fInstalled = (!!this.sizeRAM); // 0 is the default value for 'size' when none is specified
this.fAllocated = false;
@ -222,7 +225,13 @@ class RAMPDP11 extends Component {
* Too early...
*/
if (!this.abInit || !this.bus) return;
this.loadImage(this.abInit, this.addrLoad, this.addrExec, this.addrRAM);
if (this.loadImage(this.abInit, this.addrLoad, this.addrExec, this.addrRAM)) {
this.status('Loaded image "' + this.sFileName + '"');
} else {
this.notice('Error loading image "' + this.sFileName + '"');
}
/*
* NOTE: We now retain this data, so that reset() can return the RAM to its predefined state.
*

View file

@ -67,8 +67,8 @@ class ROMPDP11 extends Component {
this.abInit = null;
this.aSymbols = null;
this.addrROM = parmsROM['addr'];
this.sizeROM = parmsROM['size'];
this.addrROM = +parmsROM['addr'];
this.sizeROM = +parmsROM['size'];
this.fRetainROM = false;
/*
@ -83,6 +83,9 @@ class ROMPDP11 extends Component {
* Most ROMs are not aliased, in which case the 'alias' property should have the default value of null.
*/
this.addrAlias = parmsROM['alias'];
if (typeof this.addrAlias == "string") {
this.addrAlias = eval(this.addrAlias);
}
this.sFilePath = parmsROM['file'];
this.sFileName = Str.getBaseName(this.sFilePath);

View file

@ -100,11 +100,11 @@ class SerialPortPDP11 extends Component {
{
super("SerialPort", parmsSerial, SerialPortPDP11, MessagesPDP11.SERIAL);
this.iAdapter = parmsSerial['adapter'];
this.nBaudReceive = parmsSerial['baudReceive'] || PDP11.DL11.RCSR.BAUD;
this.nBaudTransmit = parmsSerial['baudTransmit'] || PDP11.DL11.XCSR.BAUD;
this.iAdapter = +parmsSerial['adapter'];
this.nBaudReceive = +parmsSerial['baudReceive'] || PDP11.DL11.RCSR.BAUD;
this.nBaudTransmit = +parmsSerial['baudTransmit'] || PDP11.DL11.XCSR.BAUD;
this.fUpperCase = parmsSerial['upperCase'];
if (typeof this.fUpperCase == "string") this.fUpperCase = (this.fUpperCase == "true");
/**
* consoleOutput becomes a string that records serial port output if the 'binding' property is set to the
* reserved name "console". Nothing is written to the console, however, until a linefeed (0x0A) is output
@ -138,8 +138,8 @@ class SerialPortPDP11 extends Component {
* charBOL, if nonzero, is a character to automatically output at the beginning of every line. This probably
* isn't generally useful; I use it internally to preformat serial output.
*/
this.tabSize = parmsSerial['tabSize'];
this.charBOL = parmsSerial['charBOL'];
this.tabSize = +parmsSerial['tabSize'];
this.charBOL = +parmsSerial['charBOL'];
this.iLogicalCol = 0;
this.fNullModem = true;
@ -171,7 +171,8 @@ class SerialPortPDP11 extends Component {
this['exports'] = {
'connect': this.initConnection,
'receiveData': this.receiveData,
'receiveStatus': this.receiveStatus
'receiveStatus': this.receiveStatus,
'setConnection': this.setConnection
};
}
@ -624,6 +625,24 @@ class SerialPortPDP11 extends Component {
}
}
/**
* setConnection(component, fn)
*
* @this {SerialPortPDP11}
* @param {Object|null} component
* @param {function(number)} fn
* @return {boolean}
*/
setConnection(component, fn)
{
if (!this.connection) {
this.connection = component;
this.sendData = fn;
return true;
}
return false;
}
/**
* transmitByte(b)
*

View file

@ -97,7 +97,7 @@ class Debugger extends Component {
/*
* Default base used to display all values; modified with the "s base" command.
*/
this.nBase = parmsDbg['base'] || 16;
this.nBase = +parmsDbg['base'] || 16;
this.fParens = false;
/*

View file

@ -310,23 +310,25 @@ class Net {
* @param {string} sURL
* @param {Object|null} [dataPost] for a POST request (default is a GET request)
* @param {boolean} [fAsync] is true for an asynchronous request
* @param {function(string,string,number)} [done]
* @param {function(string,string|null,number)} [done]
* @return {Array|null} Array containing [sResource, nErrorCode], or null if no response yet
*/
static getResource(sURL, dataPost, fAsync, done)
{
var nErrorCode = -1, sResource = null, response = null;
if (Net.isRemote(sURL)) {
console.log('Net.getResource("' + sURL + '"): unimplemented');
} else {
if (!Net.sServerRoot) {
Net.sServerRoot = path.join(path.dirname(fs.realpathSync(__filename)), "../../../");
}
/*
* TODO: Revisit why we pass back sBaseName instead of the original sURL....
*/
var sBaseName = Str.getBaseName(sURL);
if (Net.isRemote(sURL)) {
console.log('Net.getResource("' + sURL + '"): unimplemented');
if (done) done(sBaseName, null, -1);
} else {
if (!Net.sServerRoot) {
Net.sServerRoot = path.join(path.dirname(fs.realpathSync(__filename)), "../../../");
}
var sFile = path.join(Net.sServerRoot, sURL);
if (fAsync) {
fs.readFile(sFile, {encoding: "utf8"}, function(err, s)

View file

@ -620,4 +620,16 @@ Str.aASCIICodes = {
0x1F: "US" // Unit Separator
};
Str.TYPES = {
NULL: 0,
BYTE: 1,
WORD: 2,
DWORD: 3,
NUMBER: 4,
STRING: 5,
BOOLEAN: 6,
OBJECT: 7,
ARRAY: 8
};
module.exports = Str;

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