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();
}
return;
}
// process.stdout.write(buf);
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;
/*
* 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)), "../../../");
}
/*
* TODO: Revisit why we pass back sBaseName instead of the original sURL....
*/
var sBaseName = Str.getBaseName(sURL);
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;