The first PDPjs script is operational (see /devices/pdp11/machine/1170/panel/debugger/xxdp)

This commit is contained in:
Jeff Parsons 2017-02-05 13:49:30 -08:00 committed by Jeff Parsons
commit 033f0bf212
7 changed files with 1602 additions and 1373 deletions

View file

@ -14,10 +14,11 @@ machines:
sticky: top
commands:
bootXXDP: |
select rl11 listDrives RL0;
select rl11 listDisks "XXDP+ Diagnostics";
click rl11 loadDisk;
click rl11 bootDisk;
selectDrive RL11 RL0;
select RL11 listDisks "XXDP+ Diagnostics";
load RL11;
wait RL11;
boot RL11;
---
This machine is ready to boot [XXDP+ Diagnostics](/disks/dec/rl02k/xxdp/) ("BOOT RL0") and run diagnostics
@ -39,4 +40,4 @@ could contain up to four such drives, which we refer to as RL0 through RL3.
To select drive RL0, press {% include machine-command.html type='button' label='Select RL0' machine='test1170' component='RL11' command='selectDrive' value='RL0' %}
To boot the [RL02K XXDP+ Diagnostics Disk](/disks/dec/rl02k/xxdp/) in one step, press {% include machine-command.html type='button' label='Boot XXDP+' machine='test1170' component='RL11' command='bootXXDP' %}
To boot the [RL02K XXDP+ Diagnostics Disk](/disks/dec/rl02k/xxdp/) in one step, press {% include machine-command.html type='button' label='Boot XXDP+' machine='test1170' command='bootXXDP' %}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -123,7 +123,10 @@ class DriveController extends Component {
this.irq = null;
this['exports'] = {
'selectDrive': this.selectDrive
'boot': this.bootSelectedDisk,
'load': this.loadSelectedDrive,
'selectDrive': this.selectDrive,
'wait': this.waitDrives,
};
}
@ -169,26 +172,8 @@ class DriveController extends Component {
case "listDisks":
this.bindings[sBinding] = control;
control.onchange = function onChangeListDisks(event) {
var controlDesc = dc.bindings["descDisk"];
var controlOption = control.options && control.options[control.selectedIndex];
if (controlDesc && controlOption) {
var dataValue = {};
var sValue = controlOption.getAttribute("data-value");
if (sValue) {
try {
dataValue = eval("(" + sValue + ")");
} catch (e) {
Component.error(dc.type + " option error: " + e.message);
}
}
var sHTML = dataValue['desc'];
if (sHTML === undefined) sHTML = "";
var sHRef = dataValue['href'];
if (sHRef !== undefined) sHTML = "<a href=\"" + sHRef + "\" target=\"_blank\">" + sHTML + "</a>";
controlDesc.innerHTML = sHTML;
}
dc.updateSelectedDisk();
};
return true;
@ -208,20 +193,13 @@ class DriveController extends Component {
case "loadDisk":
this.bindings[sBinding] = control;
control.onclick = function onClickLoadDrive(event) {
var controlDisks = dc.bindings["listDisks"];
if (controlDisks && controlDisks.options) {
var sDiskName = controlDisks.options[controlDisks.selectedIndex].text;
var sDiskPath = controlDisks.value;
dc.loadSelectedDrive(sDiskName, sDiskPath);
}
dc.loadSelectedDrive();
};
return true;
case "bootDisk":
this.bindings[sBinding] = control;
control.onclick = function onClickBootDisk(event) {
dc.bootSelectedDisk();
};
@ -549,6 +527,7 @@ class DriveController extends Component {
drive.iDrive = iDrive;
drive.name = this.idComponent;
drive.fBusy = drive.fLocal = false;
drive.fnCallReady = null;
drive.fRemovable = true;
/*
@ -747,28 +726,37 @@ class DriveController extends Component {
* loadSelectedDrive(sDiskName, sDiskPath, file)
*
* @this {DriveController}
* @param {string} sDiskName
* @param {string} sDiskPath
* @param {string} [sDiskName]
* @param {string} [sDiskPath]
* @param {File} [file] is set if there's an associated File object
* @return {boolean}
*/
loadSelectedDrive(sDiskName, sDiskPath, file)
{
if (!sDiskName && !sDiskPath) {
var controlDisks = this.bindings["listDisks"];
if (controlDisks && controlDisks.options) {
sDiskName = controlDisks.options[controlDisks.selectedIndex].text;
sDiskPath = controlDisks.value;
}
}
var controlDrives = this.bindings["listDrives"];
var iDrive = controlDrives && Str.parseInt(controlDrives.value, 10);
if (iDrive === undefined || iDrive < 0 || iDrive >= this.aDrives.length) {
this.notice("Unable to load the selected drive");
return;
return false;
}
if (!sDiskPath) {
this.unloadDrive(iDrive);
return;
return true;
}
if (sDiskPath == DriveController.SOURCE.LOCAL) {
this.notice('Use "Choose File" and "Mount" to select and load a local disk.');
return;
return false;
}
/*
@ -782,7 +770,7 @@ class DriveController extends Component {
*/
if (sDiskPath == DriveController.SOURCE.REMOTE) {
sDiskPath = window.prompt("Enter the URL of a remote disk image.", "") || "";
if (!sDiskPath) return;
if (!sDiskPath) return false;
sDiskName = Str.getBaseName(sDiskPath);
this.status("Attempting to load " + sDiskPath + " as \"" + sDiskName + "\"");
this.sDiskSource = DriveController.SOURCE.REMOTE;
@ -792,12 +780,14 @@ class DriveController extends Component {
}
this.loadDrive(iDrive, sDiskName, sDiskPath, false, file);
return true;
}
/**
* bootSelectedDisk()
*
* @this {DriveController}
* @return {boolean}
*/
bootSelectedDisk()
{
@ -807,12 +797,14 @@ class DriveController extends Component {
if (iDrive == null || iDrive < 0 || iDrive >= this.aDrives.length || !(drive = this.aDrives[iDrive])) {
this.notice("Unable to boot the selected drive");
return;
return false;
}
if (!drive.disk) {
this.notice("Load a disk into the drive first");
return;
return false;
}
/*
* NOTE: We're calling setReset() BEFORE reading the boot code in order to eliminate any side-effects
* of the previous state of either the controller OR the CPU; for example, we don't want any previous MMU
@ -821,10 +813,13 @@ class DriveController extends Component {
* a READY state is assured, and the readData() call shouldn't do anything to change that.
*/
this.cpu.setReset(0, true, iDrive);
var err = this.readData(drive, drive.iCylinderBoot, drive.iHeadBoot, drive.iSectorBoot, drive.cbSectorBoot, 0x0000, 2);
if (err) {
this.notice("Unable to read the boot sector (" + err + ")");
return false;
}
return true;
}
/**
@ -961,6 +956,11 @@ class DriveController extends Component {
}
this.displayDisk(drive.iDrive);
if (drive.fnCallReady) {
drive.fnCallReady();
drive.fnCallReady = null;
}
}
/**
@ -1077,7 +1077,7 @@ class DriveController extends Component {
/**
* selectDrive(sDrive)
*
* Used to programmatically select a drive by name.
* Used to select a drive by name.
*
* @this {DriveController}
* @param {number} sDrive
@ -1089,7 +1089,7 @@ class DriveController extends Component {
if (controlDrives && controlDrives.options) {
var nDrives = controlDrives.options.length;
for (var i = 0; i < nDrives; i++) {
if (controlDrives.options[i].innerHTML == sDrive) {
if (controlDrives.options[i].textContent == sDrive) {
var iDrive = Str.parseInt(controlDrives.options[i].value, 10);
if (iDrive >= 0) {
return this.displayDisk(iDrive, true);
@ -1100,6 +1100,55 @@ class DriveController extends Component {
return false;
}
/**
* updateSelectedDisk()
*
* @this {DriveController}
*/
updateSelectedDisk()
{
var control = this.bindings["listDisks"];
var controlDesc = this.bindings["descDisk"];
var controlOption = control.options && control.options[control.selectedIndex];
if (controlDesc && controlOption) {
var dataValue = {};
var sValue = controlOption.getAttribute("data-value");
if (sValue) {
try {
dataValue = eval("(" + sValue + ")");
} catch (e) {
Component.error(this.type + " option error: " + e.message);
}
}
var sHTML = dataValue['desc'];
if (sHTML === undefined) sHTML = "";
var sHRef = dataValue['href'];
if (sHRef !== undefined) sHTML = "<a href=\"" + sHRef + "\" target=\"_blank\">" + sHTML + "</a>";
controlDesc.innerHTML = sHTML;
}
}
/**
* waitDrives(fnCallReady)
*
* @this {DriveController}
* @param {function()|null} fnCallReady
* @return {boolean} true if successful, false if not
*/
waitDrives(fnCallReady)
{
for (var iDrive = 0; iDrive < this.aDrives.length; iDrive++) {
var drive = this.aDrives[iDrive];
if (drive && drive.fBusy) {
drive.fnCallReady = fnCallReady;
fnCallReady = null;
break;
}
}
if (fnCallReady) fnCallReady();
return true;
}
/**
* unloadDrive(iDrive, fLoading)
*

View file

@ -618,6 +618,159 @@ class Component {
return ae;
}
/**
* Component.getScriptCommands(sScript)
*
* @param {string} sScript
* @return {Array}
*/
static getScriptCommands(sScript)
{
var cch = sScript.length;
var aaCommands = [], aTokens = [], sToken = "", chQuote = null;
for (var i = 0; i < cch; i++) {
var ch = sScript[i];
if (ch == '"' || ch == "'") {
if (chQuote && ch != chQuote) {
sToken += ch;
continue;
}
if (!chQuote) {
chQuote = ch;
} else {
chQuote = null;
}
if (sToken) {
aTokens.push(sToken);
sToken = "";
}
continue;
}
if (!chQuote) {
if (ch == '\r' || ch == '\n') {
ch = ';';
}
if (ch == ' ' || ch == '\t' || ch == ';') {
if (sToken) {
aTokens.push(sToken);
sToken = "";
}
if (ch == ';' && aTokens.length) {
aaCommands.push(aTokens);
aTokens = [];
}
continue;
}
}
sToken += ch;
}
if (aTokens.length) {
aaCommands.push(aTokens);
}
return aaCommands;
}
/**
* Component.processScript(idMachine, sComponent, sScript)
*
* @param {string} idMachine
* @param {string} sComponent
* @param {string} sScript
* @return {boolean}
*/
static processScript(idMachine, sComponent, sScript)
{
var fSuccess = false;
if (typeof sScript == "string") {
fSuccess = true;
var aaCommands = Component.getScriptCommands(sScript);
if (!Component.processCommands(idMachine + ".machine", aaCommands, 0)) {
fSuccess = false;
}
}
return fSuccess;
}
/**
* Component.processCommands(idMachine, aaCommands, iCommand)
*
* @param {string} idMachine
* @param {Array} aaCommands
* @param {number} iCommand
* @return {boolean}
*/
static processCommands(idMachine, aaCommands, iCommand)
{
var fSuccess = true;
while (iCommand < aaCommands.length) {
var aTokens = aaCommands[iCommand];
var sCommand = aTokens[0];
var fnScript = Component.scriptCommands[sCommand];
var component = Component.getComponentByType(aTokens[1], idMachine);
if (fnScript) {
fSuccess = fnScript(component, aTokens[2], aTokens[3]);
}
else {
fSuccess = false;
if (component) {
var exports = component['exports'];
if (exports) {
var fnCommand = exports[sCommand];
if (fnCommand) {
if (sCommand == "wait") {
var fnCallReady = function processNextCommand(iNextCommand) {
return function() {
Component.processCommands(idMachine, aaCommands, iNextCommand);
}
}(iCommand + 1);
fSuccess = fnCommand.call(component, fnCallReady);
break;
}
fSuccess = fnCommand.call(component, aTokens[2], aTokens[3]);
}
}
}
}
if (!fSuccess) {
Component.alertUser("error processing script command (" + sCommand + ")");
break;
}
iCommand++;
}
return fSuccess;
}
/**
* Component.scriptSelect(component, sBinding, sValue)
*
* @param {Component} component
* @param {string} sBinding
* @param {string} sValue
* @return {boolean}
*/
static scriptSelect(component, sBinding, sValue)
{
var fSuccess = false;
if (component) {
var aBindings = component['bindings'];
var control = aBindings[sBinding];
if (control) {
for (var i = 0; i < control.options.length; i++) {
if (control.options[i].textContent == sValue) {
if (control.selectedIndex != i) {
control.selectedIndex = i;
}
fSuccess = true;
break;
}
}
}
}
return fSuccess;
}
/**
* toString()
*
@ -1070,6 +1223,10 @@ if (window) {
Component.machines = window? window['PCjs']['Machines'] : {};
Component.components = window? window['PCjs']['Components'] : [];
Component.scriptCommands = {
'select': Component.scriptSelect
};
/*
* The following polyfills provide ES5 functionality that's missing in older browsers (eg, IE8),
* allowing PCjs apps to run without slamming into exceptions; however, due to the lack of HTML5 canvas

View file

@ -606,8 +606,24 @@ function findMachineComponent(idMachine, sType)
return Component.getComponentByType(sType, idMachine + ".machine");
}
/**
* processMachineScript(idMachine, sComponent, sScript)
*
* @param {string} idMachine
* @param {string} sComponent
* @param {string} sScript
* @return {boolean}
*/
function processMachineScript(idMachine, sComponent, sScript)
{
return Component.processScript(idMachine, sComponent, sScript);
}
/**
* Prevent the Closure Compiler from renaming functions we want to export, by adding them as global properties.
*
* TODO: Consider making all these functions properties on a single global object (eg, 'PCjs'), to minimize global
* pollution and risk of name collision.
*/
if (APPNAME == "C1Pjs") {
window['embedC1P'] = embedC1P;
@ -624,6 +640,7 @@ if (APPNAME == "PDPjs") {
}
window['findMachineComponent'] = findMachineComponent;
window['processMachineScript'] = processMachineScript;
window['enableEvents'] = Web.enablePageEvents;
window['sendEvent'] = Web.sendPageEvent;

View file

@ -94,9 +94,10 @@ function addStickyMachine(idMachine, sPosition)
* website rather than of the machines. And since the machines are compiled, all their code and data is completely
* opaque to us, except for those functions explicitly exported by embed.js.
*
* So now, in addition to the functions for embedding machines on a webpage, embed.js includes a new function:
* So now, in addition to the functions for embedding machines on a webpage, embed.js includes a few new functions:
*
* findMachineComponent()
* processMachineScript()
*
* which uses existing Component methods to find the requested component for a specific machine, and if the
* component is found, then its 'exports' table is checked for an entry matching the specified command string, and if
@ -106,30 +107,28 @@ function addStickyMachine(idMachine, sPosition)
* @param {string} sComponent
* @param {string} sCommand
* @param {string} [sValue]
* @return {boolean}
*/
function commandMachine(idMachine, sComponent, sCommand, sValue)
{
if (window.findMachineComponent) {
if (sCommand == "script" && window.processMachineScript) {
return window.processMachineScript(idMachine, sComponent, sValue);
}
if (sComponent && window.findMachineComponent) {
var component = window.findMachineComponent(idMachine, sComponent);
if (component) {
if (sCommand == "script") {
window.alert("commandScript('" + sValue + "')");
} else {
var exports = component['exports'];
if (exports) {
var fnCommand = exports[sCommand];
if (fnCommand) {
//noinspection JSUnresolvedFunction
if (!fnCommand.call(component, sValue)) {
window.alert("Error calling " + idMachine + "." + sComponent + "." + sCommand + "(" + sValue + ")");
}
return;
}
var exports = component['exports'];
if (exports) {
var fnCommand = exports[sCommand];
if (fnCommand) {
//noinspection JSUnresolvedFunction
return !!fnCommand.call(component, sValue);
}
}
}
}
console.log("unimplemented: commandMachine('" + idMachine + "','" + sComponent + "','" + sCommand + "','" + sValue + "')");
return false;
}
/**