Converted the rest of the PCjs machines to ES6
This commit is contained in:
parent
2b1e171ecb
commit
323a42be37
301 changed files with 60754 additions and 52273 deletions
|
|
@ -26,345 +26,331 @@
|
|||
* as to their contents.
|
||||
*/
|
||||
|
||||
/*
|
||||
* BUILD INSTRUCTIONS
|
||||
*
|
||||
* To build C1Pjs (c1p.js), run Google's Closure Compiler, replacing "*.js" with
|
||||
* the input file sequence defined by the "c1pJSFiles" property in package.json:
|
||||
*
|
||||
* java -jar compiler.jar
|
||||
* --compilation_level ADVANCED_OPTIMIZATIONS
|
||||
* --define='DEBUG=false'
|
||||
* --warning_level=VERBOSE
|
||||
* --js *.js
|
||||
* --js_output_file c1p.js
|
||||
*
|
||||
* Google's Closure Compiler (compiler.jar) is documented at
|
||||
* https://developers.google.com/closure/compiler/ and is available
|
||||
* for download here:
|
||||
*
|
||||
* http://closure-compiler.googlecode.com/files/compiler-latest.zip
|
||||
*
|
||||
* The C1Pjs JavaScript files do have some initialization-order dependencies.
|
||||
* If you load the files individually, it's recommended that you load them in
|
||||
* the same order that they're compiled (see above).
|
||||
*
|
||||
* Generally speaking, component.js should be first, computer.js should be
|
||||
* last (of the files based on component.js), and panel.js should be listed
|
||||
* early so that the Control Panel is ready as soon as possible.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var Web = require("../../shared/es6/weblib");
|
||||
var Component = require("../../shared/es6/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* C1PComputer(parmsComputer, modules)
|
||||
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
|
||||
* which would force us to declare all class properties in the constructor, as well as prevent
|
||||
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
|
||||
*
|
||||
* The C1PComputer component expects the following (parmsComputer) properties:
|
||||
*
|
||||
* modules[{}] (from the <module> definition(s) for the computer)
|
||||
*
|
||||
* This component processes all the <module> "start" and "end" specifications
|
||||
* and "wires" everything to a common "address buffer"; namely, the abMemory array.
|
||||
* abMemory encompasses the computer's entire address space, but every component must
|
||||
* play nice and use only its assigned section of abMemory -- and pretend it's an array
|
||||
* of bytes, when in fact it's an array of floating-point values (the only primitive
|
||||
* numeric data type that JavaScript provides).
|
||||
*
|
||||
* This component also insures that all the other components are ready; in particular,
|
||||
* this means that the ROM and Video components have finished loading their resources
|
||||
* and are ready for operation. Other components become ready as soon as we call their
|
||||
* setBuffer() method (eg, CPU, RAM, Keyboard, Debugger, SerialPort, DiskController), and
|
||||
* others, like Panel, become ready even earlier, at the end of their initialization.
|
||||
*
|
||||
* Once every component has indicated it's ready, we call its setPower() notification
|
||||
* function (if it has one; it's optional). We call the CPU's setPower() function last,
|
||||
* so that the CPU is assured that all other components are ready and "powered".
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @unrestricted
|
||||
*/
|
||||
function C1PComputer(parmsComputer, modules)
|
||||
{
|
||||
Component.call(this, "C1PComputer", parmsComputer);
|
||||
|
||||
this.modules = modules;
|
||||
}
|
||||
|
||||
Component.subclass(C1PComputer);
|
||||
|
||||
/**
|
||||
* @this {C1PComputer}
|
||||
* @param {boolean} [fPowerOn] is true to indicate that we should start the CPU running
|
||||
*/
|
||||
C1PComputer.prototype.reset = function(fPowerOn)
|
||||
{
|
||||
var cpu = null;
|
||||
for (var sType in this.modules) {
|
||||
for (var i=0; i < this.modules[sType].length; i++) {
|
||||
var component = this.modules[sType][i];
|
||||
if (component && component.reset) {
|
||||
if (DEBUG) this.println("resetting " + sType);
|
||||
component.reset();
|
||||
if (sType == "cpu") cpu = component;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cpu) {
|
||||
cpu.update();
|
||||
if (fPowerOn) cpu.run();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PComputer}
|
||||
*
|
||||
* Called by the CPU to notify all component start() handlers
|
||||
*/
|
||||
C1PComputer.prototype.start = function()
|
||||
{
|
||||
for (var sType in this.modules) {
|
||||
if (sType == "cpu") continue;
|
||||
for (var i=0; i < this.modules[sType].length; i++) {
|
||||
var component = this.modules[sType][i];
|
||||
if (component && component.start) {
|
||||
component.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PComputer}
|
||||
* @param {number} msStart
|
||||
* @param {number} nCycles
|
||||
*
|
||||
* Called by the CPU to notify all component stop() handlers
|
||||
*/
|
||||
C1PComputer.prototype.stop = function(msStart, nCycles)
|
||||
{
|
||||
for (var sType in this.modules) {
|
||||
if (sType == "cpu") continue;
|
||||
for (var i=0; i < this.modules[sType].length; i++) {
|
||||
var component = this.modules[sType][i];
|
||||
if (component && component.stop) {
|
||||
component.stop(msStart, nCycles);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PComputer}
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @param {string} [sValue] optional data value
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
C1PComputer.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
||||
{
|
||||
switch(sBinding) {
|
||||
case "reset":
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function(computer) {
|
||||
return function() {
|
||||
computer.reset();
|
||||
};
|
||||
}(this);
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* NOTE: If there are multiple components for a given type, we may need to provide a means of discriminating.
|
||||
*
|
||||
* @this {C1PComputer}
|
||||
* @param {string} sType
|
||||
* @param {string} [idRelated] of related component
|
||||
* @param {Component|null} [componentPrev] of previously returned component, if any
|
||||
* @return {Component|null}
|
||||
*/
|
||||
C1PComputer.prototype.getComponentByType = function(sType, idRelated, componentPrev)
|
||||
{
|
||||
if (this.modules[sType]) {
|
||||
return this.modules[sType][0];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
C1PComputer.power = function(computer)
|
||||
{
|
||||
/*
|
||||
* Insure that the ROMs, Video and CPU are all ready before "powering" everything; always "power"
|
||||
* the CPU last, to make sure it doesn't start asking other components to do things before they're ready.
|
||||
class C1PComputer extends Component {
|
||||
/**
|
||||
* C1PComputer(parmsComputer, modules)
|
||||
*
|
||||
* The C1PComputer component expects the following (parmsComputer) properties:
|
||||
*
|
||||
* modules[{}] (from the <module> definition(s) for the computer)
|
||||
*
|
||||
* This component processes all the <module> "start" and "end" specifications
|
||||
* and "wires" everything to a common "address buffer"; namely, the abMemory array.
|
||||
* abMemory encompasses the computer's entire address space, but every component must
|
||||
* play nice and use only its assigned section of abMemory -- and pretend it's an array
|
||||
* of bytes, when in fact it's an array of floating-point values (the only primitive
|
||||
* numeric data type that JavaScript provides).
|
||||
*
|
||||
* This component also insures that all the other components are ready; in particular,
|
||||
* this means that the ROM and Video components have finished loading their resources
|
||||
* and are ready for operation. Other components become ready as soon as we call their
|
||||
* setBuffer() method (eg, CPU, RAM, Keyboard, Debugger, SerialPort, DiskController), and
|
||||
* others, like Panel, become ready even earlier, at the end of their initialization.
|
||||
*
|
||||
* Once every component has indicated it's ready, we call its setPower() notification
|
||||
* function (if it has one; it's optional). We call the CPU's setPower() function last,
|
||||
* so that the CPU is assured that all other components are ready and "powered".
|
||||
*
|
||||
* @this {C1PComputer}
|
||||
* @param {Object} parmsComputer
|
||||
* @param {Object} modules
|
||||
*/
|
||||
var cpu = null;
|
||||
for (var sType in computer.modules) {
|
||||
for (var i=0; i < computer.modules[sType].length; i++) {
|
||||
var component = computer.modules[sType][i];
|
||||
if (!component) continue;
|
||||
if (!component.isReady()) {
|
||||
component.isReady(function(computer) {
|
||||
return function() {
|
||||
C1PComputer.power(computer);
|
||||
};
|
||||
}(computer)); // jshint ignore:line
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* The CPU component's setPower() notification handler is a special case: we don't want
|
||||
* to call it until the end (below), after all others have been called.
|
||||
*/
|
||||
if (sType == "cpu")
|
||||
cpu = component;
|
||||
else if (component.setPower) {
|
||||
component.setPower(true, computer);
|
||||
}
|
||||
}
|
||||
constructor(parmsComputer, modules)
|
||||
{
|
||||
super("C1PComputer", parmsComputer);
|
||||
|
||||
this.modules = modules;
|
||||
}
|
||||
|
||||
/*
|
||||
* The entire computer is finally ready; we call our own setReady() for completeness, not because any
|
||||
* other component actually cares when we're ready.
|
||||
/**
|
||||
* reset(fPowerOn)
|
||||
*
|
||||
* @this {C1PComputer}
|
||||
* @param {boolean} [fPowerOn] is true to indicate that we should start the CPU running
|
||||
*/
|
||||
computer.setReady();
|
||||
|
||||
computer.println(C1PJS.APPNAME + " v" + C1PJS.APPVERSION + "\n" + COPYRIGHT);
|
||||
|
||||
/*
|
||||
* Once we get to this point, we're guaranteed that all components are ready, so it's safe to "power" the CPU;
|
||||
* setPower() includes an automatic reset(fPowerOn), so the CPU should begin executing immediately, unless a debugger
|
||||
* is attached.
|
||||
*/
|
||||
if (cpu) cpu.setPower(true, computer);
|
||||
};
|
||||
|
||||
/*
|
||||
* C1PComputer.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "c1pjs-computer", extracting the
|
||||
* JSON-encoded parameters for the C1PComputer constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PComputer component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
C1PComputer.init = function()
|
||||
{
|
||||
/*
|
||||
* In non-COMPILED builds, embedMachine() may have set XMLVERSION.
|
||||
*/
|
||||
if (!COMPILED && XMLVERSION) C1PJS.APPVERSION = XMLVERSION;
|
||||
|
||||
var aeComputers = Component.getElementsByClass(document, C1PJS.APPCLASS, "computer");
|
||||
|
||||
for (var iComputer=0; iComputer < aeComputers.length; iComputer++) {
|
||||
|
||||
var eComputer = aeComputers[iComputer];
|
||||
var parmsComputer = Component.getComponentParms(eComputer);
|
||||
|
||||
var component;
|
||||
var modules = {};
|
||||
|
||||
var abMemory;
|
||||
var addrStart = 0, addrEnd = 0;
|
||||
|
||||
for (var iAddr=0; iAddr < parmsComputer['modules'].length; iAddr++) {
|
||||
var addrInfo = parmsComputer['modules'][iAddr];
|
||||
/*
|
||||
* The first address range (ie, the CPU range) must specify the range for the entire
|
||||
* address space (abMemory), which we allocate and zero-initialize.
|
||||
*
|
||||
* NOTE: We might consider doing what the Video component does on first reset: initializing
|
||||
* the entire memory buffer to random values. However, a constant (eg, 0xA5) might be
|
||||
* more useful, acting as a crude indicator of memory the client code hasn't written yet.
|
||||
*/
|
||||
if (!iAddr) {
|
||||
if (addrInfo['type'] != "cpu") break;
|
||||
addrStart = addrInfo['start'];
|
||||
addrEnd = addrInfo['end'];
|
||||
abMemory = new Array(addrEnd+1 - addrStart);
|
||||
for (var addr=addrStart; addr < abMemory.length; addr++) {
|
||||
abMemory[addr] = 0;
|
||||
reset(fPowerOn)
|
||||
{
|
||||
var cpu = null;
|
||||
for (var sType in this.modules) {
|
||||
for (var i=0; i < this.modules[sType].length; i++) {
|
||||
var component = this.modules[sType][i];
|
||||
if (component && component.reset) {
|
||||
if (DEBUG) this.println("resetting " + sType);
|
||||
component.reset();
|
||||
if (sType == "cpu") cpu = component;
|
||||
}
|
||||
}
|
||||
component = Component.getComponentByID(addrInfo['refID'], parmsComputer['id']);
|
||||
}
|
||||
if (cpu) {
|
||||
cpu.update();
|
||||
if (fPowerOn) cpu.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* start()
|
||||
*
|
||||
* Called by the CPU to notify all component start() handlers.
|
||||
*
|
||||
* @this {C1PComputer}
|
||||
*/
|
||||
start()
|
||||
{
|
||||
for (var sType in this.modules) {
|
||||
if (sType == "cpu") continue;
|
||||
for (var i=0; i < this.modules[sType].length; i++) {
|
||||
var component = this.modules[sType][i];
|
||||
if (component && component.start) {
|
||||
component.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* stop(msStart, nCycles)
|
||||
*
|
||||
* Called by the CPU to notify all component stop() handlers
|
||||
*
|
||||
* @this {C1PComputer}
|
||||
* @param {number} msStart
|
||||
* @param {number} nCycles
|
||||
*/
|
||||
stop(msStart, nCycles)
|
||||
{
|
||||
for (var sType in this.modules) {
|
||||
if (sType == "cpu") continue;
|
||||
for (var i=0; i < this.modules[sType].length; i++) {
|
||||
var component = this.modules[sType][i];
|
||||
if (component && component.stop) {
|
||||
component.stop(msStart, nCycles);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PComputer}
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @param {string} [sValue] optional data value
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
setBinding(sHTMLType, sBinding, control, sValue)
|
||||
{
|
||||
switch(sBinding) {
|
||||
case "reset":
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function(computer) {
|
||||
return function() {
|
||||
computer.reset();
|
||||
};
|
||||
}(this);
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: If there are multiple components for a given type, we may need to provide a means of discriminating.
|
||||
*
|
||||
* @this {C1PComputer}
|
||||
* @param {string} sType
|
||||
* @param {string} [idRelated] of related component
|
||||
* @param {Component|null} [componentPrev] of previously returned component, if any
|
||||
* @return {Component|null}
|
||||
*/
|
||||
getComponentByType(sType, idRelated, componentPrev)
|
||||
{
|
||||
if (this.modules[sType]) {
|
||||
return this.modules[sType][0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static power(computer)
|
||||
{
|
||||
/*
|
||||
* Insure that the ROMs, Video and CPU are all ready before "powering" everything; always "power"
|
||||
* the CPU last, to make sure it doesn't start asking other components to do things before they're ready.
|
||||
*/
|
||||
var cpu = null;
|
||||
for (var sType in computer.modules) {
|
||||
for (var i=0; i < computer.modules[sType].length; i++) {
|
||||
var component = computer.modules[sType][i];
|
||||
if (!component) continue;
|
||||
if (!component.isReady()) {
|
||||
component.isReady(function(computer) {
|
||||
return function() {
|
||||
C1PComputer.power(computer);
|
||||
};
|
||||
}(computer)); // jshint ignore:line
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* The CPU component's setPower() notification handler is a special case: we don't want
|
||||
* to call it until the end (below), after all others have been called.
|
||||
*/
|
||||
if (sType == "cpu")
|
||||
cpu = component;
|
||||
else if (component.setPower) {
|
||||
component.setPower(true, computer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The entire computer is finally ready; we call our own setReady() for completeness, not because any
|
||||
* other component actually cares when we're ready.
|
||||
*/
|
||||
computer.setReady();
|
||||
|
||||
computer.println(C1PJS.APPNAME + " v" + C1PJS.APPVERSION + "\n" + COPYRIGHT);
|
||||
|
||||
/*
|
||||
* Once we get to this point, we're guaranteed that all components are ready, so it's safe to "power" the CPU;
|
||||
* setPower() includes an automatic reset(fPowerOn), so the CPU should begin executing immediately, unless a debugger
|
||||
* is attached.
|
||||
*/
|
||||
if (cpu) cpu.setPower(true, computer);
|
||||
}
|
||||
|
||||
/*
|
||||
* C1PComputer.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "c1pjs-computer", extracting the
|
||||
* JSON-encoded parameters for the C1PComputer constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PComputer component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
/*
|
||||
* In non-COMPILED builds, embedMachine() may have set XMLVERSION.
|
||||
*/
|
||||
if (!COMPILED && XMLVERSION) C1PJS.APPVERSION = XMLVERSION;
|
||||
|
||||
var aeComputers = Component.getElementsByClass(document, C1PJS.APPCLASS, "computer");
|
||||
|
||||
for (var iComputer=0; iComputer < aeComputers.length; iComputer++) {
|
||||
|
||||
var eComputer = aeComputers[iComputer];
|
||||
var parmsComputer = Component.getComponentParms(eComputer);
|
||||
|
||||
var component;
|
||||
var modules = {};
|
||||
|
||||
var abMemory;
|
||||
var addrStart = 0, addrEnd = 0;
|
||||
|
||||
for (var iAddr=0; iAddr < parmsComputer['modules'].length; iAddr++) {
|
||||
var addrInfo = parmsComputer['modules'][iAddr];
|
||||
/*
|
||||
* The first address range (ie, the CPU range) must specify the range for the entire
|
||||
* address space (abMemory), which we allocate and zero-initialize.
|
||||
*
|
||||
* NOTE: We might consider doing what the Video component does on first reset: initializing
|
||||
* the entire memory buffer to random values. However, a constant (eg, 0xA5) might be
|
||||
* more useful, acting as a crude indicator of memory the client code hasn't written yet.
|
||||
*/
|
||||
if (!iAddr) {
|
||||
if (addrInfo['type'] != "cpu") break;
|
||||
addrStart = addrInfo['start'];
|
||||
addrEnd = addrInfo['end'];
|
||||
abMemory = new Array(addrEnd+1 - addrStart);
|
||||
for (var addr=addrStart; addr < abMemory.length; addr++) {
|
||||
abMemory[addr] = 0;
|
||||
}
|
||||
}
|
||||
component = Component.getComponentByID(addrInfo['refID'], parmsComputer['id']);
|
||||
if (component) {
|
||||
var sType = addrInfo['type'];
|
||||
if (modules[sType] === undefined)
|
||||
modules[sType] = [];
|
||||
modules[sType].push(component);
|
||||
if (component.setBuffer && addrInfo['start'] !== undefined) {
|
||||
component.setBuffer(abMemory, addrInfo['start'], addrInfo['end'], modules['cpu'][0]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Component.error("no component for <module refid=\"" + addrInfo['refID'] + "\">");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (abMemory === undefined) {
|
||||
Component.error("<module type=\"cpu\"> definition must appear first in the <computer> specification");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Let's see if the Debugger is installed (NOTE: its ID must be "debugger", and only one per machine is supported);
|
||||
* the Debugger needs our setBuffer(), setPower() and reset() notifications, and this relieves us from having an explicit
|
||||
* <module> entry for type="debugger".
|
||||
*/
|
||||
component = Component.getComponentByID('debugger', parmsComputer['id']);
|
||||
if (component) {
|
||||
var sType = addrInfo['type'];
|
||||
if (modules[sType] === undefined)
|
||||
modules[sType] = [];
|
||||
modules[sType].push(component);
|
||||
if (component.setBuffer && addrInfo['start'] !== undefined) {
|
||||
component.setBuffer(abMemory, addrInfo['start'], addrInfo['end'], modules['cpu'][0]);
|
||||
modules['debugger'] = [component];
|
||||
if (component.setBuffer) {
|
||||
component.setBuffer(abMemory, addrStart, addrEnd, modules['cpu'][0]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Component.error("no component for <module refid=\"" + addrInfo['refID'] + "\">");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (abMemory === undefined) {
|
||||
Component.error("<module type=\"cpu\"> definition must appear first in the <computer> specification");
|
||||
return;
|
||||
}
|
||||
var computer = new C1PComputer(parmsComputer, modules);
|
||||
|
||||
/*
|
||||
* Let's see if the Debugger is installed (NOTE: its ID must be "debugger", and only one per machine is supported);
|
||||
* the Debugger needs our setBuffer(), setPower() and reset() notifications, and this relieves us from having an explicit
|
||||
* <module> entry for type="debugger".
|
||||
*/
|
||||
component = Component.getComponentByID('debugger', parmsComputer['id']);
|
||||
if (component) {
|
||||
modules['debugger'] = [component];
|
||||
if (component.setBuffer) {
|
||||
component.setBuffer(abMemory, addrStart, addrEnd, modules['cpu'][0]);
|
||||
}
|
||||
}
|
||||
|
||||
var computer = new C1PComputer(parmsComputer, modules);
|
||||
|
||||
/*
|
||||
* Let's see if the Control Panel is installed (NOTE: its ID must be "panel", and only one per machine is supported);
|
||||
* the Panel needs our setPower() notifications, and this relieves us from having an explicit <module> entry for type="panel".
|
||||
*/
|
||||
var panel = Component.getComponentByID('panel', parmsComputer['id']);
|
||||
if (panel) {
|
||||
modules['panel'] = [panel];
|
||||
/*
|
||||
* Iterate through all the other components and update their print methods if the Control Panel has provided overrides.
|
||||
* Let's see if the Control Panel is installed (NOTE: its ID must be "panel", and only one per machine is supported);
|
||||
* the Panel needs our setPower() notifications, and this relieves us from having an explicit <module> entry for type="panel".
|
||||
*/
|
||||
if (panel.controlPrint) {
|
||||
var aComponents = Component.getComponents(parmsComputer['id']);
|
||||
for (var iComponent = 0; iComponent < aComponents.length; iComponent++) {
|
||||
component = aComponents[iComponent];
|
||||
if (component == panel) continue;
|
||||
component.notice = panel.notice;
|
||||
component.println = panel.println;
|
||||
component.controlPrint = panel.controlPrint;
|
||||
var panel = Component.getComponentByID('panel', parmsComputer['id']);
|
||||
if (panel) {
|
||||
modules['panel'] = [panel];
|
||||
/*
|
||||
* Iterate through all the other components and update their print methods if the Control Panel has provided overrides.
|
||||
*/
|
||||
if (panel.controlPrint) {
|
||||
var aComponents = Component.getComponents(parmsComputer['id']);
|
||||
for (var iComponent = 0; iComponent < aComponents.length; iComponent++) {
|
||||
component = aComponents[iComponent];
|
||||
if (component == panel) continue;
|
||||
component.notice = panel.notice;
|
||||
component.println = panel.println;
|
||||
component.controlPrint = panel.controlPrint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We may eventually add a "Power" button, but for now, all we have is a "Reset" button
|
||||
*/
|
||||
Component.bindComponentControls(computer, eComputer, C1PJS.APPCLASS);
|
||||
|
||||
/*
|
||||
* "Power" the computer automatically
|
||||
*/
|
||||
C1PComputer.power(computer);
|
||||
}
|
||||
|
||||
/*
|
||||
* We may eventually add a "Power" button, but for now, all we have is a "Reset" button
|
||||
*/
|
||||
Component.bindComponentControls(computer, eComputer, C1PJS.APPCLASS);
|
||||
|
||||
/*
|
||||
* "Power" the computer automatically
|
||||
*/
|
||||
C1PComputer.power(computer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize every Computer on the page.
|
||||
*/
|
||||
web.onInit(C1PComputer.init);
|
||||
Web.onInit(C1PComputer.init);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -29,100 +29,107 @@
|
|||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var Web = require("../../shared/es6/weblib");
|
||||
var Component = require("../../shared/es6/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* C1PPanel(parmsPanel)
|
||||
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
|
||||
* which would force us to declare all class properties in the constructor, as well as prevent
|
||||
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
|
||||
*
|
||||
* The Panel component has no required (parmsPanel) properties.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @unrestricted
|
||||
*/
|
||||
function C1PPanel(parmsPanel)
|
||||
{
|
||||
Component.call(this, "C1PPanel", parmsPanel);
|
||||
class C1PPanel extends Component {
|
||||
/**
|
||||
* C1PPanel(parmsPanel)
|
||||
*
|
||||
* The Panel component has no required (parmsPanel) properties.
|
||||
*
|
||||
* @this {C1PPanel}
|
||||
* @param {Object} parmsPanel
|
||||
*/
|
||||
constructor(parmsPanel)
|
||||
{
|
||||
super("C1PPanel", parmsPanel);
|
||||
|
||||
this.flags.powered = false;
|
||||
}
|
||||
|
||||
Component.subclass(C1PPanel);
|
||||
|
||||
/**
|
||||
* The Panel doesn't have any bindings of its own; it passes along all binding requests to
|
||||
* the Computer, CPU, Keyboard and Debugger components. The order shouldn't matter, since any
|
||||
* component that doesn't recognize the specified binding should simply ignore it.
|
||||
*
|
||||
* @this {C1PPanel}
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @param {string} [sValue] optional data value
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
C1PPanel.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
||||
{
|
||||
if (this.cmp && this.cmp.setBinding(sHTMLType, sBinding, control, sValue)) return true;
|
||||
if (this.cpu && this.cpu.setBinding(sHTMLType, sBinding, control, sValue)) return true;
|
||||
if (this.kbd && this.kbd.setBinding(sHTMLType, sBinding, control, sValue)) return true;
|
||||
if (DEBUGGER && this.dbg && this.dbg.setBinding(sHTMLType, sBinding, control, sValue)) return true;
|
||||
return Component.prototype.setBinding.call(this, sHTMLType, sBinding, control, sValue);
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PPanel}
|
||||
* @param {boolean} fOn
|
||||
* @param {C1PComputer} cmp
|
||||
*/
|
||||
C1PPanel.prototype.setPower = function(fOn, cmp)
|
||||
{
|
||||
if (fOn && !this.flags.powered) {
|
||||
this.flags.powered = true;
|
||||
this.cmp = cmp;
|
||||
this.cpu = cmp.getComponentByType("cpu");
|
||||
this.kbd = cmp.getComponentByType("keyboard");
|
||||
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
|
||||
C1PPanel.init();
|
||||
this.flags.powered = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* C1PPanel.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "panel", extracting the
|
||||
* JSON-encoded parameters for the C1PPanel constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PPanel component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*
|
||||
* NOTE: Unlike most other component init() functions, this one is designed to be
|
||||
* called multiple times: once at load time, so that we can binding our print()
|
||||
* function to the panel's output control ASAP, and again when the C1PComputer component
|
||||
* is verifying that all components are ready and invoking their setPower() functions.
|
||||
*
|
||||
* Our setPower() method gives us a second opportunity to notify any components that
|
||||
* that might care (eg, C1PCPU, C1PKeyboard, and C1PDebugger) that we have some controls
|
||||
* they might want to use.
|
||||
*/
|
||||
C1PPanel.init = function()
|
||||
{
|
||||
var fReady = false;
|
||||
var aePanels = Component.getElementsByClass(document, C1PJS.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 C1PPanel(parmsPanel);
|
||||
/**
|
||||
* The Panel doesn't have any bindings of its own; it passes along all binding requests to
|
||||
* the Computer, CPU, Keyboard and Debugger components. The order shouldn't matter, since any
|
||||
* component that doesn't recognize the specified binding should simply ignore it.
|
||||
*
|
||||
* @this {C1PPanel}
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @param {string} [sValue] optional data value
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
setBinding(sHTMLType, sBinding, control, sValue)
|
||||
{
|
||||
if (this.cmp && this.cmp.setBinding(sHTMLType, sBinding, control, sValue)) return true;
|
||||
if (this.cpu && this.cpu.setBinding(sHTMLType, sBinding, control, sValue)) return true;
|
||||
if (this.kbd && this.kbd.setBinding(sHTMLType, sBinding, control, sValue)) return true;
|
||||
if (DEBUGGER && this.dbg && this.dbg.setBinding(sHTMLType, sBinding, control, sValue)) return true;
|
||||
return super.setBinding(sHTMLType, sBinding, control, sValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PPanel}
|
||||
* @param {boolean} fOn
|
||||
* @param {C1PComputer} cmp
|
||||
*/
|
||||
setPower(fOn, cmp)
|
||||
{
|
||||
if (fOn && !this.flags.powered) {
|
||||
this.flags.powered = true;
|
||||
this.cmp = cmp;
|
||||
this.cpu = cmp.getComponentByType("cpu");
|
||||
this.kbd = cmp.getComponentByType("keyboard");
|
||||
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
|
||||
C1PPanel.init();
|
||||
}
|
||||
Component.bindComponentControls(panel, ePanel, C1PJS.APPCLASS);
|
||||
if (fReady) panel.setReady();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* C1PPanel.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "panel", extracting the
|
||||
* JSON-encoded parameters for the C1PPanel constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PPanel component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*
|
||||
* NOTE: Unlike most other component init() functions, this one is designed to be
|
||||
* called multiple times: once at load time, so that we can binding our print()
|
||||
* function to the panel's output control ASAP, and again when the C1PComputer component
|
||||
* is verifying that all components are ready and invoking their setPower() functions.
|
||||
*
|
||||
* Our setPower() method gives us a second opportunity to notify any components that
|
||||
* that might care (eg, C1PCPU, C1PKeyboard, and C1PDebugger) that we have some controls
|
||||
* they might want to use.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var fReady = false;
|
||||
var aePanels = Component.getElementsByClass(document, C1PJS.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 C1PPanel(parmsPanel);
|
||||
}
|
||||
Component.bindComponentControls(panel, ePanel, C1PJS.APPCLASS);
|
||||
if (fReady) panel.setReady();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize every Panel module on the page.
|
||||
*/
|
||||
web.onInit(C1PPanel.init);
|
||||
Web.onInit(C1PPanel.init);
|
||||
|
|
|
|||
|
|
@ -29,66 +29,73 @@
|
|||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var Web = require("../../shared/es6/weblib");
|
||||
var Component = require("../../shared/es6/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* C1PRAM(parmsRAM)
|
||||
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
|
||||
* which would force us to declare all class properties in the constructor, as well as prevent
|
||||
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
|
||||
*
|
||||
* The RAM component expects the following (parmsRAM) properties:
|
||||
*
|
||||
* size: amount of RAM, in bytes
|
||||
*
|
||||
* NOTE: We may make a note of the specified size, but we will not actually allocate
|
||||
* any memory for the RAM; we wait for the Computer object to tell us where our RAM is,
|
||||
* using the setBuffer() method.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @unrestricted
|
||||
*/
|
||||
function C1PRAM(parmsRAM)
|
||||
{
|
||||
Component.call(this, "C1PRAM", parmsRAM);
|
||||
}
|
||||
|
||||
Component.subclass(C1PRAM);
|
||||
|
||||
/**
|
||||
* @this {C1PRAM}
|
||||
* @param {Array} abMemory
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {C1PCPU} cpu
|
||||
*/
|
||||
C1PRAM.prototype.setBuffer = function(abMemory, start, end, cpu)
|
||||
{
|
||||
this.abMem = abMemory;
|
||||
// this.offRAM = start;
|
||||
// this.cbRAM = end - start + 1;
|
||||
this.setReady();
|
||||
};
|
||||
|
||||
/**
|
||||
* C1PRAM.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "ram", extracting the
|
||||
* JSON-encoded parameters for the C1PRAM constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PRAM component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
C1PRAM.init = function()
|
||||
{
|
||||
var aeRAM = Component.getElementsByClass(document, C1PJS.APPCLASS, "ram");
|
||||
for (var iRAM=0; iRAM < aeRAM.length; iRAM++) {
|
||||
var eRAM = aeRAM[iRAM];
|
||||
var parmsRAM = Component.getComponentParms(eRAM);
|
||||
var ram = new C1PRAM(parmsRAM);
|
||||
Component.bindComponentControls(ram, eRAM, C1PJS.APPCLASS);
|
||||
class C1PRAM extends Component {
|
||||
/**
|
||||
* C1PRAM(parmsRAM)
|
||||
*
|
||||
* The RAM component expects the following (parmsRAM) properties:
|
||||
*
|
||||
* size: amount of RAM, in bytes
|
||||
*
|
||||
* NOTE: We may make a note of the specified size, but we will not actually allocate
|
||||
* any memory for the RAM; we wait for the Computer object to tell us where our RAM is,
|
||||
* using the setBuffer() method.
|
||||
*
|
||||
* @this {C1PRAM}
|
||||
* @param {Object} parmsRAM
|
||||
*/
|
||||
constructor(parmsRAM)
|
||||
{
|
||||
super("C1PRAM", parmsRAM);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PRAM}
|
||||
* @param {Array} abMemory
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {C1PCPU} cpu
|
||||
*/
|
||||
setBuffer(abMemory, start, end, cpu)
|
||||
{
|
||||
this.abMem = abMemory;
|
||||
// this.offRAM = start;
|
||||
// this.cbRAM = end - start + 1;
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* C1PRAM.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "ram", extracting the
|
||||
* JSON-encoded parameters for the C1PRAM constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PRAM component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var aeRAM = Component.getElementsByClass(document, C1PJS.APPCLASS, "ram");
|
||||
for (var iRAM=0; iRAM < aeRAM.length; iRAM++) {
|
||||
var eRAM = aeRAM[iRAM];
|
||||
var parmsRAM = Component.getComponentParms(eRAM);
|
||||
var ram = new C1PRAM(parmsRAM);
|
||||
Component.bindComponentControls(ram, eRAM, C1PJS.APPCLASS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize all the RAM modules on the page.
|
||||
*/
|
||||
web.onInit(C1PRAM.init);
|
||||
Web.onInit(C1PRAM.init);
|
||||
|
|
|
|||
|
|
@ -29,215 +29,222 @@
|
|||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var Str = require("../../shared/es6/strlib");
|
||||
var Web = require("../../shared/es6/weblib");
|
||||
var DumpAPI = require("../../shared/es6/dumpapi");
|
||||
var Component = require("../../shared/es6/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* C1PROM(parmsROM)
|
||||
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
|
||||
* which would force us to declare all class properties in the constructor, as well as prevent
|
||||
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
|
||||
*
|
||||
* The ROM component expects the following (parmsROM) properties:
|
||||
*
|
||||
* size: amount of ROM, in bytes
|
||||
* image: name of ROM image file
|
||||
*
|
||||
* NOTE: The final location for the ROM image, once loaded, will be specified
|
||||
* by the Computer object, using the setBuffer() method.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @property {function()} convertImage
|
||||
* @unrestricted
|
||||
*/
|
||||
function C1PROM(parmsROM)
|
||||
{
|
||||
Component.call(this, "C1PROM", parmsROM);
|
||||
|
||||
this.abMem = null;
|
||||
this.abImage = null;
|
||||
this.cbROM = parmsROM['size'];
|
||||
this.sImage = parmsROM['image'];
|
||||
if (this.sImage) {
|
||||
var sFileURL = this.sImage;
|
||||
/**
|
||||
* If the selected ROM image has a ".json" extension, then we assume it's a pre-converted
|
||||
* JSON-encoded ROM image, so we load it as-is; ditto for files with a ".hex" extension. Otherwise,
|
||||
* we ask our server-side ROM image converter to return the corresponding JSON-encoded data,
|
||||
* in compact form (ie, minimal whitespace, no ASCII data comments, etc).
|
||||
*/
|
||||
var sFileExt = str.getExtension(this.sImage);
|
||||
if (sFileExt != DumpAPI.FORMAT.JSON && sFileExt != DumpAPI.FORMAT.HEX) {
|
||||
sFileURL = web.getHost() + DumpAPI.ENDPOINT + '?' + DumpAPI.QUERY.FILE + '=' + this.sImage + '&' + DumpAPI.QUERY.FORMAT + '=' + DumpAPI.FORMAT.BYTES;
|
||||
}
|
||||
var rom = this;
|
||||
web.getResource(sFileURL, null, true, function(sURL, sResponse, nErrorCode) {
|
||||
rom.convertImage(sURL, sResponse, nErrorCode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Component.subclass(C1PROM);
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {Array} abMemory
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {C1PCPU} cpu
|
||||
*/
|
||||
C1PROM.prototype.setBuffer = function(abMemory, start, end, cpu)
|
||||
{
|
||||
this.abMem = abMemory;
|
||||
this.offROM = start;
|
||||
var cbROM = end - start + 1;
|
||||
/*
|
||||
* It's possible that the ROM component didn't specify a size,
|
||||
* in which case just use the size the Computer component has specified.
|
||||
class C1PROM extends Component {
|
||||
/**
|
||||
* C1PROM(parmsROM)
|
||||
*
|
||||
* The ROM component expects the following (parmsROM) properties:
|
||||
*
|
||||
* size: amount of ROM, in bytes
|
||||
* image: name of ROM image file
|
||||
*
|
||||
* NOTE: The final location for the ROM image, once loaded, will be specified
|
||||
* by the Computer object, using the setBuffer() method.
|
||||
*
|
||||
* @this {C1PROM}
|
||||
* @param {Object} parmsROM
|
||||
* @property {function()} convertImage
|
||||
*/
|
||||
if (!this.cbROM)
|
||||
this.cbROM = cbROM;
|
||||
if (cbROM != this.cbROM) {
|
||||
this.setError("computer-specified ROM size (" + str.toHexWord(cbROM) + ") does not match component-specified size (" + str.toHexWord(this.cbROM) + ")");
|
||||
return;
|
||||
}
|
||||
if (cpu) {
|
||||
this.cpu = cpu;
|
||||
cpu.addWriteNotify(start, end, this, this.setByte);
|
||||
}
|
||||
this.copyImage();
|
||||
};
|
||||
constructor(parmsROM)
|
||||
{
|
||||
super("C1PROM", parmsROM);
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {boolean} fOn
|
||||
* @param {C1PComputer} cmp
|
||||
*/
|
||||
C1PROM.prototype.setPower = function(fOn, cmp)
|
||||
{
|
||||
if (fOn && !this.flags.powered) {
|
||||
this.flags.powered = true;
|
||||
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {number} addr
|
||||
* @param {number|undefined} [addrFrom]
|
||||
*/
|
||||
C1PROM.prototype.setByte = function(addr, addrFrom)
|
||||
{
|
||||
/*
|
||||
* Beyond reporting this write, we need to "repair" the ROM, using the original image data,
|
||||
* but only if addrFrom is defined (undefined implies this is a write from the Debugger, and
|
||||
* we need to allow the Debugger to modify ROM contents).
|
||||
*/
|
||||
if (addrFrom !== undefined) {
|
||||
if (DEBUGGER && this.dbg) this.dbg.messageIO(this, addr, addrFrom, this.dbg.MESSAGE_PORT, true);
|
||||
var offset = (addr - this.offROM);
|
||||
Component.assert(offset >= 0 && offset < this.cbROM);
|
||||
if (!this.abImage)
|
||||
this.abMem[this.offROM + offset] = 0;
|
||||
else
|
||||
this.abMem[this.offROM + offset] = this.abImage[offset];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {string} sImageName
|
||||
* @param {string} sImageData
|
||||
* @param {number} nErrorCode (response from server if anything other than 200)
|
||||
*/
|
||||
C1PROM.prototype.convertImage = function(sImageName, sImageData, nErrorCode)
|
||||
{
|
||||
if (nErrorCode) {
|
||||
this.println("Error loading ROM \"" + sImageName + "\" (" + nErrorCode + ")");
|
||||
return;
|
||||
}
|
||||
if (sImageData.charAt(0) == "[" || sImageData.charAt(0) == "{") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be here: parsing the JSON-encoded ROM data.
|
||||
this.abMem = null;
|
||||
this.abImage = null;
|
||||
this.cbROM = parmsROM['size'];
|
||||
this.sImage = parmsROM['image'];
|
||||
if (this.sImage) {
|
||||
var sFileURL = this.sImage;
|
||||
/**
|
||||
* If the selected ROM image has a ".json" extension, then we assume it's a pre-converted
|
||||
* JSON-encoded ROM image, so we load it as-is; ditto for files with a ".hex" extension. Otherwise,
|
||||
* we ask our server-side ROM image converter to return the corresponding JSON-encoded data,
|
||||
* in compact form (ie, minimal whitespace, no ASCII data comments, etc).
|
||||
*/
|
||||
var rom = eval("(" + sImageData + ")");
|
||||
var ab = rom['bytes'];
|
||||
if (ab) {
|
||||
this.abImage = ab;
|
||||
} else {
|
||||
this.abImage = rom;
|
||||
var sFileExt = Str.getExtension(this.sImage);
|
||||
if (sFileExt != DumpAPI.FORMAT.JSON && sFileExt != DumpAPI.FORMAT.HEX) {
|
||||
sFileURL = Web.getHost() + DumpAPI.ENDPOINT + '?' + DumpAPI.QUERY.FILE + '=' + this.sImage + '&' + DumpAPI.QUERY.FORMAT + '=' + DumpAPI.FORMAT.BYTES;
|
||||
}
|
||||
} catch (e) {
|
||||
this.println("Error processing ROM \"" + sImageName + "\": " + e.message);
|
||||
var rom = this;
|
||||
Web.getResource(sFileURL, null, true, function(sURL, sResponse, nErrorCode) {
|
||||
rom.convertImage(sURL, sResponse, nErrorCode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {Array} abMemory
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {C1PCPU} cpu
|
||||
*/
|
||||
setBuffer(abMemory, start, end, cpu)
|
||||
{
|
||||
this.abMem = abMemory;
|
||||
this.offROM = start;
|
||||
var cbROM = end - start + 1;
|
||||
/*
|
||||
* It's possible that the ROM component didn't specify a size,
|
||||
* in which case just use the size the Computer component has specified.
|
||||
*/
|
||||
if (!this.cbROM)
|
||||
this.cbROM = cbROM;
|
||||
if (cbROM != this.cbROM) {
|
||||
this.setError("computer-specified ROM size (" + Str.toHexWord(cbROM) + ") does not match component-specified size (" + Str.toHexWord(this.cbROM) + ")");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Parse the ROM image data manually; we assume it's in "simplified" hex form (a series of hex byte-values separated by whitespace)
|
||||
*/
|
||||
var sData = sImageData.replace(/\n/gm, " ").replace(/ +$/, "");
|
||||
var asData = sData.split(" ");
|
||||
this.abImage = new Array(asData.length);
|
||||
for (var i=0; i < asData.length; i++) {
|
||||
this.abImage[i] = parseInt(asData[i], 16);
|
||||
if (cpu) {
|
||||
this.cpu = cpu;
|
||||
cpu.addWriteNotify(start, end, this, this.setByte);
|
||||
}
|
||||
this.copyImage();
|
||||
}
|
||||
this.copyImage();
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
*/
|
||||
C1PROM.prototype.copyImage = function()
|
||||
{
|
||||
/*
|
||||
* The Computer object may give us the address of the ROM image before we've finished downloading the image,
|
||||
* so both setBuffer() and convertImage() call copyImage(), which in turn will copy the image ONLY when both
|
||||
* pieces are in place. At that point, the component becomes "ready", in much the same way that other components
|
||||
* (eg, CPU and Screen) become "ready" when all their prerequisites are satisfied.
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {boolean} fOn
|
||||
* @param {C1PComputer} cmp
|
||||
*/
|
||||
if (!this.isReady()) {
|
||||
if (!this.sImage) {
|
||||
this.setReady();
|
||||
setPower(fOn, cmp)
|
||||
{
|
||||
if (fOn && !this.flags.powered) {
|
||||
this.flags.powered = true;
|
||||
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
|
||||
}
|
||||
else
|
||||
if (this.abImage && this.abMem) {
|
||||
var cbImage = this.abImage.length;
|
||||
if (cbImage != this.cbROM) {
|
||||
this.setError("ROM image size (" + str.toHexWord(cbImage) + ") does not match component-specified size (" + str.toHexWord(this.cbROM) + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {number} addr
|
||||
* @param {number|undefined} [addrFrom]
|
||||
*/
|
||||
setByte(addr, addrFrom)
|
||||
{
|
||||
/*
|
||||
* Beyond reporting this write, we need to "repair" the ROM, using the original image data,
|
||||
* but only if addrFrom is defined (undefined implies this is a write from the Debugger, and
|
||||
* we need to allow the Debugger to modify ROM contents).
|
||||
*/
|
||||
if (addrFrom !== undefined) {
|
||||
if (DEBUGGER && this.dbg) this.dbg.messageIO(this, addr, addrFrom, this.dbg.MESSAGE_PORT, true);
|
||||
var offset = (addr - this.offROM);
|
||||
Component.assert(offset >= 0 && offset < this.cbROM);
|
||||
if (!this.abImage)
|
||||
this.abMem[this.offROM + offset] = 0;
|
||||
else
|
||||
this.abMem[this.offROM + offset] = this.abImage[offset];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {string} sImageName
|
||||
* @param {string} sImageData
|
||||
* @param {number} nErrorCode (response from server if anything other than 200)
|
||||
*/
|
||||
convertImage(sImageName, sImageData, nErrorCode)
|
||||
{
|
||||
if (nErrorCode) {
|
||||
this.println("Error loading ROM \"" + sImageName + "\" (" + nErrorCode + ")");
|
||||
return;
|
||||
}
|
||||
if (sImageData.charAt(0) == "[" || sImageData.charAt(0) == "{") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be here: parsing the JSON-encoded ROM data.
|
||||
*/
|
||||
var rom = eval("(" + sImageData + ")");
|
||||
var ab = rom['bytes'];
|
||||
if (ab) {
|
||||
this.abImage = ab;
|
||||
} else {
|
||||
this.abImage = rom;
|
||||
}
|
||||
} catch (e) {
|
||||
this.println("Error processing ROM \"" + sImageName + "\": " + e.message);
|
||||
return;
|
||||
}
|
||||
if (DEBUG) this.log("copyImage(): copying ROM to " + str.toHexWord(this.offROM) + " (" + str.toHexWord(cbImage) + " bytes)");
|
||||
for (var i=0; i < cbImage; i++) {
|
||||
this.abMem[this.offROM + i] = this.abImage[i];
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Parse the ROM image data manually; we assume it's in "simplified" hex form (a series of hex byte-values separated by whitespace)
|
||||
*/
|
||||
var sData = sImageData.replace(/\n/gm, " ").replace(/ +$/, "");
|
||||
var asData = sData.split(" ");
|
||||
this.abImage = new Array(asData.length);
|
||||
for (var i=0; i < asData.length; i++) {
|
||||
this.abImage[i] = parseInt(asData[i], 16);
|
||||
}
|
||||
}
|
||||
this.copyImage();
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
*/
|
||||
copyImage()
|
||||
{
|
||||
/*
|
||||
* The Computer object may give us the address of the ROM image before we've finished downloading the image,
|
||||
* so both setBuffer() and convertImage() call copyImage(), which in turn will copy the image ONLY when both
|
||||
* pieces are in place. At that point, the component becomes "ready", in much the same way that other components
|
||||
* (eg, CPU and Screen) become "ready" when all their prerequisites are satisfied.
|
||||
*/
|
||||
if (!this.isReady()) {
|
||||
if (!this.sImage) {
|
||||
this.setReady();
|
||||
}
|
||||
else
|
||||
if (this.abImage && this.abMem) {
|
||||
var cbImage = this.abImage.length;
|
||||
if (cbImage != this.cbROM) {
|
||||
this.setError("ROM image size (" + Str.toHexWord(cbImage) + ") does not match component-specified size (" + Str.toHexWord(this.cbROM) + ")");
|
||||
return;
|
||||
}
|
||||
if (DEBUG) this.log("copyImage(): copying ROM to " + Str.toHexWord(this.offROM) + " (" + Str.toHexWord(cbImage) + " bytes)");
|
||||
for (var i=0; i < cbImage; i++) {
|
||||
this.abMem[this.offROM + i] = this.abImage[i];
|
||||
}
|
||||
this.setReady();
|
||||
}
|
||||
this.setReady();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* C1PROM.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "rom", extracting the
|
||||
* JSON-encoded parameters for the C1PROM constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PROM component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
C1PROM.init = function()
|
||||
{
|
||||
var aeROM = Component.getElementsByClass(document, C1PJS.APPCLASS, "rom");
|
||||
for (var iROM=0; iROM < aeROM.length; iROM++) {
|
||||
var eROM = aeROM[iROM];
|
||||
var parmsROM = Component.getComponentParms(eROM);
|
||||
var rom = new C1PROM(parmsROM);
|
||||
Component.bindComponentControls(rom, eROM, C1PJS.APPCLASS);
|
||||
/**
|
||||
* C1PROM.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "rom", extracting the
|
||||
* JSON-encoded parameters for the C1PROM constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PROM component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var aeROM = Component.getElementsByClass(document, C1PJS.APPCLASS, "rom");
|
||||
for (var iROM=0; iROM < aeROM.length; iROM++) {
|
||||
var eROM = aeROM[iROM];
|
||||
var parmsROM = Component.getComponentParms(eROM);
|
||||
var rom = new C1PROM(parmsROM);
|
||||
Component.bindComponentControls(rom, eROM, C1PJS.APPCLASS);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize all the ROM modules on the page.
|
||||
*/
|
||||
web.onInit(C1PROM.init);
|
||||
Web.onInit(C1PROM.init);
|
||||
|
|
|
|||
|
|
@ -29,31 +29,411 @@
|
|||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var Str = require("../../shared/es6/strlib");
|
||||
var Web = require("../../shared/es6/weblib");
|
||||
var Component = require("../../shared/es6/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* C1PSerialPort(parmsSerial)
|
||||
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
|
||||
* which would force us to declare all class properties in the constructor, as well as prevent
|
||||
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
|
||||
*
|
||||
* The SerialPort component has no component-specific parameters.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @unrestricted
|
||||
*/
|
||||
function C1PSerialPort(parmsSerial)
|
||||
{
|
||||
Component.call(this, "C1PSerialPort", parmsSerial);
|
||||
class C1PSerialPort extends Component {
|
||||
/**
|
||||
* C1PSerialPort(parmsSerial)
|
||||
*
|
||||
* The SerialPort component has no component-specific parameters.
|
||||
*
|
||||
* @this {C1PSerialPort}
|
||||
* @param {Object} parmsSerial
|
||||
*/
|
||||
constructor(parmsSerial)
|
||||
{
|
||||
super("C1PSerialPort", parmsSerial);
|
||||
|
||||
this.flags.powered = false;
|
||||
this.fDemo = parmsSerial['demo'];
|
||||
this.flags.powered = false;
|
||||
this.fDemo = parmsSerial['demo'];
|
||||
|
||||
this.reset(true);
|
||||
this.reset(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {boolean} [fHard]
|
||||
*/
|
||||
reset(fHard)
|
||||
{
|
||||
/*
|
||||
* Because we reset the machine at the start of a 6502 HEX command file auto-load,
|
||||
* we must avoid tossing the serial port's input buffer in that particular case (2).
|
||||
*/
|
||||
if (fHard || this.autoLoad != C1PSerialPort.AUTOLOAD_6502) {
|
||||
|
||||
this.bInput = -1;
|
||||
this.iInput = 0;
|
||||
this.sInput = "";
|
||||
if (this.fDemo) {
|
||||
this.sInput = "10 PRINT \"HELLO OSI #" + this.getMachineNum() + "\"\n";
|
||||
}
|
||||
|
||||
// this.sOutput = new Array(0);
|
||||
// this.iOutputNext = 0;
|
||||
|
||||
this.fConvertLF = true;
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
start()
|
||||
{
|
||||
if (this.kbd && this.fDemo) {
|
||||
this.kbd.injectKeys(" C\n\n", 3000); // override the default injection delay (currently 300ms)
|
||||
setTimeout(function(serial) { return function() {serial.startLoad();}; }(this), 12000);
|
||||
}
|
||||
this.fDemo = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listSerial")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @param {string} [sValue] optional data value
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
setBinding(sHTMLType, sBinding, control, sValue)
|
||||
{
|
||||
var serial = this;
|
||||
|
||||
switch(sBinding) {
|
||||
|
||||
case "listSerial":
|
||||
this.bindings[sBinding] = control;
|
||||
return true;
|
||||
|
||||
case "loadSerial":
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
control.onclick = function(event) {
|
||||
if (serial.bindings["listSerial"]) {
|
||||
var sFile = serial.bindings["listSerial"].value;
|
||||
// serial.println("loading " + sFile + "...");
|
||||
Web.getResource(sFile, null, true, function(sURL, sResponse, nErrorCode) {
|
||||
serial.loadFile(sURL, sResponse, nErrorCode);
|
||||
});
|
||||
}
|
||||
};
|
||||
return true;
|
||||
|
||||
case "mountSerial":
|
||||
/*
|
||||
* Check for non-mobile (desktop) browser and the availability of FileReader
|
||||
*/
|
||||
if (!Web.isMobile() && window && 'FileReader' in window) {
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
/*
|
||||
* Enable "Mount" button only if a file is actually selected
|
||||
*/
|
||||
control.addEventListener('change', function() {
|
||||
var fieldset = control.children[0];
|
||||
var files = fieldset.children[0].files;
|
||||
var submit = fieldset.children[1];
|
||||
submit.disabled = !files.length;
|
||||
});
|
||||
|
||||
control.onsubmit = function(event) {
|
||||
var file = event.currentTarget[1].files[0];
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
// serial.println("mounting " + file.name + "...");
|
||||
serial.loadFile(file.name, reader.result.toString(), 0);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
|
||||
/*
|
||||
* Prevent reloading of web page after form submission
|
||||
*/
|
||||
return false;
|
||||
};
|
||||
}
|
||||
else {
|
||||
if (DEBUG) this.log("Local file support not available");
|
||||
control.parentNode.removeChild(/** @type {Node} */ (control));
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {Array} abMemory
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {C1PCPU} cpu
|
||||
*/
|
||||
setBuffer(abMemory, start, end, cpu)
|
||||
{
|
||||
this.abMem = abMemory;
|
||||
this.offPort = start;
|
||||
this.cbPort = end - start + 1;
|
||||
this.offPortLimit = this.offPort + this.cbPort;
|
||||
if ((this.cpu = cpu)) {
|
||||
cpu.addReadNotify(start, end, this, this.getByte);
|
||||
cpu.addWriteNotify(start, end, this, this.setByte);
|
||||
}
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {boolean} fOn
|
||||
* @param {C1PComputer} cmp
|
||||
*
|
||||
* We make a note of the Computer component, so that we can invoke its reset() method whenever we need to
|
||||
* simulate a warm start, and we query the Keyboard component so that we can use its injectKeys() function.
|
||||
*/
|
||||
setPower(fOn, cmp)
|
||||
{
|
||||
if (fOn && !this.flags.powered) {
|
||||
this.flags.powered = true;
|
||||
this.cmp = cmp;
|
||||
this.kbd = cmp.getComponentByType("keyboard");
|
||||
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
startLoad()
|
||||
{
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_BASIC;
|
||||
this.kbd.injectKeys("LOAD\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {string} sFileName
|
||||
* @param {string} sFileData (null if getResource() encountered an error)
|
||||
* @param {number} nResponse from server
|
||||
*/
|
||||
loadFile(sFileName, sFileData, nResponse)
|
||||
{
|
||||
if (!sFileData) {
|
||||
this.println("Error loading file \"" + sFileName + "\" (" + nResponse + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
this.iInput = 0;
|
||||
this.sInput = sFileData;
|
||||
this.fConvertLF = true;
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_NONE;
|
||||
|
||||
/*
|
||||
* The following code adds support for loading "65V" files encoded as JSON, which is a cleaner
|
||||
* way to store and deliver those files when they contain binary (non-ASCII) data.
|
||||
*
|
||||
* For example, my 6502 ASSEMBLER/DISASSEMBLER program starts with a conventional "65V" loading
|
||||
* sequence, which loads and launches a small program loader that loads the rest of the program
|
||||
* using a raw (1-to-1) binary format instead of the usual (3-to-1) HEX format used by "65V" files.
|
||||
*
|
||||
* The "rawness" of the binary format also necessitates disabling fConvertLF.
|
||||
*/
|
||||
if (Str.endsWith(sFileName, ".json")) {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be here: parsing the JSON-encoded data.
|
||||
*/
|
||||
var s = "";
|
||||
var data = eval("(" + sFileData + ")");
|
||||
var ab = data['bytes'];
|
||||
for (var i = 0; i < ab.length; i++) {
|
||||
s += String.fromCharCode(ab[i]);
|
||||
}
|
||||
this.sInput = s;
|
||||
this.fConvertLF = false;
|
||||
} catch (e) {
|
||||
this.println("Error processing file \"" + sFileName + "\": " + e.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.cmp && this.kbd && this.cpu.isRunning()) {
|
||||
this.println("auto-loading " + sFileName);
|
||||
/*
|
||||
* QUESTION: Is this setFocus() call strictly necessary? We're being called in the
|
||||
* context of getResource(), not some user action. If there was an original user action,
|
||||
* then the handler for THAT action should take care to switch focus back, not us.
|
||||
*/
|
||||
this.cpu.setFocus();
|
||||
/*
|
||||
* We interpret the presence of a "." at the beginning of the file as a "65V Monitor"
|
||||
* address-mode command, and consequently treat the file as 6502 HEX command file.
|
||||
*
|
||||
* Anything else is treated as commands for the BASIC interpreter, which we re-initialize
|
||||
* with "NEW" and "LOAD" commands. To prevent that behavior, halt the CPU, perform the load,
|
||||
* and then start it running again. BASIC will start reading the data as soon as you type
|
||||
* LOAD.
|
||||
*/
|
||||
if (this.sInput.charAt(0) != '.') {
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_BASIC;
|
||||
this.kbd.injectKeys("NEW\nLOAD\n");
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Set autoLoad to AUTOLOAD_6502 before the reset, so that when our reset() method is called,
|
||||
* we'll take care to preserve all the data we just loaded.
|
||||
*/
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_6502;
|
||||
/*
|
||||
* Although the Keyboard allows us to inject any key, even the BREAK key, like so:
|
||||
*
|
||||
* this.kbd.injectKeys(String.fromCharCode(this.kbd.CHARCODE_BREAK))
|
||||
*
|
||||
* it's easier to initiate a reset() ourselves and then start the machine-language load process
|
||||
*/
|
||||
this.cmp.reset(true);
|
||||
this.kbd.injectKeys("ML");
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.println(sFileName + " ready to load");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {number} addr
|
||||
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to read the specified addr)
|
||||
*/
|
||||
getByte(addr, addrFrom)
|
||||
{
|
||||
/*
|
||||
* Don't trigger any further hardware emulation (beyond what we've already stored in memory) if
|
||||
* the Debugger performed this read (need a special Debugger I/O command if/when you really want to do that).
|
||||
*/
|
||||
if (addrFrom !== undefined) {
|
||||
/*
|
||||
* WARNING: All I need to do for now is load the COM interface's "data byte"
|
||||
* with the next byte from the virtual cassette data stream -JP
|
||||
*/
|
||||
if (!(addr & 0x01)) {
|
||||
/*
|
||||
* An EVEN address implies they're looking, so if we have a fresh buffer,
|
||||
* then prime the pump.
|
||||
*/
|
||||
if (this.sInput && !this.iInput)
|
||||
this.advanceInput();
|
||||
} else {
|
||||
/*
|
||||
* An ODD address implies they just grabbed a data byte, so prep the next data byte.
|
||||
*/
|
||||
this.advanceInput();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {number} addr
|
||||
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to write the specified addr)
|
||||
*/
|
||||
setByte(addr, addrFrom)
|
||||
{
|
||||
/*
|
||||
* Don't trigger any further hardware emulation (beyond what we've already stored in memory) if
|
||||
* the Debugger performed this write (need a special Debugger I/O command if/when you really want to do that).
|
||||
*/
|
||||
if (addrFrom !== undefined) {
|
||||
if (DEBUGGER && this.dbg) this.dbg.messageIO(this, addr, addrFrom, this.dbg.MESSAGE_SERIAL, true);
|
||||
/*
|
||||
* WARNING: I don't yet care what state the CPU puts the port into. When it's time to support serial output,
|
||||
* obviously that will become an issue.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
advanceInput()
|
||||
{
|
||||
if (this.sInput !== undefined) {
|
||||
this.bInput = -1;
|
||||
if (this.iInput < this.sInput.length) {
|
||||
var b = this.sInput.charCodeAt(this.iInput++) & 0xff;
|
||||
if (this.fConvertLF) {
|
||||
if (b == 0x0a) b = 0x0d;
|
||||
}
|
||||
this.bInput = b;
|
||||
// if (DEBUG) this.log("advanceInput(" + Str.toHexByte(b) + ")");
|
||||
}
|
||||
else {
|
||||
this.sInput = "";
|
||||
this.iInput = 0;
|
||||
if (DEBUG) this.log("advanceInput(): out of data");
|
||||
if (this.autoLoad == C1PSerialPort.AUTOLOAD_BASIC && this.kbd) {
|
||||
this.kbd.injectKeys(" \nRUN\n");
|
||||
}
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_NONE;
|
||||
}
|
||||
this.updateMemory();
|
||||
}
|
||||
// else if (DEBUG) this.log("advanceInput(): no input");
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
updateMemory()
|
||||
{
|
||||
var offset;
|
||||
/*
|
||||
* Update all the status (even) bytes
|
||||
*/
|
||||
for (offset = this.offPort+0; offset < this.offPortLimit; offset+=2) {
|
||||
this.abMem[offset] = (this.bInput >= 0? C1PSerialPort.STATUS_DATA : C1PSerialPort.STATUS_NONE);
|
||||
}
|
||||
/*
|
||||
* Update all the data (odd) bytes
|
||||
*/
|
||||
for (offset = this.offPort+1; offset < this.offPortLimit; offset+=2) {
|
||||
this.abMem[offset] = (this.bInput >= 0? this.bInput : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* C1PSerialPort.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "serial", extracting the
|
||||
* JSON-encoded parameters for the C1PSerialPort constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PSerialPort component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var aeSerial = Component.getElementsByClass(document, C1PJS.APPCLASS, "serial");
|
||||
for (var iSerial=0; iSerial < aeSerial.length; iSerial++) {
|
||||
var eSerial = aeSerial[iSerial];
|
||||
var parmsSerial = Component.getComponentParms(eSerial);
|
||||
var serial = new C1PSerialPort(parmsSerial);
|
||||
Component.bindComponentControls(serial, eSerial, C1PJS.APPCLASS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.subclass(C1PSerialPort);
|
||||
|
||||
C1PSerialPort.STATUS_NONE = 0x00;
|
||||
C1PSerialPort.STATUS_DATA = 0x01; // indicates data available
|
||||
|
||||
|
|
@ -68,380 +448,7 @@ C1PSerialPort.AUTOLOAD_NONE = 0;
|
|||
C1PSerialPort.AUTOLOAD_BASIC = 1;
|
||||
C1PSerialPort.AUTOLOAD_6502 = 2;
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {boolean} [fHard]
|
||||
*/
|
||||
C1PSerialPort.prototype.reset = function(fHard)
|
||||
{
|
||||
/*
|
||||
* Because we reset the machine at the start of a 6502 HEX command file auto-load,
|
||||
* we must avoid tossing the serial port's input buffer in that particular case (2).
|
||||
*/
|
||||
if (fHard || this.autoLoad != C1PSerialPort.AUTOLOAD_6502) {
|
||||
|
||||
this.bInput = -1;
|
||||
this.iInput = 0;
|
||||
this.sInput = "";
|
||||
if (this.fDemo) {
|
||||
this.sInput = "10 PRINT \"HELLO OSI #" + this.getMachineNum() + "\"\n";
|
||||
}
|
||||
|
||||
// this.sOutput = new Array(0);
|
||||
// this.iOutputNext = 0;
|
||||
|
||||
this.fConvertLF = true;
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_NONE;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
C1PSerialPort.prototype.start = function()
|
||||
{
|
||||
if (this.kbd && this.fDemo) {
|
||||
this.kbd.injectKeys(" C\n\n", 3000); // override the default injection delay (currently 300ms)
|
||||
setTimeout(function(serial) { return function() {serial.startLoad();}; }(this), 12000);
|
||||
}
|
||||
this.fDemo = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listSerial")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @param {string} [sValue] optional data value
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
C1PSerialPort.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
||||
{
|
||||
var serial = this;
|
||||
|
||||
switch(sBinding) {
|
||||
|
||||
case "listSerial":
|
||||
this.bindings[sBinding] = control;
|
||||
return true;
|
||||
|
||||
case "loadSerial":
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
control.onclick = function(event) {
|
||||
if (serial.bindings["listSerial"]) {
|
||||
var sFile = serial.bindings["listSerial"].value;
|
||||
// serial.println("loading " + sFile + "...");
|
||||
web.getResource(sFile, null, true, function(sURL, sResponse, nErrorCode) {
|
||||
serial.loadFile(sURL, sResponse, nErrorCode);
|
||||
});
|
||||
}
|
||||
};
|
||||
return true;
|
||||
|
||||
case "mountSerial":
|
||||
/*
|
||||
* Check for non-mobile (desktop) browser and the availability of FileReader
|
||||
*/
|
||||
if (!web.isMobile() && window && 'FileReader' in window) {
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
/*
|
||||
* Enable "Mount" button only if a file is actually selected
|
||||
*/
|
||||
control.addEventListener('change', function() {
|
||||
var fieldset = control.children[0];
|
||||
var files = fieldset.children[0].files;
|
||||
var submit = fieldset.children[1];
|
||||
submit.disabled = !files.length;
|
||||
});
|
||||
|
||||
control.onsubmit = function(event) {
|
||||
var file = event.currentTarget[1].files[0];
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
// serial.println("mounting " + file.name + "...");
|
||||
serial.loadFile(file.name, reader.result.toString(), 0);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
|
||||
/*
|
||||
* Prevent reloading of web page after form submission
|
||||
*/
|
||||
return false;
|
||||
};
|
||||
}
|
||||
else {
|
||||
if (DEBUG) this.log("Local file support not available");
|
||||
control.parentNode.removeChild(/** @type {Node} */ (control));
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {Array} abMemory
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {C1PCPU} cpu
|
||||
*/
|
||||
C1PSerialPort.prototype.setBuffer = function(abMemory, start, end, cpu)
|
||||
{
|
||||
this.abMem = abMemory;
|
||||
this.offPort = start;
|
||||
this.cbPort = end - start + 1;
|
||||
this.offPortLimit = this.offPort + this.cbPort;
|
||||
if ((this.cpu = cpu)) {
|
||||
cpu.addReadNotify(start, end, this, this.getByte);
|
||||
cpu.addWriteNotify(start, end, this, this.setByte);
|
||||
}
|
||||
this.setReady();
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {boolean} fOn
|
||||
* @param {C1PComputer} cmp
|
||||
*
|
||||
* We make a note of the Computer component, so that we can invoke its reset() method whenever we need to
|
||||
* simulate a warm start, and we query the Keyboard component so that we can use its injectKeys() function.
|
||||
*/
|
||||
C1PSerialPort.prototype.setPower = function(fOn, cmp)
|
||||
{
|
||||
if (fOn && !this.flags.powered) {
|
||||
this.flags.powered = true;
|
||||
this.cmp = cmp;
|
||||
this.kbd = cmp.getComponentByType("keyboard");
|
||||
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
C1PSerialPort.prototype.startLoad = function()
|
||||
{
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_BASIC;
|
||||
this.kbd.injectKeys("LOAD\n");
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {string} sFileName
|
||||
* @param {string} sFileData (null if getResource() encountered an error)
|
||||
* @param {number} nResponse from server
|
||||
*/
|
||||
C1PSerialPort.prototype.loadFile = function(sFileName, sFileData, nResponse)
|
||||
{
|
||||
if (!sFileData) {
|
||||
this.println("Error loading file \"" + sFileName + "\" (" + nResponse + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
this.iInput = 0;
|
||||
this.sInput = sFileData;
|
||||
this.fConvertLF = true;
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_NONE;
|
||||
|
||||
/*
|
||||
* The following code adds support for loading "65V" files encoded as JSON, which is a cleaner
|
||||
* way to store and deliver those files when they contain binary (non-ASCII) data.
|
||||
*
|
||||
* For example, my 6502 ASSEMBLER/DISASSEMBLER program starts with a conventional "65V" loading
|
||||
* sequence, which loads and launches a small program loader that loads the rest of the program
|
||||
* using a raw (1-to-1) binary format instead of the usual (3-to-1) HEX format used by "65V" files.
|
||||
*
|
||||
* The "rawness" of the binary format also necessitates disabling fConvertLF.
|
||||
*/
|
||||
if (str.endsWith(sFileName, ".json")) {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be here: parsing the JSON-encoded data.
|
||||
*/
|
||||
var s = "";
|
||||
var data = eval("(" + sFileData + ")");
|
||||
var ab = data['bytes'];
|
||||
for (var i = 0; i < ab.length; i++) {
|
||||
s += String.fromCharCode(ab[i]);
|
||||
}
|
||||
this.sInput = s;
|
||||
this.fConvertLF = false;
|
||||
} catch (e) {
|
||||
this.println("Error processing file \"" + sFileName + "\": " + e.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.cmp && this.kbd && this.cpu.isRunning()) {
|
||||
this.println("auto-loading " + sFileName);
|
||||
/*
|
||||
* QUESTION: Is this setFocus() call strictly necessary? We're being called in the
|
||||
* context of getResource(), not some user action. If there was an original user action,
|
||||
* then the handler for THAT action should take care to switch focus back, not us.
|
||||
*/
|
||||
this.cpu.setFocus();
|
||||
/*
|
||||
* We interpret the presence of a "." at the beginning of the file as a "65V Monitor"
|
||||
* address-mode command, and consequently treat the file as 6502 HEX command file.
|
||||
*
|
||||
* Anything else is treated as commands for the BASIC interpreter, which we re-initialize
|
||||
* with "NEW" and "LOAD" commands. To prevent that behavior, halt the CPU, perform the load,
|
||||
* and then start it running again. BASIC will start reading the data as soon as you type
|
||||
* LOAD.
|
||||
*/
|
||||
if (this.sInput.charAt(0) != '.') {
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_BASIC;
|
||||
this.kbd.injectKeys("NEW\nLOAD\n");
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Set autoLoad to AUTOLOAD_6502 before the reset, so that when our reset() method is called,
|
||||
* we'll take care to preserve all the data we just loaded.
|
||||
*/
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_6502;
|
||||
/*
|
||||
* Although the Keyboard allows us to inject any key, even the BREAK key, like so:
|
||||
*
|
||||
* this.kbd.injectKeys(String.fromCharCode(this.kbd.CHARCODE_BREAK))
|
||||
*
|
||||
* it's easier to initiate a reset() ourselves and then start the machine-language load process
|
||||
*/
|
||||
this.cmp.reset(true);
|
||||
this.kbd.injectKeys("ML");
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.println(sFileName + " ready to load");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {number} addr
|
||||
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to read the specified addr)
|
||||
*/
|
||||
C1PSerialPort.prototype.getByte = function(addr, addrFrom)
|
||||
{
|
||||
/*
|
||||
* Don't trigger any further hardware emulation (beyond what we've already stored in memory) if
|
||||
* the Debugger performed this read (need a special Debugger I/O command if/when you really want to do that).
|
||||
*/
|
||||
if (addrFrom !== undefined) {
|
||||
/*
|
||||
* WARNING: All I need to do for now is load the COM interface's "data byte"
|
||||
* with the next byte from the virtual cassette data stream -JP
|
||||
*/
|
||||
if (!(addr & 0x01)) {
|
||||
/*
|
||||
* An EVEN address implies they're looking, so if we have a fresh buffer,
|
||||
* then prime the pump.
|
||||
*/
|
||||
if (this.sInput && !this.iInput)
|
||||
this.advanceInput();
|
||||
} else {
|
||||
/*
|
||||
* An ODD address implies they just grabbed a data byte, so prep the next data byte.
|
||||
*/
|
||||
this.advanceInput();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {number} addr
|
||||
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to write the specified addr)
|
||||
*/
|
||||
C1PSerialPort.prototype.setByte = function(addr, addrFrom)
|
||||
{
|
||||
/*
|
||||
* Don't trigger any further hardware emulation (beyond what we've already stored in memory) if
|
||||
* the Debugger performed this write (need a special Debugger I/O command if/when you really want to do that).
|
||||
*/
|
||||
if (addrFrom !== undefined) {
|
||||
if (DEBUGGER && this.dbg) this.dbg.messageIO(this, addr, addrFrom, this.dbg.MESSAGE_SERIAL, true);
|
||||
/*
|
||||
* WARNING: I don't yet care what state the CPU puts the port into. When it's time to support serial output,
|
||||
* obviously that will become an issue.
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
C1PSerialPort.prototype.advanceInput = function()
|
||||
{
|
||||
if (this.sInput !== undefined) {
|
||||
this.bInput = -1;
|
||||
if (this.iInput < this.sInput.length) {
|
||||
var b = this.sInput.charCodeAt(this.iInput++) & 0xff;
|
||||
if (this.fConvertLF) {
|
||||
if (b == 0x0a) b = 0x0d;
|
||||
}
|
||||
this.bInput = b;
|
||||
// if (DEBUG) this.log("advanceInput(" + str.toHexByte(b) + ")");
|
||||
}
|
||||
else {
|
||||
this.sInput = "";
|
||||
this.iInput = 0;
|
||||
if (DEBUG) this.log("advanceInput(): out of data");
|
||||
if (this.autoLoad == C1PSerialPort.AUTOLOAD_BASIC && this.kbd) {
|
||||
this.kbd.injectKeys(" \nRUN\n");
|
||||
}
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_NONE;
|
||||
}
|
||||
this.updateMemory();
|
||||
}
|
||||
// else if (DEBUG) this.log("advanceInput(): no input");
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
C1PSerialPort.prototype.updateMemory = function()
|
||||
{
|
||||
var offset;
|
||||
/*
|
||||
* Update all the status (even) bytes
|
||||
*/
|
||||
for (offset = this.offPort+0; offset < this.offPortLimit; offset+=2) {
|
||||
this.abMem[offset] = (this.bInput >= 0? C1PSerialPort.STATUS_DATA : C1PSerialPort.STATUS_NONE);
|
||||
}
|
||||
/*
|
||||
* Update all the data (odd) bytes
|
||||
*/
|
||||
for (offset = this.offPort+1; offset < this.offPortLimit; offset+=2) {
|
||||
this.abMem[offset] = (this.bInput >= 0? this.bInput : 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* C1PSerialPort.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "serial", extracting the
|
||||
* JSON-encoded parameters for the C1PSerialPort constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PSerialPort component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
C1PSerialPort.init = function()
|
||||
{
|
||||
var aeSerial = Component.getElementsByClass(document, C1PJS.APPCLASS, "serial");
|
||||
for (var iSerial=0; iSerial < aeSerial.length; iSerial++) {
|
||||
var eSerial = aeSerial[iSerial];
|
||||
var parmsSerial = Component.getComponentParms(eSerial);
|
||||
var serial = new C1PSerialPort(parmsSerial);
|
||||
Component.bindComponentControls(serial, eSerial, C1PJS.APPCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize every SerialPort module on the page.
|
||||
*/
|
||||
web.onInit(C1PSerialPort.init);
|
||||
Web.onInit(C1PSerialPort.init);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue