my_modules is simply modules now
This commit is contained in:
parent
295ab39024
commit
e0012c3c62
139 changed files with 187 additions and 197 deletions
8
modules/README.md
Normal file
8
modules/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
PCjs Node Modules
|
||||
===
|
||||
|
||||
This folder contains all the **PCjs** JavaScript source code, organized into Node modules. It is the counterpart to
|
||||
[node_modules](../node_modules/), where all the *public* Node modules are installed.
|
||||
|
||||
Only private Node modules are checked into this project. If any of these modules are later published on
|
||||
[npmjs.org](http://npmjs.org), then they will be moved to [node_modules](../node_modules/) and removed from this folder.
|
||||
39
modules/c1pjs/lib/.jshintrc
Normal file
39
modules/c1pjs/lib/.jshintrc
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"boss": true,
|
||||
"eqnull": true,
|
||||
"evil": true,
|
||||
"loopfunc": true,
|
||||
"sub": true,
|
||||
"globalstrict": true,
|
||||
"globals": {
|
||||
"window": true,
|
||||
"APPNAME": false,
|
||||
"APPVERSION": false,
|
||||
"SITEHOST": false,
|
||||
"DEBUG": true,
|
||||
"MAXDEBUG": false,
|
||||
"C1PJSCLASS": true,
|
||||
"DEBUGGER": true,
|
||||
"Component": true,
|
||||
"State": true,
|
||||
"C1PComputer": true,
|
||||
"C1PCPU": true,
|
||||
"C1PDebugger": true,
|
||||
"C1PDiskController": true,
|
||||
"C1PKeyboard": true,
|
||||
"C1PPanel": true,
|
||||
"C1PRAM": true,
|
||||
"C1PROM": true,
|
||||
"C1PSerialPort": true,
|
||||
"C1PVideo": true,
|
||||
"str": true,
|
||||
"usr": true,
|
||||
"web": true,
|
||||
"global": true,
|
||||
"module": true,
|
||||
"require": true,
|
||||
"setTimeout": false,
|
||||
"clearTimeout": false,
|
||||
"Image": false
|
||||
}
|
||||
}
|
||||
368
modules/c1pjs/lib/computer.js
Normal file
368
modules/c1pjs/lib/computer.js
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
/**
|
||||
* @fileoverview This file implements the C1Pjs Computer component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Jun-15
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright 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";
|
||||
|
||||
/**
|
||||
* 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".
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
*/
|
||||
function C1PComputer(parmsComputer, modules)
|
||||
{
|
||||
Component.call(this, "C1PComputer", parmsComputer);
|
||||
|
||||
this.modules = modules;
|
||||
}
|
||||
|
||||
C1PComputer.sAppName = APPNAME || "C1Pjs";
|
||||
C1PComputer.sAppVer = APPVERSION;
|
||||
C1PComputer.sCopyright = "Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>";
|
||||
|
||||
Component.subclass(Component, 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} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @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)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
C1PComputer.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
|
||||
{
|
||||
switch(sBinding) {
|
||||
case "reset":
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function(computer) {
|
||||
return function() {
|
||||
computer.reset();
|
||||
};
|
||||
}(this);
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PComputer}
|
||||
* @param {string} sType
|
||||
* @return {Component}
|
||||
*
|
||||
* NOTE: If there are multiple components for a given type, we may need to provide a means of discriminating.
|
||||
*/
|
||||
C1PComputer.prototype.getComponentByType = function(sType)
|
||||
{
|
||||
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.
|
||||
*/
|
||||
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(C1PComputer.sAppName + " v" + C1PComputer.sAppVer + "\n" + C1PComputer.sCopyright);
|
||||
|
||||
/*
|
||||
* 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 element (e) of class "computer", and initializes
|
||||
* all the necessary HTML to construct the C1PComputer(s) as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "computer" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the C1PComputer constructor
|
||||
* expects.
|
||||
*/
|
||||
C1PComputer.init = function()
|
||||
{
|
||||
var aeComputers = Component.getElementsByClass(window.document, C1PJSCLASS, "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) {
|
||||
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.
|
||||
*/
|
||||
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, C1PJSCLASS);
|
||||
|
||||
/*
|
||||
* "Power" the computer automatically
|
||||
*/
|
||||
C1PComputer.power(computer);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize every Computer on the page.
|
||||
*/
|
||||
web.onInit(C1PComputer.init);
|
||||
3887
modules/c1pjs/lib/cpu.js
Normal file
3887
modules/c1pjs/lib/cpu.js
Normal file
File diff suppressed because it is too large
Load diff
2224
modules/c1pjs/lib/debugger.js
Normal file
2224
modules/c1pjs/lib/debugger.js
Normal file
File diff suppressed because it is too large
Load diff
53
modules/c1pjs/lib/defines.js
Normal file
53
modules/c1pjs/lib/defines.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* @fileoverview C1Pjs-specific compile-time definitions.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-May-08
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @define {string}
|
||||
*/
|
||||
var C1PJSCLASS = "c1pjs"; // this @define is the default application class (formerly APPCLASS) to use for C1Pjs
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
*
|
||||
* WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
|
||||
* In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate. When it's *false*,
|
||||
* nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
|
||||
* "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
|
||||
* debugger.js from the compilation process altogether.
|
||||
*
|
||||
* However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
|
||||
* I would like to skip loading debugger.js altogether. When doing that, we must ALSO arrange for an additional file
|
||||
* (nodebugger.js) to be loaded immediately after this file, which *explicitly* overrides DEBUGGER with *false*.
|
||||
*/
|
||||
var DEBUGGER = true; // this @define is overridden by the Closure Compiler to remove Debugger-related support
|
||||
1265
modules/c1pjs/lib/disk.js
Normal file
1265
modules/c1pjs/lib/disk.js
Normal file
File diff suppressed because it is too large
Load diff
1104
modules/c1pjs/lib/keyboard.js
Normal file
1104
modules/c1pjs/lib/keyboard.js
Normal file
File diff suppressed because it is too large
Load diff
47
modules/c1pjs/lib/nodebugger.js
Normal file
47
modules/c1pjs/lib/nodebugger.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* @fileoverview Compile-time definitions for Debugger-less configurations.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-May-08
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
|
||||
* In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate; when it's *false*,
|
||||
* nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
|
||||
* "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
|
||||
* debugger.js from the compilation process altogether.
|
||||
*
|
||||
* However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
|
||||
* I would like to skip loading debugger.js altogether. To do that, we must arrange for this additional file
|
||||
* (nodebugger.js) to be loaded as early as possible, which *explicitly* overrides the previously defined value
|
||||
* of DEBUGGER with *false*.
|
||||
*/
|
||||
DEBUGGER = false;
|
||||
97
modules/c1pjs/lib/outline.xml
Normal file
97
modules/c1pjs/lib/outline.xml
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml-stylesheet type="text/xsl" href="/modules/c1pjs/templates/outline-debug.xsl"?>
|
||||
<outline id="osi-6502-overview">
|
||||
<title>C1Pjs</title>
|
||||
<excerpt>The following document describes the C1Pjs source code.</excerpt>
|
||||
<content>
|
||||
<machine id="c1psim" class="c1p" border="1" width="272px" pos="right" padleft="50px" padright="50px">
|
||||
<computer id="c1p" name="Challenger 1P">
|
||||
<module type="cpu" refid="cpu6502" start="0x0000" end="0xffff"/>
|
||||
<module type="ram" refid="ram8K" start="0x0000" end="0x1fff"/>
|
||||
<module type="rom" refid="romNull" start="0x2000" end="0x9fff"/>
|
||||
<module type="rom" refid="romBasic" start="0xa000" end="0xbfff"/>
|
||||
<module type="video" refid="video" start="0xd000" end="0xd3ff"/>
|
||||
<module type="keyboard" refid="keyboard" start="0xdf00" end="0xdfff"/>
|
||||
<module type="serial" refid="serialPort" start="0xf000" end="0xf0ff"/>
|
||||
<module type="rom" refid="romSystem" start="0xf800" end="0xffff"/>
|
||||
</computer>
|
||||
<cpu id="cpu6502"/>
|
||||
<ram id="ram8K" size="0x2000"/>
|
||||
<rom id="romNull" size="0x8000"/>
|
||||
<rom id="romBasic" size="0x2000" image="/devices/c1p/rom/basic-gcpatch.hex"/>
|
||||
<rom id="romSystem" size="0x0800" image="/devices/c1p/rom/system.hex"/>
|
||||
<video id="video" screenwidth="256" screenheight="192" cols="32" rows="32" charset="/devices/c1p/video/chargen1x.png" padding="8px"/>
|
||||
<keyboard id="keyboard">
|
||||
<control type="button" class="input" binding="ctrl-c">CTRL-C</control>
|
||||
<control type="button" class="input" binding="break">BREAK</control>
|
||||
</keyboard>
|
||||
<serial id="serialPort" demo="true"/>
|
||||
</machine>
|
||||
<p>C1Pjs is a simulation of the <a href="/devices/c1p/">Challenger 1P</a> micro-computer, a 6502-based system built by Ohio Scientific in the late 1970's.
|
||||
This simulation is implemented entirely in JavaScript, and it is built from the following files:
|
||||
<ul>
|
||||
<li><a href="../../shared/lib/component.js">Component</a></li>
|
||||
<li><a href="computer.js">Computer</a></li>
|
||||
<li><a href="panel.js">Control Panel</a></li>
|
||||
<li><a href="cpu.js">CPU</a></li>
|
||||
<li><a href="debugger.js">Debugger</a></li>
|
||||
<li><a href="disk.js">Disk Controller</a></li>
|
||||
<li><a href="keyboard.js">Keyboard</a></li>
|
||||
<li><a href="ram.js">RAM</a></li>
|
||||
<li><a href="rom.js">ROM</a></li>
|
||||
<li><a href="serial.js">Serial Port</a></li>
|
||||
<li><a href="video.js">Video Display</a></li>
|
||||
<li><a href="embed.js">Embed Support</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>The <a href="../../shared/lib/component.js">Component</a> file defines a generic Component object with some common functionality, and
|
||||
each of the other files copy and extend the Component object to define new components that simulate various pieces of computer
|
||||
hardware, such as the 6502 CPU, the keyboard, the video display, etc. Each file implements exactly one type of component,
|
||||
which are all "compiled" into a single JavaScript file using Google's <a href="https://developers.google.com/closure/compiler/">Closure Compiler</a>.</p>
|
||||
<p>Most of the components are optional. You assemble them into a virtual machine by creating a virtual machine definition file, which is
|
||||
an XML file that lists the components to be used, along with the HTML elements that will visually represent those components, and all
|
||||
the "bindings" that connect the components to the visual elements.</p>
|
||||
<p>Here are some working examples:
|
||||
<ul>
|
||||
<li><a href="/devices/c1p/machine/8kb/large/debugger/">Challenger 1P w/Debugger</a></li>
|
||||
<li><a href="/devices/c1p/machine/8kb/array/">Challenger 1P "Server Array" Demo</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>Here's what a machine definition XML file typically looks like:
|
||||
<pre>
|
||||
<lt/>?xml version="1.0" encoding="UTF-8"?<gt/>
|
||||
<lt/>?xml-stylesheet type="text/xsl" href="/versions/c1pjs/1.12.1/machine.xsl"?<gt/>
|
||||
<lt/>machine id="c1psim" class="c1p" border="1" width="272px"<gt/>
|
||||
<lt/>name<gt/>Challenger 1P Simulation<lt/>/name<gt/>
|
||||
<lt/>computer id="c1p" name="Challenger 1P"<gt/>
|
||||
<lt/>module type="cpu" refid="cpu6502" start="0x0000" end="0xffff"/<gt/>
|
||||
<lt/>module type="ram" refid="ram8K" start="0x0000" end="0x1fff"/<gt/>
|
||||
<lt/>module type="rom" refid="romNull" start="0x2000" end="0x9fff"/<gt/>
|
||||
<lt/>module type="rom" refid="romBasic" start="0xa000" end="0xbfff"/<gt/>
|
||||
<lt/>module type="video" refid="video" start="0xd000" end="0xd3ff"/<gt/>
|
||||
<lt/>module type="keyboard" refid="keyboard" start="0xdf00" end="0xdfff"/<gt/>
|
||||
<lt/>module type="rom" refid="romSystem" start="0xf800" end="0xffff"/<gt/>
|
||||
<lt/>/computer<gt/>
|
||||
<lt/>cpu id="cpu6502"/<gt/>
|
||||
<lt/>ram id="ram8K" size="0x2000"/<gt/>
|
||||
<lt/>rom id="romNull" size="0x8000"/<gt/>
|
||||
<lt/>rom id="romBasic" size="0x2000" image="/devices/c1p/rom/basic-gcpatch.hex"/<gt/>
|
||||
<lt/>rom id="romSystem" size="0x0800" image="/devices/c1p/rom/system.hex"/<gt/>
|
||||
<lt/>video id="video" screenwidth="256" screenheight="192" cols="32" rows="32"
|
||||
charset="/devices/c1p/video/chargen1x.png" width="256px" padding="8px"/<gt/>
|
||||
<lt/>keyboard id="keyboard"<gt/>
|
||||
<lt/>control type="button" class="input" binding="ctrl-c"<gt/>CTRL-C<lt/>/control<gt/>
|
||||
<lt/>control type="button" class="input" binding="ctrl-o"<gt/>CTRL-O<lt/>/control<gt/>
|
||||
<lt/>control type="button" class="input" binding="break"<gt/>BREAK<lt/>/control<gt/>
|
||||
<lt/>/keyboard<gt/>
|
||||
<lt/>/machine<gt/>
|
||||
</pre>
|
||||
</p>
|
||||
<p>The order of the components (and any controls they define) determines the order in which the corresponding HTML elements are laid out on the page.
|
||||
It has no bearing on the initialization sequence of the JavaScript components, which is determined entirely by the order in which the JavaScript files
|
||||
were "compiled" (or by the order the individual JavaScript files are listed on the page).</p>
|
||||
<p>NOTE: If you're debugging a simulation using the browser's Developer Tools, then obviously you'll want to load the original, individual JavaScript files,
|
||||
but in general, you'll get much better performance -- both load-time and run-time -- by loading a single "compiled" JavaScript file.</p>
|
||||
<p class="noindent"><em>Jeff Parsons<br/>August 28, 2012</em></p>
|
||||
</content>
|
||||
</outline>
|
||||
129
modules/c1pjs/lib/panel.js
Normal file
129
modules/c1pjs/lib/panel.js
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/**
|
||||
* @fileoverview This file implements the C1Pjs Panel component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Jun-19
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* C1PPanel(parmsPanel)
|
||||
*
|
||||
* The Panel component has no required (parmsPanel) properties.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
*/
|
||||
function C1PPanel(parmsPanel)
|
||||
{
|
||||
Component.call(this, "C1PPanel", parmsPanel);
|
||||
|
||||
this.aFlags.fPowered = false;
|
||||
}
|
||||
|
||||
Component.subclass(Component, 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} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @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)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
C1PPanel.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
|
||||
{
|
||||
if (this.cmp && this.cmp.setBinding(sHTMLClass, sHTMLType, sBinding, control)) return true;
|
||||
if (this.cpu && this.cpu.setBinding(sHTMLClass, sHTMLType, sBinding, control)) return true;
|
||||
if (this.kbd && this.kbd.setBinding(sHTMLClass, sHTMLType, sBinding, control)) return true;
|
||||
if (DEBUGGER && this.dbg && this.dbg.setBinding(sHTMLClass, sHTMLType, sBinding, control)) return true;
|
||||
return Component.prototype.setBinding.call(this, sHTMLClass, sHTMLType, sBinding, control);
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PPanel}
|
||||
* @param {boolean} fOn
|
||||
* @param {C1PComputer} cmp
|
||||
*/
|
||||
C1PPanel.prototype.setPower = function(fOn, cmp)
|
||||
{
|
||||
if (fOn && !this.aFlags.fPowered) {
|
||||
this.aFlags.fPowered = true;
|
||||
this.cmp = cmp;
|
||||
this.cpu = cmp.getComponentByType("cpu");
|
||||
this.kbd = cmp.getComponentByType("keyboard");
|
||||
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
|
||||
C1PPanel.init();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* C1PPanel.init()
|
||||
*
|
||||
* This function operates on every element (e) of class "panel", and initializes
|
||||
* all the necessary HTML to construct the Panel module(s) as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "panel" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the Panel constructor
|
||||
* expects.
|
||||
*
|
||||
* 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 Computer 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, CPU, Keyboard, and Debugger) that we have some controls they
|
||||
* might want to use.
|
||||
*/
|
||||
C1PPanel.init = function()
|
||||
{
|
||||
var fReady = false;
|
||||
var aePanels = Component.getElementsByClass(window.document, C1PJSCLASS, "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, C1PJSCLASS);
|
||||
if (fReady) panel.setReady();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize every Panel module on the page.
|
||||
*/
|
||||
web.onInit(C1PPanel.init);
|
||||
95
modules/c1pjs/lib/ram.js
Normal file
95
modules/c1pjs/lib/ram.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* @fileoverview This file implements the C1Pjs RAM component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Jun-15
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
*/
|
||||
function C1PRAM(parmsRAM)
|
||||
{
|
||||
Component.call(this, "C1PRAM", parmsRAM);
|
||||
}
|
||||
|
||||
Component.subclass(Component, 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 element (e) of class "ram", and initializes
|
||||
* all the necessary HTML to construct the RAM module(s) as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "ram" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the RAM constructor
|
||||
* expects.
|
||||
*/
|
||||
C1PRAM.init = function()
|
||||
{
|
||||
var aeRAM = Component.getElementsByClass(window.document, C1PJSCLASS, "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, C1PJSCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize all the RAM modules on the page.
|
||||
*/
|
||||
web.onInit(C1PRAM.init);
|
||||
240
modules/c1pjs/lib/rom.js
Normal file
240
modules/c1pjs/lib/rom.js
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
/**
|
||||
* @fileoverview This file implements the C1Pjs ROM component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Jun-15
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @property {function()} convertImage
|
||||
*/
|
||||
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 != "json" && sFileExt != "hex") {
|
||||
/**
|
||||
* TODO: This code was using a deprecated parameter (compact=1); make sure things still work.
|
||||
*
|
||||
* TODO: Convert this code to use the new shared File API definitions and weblib functions; eg:
|
||||
*
|
||||
* sFileURL = web.getHost() + DumpAPI.ENDPOINT + "?" + DumpAPI.QUERY.FILE + "=" + this.sImage;
|
||||
*/
|
||||
sFileURL = "http://" + window.location.host + "/api/v1/dump?file=" + this.sImage;
|
||||
}
|
||||
web.loadResource(sFileURL, true, null, this, this.convertImage);
|
||||
}
|
||||
}
|
||||
|
||||
Component.subclass(Component, 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.
|
||||
*/
|
||||
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();
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {boolean} fOn
|
||||
* @param {C1PComputer} cmp
|
||||
*/
|
||||
C1PROM.prototype.setPower = function(fOn, cmp)
|
||||
{
|
||||
if (fOn && !this.aFlags.fPowered) {
|
||||
this.aFlags.fPowered = 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.messagePort(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[0] == "[") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be right here, where we're parsing
|
||||
* the JSON-encoded ROM data.
|
||||
*/
|
||||
this.abImage = eval("(" + sImageData + ")");
|
||||
} catch (e) {
|
||||
this.println("Error processing ROM \"" + sImageName + "\": " + e.message);
|
||||
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);
|
||||
}
|
||||
}
|
||||
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.
|
||||
*/
|
||||
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.toHexAddr(this.offROM) + " (0x" + str.toHexWord(cbImage) + " bytes)");
|
||||
for (var i=0; i < cbImage; i++) {
|
||||
this.abMem[this.offROM + i] = this.abImage[i];
|
||||
}
|
||||
this.setReady();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* C1PROM.init()
|
||||
*
|
||||
* This function operates on every element (e) of class "rom", and initializes
|
||||
* all the necessary HTML to construct the ROM module(s) as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "rom" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the ROM constructor
|
||||
* expects.
|
||||
*/
|
||||
C1PROM.init = function()
|
||||
{
|
||||
var aeROM = Component.getElementsByClass(window.document, C1PJSCLASS, "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, C1PJSCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize all the ROM modules on the page.
|
||||
*/
|
||||
web.onInit(C1PROM.init);
|
||||
402
modules/c1pjs/lib/serial.js
Normal file
402
modules/c1pjs/lib/serial.js
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
/**
|
||||
* @fileoverview This file implements the C1Pjs SerialPort component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Jul-01
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* C1PSerialPort(parmsSerial)
|
||||
*
|
||||
* The SerialPort component has no component-specific parameters.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
*/
|
||||
function C1PSerialPort(parmsSerial)
|
||||
{
|
||||
Component.call(this, "C1PSerialPort", parmsSerial);
|
||||
|
||||
this.aFlags.fPowered = false;
|
||||
this.fDemo = parmsSerial['demo'];
|
||||
|
||||
this.STATUS_NONE = 0x00;
|
||||
this.STATUS_DATA = 0x01; // indicates data available
|
||||
|
||||
this.reset();
|
||||
}
|
||||
|
||||
Component.subclass(Component, C1PSerialPort);
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
C1PSerialPort.prototype.reset = function()
|
||||
{
|
||||
/*
|
||||
* 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 (this.autoLoad != 2) {
|
||||
|
||||
this.bInput = 0;
|
||||
this.cbInput = 0;
|
||||
|
||||
this.sInput = "10 PRINT \"HELLO OSI #" + this.getMachineNum() + "\"\n";
|
||||
this.iInputNext = 0;
|
||||
|
||||
// this.sOutput = new Array(0);
|
||||
// this.iOutputNext = 0;
|
||||
|
||||
/*
|
||||
* Values for autoLoad:
|
||||
*
|
||||
* 0: no auto-load active
|
||||
* 1: BASIC command file auto-load in progress
|
||||
* 2: 6502 HEX command file auto-load in progress
|
||||
*/
|
||||
this.autoLoad = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @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} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @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)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
C1PSerialPort.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
|
||||
{
|
||||
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.loadResource(sFile, true, null, serial, serial.loadFile);
|
||||
}
|
||||
};
|
||||
return true;
|
||||
|
||||
case "mountSerial":
|
||||
/*
|
||||
* Check for availability of FileReader
|
||||
*/
|
||||
if (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 == 0);
|
||||
});
|
||||
|
||||
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(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.aFlags.fPowered) {
|
||||
this.aFlags.fPowered = true;
|
||||
this.cmp = cmp;
|
||||
this.kbd = cmp.getComponentByType("keyboard");
|
||||
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
C1PSerialPort.prototype.startLoad = function()
|
||||
{
|
||||
this.autoLoad = 1;
|
||||
this.kbd.injectKeys("LOAD\n");
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {String} sFileName
|
||||
* @param {string} sFileData (null if loadResource() encountered an error)
|
||||
* @param {number} nResponse from server
|
||||
*/
|
||||
C1PSerialPort.prototype.loadFile = function(sFileName, sFileData, nResponse)
|
||||
{
|
||||
if (!sFileData) {
|
||||
this.println(sFileName + " load error (" + nResponse + ")");
|
||||
return;
|
||||
}
|
||||
this.sInput = sFileData;
|
||||
this.iInputNext = 0;
|
||||
this.autoLoad = 0;
|
||||
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 loadResource(), 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 = 1;
|
||||
this.kbd.injectKeys("NEW\nLOAD\n");
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Set autoLoad to 2 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 = 2;
|
||||
/*
|
||||
* 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.iInputNext)
|
||||
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.messagePort(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 = 0;
|
||||
this.cbInput = 0;
|
||||
if (this.iInputNext < this.sInput.length) {
|
||||
var b = this.sInput.charCodeAt(this.iInputNext++);
|
||||
if (b == 0x0a) b = 0x0d;
|
||||
this.bInput = b;
|
||||
this.cbInput = 1;
|
||||
// if (DEBUG) this.log("advanceInput(" + str.toHexByte(b) + ")");
|
||||
}
|
||||
else {
|
||||
if (DEBUG) this.log("advanceInput(): out of data");
|
||||
if (this.autoLoad == 1 && this.kbd) {
|
||||
this.kbd.injectKeys(" \nRUN\n");
|
||||
}
|
||||
this.autoLoad = 0;
|
||||
}
|
||||
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.cbInput? this.STATUS_DATA : this.STATUS_NONE);
|
||||
/*
|
||||
* Update all the data (odd) bytes
|
||||
*/
|
||||
for (offset = this.offPort+1; offset < this.offPortLimit; offset+=2)
|
||||
this.abMem[offset] = (this.cbInput? this.bInput : 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* C1PSerialPort.init()
|
||||
*
|
||||
* This function operates on every element (e) of class "serial", and initializes
|
||||
* all the necessary HTML to construct the SerialPort module(s) as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "serial" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the SerialPort constructor
|
||||
* expects.
|
||||
*/
|
||||
C1PSerialPort.init = function()
|
||||
{
|
||||
var aeSerial = Component.getElementsByClass(window.document, C1PJSCLASS, "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, C1PJSCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize every SerialPort module on the page.
|
||||
*/
|
||||
web.onInit(C1PSerialPort.init);
|
||||
599
modules/c1pjs/lib/video.js
Normal file
599
modules/c1pjs/lib/video.js
Normal file
|
|
@ -0,0 +1,599 @@
|
|||
/**
|
||||
* @fileoverview This file implements the C1Pjs Video component
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Jun-15
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* C1PVideo(parmsVideo, eCanvas, context, imgChars)
|
||||
*
|
||||
* The Video component can be configured with the following (parmsVideo) properties:
|
||||
*
|
||||
* model: model number (one of: 540 or 600; 600 is the default)
|
||||
* screenWidth: width of the screen window, in pixels
|
||||
* screenHeight: height of the screen window, in pixels
|
||||
* charCols: number of character columns
|
||||
* charRows: number of character rows
|
||||
* charWidth: width of charSet characters, in pixels (default is 0)
|
||||
* charHeight: height of charSet characters, in pixels (default is 0)
|
||||
* charSet: path to image (eg, PNG) file that defines the character set
|
||||
* screenColor: background color of the screen window (default is black)
|
||||
*
|
||||
* The Video object assumes that the video buffer is organized such that offset 0 is mapped
|
||||
* to the left-most column and top-most row (col=0,row=0), offset 1 is (1,0), offset 2
|
||||
* is (2,0), and so on.
|
||||
*
|
||||
* The Video object initially contains no underlying video buffer; memory for the buffer
|
||||
* must be given to it by the Computer object. We allocate a separate buffer, called
|
||||
* the screen buffer, into which we periodically copy the contents of the video buffer
|
||||
* via updateScreen(); any differences between the two buffers are then rendered in the
|
||||
* associated window, via updateWindow().
|
||||
*
|
||||
* When updateScreen() finds a byte in the screen buffer must be redisplayed, it converts
|
||||
* the offset of that byte into a (col,row) character position for the updateWindow() function,
|
||||
* which then converts (col,row) into (x,y) pixel offsets within the underlying canvas.
|
||||
*
|
||||
* Regarding the C1P (aka Model 600): The C1P has a 1K video buffer located at 0xD000-0xD3FF.
|
||||
* The ROM draws the initial "D/C/W/M ?" prompt at the "bottom" of the video buffer at location
|
||||
* 0xD365. That row really begins at 0xD360, but the C1P "indents" everything by 5 columns due
|
||||
* to the lack of a "guard band feature." Similarly, BASIC defaults to a width of 24 columns
|
||||
* avoid display problems near the right edge. BASIC will let you choose a width SMALLER than
|
||||
* 24 but not larger. So, while the video buffer supports a theoretical maximum of 32 rows x 32
|
||||
* columns, the practical maximum is 24 rows x 24 columns; the last 4 rows of the video buffer
|
||||
* are never used, and while content scrolls through the top 4 lines of the buffer, it is never
|
||||
* assumed that you can see the top 4 lines.
|
||||
*
|
||||
* This is partially confirmed by the "C1P Character Graphics Reference Manual", p3, which says
|
||||
* that the "the visible character field consists of 25 lines of 25 columns" and that the "first
|
||||
* visible character in the upper left of the screen is accessed via address 53379," or 0xD083,
|
||||
* confirming that the first 4 lines are not assumed to be visible. However, the comment
|
||||
* regarding "25 lines of 25 columns" seems to be off by one in both dimensions. And why would
|
||||
* they say that the first visible address is 0xD083 instead of 0xD085? An indentation of 5 bytes,
|
||||
* rather than 3, would be more consistent with how the C1P ROMs use video memory.
|
||||
*
|
||||
* Model 540 Video Board vs. Model 600 "Superboard II"
|
||||
* ---------------------------------------------------
|
||||
* This emulation was originally written for the Model 600 "Superboard II" (eg, Challenger 1P).
|
||||
* Support for the Model 540 video board (as used in the Challenger II-4P and II-8P) was added
|
||||
* later.
|
||||
*
|
||||
* NOTE: When Model 540 video emulation is enabled, Model 542 keyboard emulation must also be
|
||||
* enabled, because the former always came with the latter keyboard interface; this is why when
|
||||
* we call this.setModel(540), we must also notify the Keyboard via kbd.setModel(542).
|
||||
*
|
||||
* Key features/differences of the Model 540 video board include:
|
||||
*
|
||||
* 2K (8 pages) of video memory located at 0xD000-0xD7FF
|
||||
* Two display modes: 32 rows x 64 cols (default on power up), and 32 rows x 32 cols
|
||||
* 64 bytes per screen row, regardless which display mode is selected
|
||||
* The following options can be selected via WRITE to port address 0xDE00:
|
||||
* Bit 0: clear to enable 32/64 mode (default on power up), set to enable 32/32
|
||||
* Bit 1: 1=tone on (542 keyboard)
|
||||
* Bit 2: 1=color on (Rev. B only?)
|
||||
* bit 3: 1=enable 38-40Khz AC Home control output (Rev. B only?)
|
||||
* Video timing counter status via READ from port address 0xDE00:
|
||||
* Bit 7: 0 for 1/120 second, then 1 for 1/120 second, based on video clock (60Hz)
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
*/
|
||||
function C1PVideo(parmsVideo, eCanvas, context, imgChars)
|
||||
{
|
||||
Component.call(this, "C1PVideo", parmsVideo);
|
||||
|
||||
this.nDefaultModel = parmsVideo['model'];
|
||||
this.nDefaultCols = parmsVideo['charCols'];
|
||||
this.nDefaultRows = parmsVideo['charRows'];
|
||||
|
||||
this.cxScreen = parmsVideo['screenWidth'];
|
||||
this.cyScreen = parmsVideo['screenHeight'];
|
||||
|
||||
/*
|
||||
* These (source) character dimensions are tentative, and may not even be provided,
|
||||
* but they will become definitive once imgChars has finished loading and setReady() is called.
|
||||
*/
|
||||
this.cxChar = parmsVideo['charWidth'];
|
||||
this.cyChar = parmsVideo['charHeight'];
|
||||
|
||||
/*
|
||||
* This is a preliminary call to setDimensions(), to initialize default screen buffer and
|
||||
* window dimensions. A more extensive call to setDimensions() will take place when setModel()
|
||||
* is called later, from reset() and possibly via the tripGuard() handler.
|
||||
*
|
||||
* This preliminary call merely establishes a default screen buffer size, so that when
|
||||
* setBuffer() is called, it's able to verify the assigned address space is at least as big
|
||||
* as the screen buffer.
|
||||
*/
|
||||
this.setDimensions();
|
||||
|
||||
this.eCanvas = eCanvas;
|
||||
this.context = context;
|
||||
this.imgChars = imgChars;
|
||||
|
||||
/*
|
||||
* QUESTION: Does this video port exist only on the Model 540?
|
||||
*/
|
||||
this.addrVideoPort = 0xDE00; // WARNING: Hard-coded port address -JP
|
||||
}
|
||||
|
||||
Component.subclass(Component, C1PVideo);
|
||||
|
||||
/**
|
||||
* @this {C1PVideo}
|
||||
* @param {boolean} [fPowerOn] is true for the initial reset, so that we have
|
||||
* the option of rendering "random" graphic characters, just like the real machine would do.
|
||||
*/
|
||||
C1PVideo.prototype.reset = function(fPowerOn)
|
||||
{
|
||||
this.setModel(this.nDefaultModel);
|
||||
|
||||
if (this.abMem) {
|
||||
/*
|
||||
* Let's treat every reset like a power-cycle, just for fun.
|
||||
* If you don't think that's fun, then simply remove the next line.
|
||||
*
|
||||
fPowerOn = true;
|
||||
*/
|
||||
for (var offset = this.offVideo; offset < this.offVideoLimit; offset++) {
|
||||
var b = (fPowerOn? Math.floor(Math.random() * 256) : 0x20);
|
||||
Component.assert(b >= 0 && b <= 255);
|
||||
this.abMem[offset] = b;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PVideo}
|
||||
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @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, "refresh")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
C1PVideo.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
|
||||
{
|
||||
switch(sBinding) {
|
||||
case "refresh":
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function(video) {
|
||||
return function() {
|
||||
if (DEBUG) video.println("refreshScreen()");
|
||||
video.initScreen();
|
||||
video.updateScreen();
|
||||
};
|
||||
}(this);
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PVideo}
|
||||
* @param {Array} abMemory
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {C1PCPU} cpu
|
||||
*/
|
||||
C1PVideo.prototype.setBuffer = function(abMemory, start, end, cpu)
|
||||
{
|
||||
this.abMem = abMemory;
|
||||
this.offVideo = start;
|
||||
this.cbVideo = end - start + 1;
|
||||
this.offVideoLimit = this.offVideo + this.cbVideo;
|
||||
Component.assert(this.cbScreen <= this.cbVideo, "screen size (0x" + this.cbScreen.toString(16) + ") exceeds video buffer size (0x" + this.cbVideo.toString(16) + ")");
|
||||
if (cpu) {
|
||||
this.cpu = cpu;
|
||||
if (this.addrVideoPort !== undefined) {
|
||||
cpu.addReadNotify(this.addrVideoPort, this.addrVideoPort, this, this.getByte);
|
||||
cpu.addWriteNotify(this.addrVideoPort, this.addrVideoPort, this, this.setByte);
|
||||
}
|
||||
}
|
||||
this.reset(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PVideo}
|
||||
* @param {number|undefined} [nCols] (default is nDefaultCols)
|
||||
* @param {number|undefined} [nRows] (default is nDefaultRows)
|
||||
* @param {number|undefined} [iRowTop] (eg, 4; default is 0)
|
||||
* @param {number|undefined} [nRowsVisible] (eg, 24; default is nRows)
|
||||
*/
|
||||
C1PVideo.prototype.setDimensions = function(nCols, nRows, iRowTop, nRowsVisible)
|
||||
{
|
||||
this.nCols = (nCols !== undefined? nCols : this.nDefaultCols);
|
||||
this.nRows = (nRows !== undefined? nRows : this.nDefaultRows);
|
||||
this.cbScreen = this.nCols * this.nRows;
|
||||
this.offVideoLimit = this.offVideo + this.cbScreen;
|
||||
/*
|
||||
* Set the first visible row and total visible rows next
|
||||
*/
|
||||
this.iRowTop = (iRowTop !== undefined? iRowTop : 0);
|
||||
this.nRowsVisible = (nRowsVisible !== undefined? nRowsVisible : nRows);
|
||||
this.setDrawingDimensions();
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PVideo}
|
||||
*
|
||||
* cxScreen and cyScreen give us the overall dimensions of the destination surface. Dividing that by the number of
|
||||
* columns and rows yields a target cell size (cxCharDst,cyCharDst), which may or may not map 1-1 to the source cell size
|
||||
* (cxChar,cyChar).
|
||||
*/
|
||||
C1PVideo.prototype.setDrawingDimensions = function()
|
||||
{
|
||||
this.cxCharDst = Math.floor(this.cxScreen / this.nCols);
|
||||
this.cyCharDst = Math.floor(this.cyScreen / this.nRowsVisible);
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PVideo}
|
||||
*/
|
||||
C1PVideo.prototype.setFocus = function()
|
||||
{
|
||||
this.eCanvas.focus();
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PVideo}
|
||||
* @param {number} nModel
|
||||
*/
|
||||
C1PVideo.prototype.setModel = function(nModel)
|
||||
{
|
||||
this.nModel = nModel;
|
||||
/*
|
||||
* Default to model 600 behavior (1K video buffer);
|
||||
* the only other supported model is 540 (2K video buffer).
|
||||
*/
|
||||
if (this.nModel == 600) {
|
||||
this.setDimensions(this.nDefaultCols, this.nDefaultRows, 4, 24);
|
||||
if (this.cbScreen == 1024 && this.cpu) {
|
||||
/*
|
||||
* NOTE: We deliberately set the guard address to the LAST byte of the 2K
|
||||
* buffer range, not the FIRST byte, which has the same effect but with the
|
||||
* added benefit of deferring any screen update until after the "Model 540"
|
||||
* screen initialization code has completely blanked the entire 2K buffer,
|
||||
* avoiding a brief flicker of unsightly characters.
|
||||
*/
|
||||
this.addrGuard = this.offVideoLimit + this.cbScreen - 1;
|
||||
this.cpu.addWriteNotify(this.addrGuard, this.addrGuard, this, this.tripGuard);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.println("updated video model: " + this.nModel);
|
||||
this.setDimensions(64, 32);
|
||||
}
|
||||
this.initScreen();
|
||||
this.updateScreen();
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PVideo}
|
||||
* @param {boolean} fOn
|
||||
* @param {C1PComputer} cmp
|
||||
*/
|
||||
C1PVideo.prototype.setPower = function(fOn, cmp)
|
||||
{
|
||||
/*
|
||||
* NOTE: No one should be calling power(true) before first checking isReady(), but we check
|
||||
* it ourselves, too. This also means that updateScreen() need check only fPower and not isReady(),
|
||||
* since we guarantee that the former implies the latter.
|
||||
*/
|
||||
if (fOn && !this.aFlags.fPowered && this.isReady()) {
|
||||
this.aFlags.fPowered = true;
|
||||
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
|
||||
/*
|
||||
* If we have an associated keyboard, then ensure that the keyboard will be notified whenever
|
||||
* the canvas gets focus and receives input.
|
||||
*
|
||||
* Also, when simulating a Model 540 video board, we need to access to the Keyboard component due
|
||||
* to some shared I/O responsibilities; ie, bit 1 of the video control port at 0xDE00 enables whatever
|
||||
* tone has been selected via the keyboard frequency port at 0xDF01 (frequency == 49152/n, where n
|
||||
* is the value stored at 0xDF01).
|
||||
*/
|
||||
this.kbd = cmp.getComponentByType("keyboard");
|
||||
if (this.kbd) {
|
||||
this.kbd.setBinding("input", "canvas", "keyDown", this.eCanvas);
|
||||
this.kbd.setBinding("input", "canvas", "keyPress", this.eCanvas);
|
||||
this.kbd.setBinding("input", "canvas", "keyUp", this.eCanvas);
|
||||
}
|
||||
}
|
||||
else
|
||||
if (!fOn && this.aFlags.fPowered) {
|
||||
this.aFlags.fPowered = false;
|
||||
/*
|
||||
* This is where we would add some method of blanking the display, without the disturbing the video
|
||||
* buffer contents, and blocking all further updates to the display.
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PVideo}
|
||||
*
|
||||
* cxChar and cyChar are the source cell size. Originally, those values came strictly from the parmsVideo
|
||||
* 'charWidth' and 'charHeight' properties. Now, if those aren't defined (which is normally the case now),
|
||||
* then we infer the source cell size from the dimensions of imgChars, which is expected to be a 16x16 array of
|
||||
* character bitmaps. We could be even more flexible, by allowing imgChars to be any rectangular dimension
|
||||
* (eg, 1x256) as long as we can assume it contains exactly 256 characters, but there's no need to get carried away....
|
||||
*/
|
||||
C1PVideo.prototype.setReady = function()
|
||||
{
|
||||
if (!this.cxChar) this.cxChar = Math.floor(this.imgChars.width / 16);
|
||||
if (!this.cyChar) this.cyChar = Math.floor(this.imgChars.height / 16);
|
||||
Component.prototype.setReady.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PVideo}
|
||||
* @param {number} addr (ie, addrVideoPort)
|
||||
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to read the specified addr)
|
||||
*
|
||||
* NOTE: Ordinarily, I wouldn't allow Debugger writes (addrFrom === undefined) to interfere with the simulated
|
||||
* hardware state, but for now, I find it useful to be able to prod the simulation code directly from the Debugger.
|
||||
*/
|
||||
C1PVideo.prototype.getByte = function(addr, addrFrom)
|
||||
{
|
||||
var b = this.cpu.getByte(addr);
|
||||
if (addrFrom !== undefined) {
|
||||
if (DEBUGGER && this.dbg) this.dbg.messagePort(this, addr, addrFrom, this.dbg.MESSAGE_VIDEO);
|
||||
}
|
||||
/*
|
||||
* The only documented READ bit in addrVideoPort is bit 7, which is supposed to alternate between
|
||||
* 0 and 1 every 1/120 of a second. There's no way we're going to add special code to the emulator to update
|
||||
* this stupid byte every 8,333 cycles (assuming 1Mhz operation), so clearly we're going to fake it.
|
||||
*
|
||||
* Faking it means that any polling code will unavoidably get a stale value the FIRST time it reads bit 7.
|
||||
* However, we can still do a pretty good job of faking any EXTENSIVE polling: get the number of cycles
|
||||
* executed so far, divide that by 8333, floor the quotient, and then set/clear bit 7 according to whether the
|
||||
* result is odd/even.
|
||||
*/
|
||||
var nCyclesHigh = Math.floor(this.cpu.getCycles() / 8333);
|
||||
this.cpu.setByte(addr, (b & 0x7F) | ((nCyclesHigh & 0x1)? 0x80 : 0));
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PVideo}
|
||||
* @param {number} addr (ie, addrVideoPort)
|
||||
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to write the specified addr)
|
||||
*/
|
||||
C1PVideo.prototype.setByte = function(addr, addrFrom)
|
||||
{
|
||||
if (addrFrom !== undefined) {
|
||||
if (DEBUGGER && this.dbg) this.dbg.messagePort(this, addr, addrFrom, this.dbg.MESSAGE_VIDEO);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PVideo}
|
||||
* @param {number} addr (ie, addrGuard)
|
||||
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to read the specified addr)
|
||||
*/
|
||||
C1PVideo.prototype.tripGuard = 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) {
|
||||
if (DEBUGGER && this.dbg) this.dbg.messagePort(this, addr, addrFrom, this.dbg.MESSAGE_VIDEO, true);
|
||||
/*
|
||||
* The CPU has just written to the guard address we established just beyond the video buffer's 1K boundary,
|
||||
* implying that the system thinks we have a 2K buffer instead. So we bump our model to 540, bump the
|
||||
* associated keyboard model to 542, and remove this guard handler.
|
||||
*/
|
||||
this.setModel(540);
|
||||
if (this.kbd) this.kbd.setModel(542);
|
||||
this.cpu.removeWriteNotify(this.addrGuard, this.addrGuard, this, this.tripGuard);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PVideo}
|
||||
*/
|
||||
C1PVideo.prototype.initScreen = function()
|
||||
{
|
||||
this.abScreen = new Array(this.cbScreen);
|
||||
for (var offset=0; offset <= this.cbScreen; offset++) {
|
||||
this.abScreen[offset] = -1; // initialize every cell of the screen to an invalid value
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* updateScreen() updates the screen buffer from the video buffer and updates the window with any changes.
|
||||
*
|
||||
* @this {C1PVideo}
|
||||
* @return {boolean}
|
||||
*
|
||||
* For every byte in the video buffer, this renders it if it differs from the byte stored in the screen buffer,
|
||||
* and then updates the screen buffer to match. Since initScreen() sets every byte in the screen buffer
|
||||
* to an illegal byte value (ie, a value which is outside the byte range 0x00-0xff), that assures the first call
|
||||
* to updateScreen() will redraw every byte in the video buffer.
|
||||
*/
|
||||
C1PVideo.prototype.updateScreen = function()
|
||||
{
|
||||
var offset = 0;
|
||||
if (this.aFlags.fPowered) {
|
||||
while (offset < this.cbScreen) {
|
||||
var b = this.abMem[this.offVideo + offset];
|
||||
if (this.abScreen[offset] != b) {
|
||||
if (!this.writeByte(offset, b)) {
|
||||
break;
|
||||
}
|
||||
this.abScreen[offset] = b;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
return (offset == this.cbScreen);
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PVideo}
|
||||
* @param {number} offset
|
||||
* @param {number} b
|
||||
* @return {boolean}
|
||||
*/
|
||||
C1PVideo.prototype.writeByte = function(offset, b)
|
||||
{
|
||||
var col = offset % this.nCols;
|
||||
var row = Math.floor(offset / this.nCols);
|
||||
// if (b == 0) this.cpu.halt(); // I must have been testing something here...
|
||||
return this.updateWindow(col, row, b);
|
||||
};
|
||||
|
||||
/**
|
||||
* updateWindow() updates a particular position (row,col) in the associated window with the given byte (b)
|
||||
*
|
||||
* @this {C1PVideo}
|
||||
* @param {number} col
|
||||
* @param {number} row
|
||||
* @param {number} b
|
||||
* @return {boolean} true if successful, false if not
|
||||
*
|
||||
* I originally used (screenWidth,screenHeight) == (512,448) and (cols,rows) == (32,32) and (cxChar,cyChar) == (16,16),
|
||||
* and I simply copied the source cells 1-to-1 to the destination (16,16), knowing that we would never try to display more
|
||||
* than 28 rows (the last 4 rows of the 32 possible rows were never used to display any content). However, I should still
|
||||
* have ignored any attempt to draw past row 28 (aka screenHeight 448). I now perform row clipping and biasing, according
|
||||
* to the first visible row (iRowTop) and total visible rows (nRowsVisible).
|
||||
*
|
||||
* Moreover, I no longer copy the source cell images to the destination 1-to-1. I calculate (cxCharDst,cyCharDst) separately
|
||||
* (see setDrawingDimensions). And I no longer assume that (cxChar,cyChar) are (16,16); once the source image file has finished
|
||||
* loading, I calculate (cxChar,cyChar) based on the size of image file (see setReady). I made this change when I created
|
||||
* chargen1x.png. In fact, at first I thought I might be able to eliminate chargen2x.png and just let drawImage() scale up
|
||||
* the individual character images from (8,8) to (16,16) or whatever (cxCharDst,cyCharDst) size was needed, but the results were
|
||||
* fuzzy, so it's still best to use chargen2x.png when using larger window sizes.
|
||||
*/
|
||||
C1PVideo.prototype.updateWindow = function(col, row, b)
|
||||
{
|
||||
if (row >= this.iRowTop) {
|
||||
row -= this.iRowTop;
|
||||
if (row < this.nRowsVisible) {
|
||||
var xChar = (b * this.cxChar);
|
||||
var ySrc = Math.floor(xChar / this.imgChars.width) * this.cyChar;
|
||||
var xSrc = xChar % this.imgChars.width;
|
||||
var xDst = col * this.cxCharDst;
|
||||
var yDst = row * this.cyCharDst;
|
||||
// if (DEBUG) this.log("updateWindow(" + col + "," + row + "," + b +"): drawing from " + xSrc + "," + ySrc + " to " + xDst + "," + yDst);
|
||||
this.context.drawImage(this.imgChars, xSrc, ySrc, this.cxChar, this.cyChar, xDst, yDst, this.cxCharDst, this.cyCharDst);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* C1PVideo.init()
|
||||
*
|
||||
* This function operates on every element (e) of class "video", and initializes
|
||||
* all the necessary HTML to construct every Video module as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "video" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the Video constructor
|
||||
* expects.
|
||||
*/
|
||||
C1PVideo.init = function()
|
||||
{
|
||||
var aeVideo = Component.getElementsByClass(window.document, C1PJSCLASS, "video");
|
||||
for (var iVideo=0; iVideo < aeVideo.length; iVideo++) {
|
||||
var eVideo = aeVideo[iVideo];
|
||||
var parmsVideo = Component.getComponentParms(eVideo);
|
||||
|
||||
/*
|
||||
* As noted in keyboard.js, the keyboard on an iOS device pops up with the SHIFT key depressed,
|
||||
* which is not the initial keyboard state that the C1P expects. I originally tried to fix that by
|
||||
* adding an 'autocapitalize="off"' attribute alongside the 'contenteditable="true"' attribute
|
||||
* on the <canvas> element, but apparently Safari honors that only inside certain elements (eg, <input>).
|
||||
*
|
||||
* I've since settled on a better work-around in keyboard.js, so I've stopped worrying about how to make
|
||||
* "autocapitalize" work here.
|
||||
*/
|
||||
var eCanvas = window.document.createElement("canvas");
|
||||
if (eCanvas === undefined) {
|
||||
eVideo.innerHTML = "<br/>Missing <canvas> support; try a new web browser.";
|
||||
return;
|
||||
}
|
||||
eCanvas.setAttribute("class", C1PJSCLASS + "-canvas");
|
||||
eCanvas.setAttribute("width", parmsVideo['screenWidth']);
|
||||
eCanvas.setAttribute("height", parmsVideo['screenHeight']);
|
||||
|
||||
eCanvas.setAttribute("contenteditable", "true");
|
||||
eCanvas.setAttribute("autocapitalize", "off");
|
||||
eCanvas.setAttribute("autocorrect", "off");
|
||||
eCanvas.style.backgroundColor = parmsVideo['screenColor'];
|
||||
|
||||
/*
|
||||
* HACK: A canvas style of "auto" provides for excellent responsive canvas scaling in EVERY browser
|
||||
* except IE9/IE10, so I recalculate the appropriate CSS height every time the parent DIV is resized;
|
||||
* IE11 works without this hack, so we take advantage of the fact that IE11 doesn't report itself as "MSIE".
|
||||
*/
|
||||
eCanvas.style.height = "auto";
|
||||
if (web.getUserAgent().indexOf("MSIE") >= 0) {
|
||||
eCanvas.style.height = (((eVideo.clientWidth * parmsVideo['screenHeight']) / parmsVideo['screenWidth']) | 0) + "px";
|
||||
eVideo.onresize = function(eParent, eChild, cx, cy) {
|
||||
return function() {
|
||||
eChild.style.height = (((eParent.clientWidth * cy) / cx) | 0) + "px";
|
||||
};
|
||||
}(eVideo, eCanvas, parmsVideo['screenWidth'], parmsVideo['screenHeight']);
|
||||
}
|
||||
eVideo.appendChild(eCanvas);
|
||||
|
||||
/*
|
||||
* Now we can create the Video object, record it, and wire it up to the associated document elements.
|
||||
*
|
||||
* Regarding "new Image()", see https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement.Image:
|
||||
*
|
||||
* This constructor exists for historical reasons only and returns an HTMLImageElement instance just as
|
||||
* document.createElement('img') would.
|
||||
*/
|
||||
var imgCharSet = new Image();
|
||||
var contextVideo = eCanvas.getContext("2d");
|
||||
var video = new C1PVideo(parmsVideo, eCanvas, contextVideo, imgCharSet);
|
||||
imgCharSet.onload = function(video, sCharSet) {
|
||||
return function() {
|
||||
if (DEBUG) video.log("onload(): finished loading " + sCharSet);
|
||||
video.setReady();
|
||||
};
|
||||
}(video, parmsVideo['charSet']); // jshint ignore:line
|
||||
imgCharSet.src = parmsVideo['charSet'];
|
||||
|
||||
/*
|
||||
* Bind any video-specific controls (eg, the Refresh button). There are no essential controls, however;
|
||||
* even the "Refresh" button is just a diagnostic tool, to verify that the screen contents are up-to-date.
|
||||
*/
|
||||
Component.bindComponentControls(video, eVideo, C1PJSCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize every Video module on the page.
|
||||
*/
|
||||
web.onInit(C1PVideo.init);
|
||||
14
modules/c1pjs/templates/README.md
Normal file
14
modules/c1pjs/templates/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
C1Pjs Templates
|
||||
===
|
||||
Template folders contain a variety of XML and HTML templates and supporting files, including:
|
||||
|
||||
- DTD files (Document Type Definitions)
|
||||
- XSD files (XML schemas -- eventually)
|
||||
- XSL files (XML stylesheets)
|
||||
- CSS files (stylesheets that the XSL files rely upon)
|
||||
- HTML files (HTML fragments used to generate part or all of a web page)
|
||||
|
||||
[*components.xsl*](components.xsl) transforms all the elements of a machine XML file into an HTML fragment
|
||||
that includes a series of **DIV** tags with corresponding *id* and *data-value* attributes that allow our
|
||||
JavaScript components to bind themselves to visual elements (eg, virtual screen, virtual keyboard, control
|
||||
panel) on a web page.
|
||||
111
modules/c1pjs/templates/components.css
Normal file
111
modules/c1pjs/templates/components.css
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
@CHARSET "UTF-8";
|
||||
/* @author Jeff Parsons (@jeffpar)
|
||||
@website http://www.pcjs.org/
|
||||
@created 2013-05-05
|
||||
@modified 2014-03-12
|
||||
@license http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
*:not(input,textarea) {
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.c1pjs-embed {
|
||||
}
|
||||
.c1pjs-embed:after {
|
||||
clear:both;
|
||||
}
|
||||
.c1pjs-name {
|
||||
clear: both;
|
||||
font-weight: bold;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.c1pjs-canvas {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.c1pjs-container {
|
||||
color: #000000;
|
||||
position: relative;
|
||||
}
|
||||
.c1pjs-label {
|
||||
font-size: small;
|
||||
line-height: 19px;
|
||||
vertical-align: middle;
|
||||
float: left;
|
||||
font-family: "Lucida Console", monospace;
|
||||
}
|
||||
.c1pjs-control textarea {
|
||||
font-family: Monaco, monospace;
|
||||
font-size: x-small;
|
||||
}
|
||||
.c1pjs-fieldset {
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.c1pjs-flag {
|
||||
font-family: "Lucida Console", monospace;
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
line-height: 19px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.c1pjs-register {
|
||||
font-family: "Lucida Console", monospace;
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
line-height: 19px;
|
||||
vertical-align: middle;
|
||||
border: 1px solid black;
|
||||
}
|
||||
.c1pjs-switches {
|
||||
float: left;
|
||||
}
|
||||
.c1pjs-bitBucket {
|
||||
float: left;
|
||||
width: 19px;
|
||||
height: 38px;
|
||||
}
|
||||
.c1pjs-bitCell {
|
||||
float: left;
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
margin-right: -1px;
|
||||
margin-bottom: -1px;
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
line-height: 19px; /* the equivalent of "vertical-align: middle" for single-line elements */
|
||||
}
|
||||
.c1pjs-bitCellLeft {
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
.c1pjs-bitLabel {
|
||||
font-size: xx-small;
|
||||
text-align: center;
|
||||
}
|
||||
.c1pjs-description, .c1pjs-status {
|
||||
font-size: small;
|
||||
line-height: 2em;
|
||||
}
|
||||
.c1pjs-key {
|
||||
border: 1px solid black;
|
||||
font-size: x-small;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
height: 34px;
|
||||
line-height: 34px; /* the equivalent of "vertical-align: middle" for single-line elements */
|
||||
}
|
||||
.c1pjs-reference {
|
||||
float: left;
|
||||
font-size: x-small;
|
||||
}
|
||||
.c1pjs-reference a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.c1pjs-copyright {
|
||||
float: right;
|
||||
font-size: x-small;
|
||||
}
|
||||
.c1pjs-copyright a {
|
||||
text-decoration: none;
|
||||
}
|
||||
572
modules/c1pjs/templates/components.xsl
Normal file
572
modules/c1pjs/templates/components.xsl
Normal file
|
|
@ -0,0 +1,572 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- author="Jeff Parsons (@jeffpar)" website="http://www.pcjs.org/" created="2012-05-05" modified="2013-01-29" license="http://www.gnu.org/licenses/gpl.html" -->
|
||||
<!DOCTYPE xsl:stylesheet [
|
||||
<!-- XSLT understands these entities only: lt, gt, apos, quot, and amp. Other required entities may be defined below (see http://www.pcjs.org/modules/shared/templates/entities.dtd). -->
|
||||
]>
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
<xsl:param name="rootDir" select="''"/>
|
||||
<xsl:param name="generator" select="'client'"/>
|
||||
|
||||
<xsl:variable name="MACHINECLASS">c1p</xsl:variable>
|
||||
<xsl:variable name="APPCLASS">c1pjs</xsl:variable>
|
||||
<xsl:variable name="APPVERSION">1.x.x</xsl:variable>
|
||||
<xsl:variable name="SITEHOST">www.pcjs.org</xsl:variable>
|
||||
|
||||
<xsl:template name="componentStyles">
|
||||
<link rel="stylesheet" type="text/css" href="/versions/{$APPCLASS}/{$APPVERSION}/components.css"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="componentScripts">
|
||||
<xsl:param name="component"/>
|
||||
<script type="text/javascript" src="/versions/{$APPCLASS}/{$APPVERSION}/{$component}.js"></script>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="componentIncludes">
|
||||
<xsl:param name="component"/>
|
||||
<xsl:call-template name="componentScripts"><xsl:with-param name="component" select="$component"/></xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="machine[@ref]">
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/machine"><xsl:with-param name="machine" select="@id"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="machine[not(@ref)]">
|
||||
<xsl:param name="machine"><xsl:value-of select="@id"/></xsl:param>
|
||||
<div id="{$machine}" class="machine {@class}js">
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="component" select="'machine'"/>
|
||||
<xsl:with-param name="class"><xsl:value-of select="@class"/>js</xsl:with-param>
|
||||
<xsl:with-param name="parms"><xsl:if test="@parms">,<xsl:value-of select="@parms"/></xsl:if></xsl:with-param>
|
||||
<xsl:with-param name="url"><xsl:value-of select="@url"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="component[@ref]">
|
||||
<xsl:param name="machine"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/component"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="component[not(@ref)]">
|
||||
<xsl:param name="machine"/>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class" select="@class"/>
|
||||
<xsl:with-param name="parms"><xsl:if test="@parms">,<xsl:value-of select="@parms"/></xsl:if></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="component">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:param name="component" select="name(.)"/>
|
||||
<xsl:param name="class" select="''"/>
|
||||
<xsl:param name="parms" select="''"/>
|
||||
<xsl:param name="url" select="''"/>
|
||||
<xsl:variable name="id">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$component = 'machine'"><xsl:value-of select="$machine"/>.machine</xsl:when>
|
||||
<xsl:when test="$machine != ''"><xsl:value-of select="$machine"/><xsl:if test="@id">.<xsl:value-of select="@id"/></xsl:if></xsl:when>
|
||||
<xsl:when test="@id"><xsl:value-of select="@id"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="name">
|
||||
<xsl:choose>
|
||||
<xsl:when test="name"><xsl:value-of select="name"/></xsl:when>
|
||||
<xsl:when test="@name"><xsl:value-of select="@name"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="border">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@border = '1'">border:1px solid black;border-radius:10px;</xsl:when>
|
||||
<xsl:when test="@border">border:<xsl:value-of select="@border"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="left">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@left">left:<xsl:value-of select="@left"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="top">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@top">top:<xsl:value-of select="@top"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="width">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@width">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$left != '' or $top != ''">width:<xsl:value-of select="@width"/>;</xsl:when>
|
||||
<xsl:otherwise>width:auto;max-width:<xsl:value-of select="@width"/>;</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="height">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@height">height:<xsl:value-of select="@height"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="padding">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@padding">padding:<xsl:value-of select="@padding"/>;</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:if test="@padtop">padding-top:<xsl:value-of select="@padtop"/>;</xsl:if>
|
||||
<xsl:if test="@padright">padding-right:<xsl:value-of select="@padright"/>;</xsl:if>
|
||||
<xsl:if test="@padbottom">padding-bottom:<xsl:value-of select="@padbottom"/>;</xsl:if>
|
||||
<xsl:if test="@padleft">padding-left:<xsl:value-of select="@padleft"/>;</xsl:if>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="pos">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@pos = 'left'">float:left;</xsl:when>
|
||||
<xsl:when test="@pos = 'right'">float:right;</xsl:when>
|
||||
<xsl:when test="@pos = 'center'">margin:0 auto;</xsl:when>
|
||||
<xsl:when test="@pos">position:<xsl:value-of select="@pos"/>;</xsl:when>
|
||||
<xsl:when test="$left != '' or $top != ''">position:absolute;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="style">
|
||||
<xsl:if test="$component = 'machine'">overflow:auto;width:100%;</xsl:if>
|
||||
<xsl:if test="@style"><xsl:value-of select="@style"/></xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="componentClass">
|
||||
<xsl:value-of select="$APPCLASS"/><xsl:text>-</xsl:text><xsl:value-of select="$component"/><xsl:text> </xsl:text><xsl:value-of select="$APPCLASS"/><xsl:text>-component</xsl:text>
|
||||
</xsl:variable>
|
||||
<div id="{$id}" class="{$componentClass}" style="{$width}{$height}{$pos}{$left}{$top}{$padding}">
|
||||
<xsl:if test="$component = 'machine'">
|
||||
<xsl:apply-templates select="name" mode="machine"/>
|
||||
</xsl:if>
|
||||
<xsl:if test="$component != 'machine'">
|
||||
<xsl:apply-templates select="name" mode="component"/>
|
||||
</xsl:if>
|
||||
<div class="{$APPCLASS}-container" style="{$border}{$style}">
|
||||
<xsl:if test="$class != '' and $component != 'machine'">
|
||||
<div class="{$APPCLASS}-{$class}-object" data-value="id:'{$id}',name:'{$name}'{$parms}"></div>
|
||||
</xsl:if>
|
||||
<xsl:if test="control">
|
||||
<div class="{$APPCLASS}-controls">
|
||||
<xsl:apply-templates select="control" mode="component"/>
|
||||
</div>
|
||||
</xsl:if>
|
||||
<xsl:apply-templates><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</div>
|
||||
<xsl:if test="$component = 'machine'">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$url != ''"><div class="{$APPCLASS}-reference">[<a href="{$url}">XML</a>]</div></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
<div class="{$APPCLASS}-copyright">
|
||||
<a href="http://{$SITEHOST}/{$APPCLASS}" target="_blank">C1Pjs</a> v<xsl:value-of select="$APPVERSION"/> © 2012-2014 by <a href="http://twitter.com/jeffpar" target="_blank">@jeffpar</a>
|
||||
</div>
|
||||
<div style="clear:both"></div>
|
||||
</xsl:if>
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="name" mode="machine">
|
||||
<xsl:variable name="pos">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@pos = 'center'">text-align:center;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<h2 style="{$pos}"><xsl:apply-templates/></h2>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="name" mode="component">
|
||||
<div class="{$APPCLASS}-name"><xsl:apply-templates/></div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="control" mode="component">
|
||||
<xsl:variable name="type">
|
||||
type:'<xsl:value-of select="@type"/>'
|
||||
</xsl:variable>
|
||||
<xsl:variable name="binding">
|
||||
binding:'<xsl:value-of select="@binding"/>'
|
||||
</xsl:variable>
|
||||
<xsl:variable name="border">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@border = '1'">border:1px solid black;</xsl:when>
|
||||
<xsl:when test="@border">border:<xsl:value-of select="@border"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="width">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@width">width:<xsl:value-of select="@width"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="height">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@height">height:<xsl:value-of select="@height"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="left">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@left">left:<xsl:value-of select="@left"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="top">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@top">top:<xsl:value-of select="@top"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="pos">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$left != '' or $top != ''">position:absolute;</xsl:when>
|
||||
<xsl:when test="@pos = 'left'">float:left;</xsl:when>
|
||||
<xsl:when test="@pos = 'right'">float:right;</xsl:when>
|
||||
<xsl:when test="@pos = 'center'">margin:0 auto;</xsl:when>
|
||||
<xsl:when test="@pos"><xsl:value-of select="@pos"/>;</xsl:when>
|
||||
<xsl:otherwise><xsl:if test="$left = ''">float:left;</xsl:if></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="style">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@style"><xsl:value-of select="@style"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<div class="{$APPCLASS}-control" style="{$pos}{$left}{$top}">
|
||||
<xsl:variable name="fontsize">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@size = 'large'">font-size:<xsl:value-of select="@size"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="subclass">
|
||||
<xsl:if test="@label"><xsl:text> </xsl:text><xsl:value-of select="$APPCLASS"/><xsl:text>-label</xsl:text></xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="labelwidth">
|
||||
<xsl:if test="@labelwidth">width:<xsl:value-of select="@labelwidth"/>;</xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="labelstyle">
|
||||
<xsl:if test="@labelstyle"><xsl:value-of select="@labelstyle"/></xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:if test="@label">
|
||||
<xsl:if test="not(@labelpos) or @labelpos = 'left'">
|
||||
<div class="{$APPCLASS}-label" style="{$labelwidth}{$labelstyle}"><xsl:value-of select="@label"/></div>
|
||||
</xsl:if>
|
||||
</xsl:if>
|
||||
<xsl:choose>
|
||||
<xsl:when test="@type = 'button'">
|
||||
<button class="{$APPCLASS}-{@class}" style="{$border}{$width}{$height}{$fontsize}{$style}" data-value="{$type},{$binding}"><xsl:apply-templates/></button>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'list'">
|
||||
<select class="{$APPCLASS}-{@class}" style="{$border}{$width}{$height}{$fontsize}{$style}" data-value="{$type},{$binding}">
|
||||
<xsl:apply-templates select="item" mode="component"/>
|
||||
</select>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'text'">
|
||||
<input class="{$APPCLASS}-{@class}" type="text" style="{$border}{$width}{$height}{$style}" data-value="{$type},{$binding}" value="" autocapitalize="off" autocorrect="off"/>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'submit'">
|
||||
<input class="{$APPCLASS}-{@class}" type="submit" style="{$border}{$fontsize}{$style}" data-value="{$type},{$binding}" value="{.}"/>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'textarea'">
|
||||
<textarea class="{$APPCLASS}-{@class}" style="{$border}{$width}{$height}{$style}" data-value="{$type},{$binding}" readonly="readonly"></textarea>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'heading'">
|
||||
<div><xsl:value-of select="."/></div>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'file'">
|
||||
<form class="{$APPCLASS}-{@class}" style="{$border}{$width}{$height}{$style}" data-value="{$type},{$binding}">
|
||||
<fieldset class="{$APPCLASS}-fieldset">
|
||||
<input type="file"/>
|
||||
<input type="submit" value="Load" disabled="true"/>
|
||||
</fieldset>
|
||||
</form>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'separator'">
|
||||
<hr/>
|
||||
</xsl:when>
|
||||
<xsl:when test="not(@type)">
|
||||
<div style="clear:both"></div><br/>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<div class="{$APPCLASS}-{@class}{$subclass}" style="{$border}{$width}{$height}{$style}" data-value="{$type},{$binding}"><xsl:apply-templates/></div>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
<xsl:if test="@label">
|
||||
<xsl:if test="@labelpos = 'right'">
|
||||
<div class="{$APPCLASS}-label" style="{$labelwidth}{$labelstyle}"><xsl:value-of select="@label"/></div>
|
||||
</xsl:if>
|
||||
<div style="clear:both"></div>
|
||||
</xsl:if>
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="item" mode="component">
|
||||
<option value="{@ref}"><xsl:value-of select="."/></option>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="name">
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="control">
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="cpu[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/cpu"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="cpu[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="autoStart">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@autostart"><xsl:value-of select="@autostart"/></xsl:when>
|
||||
<xsl:otherwise>null</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class" select="'cpu'"/>
|
||||
<xsl:with-param name="parms">,autoStart:<xsl:value-of select="$autoStart"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="keyboard[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/keyboard"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="keyboard[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="model">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@model"><xsl:value-of select="@model"/></xsl:when>
|
||||
<xsl:otherwise>600</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">keyboard</xsl:with-param>
|
||||
<xsl:with-param name="parms">,model:<xsl:value-of select="$model"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="serial[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/serial"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="serial[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="demo">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@demo"><xsl:value-of select="@demo"/></xsl:when>
|
||||
<xsl:otherwise>false</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">serial</xsl:with-param>
|
||||
<xsl:with-param name="parms">,demo:<xsl:value-of select="$demo"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="disk[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/disk"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="disk[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">disk</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="rom[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/rom"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="rom[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="size">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@size"><xsl:value-of select="@size"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="image">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@image"><xsl:value-of select="@image"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">rom</xsl:with-param>
|
||||
<xsl:with-param name="parms">,size:<xsl:value-of select="$size"/>,image:'<xsl:value-of select="$image"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="ram[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/ram"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="ram[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="size">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@size"><xsl:value-of select="@size"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">ram</xsl:with-param>
|
||||
<xsl:with-param name="parms">,size:<xsl:value-of select="$size"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="video[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/video"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="video[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="model">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@model"><xsl:value-of select="@model"/></xsl:when>
|
||||
<xsl:otherwise>600</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="screenWidth">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@screenwidth"><xsl:value-of select="@screenwidth"/></xsl:when>
|
||||
<xsl:otherwise>256</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="screenHeight">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@screenheight"><xsl:value-of select="@screenheight"/></xsl:when>
|
||||
<xsl:otherwise>224</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="charCols">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@cols"><xsl:value-of select="@cols"/></xsl:when>
|
||||
<xsl:otherwise>32</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="charRows">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@rows"><xsl:value-of select="@rows"/></xsl:when>
|
||||
<xsl:otherwise>32</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="charWidth">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@charwidth"><xsl:value-of select="@charwidth"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="charHeight">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@charheight"><xsl:value-of select="@charheight"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="charSet">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@charset"><xsl:value-of select="@charset"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="screenColor">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@screencolor"><xsl:value-of select="@screencolor"/></xsl:when>
|
||||
<xsl:otherwise>black</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">video</xsl:with-param>
|
||||
<xsl:with-param name="parms">,model:<xsl:value-of select="$model"/>,screenWidth:<xsl:value-of select="$screenWidth"/>,screenHeight:<xsl:value-of select="$screenHeight"/>,charCols:<xsl:value-of select="$charCols"/>,charRows:<xsl:value-of select="$charRows"/>,charWidth:<xsl:value-of select="$charWidth"/>,charHeight:<xsl:value-of select="$charHeight"/>,charSet:'<xsl:value-of select="$charSet"/>',screenColor:'<xsl:value-of select="$screenColor"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="debugger[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/debugger"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="debugger[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">debugger</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="panel[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/panel"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="panel[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">panel</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="computer[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/computer"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="computer[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="modules">
|
||||
[<xsl:for-each select="module">
|
||||
{<xsl:call-template name="module"/>}
|
||||
<xsl:if test="position() != last()">,</xsl:if>
|
||||
</xsl:for-each>]
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">computer</xsl:with-param>
|
||||
<xsl:with-param name="parms">,modules:<xsl:value-of select="$modules"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="module">
|
||||
type:'<xsl:value-of select="@type"/>',refID:'<xsl:value-of select="@refid"/>'<xsl:if test="@start">,start:<xsl:value-of select="@start"/>,end:<xsl:value-of select="@end"/></xsl:if>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
31
modules/diskdump/README.md
Normal file
31
modules/diskdump/README.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
DiskDump
|
||||
===
|
||||
|
||||
**DiskDump** is a Node module with both a command-line interface and a web server API for converting disk images
|
||||
to/from various formats (eg, JSON files, JSON files with comments, IMG disk images, etc).
|
||||
|
||||
Building Disk Images from Folders/Files
|
||||
---
|
||||
In addition to converting disk images to/from JSON, DiskDump can also create disk images from the contents of local
|
||||
files/folders.
|
||||
|
||||
For example, from the root directory of the project, you could run:
|
||||
|
||||
node modules/diskdump/bin/diskdump --path="apps/pc/1981/visicalc/README.md" --format=img --output=disk.img
|
||||
|
||||
to produce a `disk.img` containing one file, "README.md", which you could then mount on your local operating
|
||||
system *or* inside a PCjs machine.
|
||||
|
||||
To make the disk image more useful, you might want to download a copy of [VisiCalc](http://www.bricklin.com/history/vcexecutable.htm)
|
||||
into that folder as well, so that you could then run:
|
||||
|
||||
node modules/diskdump/bin/diskdump --path="apps/pc/1981/visicalc/vc.com;README.md" --format=img --output=disk.img
|
||||
|
||||
to produce a `disk.img` containing both "VC.COM" and "README.md". In fact, this is exactly how the
|
||||
[disk.json](/apps/pc/1981/visicalc/disk.json) stored in the [VisiCalc](/apps/pc/1981/visicalc/) folder was generated.
|
||||
|
||||
The equivalent web server API request would look like:
|
||||
|
||||
http://localhost:8088/api/v1/dump?path=/apps/pc/1981/visicalc/vc.com;README.md&format=img
|
||||
|
||||
DiskDump is a port of the original [JavaScript Machines](http://jsmachines.net/) **convdisk.php** utility.
|
||||
40
modules/diskdump/bin/diskdump
Normal file
40
modules/diskdump/bin/diskdump
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview Implements the DiskDump command-line interface
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Sep-04
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var lib = path.join(path.dirname(fs.realpathSync(__filename)), "../lib/");
|
||||
|
||||
require(lib + "diskdump.js").CLI();
|
||||
2721
modules/diskdump/lib/diskdump.js
Normal file
2721
modules/diskdump/lib/diskdump.js
Normal file
File diff suppressed because it is too large
Load diff
19
modules/diskdump/package.json
Normal file
19
modules/diskdump/package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "diskdump",
|
||||
"version": "0.2.0",
|
||||
"description": "Converts disk images to/from JSON",
|
||||
"main": "./lib/diskdump",
|
||||
"scripts": {
|
||||
"test": "echo \"error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Jeff Parsons <Jeff@pcjs.org>",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "GPLv3",
|
||||
"url": "http://www.gnu.org/licenses/gpl.html"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"diskdump": "./bin/diskdump"
|
||||
}
|
||||
}
|
||||
3
modules/filedump/README.md
Normal file
3
modules/filedump/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
FileDump
|
||||
===
|
||||
Module (and command-line utility) for converting the contents of files to JSON.
|
||||
40
modules/filedump/bin/filedump
Normal file
40
modules/filedump/bin/filedump
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview Implements the FileDump command-line interface
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Sep-04
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var lib = path.join(path.dirname(fs.realpathSync(__filename)), "../lib/");
|
||||
|
||||
require(lib + "filedump.js").CLI();
|
||||
673
modules/filedump/lib/filedump.js
Normal file
673
modules/filedump/lib/filedump.js
Normal file
|
|
@ -0,0 +1,673 @@
|
|||
/**
|
||||
* @fileoverview Converts file contents to JSON
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
|
||||
* @version 1.0
|
||||
* Created 2014-02-01
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var mkdirp = require("mkdirp");
|
||||
var net = require("../../shared/lib/netlib");
|
||||
var proc = require("../../shared/lib/proclib");
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||
|
||||
/**
|
||||
* FileDump()
|
||||
*
|
||||
* TODO: Consider adding a "map" option that allows the user to supply a MAP filename (via a "map" API parameter
|
||||
* or a "--map" command-line option), which in turn triggers a call to loadMap(). Note that loadMap() will need
|
||||
* to be a bit more general and use a worker function that calls either net.getFile() or fs.readFile(), similar
|
||||
* to what our loadFile() function already does.
|
||||
*
|
||||
* @constructor
|
||||
* @param {string|undefined} sFormat should be one of "json"|"data"|"hex"|"bytes"|"rom" (see the FORMAT constants)
|
||||
* @param {boolean|string|undefined} fComments enables comments and other readability enhancements in the JSON output
|
||||
* @param {boolean|string|undefined} fDecimal forces decimal output if not undefined
|
||||
* @param {string} [sServerRoot]
|
||||
*/
|
||||
function FileDump(sFormat, fComments, fDecimal, sServerRoot)
|
||||
{
|
||||
this.fDebug = false;
|
||||
this.sFormat = (sFormat || DumpAPI.FORMAT.JSON);
|
||||
this.fJSONNative = (this.sFormat == DumpAPI.FORMAT.JSON && !fComments);
|
||||
this.nJSONIndent = 0;
|
||||
this.fJSONComments = fComments;
|
||||
this.sJSONWhitespace = (this.fJSONComments? " " : "");
|
||||
this.fDecimal = fDecimal;
|
||||
this.sServerRoot = sServerRoot || process.cwd();
|
||||
this.buf = null;
|
||||
/*
|
||||
* TODO: Decide what to do with this usage info; we can't use it as a default, because setting this.json
|
||||
* causes outputFile() to ignore this.buf indiscriminately (ie, it breaks non-JSON output modes).
|
||||
*/
|
||||
this.json = ""; // "[\n /**\n * " + FileDump.sAPIURL + " " + FileDump.sCopyright + "\n * " + FileDump.sUsage + "\n */\n]";
|
||||
}
|
||||
|
||||
/*
|
||||
* Class constants
|
||||
*/
|
||||
FileDump.sAPIURL = "http://www.pcjs.org" + DumpAPI.ENDPOINT;
|
||||
FileDump.sCopyright = "© 2012-2014 by Jeff Parsons (@jeffpar)";
|
||||
FileDump.sNotice = FileDump.sAPIURL + " " + FileDump.sCopyright;
|
||||
FileDump.sUsage = "Usage: " + FileDump.sAPIURL + "?" + DumpAPI.QUERY.FILE + "=({path}|{URL})&" + DumpAPI.QUERY.FORMAT + "=(json|data|hex|bytes|rom)";
|
||||
|
||||
FileDump.asBadExts = [
|
||||
"js", "log"
|
||||
];
|
||||
|
||||
/*
|
||||
* Class methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* CLI()
|
||||
*
|
||||
* Provides the command-line interface for the FileDump module.
|
||||
*
|
||||
* Usage
|
||||
* ---
|
||||
* filedump --file=({path}|{URL}) [--merge=({path}|{url})] [--format=(json|data|hex|bytes|rom)] [--comments]
|
||||
* [--decimal] [--output={path}] [--overwrite]
|
||||
*
|
||||
* Arguments
|
||||
* ---
|
||||
* The default format is "json", which generates an array of signed 32-bit decimal values; "hex" is an older
|
||||
* text format that consists entirely of 2-character hex values (deprecated), and "bytes" is a JSON-like format
|
||||
* that also uses hex values (but with "0x" prefixes) and is normally used only when comments are enabled (use
|
||||
* --decimal to force decimal byte output).
|
||||
*
|
||||
* When a second file is "merged", the first file sets all even bytes and the second file sets all odd bytes.
|
||||
* In fact, any number of files can be merged: if there are N files, file #1 sets bytes at "offset mod N == 0",
|
||||
* file #2 sets all bytes at "offset mod N == 1", and file #N sets all bytes at "offset mod N == N - 1".
|
||||
*
|
||||
* Note that command-line arguments, if any, are not validated. For example, argv['comments'] may be any of
|
||||
* boolean, string, or undefined, since the user may have typed "--comments" or "--comments=foo" or nothing at all.
|
||||
*
|
||||
* Examples
|
||||
* ---
|
||||
* filedump --file=devices/pc/video/ibm-ega.rom --format=bytes --decimal
|
||||
*
|
||||
* Notes
|
||||
* ---
|
||||
* Originally, we had to specify `--format=bytes` because the onLoadROM() code in rom.js assumed the data was
|
||||
* always byte-sized, but it has since been updated to support dword arrays, so the default format ("json")
|
||||
* works fine as well. Also, `--decimal` reduces the size of the output file significantly.
|
||||
*
|
||||
* If there's a ".map" file (eg, "ibm-ega.map"), it's automatically loaded and appended to the ROM data as a
|
||||
* "symbols" property; we may want to consider an option to disable the processing of map files, but for now, the
|
||||
* simple answer is: if you don't want one, don't create one.
|
||||
*/
|
||||
FileDump.CLI = function()
|
||||
{
|
||||
var args = proc.getArgs();
|
||||
|
||||
if (!args.argc) {
|
||||
console.log("usage: filedump --file=({path}|{URL}) [--merge=({path}|{url})] [--format=(json|data|hex|bytes|rom)] [--comments] [--decimal] [--output={path}] [--overwrite]");
|
||||
return;
|
||||
}
|
||||
|
||||
var argv = args.argv;
|
||||
var sFile = argv['file'];
|
||||
if (!sFile || FileDump.asBadExts.indexOf(str.getExtension(sFile)) >= 0) {
|
||||
FileDump.logError(new Error("bad or missing input filename"));
|
||||
return;
|
||||
}
|
||||
|
||||
var sOutputFile = argv['output'];
|
||||
if (typeof sOutputFile != "string") {
|
||||
FileDump.logError(new Error("bad or missing output filename"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (sOutputFile && sOutputFile.charAt(0) != '/') sOutputFile = path.join(process.cwd(), sOutputFile);
|
||||
var fOverwrite = argv['overwrite'];
|
||||
|
||||
var sFormat = FileDump.validateFormat(argv['format']);
|
||||
if (sFormat === false) {
|
||||
FileDump.logError(new Error("unrecognized format"));
|
||||
return;
|
||||
}
|
||||
|
||||
var sMergeFile, asMergeFiles = [];
|
||||
var file = new FileDump(sFormat, argv['comments'], argv['decimal']);
|
||||
if (argv['merge']) {
|
||||
if (typeof argv['merge'] == "string") {
|
||||
asMergeFiles.push(argv['merge']);
|
||||
} else {
|
||||
for (sMergeFile in argv['merge']) asMergeFiles.push(sMergeFile);
|
||||
}
|
||||
}
|
||||
var cMergesPending = asMergeFiles.length;
|
||||
var iStart = 0, nSkip = cMergesPending;
|
||||
file.loadFile(sFile, iStart++, nSkip, function(err) {
|
||||
if (!err) {
|
||||
var cErrors = 0;
|
||||
while ((sMergeFile = asMergeFiles.shift())) {
|
||||
file.loadFile(sMergeFile, iStart++, nSkip, function(err) {
|
||||
if (err) cErrors++;
|
||||
if (!--cMergesPending) {
|
||||
if (!cErrors) file.convertToFile(sOutputFile, fOverwrite);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!cMergesPending) file.convertToFile(sOutputFile, fOverwrite);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* logError(err)
|
||||
*
|
||||
* Conditionally logs an error to the console.
|
||||
*
|
||||
* @param {Error} err
|
||||
* @return {string} the error message that was logged (or that would have been logged had logging been enabled)
|
||||
*/
|
||||
FileDump.logError = function(err)
|
||||
{
|
||||
var sError = "";
|
||||
if (err) {
|
||||
sError = "filedump error: " + err.message;
|
||||
console.log(sError);
|
||||
}
|
||||
return sError;
|
||||
};
|
||||
|
||||
/**
|
||||
* validateFormat(sFormat)
|
||||
*
|
||||
* @param {string} sFormat
|
||||
* @return {null|string|boolean} the validated format, null if unspecified, or false if invalid
|
||||
*/
|
||||
FileDump.validateFormat = function(sFormat)
|
||||
{
|
||||
if (!sFormat) {
|
||||
return null;
|
||||
}
|
||||
for (var s in DumpAPI.FORMAT) {
|
||||
if (sFormat == DumpAPI.FORMAT[s]) return sFormat;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/*
|
||||
* Object methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* loadFile(sFile, iStart, nSkip, done)
|
||||
*
|
||||
* This used to be part of the FileDump constructor, but I felt it would be safer to separate
|
||||
* object creation from any I/O that the object may perform, to ensure that a callback can never
|
||||
* be called before the caller has actually received the newly created object.
|
||||
*
|
||||
* @this {FileDump}
|
||||
* @param {string} sFile
|
||||
* @param {number} iStart
|
||||
* @param {number} nSkip
|
||||
* @param {function(Error)} done
|
||||
*/
|
||||
FileDump.prototype.loadFile = function(sFile, iStart, nSkip, done)
|
||||
{
|
||||
/*
|
||||
* Since we don't include an 'options' object (with an 'encoding' property) between
|
||||
* the sFilePath and callback parameters to readFile(), the callback's 2nd parameter will
|
||||
* be a Buffer object rather than a String -- and so we call it 'buf' instead of 'data'.
|
||||
*/
|
||||
var obj = this;
|
||||
var sFilePath = net.isRemote(sFile)? sFile : path.join(this.sServerRoot, sFile);
|
||||
|
||||
if (!this.sFilePath) this.sFilePath = sFilePath;
|
||||
if (this.fDebug) console.log("loadFile(" + sFilePath + "," + iStart + "," + nSkip + ")");
|
||||
|
||||
if (net.isRemote(sFilePath)) {
|
||||
net.getFile(sFilePath, null, function(err, status, buf) {
|
||||
if (err) {
|
||||
FileDump.logError(err);
|
||||
done(err);
|
||||
return;
|
||||
}
|
||||
obj.setData(buf, iStart, nSkip);
|
||||
done(null);
|
||||
});
|
||||
} else {
|
||||
fs.readFile(sFilePath, function(err, buf) {
|
||||
if (err) {
|
||||
FileDump.logError(err);
|
||||
done(err);
|
||||
return;
|
||||
}
|
||||
obj.setData(buf, iStart, nSkip);
|
||||
done(null);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* setData(buf, iStart, nSkip)
|
||||
*
|
||||
* Records the given file data in the FileDump's buffer
|
||||
*
|
||||
* @this {FileDump}
|
||||
* @param {Buffer} buf
|
||||
* @param {number} iStart
|
||||
* @param {number} nSkip
|
||||
*/
|
||||
FileDump.prototype.setData = function(buf, iStart, nSkip)
|
||||
{
|
||||
if (!this.buf) {
|
||||
if (!nSkip) {
|
||||
this.buf = buf;
|
||||
} else {
|
||||
this.buf = new Buffer(buf.length * (nSkip + 1));
|
||||
}
|
||||
}
|
||||
if (nSkip) {
|
||||
var b;
|
||||
for (var i = 0; i < buf.length; i++) {
|
||||
this.buf.writeUInt8(b = buf.readUInt8(i), iStart);
|
||||
iStart += nSkip + 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* dumpLine(nIndent, sLine, sComment)
|
||||
*
|
||||
* @this {FileDump}
|
||||
* @param {number} [nIndent] is the relative number of characters to indent the given line (0 if none)
|
||||
* @param {string} [sLine] is the given line
|
||||
* @param {string} [sComment] is an optional comment to append to the line, if comment output is enabled
|
||||
* @return {string} the indented/commented line
|
||||
*/
|
||||
FileDump.prototype.dumpLine = function(nIndent, sLine, sComment)
|
||||
{
|
||||
if (nIndent < 0) {
|
||||
this.nJSONIndent += nIndent;
|
||||
}
|
||||
if (this.fJSONComments) {
|
||||
sLine = " ".substr(0, this.nJSONIndent) + (sLine? (sLine + (sComment? (" // " + sComment) : "")) : "");
|
||||
}
|
||||
if (sLine) sLine += "\n";
|
||||
if (nIndent > 0) {
|
||||
this.nJSONIndent += nIndent;
|
||||
}
|
||||
return sLine;
|
||||
};
|
||||
|
||||
/**
|
||||
* dumpBuffer(sKey, buf, len, cbItem, offData)
|
||||
*
|
||||
* @this {FileDump}
|
||||
* @param {string|null} sKey is name of buffer data element
|
||||
* @param {Buffer} buf is a Buffer containing the bytes to dump
|
||||
* @param {number} len is the number of bytes to dump
|
||||
* @param {number} cbItem is either 1 or 4, to dump bytes or dwords respectively
|
||||
* @param {number} [offData] is a relative offset of this data within the parent (for display purposes only)
|
||||
* @return {string} hex (or decimal) representation of the data
|
||||
*/
|
||||
FileDump.prototype.dumpBuffer = function(sKey, buf, len, cbItem, offData)
|
||||
{
|
||||
var chOpen = '', chClose = '', chSep = ' ', sHexPrefix = "";
|
||||
|
||||
this.sKey = sKey;
|
||||
|
||||
if (this.sFormat != DumpAPI.FORMAT.HEX) {
|
||||
chOpen = '['; chClose = ']'; chSep = ','; sHexPrefix = "0x";
|
||||
}
|
||||
|
||||
var sDump = this.dumpLine(2, (sKey? '"' + sKey + '":' : "") + this.sJSONWhitespace + chOpen);
|
||||
|
||||
var sLine = "";
|
||||
var sASCII = "";
|
||||
var cMaxCols = 16 * cbItem;
|
||||
if (offData === undefined) offData = 0;
|
||||
|
||||
/*
|
||||
* TODO: Assert that off is always < buf.length as well.
|
||||
*/
|
||||
for (var off = 0; off < len; off += cbItem) {
|
||||
|
||||
var v = (cbItem == 1? buf.readUInt8(off) : buf.readInt32LE(off));
|
||||
|
||||
if (off) {
|
||||
sLine += chSep;
|
||||
if (!(off % cMaxCols)) { // jshint ignore:line
|
||||
sDump += this.dumpLine(0, sLine, sASCII);
|
||||
sLine = sASCII = "";
|
||||
}
|
||||
}
|
||||
if (cbItem > 1) {
|
||||
sLine += v;
|
||||
}
|
||||
else {
|
||||
if (this.fDecimal) {
|
||||
sLine += v;
|
||||
} else {
|
||||
sLine += sHexPrefix + str.toHexByte(v);
|
||||
}
|
||||
if (!sASCII) sASCII = "0x" + str.toHex(offData + off) + " ";
|
||||
sASCII += (v >= 0x20 && v < 0x7F && v != 0x3C && v != 0x3E? String.fromCharCode(v) : ".");
|
||||
}
|
||||
}
|
||||
|
||||
sDump += this.dumpLine(0, sLine + chClose, sASCII);
|
||||
this.dumpLine(-2);
|
||||
|
||||
return sDump;
|
||||
};
|
||||
|
||||
/**
|
||||
* loadMap(sFilePath, done)
|
||||
*
|
||||
* NOTE: Since ".map" files are an internal construct, I support only local map files (for now)
|
||||
*
|
||||
* @this {FileDump}
|
||||
* @param {string} sFilePath
|
||||
* @param {function(Error,string)} done
|
||||
*/
|
||||
FileDump.prototype.loadMap = function(sFilePath, done)
|
||||
{
|
||||
/*
|
||||
* The HEX format doesn't support MAP files. For all other (JSON) formats, we assume that the JSON
|
||||
* is "unwrapped" at this point, and that even if loadMap() doesn't find a map file, it will still wrap
|
||||
* the resulting JSON with braces.
|
||||
*/
|
||||
if (this.sFormat != DumpAPI.FORMAT.HEX) {
|
||||
var obj = this;
|
||||
var sMapPath = sFilePath.replace(/\.(rom|json)$/, ".map");
|
||||
if (str.endsWith(sMapPath, ".map")) {
|
||||
var sMapFile = path.basename(sMapPath);
|
||||
fs.readFile(sMapPath, {encoding: "utf8"}, function(err, str) {
|
||||
var sMapData = null;
|
||||
if (err) {
|
||||
/*
|
||||
* This isn't really an error (map files are optional), although it might be helpful to display
|
||||
* a warning. In any case, this is also why the first done() callback below always passes null for
|
||||
* the Error parameter.
|
||||
*
|
||||
FileDump.logError(err);
|
||||
*/
|
||||
}
|
||||
else {
|
||||
// console.log("add this to obj.json:\n" + str);
|
||||
|
||||
/*
|
||||
* Parse MAP data into a set of properties; for example, if the .map file contains:
|
||||
*
|
||||
* 0320 = HF_PORT
|
||||
* 0000:0034 4 HDISK_INT
|
||||
* 0040:0042 1 CMD_BLOCK
|
||||
* 0003 @ DISK_SETUP
|
||||
* 0000:004C 4 ORG_VECTOR
|
||||
* 0028 . MOV AX,WORD PTR ORG_VECTOR ;GET DISKETTE VECTOR
|
||||
*
|
||||
* where the symbols in the second column of the .map file indicate type/size information as follows:
|
||||
*
|
||||
* = unsized value
|
||||
* 1 1-byte (DB) value
|
||||
* 2 2-byte (DW) value
|
||||
* 4 4-byte (DD) value
|
||||
* @ label
|
||||
* . reference
|
||||
* + bias (ie, value to be added to all following offsets)
|
||||
*
|
||||
* then we should produce the following corresponding JSON:
|
||||
*
|
||||
* {
|
||||
* "HF_PORT": {
|
||||
* "v":800
|
||||
* },
|
||||
* "HDISK_INT": {
|
||||
* "b":4, "s":0, "o":52
|
||||
* },
|
||||
* "ORG_VECTOR": {
|
||||
* "b":4, "s":0, "o":76
|
||||
* },
|
||||
* "CMD_BLOCK": {
|
||||
* "b":1, "s":64, "o":66
|
||||
* },
|
||||
* "DISK_SETUP": {
|
||||
* "o":3
|
||||
* },
|
||||
* ".40": {
|
||||
* "o":64, "a":"MOV AX,WORD PTR ORG_VECTOR ;GET DISKETTE VECTOR"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* where "v" is the value of an absolute (unsized) value; "b" is either 1, 2, 4 or undefined; "s" is either a hard-coded
|
||||
* segment or undefined; and "o" is the offset of an symbol. Also, if the symbol is not entirely upper-case, then we
|
||||
* store the original-case version of the symbol as an "l" property.
|
||||
*
|
||||
* If the same symbol appears more than once in a .map file, the value of the last occurrence will replace any previous
|
||||
* occurrence(s).
|
||||
*
|
||||
* aSymbols() wil be an associative array containing an entry for every symbol, where the key is the symbol and the value
|
||||
* is another associative array containing the other properties described above.
|
||||
*/
|
||||
var nBias = 0;
|
||||
var aSymbols = {};
|
||||
var asLines = str.split('\n');
|
||||
for (var iLine = 0; iLine < asLines.length; iLine++){
|
||||
var s = asLines[iLine].trim();
|
||||
if (!s || s.charAt(0) == ';') continue;
|
||||
var match = s.match(/^\s*([0-9A-Z:]+)\s+([=124@\.\+])\s*(.*?)\s*$/i);
|
||||
if (match) {
|
||||
var sValue = match[1];
|
||||
var sSegment = null;
|
||||
var i = sValue.indexOf(':');
|
||||
if (i >= 0) {
|
||||
sSegment = sValue.substr(0, i);
|
||||
sValue = sValue.substr(i+1);
|
||||
}
|
||||
var sType = match[2];
|
||||
var sSymbol = match[3].replace(/"/g, "''");
|
||||
var sComment = null;
|
||||
i = sSymbol.indexOf(';');
|
||||
if (i >= 0) {
|
||||
sComment = sSymbol.substr(i+1).trim();
|
||||
sSymbol = sSymbol.substr(0, i).trim();
|
||||
}
|
||||
var sID = sSymbol.toUpperCase();
|
||||
var aValue = {};
|
||||
switch (sType) {
|
||||
case '=':
|
||||
aValue['v'] = parseInt(sValue, 16);
|
||||
break;
|
||||
case '1':
|
||||
case '2':
|
||||
case '4':
|
||||
aValue['b'] = parseInt(sType, 10);
|
||||
/* falls through */
|
||||
case '@':
|
||||
case '.':
|
||||
aValue['o'] = parseInt(sValue, 16) + nBias;
|
||||
if (sSegment) {
|
||||
aValue['s'] = parseInt(sSegment, 16);
|
||||
}
|
||||
if (sType != '.') break;
|
||||
match = sSymbol.match(/^([A-Z_][A-Z0-9_]*):\s*(.*)/i);
|
||||
if (match) {
|
||||
sSymbol = match[1];
|
||||
sID = sSymbol.toUpperCase();
|
||||
if (match[2]) aValue['a'] = match[2];
|
||||
if (aSymbols[sID]) {
|
||||
sID = '.' + parseInt(sValue, 16);
|
||||
}
|
||||
} else {
|
||||
aValue['a'] = sSymbol;
|
||||
sID = sSymbol = '.' + parseInt(sValue, 16);
|
||||
}
|
||||
break;
|
||||
case '+':
|
||||
nBias = parseInt(sValue, 16);
|
||||
continue;
|
||||
default:
|
||||
done(new Error("unrecognized symbol type (" + sType + ") in MAP file: " + sMapFile), null);
|
||||
return;
|
||||
}
|
||||
if (sID != sSymbol) {
|
||||
aValue['l'] = sSymbol;
|
||||
}
|
||||
if (sComment) {
|
||||
aValue['c'] = sComment;
|
||||
}
|
||||
aSymbols[sID] = aValue;
|
||||
continue;
|
||||
}
|
||||
done(new Error("unrecognized line (" + s + ") in MAP file: " + sMapFile), null);
|
||||
return;
|
||||
}
|
||||
sMapData = JSON.stringify(aSymbols);
|
||||
if (sMapData) {
|
||||
if (!obj.sKey) {
|
||||
obj.json = '"bytes":' + obj.json;
|
||||
}
|
||||
obj.json = '{' + obj.json + ',"symbols":' + sMapData + '}';
|
||||
}
|
||||
}
|
||||
if (!sMapData) {
|
||||
obj.json = '{' + obj.json + '}';
|
||||
}
|
||||
done(null, obj.json);
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.json = '{' + this.json + '}';
|
||||
}
|
||||
done(null, this.json);
|
||||
};
|
||||
|
||||
/**
|
||||
* buildJSON()
|
||||
*
|
||||
* Common code between the API helper (convertToJSON()) and the command-line helper (convertToFile()).
|
||||
*
|
||||
* @this {FileDump}
|
||||
*/
|
||||
FileDump.prototype.buildJSON = function()
|
||||
{
|
||||
this.json = "";
|
||||
if (!this.buf) {
|
||||
// console.log("no data available in file");
|
||||
this.json = "[ /* no data */ ]";
|
||||
} else {
|
||||
// console.log("length of buffer: " + this.buf.length);
|
||||
if (this.fJSONComments || this.sFormat == DumpAPI.FORMAT.HEX || this.sFormat == DumpAPI.FORMAT.BYTES) {
|
||||
this.json += this.dumpBuffer(null, this.buf, this.buf.length, 1);
|
||||
} else {
|
||||
this.json += this.dumpBuffer("data", this.buf, this.buf.length, 4);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* convertToJSON(done)
|
||||
*
|
||||
* Converts the data buffer to JSON.
|
||||
*
|
||||
* @this {FileDump}
|
||||
* @param {function(Error,string)} done
|
||||
*/
|
||||
FileDump.prototype.convertToJSON = function(done)
|
||||
{
|
||||
this.buildJSON();
|
||||
this.loadMap(this.sFilePath, done);
|
||||
};
|
||||
|
||||
/**
|
||||
* convertToFile(sOutputFile, fOverwrite)
|
||||
*
|
||||
* Converts the data buffer to JSON, as appropriate.
|
||||
*
|
||||
* @this {FileDump}
|
||||
* @param {string} sOutputFile
|
||||
* @param {boolean} fOverwrite
|
||||
*/
|
||||
FileDump.prototype.convertToFile = function(sOutputFile, fOverwrite)
|
||||
{
|
||||
if (this.sFormat != DumpAPI.FORMAT.ROM) {
|
||||
var obj = this;
|
||||
this.buildJSON();
|
||||
this.loadMap(sOutputFile || this.sFilePath, function(err, str) {
|
||||
if (err) {
|
||||
FileDump.logError(err);
|
||||
} else {
|
||||
obj.outputFile(sOutputFile, fOverwrite);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.outputFile(sOutputFile, fOverwrite);
|
||||
};
|
||||
|
||||
/**
|
||||
* outputFile(sOutputFile, fOverwrite)
|
||||
*
|
||||
* @this {FileDump}
|
||||
* @param {string} sOutputFile
|
||||
* @param {boolean} fOverwrite
|
||||
*/
|
||||
FileDump.prototype.outputFile = function(sOutputFile, fOverwrite)
|
||||
{
|
||||
var data = this.json || this.buf;
|
||||
|
||||
var sFormat = this.sFormat.toUpperCase();
|
||||
|
||||
if (sOutputFile) {
|
||||
try {
|
||||
if (fs.existsSync(sOutputFile) && !fOverwrite) {
|
||||
console.log(sOutputFile + " exists, use --overwrite to rewrite");
|
||||
} else {
|
||||
var sDirName = path.dirname(sOutputFile);
|
||||
if (!fs.existsSync(sDirName)) mkdirp.sync(sDirName);
|
||||
fs.writeFileSync(sOutputFile, data);
|
||||
console.log(data.length + "-byte " + sFormat + " file saved as " + sOutputFile);
|
||||
}
|
||||
} catch(err) {
|
||||
FileDump.logError(err);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* We'll dump JSON to the console, but not a raw file buffer; we could add an option to
|
||||
* "stringify" buffers, but if that's what the caller wants, they should use "--format=json".
|
||||
*/
|
||||
if (typeof data == "string") {
|
||||
console.log(data);
|
||||
} else {
|
||||
console.log("specify --output={file} to save " + data.length + "-byte " + sFormat + " file");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = FileDump;
|
||||
19
modules/filedump/package.json
Normal file
19
modules/filedump/package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "filedump",
|
||||
"version": "0.2.0",
|
||||
"description": "Converts file contents to JSON",
|
||||
"main": "./lib/filedump",
|
||||
"scripts": {
|
||||
"test": "echo \"error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Jeff Parsons <Jeff@pcjs.org>",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "GPLv3",
|
||||
"url": "http://www.gnu.org/licenses/gpl.html"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"filedump": "./bin/filedump"
|
||||
}
|
||||
}
|
||||
17
modules/grunts/README.md
Normal file
17
modules/grunts/README.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
Experimental Grunt Tasks
|
||||
===
|
||||
|
||||
I experimented briefly with a few Grunt tasks, but working with Grunt wasn't all that pleasant,
|
||||
and neither of these tasks are all that important right now, so they're only here for a rainy day or
|
||||
to be cannibalized for some other purpose.
|
||||
|
||||
[manifester](manifester/) was intended to open a manifest.xml file (discussed in more detail [here](/apps/))
|
||||
and download all the referenced files. The task was inspired by `npm install`, which downloads all the required
|
||||
modules specified in [package.json](/package.json) without requiring them to be checked into the project.
|
||||
However, the manifest design needs to be fleshed out more before work on this continues.
|
||||
|
||||
[prepjs](prepjs/) was intended to inline well-defined constants in all JavaScript files before running them
|
||||
through the Closure Compiler, but it turned out that:
|
||||
|
||||
- the Grunt task would quickly run out of memory
|
||||
- the Closure Compiler actually did a pretty good job inlining all by itself
|
||||
3
modules/grunts/manifester/.gitignore
vendored
Normal file
3
modules/grunts/manifester/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
npm-debug.log
|
||||
tmp
|
||||
13
modules/grunts/manifester/.jshintrc
Normal file
13
modules/grunts/manifester/.jshintrc
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"curly": true,
|
||||
"eqeqeq": true,
|
||||
"immed": true,
|
||||
"latedef": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"sub": true,
|
||||
"undef": true,
|
||||
"boss": true,
|
||||
"eqnull": true,
|
||||
"node": true
|
||||
}
|
||||
73
modules/grunts/manifester/Gruntfile.js
Normal file
73
modules/grunts/manifester/Gruntfile.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* grunt-manifester
|
||||
* https://github.com/jeffpar/jsmachines
|
||||
*
|
||||
* Copyright (c) 2014 jeffpar
|
||||
* Licensed under the MIT license.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function (grunt) {
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
jshint: {
|
||||
all: [
|
||||
'Gruntfile.js',
|
||||
'tasks/*.js',
|
||||
'<%= nodeunit.tests %>'
|
||||
],
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
}
|
||||
},
|
||||
|
||||
// Before generating any new files, remove any previously-created files.
|
||||
clean: {
|
||||
tests: ['tmp']
|
||||
},
|
||||
|
||||
// Configuration to be run (and then tested).
|
||||
manifester: {
|
||||
default_options: {
|
||||
options: {
|
||||
},
|
||||
files: {
|
||||
'tmp/default_options': ['test/fixtures/testing', 'test/fixtures/123']
|
||||
}
|
||||
},
|
||||
custom_options: {
|
||||
options: {
|
||||
separator: ': ',
|
||||
punctuation: ' !!!'
|
||||
},
|
||||
files: {
|
||||
'tmp/custom_options': ['test/fixtures/testing', 'test/fixtures/123']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Unit tests.
|
||||
nodeunit: {
|
||||
tests: ['test/*_test.js']
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Actually load this plugin's task(s).
|
||||
grunt.loadTasks('tasks');
|
||||
|
||||
// These plugins provide necessary tasks.
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-nodeunit');
|
||||
|
||||
// Whenever the "test" task is run, first clean the "tmp" dir, then run this
|
||||
// plugin's task(s), then test the result.
|
||||
grunt.registerTask('test', ['clean', 'manifester', 'nodeunit']);
|
||||
|
||||
// By default, lint and run all tests.
|
||||
grunt.registerTask('default', ['jshint', 'test']);
|
||||
|
||||
};
|
||||
22
modules/grunts/manifester/LICENSE-MIT
Normal file
22
modules/grunts/manifester/LICENSE-MIT
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2014 jeffpar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
89
modules/grunts/manifester/README.md
Normal file
89
modules/grunts/manifester/README.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# grunt-manifester
|
||||
|
||||
> manifest.xml processor
|
||||
|
||||
## Getting Started
|
||||
This plugin requires Grunt `~0.4.4`
|
||||
|
||||
If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
|
||||
|
||||
```shell
|
||||
npm install grunt-manifester --save-dev
|
||||
```
|
||||
|
||||
Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:
|
||||
|
||||
```js
|
||||
grunt.loadNpmTasks('grunt-manifester');
|
||||
```
|
||||
|
||||
## The "manifester" task
|
||||
|
||||
### Overview
|
||||
In your project's Gruntfile, add a section named `manifester` to the data object passed into `grunt.initConfig()`.
|
||||
|
||||
```js
|
||||
grunt.initConfig({
|
||||
manifester: {
|
||||
options: {
|
||||
// Task-specific options go here.
|
||||
},
|
||||
your_target: {
|
||||
// Target-specific file lists and/or options go here.
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
#### options.separator
|
||||
Type: `String`
|
||||
Default value: `', '`
|
||||
|
||||
A string value that is used to do something with whatever.
|
||||
|
||||
#### options.punctuation
|
||||
Type: `String`
|
||||
Default value: `'.'`
|
||||
|
||||
A string value that is used to do something else with whatever else.
|
||||
|
||||
### Usage Examples
|
||||
|
||||
#### Default Options
|
||||
In this example, the default options are used to do something with whatever. So if the `testing` file has the content `Testing` and the `123` file had the content `1 2 3`, the generated result would be `Testing, 1 2 3.`
|
||||
|
||||
```js
|
||||
grunt.initConfig({
|
||||
manifester: {
|
||||
options: {},
|
||||
files: {
|
||||
'dest/default_options': ['src/testing', 'src/123'],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Custom Options
|
||||
In this example, custom options are used to do something else with whatever else. So if the `testing` file has the content `Testing` and the `123` file had the content `1 2 3`, the generated result in this case would be `Testing: 1 2 3 !!!`
|
||||
|
||||
```js
|
||||
grunt.initConfig({
|
||||
manifester: {
|
||||
options: {
|
||||
separator: ': ',
|
||||
punctuation: ' !!!',
|
||||
},
|
||||
files: {
|
||||
'dest/default_options': ['src/testing', 'src/123'],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Contributing
|
||||
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).
|
||||
|
||||
## Release History
|
||||
_(Nothing yet)_
|
||||
41
modules/grunts/manifester/package.json
Normal file
41
modules/grunts/manifester/package.json
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "grunt-manifester",
|
||||
"description": "manifest.xml processor",
|
||||
"version": "0.1.0",
|
||||
"homepage": "https://github.com/jeffpar/jsmachines",
|
||||
"author": {
|
||||
"name": "jeffpar",
|
||||
"email": "jeffpar@mac.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/jeffpar/jsmachines.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/jeffpar/jsmachines/issues"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT",
|
||||
"url": "https://github.com/jeffpar/jsmachines/blob/master/LICENSE-MIT"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt-contrib-jshint": "~0.6.0",
|
||||
"grunt-contrib-clean": "~0.4.0",
|
||||
"grunt-contrib-nodeunit": "~0.2.0",
|
||||
"grunt": "~0.4.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"grunt": "~0.4.4"
|
||||
},
|
||||
"keywords": [
|
||||
"gruntplugin"
|
||||
]
|
||||
}
|
||||
134
modules/grunts/manifester/tasks/manifester.js
Normal file
134
modules/grunts/manifester/tasks/manifester.js
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
/**
|
||||
* grunt-manifester
|
||||
* https://github.com/jeffpar/jsmachines
|
||||
*
|
||||
* Copyright (c) 2014 jeffpar
|
||||
* Licensed under the MIT license.
|
||||
*
|
||||
* TODO: Update this header with our standard header and fix all the JSHint warnings
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var mkdirp = require("mkdirp");
|
||||
var url = require("url");
|
||||
var async = require("async");
|
||||
var parseXML = require("xml2js").parseString; // see: https://github.com/Leonidas-from-XIV/node-xml2js
|
||||
var unzip = require("unzip");
|
||||
var util = require("util");
|
||||
var net = require("../../../shared/lib/netlib");
|
||||
|
||||
module.exports = function (grunt) {
|
||||
|
||||
/*
|
||||
* Please see the Grunt documentation for more information regarding task
|
||||
* creation: http://gruntjs.com/creating-tasks
|
||||
*/
|
||||
grunt.registerMultiTask('manifester', 'manifest.xml processor', function() {
|
||||
|
||||
/*
|
||||
* Merge task-specific and/or target-specific options with these defaults
|
||||
*
|
||||
var options = this.options({
|
||||
});
|
||||
*/
|
||||
|
||||
/*
|
||||
* Tell grunt this task is asynchronous
|
||||
*/
|
||||
var asManifests = [];
|
||||
var doneGrunt = this.async();
|
||||
|
||||
/*
|
||||
* Iterate over all specified file groups
|
||||
*/
|
||||
this.files.forEach(function(file) {
|
||||
|
||||
file.src.filter(function(sFilePath) {
|
||||
/*
|
||||
* Warn on and remove invalid source files (if nonull was set)
|
||||
*/
|
||||
if (!grunt.file.exists(sFilePath)) {
|
||||
grunt.log.warn('Source file "' + sFilePath + '" not found.');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).map(function(sFilePath) {
|
||||
/*
|
||||
* TODO: Given the memory constraints I've run into with Grunt in the past, it would
|
||||
* probably be better queue up the file paths instead of the file contents, and do async
|
||||
* readFile() calls.
|
||||
*/
|
||||
asManifests.push(sFilePath);
|
||||
});
|
||||
});
|
||||
|
||||
async.each(asManifests, function processXML(sManifestFile, doneAsync) {
|
||||
var sManifestXML = grunt.file.read(sManifestFile);
|
||||
parseXML(sManifestXML, function doneParseXML(err, xml) {
|
||||
var cCallbacks = 0;
|
||||
if (xml.manifest) {
|
||||
// console.log(util.inspect(xml, false, null));
|
||||
for (var iRepo in xml.manifest.repo) {
|
||||
var repo = xml.manifest.repo[iRepo];
|
||||
if (repo.src) {
|
||||
var src = repo.src[0];
|
||||
var sURL = src.$['href'];
|
||||
// var sURL = src._ || src; // the former is set if there are any attributes, otherwise the element is just a string
|
||||
console.log('found src URL: "' + sURL + '"');
|
||||
if (sURL.slice(0, 5) == "http:" && sURL.slice(-1) == '/') {
|
||||
for (var iDownload in repo.download) {
|
||||
var download = repo.download[iDownload];
|
||||
var sDownloadFile = download.$['href'];
|
||||
// var sDownloadFile = download._ || download;
|
||||
console.log("processing download " + sDownloadFile);
|
||||
var sRemoteFile = sURL + sDownloadFile;
|
||||
var sLocalDir = path.join(path.dirname(sManifestFile), repo.$['dir']);
|
||||
if (grunt.file.exists(sLocalDir) || mkdirp.sync(sLocalDir)) {
|
||||
var sLocalFile = path.join(sLocalDir, sDownloadFile);
|
||||
if (grunt.file.exists(sLocalFile)) {
|
||||
console.log("file already exists: " + sLocalFile);
|
||||
} else {
|
||||
console.log('downloadFile("' + sRemoteFile + '", "' + sLocalFile + '")...');
|
||||
cCallbacks++;
|
||||
net.downloadFile(sRemoteFile, sLocalFile, function doneDownloadFile(err, status) {
|
||||
console.log('downloadFile("' + sRemoteFile + '") returned ' + status + ': ' + (err? false : true));
|
||||
if (!err && status == 200 && sLocalFile.slice(-4) == ".zip") {
|
||||
var sLocalZipDir = path.join(sLocalDir, path.basename(sLocalFile, ".zip"));
|
||||
if (grunt.file.exists(sLocalZipDir) || mkdirp.sync(sLocalZipDir)) {
|
||||
/*
|
||||
* TODO: As explained here (https://github.com/EvanOxfeld/node-unzip/issues/40), determine why this ZIP
|
||||
* file (http://beej.us/moria/files/pc/zip-arc/mor55-88.zip) causes an "invalid stored block lengths" error.
|
||||
*/
|
||||
fs.createReadStream(sLocalFile).pipe(unzip.Extract({path: sLocalZipDir})).on('close', function() {
|
||||
console.log("unzip complete: " + sLocalZipDir);
|
||||
if (--cCallbacks == 0) doneAsync();
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
grunt.log.warn("unzip directory not available: " + sLocalZipDir);
|
||||
}
|
||||
}
|
||||
if (--cCallbacks == 0) doneAsync();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
grunt.log.warn("download directory not available: " + sLocalDir);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
grunt.log.warn("unsupported repo src: " + sURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!cCallbacks) doneAsync();
|
||||
});
|
||||
}, function(err) {
|
||||
doneGrunt();
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
1
modules/grunts/manifester/test/expected/custom_options
Normal file
1
modules/grunts/manifester/test/expected/custom_options
Normal file
|
|
@ -0,0 +1 @@
|
|||
Testing: 1 2 3 !!!
|
||||
1
modules/grunts/manifester/test/expected/default_options
Normal file
1
modules/grunts/manifester/test/expected/default_options
Normal file
|
|
@ -0,0 +1 @@
|
|||
Testing, 1 2 3.
|
||||
1
modules/grunts/manifester/test/fixtures/123
vendored
Normal file
1
modules/grunts/manifester/test/fixtures/123
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
1 2 3
|
||||
1
modules/grunts/manifester/test/fixtures/testing
vendored
Normal file
1
modules/grunts/manifester/test/fixtures/testing
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
Testing
|
||||
48
modules/grunts/manifester/test/manifester_test.js
Normal file
48
modules/grunts/manifester/test/manifester_test.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
'use strict';
|
||||
|
||||
var grunt = require('grunt');
|
||||
|
||||
/*
|
||||
======== A Handy Little Nodeunit Reference ========
|
||||
https://github.com/caolan/nodeunit
|
||||
|
||||
Test methods:
|
||||
test.expect(numAssertions)
|
||||
test.done()
|
||||
Test assertions:
|
||||
test.ok(value, [message])
|
||||
test.equal(actual, expected, [message])
|
||||
test.notEqual(actual, expected, [message])
|
||||
test.deepEqual(actual, expected, [message])
|
||||
test.notDeepEqual(actual, expected, [message])
|
||||
test.strictEqual(actual, expected, [message])
|
||||
test.notStrictEqual(actual, expected, [message])
|
||||
test.throws(block, [error], [message])
|
||||
test.doesNotThrow(block, [error], [message])
|
||||
test.ifError(value)
|
||||
*/
|
||||
|
||||
exports.manifester = {
|
||||
setUp: function(done) {
|
||||
// setup here if necessary
|
||||
done();
|
||||
},
|
||||
default_options: function(test) {
|
||||
test.expect(1);
|
||||
|
||||
var actual = grunt.file.read('tmp/default_options');
|
||||
var expected = grunt.file.read('test/expected/default_options');
|
||||
test.equal(actual, expected, 'should describe what the default behavior is.');
|
||||
|
||||
test.done();
|
||||
},
|
||||
custom_options: function(test) {
|
||||
test.expect(1);
|
||||
|
||||
var actual = grunt.file.read('tmp/custom_options');
|
||||
var expected = grunt.file.read('test/expected/custom_options');
|
||||
test.equal(actual, expected, 'should describe what the custom option(s) behavior is.');
|
||||
|
||||
test.done();
|
||||
},
|
||||
};
|
||||
13
modules/grunts/prepjs/.jshintrc
Normal file
13
modules/grunts/prepjs/.jshintrc
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"curly": true,
|
||||
"eqeqeq": true,
|
||||
"immed": true,
|
||||
"latedef": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"sub": true,
|
||||
"undef": true,
|
||||
"boss": true,
|
||||
"eqnull": true,
|
||||
"node": true
|
||||
}
|
||||
143
modules/grunts/prepjs/Gruntfile.js
Normal file
143
modules/grunts/prepjs/Gruntfile.js
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* grunt-prepjs
|
||||
* https://github.com/jeffpar/jsmachines
|
||||
*
|
||||
* Copyright (c) 2014 jeffpar
|
||||
* Licensed under the MIT license.
|
||||
*
|
||||
* Genesis:
|
||||
*
|
||||
* sudo npm install -g grunt-init
|
||||
* git clone git://github.com/gruntjs/grunt-init-gruntplugin.git ~/.grunt-init/gruntplugin
|
||||
* cd ~/Sites/pcjs/modules
|
||||
* mkdir -p grunts/prepjs
|
||||
* cd grunts/prepjs
|
||||
* grunt-init gruntplugin
|
||||
* npm install
|
||||
*
|
||||
* This file was generated at the "grunt-init gruntplugin" stage. Here's what that process looked like:
|
||||
*
|
||||
* Running "init:gruntplugin" (init) task
|
||||
* This task will create one or more files in the current directory, based on the
|
||||
* environment and the answers to a few questions. Note that answering "?" to any
|
||||
* question will show question-specific help and answering "none" to most questions
|
||||
* will leave its value blank.
|
||||
*
|
||||
* "gruntplugin" template notes:
|
||||
* For more information about Grunt plugin best practices, please see the docs at
|
||||
* http://gruntjs.com/creating-plugins
|
||||
*
|
||||
* Please answer the following:
|
||||
* [?] Project name (grunt-prep) grunt-prepjs
|
||||
* [?] Description (The best Grunt plugin ever.) JS Preprocessor
|
||||
* [?] Version (0.1.0)
|
||||
* [?] Project git repository (git://github.com/jeffpar/jsmachines.git)
|
||||
* [?] Project homepage (https://github.com/jeffpar/jsmachines)
|
||||
* [?] Project issues tracker (https://github.com/jeffpar/jsmachines/issues)
|
||||
* [?] Licenses (MIT)
|
||||
* [?] Author name (jeffpar)
|
||||
* [?] Author email (jeffpar@mac.com)
|
||||
* [?] Author url (none)
|
||||
* [?] What versions of grunt does it require? (~0.4.4)
|
||||
* [?] What versions of node does it run on? (>= 0.8.0)
|
||||
* [?] Do you need to make any changes to the above before continuing? (y/N)
|
||||
*
|
||||
* Writing .gitignore...OK
|
||||
* Writing .jshintrc...OK
|
||||
* Writing Gruntfile.js...OK
|
||||
* Writing README.md...OK
|
||||
* Writing tasks/prepjs.js...OK
|
||||
* Writing test/expected/custom_options...OK
|
||||
* Writing test/expected/default_options...OK
|
||||
* Writing test/fixtures/123...OK
|
||||
* Writing test/fixtures/testing...OK
|
||||
* Writing test/prepjs_test.js...OK
|
||||
* Writing LICENSE-MIT...OK
|
||||
* Writing package.json...OK
|
||||
*
|
||||
* Initialized from template "gruntplugin".
|
||||
* You should now install project dependencies with npm install. After that, you
|
||||
* may execute project tasks with grunt. For more information about installing
|
||||
* and configuring Grunt, please see the Getting Started guide:
|
||||
*
|
||||
* http://gruntjs.com/getting-started
|
||||
*
|
||||
* Done, without errors.
|
||||
*
|
||||
* The process was a bit sloppy about trailing commas, though. I've cleaned those up, thanks to PhpStorm.
|
||||
*
|
||||
* Online tutorials further recommended the following in my project's root:
|
||||
*
|
||||
* npm install modules/grunts/prepjs --save-dev
|
||||
*
|
||||
* However, all that does is make another copy of my "grunt-prepjs" module, inside the "node_modules" folders;
|
||||
* I can avoid that by simply including the following in my root Gruntfile.js:
|
||||
*
|
||||
* grunt.loadTasks("modules/grunts/prepjs/tasks");
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function (grunt) {
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
jshint: {
|
||||
all: [
|
||||
'Gruntfile.js',
|
||||
'tasks/*.js',
|
||||
'<%= nodeunit.tests %>'
|
||||
],
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
}
|
||||
},
|
||||
|
||||
// Before generating any new files, remove any previously-created files.
|
||||
clean: {
|
||||
tests: ['tmp']
|
||||
},
|
||||
|
||||
// Configuration to be run (and then tested).
|
||||
prepjs: {
|
||||
default_options: {
|
||||
options: {
|
||||
},
|
||||
files: {
|
||||
'tmp/default_options': ['test/fixtures/testing', 'test/fixtures/123']
|
||||
}
|
||||
},
|
||||
custom_options: {
|
||||
options: {
|
||||
separator: ': ',
|
||||
punctuation: ' !!!'
|
||||
},
|
||||
files: {
|
||||
'tmp/custom_options': ['test/fixtures/testing', 'test/fixtures/123']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Unit tests.
|
||||
nodeunit: {
|
||||
tests: ['test/*_test.js']
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Actually load this plugin's task(s).
|
||||
grunt.loadTasks('tasks');
|
||||
|
||||
// These plugins provide necessary tasks.
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-nodeunit');
|
||||
|
||||
// Whenever the "test" task is run, first clean the "tmp" dir, then run this
|
||||
// plugin's task(s), then test the result.
|
||||
grunt.registerTask('test', ['clean', 'prepjs', 'nodeunit']);
|
||||
|
||||
// By default, lint and run all tests.
|
||||
grunt.registerTask('default', ['jshint', 'test']);
|
||||
|
||||
};
|
||||
22
modules/grunts/prepjs/LICENSE-MIT
Normal file
22
modules/grunts/prepjs/LICENSE-MIT
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2014 jeffpar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
89
modules/grunts/prepjs/README.md
Normal file
89
modules/grunts/prepjs/README.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# grunt-prepjs
|
||||
|
||||
> JS Preprocessor
|
||||
|
||||
## Getting Started
|
||||
This plugin requires Grunt `~0.4.4`
|
||||
|
||||
If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
|
||||
|
||||
```shell
|
||||
npm install grunt-prepjs --save-dev
|
||||
```
|
||||
|
||||
Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:
|
||||
|
||||
```js
|
||||
grunt.loadNpmTasks('grunt-prepjs');
|
||||
```
|
||||
|
||||
## The "prepjs" task
|
||||
|
||||
### Overview
|
||||
In your project's Gruntfile, add a section named `prepjs` to the data object passed into `grunt.initConfig()`.
|
||||
|
||||
```js
|
||||
grunt.initConfig({
|
||||
prepjs: {
|
||||
options: {
|
||||
// Task-specific options go here.
|
||||
},
|
||||
your_target: {
|
||||
// Target-specific file lists and/or options go here.
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
#### options.separator
|
||||
Type: `String`
|
||||
Default value: `', '`
|
||||
|
||||
A string value that is used to do something with whatever.
|
||||
|
||||
#### options.punctuation
|
||||
Type: `String`
|
||||
Default value: `'.'`
|
||||
|
||||
A string value that is used to do something else with whatever else.
|
||||
|
||||
### Usage Examples
|
||||
|
||||
#### Default Options
|
||||
In this example, the default options are used to do something with whatever. So if the `testing` file has the content `Testing` and the `123` file had the content `1 2 3`, the generated result would be `Testing, 1 2 3.`
|
||||
|
||||
```js
|
||||
grunt.initConfig({
|
||||
prepjs: {
|
||||
options: {},
|
||||
files: {
|
||||
'dest/default_options': ['src/testing', 'src/123'],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Custom Options
|
||||
In this example, custom options are used to do something else with whatever else. So if the `testing` file has the content `Testing` and the `123` file had the content `1 2 3`, the generated result in this case would be `Testing: 1 2 3 !!!`
|
||||
|
||||
```js
|
||||
grunt.initConfig({
|
||||
prepjs: {
|
||||
options: {
|
||||
separator: ': ',
|
||||
punctuation: ' !!!',
|
||||
},
|
||||
files: {
|
||||
'dest/default_options': ['src/testing', 'src/123'],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Contributing
|
||||
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).
|
||||
|
||||
## Release History
|
||||
_(Nothing yet)_
|
||||
41
modules/grunts/prepjs/package.json
Normal file
41
modules/grunts/prepjs/package.json
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "grunt-prepjs",
|
||||
"description": "JS Preprocessor",
|
||||
"version": "0.1.0",
|
||||
"homepage": "https://github.com/jeffpar/jsmachines",
|
||||
"author": {
|
||||
"name": "jeffpar",
|
||||
"email": "jeffpar@mac.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/jeffpar/jsmachines.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/jeffpar/jsmachines/issues"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT",
|
||||
"url": "https://github.com/jeffpar/jsmachines/blob/master/LICENSE-MIT"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt-contrib-jshint": "~0.6.0",
|
||||
"grunt-contrib-clean": "~0.4.0",
|
||||
"grunt-contrib-nodeunit": "~0.2.0",
|
||||
"grunt": "~0.4.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"grunt": "~0.4.4"
|
||||
},
|
||||
"keywords": [
|
||||
"gruntplugin"
|
||||
]
|
||||
}
|
||||
401
modules/grunts/prepjs/tasks/prepjs.js
Normal file
401
modules/grunts/prepjs/tasks/prepjs.js
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
/**
|
||||
* @fileoverview Pre-process JavaScript file(s) with well-defined constants inlined
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
|
||||
* @version 1.1
|
||||
* Created 2014-Mar-22
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of C1Pjs, PCjs, and other related components written
|
||||
* by Jeff Parsons and originally published at cpusim.org and jsmachines.net.
|
||||
*
|
||||
* C1Pjs and PCjs are free software: you can redistribute them and/or modify
|
||||
* them under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* C1Pjs and PCjs are distributed in the hope that they will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with C1Pjs and PCjs. If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source
|
||||
* code file of every copy or modified version of this work, and to display
|
||||
* that copyright notice on every screen that loads or runs any version
|
||||
* of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some C1Pjs and PCjs files also attempt to load external resource files, such
|
||||
* as character-image files and ROM files. Those external resource files are
|
||||
* not considered part of the PCjs program for purposes of the GNU General Public
|
||||
* License, and the author does not claim any copyright as to their contents.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Options
|
||||
* ---
|
||||
* The 'includeObjectConstants' option allows replacement of constants defined
|
||||
* within objects, instead of only global constants. Use with caution, because
|
||||
* constants defined within an object scope may not be unique. This does attempt
|
||||
* to catch any constant collisions and completely disable their replacement, but
|
||||
* it's not foolproof.
|
||||
*
|
||||
* History
|
||||
* ---
|
||||
* Although we run all our code through Google's Closure Compiler, which does a
|
||||
* great job of inlining not only variables but also code to improve performance,
|
||||
* JavaScript doesn't have the notion of "constants", so we have to define all our
|
||||
* constants as properties and simply trust that the Closure Compiler will inline
|
||||
* them all. I'm not completely trusting, so I've created this script that allows
|
||||
* us to verify the compiler produces substantially the same code whether or not
|
||||
* we inline all our constants first.
|
||||
*
|
||||
* See "inline.php" for the original PHP version of this module.
|
||||
*
|
||||
* Conventions
|
||||
* ---
|
||||
* This script looks for global constant definitions of the form "Component.XXX = YYY;".
|
||||
*
|
||||
* The current approach requires all inlined constants to be defined as properties on
|
||||
* the associated class constructor (ie, as "class constants"). Life might be simpler
|
||||
* if the Closure Compiler honored "@const" references for properties, and who knows,
|
||||
* perhaps the latest version does now; but on the other hand, having my own convention
|
||||
* relieves me from having to annotate every single constant with a "@const" JSDoc tag.
|
||||
*
|
||||
* TODO: Update the C1Pjs sources to use class constants instead of "object constants",
|
||||
* because in order for C1Pjs to benefit from constant inlining, we must reply on the
|
||||
* 'includeObjectConstants' option (hack) to expand the contexts that constants may live
|
||||
* in, which is inherently less safe. Moreover, that option prevents us from removing
|
||||
* the original constant definitions, because constants like "this.PORT_CRA" could be used
|
||||
* in other contexts that this script will NOT catch (eg, "controller.PORT_CRA").
|
||||
*
|
||||
* TODO: Think about adding another quick hack to this tool, to convert all:
|
||||
*
|
||||
* at-param {Debugger} dbg
|
||||
* to:
|
||||
* at-param {Component} dbg
|
||||
*
|
||||
* prior to compilation. The only reason I declared my "dbg" variables generically,
|
||||
* as Component objects rather than Debugger objects, was to work around compilation
|
||||
* errors in the non-Debugger builds.
|
||||
*
|
||||
* Implementation
|
||||
* ---
|
||||
* This script uses a very simplistic replacement approach that doesn't perform any
|
||||
* parsing, tokenizing or other pre-processing of the source code, which would otherwise
|
||||
* be required if we wanted to guarantee that all our replacements precisely mirrored what
|
||||
* JavaScript actually replaces at run-time. For example, JavaScript allows any so-called
|
||||
* constant to be redefined at any point, and we don't attempt to catch modifications.
|
||||
*
|
||||
* This is why it's important that we limit inlining to only those constants described
|
||||
* above, and why such constants must never be altered by the code using them.
|
||||
*
|
||||
* Debugging
|
||||
* ===
|
||||
* Use the following command to debug this task (after making Chrome your default browser):
|
||||
*
|
||||
* node-debug $(which grunt) prepjs
|
||||
*
|
||||
* which required installing "node-inspector" first:
|
||||
*
|
||||
* sudo npm install -g node-inspector
|
||||
*
|
||||
* You may also want to enable the heapdump code below, which required installing "heapdump" first:
|
||||
*
|
||||
* npm install heapdump --save-dev
|
||||
*
|
||||
* TODO: Resolve once and for all the "process out of memory" error that occurs if we don't divide
|
||||
* the src input into smaller chunks. See the WARNING below.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// var heapdump = require("heapdump");
|
||||
|
||||
/**
|
||||
* compareConstants(a, b)
|
||||
*
|
||||
* @param {Array} a
|
||||
* @param {Array} b
|
||||
* @returns {number}
|
||||
*/
|
||||
var compareConstants = function(a, b)
|
||||
{
|
||||
return b[0].length - a[0].length;
|
||||
};
|
||||
|
||||
/**
|
||||
* indexOfConstant(sConstant, aConstants)
|
||||
*
|
||||
* @param {string} sConstant
|
||||
* @param {Array} aConstants
|
||||
* @returns {number} index of aConstants entry, or -1 if not found
|
||||
*/
|
||||
var indexOfConstant = function(sConstant, aConstants)
|
||||
{
|
||||
for (var i = 0; i < aConstants.length; i++) {
|
||||
if (sConstant == aConstants[i][0]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* findConstants(sInput)
|
||||
*
|
||||
* @param {string} sInput
|
||||
* @param {Array} aConstants
|
||||
* @param {boolean} fObjectConstants
|
||||
* @param {boolean} fReplaceConstants
|
||||
* @param {function} [fnLog]
|
||||
* @returns {string} modified input
|
||||
*/
|
||||
var findConstants = function(sInput, aConstants, fObjectConstants, fReplaceConstants, fnLog) {
|
||||
var sWarning = "";
|
||||
var sConstDef = "[A-Z][A-Za-z0-9_]*";
|
||||
if (fObjectConstants) {
|
||||
sConstDef = "(?:" + sConstDef + "|this)";
|
||||
}
|
||||
var aConstDef;
|
||||
var reConstDef = new RegExp("[ \t]*(" + sConstDef + "\\.[A-Z_][A-Z0-9_\\.]*)\\s*=\\s*(.*?)\\s*;[\t ]*(?://[^\n]*|)\n", "g");
|
||||
while (aConstDef = reConstDef.exec(sInput)) {
|
||||
var i;
|
||||
var sFind = aConstDef[1];
|
||||
var sReplace = aConstDef[2];
|
||||
if (fReplaceConstants && !fObjectConstants) {
|
||||
sInput = sInput.substr(0, aConstDef.index) + sInput.substr(aConstDef.index + aConstDef[0].length);
|
||||
reConstDef.lastIndex -= aConstDef[0].length;
|
||||
}
|
||||
if ((i = indexOfConstant(sFind, aConstants)) >= 0) {
|
||||
sWarning += "/*\n * warning: multiple definitions for '" + sFind + "' (" + sReplace + ")\n */\n";
|
||||
aConstants[i][2] = -1; // set a negative replacement count to disable this constant definition
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
* If the replacement string is entirely quoted, or parenthesized, or a single constant, then we can leave
|
||||
* the replacement string as-is; otherwise, let's wrap it with parentheses (we could probably wrap everything
|
||||
* with parentheses, but I like to avoid doing that whenever it's completely unnecessary).
|
||||
*/
|
||||
if (!sReplace.match(/^["'\(].*["'\)]$/) && sReplace.match(/[^A-Za-z0-9\.]/)) {
|
||||
sReplace = "(" + sReplace + ")";
|
||||
}
|
||||
if (fnLog) {
|
||||
fnLog("found '" + sFind + "' => '" + sReplace + "'");
|
||||
/*
|
||||
if (sFind == "Video.CRT.CURSOR_END") {
|
||||
fnLog("here's where we usually run of memory (" + process.cwd() + ")");
|
||||
heapdump.writeSnapshot();
|
||||
}
|
||||
*/
|
||||
}
|
||||
aConstants.push([sFind, sReplace, 0]);
|
||||
}
|
||||
if (sWarning) sInput = sWarning + sInput;
|
||||
return sInput;
|
||||
};
|
||||
|
||||
/**
|
||||
* replaceConstants(sInput, aConstants, fObjectConstants, fInConstant, fnLog)
|
||||
*
|
||||
* @param {string} sInput
|
||||
* @param {Array} aConstants
|
||||
* @param {boolean} fObjectConstants
|
||||
* @param {boolean} [fInConstant]
|
||||
* @param {function} [fnLog]
|
||||
* @returns {string} modified input
|
||||
*/
|
||||
var replaceConstants = function(sInput, aConstants, fObjectConstants, fInConstant, fnLog)
|
||||
{
|
||||
do {
|
||||
var cReplacements = 0;
|
||||
for (var i = 0; i < aConstants.length; i++) {
|
||||
if (aConstants[i][2] < 0) continue; // skip any replacement for which we recorded multiple definitions (ie, negative replacement count)
|
||||
var sFind = aConstants[i][0];
|
||||
var sReplace = aConstants[i][1];
|
||||
var cchFind = sFind.length;
|
||||
var cchReplace = sReplace.length;
|
||||
var iNext = 0;
|
||||
while ((iNext = sInput.indexOf(sFind, iNext)) >= 0) {
|
||||
if (fObjectConstants) {
|
||||
/*
|
||||
* As discussed earlier, the 'includeObjectConstants' option precludes removing any constant definitions,
|
||||
* so we must additionally ensure that we don't inadvertently perform replacements on those definitions.
|
||||
*/
|
||||
if (sInput.substr(iNext, 1024).match(/([A-Za-z][A-Za-z0-9_]*\.[A-Z_][A-Z0-9_\.]*)\s*=\s*(.*?)\s*;[\t ]*(?:\/\/[^\n]*|)\n/)) {
|
||||
iNext += cchFind;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (fnLog) {
|
||||
fnLog("replaced '" + sFind + "' with '" + sReplace + "'");
|
||||
}
|
||||
sInput = sInput.substr(0, iNext) + sReplace + sInput.substr(iNext + cchFind);
|
||||
aConstants[i][2]++;
|
||||
cReplacements++;
|
||||
iNext += cchReplace;
|
||||
}
|
||||
/*
|
||||
* If we've just done any replacements within another constant, then start the process over again
|
||||
* with the longest constant, to ensure we don't perform any partial replacements.
|
||||
*/
|
||||
if (fInConstant && cReplacements) break;
|
||||
}
|
||||
} while (cReplacements);
|
||||
return sInput;
|
||||
};
|
||||
|
||||
module.exports = function(grunt) {
|
||||
|
||||
// Please see the Grunt documentation for more information regarding task
|
||||
// creation: http://gruntjs.com/creating-tasks
|
||||
|
||||
grunt.registerMultiTask('prepjs', 'JS Preprocessor', function() {
|
||||
/*
|
||||
* Merge task-specific and/or target-specific options with these defaults.
|
||||
*/
|
||||
var options = this.options({
|
||||
includeObjectConstants: false,
|
||||
listConstants: true,
|
||||
replaceConstants: true
|
||||
});
|
||||
/*
|
||||
* Iterate over all specified file groups.
|
||||
*
|
||||
* See http://gruntjs.com/inside-tasks#this.files, which says in part:
|
||||
*
|
||||
* Your task should iterate over the this.files array, utilizing the src and dest
|
||||
* properties of each object in that array. The this.files property will always be an array.
|
||||
* The src property will also always be an array, in case your task cares about multiple
|
||||
* source files per destination file.
|
||||
*
|
||||
* And http://gruntjs.com/configuring-tasks#files-array-format, which explains that all files
|
||||
* objects support src and dest but the 'Files Array' format supports a few additional properties:
|
||||
*
|
||||
* filter: Either a valid fs.Stats method name or a function that is passed the matched
|
||||
* src filepath and returns true or false;
|
||||
*
|
||||
* nonull: If set to true then the operation will include non-matching patterns. Combined
|
||||
* with grunt's --verbose flag, this option can help debug file path issues;
|
||||
*
|
||||
* dot: Allow patterns to match filenames starting with a period, even if the pattern does
|
||||
* not explicitly have a period in that spot;
|
||||
*
|
||||
* matchBase: If set, patterns without slashes will be matched against the basename of the path
|
||||
* if it contains slashes. For example, a?b would match the path /xyz/123/acb, but not /xyz/acb/123;
|
||||
*
|
||||
* expand: Process a dynamic src-dest file mapping, see "Building the files object dynamically"
|
||||
* for more information.
|
||||
*/
|
||||
this.files.forEach(function(file) {
|
||||
|
||||
/*
|
||||
* Allow any of our options to be set at the target level as well
|
||||
*/
|
||||
var fObjectConstants = file.includeObjectConstants;
|
||||
if (fObjectConstants === undefined) fObjectConstants = options.includeObjectConstants;
|
||||
|
||||
var fListConstants = file.listConstants;
|
||||
if (fListConstants === undefined) fListConstants = options.listConstants;
|
||||
|
||||
var fReplaceConstants = file.replaceConstants;
|
||||
if (fReplaceConstants === undefined) fReplaceConstants = options.replaceConstants;
|
||||
|
||||
/*
|
||||
* Read all file contents into src
|
||||
*/
|
||||
var src = file.src.filter(function(sFilePath) {
|
||||
/*
|
||||
* Warn on and remove invalid source files (if nonull was set)
|
||||
*/
|
||||
if (!grunt.file.exists(sFilePath)) {
|
||||
grunt.log.warn('Source file "' + sFilePath + '" not found.');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).map(function(sFilePath) {
|
||||
/*
|
||||
* Read a file
|
||||
*/
|
||||
return grunt.file.read(sFilePath);
|
||||
}).join("\n");
|
||||
|
||||
/*
|
||||
* Find all the constants in src, and remove their definitions from src if possible
|
||||
* (ie, if fReplaceConstants is true and fObjectConstants is false).
|
||||
*
|
||||
* aConstants is the array of constant definitions, where each definition is another
|
||||
* 3-element array:
|
||||
*
|
||||
* [0]: the original string (ie, the name of the constant)
|
||||
* [1]: the replacement string (ie, the value of the constant)
|
||||
* [2]: a replacement count, initialized to zero; set to -1 if a duplicate is found
|
||||
*
|
||||
* WARNING: Grunt (ie, Node) fails with the following error:
|
||||
*
|
||||
* FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory
|
||||
*
|
||||
* when processing a large src stream (eg, on the order 1.5Mb), and it always seems to
|
||||
* die in findConstants()'s call to RegExp's exec() method. I couldn't glean any clues
|
||||
* from the heapdump (other than observing that, yes, running Grunt generates a shitload of
|
||||
* objects and is probably not very well-tuned), so I've implemented a simple work-around:
|
||||
* divide the src stream into two parts. If necessary, this work-around can easily be
|
||||
* generalized to N parts. If the problem is a direct side-effect of passing very large
|
||||
* strings to the RegExp library, then this is clearly one way to avoid that problem.
|
||||
*/
|
||||
var aConstants = [];
|
||||
var i = src.length/2;
|
||||
i = src.indexOf("\n", i) + 1; // divide the src after the first linefeed beyond the midpoint
|
||||
var src1 = src.substr(0, i);
|
||||
var src2 = src.substr(i);
|
||||
// grunt.log.writeln("findConstants(src1): " + src1.length + " chars");
|
||||
src1 = findConstants(src1, aConstants, fObjectConstants, fReplaceConstants);
|
||||
// grunt.log.writeln("findConstants(src2): " + src2.length + " chars");
|
||||
src2 = findConstants(src2, aConstants, fObjectConstants, fReplaceConstants);
|
||||
|
||||
/*
|
||||
* Sort the constants in order of longest definition to shortest, because we perform all the replacements
|
||||
* in array order, and we can't allow definitions that are subsets of longer definitions to be replaced first.
|
||||
*/
|
||||
// grunt.log.writeln("aConstants.sort()");
|
||||
aConstants.sort(compareConstants);
|
||||
|
||||
// grunt.log.writeln("replacing constants in other constants");
|
||||
aConstants.forEach(function(constant) {
|
||||
constant[1] = replaceConstants(constant[1], aConstants, fObjectConstants, true);
|
||||
});
|
||||
|
||||
if (fReplaceConstants) {
|
||||
// grunt.log.writeln("replaceConstants(src1)");
|
||||
src1 = replaceConstants(src1, aConstants, fObjectConstants, false);
|
||||
// grunt.log.writeln("replaceConstants(src2)");
|
||||
src2 = replaceConstants(src2, aConstants, fObjectConstants, false);
|
||||
// grunt.log.writeln("replaceConstants() complete");
|
||||
}
|
||||
|
||||
var sListing = "";
|
||||
|
||||
if (fListConstants) {
|
||||
// grunt.log.writeln("listing constants");
|
||||
sListing += "/*\n * List of grunt-prepjs replacements:\n *\n";
|
||||
for (i = 0; i < aConstants.length; i++) {
|
||||
sListing += " * " + aConstants[i][0] + " => " + aConstants[i][1] + " (" + aConstants[i][2] + " occurrences)\n";
|
||||
}
|
||||
sListing += " */\n";
|
||||
}
|
||||
|
||||
/*
|
||||
* Write the destination file
|
||||
*/
|
||||
// grunt.log.writeln("writing " + f.dest);
|
||||
grunt.file.write(file.dest, sListing + src1 + src2);
|
||||
|
||||
/*
|
||||
* Print a success message
|
||||
*/
|
||||
grunt.log.writeln('File "' + file.dest + '" created.');
|
||||
});
|
||||
});
|
||||
};
|
||||
1
modules/grunts/prepjs/test/expected/custom_options
Normal file
1
modules/grunts/prepjs/test/expected/custom_options
Normal file
|
|
@ -0,0 +1 @@
|
|||
Testing: 1 2 3 !!!
|
||||
1
modules/grunts/prepjs/test/expected/default_options
Normal file
1
modules/grunts/prepjs/test/expected/default_options
Normal file
|
|
@ -0,0 +1 @@
|
|||
Testing, 1 2 3.
|
||||
1
modules/grunts/prepjs/test/fixtures/123
vendored
Normal file
1
modules/grunts/prepjs/test/fixtures/123
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
1 2 3
|
||||
1
modules/grunts/prepjs/test/fixtures/testing
vendored
Normal file
1
modules/grunts/prepjs/test/fixtures/testing
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
Testing
|
||||
48
modules/grunts/prepjs/test/prepjs_test.js
Normal file
48
modules/grunts/prepjs/test/prepjs_test.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
'use strict';
|
||||
|
||||
var grunt = require('grunt');
|
||||
|
||||
/*
|
||||
======== A Handy Little Nodeunit Reference ========
|
||||
https://github.com/caolan/nodeunit
|
||||
|
||||
Test methods:
|
||||
test.expect(numAssertions)
|
||||
test.done()
|
||||
Test assertions:
|
||||
test.ok(value, [message])
|
||||
test.equal(actual, expected, [message])
|
||||
test.notEqual(actual, expected, [message])
|
||||
test.deepEqual(actual, expected, [message])
|
||||
test.notDeepEqual(actual, expected, [message])
|
||||
test.strictEqual(actual, expected, [message])
|
||||
test.notStrictEqual(actual, expected, [message])
|
||||
test.throws(block, [error], [message])
|
||||
test.doesNotThrow(block, [error], [message])
|
||||
test.ifError(value)
|
||||
*/
|
||||
|
||||
exports.prepjs = {
|
||||
setUp: function(done) {
|
||||
// setup here if necessary
|
||||
done();
|
||||
},
|
||||
default_options: function(test) {
|
||||
test.expect(1);
|
||||
|
||||
var actual = grunt.file.read('tmp/default_options');
|
||||
var expected = grunt.file.read('test/expected/default_options');
|
||||
test.equal(actual, expected, 'should describe what the default behavior is.');
|
||||
|
||||
test.done();
|
||||
},
|
||||
custom_options: function(test) {
|
||||
test.expect(1);
|
||||
|
||||
var actual = grunt.file.read('tmp/custom_options');
|
||||
var expected = grunt.file.read('test/expected/custom_options');
|
||||
test.equal(actual, expected, 'should describe what the custom option(s) behavior is.');
|
||||
|
||||
test.done();
|
||||
},
|
||||
};
|
||||
10
modules/htmlout/README.md
Normal file
10
modules/htmlout/README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
HTMLOut
|
||||
===
|
||||
This module provides a filter() function for [server.js](../../../server.js),
|
||||
our Express-based web server. The function is installed like so:
|
||||
|
||||
app.use(HTMLOut.filter);
|
||||
|
||||
The filter function examines the URL, and if it corresponds to a directory on the server,
|
||||
the function will generate an "index.html" in that directory, based on the contents of a
|
||||
default template file (currently [common.html](../shared/templates/common.html)).
|
||||
7
modules/htmlout/bin/delete_indexes.sh
Executable file
7
modules/htmlout/bin/delete_indexes.sh
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# This script was created for the Grunt "delete-indexes" task in /Gruntfile.js; it lives here
|
||||
# because htmlout is the module responsible for "littering" the project with "index.html" files,
|
||||
# therefore it bears responsibility for cleaning them up. This is its "poor man's" solution.
|
||||
#
|
||||
find . -name "index.html" -exec grep -H -l -e "<title>pcjs.org" {} \; | sed -E "s/(.*)/rm -v \"\1\"/" | bash
|
||||
40
modules/htmlout/bin/htmlout
Normal file
40
modules/htmlout/bin/htmlout
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview Implements the HTMLOut command-line interface
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Sep-04
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var lib = path.join(path.dirname(fs.realpathSync(__filename)), "../lib/");
|
||||
|
||||
require(lib + "htmlout.js").CLI();
|
||||
1918
modules/htmlout/lib/htmlout.js
Normal file
1918
modules/htmlout/lib/htmlout.js
Normal file
File diff suppressed because it is too large
Load diff
1142
modules/htmlout/lib/httpapi.js
Normal file
1142
modules/htmlout/lib/httpapi.js
Normal file
File diff suppressed because it is too large
Load diff
19
modules/htmlout/package.json
Normal file
19
modules/htmlout/package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "htmlout",
|
||||
"version": "0.1.0",
|
||||
"description": "Filters HTTP requests and creates default documents from HTML templates",
|
||||
"main": "./lib/htmlout",
|
||||
"scripts": {
|
||||
"test": "echo \"error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Jeff Parsons <Jeff@pcjs.org>",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "GPLv3",
|
||||
"url": "http://www.gnu.org/licenses/gpl.html"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"example": "./bin/htmlout"
|
||||
}
|
||||
}
|
||||
5
modules/markout/README.md
Normal file
5
modules/markout/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
MarkOut
|
||||
===
|
||||
This modules transforms a subset of Markdown into HTML. It's used by the
|
||||
[HTMLOut](../htmlout) module to process README.md files and incorporate their
|
||||
contents into the "index.html" files that we generate when browsing directories.
|
||||
40
modules/markout/bin/markout
Normal file
40
modules/markout/bin/markout
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview Implements the MarkOut command-line interface
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Sep-04
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var lib = path.join(path.dirname(fs.realpathSync(__filename)), "../lib/");
|
||||
|
||||
require(lib + "markout.js").CLI();
|
||||
1106
modules/markout/lib/markout.js
Normal file
1106
modules/markout/lib/markout.js
Normal file
File diff suppressed because it is too large
Load diff
19
modules/markout/package.json
Normal file
19
modules/markout/package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "markout",
|
||||
"version": "0.1.0",
|
||||
"description": "Transforms simplified Markdown to HTML",
|
||||
"main": "./lib/markout",
|
||||
"scripts": {
|
||||
"test": "echo \"error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Jeff Parsons <Jeff@pcjs.org>",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "GPLv3",
|
||||
"url": "http://www.gnu.org/licenses/gpl.html"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"example": "./bin/markout"
|
||||
}
|
||||
}
|
||||
10
modules/pcjs/bin/.jshintrc
Normal file
10
modules/pcjs/bin/.jshintrc
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"globalstrict": true,
|
||||
"sub": true,
|
||||
"globals": {
|
||||
"global": true,
|
||||
"console": true,
|
||||
"module": true,
|
||||
"require": true
|
||||
}
|
||||
}
|
||||
65
modules/pcjs/bin/fptest.js
Normal file
65
modules/pcjs/bin/fptest.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* @fileoverview This file tests raw floating point access using typed arrays.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-Aug-20
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
try {
|
||||
/*
|
||||
* If Node is running us, this will succeed, and we'll have a print()
|
||||
* function (an alias for console.log). If JSC is running us instead,
|
||||
* then this will fail (there is neither a global NOR a console object),
|
||||
* but that's OK, because print() is a built-in function.
|
||||
*
|
||||
* TODO: Find a cleaner way of doing this, and while you're at it, alias
|
||||
* Node's process.argv to JSC's "arguments" array, and Node's process.exit()
|
||||
* to JSC's quit().
|
||||
*
|
||||
* UPDATE: Node *must* be used to run this test, because JSC's support for
|
||||
* typed arrays is incomplete.
|
||||
*/
|
||||
var print = console.log;
|
||||
} catch(err) {}
|
||||
|
||||
function toHex(v, len) {
|
||||
var s = "00000000" + v.toString(16);
|
||||
return "0x" + s.slice(s.length - (!len? 8 : (len < 8? len : 8))).toUpperCase();
|
||||
}
|
||||
|
||||
var f = 3.4;
|
||||
|
||||
var af = new Float64Array(1);
|
||||
af.set([f]); // the optional offset defaults to zero
|
||||
var dv = new DataView(af.buffer);
|
||||
var dw1 = dv.getUint32(0);
|
||||
var dw2 = dv.getUint32(4);
|
||||
|
||||
print(f + " = " + toHex(dw1) + "," + toHex(dw2));
|
||||
91
modules/pcjs/bin/machine.json
Normal file
91
modules/pcjs/bin/machine.json
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"computer": {
|
||||
"id": "ibm5150.pc-mda-64k",
|
||||
"name": "IBM PC",
|
||||
"resume": "1",
|
||||
"state": ""
|
||||
},
|
||||
"ram": [
|
||||
{ "id": "ibm5150.ramLow",
|
||||
"name": "",
|
||||
"addr": 0x00000,
|
||||
"size": 0,
|
||||
"test": true
|
||||
}
|
||||
],
|
||||
"rom": [
|
||||
{ "id": "ibm5150.romBASIC",
|
||||
"name": "",
|
||||
"addr": 0xf6000,
|
||||
"size": 0x8000,
|
||||
"file": "/devices/pc/basic/ibm-basic-1.00.json",
|
||||
"notify": ""
|
||||
},
|
||||
{ "id": "ibm5150.romBIOS",
|
||||
"name": "",
|
||||
"addr": 0xfe000,
|
||||
"size": 0x2000,
|
||||
"file": "/devices/pc/bios/5150/1981-04-24.json",
|
||||
"notify": ""
|
||||
}
|
||||
],
|
||||
"video": [
|
||||
{ "id": "ibm5150.videoMDA",
|
||||
"name": "Monochrome Display",
|
||||
"model": "",
|
||||
"mode": 7,
|
||||
"screenWidth": 720,
|
||||
"screenHeight": 350,
|
||||
"scale": true,
|
||||
"charCols": 80,
|
||||
"charRows": 25,
|
||||
"fontROM": "/devices/pc/video/ibm-mda-cga.json",
|
||||
"screenColor": "black",
|
||||
"touchScreen": false
|
||||
}
|
||||
],
|
||||
"cpu": {
|
||||
"id": "ibm5150.cpu8088",
|
||||
"name": "",
|
||||
"model": 8088,
|
||||
"clock": 0,
|
||||
"multiplier": 1,
|
||||
"autoStart": true,
|
||||
"csStart": -1,
|
||||
"csInterval": -1,
|
||||
"csStop": -1
|
||||
},
|
||||
"keyboard": {
|
||||
"id": "ibm5150.keyboard",
|
||||
"name": "",
|
||||
"model": ""
|
||||
},
|
||||
"fdc": {
|
||||
"id": "ibm5150.fdcNEC",
|
||||
"name": "",
|
||||
"autoMount": {
|
||||
"A": {
|
||||
"name": "PC-DOS 2.00 (Disk 1)",
|
||||
"path": "/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json"
|
||||
},
|
||||
"B": {
|
||||
"name": "PC-DOS 2.00 (Disk 2)",
|
||||
"path": "/disks/pc/dos/ibm/2.00/PCDOS200-DISK2.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chipset": {
|
||||
"id": "ibm5150.chipset",
|
||||
"name": "",
|
||||
"model": "5150",
|
||||
"sw1": "01000001",
|
||||
"sw2": "11110000",
|
||||
"sound": true
|
||||
},
|
||||
"debugger": {
|
||||
"id": "ibm5150.debugger",
|
||||
"name": "",
|
||||
"messages": ""
|
||||
},
|
||||
"xml": "/devices/pc/machine/5150/mda/64kb/machine.xml"
|
||||
}
|
||||
359
modules/pcjs/bin/pcjs
Normal file
359
modules/pcjs/bin/pcjs
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview Implements the PCjs command-line interface
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Sep-04
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var repl = require("repl");
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var proc = require("../../shared/lib/proclib");
|
||||
|
||||
var fConsole = false;
|
||||
var fDebug = false;
|
||||
var args = proc.getArgs();
|
||||
var argv = args.argv;
|
||||
if (argv['console'] !== undefined) fConsole = argv['console'];
|
||||
if (argv['debug'] !== undefined) fDebug = argv['debug'];
|
||||
|
||||
var lib = path.join(path.dirname(fs.realpathSync(__filename)), "../lib/");
|
||||
|
||||
try {
|
||||
var pkg = require(lib + "../../../package.json");
|
||||
} catch(err) {
|
||||
console.log(err.message);
|
||||
}
|
||||
|
||||
/*
|
||||
* We will build an array of components whose names will match the component names used
|
||||
* in a JSON machine definition file; eg:
|
||||
*
|
||||
* [
|
||||
* {name: "panel",
|
||||
* Create: Panel,
|
||||
* objects: []
|
||||
* },
|
||||
* {name: "chipset":
|
||||
* Create: ChipSet,
|
||||
* objects: []
|
||||
* },
|
||||
* ...
|
||||
* ]
|
||||
*
|
||||
* Every component name comes from the component filename, minus the ".js" extension;
|
||||
* Create is the constructor returned by require(). The only bit of fudging we do is
|
||||
* overriding the constructor for component "cpu" with the constructor for "x86cpu",
|
||||
* because when a "cpu" definition is encountered, it's the "x86cpu" subclass that we
|
||||
* actually want to create, not the "cpu" superclass.
|
||||
*
|
||||
* TODO: Update the list of ignored (ie, ignorable) components.
|
||||
*/
|
||||
var Component;
|
||||
var dbg;
|
||||
var aComponents = [];
|
||||
var asComponentsIgnore = ["embed"];
|
||||
|
||||
/**
|
||||
* loadComponents(asFiles)
|
||||
*
|
||||
* @param {Array.<string>} asFiles
|
||||
*/
|
||||
function loadComponents(asFiles)
|
||||
{
|
||||
for (var i = 0; i < asFiles.length; i++) {
|
||||
var sFile = asFiles[i];
|
||||
if (str.getExtension(sFile) != "js") continue;
|
||||
var sName = str.getBaseName(sFile, true);
|
||||
if (asComponentsIgnore.indexOf(sName) >= 0) continue;
|
||||
if (fDebug) console.log(sFile);
|
||||
try {
|
||||
/*
|
||||
* We COULD load ("require") all the files on-demand, because it's only the
|
||||
* browser initialization sequence we want to mimic in loadMachine(), but this
|
||||
* is simpler, and it also gives us direct references to certain components
|
||||
* we'll want to access later (eg, "component" in getComponentByType()).
|
||||
*/
|
||||
var fn = require(lib + "../../../" + sFile);
|
||||
if (sName == "x86cpu") {
|
||||
for (var j = 0; j < aComponents.length; j++) {
|
||||
if (aComponents[j].name == "cpu") {
|
||||
aComponents[j].Create = fn;
|
||||
sName = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sName == "component") {
|
||||
fn.println = function(s, type) {
|
||||
console.log((type !== undefined? (type + ": ") : "") + (s || ""));
|
||||
}; // jshint ignore:line
|
||||
}
|
||||
if (sName) {
|
||||
aComponents.push({name: sName, Create: fn, objects: []});
|
||||
}
|
||||
if (sName == "defines") {
|
||||
/*
|
||||
* Enabling component console messages requires setting CONSOLE to true.
|
||||
*/
|
||||
if (global.DEBUG !== undefined) {
|
||||
global.DEBUG = fDebug;
|
||||
global.CONSOLE = fConsole;
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
console.log(err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getComponentByName(sName)
|
||||
*
|
||||
* @param sName
|
||||
* @return {*}
|
||||
*/
|
||||
function getComponentByName(sName)
|
||||
{
|
||||
for (var i = 0; i < aComponents.length; i++) {
|
||||
if (aComponents[i].name == sName) {
|
||||
return aComponents[i].Create;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* getComponentByType(sType)
|
||||
*
|
||||
* @param sType
|
||||
* @return {*}
|
||||
*/
|
||||
function getComponentByType(sType)
|
||||
{
|
||||
var component = null;
|
||||
|
||||
if (!Component) {
|
||||
Component = getComponentByName("component");
|
||||
}
|
||||
if (Component) {
|
||||
component = Component.getComponentByType(sType);
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* loadMachine(sFile)
|
||||
*
|
||||
* @param {string} sFile
|
||||
* @return {Object} representing the machine whose component objects have been loaded into aComponents
|
||||
*/
|
||||
function loadMachine(sFile)
|
||||
{
|
||||
if (fDebug) console.log('loadMachine("' + sFile + '")');
|
||||
|
||||
/*
|
||||
* Clear any/all saved objects from any previous machine
|
||||
*/
|
||||
var i, j;
|
||||
Component = dbg = null;
|
||||
for (i = 0; i < aComponents.length; i++) {
|
||||
aComponents[i].objects = [];
|
||||
}
|
||||
|
||||
var machine;
|
||||
try {
|
||||
/*
|
||||
* Since our JSON files may contain comments, hex values, and/or other tokens deemed
|
||||
* unacceptable by the JSON Overlords, we can't use require() to load it, as we're able to
|
||||
* do with "package.json". Also note that require() assumes the same path as that of the
|
||||
* requiring file, whereas fs.readFileSync() assumes the path reported by process.cwd().
|
||||
*
|
||||
* TODO: Actually, I removed the comments from my sample "machine.json" file, so we should
|
||||
* try to reinstate this code now.
|
||||
*
|
||||
* var machine = require(lib + "../bin/" +sFile);
|
||||
*/
|
||||
var sMachine = fs.readFileSync(sFile, {encoding: "utf8"});
|
||||
sMachine = '(' + sMachine + ')';
|
||||
if (fDebug) console.log(sMachine);
|
||||
machine = eval(sMachine); // jshint ignore:line
|
||||
if (machine) {
|
||||
/*
|
||||
* Since we have a machine object, we now mimic the initialization sequence that occurs
|
||||
* in the browser, by walking the list of PCjs components we loaded above and looking for
|
||||
* matches.
|
||||
*/
|
||||
for (i = 0; i < aComponents.length; i++) {
|
||||
var parms = machine[aComponents[i].name];
|
||||
/*
|
||||
* If parms is undefined, it means there is no component with that name defined in the
|
||||
* machine object (NOT that the component has no parms), and therefore we should skip it.
|
||||
*/
|
||||
if (parms === undefined) continue;
|
||||
/*
|
||||
* If parms is an Array, then we must create an object for each parms element; and yes,
|
||||
* I'm relying on the fact that none of my parm objects use a "length" property, as a quick
|
||||
* and dirty way of differentiating objects from arrays.
|
||||
*/
|
||||
var aParms = parms.length !== undefined? parms : [parms];
|
||||
for (j = 0; j < aParms.length; j++) {
|
||||
|
||||
var obj;
|
||||
if (fDebug) console.log("creating " + aComponents[i].name + "...");
|
||||
if (fDebug) console.log(aParms[j]);
|
||||
|
||||
if (aComponents[i].name == "cpu") {
|
||||
aParms[j]['autoStart'] = false;
|
||||
}
|
||||
|
||||
try {
|
||||
obj = new aComponents[i].Create(aParms[j]);
|
||||
} catch(err) {
|
||||
console.log("error creating " + aComponents[i].name + ": " + err.message);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(obj['id'] + " object created");
|
||||
aComponents[i].objects.push(obj);
|
||||
|
||||
if (obj.type == "Debugger") {
|
||||
dbg = obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Return the original machine object only in DEBUG mode
|
||||
*/
|
||||
if (!fDebug) machine = true;
|
||||
}
|
||||
} catch(err) {
|
||||
console.log(err.message);
|
||||
}
|
||||
return machine;
|
||||
}
|
||||
|
||||
/**
|
||||
* doCommand(sCmd)
|
||||
*
|
||||
* @param {string} sCmd
|
||||
* @return {*}
|
||||
*/
|
||||
function doCommand(sCmd)
|
||||
{
|
||||
var result = false;
|
||||
var aTokens = sCmd.split(' ');
|
||||
|
||||
switch(aTokens[0]) {
|
||||
case "cwd":
|
||||
result = process.cwd();
|
||||
break;
|
||||
case "load":
|
||||
result = loadMachine(aTokens[1]);
|
||||
break;
|
||||
case "quit":
|
||||
process.exit();
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
if (sCmd) {
|
||||
try {
|
||||
if (dbg && !dbg.doCommand(sCmd, true)) {
|
||||
sCmd = '(' + sCmd + ')';
|
||||
result = eval(sCmd); // jshint ignore:line
|
||||
}
|
||||
} catch(err) {
|
||||
console.log(err.message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* onCommand(cmd, context, filename, callback)
|
||||
*
|
||||
* The Node docs (http://nodejs.org/api/repl.html) say that repl.start's "eval" option is:
|
||||
*
|
||||
* a function that will be used to eval each given line; defaults to an async wrapper for eval()
|
||||
*
|
||||
* and it gives this example of such a function:
|
||||
*
|
||||
* function eval(cmd, context, filename, callback) {
|
||||
* callback(null, result);
|
||||
* }
|
||||
*
|
||||
* but it defines NEITHER the parameters for the function NOR the parameters for the callback().
|
||||
*
|
||||
* It's pretty clear that "result" is expected to return whatever "eval()" would return for the expression
|
||||
* in "cmd" (which is always parenthesized in preparation for a call to "eval()"), but it's not clear what
|
||||
* the first callback() parameter (represented by null) is supposed to be. Should we assume it's an Error
|
||||
* object, in case we want to report an error?
|
||||
*
|
||||
* @param {string} cmd
|
||||
* @param {Object} context
|
||||
* @param {string} filename
|
||||
* @param {function(Object|null, Object)} callback
|
||||
*/
|
||||
var onCommand = function (cmd, context, filename, callback)
|
||||
{
|
||||
var result = false;
|
||||
var match = cmd.match(/\(\s*(.*)\s*\)/);
|
||||
if (match) {
|
||||
result = doCommand(match[1]);
|
||||
}
|
||||
callback(null, result);
|
||||
};
|
||||
|
||||
if (pkg) {
|
||||
loadComponents(pkg.pcJSFiles);
|
||||
}
|
||||
|
||||
/*
|
||||
* Before falling into the REPL, process any command-line (--cmd) commands -- which should eventually include batch files.
|
||||
*/
|
||||
if (argv['cmd'] !== undefined) {
|
||||
var cmds = argv['cmd'];
|
||||
var aCmds = (typeof cmds == "string"? [cmds] : cmds);
|
||||
for (var i = 0; i < aCmds.length; i++) {
|
||||
doCommand(aCmds[i]);
|
||||
}
|
||||
}
|
||||
|
||||
repl.start({
|
||||
prompt: "PCjs> ",
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
eval: onCommand
|
||||
});
|
||||
843
modules/pcjs/bin/x86gen.js
Normal file
843
modules/pcjs/bin/x86gen.js
Normal file
|
|
@ -0,0 +1,843 @@
|
|||
/**
|
||||
* @fileoverview This file generates PCjs 8086 mode-byte decoders.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Sep-08
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
try {
|
||||
/*
|
||||
* If Node is running us, this will succeed, and we'll have a print()
|
||||
* function (an alias for console.log). If JSC is running us instead,
|
||||
* then this will fail (there is neither a global NOR a console object),
|
||||
* but that's OK, because print() is a built-in function.
|
||||
*
|
||||
* TODO: Find a cleaner way of doing this, and while you're at it, alias
|
||||
* Node's process.argv to JSC's "arguments" array, and Node's process.exit()
|
||||
* to JSC's quit().
|
||||
*/
|
||||
var print = console.log;
|
||||
} catch(err) {}
|
||||
|
||||
/*
|
||||
* I'm going to start by creating 4 sets of "mod,reg,r/m" aka OpMod tables:
|
||||
*
|
||||
* Set 0: mod,r/m is dst, size is byte, dispatch table is aOpModMemByte
|
||||
* Set 1: mod,r/m is dst, size is word, dispatch table is aOpModMemWord
|
||||
* Set 2: reg is dst, size is byte, dispatch table is aOpModRegByte
|
||||
* Set 3: reg is dst, size is word, dispatch table is aOpModRegWord
|
||||
*
|
||||
* See p. 3-41 of "The 8086 Book" for more details.
|
||||
*/
|
||||
|
||||
var aDst = ["Mem", "Reg"];
|
||||
var aSize = ["Byte", "Word"];
|
||||
var aDisp = ["8", "16"];
|
||||
|
||||
/*
|
||||
* Index aREG like so: aREG[w][reg]
|
||||
*/
|
||||
var aREG = [
|
||||
["AL", "CL", "DL", "BL", "AH", "CH", "DH", "BH"],
|
||||
["AX", "CX", "DX", "BX", "SP", "BP", "SI", "DI"]
|
||||
];
|
||||
|
||||
var w, sError = "";
|
||||
var fGenMods = true;
|
||||
var fGenTables = true;
|
||||
var sEAFuncs = "";
|
||||
|
||||
if (!fGenMods) {
|
||||
|
||||
var sOps = "", cOps = 0;
|
||||
|
||||
for (var op = 0x00; op <= 0xFF; op++) {
|
||||
|
||||
var i, iReg, sOp, sOpCode, sOperator, sDst, sDstReg;
|
||||
|
||||
if (op >= 0x40 && op <= 0x4F) {
|
||||
i = op - 0x40;
|
||||
iReg = i % 8;
|
||||
sOpCode = (op < 0x48? "INC" : "DEC");
|
||||
sOperator = (op < 0x48? "+" : "-");
|
||||
sDst = aREG[1][iReg];
|
||||
sDstReg = "this.reg." + aREG[1][iReg];
|
||||
print(" /**");
|
||||
print(" * @this {X86CPU}");
|
||||
print(" *");
|
||||
print(" * op=0x" + toHex(op, 2) + " (" + sOpCode.toLowerCase() + " " + sDst + ")");
|
||||
print(" */");
|
||||
sOp = "op" + sOpCode + sDst;
|
||||
print(" " + sOp + ": function() {");
|
||||
print(" " + sDstReg + " = (" + sDstReg + " " + sOperator + " 1) & 0xffff;");
|
||||
print(" },");
|
||||
if (sOps) sOps += ((cOps % 4)? ", " : ",\n");
|
||||
sOps += " this." + sOp;
|
||||
cOps++;
|
||||
}
|
||||
else if (op >= 0x50 && op <= 0x5F) {
|
||||
i = op - 0x50;
|
||||
iReg = i % 8;
|
||||
sOpCode = (op < 0x58? "PUSH" : "POP");
|
||||
sDst = aREG[1][iReg];
|
||||
sDstReg = "this.reg." + aREG[1][iReg];
|
||||
print(" /**");
|
||||
print(" * @this {X86CPU}");
|
||||
print(" *");
|
||||
print(" * op=0x" + toHex(op, 2) + " (" + sOpCode.toLowerCase() + " " + sDst + ")");
|
||||
print(" */");
|
||||
sOp = "op" + sOpCode + sDst;
|
||||
print(" " + sOp + ": function() {");
|
||||
if (op < 0x58) {
|
||||
print(" this.pushWord(" + sDstReg + ");");
|
||||
}
|
||||
else {
|
||||
print(" " + sDstReg + " = this.popWord();");
|
||||
}
|
||||
print(" },");
|
||||
if (sOps) sOps += ((cOps % 4)? ", " : ",\n");
|
||||
sOps += " this." + sOp;
|
||||
cOps++;
|
||||
}
|
||||
else if (op == 0x90) {
|
||||
if (sOps) sOps += ((cOps % 4)? ", " : ",\n");
|
||||
sOps += " this.opNOP";
|
||||
cOps++;
|
||||
}
|
||||
else if (op >= 0x91 && op <= 0x97) {
|
||||
i = op - 0x90;
|
||||
iReg = i % 8;
|
||||
sOpCode = "XCHG";
|
||||
sDst = aREG[1][iReg];
|
||||
sDstReg = "this.reg." + aREG[1][iReg];
|
||||
print(" /**");
|
||||
print(" * @this {X86CPU}");
|
||||
print(" *");
|
||||
print(" * op=0x" + toHex(op, 2) + " (" + sOpCode.toLowerCase() + " AX," + sDst + ")");
|
||||
print(" */");
|
||||
sOp = "op" + sOpCode + sDst;
|
||||
print(" " + sOp + ": function() {");
|
||||
print(" var temp = this.regAX; this.regAX = " + sDstReg + "; " + sDstReg + " = temp;");
|
||||
print(" },");
|
||||
if (sOps) sOps += ((cOps % 4)? ", " : ",\n");
|
||||
sOps += " this." + sOp;
|
||||
cOps++;
|
||||
}
|
||||
else if (op >= 0xB0 && op <= 0xBF) {
|
||||
i = op - 0xB0;
|
||||
w = (i < 8? 0 : 1);
|
||||
i = i % 8;
|
||||
sDst = aREG[w][i].toLowerCase();
|
||||
print(" /**");
|
||||
print(" * @this {X86CPU}");
|
||||
print(" *");
|
||||
print(" * op=0x" + toHex(op, 2) + " (mov " + aREG[w][i] + "," + aSize[w].toLowerCase() + ")");
|
||||
print(" */");
|
||||
sOp = "opMOV" + aREG[w][i] + aDisp[w];
|
||||
print(" " + sOp + ": function() {");
|
||||
var sRegSet = "", sRegSetEnd = null;
|
||||
if (w == 1) {
|
||||
sRegSet = "this.reg" + aREG[1][i] + " = ";
|
||||
}
|
||||
else {
|
||||
if (i < 4) {
|
||||
sRegSet = "this.reg" + aREG[1][i] + " = (this.reg" + aREG[1][i] + " & ~0xff) | ";
|
||||
}
|
||||
else {
|
||||
sRegSet = "this.reg" + aREG[1][i - 4] + " = (this.reg" + aREG[1][i - 4] + " & 0xff) | ";
|
||||
sRegSetEnd = " << 8";
|
||||
}
|
||||
}
|
||||
print(" " + sRegSet + (sRegSetEnd? "(" : "") + "this.getIP" + aSize[w] + "()" + (sRegSetEnd? (sRegSetEnd + ")") : "") + ";");
|
||||
print(" },");
|
||||
if (sOps) sOps += ((cOps % 4)? ", " : ",\n");
|
||||
sOps += " this." + sOp;
|
||||
cOps++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fGenTables) print(" this.aOpCodeFuncs = [");
|
||||
if (fGenTables) print(sOps);
|
||||
if (fGenTables) print(" ];");
|
||||
}
|
||||
else {
|
||||
|
||||
/*
|
||||
* Index aMOD like so: aMOD[mod]
|
||||
*/
|
||||
var aMOD = ["mem", "mem+d8", "mem+d16", "reg"];
|
||||
|
||||
/*
|
||||
* Index aRM like so: aRM[mod][w][r_m], forcing w to 0 unless mod is 3
|
||||
*/
|
||||
var aRM = [
|
||||
[["BX+SI", "BX+DI", "BP+SI", "BP+DI", "SI", "DI", "d16", "BX"], []],
|
||||
[["BX+SI+d8", "BX+DI+d8", "BP+SI+d8", "BP+DI+d8", "SI+d8", "DI+d8", "BP+d8", "BX+d8"], []],
|
||||
[["BX+SI+d16", "BX+DI+d16", "BP+SI+d16", "BP+DI+d16", "SI+d16", "DI+d16", "BP+d16", "BX+d16"], []],
|
||||
[["AL", "CL", "DL", "BL", "AH", "CH", "DH", "BH"], ["AX", "CX", "DX", "BX", "SP", "BP", "SI", "DI"]]
|
||||
];
|
||||
|
||||
var cOpMods, mrm;
|
||||
var sOpMods, sOpMod, sContainer;
|
||||
|
||||
print('"use strict";\n');
|
||||
print("var X86Mods = {};\n");
|
||||
|
||||
for (var d = 0; d <= 1 && !sError; d++) {
|
||||
|
||||
for (w = 0; w <= 1 && !sError; w++) {
|
||||
|
||||
cOpMods = 0;
|
||||
sOpMods = "";
|
||||
|
||||
sContainer = "X86Mods"; // + aDst[d].substr(0, 1) + aSize[w].substr(0, 1);
|
||||
|
||||
if (fGenTables) print(sContainer + " = {");
|
||||
|
||||
for (mrm = 0x00; mrm <= 0xff && !sError; mrm++) {
|
||||
sOpMod = genMode(d, w, mrm);
|
||||
if (sOpMod) {
|
||||
if (sOpMods) sOpMods += ((cOpMods % 4)? ", " : ",\n");
|
||||
sOpMods += " " + sContainer + "." + sOpMod;
|
||||
cOpMods++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fGenTables) print("};\n");
|
||||
|
||||
if (fGenTables) print("X86Mods.aOpMod" + aDst[d] + aSize[w] + " = [");
|
||||
if (fGenTables) print(sOpMods);
|
||||
if (fGenTables) print("];\n");
|
||||
}
|
||||
}
|
||||
|
||||
for (w = 0; w <= 1 && !sError; w++) {
|
||||
|
||||
cOpMods = 0;
|
||||
sOpMods = "";
|
||||
|
||||
sContainer = "X86Mods"; // + "G" + aSize[w].substr(0, 1);
|
||||
|
||||
if (fGenTables) print(sContainer + " = {");
|
||||
|
||||
for (mrm = 0x00; mrm <= 0xff && !sError; mrm++) {
|
||||
sOpMod = genMode(0, w, mrm, "Grp");
|
||||
if (sOpMod) {
|
||||
if (sOpMods) sOpMods += ((cOpMods % 4)? ", " : ",\n");
|
||||
sOpMods += " " + sContainer + "." + sOpMod;
|
||||
cOpMods++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fGenTables) print("};\n");
|
||||
|
||||
if (fGenTables) print("X86Mods.aOpMod" + "Grp" + aSize[w] + " = [");
|
||||
if (fGenTables) print(sOpMods);
|
||||
if (fGenTables) print("];\n");
|
||||
}
|
||||
|
||||
if (sEAFuncs) {
|
||||
print("var X86Mods = {\n" + sEAFuncs + "};\n");
|
||||
}
|
||||
}
|
||||
|
||||
function genMode(d, w, mrm, sGroup, sRO) {
|
||||
var mod = (mrm >> 6);
|
||||
var reg = (mrm >> 3) & 0x7;
|
||||
var r_m = (mrm & 0x7);
|
||||
var sRegGet = null;
|
||||
var sRegSet = null;
|
||||
var sRegSetBegin = "", sRegSetEnd = "";
|
||||
if (!w) {
|
||||
switch (reg) {
|
||||
case 0:
|
||||
sRegGet = "this.regAX & 0xff";
|
||||
sRegSet = "this.regAX = (this.regAX & ~0xff) | ";
|
||||
break;
|
||||
case 1:
|
||||
sRegGet = "this.regCX & 0xff";
|
||||
sRegSet = "this.regCX = (this.regCX & ~0xff) | ";
|
||||
break;
|
||||
case 2:
|
||||
sRegGet = "this.regDX & 0xff";
|
||||
sRegSet = "this.regDX = (this.regDX & ~0xff) | ";
|
||||
break;
|
||||
case 3:
|
||||
sRegGet = "this.regBX & 0xff";
|
||||
sRegSet = "this.regBX = (this.regBX & ~0xff) | ";
|
||||
break;
|
||||
case 4:
|
||||
sRegGet = "this.regAX >> 8";
|
||||
sRegSet = "this.regAX = (this.regAX & 0xff) | ";
|
||||
sRegSetEnd = " << 8";
|
||||
break;
|
||||
case 5:
|
||||
sRegGet = "this.regCX >> 8";
|
||||
sRegSet = "this.regCX = (this.regCX & 0xff) | ";
|
||||
sRegSetEnd = " << 8";
|
||||
break;
|
||||
case 6:
|
||||
sRegGet = "this.regDX >> 8";
|
||||
sRegSet = "this.regDX = (this.regDX & 0xff) | ";
|
||||
sRegSetEnd = " << 8";
|
||||
break;
|
||||
case 7:
|
||||
sRegGet = "this.regBX >> 8";
|
||||
sRegSet = "this.regBX = (this.regBX & 0xff) | ";
|
||||
sRegSetEnd = " << 8";
|
||||
break;
|
||||
default:
|
||||
sError = "unrecognized w=0 reg: " + reg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (reg) {
|
||||
case 0:
|
||||
sRegGet = "this.regAX";
|
||||
break;
|
||||
case 1:
|
||||
sRegGet = "this.regCX";
|
||||
break;
|
||||
case 2:
|
||||
sRegGet = "this.regDX";
|
||||
break;
|
||||
case 3:
|
||||
sRegGet = "this.regBX";
|
||||
break;
|
||||
case 4:
|
||||
sRegGet = "this.regSP";
|
||||
break;
|
||||
case 5:
|
||||
sRegGet = "this.regBP";
|
||||
break;
|
||||
case 6:
|
||||
sRegGet = "this.regSI";
|
||||
break;
|
||||
case 7:
|
||||
sRegGet = "this.regDI";
|
||||
break;
|
||||
default:
|
||||
sError = "unrecognized w=1 reg: " + reg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* The 8086/8088 cycle counts below come from p.3-48 of "The 8086 Book", where it discusses EA
|
||||
* ("effective address") calculations and the number of execution cycles required for each type
|
||||
* of calculation.
|
||||
*
|
||||
*
|
||||
*/
|
||||
var fInline = true;
|
||||
var nCycles = null;
|
||||
var sModAddr = null;
|
||||
var sModFunc = null;
|
||||
var sModRegGet = null;
|
||||
var sModRegSet = null;
|
||||
var sModRegSetBegin = "", sModRegSetEnd = "";
|
||||
var sModRegSeg = "this.segData";
|
||||
switch (mod) {
|
||||
case 0:
|
||||
switch (r_m) {
|
||||
case 0:
|
||||
sModAddr = "this.regBX + this.regSI";
|
||||
sModFunc = "BXSI";
|
||||
nCycles = "this.nEACyclesBaseIndex"; // 8086: 7
|
||||
break;
|
||||
case 1:
|
||||
sModAddr = "this.regBX + this.regDI";
|
||||
sModFunc = "BXDI";
|
||||
nCycles = "this.nEACyclesBaseIndexExtra"; // 8086: 8
|
||||
break;
|
||||
case 2:
|
||||
sModAddr = "this.regBP + this.regSI";
|
||||
sModFunc = "BPSI";
|
||||
sModRegSeg = "this.segStack";
|
||||
nCycles = "this.nEACyclesBaseIndexExtra"; // 8086: 8
|
||||
break;
|
||||
case 3:
|
||||
sModAddr = "this.regBP + this.regDI";
|
||||
sModFunc = "BPDI";
|
||||
sModRegSeg = "this.segStack";
|
||||
nCycles = "this.nEACyclesBaseIndex"; // 8086: 7
|
||||
break;
|
||||
case 4:
|
||||
sModAddr = "this.regSI";
|
||||
sModFunc = "SI";
|
||||
nCycles = "this.nEACyclesBase"; // 8086: 5
|
||||
break;
|
||||
case 5:
|
||||
sModAddr = "this.regDI";
|
||||
sModFunc = "DI";
|
||||
nCycles = "this.nEACyclesBase"; // 8086: 5
|
||||
break;
|
||||
case 6:
|
||||
sModAddr = "this.getIPWord()";
|
||||
sModFunc = "D16";
|
||||
nCycles = "this.nEACyclesDisp"; // 8086: 6
|
||||
break;
|
||||
case 7:
|
||||
sModAddr = "this.regBX";
|
||||
sModFunc = "BX";
|
||||
nCycles = "this.nEACyclesBase"; // 8086: 5
|
||||
break;
|
||||
default:
|
||||
sError = "unrecognized mod=0 r/m: " + r_m;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
switch (r_m) {
|
||||
case 0:
|
||||
sModAddr = "this.regBX + this.regSI + this.getIPDisp()";
|
||||
sModFunc = "BXSID8";
|
||||
nCycles = "this.nEACyclesBaseIndexDisp"; // 8086: 11
|
||||
break;
|
||||
case 1:
|
||||
sModAddr = "this.regBX + this.regDI + this.getIPDisp()";
|
||||
sModFunc = "BXDID8";
|
||||
nCycles = "this.nEACyclesBaseIndexDispExtra"; // 8086: 12
|
||||
break;
|
||||
case 2:
|
||||
sModAddr = "this.regBP + this.regSI + this.getIPDisp()";
|
||||
sModFunc = "BPSID8";
|
||||
sModRegSeg = "this.segStack";
|
||||
nCycles = "this.nEACyclesBaseIndexDispExtra"; // 8086: 12
|
||||
break;
|
||||
case 3:
|
||||
sModAddr = "this.regBP + this.regDI + this.getIPDisp()";
|
||||
sModFunc = "BPDID8";
|
||||
sModRegSeg = "this.segStack";
|
||||
nCycles = "this.nEACyclesBaseIndexDisp"; // 8086: 11
|
||||
break;
|
||||
case 4:
|
||||
sModAddr = "this.regSI + this.getIPDisp()";
|
||||
sModFunc = "SID8";
|
||||
nCycles = "this.nEACyclesBaseDisp"; // 8086: 9
|
||||
break;
|
||||
case 5:
|
||||
sModAddr = "this.regDI + this.getIPDisp()";
|
||||
sModFunc = "DID8";
|
||||
nCycles = "this.nEACyclesBaseDisp"; // 8086: 9
|
||||
break;
|
||||
case 6:
|
||||
sModAddr = "this.regBP + this.getIPDisp()";
|
||||
sModFunc = "BPD8";
|
||||
sModRegSeg = "this.segStack";
|
||||
nCycles = "this.nEACyclesBaseDisp"; // 8086: 9
|
||||
break;
|
||||
case 7:
|
||||
sModAddr = "this.regBX + this.getIPDisp()";
|
||||
sModFunc = "BXD8";
|
||||
nCycles = "this.nEACyclesBaseDisp"; // 8086: 9
|
||||
break;
|
||||
default:
|
||||
sError = "unrecognized mod=1 r/m: " + r_m;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
switch (r_m) {
|
||||
case 0:
|
||||
sModAddr = "this.regBX + this.regSI + this.getIPWord()";
|
||||
sModFunc = "BXSID16";
|
||||
nCycles = "this.nEACyclesBaseIndexDisp"; // 8086: 11
|
||||
break;
|
||||
case 1:
|
||||
sModAddr = "this.regBX + this.regDI + this.getIPWord()";
|
||||
sModFunc = "BXDID16";
|
||||
nCycles = "this.nEACyclesBaseIndexDispExtra"; // 8086: 12
|
||||
break;
|
||||
case 2:
|
||||
sModAddr = "this.regBP + this.regSI + this.getIPWord()";
|
||||
sModFunc = "BPSID16";
|
||||
sModRegSeg = "this.segStack";
|
||||
nCycles = "this.nEACyclesBaseIndexDispExtra"; // 8086: 12
|
||||
break;
|
||||
case 3:
|
||||
sModAddr = "this.regBP + this.regDI + this.getIPWord()";
|
||||
sModFunc = "BPDID16";
|
||||
sModRegSeg = "this.segStack";
|
||||
nCycles = "this.nEACyclesBaseIndexDisp"; // 8086: 11
|
||||
break;
|
||||
case 4:
|
||||
sModAddr = "this.regSI + this.getIPWord()";
|
||||
sModFunc = "SID16";
|
||||
nCycles = "this.nEACyclesBaseDisp"; // 8086: 9
|
||||
break;
|
||||
case 5:
|
||||
sModAddr = "this.regDI + this.getIPWord()";
|
||||
sModFunc = "DID16";
|
||||
nCycles = "this.nEACyclesBaseDisp"; // 8086: 9
|
||||
break;
|
||||
case 6:
|
||||
sModAddr = "this.regBP + this.getIPWord()";
|
||||
sModFunc = "BPD16";
|
||||
sModRegSeg = "this.segStack";
|
||||
nCycles = "this.nEACyclesBaseDisp"; // 8086: 9
|
||||
break;
|
||||
case 7:
|
||||
sModAddr = "this.regBX + this.getIPWord()";
|
||||
sModFunc = "BXD16";
|
||||
nCycles = "this.nEACyclesBaseDisp"; // 8086: 9
|
||||
break;
|
||||
default:
|
||||
sError = "unrecognized mod=2 r/m: " + r_m;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (!w) {
|
||||
switch (r_m) {
|
||||
case 0:
|
||||
sModRegGet = "this.regAX & 0xff";
|
||||
sModRegSet = "this.regAX = (this.regAX & ~0xff) | ";
|
||||
break;
|
||||
case 1:
|
||||
sModRegGet = "this.regCX & 0xff";
|
||||
sModRegSet = "this.regCX = (this.regCX & ~0xff) | ";
|
||||
break;
|
||||
case 2:
|
||||
sModRegGet = "this.regDX & 0xff";
|
||||
sModRegSet = "this.regDX = (this.regDX & ~0xff) | ";
|
||||
break;
|
||||
case 3:
|
||||
sModRegGet = "this.regBX & 0xff";
|
||||
sModRegSet = "this.regBX = (this.regBX & ~0xff) | ";
|
||||
break;
|
||||
case 4:
|
||||
sModRegGet = "this.regAX >> 8";
|
||||
sModRegSet = "this.regAX = (this.regAX & 0xff) | ";
|
||||
sModRegSetEnd = " << 8";
|
||||
break;
|
||||
case 5:
|
||||
sModRegGet = "this.regCX >> 8";
|
||||
sModRegSet = "this.regCX = (this.regCX & 0xff) | ";
|
||||
sModRegSetEnd = " << 8";
|
||||
break;
|
||||
case 6:
|
||||
sModRegGet = "this.regDX >> 8";
|
||||
sModRegSet = "this.regDX = (this.regDX & 0xff) | ";
|
||||
sModRegSetEnd = " << 8";
|
||||
break;
|
||||
case 7:
|
||||
sModRegGet = "this.regBX >> 8";
|
||||
sModRegSet = "this.regBX = (this.regBX & 0xff) | ";
|
||||
sModRegSetEnd = " << 8";
|
||||
break;
|
||||
default:
|
||||
sError = "unrecognized w=0 mod=3 r/m: " + r_m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (r_m) {
|
||||
case 0:
|
||||
sModRegGet = "this.regAX";
|
||||
break;
|
||||
case 1:
|
||||
sModRegGet = "this.regCX";
|
||||
break;
|
||||
case 2:
|
||||
sModRegGet = "this.regDX";
|
||||
break;
|
||||
case 3:
|
||||
sModRegGet = "this.regBX";
|
||||
break;
|
||||
case 4:
|
||||
sModRegGet = "this.regSP";
|
||||
break;
|
||||
case 5:
|
||||
sModRegGet = "this.regBP";
|
||||
break;
|
||||
case 6:
|
||||
sModRegGet = "this.regSI";
|
||||
break;
|
||||
case 7:
|
||||
sModRegGet = "this.regDI";
|
||||
break;
|
||||
default:
|
||||
sError = "unrecognized w=1 mod=3 r/m: " + r_m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
sError = "unrecognized mod: " + mod;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sError) {
|
||||
print(sError);
|
||||
return null;
|
||||
}
|
||||
|
||||
sOpMod = "opMod" + (sGroup? sGroup + (sRO? sRO : "") : aDst[d]) + aSize[w] + toHex(mrm, 2);
|
||||
|
||||
var sTemp = aSize[w].charAt(0).toLowerCase();
|
||||
|
||||
if (sGroup) {
|
||||
/*
|
||||
* Use this to generate ModRM decoders that accept an array (ie, "group") of functions, and pass along an implied argument as well
|
||||
*/
|
||||
if (sRO && reg != 7) {
|
||||
sOpMod = "opMod" + (sGroup? sGroup : aDst[d]) + aSize[w] + toHex(mrm, 2);
|
||||
return sOpMod;
|
||||
}
|
||||
|
||||
print(" /**");
|
||||
print(" * @this {X86CPU}");
|
||||
print(" * @param {Array.<function(number,number)>} afnGrp");
|
||||
print(" * @param {function()} fnSrc");
|
||||
print(" *");
|
||||
print(" * mod=" + toMod(d, mod) + " reg=" + toReg(d, w, reg, sGroup) + " r/m=" + toRM(mod, w, r_m));
|
||||
print(" */");
|
||||
print(" " + sOpMod + ": function(afnGrp, fnSrc) {");
|
||||
|
||||
if (sModAddr) {
|
||||
if (sModAddr.indexOf("+") > 0)
|
||||
sModAddr = "((" + sModAddr + ") & 0xffff)";
|
||||
if (!d) {
|
||||
if (reg == 7 && sRO) {
|
||||
if (sModFunc) {
|
||||
if (fInline) {
|
||||
print(" afnGrp[" + reg + "].call(this, this.getEA" + aSize[w] + "(" + sModRegSeg + ", " + sModAddr + "), fnSrc.call(this));");
|
||||
} else {
|
||||
sModFunc = "read" + sModFunc + aSize[w];
|
||||
print(" var addr = X86Mods." + sModFunc + ".call(this);");
|
||||
print(" afnGrp[" + reg + "].call(this, this.getEA" + aSize[w] + "(addr), fnSrc.call(this));");
|
||||
genEAFunc(sModFunc, "this.regEA = " + sModRegSeg + "[1] + " + sModAddr);
|
||||
}
|
||||
} else {
|
||||
print(" this.regEA = " + sModRegSeg + "[1] + " + sModAddr + ";");
|
||||
print(" afnGrp[" + reg + "].call(this, this.getEA" + aSize[w] + "(this.regEA), fnSrc.call(this));");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (sModFunc) {
|
||||
if (fInline) {
|
||||
print(" var " + sTemp + " = afnGrp[" + reg + "].call(this, this.modEA" + aSize[w] + "(" + sModRegSeg + ", " + sModAddr + "), fnSrc.call(this));");
|
||||
print(" this.setEA" + aSize[w] + "(" + sTemp + ");");
|
||||
} else {
|
||||
sModFunc = "write" + sModFunc + aSize[w];
|
||||
print(" var addr = X86Mods." + sModFunc + ".call(this);");
|
||||
print(" var " + sTemp + " = afnGrp[" + reg + "].call(this, this.modEA" + aSize[w] + "(addr), fnSrc.call(this));");
|
||||
print(" this.setEA" + aSize[w] + "(addr, " + sTemp + ");");
|
||||
genEAFunc(sModFunc, "this.regEAWrite = " + sModRegSeg + "[1] + " + sModAddr);
|
||||
}
|
||||
} else {
|
||||
print(" this.regEAWrite = " + sModRegSeg + "[1] + " + sModAddr + ";");
|
||||
print(" var " + sTemp + " = afnGrp[" + reg + "].call(this, this.modEA" + aSize[w] + "(this.regEAWrite), fnSrc.call(this));");
|
||||
print(" this.setEA" + aSize[w] + "(this.regEAWrite, " + sTemp + ");");
|
||||
}
|
||||
}
|
||||
if (nCycles !== null)
|
||||
print(" this.nStepCycles -= " + nCycles + ";");
|
||||
}
|
||||
}
|
||||
else if (sModRegGet) {
|
||||
if (!sModRegSet) {
|
||||
sModRegSet = sModRegGet + " = ";
|
||||
sTemp = null;
|
||||
}
|
||||
if (sModRegSetEnd) {
|
||||
sModRegSetBegin = "(";
|
||||
sModRegSetEnd += ")";
|
||||
}
|
||||
if (reg == 7 && sRO) {
|
||||
print(" afnGrp[" + reg + "].call(this, " + sModRegGet + ", fnSrc.call(this));");
|
||||
} else {
|
||||
if (!sTemp) {
|
||||
print(" " + sModRegSet + sModRegSetBegin + "afnGrp[" + reg + "].call(this, " + sModRegGet + ", fnSrc.call(this))" + sModRegSetEnd + ";");
|
||||
} else {
|
||||
print(" var " + sTemp + " = afnGrp[" + reg + "].call(this, " + sModRegGet + ", fnSrc.call(this));");
|
||||
print(" " + sModRegSet + sModRegSetBegin + sTemp + sModRegSetEnd + ";");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
/*
|
||||
* Is this OpMod a duplicate OpMod? Specifically, when mod is 3 and d is 0, the destination is a register specified by r_m,
|
||||
* which should match the OpMod handler for when mod' == 3 and d' == 1 and reg' == r_m and r_m' == reg.
|
||||
*/
|
||||
if (mod == 3 && !d) {
|
||||
var mrmPrime = (mod << 6) | (r_m << 3) | reg;
|
||||
sOpMod = "opMod" + aDst[1] + aSize[w] + toHex(mrmPrime, 2);
|
||||
return sOpMod;
|
||||
}
|
||||
|
||||
print(" /**");
|
||||
print(" * @this {X86CPU}");
|
||||
print(" * @param {function(number,number)} fn (dst,src)");
|
||||
print(" *");
|
||||
print(" * mod=" + toMod(d, mod) + " reg=" + toReg(d, w, reg) + " r/m=" + toRM(mod, w, r_m));
|
||||
print(" */");
|
||||
print(" " + sOpMod + ": function(fn) {");
|
||||
|
||||
if (sModAddr && sRegGet) {
|
||||
|
||||
if (sModAddr.indexOf("+") > 0)
|
||||
sModAddr = "((" + sModAddr + ") & 0xffff)";
|
||||
if (!d) {
|
||||
if (sModFunc) {
|
||||
if (fInline) {
|
||||
print(" var " + sTemp + " = fn.call(this, this.modEA" + aSize[w] + "(" + sModRegSeg + ", " + sModAddr + "), " + sRegGet + ");");
|
||||
print(" this.setEA" + aSize[w] + "(" + sTemp + ");");
|
||||
} else {
|
||||
sModFunc = "write" + sModFunc + aSize[w];
|
||||
print(" var addr = X86Mods." + sModFunc + ".call(this);");
|
||||
print(" var " + sTemp + " = fn.call(this, this.modEA" + aSize[w] + "(addr), " + sRegGet + ");");
|
||||
print(" this.setEA" + aSize[w] + "(addr, " + sTemp + ");");
|
||||
genEAFunc(sModFunc, "this.regEAWrite = " + sModRegSeg + "[1] + " + sModAddr);
|
||||
}
|
||||
} else {
|
||||
print(" this.regEAWrite = " + sModRegSeg + "[1] + " + sModAddr + ";");
|
||||
print(" var " + sTemp + " = fn.call(this, this.modEA" + aSize[w] + "(this.regEAWrite), " + sRegGet + ");");
|
||||
print(" this.setEA" + aSize[w] + "(this.regEAWrite, " + sTemp + ");");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!sRegSet) {
|
||||
sRegSet = sRegGet + " = ";
|
||||
sTemp = null;
|
||||
}
|
||||
if (sRegSetEnd) {
|
||||
sRegSetBegin = "(";
|
||||
sRegSetEnd += ")";
|
||||
}
|
||||
if (sModFunc) {
|
||||
if (fInline) {
|
||||
if (!sTemp) {
|
||||
print(" " + sRegSet + sRegSetBegin + "fn.call(this, " + sRegGet + ", this.getEA" + aSize[w] + "(" + sModRegSeg + ", " + sModAddr + "))" + sRegSetEnd + ";");
|
||||
} else {
|
||||
print(" var " + sTemp + " = fn.call(this, " + sRegGet + ", this.getEA" + aSize[w] + "(" + sModRegSeg + ", " + sModAddr + "));");
|
||||
print(" " + sRegSet + sRegSetBegin + sTemp + sRegSetEnd + ";");
|
||||
}
|
||||
} else {
|
||||
sModFunc = "read" + sModFunc + aSize[w];
|
||||
print(" var addr = X86Mods." + sModFunc + ".call(this);");
|
||||
if (!sTemp) {
|
||||
print(" " + sRegSet + sRegSetBegin + "fn.call(this, " + sRegGet + ", this.getEA" + aSize[w] + "(addr))" + sRegSetEnd + ";");
|
||||
} else {
|
||||
print(" var " + sTemp + " = fn.call(this, " + sRegGet + ", this.getEA" + aSize[w] + "(addr));");
|
||||
print(" " + sRegSet + sRegSetBegin + sTemp + sRegSetEnd + ";");
|
||||
}
|
||||
genEAFunc(sModFunc, "this.regEA = " + sModRegSeg + "[1] + " + sModAddr);
|
||||
}
|
||||
} else {
|
||||
print(" this.regEA = " + sModRegSeg + "[1] + " + sModAddr + ";");
|
||||
if (!sTemp) {
|
||||
print(" " + sRegSet + sRegSetBegin + "fn.call(this, " + sRegGet + ", this.getEA" + aSize[w] + "(this.regEA))" + sRegSetEnd + ";");
|
||||
} else {
|
||||
print(" var " + sTemp + " = fn.call(this, " + sRegGet + ", this.getEA" + aSize[w] + "(this.regEA));");
|
||||
print(" " + sRegSet + sRegSetBegin + sTemp + sRegSetEnd + ";");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nCycles !== null)
|
||||
print(" this.nStepCycles -= " + nCycles + ";");
|
||||
}
|
||||
else if (sModRegGet && sRegGet) {
|
||||
if (!d) {
|
||||
if (!sModRegSet) {
|
||||
sModRegSet = sModRegGet + " = ";
|
||||
sTemp = null;
|
||||
}
|
||||
if (sModRegSetEnd) {
|
||||
sModRegSetBegin = "(";
|
||||
sModRegSetEnd += ")";
|
||||
}
|
||||
if (!sTemp) {
|
||||
print(" " + sModRegSet + sModRegSetBegin + "fn.call(this, " + sModRegGet + ", " + sRegGet + ")" + sModRegSetEnd + ";");
|
||||
} else {
|
||||
print(" var " + sTemp + " = fn.call(this, " + sModRegGet + ", " + sRegGet + ");");
|
||||
print(" " + sModRegSet + sModRegSetBegin + sTemp + sModRegSetEnd + ";");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!sRegSet) {
|
||||
sRegSet = sRegGet + " = ";
|
||||
sTemp = null;
|
||||
}
|
||||
if (sRegSetEnd) {
|
||||
sRegSetBegin = "(";
|
||||
sRegSetEnd += ")";
|
||||
}
|
||||
if (!sTemp) {
|
||||
print(" " + sRegSet + sRegSetBegin + "fn.call(this, " + sRegGet + ", " + sModRegGet + ")" + sRegSetEnd + ";");
|
||||
} else {
|
||||
print(" var " + sTemp + " = fn.call(this, " + sRegGet + ", " + sModRegGet + ");");
|
||||
print(" " + sRegSet + sRegSetBegin + sTemp + sRegSetEnd + ";");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print(" }" + (mrm < 0xff? "," : ""));
|
||||
|
||||
return sOpMod;
|
||||
}
|
||||
|
||||
function genEAFunc(sFuncName, sFuncBody) {
|
||||
if (sEAFuncs.indexOf(sFuncName) < 0) {
|
||||
sEAFuncs += " /**\n";
|
||||
sEAFuncs += " * @this {X86CPU}\n";
|
||||
sEAFuncs += " * @return {number}\n";
|
||||
sEAFuncs += " */\n";
|
||||
sEAFuncs += " " + sFuncName + ": function() {\n";
|
||||
sEAFuncs += " return (" + sFuncBody + ");\n";
|
||||
sEAFuncs += " },\n";
|
||||
}
|
||||
}
|
||||
|
||||
function toMod(d, mod) {
|
||||
return toBin(mod, 2) + " (" + aMOD[mod] + ":" + (d? "src" : "dst") + ")";
|
||||
}
|
||||
|
||||
function toReg(d, w, reg, sGroup) {
|
||||
return toBin(reg, 3) + " (" + (sGroup? "afnGrp[" + reg + "]" : aREG[w][reg] + ":" + (d? "dst" : "src")) + ")";
|
||||
}
|
||||
|
||||
function toRM(mod, w, r_m) {
|
||||
return toBin(r_m, 3) + " (" + aRM[mod][mod < 3? 0 : w][r_m] + ")";
|
||||
}
|
||||
|
||||
function toBin(v, len) {
|
||||
var s = "0000000000000000" + v.toString(2);
|
||||
return s.slice(s.length - (len === undefined? 8 : (len < 16? len : 16)));
|
||||
}
|
||||
|
||||
function toHex(v, len) {
|
||||
var s = "00000000" + v.toString(16);
|
||||
return s.slice(s.length - (len === undefined? 4 : (len < 8? len : 8))).toUpperCase();
|
||||
}
|
||||
58
modules/pcjs/lib/.jshintrc
Normal file
58
modules/pcjs/lib/.jshintrc
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"boss": true,
|
||||
"eqnull": true,
|
||||
"evil": true,
|
||||
"loopfunc": true,
|
||||
"sub": true,
|
||||
"globalstrict": true,
|
||||
"globals": {
|
||||
"APP_PCJS": true,
|
||||
"APPNAME": false,
|
||||
"APPVERSION": false,
|
||||
"SITEHOST": false,
|
||||
"DEBUG": true,
|
||||
"MAXDEBUG": false,
|
||||
"PCJSCLASS": true,
|
||||
"DEBUGGER": true,
|
||||
"PREFETCH": true,
|
||||
"EAFUNCS": true,
|
||||
"FATARRAYS": true,
|
||||
"TYPEDARRAYS": true,
|
||||
"Component": true,
|
||||
"State": true,
|
||||
"Bus": true,
|
||||
"ChipSet": true,
|
||||
"Computer": true,
|
||||
"CPU": true,
|
||||
"Debugger": true,
|
||||
"Disk": true,
|
||||
"FDC": true,
|
||||
"HDC": true,
|
||||
"Keyboard": true,
|
||||
"Memory": true,
|
||||
"Mouse": true,
|
||||
"Panel": true,
|
||||
"RAM": true,
|
||||
"ROM": true,
|
||||
"SerialPort": true,
|
||||
"Video": true,
|
||||
"X86": true,
|
||||
"X86Seg": true,
|
||||
"X86CPU": true,
|
||||
"X86Grps": true,
|
||||
"X86Help": true,
|
||||
"X86Mods": true,
|
||||
"X86OpXX": true,
|
||||
"X86Op0F": true,
|
||||
"str": true,
|
||||
"usr": true,
|
||||
"web": true,
|
||||
"global": true,
|
||||
"module": true,
|
||||
"require": true,
|
||||
"setTimeout": false,
|
||||
"clearTimeout": false,
|
||||
"webkitAudioContext": false,
|
||||
"window": true
|
||||
}
|
||||
}
|
||||
67
modules/pcjs/lib/README.md
Normal file
67
modules/pcjs/lib/README.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
PCjs Sources
|
||||
===
|
||||
|
||||
Structure
|
||||
---
|
||||
All the code for PCjs is contained in the following JavaScript files, which roughly divide the
|
||||
functionality into major PC components, aka "devices". However, not every file implements a device,
|
||||
and "component" is an overloaded term, since *[Component](/docs/pcjs/component/)* is also the name of
|
||||
the shared base class used for most PCjs devices (see [component.js](../../shared/lib/component.js)).
|
||||
So it's best to refer to these files generically as "modules", and more specifically as "device modules"
|
||||
whenever they implement a specific device (or set of devices, in the case of [*Chipset*](/docs/pcjs/chipset/)).
|
||||
|
||||
Examples of non-device modules include UI modules like [panel.js](panel.js) and [debugger.js](debugger.js),
|
||||
and sub-modules like [x86opxx.js](x86opxx.js), [x86mods.js](x86mods.js) and [x86help.js](x86help.js)
|
||||
that separate the CPU functionality of [x86.js](x86.js) into more manageable pieces.
|
||||
|
||||
These modules should always be loaded or compiled in the order listed by the *pcJSFiles* property in
|
||||
[package.json](../../../package.json), which includes all the necessary *shared* modules as well.
|
||||
At the time of this writing, the order is:
|
||||
|
||||
* [shared/defines.js](../../shared/lib/defines.js)
|
||||
* [shared/diskapi.js](../../shared/lib/diskapi.js)
|
||||
* [shared/dumpapi.js](../../shared/lib/dumpapi.js)
|
||||
* [shared/reportapi.js](../../shared/lib/reportapi.js)
|
||||
* [shared/userapi.js](../../shared/lib/userapi.js)
|
||||
* [shared/strlib.js](../../shared/lib/strlib.js)
|
||||
* [shared/usrlib.js](../../shared/lib/usrlib.js)
|
||||
* [shared/weblib.js](../../shared/lib/weblib.js)
|
||||
* [shared/component.js](../../shared/lib/component.js)
|
||||
* [pcjs/defines.js](defines.js)
|
||||
* [pcjs/panel.js](panel.js)
|
||||
* [pcjs/bus.js](bus.js)
|
||||
* [pcjs/mem.js](mem.js)
|
||||
* [pcjs/cpu.js](cpu.js)
|
||||
* [pcjs/x86.js](x86.js)
|
||||
* [pcjs/x86seg.js](x86seg.js)
|
||||
* [pcjs/x86cpu.js](x86cpu.js)
|
||||
* [pcjs/x86grps.js](x86grps.js)
|
||||
* [pcjs/x86help.js](x86help.js)
|
||||
* [pcjs/x86mods.js](x86mods.js)
|
||||
* [pcjs/x86op0f.js](x86op0f.js)
|
||||
* [pcjs/x86opxx.js](x86opxx.js)
|
||||
* [pcjs/chipset.js](chipset.js)
|
||||
* [pcjs/rom.js](rom.js)
|
||||
* [pcjs/ram.js](ram.js)
|
||||
* [pcjs/keyboard.js](keyboard.js)
|
||||
* [pcjs/video.js](video.js)
|
||||
* [pcjs/serial.js](serial.js)
|
||||
* [pcjs/mouse.js](mouse.js)
|
||||
* [pcjs/disk.js](disk.js)
|
||||
* [pcjs/fdc.js](fdc.js)
|
||||
* [pcjs/hdc.js](hdc.js)
|
||||
* [pcjs/debugger.js](debugger.js)
|
||||
* [pcjs/state.js](state.js)
|
||||
* [pcjs/computer.js](computer.js)
|
||||
* [shared/embed.js](../../shared/lib/embed.js)
|
||||
|
||||
Some of the modules *can* be reordered or even omitted (eg, [debugger.js](debugger.js) or
|
||||
[embed.js](../../shared/lib/embed.js)), but you should observe the following:
|
||||
|
||||
* [component.js](../../shared/lib/component.js) must be listed before any module that extends [*Component*](/docs/pcjs/component/)
|
||||
* [panel.js](panel.js) should be loaded early to initialize the Control Panel (if any) as soon as possible
|
||||
* [computer.js](computer.js) should be the last device module, as it supervises and notifies all the other device modules
|
||||
|
||||
To minimize ordering requirements, the init() handlers and constructors of all modules should avoid
|
||||
referencing other modules. Device modules should define an initBus() notification handler, which the
|
||||
[*Computer*](/docs/pcjs/computer/) will call after it has created/initialized the *Bus* object.
|
||||
795
modules/pcjs/lib/bus.js
Normal file
795
modules/pcjs/lib/bus.js
Normal file
|
|
@ -0,0 +1,795 @@
|
|||
/**
|
||||
* @fileoverview Implements the PCjs Bus component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Sep-04
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var Memory = require("./mem");
|
||||
var State = require("./state");
|
||||
}
|
||||
|
||||
/**
|
||||
* Bus(cpu, dbg)
|
||||
*
|
||||
* The Bus component manages "physical" memory and I/O address spaces.
|
||||
*
|
||||
* The Bus component has no UI elements, so it does not require an init() handler,
|
||||
* but it still inherits from the Component class and must be allocated like any
|
||||
* other device component. It's currently allocated by the Computer's init() handler,
|
||||
* which then calls the initBus() method of all the other components.
|
||||
*
|
||||
* When initMemory() initializes the entire address space, it also passes aMemBlocks
|
||||
* to the CPU object, so that the CPU can perform all its own address-to-block and memory
|
||||
* block accesses directly.
|
||||
*
|
||||
* For memory beyond the simple needs of the ROM and RAM components (ie, memory-mapped
|
||||
* devices), the address space must still be allocated through the Bus component via
|
||||
* addMemory(). If the component needs something more than simple read/write storage,
|
||||
* it must provide a controller with getMemoryBuffer() and getMemoryAccess() methods.
|
||||
*
|
||||
* By contrast, all port access operations are defined by external handlers; they
|
||||
* register with us, and we manage those registrations, and we'll probably provide I/O
|
||||
* breakpoints at some point, but unlike memory accesses, we're not involved with I/O
|
||||
* accesses at all.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @param {Object} parmsBus
|
||||
* @param {X86CPU|Component} cpu
|
||||
* @param {Debugger|Component} dbg
|
||||
*/
|
||||
function Bus(parmsBus, cpu, dbg)
|
||||
{
|
||||
Component.call(this, "Bus", parmsBus, Bus);
|
||||
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
|
||||
this.nBusWidth = parmsBus['buswidth'] || 20;
|
||||
|
||||
/*
|
||||
* Compute all the Bus memory block addressing values that we rely on, based on the width of the bus.
|
||||
*
|
||||
* Regarding this.blockTotal, we want to avoid address-overflow-detection expressions like:
|
||||
*
|
||||
* iBlock < this.blockTotal? iBlock : 0
|
||||
*
|
||||
* and as long as we know that this.blockTotal is a power-of-two (eg, 256 or 0x100, in the case
|
||||
* of nBusWidth == 20), we can define this.blockMask as (this.blockTotal - 1) and rewrite the previous
|
||||
* expression as:
|
||||
*
|
||||
* iBlock & this.blockMask
|
||||
*
|
||||
* While we *could* say that we mask addresses with this.addrLimit to simulate "A20 wrap", the simple
|
||||
* fact is it relieves us from bounds-checking every aMemBlocks index. Address wrapping at the 1Mb
|
||||
* boundary (ie, the A20 address line) is something we'll have to deal with more carefully on the 80286.
|
||||
*
|
||||
* New property Old property Old hard-coded values (when nBusWidth was always 20)
|
||||
* ------------ ------------ ----------------------------------------------------
|
||||
* this.addrLimit Bus.ADDR.LIMIT 0xfffff
|
||||
* this.addrMask N/A N/A
|
||||
* this.blockSize Bus.BLOCK.SIZE 4096
|
||||
* this.blockLen Bus.BLOCK.LEN (this.blockSize >> 2)
|
||||
* this.blockShift Bus.BLOCK.SHIFT 12
|
||||
* this.blockLimit Bus.BLOCK.LIMIT 0xfff
|
||||
* this.blockTotal Bus.BLOCK.TOTAL ((this.addrLimit + this.blockSize) / this.blockSize) | 0
|
||||
* this.blockMask Bus.BLOCK.MASK (this.blockTotal - 1) (ie, 0xff)
|
||||
*
|
||||
* Note that the blockShift calculation below chooses a 4Kb physical memory block size for a 20-bit bus
|
||||
* (1Mb address space) and a 16Kb physical memory block for a 24-bit bus (16Mb address space). This yields
|
||||
* a 256-block array for the smaller bus and a 1024-block array for the larger bus. If we left the block
|
||||
* size at 4Kb in all cases, we'd end up with a 4096-block array for an 80286, which seems a bit excessive.
|
||||
*
|
||||
* I can't think of any reason why a coarser block granularity (of 16Kb) should hurt anything, other than
|
||||
* wasting a little memory for ROMs smaller than the block size. Realize that this is strictly a physical
|
||||
* memory implementation detail, which should have no bearing on segment or page granularity of any future
|
||||
* virtual memory implementation.
|
||||
*/
|
||||
this.addrLimit = this.addrMask = (1 << this.nBusWidth) - 1;
|
||||
this.blockShift = (this.nBusWidth <= 20? 12 : 14);
|
||||
this.blockSize = 1 << this.blockShift;
|
||||
this.blockLen = this.blockSize >> 2;
|
||||
this.blockLimit = this.blockSize - 1;
|
||||
this.blockTotal = ((this.addrLimit + this.blockSize) / this.blockSize) | 0;
|
||||
this.blockMask = this.blockTotal - 1;
|
||||
|
||||
/*
|
||||
* Lists of I/O notification functions: aPortInputNotify and aPortOutputNotify are arrays, indexed by
|
||||
* port, of sub-arrays which contain:
|
||||
*
|
||||
* [0]: registered component
|
||||
* [1]: registered function to call for every I/O access
|
||||
*
|
||||
* The registered function is called with the port address, and if the access was triggered by the CPU,
|
||||
* the physical address (EIP) that the access occurred from.
|
||||
*
|
||||
* WARNING: Unlike the (old) read and write memory notification functions, these support only one
|
||||
* pair of input/output functions per port. A more sophisticated architecture could support a list
|
||||
* of chained functions across multiple components, but I doubt that will be necessary here.
|
||||
*
|
||||
* UPDATE: The Debugger now piggy-backs on these arrays to indicate ports for which it wants notification
|
||||
* of I/O. In those cases, the registered component/function elements may or may not be set, but the following
|
||||
* additional element will be set:
|
||||
*
|
||||
* [2]: true to break on I/O, false to ignore I/O
|
||||
*
|
||||
* The false case is important if fPortInputBreakAll and/or fPortOutputBreakAll is set, because it allows the
|
||||
* Debugger to selectively ignore specific ports.
|
||||
*/
|
||||
this.aPortInputNotify = [];
|
||||
this.aPortOutputNotify = [];
|
||||
this.fPortInputBreakAll = this.fPortOutputBreakAll = false;
|
||||
|
||||
/*
|
||||
* Allocate empty Memory blocks to span the entire physical address space.
|
||||
*/
|
||||
this.initMemory();
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
Component.subclass(Component, Bus);
|
||||
|
||||
/**
|
||||
* initMemory()
|
||||
*
|
||||
* Allocate enough (empty) Memory blocks to span the entire physical address space.
|
||||
*
|
||||
* @this {Bus}
|
||||
*/
|
||||
Bus.prototype.initMemory = function()
|
||||
{
|
||||
this.aMemBlocks = new Array(this.blockTotal);
|
||||
for (var iBlock = 0; iBlock < this.blockTotal; iBlock++) {
|
||||
var addr = iBlock * this.blockSize;
|
||||
var block = this.aMemBlocks[iBlock] = new Memory(addr);
|
||||
if (DEBUGGER) block.setDebugInfo(this.cpu, this.dbg, addr, this.blockSize);
|
||||
}
|
||||
this.cpu.initMemory(this.aMemBlocks, this.addrLimit, this.blockShift, this.blockLimit, this.blockMask);
|
||||
this.cpu.setAddressMask(this.addrMask);
|
||||
};
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* @this {Bus}
|
||||
*/
|
||||
Bus.prototype.reset = function()
|
||||
{
|
||||
this.setA20(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* We don't need a powerDown() handler, because for largely historical reasons, our state (including the A20 state)
|
||||
* is saved by saveMemory().
|
||||
*
|
||||
* However, we do need a powerUp() handler, because on resumable machines, the Computer's onReset() function calls
|
||||
* everyone's powerUp() handler rather than their reset() handler.
|
||||
*
|
||||
* TODO: Perhaps Computer should be smarter: if there's no powerUp() handler, then fallback to the reset() handler.
|
||||
* In that case, however, we'd either need to remove the powerUp() stub in Component, or detect the existence of the stub.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {Object|null} data (always null because we supply no powerDown() handler)
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
Bus.prototype.powerUp = function(data, fRepower)
|
||||
{
|
||||
if (!fRepower) this.reset();
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* addMemory(addr, size, fReadOnly, controller)
|
||||
*
|
||||
* Adds new Memory blocks to the specified address range. Any Memory blocks previously
|
||||
* added to that range must first be removed via removeMemory(); otherwise, you'll get
|
||||
* an allocation conflict error. Moreover, the address range must start at a block-granular
|
||||
* address and span exactly one or more blocks; otherwise, you'll get a memory range error.
|
||||
*
|
||||
* These restrictions help prevent address calculation errors, redundant allocations, etc.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is the starting physical address of the memory address range
|
||||
* @param {number} size of the length in bytes of the range; must be a multiple of BLOCK_SIZE
|
||||
* @param {boolean} [fReadOnly] is true if the memory must be read-only; default is read-write
|
||||
* @param {Object} [controller] is an optional memory controller component
|
||||
* @return {boolean} true if successful, false if not
|
||||
*/
|
||||
Bus.prototype.addMemory = function(addr, size, fReadOnly, controller)
|
||||
{
|
||||
if (!(addr & this.blockLimit) && size && !(size & this.blockLimit)) {
|
||||
var iBlock = addr >> this.blockShift;
|
||||
while (size > 0 && iBlock < this.aMemBlocks.length) {
|
||||
var block = this.aMemBlocks[iBlock];
|
||||
if (block !== undefined && block.size) {
|
||||
return this.reportError(1, addr, size);
|
||||
}
|
||||
addr = iBlock * this.blockSize;
|
||||
block = this.aMemBlocks[iBlock++] = new Memory(addr, this.blockSize, fReadOnly, controller);
|
||||
if (DEBUGGER) block.setDebugInfo(this.cpu, this.dbg, addr, this.blockSize);
|
||||
size -= this.blockSize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return this.reportError(2, addr, size);
|
||||
};
|
||||
|
||||
/**
|
||||
* cleanMemory(addr, size)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr
|
||||
* @param {number} size
|
||||
* @return {boolean} true if all blocks were clean, false if dirty; all blocks are cleaned in the process
|
||||
*/
|
||||
Bus.prototype.cleanMemory = function(addr, size)
|
||||
{
|
||||
var fClean = true;
|
||||
var iBlock = addr >> this.blockShift;
|
||||
while (size > 0 && iBlock < this.aMemBlocks.length) {
|
||||
if (this.aMemBlocks[iBlock].fDirty) {
|
||||
this.aMemBlocks[iBlock].fDirty = fClean = false;
|
||||
this.aMemBlocks[iBlock].fDirtyEver = true;
|
||||
}
|
||||
size -= this.blockSize;
|
||||
iBlock++;
|
||||
}
|
||||
return fClean;
|
||||
};
|
||||
|
||||
/**
|
||||
* getA20()
|
||||
*
|
||||
* @this {Bus}
|
||||
* @return {boolean} true if enabled, false if disabled
|
||||
*/
|
||||
Bus.prototype.getA20 = function()
|
||||
{
|
||||
return this.addrLimit == this.addrMask;
|
||||
};
|
||||
|
||||
/**
|
||||
* setA20(fEnable)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {boolean} fEnable is true to enable A20 (default), false to disable
|
||||
*/
|
||||
Bus.prototype.setA20 = function(fEnable)
|
||||
{
|
||||
Component.assert(fEnable !== undefined);
|
||||
if (fEnable !== undefined) {
|
||||
if (this.nBusWidth > 20) {
|
||||
var addrMask = (this.addrMask & ~0x100000) | (fEnable? 0x100000 : 0);
|
||||
if (addrMask != this.addrMask) {
|
||||
this.addrMask = addrMask;
|
||||
/*
|
||||
* This callback is required only because the CPU "insists" on using its own memory access functions.
|
||||
*/
|
||||
if (this.cpu) this.cpu.setAddressMask(addrMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* setMemoryAccess(addr, size)
|
||||
*
|
||||
* Updates the access functions in every block of the specified address range. Since the only components
|
||||
* that should be dynamically modifying the memory access functions are those that use addMemory() with a custom
|
||||
* memory controller, we require that the block(s) being updated do in fact have a controller.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr
|
||||
* @param {number} size
|
||||
* @param {Array.<function()>} [afn]
|
||||
* @return {boolean} true if successful, false if not
|
||||
*/
|
||||
Bus.prototype.setMemoryAccess = function(addr, size, afn)
|
||||
{
|
||||
if (!(addr & this.blockLimit) && size && !(size & this.blockLimit)) {
|
||||
var iBlock = addr >> this.blockShift;
|
||||
while (size > 0) {
|
||||
var block = this.aMemBlocks[iBlock];
|
||||
if (!block.controller) {
|
||||
return this.reportError(5, addr, size);
|
||||
}
|
||||
block.setAccess(afn);
|
||||
size -= this.blockSize;
|
||||
iBlock++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return this.reportError(3, addr, size);
|
||||
};
|
||||
|
||||
/**
|
||||
* removeMemory(addr, size)
|
||||
*
|
||||
* Replaces every block in the specified address range with empty Memory blocks that will ignore all reads/writes.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr
|
||||
* @param {number} size
|
||||
* @return {boolean} true if successful, false if not
|
||||
*/
|
||||
Bus.prototype.removeMemory = function(addr, size)
|
||||
{
|
||||
if (!(addr & this.blockLimit) && size && !(size & this.blockLimit)) {
|
||||
var iBlock = addr >> this.blockShift;
|
||||
while (size > 0) {
|
||||
addr = iBlock * this.blockSize;
|
||||
var block = this.aMemBlocks[iBlock++] = new Memory(addr);
|
||||
if (DEBUGGER) block.setDebugInfo(this.cpu, this.dbg, addr, this.blockSize);
|
||||
size -= this.blockSize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return this.reportError(4, addr, size);
|
||||
};
|
||||
|
||||
/**
|
||||
* getByteDirect(addr)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @return {number} byte (8-bit) value at that address
|
||||
*/
|
||||
Bus.prototype.getByteDirect = function(addr)
|
||||
{
|
||||
return this.aMemBlocks[(addr & this.addrMask) >> this.blockShift].readByteDirect(addr & this.blockLimit);
|
||||
};
|
||||
|
||||
/**
|
||||
* getWordDirect(addr)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @return {number} word (16-bit) value at that address
|
||||
*/
|
||||
Bus.prototype.getWordDirect = function(addr)
|
||||
{
|
||||
var off = addr & this.blockLimit;
|
||||
var iBlock = (addr & this.addrMask) >> this.blockShift;
|
||||
if (off != this.blockLimit) {
|
||||
return this.aMemBlocks[iBlock].readWordDirect(off);
|
||||
}
|
||||
return this.aMemBlocks[iBlock++].readByteDirect(off) | (this.aMemBlocks[iBlock & this.blockMask].readByteDirect(0) << 8);
|
||||
};
|
||||
|
||||
/**
|
||||
* setByteDirect(addr, b)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} b is the byte (8-bit) value to write (we truncate it to 8 bits to be safe)
|
||||
*/
|
||||
Bus.prototype.setByteDirect = function(addr, b)
|
||||
{
|
||||
this.aMemBlocks[(addr & this.addrMask) >> this.blockShift].writeByteDirect(addr & this.blockLimit, b & 0xff);
|
||||
};
|
||||
|
||||
/**
|
||||
* setWordDirect(addr, w)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr is a physical (non-segmented) address
|
||||
* @param {number} w is the word (16-bit) value to write (we truncate it to 16 bits to be safe)
|
||||
*/
|
||||
Bus.prototype.setWordDirect = function(addr, w)
|
||||
{
|
||||
var off = addr & this.blockLimit;
|
||||
var iBlock = (addr & this.addrMask) >> this.blockShift;
|
||||
if (off != this.blockLimit) {
|
||||
this.aMemBlocks[iBlock].writeWordDirect(off, w & 0xffff);
|
||||
return;
|
||||
}
|
||||
this.aMemBlocks[iBlock++].writeByteDirect(off, w & 0xff);
|
||||
this.aMemBlocks[iBlock & this.blockMask].writeByteDirect(0, (w >> 8) & 0xff);
|
||||
};
|
||||
|
||||
/**
|
||||
* saveMemory()
|
||||
*
|
||||
* The only memory blocks we save are those marked as dirty; most likely all of RAM will have been marked dirty,
|
||||
* and even if our dirty-memory flags were as smart as our dirty-sector flags (ie, were set only when a write changed
|
||||
* what was already there), it's unlikely that would reduce the number of RAM blocks we must save/restore. At least
|
||||
* all the ROM blocks should be clean (except in the unlikely event that the Debugger was used to modify them).
|
||||
*
|
||||
* All dirty blocks will be stored in a single array, as pairs of block numbers and data arrays, like so:
|
||||
*
|
||||
* [iBlock0, [dw0, dw1, ...], iBlock1, [dw0, dw1, ...], ...]
|
||||
*
|
||||
* In a normal 4Kb block, there will be 1K DWORD values in the data array. Remember that each DWORD is a signed 32-bit
|
||||
* integer (because they are formed using bit-wise operator rather than floating-point math operators), so don't be
|
||||
* surprised to see negative numbers in the data.
|
||||
*
|
||||
* The above example assumes "uncompressed" data arrays. If we choose to use "compressed" data arrays, the data arrays
|
||||
* will look like:
|
||||
*
|
||||
* [count0, dw0, count1, dw1, ...]
|
||||
*
|
||||
* where each count indicates how many times the following DWORD value occurs. A data array length less than 1K indicates
|
||||
* that it's compressed, since we'll only store them in compressed form if they actually shrank, and we'll use State
|
||||
* helper methods compress() and decompress() to create and expand the compressed data arrays.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @return {Array} a
|
||||
*/
|
||||
Bus.prototype.saveMemory = function()
|
||||
{
|
||||
var i = 0;
|
||||
var a = [];
|
||||
for (var iBlock = 0; iBlock < this.blockTotal; iBlock++) {
|
||||
var block = this.aMemBlocks[iBlock];
|
||||
/*
|
||||
* We have to check both fDirty and fDirtyEver, because we may have called cleanMemory() on some of
|
||||
* the memory blocks (eg, video memory), and while cleanMemory() will clear a dirty block's fDirty flag,
|
||||
* it also sets the dirty block's fDirtyEver flag, which is left set for the lifetime of the machine.
|
||||
*/
|
||||
if (block.fDirty || block.fDirtyEver) {
|
||||
a[i++] = iBlock;
|
||||
a[i++] = State.compress(block.save());
|
||||
}
|
||||
}
|
||||
a[i] = this.getA20();
|
||||
return a;
|
||||
};
|
||||
|
||||
/**
|
||||
* restoreMemory(a)
|
||||
*
|
||||
* This restores the contents of all Memory blocks; called by X86CPU.restore().
|
||||
*
|
||||
* In theory, we ONLY have to save/restore block contents. Other block attributes,
|
||||
* like fReadOnly, the memory controller (if any), and the active memory access functions,
|
||||
* should already be restored, since every component (re)allocates all the memory blocks
|
||||
* it was using when it's restored. And since the CPU is guaranteed to be the last
|
||||
* component to be restored, all those blocks (and their attributes) should be in place now.
|
||||
*
|
||||
* See saveMemory() for a description of how the memory block contents are saved.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {Array} a
|
||||
* @return {boolean} true if successful, false if not
|
||||
*/
|
||||
Bus.prototype.restoreMemory = function(a)
|
||||
{
|
||||
var i;
|
||||
for (i = 0; i < a.length - 1; i += 2) {
|
||||
var iBlock = a[i];
|
||||
var adw = a[i+1];
|
||||
if (adw && adw.length < this.blockLen) {
|
||||
adw = State.decompress(adw, this.blockLen);
|
||||
}
|
||||
var block = this.aMemBlocks[iBlock];
|
||||
if (!block || !block.restore(adw)) {
|
||||
/*
|
||||
* Either the block to restore hasn't been allocated, indicating a change in the machine
|
||||
* configuration since it was last saved (the most likely explanation) or there's some internal
|
||||
* inconsistency (eg, the block size is wrong).
|
||||
*/
|
||||
Component.error("Unable to restore memory block " + iBlock);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (a[i] !== undefined) this.setA20(a[i]);
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* addMemoryBreakpoint(addr, fWrite)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr
|
||||
* @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
|
||||
*/
|
||||
Bus.prototype.addMemoryBreakpoint = function(addr, fWrite)
|
||||
{
|
||||
if (DEBUGGER) {
|
||||
var iBlock = addr >> this.blockShift;
|
||||
this.aMemBlocks[iBlock].addBreakpoint(addr & this.blockLimit, fWrite);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* removeMemoryBreakpoint(addr, fWrite)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr
|
||||
* @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
|
||||
*/
|
||||
Bus.prototype.removeMemoryBreakpoint = function(addr, fWrite)
|
||||
{
|
||||
if (DEBUGGER) {
|
||||
var iBlock = addr >> this.blockShift;
|
||||
this.aMemBlocks[iBlock].removeBreakpoint(addr & this.blockLimit, fWrite);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* addPortInputBreak(port)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} [port]
|
||||
* @return {boolean} true if break on port input enabled, false if disabled
|
||||
*/
|
||||
Bus.prototype.addPortInputBreak = function(port)
|
||||
{
|
||||
if (port === undefined) {
|
||||
this.fPortInputBreakAll = !this.fPortInputBreakAll;
|
||||
return this.fPortInputBreakAll;
|
||||
}
|
||||
if (this.aPortInputNotify[port] === undefined) {
|
||||
this.aPortInputNotify[port] = [null, null, false];
|
||||
}
|
||||
this.aPortInputNotify[port][2] = !this.aPortInputNotify[port][2];
|
||||
return this.aPortInputNotify[port][2];
|
||||
};
|
||||
|
||||
/**
|
||||
* addPortInputNotify(start, end, component, fn)
|
||||
*
|
||||
* Add a port input-notification handler to the list of such handlers.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} start port address
|
||||
* @param {number} end port address
|
||||
* @param {Component} component
|
||||
* @param {function(number,number)} fn is called with the port and EIP values at the time of the input
|
||||
*/
|
||||
Bus.prototype.addPortInputNotify = function(start, end, component, fn)
|
||||
{
|
||||
if (fn !== undefined) {
|
||||
for (var port = start; port <= end; port++) {
|
||||
if (this.aPortInputNotify[port] !== undefined) {
|
||||
Component.warning("Input port " + str.toHexWord(port) + " registered by " + this.aPortInputNotify[port][0].id + ", ignoring " + component.id);
|
||||
continue;
|
||||
}
|
||||
this.aPortInputNotify[port] = [component, fn, false, false];
|
||||
if (MAXDEBUG) this.log("addPortInputNotify(" + str.toHexWord(port) + "," + component.id + ")");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* addPortInputTable(component, table, offset)
|
||||
*
|
||||
* Add port input-notification handlers from the specified table (a batch version of addPortInputNotify)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {Component} component
|
||||
* @param {Object} table
|
||||
* @param {number} [offset] is an optional port offset
|
||||
*/
|
||||
Bus.prototype.addPortInputTable = function(component, table, offset)
|
||||
{
|
||||
if (offset === undefined) offset = 0;
|
||||
for (var port in table) {
|
||||
/*
|
||||
* JavaScript coerces property keys to strings, so we use parseInt() to coerce them back to numbers.
|
||||
*/
|
||||
port = parseInt(port, 10);
|
||||
this.addPortInputNotify(port + offset, port + offset, component, table[port]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* checkPortInputNotify(port, addrFrom)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} port
|
||||
* @param {number} [addrFrom] is the EIP value at the time of the input
|
||||
* @return {number} simulated port value (0xff if none)
|
||||
*
|
||||
* NOTE: It seems that at least parts of the ROM BIOS (like the RS-232 probes around F000:E5D7 in the 5150 BIOS)
|
||||
* assume that ports for non-existent hardware return 0xff rather than 0x00, hence my new default (0xff) below.
|
||||
*/
|
||||
Bus.prototype.checkPortInputNotify = function(port, addrFrom)
|
||||
{
|
||||
var bIn = 0xff;
|
||||
var aNotify = this.aPortInputNotify[port];
|
||||
if (aNotify !== undefined) {
|
||||
if (aNotify[1]) {
|
||||
bIn = aNotify[1].call(aNotify[0], port, addrFrom);
|
||||
}
|
||||
if (DEBUGGER && this.dbg && this.fPortInputBreakAll != aNotify[2]) {
|
||||
this.dbg.checkPortInput(port, bIn);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (DEBUGGER && this.dbg) {
|
||||
this.dbg.messagePort(this, port, null, addrFrom);
|
||||
if (this.fPortInputBreakAll) this.dbg.checkPortInput(port, bIn);
|
||||
}
|
||||
}
|
||||
return bIn;
|
||||
};
|
||||
|
||||
/**
|
||||
* removePortInputNotify(start, end, component, fn)
|
||||
*
|
||||
* Remove a port input-notification handler from the list of such handlers (to be ENABLED later if needed)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} start address
|
||||
* @param {number} end address
|
||||
* @param {Component} component
|
||||
* @param {function(number,number)} fn of previously added handler
|
||||
*
|
||||
Bus.prototype.removePortInputNotify = function(start, end, component, fn)
|
||||
{
|
||||
for (var port = start; port < end; port++) {
|
||||
if (this.aPortInputNotify[port] && this.aPortInputNotify[port][0] == component && this.aPortInputNotify[port][1] == fn) {
|
||||
this.aPortInputNotify[port] = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
/**
|
||||
* addPortOutputBreak(port)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} [port]
|
||||
* @return {boolean} true if break on port output enabled, false if disabled
|
||||
*/
|
||||
Bus.prototype.addPortOutputBreak = function(port)
|
||||
{
|
||||
if (port === undefined) {
|
||||
this.fPortOutputBreakAll = !this.fPortOutputBreakAll;
|
||||
return this.fPortOutputBreakAll;
|
||||
}
|
||||
if (this.aPortOutputNotify[port] === undefined) {
|
||||
this.aPortOutputNotify[port] = [null, null, false];
|
||||
}
|
||||
this.aPortOutputNotify[port][2] = !this.aPortOutputNotify[port][2];
|
||||
return this.aPortOutputNotify[port][2];
|
||||
};
|
||||
|
||||
/**
|
||||
* addPortOutputNotify(start, end, component, fn)
|
||||
*
|
||||
* Add a port output-notification handler to the list of such handlers.
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} start port address
|
||||
* @param {number} end port address
|
||||
* @param {Component} component
|
||||
* @param {function(number,number)} fn is called with the port and EIP values at the time of the output
|
||||
*/
|
||||
Bus.prototype.addPortOutputNotify = function(start, end, component, fn)
|
||||
{
|
||||
if (fn !== undefined) {
|
||||
for (var port = start; port <= end; port++) {
|
||||
if (this.aPortOutputNotify[port] !== undefined) {
|
||||
Component.warning("Output port " + str.toHexWord(port) + " registered by " + this.aPortOutputNotify[port][0].id + ", ignoring " + component.id);
|
||||
continue;
|
||||
}
|
||||
this.aPortOutputNotify[port] = [component, fn, false, false];
|
||||
if (MAXDEBUG) this.log("addPortOutputNotify(" + str.toHexWord(port) + "," + component.id + ")");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* addPortOutputTable(component, table, offset)
|
||||
*
|
||||
* Add port output-notification handlers from the specified table (a batch version of addPortOutputNotify)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {Component} component
|
||||
* @param {Object} table
|
||||
* @param {number} [offset] is an optional port offset
|
||||
*/
|
||||
Bus.prototype.addPortOutputTable = function(component, table, offset)
|
||||
{
|
||||
if (offset === undefined) offset = 0;
|
||||
for (var port in table) {
|
||||
/*
|
||||
* JavaScript converts property keys to strings (brilliant), so we use parseInt() to convert them back to numbers.
|
||||
*/
|
||||
port = parseInt(port, 10);
|
||||
this.addPortOutputNotify(port + offset, port + offset, component, table[port]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* checkPortOutputNotify(port, bOut, addrFrom)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} port
|
||||
* @param {number} bOut
|
||||
* @param {number} [addrFrom] is the EIP value at the time of the output
|
||||
*/
|
||||
Bus.prototype.checkPortOutputNotify = function(port, bOut, addrFrom)
|
||||
{
|
||||
var aNotify = this.aPortOutputNotify[port];
|
||||
if (aNotify !== undefined) {
|
||||
if (aNotify[1]) {
|
||||
aNotify[1].call(aNotify[0], port, bOut, addrFrom);
|
||||
}
|
||||
if (DEBUGGER && this.dbg && this.fPortOutputBreakAll != aNotify[2]) {
|
||||
this.dbg.checkPortOutput(port, bOut);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (DEBUGGER && this.dbg) {
|
||||
this.dbg.messagePort(this, port, bOut, addrFrom);
|
||||
if (this.fPortOutputBreakAll) this.dbg.checkPortOutput(port, bOut);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* removePortOutputNotify(start, end, component, fn)
|
||||
*
|
||||
* Remove a port output-notification handler from the list of such handlers (to be ENABLED later if needed)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} start address
|
||||
* @param {number} end address
|
||||
* @param {Component} component
|
||||
* @param {function(number,number)} fn of previously added handler
|
||||
*
|
||||
Bus.prototype.removePortOutputNotify = function(start, end, component, fn)
|
||||
{
|
||||
for (var port = start; port < end; port++) {
|
||||
if (this.aPortOutputNotify[port] && this.aPortOutputNotify[port][0] == component && this.aPortOutputNotify[port][1] == fn) {
|
||||
this.aPortOutputNotify[port] = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
/**
|
||||
* reportError(op, addr, size)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} op
|
||||
* @param {number} addr
|
||||
* @param {number} size
|
||||
* @return {boolean} false
|
||||
*/
|
||||
Bus.prototype.reportError = function(op, addr, size)
|
||||
{
|
||||
Component.error("Memory block error (" + op + "," + str.toHex(addr) + "," + str.toHex(size) + ")");
|
||||
return false;
|
||||
};
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.Bus = Bus;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = Bus;
|
||||
4607
modules/pcjs/lib/chipset.js
Normal file
4607
modules/pcjs/lib/chipset.js
Normal file
File diff suppressed because it is too large
Load diff
1317
modules/pcjs/lib/computer.js
Normal file
1317
modules/pcjs/lib/computer.js
Normal file
File diff suppressed because it is too large
Load diff
1126
modules/pcjs/lib/cpu.js
Normal file
1126
modules/pcjs/lib/cpu.js
Normal file
File diff suppressed because it is too large
Load diff
4734
modules/pcjs/lib/debugger.js
Normal file
4734
modules/pcjs/lib/debugger.js
Normal file
File diff suppressed because it is too large
Load diff
118
modules/pcjs/lib/defines.js
Normal file
118
modules/pcjs/lib/defines.js
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
* @fileoverview PCjs-specific compile-time definitions.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-May-08
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* APP_PCJS collects all PCjs application globals in one convenient place
|
||||
*/
|
||||
if (DEBUG) {
|
||||
var APP_PCJS = {Component: null};
|
||||
if (typeof Component === 'function') APP_PCJS.Component = Component;
|
||||
}
|
||||
|
||||
/**
|
||||
* @define {string}
|
||||
*/
|
||||
var PCJSCLASS = "pcjs"; // this @define is the default application class (formerly APPCLASS) to use for PCjs
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
*
|
||||
* WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
|
||||
* In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate. When it's *false*,
|
||||
* nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
|
||||
* "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
|
||||
* debugger.js from the compilation process altogether.
|
||||
*
|
||||
* However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
|
||||
* I would like to skip loading debugger.js altogether. When doing that, we must ALSO arrange for an additional file
|
||||
* (nodebugger.js) to be loaded immediately after this file, which *explicitly* overrides DEBUGGER with *false*.
|
||||
*/
|
||||
var DEBUGGER = true; // this @define is overridden by the Closure Compiler to remove Debugger-related support
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
*
|
||||
* PREFETCH enables the use of a prefetch queue.
|
||||
*
|
||||
* See the Bus component for details.
|
||||
*/
|
||||
var PREFETCH = false;
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
*
|
||||
* EAFUNCS enables dynamic function switching whenever the CPU needs to disable one or both EA (Effective Address)
|
||||
* memory functions for a ModRM instruction that doesn't observe the normal "read/modify/write" behavior. The goal
|
||||
* is to avoid useless memory reads (which are mostly harmless) and stale memory writes (which are mostly destructive).
|
||||
*
|
||||
* If EAFUNCS is false, then the CPU falls back to setting/testing internal OP_NOREAD and OP_NOWRITE opFlags as
|
||||
* needed. At the moment, it seems that "EAFUNCS mode" is a bit slower than "EATESTS mode", so EAFUNCS is turned off;
|
||||
* however, your mileage may vary, depending on the browser and its vintage.
|
||||
*/
|
||||
var EAFUNCS = false;
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
*
|
||||
* FATARRAYS is a Closure Compiler compile-time option that allocates an Array of numbers for every Memory block,
|
||||
* where each a number represents ONE byte; very wasteful, but potentially slightly faster.
|
||||
*
|
||||
* See the Memory component for details.
|
||||
*/
|
||||
var FATARRAYS = false;
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
*
|
||||
* TYPEDARRAYS enables use of typed arrays for Memory blocks. This used to be a compile-time-only option, but I've
|
||||
* added Memory access functions for typed arrays (see Memory.afnTypedArray), so support can be enabled dynamically.
|
||||
*
|
||||
* However, TYPEDARRAYS has always been slightly slower than the original DWORDARRAYS implementation (which uses an
|
||||
* Array of numbers that stores 32 bits -- 4 consecutive bytes -- per number), so TYPEDARRAYS is completely disabled.
|
||||
*
|
||||
* See the Memory component for details.
|
||||
*/
|
||||
var TYPEDARRAYS = false; // (typeof ArrayBuffer !== 'undefined');
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
global.PCJSCLASS = PCJSCLASS;
|
||||
global.DEBUGGER = DEBUGGER;
|
||||
global.PREFETCH = PREFETCH;
|
||||
global.EAFUNCS = EAFUNCS;
|
||||
global.FATARRAYS = FATARRAYS;
|
||||
global.TYPEDARRAYS = TYPEDARRAYS;
|
||||
/*
|
||||
* TODO: When we're "required" by Node, should we return anything via module.exports?
|
||||
*/
|
||||
}
|
||||
1645
modules/pcjs/lib/disk.js
Normal file
1645
modules/pcjs/lib/disk.js
Normal file
File diff suppressed because it is too large
Load diff
2484
modules/pcjs/lib/fdc.js
Normal file
2484
modules/pcjs/lib/fdc.js
Normal file
File diff suppressed because it is too large
Load diff
2794
modules/pcjs/lib/hdc.js
Normal file
2794
modules/pcjs/lib/hdc.js
Normal file
File diff suppressed because it is too large
Load diff
1719
modules/pcjs/lib/keyboard.js
Normal file
1719
modules/pcjs/lib/keyboard.js
Normal file
File diff suppressed because it is too large
Load diff
590
modules/pcjs/lib/mem.js
Normal file
590
modules/pcjs/lib/mem.js
Normal file
|
|
@ -0,0 +1,590 @@
|
|||
/**
|
||||
* @fileoverview Implements the PCjs "physical" Memory component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Sep-04
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Historical Notes
|
||||
*
|
||||
* To minimize possible future confusion with regard to the 80386's page tables
|
||||
* and page-based virtual memory, the original Page component was converted into
|
||||
* this new Memory component, which provides callers with "blocks" of physical
|
||||
* memory rather than "pages". Callers have been updated to refer to their Memory
|
||||
* allocations as "blocks" as well.
|
||||
*
|
||||
* Note that the Bus component continues to specify a default block size of 4Kb (for
|
||||
* the default "buswidth" of 20), but only because that seems to strike a good balance
|
||||
* between data structure overhead and the memory granularity requirements of most
|
||||
* system components. For larger bus widths, larger physical block sizes may be used;
|
||||
* see the Bus constructor for details.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var Debugger = require("./debugger");
|
||||
}
|
||||
/**
|
||||
* @class DataView
|
||||
* @property {function(number,boolean):number} getUint8
|
||||
* @property {function(number,number,boolean)} setUint8
|
||||
* @property {function(number,boolean):number} getUint16
|
||||
* @property {function(number,number,boolean)} setUint16
|
||||
* @property {function(number,boolean):number} getInt32
|
||||
* @property {function(number,number,boolean)} setInt32
|
||||
*/
|
||||
|
||||
/**
|
||||
* Memory(addr, size, fReadOnly, controller)
|
||||
*
|
||||
* The Bus component allocates Memory objects so that each has a memory buffer with a
|
||||
* block-granular starting address and an address range equal to bus.blockSize; however,
|
||||
* the size of any given Memory object's underlying buffer can be either zero or bus.blockSize;
|
||||
* memory read/write functions for empty (buffer-less) blocks are mapped to readNone/writeNone.
|
||||
*
|
||||
* The Bus allocates empty blocks for the entire address space during initialization, so that
|
||||
* any reads/writes to undefined addresses will have no effect. Later, the ROM and RAM
|
||||
* components will ask the Bus to allocate memory for specific ranges, and the Bus will allocate
|
||||
* as many new BLOCK_SIZE Memory objects as the ranges require. Partial Memory blocks could be
|
||||
* supported in theory, but in practice, they're not.
|
||||
*
|
||||
* NOTE: Since Memory blocks are low-level objects that have no UI requirements, they do not
|
||||
* inherit from the Component class; so, if you want to use println(), for example, you must
|
||||
* use the methods in the Debugger class.
|
||||
*
|
||||
* Because Memory blocks now allow us to have a "sparse" address space, we could choose to
|
||||
* take the memory hit of allocating 4K arrays per block, where each element stores only one byte,
|
||||
* instead of the more frugal but slightly slower approach of allocating arrays of 32-bit dwords
|
||||
* (DWORDARRAYS) and shifting/masking bytes/words to/from dwords; in theory, byte accesses would
|
||||
* be faster and word accesses somewhat less faster.
|
||||
*
|
||||
* However, preliminary testing of that feature (FATARRAYS) did not yield significantly faster
|
||||
* performance, so it is OFF by default to minimize our memory consumption. Using TYPEDARRAYS is
|
||||
* probably best, although not all JavaScript implementations support them (IE9 is probably the
|
||||
* only real outlier: it lacks typed arrays but otherwise has all the necessary HTML5 support).
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} addr of block (must be some multiple of bus.blockSize)
|
||||
* @param {number} [size] of block's buffer in bytes (0 for none); must be a multiple of 4
|
||||
* @param {boolean} [fReadOnly] is true if the block must be marked read-only
|
||||
* @param {Object} [controller] is an optional memory controller component
|
||||
*/
|
||||
function Memory(addr, size, fReadOnly, controller) {
|
||||
this.cb = size || 0;
|
||||
this.adw = null;
|
||||
this.offset = 0;
|
||||
this.fReadOnly = fReadOnly;
|
||||
this.controller = null;
|
||||
this.fDirty = this.fDirtyEver = false;
|
||||
|
||||
/*
|
||||
* For empty memory blocks, all we need to do is ensure all access functions
|
||||
* are mapped to "none" handlers.
|
||||
*/
|
||||
if (!size) {
|
||||
this.setAccess();
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* When a controller is specified, the controller must provide a buffer,
|
||||
* via getMemoryBuffer(), and memory access functions, via getMemoryAccess().
|
||||
*/
|
||||
if (controller) {
|
||||
this.controller = controller;
|
||||
var a = controller.getMemoryBuffer(addr);
|
||||
this.adw = a[0];
|
||||
this.offset = a[1];
|
||||
this.setAccess(controller.getMemoryAccess());
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the normal case: allocate a buffer that provides 8 bits of data per address;
|
||||
* no controller is required because our default memory access functions (see afnMemory)
|
||||
* know how to deal with this simple 1-1 mapping of addresses to bytes and words.
|
||||
*/
|
||||
if (TYPEDARRAYS) {
|
||||
this.buffer = new ArrayBuffer(size);
|
||||
this.dv = new DataView(this.buffer, 0, size);
|
||||
/*
|
||||
* We could also use dv.getUint8() and dv.setUint8(), but using ab[] to get/set bytes
|
||||
* in this.buffer is more convenient and presents no "endianness" issues.
|
||||
*/
|
||||
this.ab = new Uint8Array(this.buffer, 0, size);
|
||||
this.setAccess(Memory.afnTypedArray);
|
||||
} else {
|
||||
if (FATARRAYS) {
|
||||
this.ab = new Array(size);
|
||||
} else {
|
||||
this.adw = new Array(size >> 2);
|
||||
for (var i = 0; i < this.adw.length; i++) {
|
||||
this.adw[i] = 0;
|
||||
}
|
||||
}
|
||||
this.setAccess(Memory.afnMemory);
|
||||
}
|
||||
}
|
||||
|
||||
Memory.prototype = {
|
||||
constructor: Memory,
|
||||
/**
|
||||
* readNone(off)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
readNone: function(off) {
|
||||
if (DEBUGGER && this.dbg.messageEnabled(Debugger.MESSAGE.MEM) && !off) {
|
||||
this.dbg.message("attempt to read invalid block %" + str.toHex(this.addr) + " from " + str.toHexAddr(this.cpu.regIP, this.cpu.segCS.sel));
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
/**
|
||||
* writeNone(off, v)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {number} v (could be either a byte or word value, since we use the same handler for both kinds of accesses)
|
||||
*/
|
||||
writeNone: function(off, v) {
|
||||
if (DEBUGGER && this.dbg.messageEnabled(Debugger.MESSAGE.MEM) && !off) {
|
||||
this.dbg.message("attempt to write 0x" + str.toHexWord(v) + " to invalid block %" + str.toHex(this.addr) + " from " + str.toHexAddr(this.cpu.regIP, this.cpu.segCS.sel));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* readByteMemory(off)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
readByteMemory: function readByteMemory(off) {
|
||||
Component.assert(off >= 0 && off < this.cb);
|
||||
if (FATARRAYS) {
|
||||
return this.ab[off];
|
||||
}
|
||||
return ((this.adw[off >> 2] >>> ((off & 0x3) << 3)) & 0xff);
|
||||
},
|
||||
/**
|
||||
* readWordMemory(off)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
readWordMemory: function readWordMemory(off) {
|
||||
Component.assert(off >= 0 && off < this.cb - 1);
|
||||
if (FATARRAYS) {
|
||||
return this.ab[off] | (this.ab[off + 1] << 8);
|
||||
}
|
||||
var w;
|
||||
var idw = off >> 2;
|
||||
var nShift = (off & 0x3) << 3;
|
||||
var dw = (this.adw[idw] >>> nShift);
|
||||
if (nShift < 24) {
|
||||
w = dw & 0xffff;
|
||||
} else {
|
||||
w = (dw & 0xff) | ((this.adw[idw + 1] & 0xff) << 8);
|
||||
}
|
||||
return w;
|
||||
},
|
||||
/**
|
||||
* writeByteMemory(off, b)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {number} b
|
||||
*/
|
||||
writeByteMemory: function writeByteMemory(off, b) {
|
||||
Component.assert(off >= 0 && off < this.cb && (b & 0xff) == b);
|
||||
if (FATARRAYS) {
|
||||
this.ab[off] = b;
|
||||
} else {
|
||||
var idw = off >> 2;
|
||||
var nShift = (off & 0x3) << 3;
|
||||
this.adw[idw] = (this.adw[idw] & ~(0xff << nShift)) | (b << nShift);
|
||||
}
|
||||
this.fDirty = true;
|
||||
},
|
||||
/**
|
||||
* writeWordMemory(off, w)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {number} w
|
||||
*/
|
||||
writeWordMemory: function writeWordMemory(off, w) {
|
||||
Component.assert(off >= 0 && off < this.cb - 1 && (w & 0xffff) == w);
|
||||
if (FATARRAYS) {
|
||||
this.ab[off] = (w & 0xff);
|
||||
this.ab[off + 1] = (w >> 8);
|
||||
} else {
|
||||
var idw = off >> 2;
|
||||
var nShift = (off & 0x3) << 3;
|
||||
if (nShift < 24) {
|
||||
this.adw[idw] = (this.adw[idw] & ~(0xffff << nShift)) | (w << nShift);
|
||||
} else {
|
||||
this.adw[idw] = (this.adw[idw] & 0x00ffffff) | (w << 24);
|
||||
idw++;
|
||||
this.adw[idw] = (this.adw[idw] & 0xffffff00) | (w >> 8);
|
||||
}
|
||||
}
|
||||
this.fDirty = true;
|
||||
},
|
||||
/**
|
||||
* readByteChecked(off)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
readByteChecked: function readByteChecked(off) {
|
||||
if (DEBUGGER) this.dbg.checkMemoryRead(this.addr + off);
|
||||
return this.readByteDirect(off);
|
||||
},
|
||||
/**
|
||||
* readWordChecked(off)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
readWordChecked: function readWordChecked(off) {
|
||||
if (DEBUGGER) {
|
||||
this.dbg.checkMemoryRead(this.addr + off) || this.dbg.checkMemoryRead(this.addr + off + 1); // jshint ignore:line
|
||||
}
|
||||
return this.readWordDirect(off);
|
||||
},
|
||||
/**
|
||||
* writeByteChecked(off, b)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {number} b
|
||||
*/
|
||||
writeByteChecked: function writeByteChecked(off, b) {
|
||||
if (DEBUGGER) this.dbg.checkMemoryWrite(this.addr + off);
|
||||
this.writeByteDirect(off, b);
|
||||
},
|
||||
/**
|
||||
* writeWordChecked(off, w)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {number} w
|
||||
*/
|
||||
writeWordChecked: function writeWordChecked(off, w) {
|
||||
if (DEBUGGER) {
|
||||
this.dbg.checkMemoryWrite(this.addr + off) || this.dbg.checkMemoryWrite(this.addr + off + 1); // jshint ignore:line
|
||||
}
|
||||
this.writeWordDirect(off, w);
|
||||
},
|
||||
/**
|
||||
* readByteTypedArray(off)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
readByteTypedArray: function readByteTypedArray(off) {
|
||||
Component.assert(off >= 0 && off < this.cb);
|
||||
return this.ab[off];
|
||||
},
|
||||
/**
|
||||
* readWordTypedArray(off)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
readWordTypedArray: function readWordTypedArray(off) {
|
||||
Component.assert(off >= 0 && off < this.cb - 1);
|
||||
return this.dv.getUint16(off, true);
|
||||
},
|
||||
/**
|
||||
* writeByteTypedArray(off, b)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {number} b
|
||||
*/
|
||||
writeByteTypedArray: function writeByteTypedArray(off, b) {
|
||||
Component.assert(off >= 0 && off < this.cb && (b & 0xff) == b);
|
||||
this.ab[off] = b;
|
||||
this.fDirty = true;
|
||||
},
|
||||
/**
|
||||
* writeWordTypedArray(off, w)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {number} w
|
||||
*/
|
||||
writeWordTypedArray: function writeWordTypedArray(off, w) {
|
||||
Component.assert(off >= 0 && off < this.cb - 1 && (w & 0xffff) == w);
|
||||
this.dv.setUint16(off, w, true);
|
||||
this.fDirty = true;
|
||||
},
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* This gets the contents of a Memory block as an array of 32-bit values;
|
||||
* used by Bus.saveMemory(), which in turn is called by X86CPU.save().
|
||||
*
|
||||
* Memory blocks with custom memory controllers do NOT save their contents;
|
||||
* that's the responsibility of the controller component.
|
||||
*
|
||||
* @this {Memory}
|
||||
* @return {Array|Int32Array|null}
|
||||
*/
|
||||
save: function() {
|
||||
var adw, i;
|
||||
if (this.controller) {
|
||||
adw = null;
|
||||
}
|
||||
else if (FATARRAYS) {
|
||||
adw = new Array(this.cb >> 2);
|
||||
var off = 0;
|
||||
for (i = 0; i < adw.length; i++) {
|
||||
adw[i] = this.ab[off] | (this.ab[off + 1] << 8) | (this.ab[off + 2] << 16) | (this.ab[off + 3] << 24);
|
||||
off += 4;
|
||||
}
|
||||
}
|
||||
else if (TYPEDARRAYS) {
|
||||
/*
|
||||
* It might be tempting to just return a copy of Int32Array(this.buffer, 0, this.cb >> 2),
|
||||
* but we can't be sure of the "endianness" of an Int32Array -- which would be OK if the array
|
||||
* was always saved/restored on the same machine, but there's no guarantee of that, either.
|
||||
* So we use getInt32() and require little-endian values.
|
||||
*
|
||||
* Moreover, an Int32Array isn't treated by JSON.stringify() and JSON.parse() exactly like
|
||||
* a normal array; it's serialized as an Object rather than an Array, so it lacks a "length"
|
||||
* property and causes problems for State.store() and State.parse().
|
||||
*/
|
||||
adw = new Array(this.cb >> 2);
|
||||
for (i = 0; i < adw.length; i++) {
|
||||
adw[i] = this.dv.getInt32(i << 2, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
adw = this.adw;
|
||||
}
|
||||
return adw;
|
||||
},
|
||||
/**
|
||||
* restore(adw)
|
||||
*
|
||||
* This restores the contents of a Memory block from an array of 32-bit values;
|
||||
* used by Bus.restoreMemory(), which is called by X86CPU.restore(), after all other
|
||||
* components have been restored and thus all Memory blocks have been allocated
|
||||
* by their respective components.
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {Array|null} adw
|
||||
* @return {boolean} true if successful, false if block size mismatch
|
||||
*/
|
||||
restore: function(adw) {
|
||||
if (this.controller) {
|
||||
return (adw == null);
|
||||
}
|
||||
/*
|
||||
* At this point, it's a consistency error for adw to be null; it's happened once already,
|
||||
* when there was a restore bug in the Video component that added the frame buffer at the video
|
||||
* card's "spec'ed" address instead of the programmed address, hence there were no controller-owned
|
||||
* memory blocks installed at the programmed address, and so we arrived here at a block with no
|
||||
* controller AND no data.
|
||||
*/
|
||||
Component.assert(adw != null);
|
||||
if (adw && this.cb == adw.length << 2) {
|
||||
var i;
|
||||
if (FATARRAYS) {
|
||||
var off = 0;
|
||||
for (i = 0; i < adw.length; i++) {
|
||||
this.ab[off] = adw[i] & 0xff;
|
||||
this.ab[off + 1] = (adw[i] >> 8) & 0xff;
|
||||
this.ab[off + 2] = (adw[i] >> 16) & 0xff;
|
||||
this.ab[off + 3] = (adw[i] >> 24) & 0xff;
|
||||
off += 4;
|
||||
}
|
||||
} else if (TYPEDARRAYS) {
|
||||
for (i = 0; i < adw.length; i++) {
|
||||
this.dv.setInt32(i << 2, adw[i], true);
|
||||
}
|
||||
} else {
|
||||
this.adw = adw;
|
||||
}
|
||||
this.fDirty = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* setAccess(afn)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {Array.<function()>} [afn]
|
||||
* @param {boolean} [fDirect]
|
||||
*/
|
||||
setAccess: function(afn, fDirect) {
|
||||
if (!afn) afn = [];
|
||||
if (fDirect === undefined) fDirect = true; // TODO: Verify that this is desired default behavior
|
||||
this.setReadAccess(afn, fDirect);
|
||||
this.setWriteAccess(afn, fDirect);
|
||||
},
|
||||
/**
|
||||
* setReadAccess(afn, fDirect)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {Array.<function()>} afn
|
||||
* @param {boolean} [fDirect]
|
||||
*/
|
||||
setReadAccess: function(afn, fDirect) {
|
||||
this.readByte = afn[0]? afn[0] : this.readNone;
|
||||
this.readWord = afn[1]? afn[1] : this.readNone;
|
||||
if (fDirect) {
|
||||
this.readByteDirect = afn[0]? afn[0] : this.readNone;
|
||||
this.readWordDirect = afn[1]? afn[1] : this.readNone;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* setWriteAccess(afn, fDirect)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {Array.<function()>} afn
|
||||
* @param {boolean} [fDirect]
|
||||
*/
|
||||
setWriteAccess: function(afn, fDirect) {
|
||||
this.writeByte = afn[2] && !this.fReadOnly? afn[2] : this.writeNone;
|
||||
this.writeWord = afn[3] && !this.fReadOnly? afn[3] : this.writeNone;
|
||||
if (fDirect) {
|
||||
this.writeByteDirect = afn[2]? afn[2] : this.writeNone;
|
||||
this.writeWordDirect = afn[3]? afn[3] : this.writeNone;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* resetReadAccess()
|
||||
*
|
||||
* @this {Memory}
|
||||
*/
|
||||
resetReadAccess: function() {
|
||||
this.readByte = this.readByteDirect;
|
||||
this.readWord = this.readWordDirect;
|
||||
},
|
||||
/**
|
||||
* resetWriteAccess()
|
||||
*
|
||||
* @this {Memory}
|
||||
*/
|
||||
resetWriteAccess: function() {
|
||||
this.writeByte = this.fReadOnly? this.writeNone : this.writeByteDirect;
|
||||
this.writeWord = this.fReadOnly? this.writeNone : this.writeWordDirect;
|
||||
},
|
||||
/**
|
||||
* setDebugInfo(cpu, dbg, addr, size)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {X86CPU|Component} cpu
|
||||
* @param {Debugger|Component} dbg
|
||||
* @param {number} addr of block
|
||||
* @param {number} size of block
|
||||
*/
|
||||
setDebugInfo: function(cpu, dbg, addr, size) {
|
||||
if (DEBUGGER) {
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.addr = addr;
|
||||
this.cReadBreakpoints = this.cWriteBreakpoints = 0;
|
||||
if (this.dbg) this.dbg.redoBreakpoints(addr, size);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* addBreakpoint(off, fWrite)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {boolean} fWrite
|
||||
*/
|
||||
addBreakpoint: function(off, fWrite) {
|
||||
if (DEBUGGER) {
|
||||
if (!fWrite) {
|
||||
if (this.cReadBreakpoints++ === 0) {
|
||||
this.setReadAccess(Memory.afnChecked);
|
||||
}
|
||||
if (DEBUG) this.dbg.println("read breakpoint added to memory block " + str.toHex(this.addr));
|
||||
}
|
||||
else {
|
||||
if (this.cWriteBreakpoints++ === 0) {
|
||||
this.setWriteAccess(Memory.afnChecked);
|
||||
}
|
||||
if (DEBUG) this.dbg.println("write breakpoint added to memory block " + str.toHex(this.addr));
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* removeBreakpoint(off, fWrite)
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off
|
||||
* @param {boolean} fWrite
|
||||
*/
|
||||
removeBreakpoint: function(off, fWrite) {
|
||||
if (DEBUGGER) {
|
||||
if (!fWrite) {
|
||||
if (--this.cReadBreakpoints === 0) {
|
||||
this.resetReadAccess();
|
||||
if (DEBUG) this.dbg.println("all read breakpoints removed from memory block " + str.toHex(this.addr));
|
||||
}
|
||||
Component.assert(this.cReadBreakpoints >= 0);
|
||||
}
|
||||
else {
|
||||
if (--this.cWriteBreakpoints === 0) {
|
||||
this.resetWriteAccess();
|
||||
if (DEBUG) this.dbg.println("all write breakpoints removed from memory block " + str.toHex(this.addr));
|
||||
}
|
||||
Component.assert(this.cWriteBreakpoints >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Memory.afnMemory = [Memory.prototype.readByteMemory, Memory.prototype.readWordMemory, Memory.prototype.writeByteMemory, Memory.prototype.writeWordMemory];
|
||||
Memory.afnChecked = [Memory.prototype.readByteChecked, Memory.prototype.readWordChecked, Memory.prototype.writeByteChecked, Memory.prototype.writeWordChecked];
|
||||
|
||||
if (TYPEDARRAYS) {
|
||||
Memory.afnTypedArray = [Memory.prototype.readByteTypedArray, Memory.prototype.readWordTypedArray, Memory.prototype.writeByteTypedArray, Memory.prototype.writeWordTypedArray];
|
||||
}
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.Memory = Memory;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = Memory;
|
||||
611
modules/pcjs/lib/mouse.js
Normal file
611
modules/pcjs/lib/mouse.js
Normal file
|
|
@ -0,0 +1,611 @@
|
|||
/**
|
||||
* @fileoverview Implements the PCjs Mouse component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Jul-01
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var SerialPort = require("./serial");
|
||||
var State = require("./state");
|
||||
var Debugger = require("./debugger");
|
||||
}
|
||||
|
||||
/**
|
||||
* Mouse(parmsMouse)
|
||||
*
|
||||
* The Mouse component has the following component-specific (parmsMouse) properties:
|
||||
*
|
||||
* serial: the ID of the corresponding serial component
|
||||
*
|
||||
* Since the first version of this component supports ONLY emulation of the original Microsoft
|
||||
* serial mouse, a valid serial component ID is required. It's possible that future versions
|
||||
* of this component may support other types of simulated hardware (eg, the Microsoft InPort
|
||||
* bus mouse adapter), or a virtual driver interface that would eliminate the need for any
|
||||
* intermediate hardware simulation (at the expense of writing an intermediate software layer or
|
||||
* virtual driver for each supported operating system). However, those possibilities are extremely
|
||||
* unlikely in the near term.
|
||||
*
|
||||
* If the 'serial' property is specified, then communication will be established with the
|
||||
* SerialPort component, requesting access to the corresponding serial component ID. If the
|
||||
* SerialPort component is not installed and/or the specified serial component ID is not present,
|
||||
* a configuration error will be reported.
|
||||
*
|
||||
* TODO: Just out of curiosity, verify that the Microsoft Bus Mouse used ports 0x23D and 0x23F,
|
||||
* because I saw Windows v1.01 probing those ports immediately prior to probing COM2 (and then COM1)
|
||||
* for a serial mouse.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @param {Object} parmsMouse
|
||||
*/
|
||||
function Mouse(parmsMouse) {
|
||||
|
||||
Component.call(this, "Mouse", parmsMouse, Mouse);
|
||||
|
||||
this.idAdapter = parmsMouse['serial'];
|
||||
if (this.idAdapter) {
|
||||
this.sAdapterType = "SerialPort";
|
||||
}
|
||||
this.fActive = false;
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
/*
|
||||
* From http://paulbourke.net/dataformats/serialmouse:
|
||||
*
|
||||
* The old MicroSoft serial mouse, while no longer in general use, can be employed to provide a low cost input device,
|
||||
* for example, coupling the internal mechanism to other moving objects. The serial protocol for the mouse is:
|
||||
*
|
||||
* 1200 baud, 7 bit, 1 stop bit, no parity.
|
||||
*
|
||||
* The pinout of the connector follows the standard serial interface, as shown below:
|
||||
*
|
||||
* Pin Abbr Description
|
||||
* 1 DCD Data Carrier Detect
|
||||
* 2 RD Receive Data [serial data from mouse to host]
|
||||
* 3 TD Transmit Data
|
||||
* 4 DTR Data Terminal Ready [used to provide positive voltage to mouse, plus reset/detection]
|
||||
* 5 SG Signal Ground
|
||||
* 6 DSR Data Set Ready
|
||||
* 7 RTS Request To Send [used to provide positive voltage to mouse]
|
||||
* 8 CTS Clear To Send
|
||||
* 9 RI Ring
|
||||
*
|
||||
* Every time the mouse changes state (moved or button pressed) a three byte "packet" is sent to the serial interface.
|
||||
* For reasons known only to the engineers, the data is arranged as follows, most notably the two high order bits for the
|
||||
* x and y coordinates share the first byte with the button status.
|
||||
*
|
||||
* D6 D5 D4 D3 D2 D1 D0
|
||||
* 1st byte 1 LB RB Y7 Y6 X7 X6
|
||||
* 2nd byte 0 X5 X4 X3 X2 X1 X0
|
||||
* 3rd byte 0 Y5 Y4 Y3 Y2 Y1 Y0
|
||||
*
|
||||
* where:
|
||||
*
|
||||
* LB is the state of the left button, 1 = pressed, 0 = released.
|
||||
* RB is the state of the right button, 1 = pressed, 0 = released
|
||||
* X0-7 is movement of the mouse in the X direction since the last packet. Positive movement is toward the right.
|
||||
* Y0-7 is movement of the mouse in the Y direction since the last packet. Positive movement is back, toward the user.
|
||||
*
|
||||
* From http://www.kryslix.com/nsfaq/Q.12.html:
|
||||
*
|
||||
* The Microsoft serial mouse is the most popular 2-button mouse. It is supported by all major operating systems.
|
||||
* The maximum tracking rate for a Microsoft mouse is 40 reports/second * 127 counts per report, in other words, 5080 counts
|
||||
* per second. The most common range for mice is is 100 to 400 CPI (counts per inch) but can be up to 1000 CPI. A 100 CPI mouse
|
||||
* can discriminate motion up to 50.8 inches/second while a 400 CPI mouse can only discriminate motion up to 12.7 inches/second.
|
||||
*
|
||||
* 9-pin 25-pin Line Comments
|
||||
* shell 1 GND
|
||||
* 3 2 TD Serial data from host to mouse (only for power)
|
||||
* 2 3 RD Serial data from mouse to host
|
||||
* 7 4 RTS Positive voltage to mouse
|
||||
* 8 5 CTS
|
||||
* 6 6 DSR
|
||||
* 5 7 SGND
|
||||
* 4 20 DTR Positive voltage to mouse and reset/detection
|
||||
*
|
||||
* To function correctly, both the RTS and DTR lines must be positive. DTR/DSR and RTS/CTS must NOT be shorted.
|
||||
* RTS may be toggled negative for at least 100ms to reset the mouse. (After a cold boot, the RTS line is usually negative.
|
||||
* This provides an automatic toggle when RTS is brought positive). When DTR is toggled the mouse should send a single byte
|
||||
* (0x4D, ASCII 'M').
|
||||
*
|
||||
* Serial data parameters: 1200bps, 7 data bits, 1 stop bit
|
||||
*
|
||||
* Data is sent in 3 byte packets for each event (a button is pressed or released, or the mouse moves):
|
||||
*
|
||||
* D7 D6 D5 D4 D3 D2 D1 D0
|
||||
* Byte 1 X 1 LB RB Y7 Y6 X7 X6
|
||||
* Byte 2 X 0 X5 X4 X3 X2 X1 X0
|
||||
* Byte 3 X 0 Y5 Y4 Y3 Y2 Y1 Y0
|
||||
*
|
||||
* LB is the state of the left button (1 means down).
|
||||
* RB is the state of the right button (1 means down).
|
||||
* X7-X0 movement in X direction since last packet (signed byte).
|
||||
* Y7-Y0 movement in Y direction since last packet (signed byte).
|
||||
* The high order bit of each byte (D7) is ignored. Bit D6 indicates the start of an event, which allows the software to
|
||||
* synchronize with the mouse.
|
||||
*/
|
||||
|
||||
Component.subclass(Component, Mouse);
|
||||
|
||||
Mouse.ID_SERIAL = 0x4D;
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {Computer} cmp
|
||||
* @param {Bus} bus
|
||||
* @param {X86CPU} cpu
|
||||
* @param {Debugger} dbg
|
||||
*/
|
||||
Mouse.prototype.initBus = function(cmp, bus, cpu, dbg) {
|
||||
this.cmp = cmp;
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
};
|
||||
|
||||
/**
|
||||
* isActive()
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @return {boolean} true if active, false if not
|
||||
*/
|
||||
Mouse.prototype.isActive = function() {
|
||||
return this.fActive && (this.cpu? this.cpu.isRunning() : false);
|
||||
};
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
Mouse.prototype.powerUp = function(data, fRepower) {
|
||||
if (!fRepower) {
|
||||
if (!data || !this.restore) {
|
||||
this.reset();
|
||||
} else {
|
||||
if (!this.restore(data)) return false;
|
||||
}
|
||||
if (this.sAdapterType && !this.componentAdapter) {
|
||||
var componentAdapter = null;
|
||||
while ((componentAdapter = this.cmp.getComponentByType(this.sAdapterType, componentAdapter))) {
|
||||
if (componentAdapter.attachMouse) {
|
||||
this.componentAdapter = componentAdapter.attachMouse(this.idAdapter, this);
|
||||
if (this.componentAdapter) {
|
||||
/*
|
||||
* It's possible that the SerialPort we've just attached to might want to bring us "up to speed"
|
||||
* on the adapter's state, which is why I envisioned a subsequent syncMouse() call. And you would want
|
||||
* to do that as a separate call, not as part of attachMouse(), because componentAdapter isn't
|
||||
* set until attachMouse() returns.
|
||||
*
|
||||
* However, syncMouse() seems unnecessary, given that SerialPort initializes its MCR to an "inactive"
|
||||
* state, and even when restoring a previous state, if we've done our job properly, both SerialPort and Mouse
|
||||
* should be restored in sync, making any explicit attempt at sync'ing unnecessary (or so I hope).
|
||||
*/
|
||||
// this.componentAdapter.syncMouse();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.componentAdapter) {
|
||||
var componentScreen = this.cmp.getComponentByType("Video");
|
||||
if (componentScreen) this.canvasScreen = componentScreen.getCanvas();
|
||||
} else {
|
||||
Component.warning(this.id + ": " + this.sAdapterType + " " + this.idAdapter + " unavailable");
|
||||
}
|
||||
}
|
||||
if (this.fActive) {
|
||||
this.captureMouse(this.canvasScreen);
|
||||
} else {
|
||||
this.releaseMouse(this.canvasScreen);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* powerDown(fSave)
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {boolean} fSave
|
||||
* @return {Object|boolean}
|
||||
*/
|
||||
Mouse.prototype.powerDown = function(fSave) {
|
||||
return fSave && this.save? this.save() : true;
|
||||
};
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* @this {Mouse}
|
||||
*/
|
||||
Mouse.prototype.reset = function() {
|
||||
this.initState();
|
||||
};
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* This implements save support for the Mouse component.
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @return {Object}
|
||||
*/
|
||||
Mouse.prototype.save = function() {
|
||||
var state = new State(this);
|
||||
state.set(0, this.saveState());
|
||||
return state.data();
|
||||
};
|
||||
|
||||
/**
|
||||
* restore(data)
|
||||
*
|
||||
* This implements restore support for the Mouse component.
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {Object} data
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
Mouse.prototype.restore = function(data) {
|
||||
return this.initState(data[0]);
|
||||
};
|
||||
|
||||
/**
|
||||
* initState(data)
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {Array} [data]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
Mouse.prototype.initState = function(data) {
|
||||
var i = 0;
|
||||
if (data === undefined) data = [false, -1, -1, 0, 0, false, false, 0];
|
||||
this.fActive = data[i++];
|
||||
this.xMouse = data[i++];
|
||||
this.yMouse = data[i++];
|
||||
this.xDelta = data[i++];
|
||||
this.yDelta = data[i++];
|
||||
this.fButton1 = data[i++]; // FYI, we consider button1 to be the LEFT button
|
||||
this.fButton2 = data[i++]; // FYI, we consider button2 to be the RIGHT button
|
||||
this.bMCR = data[i];
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* saveState()
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @return {Array}
|
||||
*/
|
||||
Mouse.prototype.saveState = function() {
|
||||
var i = 0;
|
||||
var data = [];
|
||||
data[i++] = this.fActive;
|
||||
data[i++] = this.xMouse;
|
||||
data[i++] = this.yMouse;
|
||||
data[i++] = this.xDelta;
|
||||
data[i++] = this.yDelta;
|
||||
data[i++] = this.fButton1;
|
||||
data[i++] = this.fButton2;
|
||||
data[i] = this.bMCR;
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* captureMouse(control)
|
||||
*
|
||||
* NOTE: addEventListener() wasn't supported in IE until IE9, but that's OK, because IE9 is the
|
||||
* oldest IE we support anyway (since older versions of IE lacked complete HTML5/canvas support).
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {Object} control from the HTML DOM (eg, the canvas for the simulated screen)
|
||||
*/
|
||||
Mouse.prototype.captureMouse = function(control) {
|
||||
if (control) {
|
||||
var mouse = this;
|
||||
if (!this.fCaptured) {
|
||||
control.addEventListener(
|
||||
'mousemove',
|
||||
function onMouseMove(event) {
|
||||
mouse.moveMouse(event);
|
||||
},
|
||||
false // we'll specify false for the 'useCapture' parameter for now...
|
||||
);
|
||||
control.addEventListener(
|
||||
'mousedown',
|
||||
function onMouseDown(event) {
|
||||
mouse.clickMouse(event.button, true);
|
||||
},
|
||||
false // we'll specify false for the 'useCapture' parameter for now...
|
||||
);
|
||||
control.addEventListener(
|
||||
'mouseup',
|
||||
function onMouseUp(event) {
|
||||
mouse.clickMouse(event.button, false);
|
||||
},
|
||||
false // we'll specify false for the 'useCapture' parameter for now...
|
||||
);
|
||||
this.fCaptured = true;
|
||||
}
|
||||
/*
|
||||
* None of these tricks seemed to work for IE10, so I'm giving up hiding the browser's mouse pointer in IE for now.
|
||||
*
|
||||
* control['style']['cursor'] = "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjbQg61aAAAADUlEQVQYV2P4//8/IwAI/QL/+TZZdwAAAABJRU5ErkJggg=='), url('/versions/images/current/blank.cur'), none";
|
||||
*
|
||||
* Setting the cursor style to "none" may not be a standard, but it works in Safari, Firefox and Chrome, so that's pretty
|
||||
* good for a non-standard!
|
||||
*
|
||||
* TODO: The reference to '/versions/images/current/blank.cur' is also problematic for anyone who might want
|
||||
* to run this app from a different server, so think about that as well.
|
||||
*/
|
||||
control['style']['cursor'] = "none";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* releaseMouse(control)
|
||||
*
|
||||
* TODO: Use removeEventListener() if fCaptured, to clean up our handlers; since I'm currently using
|
||||
* anonymous functions, and since I'm not seeing any compelling reason to remove the handlers once they've
|
||||
* been established, it's less code to leave them in place.
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {Object} control from the HTML DOM
|
||||
*/
|
||||
Mouse.prototype.releaseMouse = function(control) {
|
||||
if (control) {
|
||||
control['style']['cursor'] = "auto";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* moveMouse(event)
|
||||
*
|
||||
* MouseEvent objects contain, among other things, the following properties:
|
||||
*
|
||||
* clientX
|
||||
* clientY
|
||||
*
|
||||
* I've selected the above properties because they're widely supported, not because I need
|
||||
* client-area coordinates. In fact, layerX and layerY are probably closer to what I really want,
|
||||
* but I don't think they're available in all browsers. screenX and screenY would work as well.
|
||||
*
|
||||
* Anyway, all I care about are deltas. For now.
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {Object} event object from a 'mousemove' event (specifically, a MouseEvent object)
|
||||
*/
|
||||
Mouse.prototype.moveMouse = function(event) {
|
||||
if (this.isActive()) {
|
||||
if (this.xMouse < 0 || this.yMouse < 0) {
|
||||
this.xMouse = event.clientX;
|
||||
this.yMouse = event.clientY;
|
||||
}
|
||||
this.xDelta = event.clientX - this.xMouse;
|
||||
this.yDelta = event.clientY - this.yMouse;
|
||||
if (this.xDelta || this.yDelta) {
|
||||
this.sendPacket(null, event.clientX, event.clientY);
|
||||
}
|
||||
this.xMouse = event.clientX;
|
||||
this.yMouse = event.clientY;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* clickMouse(iButton, fDown)
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {number} iButton is 0 for fButton1 (the LEFT button), 2 for fButton2 (the RIGHT button)
|
||||
* @param {boolean} fDown
|
||||
*/
|
||||
Mouse.prototype.clickMouse = function(iButton, fDown) {
|
||||
if (this.isActive()) {
|
||||
var sDiag;
|
||||
switch (iButton) {
|
||||
case 0:
|
||||
if (this.fButton1 != fDown) {
|
||||
this.fButton1 = fDown;
|
||||
sDiag = DEBUGGER? ("mouse button1 " + (fDown? "dn" : "up")) : null;
|
||||
this.sendPacket(sDiag);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (this.fButton2 != fDown) {
|
||||
this.fButton2 = fDown;
|
||||
sDiag = DEBUGGER? ("mouse button2 " + (fDown? "dn" : "up")) : null;
|
||||
this.sendPacket(sDiag);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* sendPacket(sDiag, xDiag, yDiag)
|
||||
*
|
||||
* If we're called, something changed.
|
||||
*
|
||||
* Let's review the 3-byte packet format:
|
||||
*
|
||||
* D7 D6 D5 D4 D3 D2 D1 D0
|
||||
* Byte 1 X 1 LB RB Y7 Y6 X7 X6
|
||||
* Byte 2 X 0 X5 X4 X3 X2 X1 X0
|
||||
* Byte 3 X 0 Y5 Y4 Y3 Y2 Y1 Y0
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {string|null} [sDiag] diagnostic message
|
||||
* @param {number} [xDiag] original x-coordinate (optional; for diagnostic use only)
|
||||
* @param {number} [yDiag] original y-coordinate (optional; for diagnostic use only)
|
||||
*/
|
||||
Mouse.prototype.sendPacket = function(sDiag, xDiag, yDiag) {
|
||||
var b1 = 0x40 | (this.fButton1? 0x20 : 0) | (this.fButton2? 0x10 : 0) | ((this.yDelta & 0xC0) >> 4) | ((this.xDelta & 0xC0) >> 6);
|
||||
var b2 = this.xDelta & 0x3F;
|
||||
var b3 = this.yDelta & 0x3F;
|
||||
this.messageDebugger((sDiag? (sDiag + ": ") : "") + (yDiag !== undefined? ("mouse (" + xDiag + "," + yDiag + "): ") : "") + "serial packet [" + str.toHexByte(b1) + "," + str.toHexByte(b2) + "," + str.toHexByte(b3) + "]");
|
||||
this.componentAdapter.sendRBR([b1, b2, b3]);
|
||||
this.xDelta = this.yDelta = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* notifyMCR(bMCR)
|
||||
*
|
||||
* The SerialPort notifies us whenever SerialPort.MCR.DTR or SerialPort.MCR.RTS changes.
|
||||
*
|
||||
* During normal serial mouse operation, both RTS and DTR must be "positive".
|
||||
*
|
||||
* Setting RTS "negative" for 100ms resets the mouse. Toggling DTR requests an identification byte (ID_SERIAL).
|
||||
*
|
||||
* NOTES: The above 3rd-party information notwithstanding, I've observed that Windows v1.01 initially writes 0x01
|
||||
* to the MCR (DTR on, RTS off), spins in a loop that reads the RBR (probably to avoid a bogus identification byte
|
||||
* sitting in the RBR), and then writes 0x0B to the MCR (DTR on, RTS on). This last step is consistent with making
|
||||
* the mouse "active", but it is NOT consistent with "toggling DTR", so I conclude that a reset is ALSO sufficient
|
||||
* for sending the identification byte. Right or wrong, this gets the ball rolling for Windows v1.01.
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {number} bMCR
|
||||
*/
|
||||
Mouse.prototype.notifyMCR = function(bMCR) {
|
||||
var fActive = ((bMCR & (SerialPort.MCR.DTR | SerialPort.MCR.RTS)) == (SerialPort.MCR.DTR | SerialPort.MCR.RTS));
|
||||
if (fActive) {
|
||||
if (!this.fActive) {
|
||||
var fIdentify = false;
|
||||
if (!(this.bMCR & SerialPort.MCR.RTS)) {
|
||||
this.reset();
|
||||
this.messageDebugger("serial mouse reset");
|
||||
fIdentify = true;
|
||||
}
|
||||
if (!(this.bMCR & SerialPort.MCR.DTR)) {
|
||||
this.messageDebugger("serial mouse ID requested");
|
||||
fIdentify = true;
|
||||
}
|
||||
if (fIdentify) {
|
||||
/*
|
||||
* HEADS UP: Everything I'd read about the (original) Microsoft Serial Mouse "reset" protocol says
|
||||
* that the device sends a single byte (0x4D aka 'M'). It's not surprising to think that newer mice
|
||||
* might send additional bytes, but you would think that newer mouse drivers (eg, MOUSE.COM v8.20)
|
||||
* would always be able to deal with mice that sent only one byte.
|
||||
*
|
||||
* You would be wrong. On an INT 0x33 reset, the v8.20 driver looks for an 'M', then it waits for
|
||||
* another byte (0x42 aka 'B'). If it doesn't receive a 'B', it will accept another 'M'. But if it
|
||||
* receives something else (or nothing at all), it will spend a long time waiting for it, and then
|
||||
* return an error.
|
||||
*
|
||||
* It's entirely possible that I've done something wrong and inadvertently "tricked" MOUSE.COM into
|
||||
* using the wrong detection logic. But given the other problems I've seen in MOUSE.COM v8.20, including
|
||||
* its failure to properly terminate-and-stay-resident when its initial INT 0x33 reset returns an error,
|
||||
* I'm not in the mood to give it the benefit of the doubt.
|
||||
*
|
||||
* So, anyway, I solve the terminate-and-stay-resident bug in MOUSE.COM v8.20 by feeding it *two* ID_SERIAL
|
||||
* bytes on a reset. This doesn't seem to adversely affect serial mouse emulation for Windows 1.01, so
|
||||
* I'm calling this good enough for now.
|
||||
*/
|
||||
this.componentAdapter.sendRBR([Mouse.ID_SERIAL, Mouse.ID_SERIAL]);
|
||||
this.messageDebugger("serial mouse ID sent");
|
||||
}
|
||||
this.captureMouse(this.canvasScreen);
|
||||
this.fActive = fActive;
|
||||
}
|
||||
} else {
|
||||
if (this.fActive) {
|
||||
/*
|
||||
* Although this would seem nice (ie, for the Windows v1.01 mouse driver to turn RTS off when its mouse
|
||||
* driver shuts down and Windows exits, since it DID turn RTS on), that doesn't appear to actually happen.
|
||||
* At the very least, Windows will have (re)masked the serial port's IRQ, so what does it matter? Not much,
|
||||
* I just would have preferred that fActive properly reflect whether we should continue dispatching mouse
|
||||
* events, displaying MESSAGE_MOUSE messages, etc.
|
||||
*
|
||||
* We could ask the ChipSet component to notify the SerialPort component whenever its IRQ is masked/unmasked,
|
||||
* and then have the SerialPort pass that notification on to us, but I'm assuming that in the real world,
|
||||
* a mouse device that's still powered may still send event data to the serial port, and if there was software
|
||||
* polling the serial port, it might expect to see that data. Unlikely, but not impossible.
|
||||
*/
|
||||
this.messageDebugger("serial mouse inactive");
|
||||
this.releaseMouse(this.canvasScreen);
|
||||
this.fActive = fActive;
|
||||
}
|
||||
}
|
||||
this.bMCR = bMCR;
|
||||
};
|
||||
|
||||
/**
|
||||
* messageDebugger(sMessage)
|
||||
*
|
||||
* This is a combination of the Debugger's messageEnabled(MESSAGE_MOUSE) and message() functions, for convenience.
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {string} sMessage is any caller-defined message string
|
||||
*/
|
||||
Mouse.prototype.messageDebugger = function(sMessage) {
|
||||
if (DEBUGGER && this.dbg) {
|
||||
if (this.dbg.messageEnabled(Debugger.MESSAGE.MOUSE)) {
|
||||
this.dbg.message(sMessage + " @" + str.toHexAddr(this.cpu.regIP, this.cpu.segCS.sel));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mouse.init()
|
||||
*
|
||||
* This function operates on every element (e) of class "mouse", and initializes
|
||||
* all the necessary HTML to construct the Mouse module(s) as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "mouse" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the Mouse constructor
|
||||
* expects.
|
||||
*/
|
||||
Mouse.init = function() {
|
||||
var aeMouse = Component.getElementsByClass(window.document, PCJSCLASS, "mouse");
|
||||
for (var iMouse = 0; iMouse < aeMouse.length; iMouse++) {
|
||||
var eMouse = aeMouse[iMouse];
|
||||
var parmsMouse = Component.getComponentParms(eMouse);
|
||||
var mouse = new Mouse(parmsMouse);
|
||||
Component.bindComponentControls(mouse, eMouse, PCJSCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize every Mouse module on the page.
|
||||
*/
|
||||
web.onInit(Mouse.init);
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.Mouse = Mouse;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = Mouse;
|
||||
53
modules/pcjs/lib/nodebugger.js
Normal file
53
modules/pcjs/lib/nodebugger.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* @fileoverview Compile-time definitions for Debugger-less configurations.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-May-08
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
|
||||
* In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate. When it's *false*,
|
||||
* nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
|
||||
* "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
|
||||
* debugger.js from the compilation process altogether.
|
||||
*
|
||||
* However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
|
||||
* I would still like to skip loading debugger.js altogether. To do that, we must arrange for an additional file,
|
||||
* nodebugger.js, to be loaded as early as possible, which must explicitly UPDATE the value of DEBUGGER to *false*.
|
||||
*/
|
||||
DEBUGGER = false;
|
||||
|
||||
/*
|
||||
* We still need some Debugger "stub" objects, so that attempts to reference Debugger constants won't trigger exceptions.
|
||||
*/
|
||||
var Debugger = {
|
||||
MESSAGE: {}
|
||||
};
|
||||
169
modules/pcjs/lib/panel.js
Normal file
169
modules/pcjs/lib/panel.js
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
/**
|
||||
* @fileoverview Implements the PCjs Panel component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Jun-19
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* Panel(parmsPanel)
|
||||
*
|
||||
* The Panel component has no required (parmsPanel) properties.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @param {Object} parmsPanel
|
||||
*/
|
||||
function Panel(parmsPanel) {
|
||||
Component.call(this, "Panel", parmsPanel, Panel);
|
||||
}
|
||||
|
||||
Component.subclass(Component, Panel);
|
||||
|
||||
/**
|
||||
* setBinding(sHTMLClass, sHTMLType, sBinding, control)
|
||||
*
|
||||
* 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 {Panel}
|
||||
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @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)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
Panel.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
|
||||
{
|
||||
if (this.cmp && this.cmp.setBinding(sHTMLClass, sHTMLType, sBinding, control)) return true;
|
||||
if (this.cpu && this.cpu.setBinding(sHTMLClass, sHTMLType, sBinding, control)) return true;
|
||||
if (this.kbd && this.kbd.setBinding(sHTMLClass, sHTMLType, sBinding, control)) return true;
|
||||
if (DEBUGGER && this.dbg && this.dbg.setBinding(sHTMLClass, sHTMLType, sBinding, control)) return true;
|
||||
/*
|
||||
* TODO: Determine how to declare this superclass method in order to avoid a type warning
|
||||
*/
|
||||
return Component.prototype.setBinding.call(this, sHTMLClass, sHTMLType, sBinding, control);
|
||||
};
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {Panel}
|
||||
* @param {Computer} cmp
|
||||
* @param {Bus} bus
|
||||
* @param {X86CPU} cpu
|
||||
* @param {Debugger} dbg
|
||||
*/
|
||||
Panel.prototype.initBus = function(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.cmp = cmp;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.kbd = cmp.getComponentByType("Keyboard");
|
||||
};
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* @this {Panel}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
Panel.prototype.powerUp = function(data, fRepower)
|
||||
{
|
||||
if (!fRepower) {
|
||||
Panel.init();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* powerDown(fSave)
|
||||
*
|
||||
* @this {Panel}
|
||||
* @param {boolean} fSave
|
||||
* @return {Object|boolean}
|
||||
*/
|
||||
Panel.prototype.powerDown = function(fSave)
|
||||
{
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Panel.init()
|
||||
*
|
||||
* This function operates on every element (e) of class "panel", and initializes
|
||||
* all the necessary HTML to construct the Panel module(s) as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "panel" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the Panel constructor
|
||||
* expects.
|
||||
*
|
||||
* 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 Computer 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, CPU, Keyboard, and Debugger) that we have some controls they
|
||||
* might want to use.
|
||||
*/
|
||||
Panel.init = function()
|
||||
{
|
||||
var fReady = false;
|
||||
var aePanels = Component.getElementsByClass(window.document, PCJSCLASS, "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 Panel(parmsPanel);
|
||||
}
|
||||
Component.bindComponentControls(panel, ePanel, PCJSCLASS);
|
||||
if (fReady) panel.setReady();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize every Panel module on the page.
|
||||
*/
|
||||
web.onInit(Panel.init);
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.Panel = Panel;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = Panel;
|
||||
214
modules/pcjs/lib/ram.js
Normal file
214
modules/pcjs/lib/ram.js
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
/**
|
||||
* @fileoverview Implements the PCjs RAM component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Jun-15
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var ROM = require("./rom");
|
||||
}
|
||||
|
||||
/**
|
||||
* RAM(parmsRAM)
|
||||
*
|
||||
* The RAM component expects the following (parmsRAM) properties:
|
||||
*
|
||||
* addr: starting physical address of RAM
|
||||
* size: amount of RAM, in bytes (optional)
|
||||
*
|
||||
* NOTE: We make a note of the specified size, but no memory is initially allocated
|
||||
* for the RAM until the Computer component calls setPower().
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @param {Object} parmsRAM
|
||||
*/
|
||||
function RAM(parmsRAM) {
|
||||
|
||||
Component.call(this, "RAM", parmsRAM, RAM);
|
||||
|
||||
this.addrRAM = parmsRAM['addr'];
|
||||
this.sizeRAM = parmsRAM['size'];
|
||||
this.fTestRAM = parmsRAM['test'];
|
||||
this.fInstalled = (!!this.sizeRAM); // 0 is the default value for 'size' when none is specified
|
||||
this.fAllocated = false;
|
||||
}
|
||||
|
||||
Component.subclass(Component, RAM);
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {RAM}
|
||||
* @param {Computer} cmp
|
||||
* @param {Bus} bus
|
||||
* @param {X86CPU} cpu
|
||||
* @param {Debugger} dbg
|
||||
*/
|
||||
RAM.prototype.initBus = function(cmp, bus, cpu, dbg) {
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.chipset = cmp.getComponentByType("ChipSet");
|
||||
this.setReady();
|
||||
};
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* @this {RAM}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
RAM.prototype.powerUp = function(data, fRepower) {
|
||||
if (!fRepower) {
|
||||
/*
|
||||
* The Computer powers up the CPU last, at which point the X86 state is restored,
|
||||
* which includes the Bus state, and since we use the Bus to allocate all our memory,
|
||||
* memory contents are already restored for us, so we don't need the usual restore
|
||||
* logic. We just need to call reset(), to allocate memory for the RAM.
|
||||
*
|
||||
if (!data || !this.restore) {
|
||||
this.reset();
|
||||
} else {
|
||||
if (!this.restore(data)) return false;
|
||||
}
|
||||
*/
|
||||
this.reset();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* powerDown(fSave)
|
||||
*
|
||||
* @this {RAM}
|
||||
* @param {boolean} fSave
|
||||
* @return {Object|boolean}
|
||||
*/
|
||||
RAM.prototype.powerDown = function(fSave) {
|
||||
/*
|
||||
* The Computer powers down the CPU first, at which point the X86 state is saved,
|
||||
* which includes the Bus state, and since we use the Bus component to allocate all
|
||||
* our memory, memory contents are already saved for us, so we don't need the usual
|
||||
* save logic.
|
||||
*
|
||||
return fSave && this.save ? this.save() : true;
|
||||
*/
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* NOTE: When we were initialized, we were given an amount of INSTALLED memory (see sizeRAM above).
|
||||
* The ChipSet component, on the other hand, tells us how much SPECIFIED memory there is -- which,
|
||||
* like a real PC, may not match the amount of installed memory (due to either user error or perhaps
|
||||
* an attempt to prevent some portion of the installed memory from being used).
|
||||
*
|
||||
* However, since we're a virtual machine, we can defer allocation of RAM until we're able to query the
|
||||
* ChipSet component, and then allocate an amount of memory that matches the SPECIFIED memory, making
|
||||
* it easy to reconfigure the machine on the fly and prevent mismatches.
|
||||
*
|
||||
* But, we do that ONLY for the RAM instance configured with an addrRAM of 0x0000, and ONLY if that RAM
|
||||
* object was not given a specific size (see fInstalled). If there are other RAM objects in the system,
|
||||
* they must necessarily specify a non-conflicting, non-zero start address, in which case their sizeRAM
|
||||
* value will never be affected by the ChipSet settings.
|
||||
*
|
||||
* @this {RAM}
|
||||
*/
|
||||
RAM.prototype.reset = function() {
|
||||
if (!this.addrRAM && !this.fInstalled && this.chipset) {
|
||||
var baseRAM = this.chipset.getSWMemorySize() * 1024;
|
||||
if (this.sizeRAM && baseRAM != this.sizeRAM) {
|
||||
this.bus.removeMemory(this.addrRAM, this.sizeRAM);
|
||||
this.fAllocated = false;
|
||||
}
|
||||
this.sizeRAM = baseRAM;
|
||||
}
|
||||
if (!this.fAllocated && this.sizeRAM) {
|
||||
if (this.bus.addMemory(this.addrRAM, this.sizeRAM)) {
|
||||
this.fAllocated = true;
|
||||
this.status(Math.floor(this.sizeRAM / 1024) + "Kb");
|
||||
/*
|
||||
* NOTE: I'm specifying MAXDEBUG for status() messages because I'm not yet sure I want these
|
||||
* messages buried in the app, since they're seen only when a Control Panel is active. Another
|
||||
* and perhaps better alternative is to add "comment" attributes to the XML configuration file
|
||||
* for these components, which the Computer component will display as it "powers up" components.
|
||||
*/
|
||||
if (MAXDEBUG && this.fInstalled) this.status("specified size overrides SW1");
|
||||
}
|
||||
}
|
||||
if (this.fAllocated) {
|
||||
if (!this.fTestRAM) {
|
||||
/*
|
||||
* HACK: Set the word at 40:72 in the ROM BIOS Data Area (RBDA) to 0x1234 to bypass the ROM BIOS
|
||||
* memory storage tests. See rom.js for all RBDA definitions.
|
||||
*/
|
||||
if (MAXDEBUG) this.status("ROM BIOS memory test has been disabled");
|
||||
this.bus.setWordDirect(ROM.BIOS.RESET_FLAG, ROM.BIOS.RESET_FLAG_WARMBOOT);
|
||||
}
|
||||
if (this.chipset) this.chipset.addCMOSMemory(this.addrRAM, this.sizeRAM);
|
||||
} else {
|
||||
Component.error("No RAM allocated");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* RAM.init()
|
||||
*
|
||||
* This function operates on every element (e) of class "ram", and initializes
|
||||
* all the necessary HTML to construct the RAM module(s) as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "ram" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the RAM constructor
|
||||
* expects.
|
||||
*/
|
||||
RAM.init = function() {
|
||||
var aeRAM = Component.getElementsByClass(window.document, PCJSCLASS, "ram");
|
||||
for (var iRAM = 0; iRAM < aeRAM.length; iRAM++) {
|
||||
var eRAM = aeRAM[iRAM];
|
||||
var parmsRAM = Component.getComponentParms(eRAM);
|
||||
var ram = new RAM(parmsRAM);
|
||||
Component.bindComponentControls(ram, eRAM, PCJSCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize all the RAM modules on the page.
|
||||
*/
|
||||
web.onInit(RAM.init);
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.RAM = RAM;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = RAM;
|
||||
358
modules/pcjs/lib/rom.js
Normal file
358
modules/pcjs/lib/rom.js
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
/**
|
||||
* @fileoverview Implements the PCjs ROM component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Jun-15
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||
var Component = require("../../shared/lib/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* ROM(parmsROM)
|
||||
*
|
||||
* The ROM component expects the following (parmsROM) properties:
|
||||
*
|
||||
* addr: physical address of ROM
|
||||
* size: amount of ROM, in bytes
|
||||
* alias: physical alias address (null if none)
|
||||
* file: name of ROM data file
|
||||
* notify: ID of a component to notify once the ROM is in place (optional)
|
||||
*
|
||||
* NOTE: The ROM data will not be copied into place until the Bus is ready (see initBus()) AND the
|
||||
* ROM data file has finished loading (see onLoadROM()).
|
||||
*
|
||||
* Also, while the size parameter may seem redundant, I consider it useful to confirm that the ROM you received
|
||||
* is the ROM you expected.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @param {Object} parmsROM
|
||||
*/
|
||||
function ROM(parmsROM)
|
||||
{
|
||||
Component.call(this, "ROM", parmsROM, ROM);
|
||||
|
||||
this.abROM = null;
|
||||
this.addrROM = parmsROM['addr'];
|
||||
this.sizeROM = parmsROM['size'];
|
||||
this.addrROMAlias = parmsROM['alias'];
|
||||
this.sFileName = parmsROM['file'];
|
||||
this.idNotify = parmsROM['notify'];
|
||||
if (this.sFileName) {
|
||||
var sFileURL = this.sFileName;
|
||||
if (DEBUG) this.log('load("' + sFileURL + '")');
|
||||
/*
|
||||
* If the selected ROM file has a ".json" extension, then we assume it's pre-converted
|
||||
* JSON-encoded ROM data, so we load it as-is; ditto for ROM files with a ".hex" extension.
|
||||
* Otherwise, we ask our server-side ROM converter to return the file in a JSON-compatible format.
|
||||
*/
|
||||
var sFileExt = str.getExtension(this.sFileName);
|
||||
if (sFileExt != DumpAPI.FORMAT.JSON && sFileExt != DumpAPI.FORMAT.HEX) {
|
||||
sFileURL = web.getHost() + DumpAPI.ENDPOINT + '?' + DumpAPI.QUERY.FILE + '=' + this.sFileName + '&' + DumpAPI.QUERY.FORMAT + '=' + DumpAPI.FORMAT.BYTES + '&' + DumpAPI.QUERY.DECIMAL + '=true';
|
||||
}
|
||||
web.loadResource(sFileURL, true, null, this, ROM.prototype.onLoadROM);
|
||||
}
|
||||
}
|
||||
|
||||
Component.subclass(Component, ROM);
|
||||
|
||||
/*
|
||||
* ROM BIOS Data Area (RBDA) definitions, in physical address form, using the same ALL-CAPS names
|
||||
* found in the original IBM PC ROM BIOS listing.
|
||||
*
|
||||
* TODO: Fill in remaining RBDA holes.
|
||||
*/
|
||||
ROM.BIOS = {};
|
||||
ROM.BIOS.RS232_BASE = 0x400; // 4 (word) I/O addresses of RS-232 adapters
|
||||
ROM.BIOS.PRINTER_BASE = 0x408; // 4 (word) I/O addresses of printer adapters
|
||||
ROM.BIOS.EQUIP_FLAG = 0x410; // installed hardware (word)
|
||||
ROM.BIOS.MFG_TEST = 0x412; // initialization flag (byte)
|
||||
ROM.BIOS.MEMORY_SIZE = 0x413; // memory size in K-bytes (word)
|
||||
ROM.BIOS.RESET_FLAG = 0x472; // set to 0x1234 if keyboard reset underway (word)
|
||||
ROM.BIOS.RESET_FLAG_WARMBOOT = 0x1234; // value stored at ROM.BIOS.RESET_FLAG to indicate a "warm boot", bypassing memory tests
|
||||
|
||||
// RESET_FLAG is the traditional end of the RBDA, as originally defined at real-mode segment 0x40.
|
||||
|
||||
/*
|
||||
* NOTE: There's currently no need for this component to have a reset() function, since
|
||||
* once the ROM data is loaded, it can't be changed, so there's nothing to reinitialize.
|
||||
*
|
||||
* OK, well, I take that back, because the Debugger, if installed, has the ability to modify
|
||||
* ROM contents, so in that case, having a reset() function that restores the original ROM data
|
||||
* might be useful; then again, it might not, depending on what you're trying to debug.
|
||||
*
|
||||
* If we do add reset(), then we'll want to change copyROM() to hang onto the original
|
||||
* ROM data; currently, we release it after copying it into the read-only memory allocated
|
||||
* via bus.addMemory().
|
||||
*/
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {ROM}
|
||||
* @param {Computer} cmp
|
||||
* @param {Bus} bus
|
||||
* @param {X86CPU} cpu
|
||||
* @param {Debugger} dbg
|
||||
*/
|
||||
ROM.prototype.initBus = function(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.copyROM();
|
||||
};
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* @this {ROM}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
ROM.prototype.powerUp = function(data, fRepower)
|
||||
{
|
||||
if (this.aSymbols) {
|
||||
if (this.dbg) {
|
||||
this.dbg.addSymbols(this.addrROM, this.sizeROM, this.aSymbols);
|
||||
}
|
||||
/*
|
||||
* Our only role in the handling of symbols is to hand them off to the Debugger at our
|
||||
* first opportunity. Now that we've done that, our copy of the symbols, if any, are toast.
|
||||
*/
|
||||
delete this.aSymbols;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* powerDown(fSave)
|
||||
*
|
||||
* Since we have nothing to do on powerDown(), and no state to return, we could simply omit
|
||||
* this function. But it doesn't hurt anything, and maybe we'll use our state to save something
|
||||
* useful down the road, like user-defined symbols (ie, symbols that the Debugger may have
|
||||
* created, above and beyond those symbols we automatically loaded, if any, along with the ROM).
|
||||
*
|
||||
* @this {ROM}
|
||||
* @param {boolean} fSave
|
||||
* @return {Object|boolean}
|
||||
*/
|
||||
ROM.prototype.powerDown = function(fSave)
|
||||
{
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* onLoadROM(sROMFile, sROMData, nErrorCode)
|
||||
*
|
||||
* @this {ROM}
|
||||
* @param {string} sROMFile
|
||||
* @param {string} sROMData
|
||||
* @param {number} nErrorCode (response from server if anything other than 200)
|
||||
*/
|
||||
ROM.prototype.onLoadROM = function(sROMFile, sROMData, nErrorCode)
|
||||
{
|
||||
if (nErrorCode) {
|
||||
this.notice("Unable to load system ROM (error " + nErrorCode + ")");
|
||||
return;
|
||||
}
|
||||
if (sROMData.charAt(0) == "[" || sROMData.charAt(0) == "{") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be here: parsing the JSON-encoded ROM data.
|
||||
*/
|
||||
var rom = eval("(" + sROMData + ")");
|
||||
var ab = rom['bytes'];
|
||||
var adw = rom['data'];
|
||||
|
||||
if (ab) {
|
||||
this.abROM = ab;
|
||||
}
|
||||
else if (adw) {
|
||||
/*
|
||||
* Convert all the DWORDs into BYTEs, so that subsequent code only has to deal with abROM.
|
||||
*/
|
||||
this.abROM = new Array(adw.length * 4);
|
||||
for (var idw = 0, ib = 0; idw < adw.length; idw++) {
|
||||
this.abROM[ib++] = adw[idw] & 0xff;
|
||||
this.abROM[ib++] = (adw[idw] >> 8) & 0xff;
|
||||
this.abROM[ib++] = (adw[idw] >> 16) & 0xff;
|
||||
this.abROM[ib++] = (adw[idw] >> 24) & 0xff;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.abROM = rom;
|
||||
}
|
||||
|
||||
this.aSymbols = rom['symbols'];
|
||||
|
||||
if (!this.abROM.length) {
|
||||
Component.error("Empty ROM: " + sROMFile);
|
||||
return;
|
||||
}
|
||||
else if (this.abROM.length == 1) {
|
||||
Component.error(this.abROM[0]);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
this.notice("ROM data error: " + e.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Parse the ROM data manually; we assume it's in "simplified" hex form (a series of hex byte-values
|
||||
* separated by whitespace).
|
||||
*/
|
||||
var sHexData = sROMData.replace(/\n/gm, " ").replace(/ +$/, "");
|
||||
var asHexData = sHexData.split(" ");
|
||||
this.abROM = new Array(asHexData.length);
|
||||
for (var i = 0; i < asHexData.length; i++) {
|
||||
this.abROM[i] = parseInt(asHexData[i], 16);
|
||||
}
|
||||
}
|
||||
this.copyROM();
|
||||
};
|
||||
|
||||
/**
|
||||
* copyROM()
|
||||
*
|
||||
* This function is called by both initBus() and onLoadROM(), but it cannot copy the the ROM data into place
|
||||
* until after initBus() has received the Bus component AND onloadROM() has received the abROM data. When both
|
||||
* those criteria are satisfied, the component becomes "ready".
|
||||
*
|
||||
* @this {ROM}
|
||||
*/
|
||||
ROM.prototype.copyROM = function()
|
||||
{
|
||||
if (!this.isReady()) {
|
||||
if (!this.sFileName) {
|
||||
this.setReady();
|
||||
}
|
||||
else if (this.abROM && this.bus) {
|
||||
if (this.abROM.length != this.sizeROM) {
|
||||
/*
|
||||
* Note that setError() sets the component's fError flag, which in turn prevents setReady() from
|
||||
* marking the component ready. TODO: Revisit this decision. One the one hand, it sounds like a
|
||||
* good idea to stop the machine in its tracks whenever a setError() occurs, but there may also be
|
||||
* times when we'd like to forge ahead anyway.
|
||||
*/
|
||||
this.setError("ROM size (0x" + str.toHex(this.abROM.length) + ") does not match specified size (0x" + str.toHex(this.sizeROM) + ")");
|
||||
}
|
||||
else if (this.addROM(this.addrROM) && this.addROM(this.addrROMAlias)) {
|
||||
/*
|
||||
* If there's a component we should notify, notify it now, and give it the internal byte array, so that
|
||||
* it doesn't have to ask the CPU for the data. Currently, the only component that uses this notification
|
||||
* option is the Video component, and only when the associated ROM contains font data that it needs.
|
||||
*/
|
||||
if (this.idNotify) {
|
||||
var component = Component.getComponentByID(this.idNotify, this.id);
|
||||
if (component) {
|
||||
component.onROMLoad(this.abROM);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* We used to hang onto the original ROM data so that we could restore any bytes the CPU overwrote,
|
||||
* using memory write-notification handlers, but with the introduction of read-only memory blocks, that's
|
||||
* no longer necessary.
|
||||
*
|
||||
* TODO: Consider an option to retain the ROM data, and give the user some way of restoring ROMs.
|
||||
* That may be useful for "resumable" machines that save/restore all dirty block of memory, regardless
|
||||
* whether they're ROM or RAM. However, the only way to modify a machine's ROM is with the Debugger,
|
||||
* and Debugger users should know better.
|
||||
*/
|
||||
delete this.abROM;
|
||||
}
|
||||
this.setReady();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* addROM(addr)
|
||||
*
|
||||
* If addr is null or undefined, then it's presumably an unused addrROMAlias, which we simply ignore (it's not
|
||||
* considered a failure condition).
|
||||
*
|
||||
* @this {ROM}
|
||||
* @param {number} addr
|
||||
* @return {boolean}
|
||||
*/
|
||||
ROM.prototype.addROM = function(addr)
|
||||
{
|
||||
if (addr == null) return true;
|
||||
if (this.bus.addMemory(addr, this.sizeROM, true)) {
|
||||
if (DEBUG) this.log("addROM(): copying ROM to " + str.toHexAddr(addr) + " (0x" + str.toHex(this.abROM.length) + " bytes)");
|
||||
for (var i = 0; i < this.abROM.length; i++) {
|
||||
this.bus.setByteDirect(addr + i, this.abROM[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
* We don't need to report an error here, because addMemory() already takes care of that.
|
||||
*/
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* ROM.init()
|
||||
*
|
||||
* This function operates on every element (e) of class "rom", and initializes
|
||||
* all the necessary HTML to construct the ROM module(s) as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "rom" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the ROM constructor
|
||||
* expects.
|
||||
*/
|
||||
ROM.init = function()
|
||||
{
|
||||
var aeROM = Component.getElementsByClass(window.document, PCJSCLASS, "rom");
|
||||
for (var iROM = 0; iROM < aeROM.length; iROM++) {
|
||||
var eROM = aeROM[iROM];
|
||||
var parmsROM = Component.getComponentParms(eROM);
|
||||
var rom = new ROM(parmsROM);
|
||||
Component.bindComponentControls(rom, eROM, PCJSCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize all the ROM modules on the page.
|
||||
*/
|
||||
web.onInit(ROM.init);
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.ROM = ROM;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = ROM;
|
||||
803
modules/pcjs/lib/serial.js
Normal file
803
modules/pcjs/lib/serial.js
Normal file
|
|
@ -0,0 +1,803 @@
|
|||
/**
|
||||
* @fileoverview Implements the PCjs SerialPort component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Jul-01
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var ChipSet = require("./chipset");
|
||||
var Debugger = require("./debugger");
|
||||
var State = require("./state");
|
||||
}
|
||||
|
||||
/**
|
||||
* SerialPort(parmsSerial)
|
||||
*
|
||||
* The SerialPort component has the following component-specific (parmsSerial) properties:
|
||||
*
|
||||
* adapter: 1 (for port 0x3F8) or 2 (for port 0x2F8); 0 if not defined
|
||||
*
|
||||
* WARNING: Since the XSL file defines 'adapter' as a number, not a string, there's no need to
|
||||
* use parseInt(), and as an added benefit, we don't need to worry about whether a hex or decimal
|
||||
* format was used.
|
||||
*
|
||||
* This hard-coded approach mimics the original IBM PC Asynchronous Adapter configuration, which
|
||||
* contained a pair of "shunt modules" that allowed the user to select a port address of either
|
||||
* 0x3F8 ("Primary") or 0x2F8 ("Secondary").
|
||||
*
|
||||
* DOS typically names the Primary adapter "COM1" and the Secondary adapter "COM2", but I prefer
|
||||
* to stick to adapter numbers, since not all operating systems follow those naming conventions.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @param {Object} parmsSerial
|
||||
*/
|
||||
function SerialPort(parmsSerial) {
|
||||
|
||||
this.iAdapter = parmsSerial['adapter'];
|
||||
|
||||
switch (this.iAdapter) {
|
||||
case 1:
|
||||
this.portBase = 0x3F8;
|
||||
this.nIRQ = ChipSet.IRQ.COM1;
|
||||
break;
|
||||
case 2:
|
||||
this.portBase = 0x2F8;
|
||||
this.nIRQ = ChipSet.IRQ.COM2;
|
||||
break;
|
||||
default:
|
||||
Component.warning("Unrecognized serial adapter #" + this.iAdapter);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* controlIOBuffer is a DOM element, if any, bound to the port (currently for output purposes only; see echoByte())
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
this.controlIOBuffer = null;
|
||||
|
||||
Component.call(this, "SerialPort", parmsSerial, SerialPort);
|
||||
|
||||
Component.bindExternalControl(this, parmsSerial['binding'], SerialPort.sIOBuffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* class SerialPort
|
||||
* property {number} iAdapter
|
||||
* property {number} portBase
|
||||
* property {number} nIRQ
|
||||
* property {Object} controlIOBuffer is a DOM element, if any, bound to the port (for rudimentary output; see echoByte())
|
||||
*
|
||||
* NOTE: This class declaration started as a way of informing the code inspector of the controlIOBuffer property,
|
||||
* which remained undefined until a setBinding() call set it later, but I've since decided that explicitly
|
||||
* initializing such properties in the constructor is a better way to go -- even though it's more code -- because
|
||||
* JavaScript compilers are supposed to be happier when the underlying object structures aren't constantly changing.
|
||||
*
|
||||
* Besides, I'm not sure I want to get into documenting every property this way, for this or any/every other class,
|
||||
* let alone getting into which ones should be considered private or protected, because PCjs isn't really a library
|
||||
* for third-party apps.
|
||||
*/
|
||||
|
||||
Component.subclass(Component, SerialPort);
|
||||
|
||||
/*
|
||||
* Internal name used for the I/O buffer control, if any, that we bind to the SerialPort.
|
||||
*
|
||||
* Alternatively, if SerialPort wants to use another component's control (eg, the Panel's
|
||||
* "print" control), it can specify the name of that control with the 'binding' property.
|
||||
*
|
||||
* For that binding to succeed, we also need to know the target component; for now, that's
|
||||
* been hard-coded to "Panel", in part because that's one of the few components we can rely
|
||||
* upon initializing before we do, but it would be a simple matter to include a component type
|
||||
* or ID as part of the 'binding' property as well, if we need more flexibility later.
|
||||
*/
|
||||
SerialPort.sIOBuffer = "buffer";
|
||||
|
||||
/*
|
||||
* 8250 I/O register offsets (add these to a I/O base address to obtain an I/O port address)
|
||||
*
|
||||
* NOTE: DLL.REG and DLM.REG form a 16-bit divisor into a clock input frequency of 1.8432Mhz. The following
|
||||
* values should be used for the corresponding baud rates. Rates above 9600 are discouraged by the IBM Tech Ref,
|
||||
* but rates as high as 128000 are listed on the NS8250A data sheet.
|
||||
*
|
||||
* Divisor Rate Percent Error
|
||||
* 0x0900 50
|
||||
* 0x0600 75
|
||||
* 0x0417 110 0.026%
|
||||
* 0x0359 134.5 0.058%
|
||||
* 0x0300 150
|
||||
* 0x0180 300
|
||||
* 0x00C0 600
|
||||
* 0x0060 1200
|
||||
* 0x0040 1800
|
||||
* 0x003A 2000 0.69%
|
||||
* 0x0030 2400
|
||||
* 0x0020 3600
|
||||
* 0x0018 4800
|
||||
* 0x0010 7200
|
||||
* 0x000C 9600
|
||||
* 0x0006 19200
|
||||
* 0x0003 38400
|
||||
* 0x0002 56000 2.86%
|
||||
* 0x0001 128000
|
||||
*/
|
||||
SerialPort.DLL = {REG: 0}; // Divisor Latch LSB (only when SerialPort.LCR.DLAB is set)
|
||||
SerialPort.THR = {REG: 0}; // Transmitter Holding Register (write)
|
||||
SerialPort.DL_DEFAULT = 0x180; // we select an arbitrary default Divisor Latch equivalent to 300 baud
|
||||
|
||||
/*
|
||||
* Receiver Buffer Register (RBR.REG, offset 0; eg, 0x3F8 or 0x2F8)
|
||||
*/
|
||||
SerialPort.RBR = {REG: 0}; // (read)
|
||||
|
||||
/*
|
||||
* Interrupt Enable Register (IER.REG, offset 1; eg, 0x3F9 or 0x2F9)
|
||||
*/
|
||||
SerialPort.IER = {};
|
||||
SerialPort.IER.REG = 1; // Interrupt Enable Register
|
||||
SerialPort.IER.RBR_AVAIL = 0x01;
|
||||
SerialPort.IER.THR_EMPTY = 0x02;
|
||||
SerialPort.IER.LSR_DELTA = 0x04;
|
||||
SerialPort.IER.MSR_DELTA = 0x08;
|
||||
SerialPort.IER.UNUSED = 0xF0; // always zero
|
||||
|
||||
SerialPort.DLM = {REG: 1}; // Divisor Latch MSB (only when SerialPort.LCR.DLAB is set)
|
||||
|
||||
/*
|
||||
* Interrupt ID Register (IIR.REG, offset 2; eg, 0x3FA or 0x2FA)
|
||||
*
|
||||
* All interrupt conditions cleared by reading the corresponding register (or, in the case of IRR_INT_THR, writing a new value to THR.REG)
|
||||
*/
|
||||
SerialPort.IIR = {};
|
||||
SerialPort.IIR.REG = 2; // Interrupt ID Register (read-only)
|
||||
SerialPort.IIR.NO_INT = 0x01;
|
||||
SerialPort.IIR.INT_LSR = 0x06; // Line Status (highest priority: Overrun error, Parity error, Framing error, or Break Interrupt)
|
||||
SerialPort.IIR.INT_RBR = 0x04; // Receiver Data Available
|
||||
SerialPort.IIR.INT_THR = 0x02; // Transmitter Holding Register Empty
|
||||
SerialPort.IIR.INT_MSR = 0x00; // Modem Status Register (lowest priority: Clear To Send, Data Set Ready, Ring Indicator, or Data Carrier Detect)
|
||||
SerialPort.IIR.INT_BITS = 0x06;
|
||||
SerialPort.IIR.UNUSED = 0xF8; // always zero (the ROM BIOS relies on these bits "floating to 1" when no SerialPort is present)
|
||||
|
||||
/*
|
||||
* Line Control Register (LCR.REG, offset 3; eg, 0x3FB or 0x2FB)
|
||||
*/
|
||||
SerialPort.LCR = {};
|
||||
SerialPort.LCR.REG = 3; // Line Control Register
|
||||
SerialPort.LCR.DATA_5BITS = 0x00;
|
||||
SerialPort.LCR.DATA_6BITS = 0x01;
|
||||
SerialPort.LCR.DATA_7BITS = 0x02;
|
||||
SerialPort.LCR.DATA_8BITS = 0x03;
|
||||
SerialPort.LCR.STOP_BITS = 0x04; // clear: 1 stop bit; set: 1.5 stop bits for LCR_DATA_5BITS, 2 stop bits for all other data lengths
|
||||
SerialPort.LCR.PARITY_BIT = 0x08; // if set, a parity bit is inserted/expected between the last data bit and the first stop bit; no parity bit if clear
|
||||
SerialPort.LCR.PARITY_EVEN = 0x10; // if set, even parity is selected (ie, the parity bit insures an even number of set bits); if clear, odd parity
|
||||
SerialPort.LCR.PARITY_STICK = 0x20; // if set, parity bit is transmitted inverted; if clear, parity bit is transmitted normally
|
||||
SerialPort.LCR.BREAK = 0x40; // if set, serial output (SOUT) signal is forced to logical 0 for the duration
|
||||
SerialPort.LCR.DLAB = 0x80; // Divisor Latch Access Bit; if set, DLL.REG and DLM.REG can be read or written
|
||||
|
||||
/*
|
||||
* Modem Control Register (MCR.REG, offset 4; eg, 0x3FC or 0x2FC)
|
||||
*/
|
||||
SerialPort.MCR = {};
|
||||
SerialPort.MCR.REG = 4; // Modem Control Register
|
||||
SerialPort.MCR.DTR = 0x01; // when set, DTR goes high, indicating ready to establish link (looped back to DSR in loop-back mode)
|
||||
SerialPort.MCR.RTS = 0x02; // when set, RTS goes high, indicating ready to exchange data (looped back to CTS in loop-back mode)
|
||||
SerialPort.MCR.OUT1 = 0x04; // when set, OUT1 goes high (looped back to RI in loop-back mode)
|
||||
SerialPort.MCR.OUT2 = 0x08; // when set, OUT2 goes high (looped back to RLSD in loop-back mode)
|
||||
SerialPort.MCR.LOOPBACK = 0x10; // when set, enables loop-back mode
|
||||
SerialPort.MCR.UNUSED = 0xE0; // always zero
|
||||
|
||||
/*
|
||||
* Line Status Register (LSR.REG, offset 5; eg, 0x3FD or 0x2FD)
|
||||
*
|
||||
* NOTE: I've seen different specs for the LSR_TSRE. I'm following the IBM Tech Ref's lead here, but the data sheet I have calls it TEMT
|
||||
* instead of TSRE, and claims that it is set whenever BOTH the THR and TSR are empty, and clear whenever EITHER the THR or TSR contain data.
|
||||
*/
|
||||
SerialPort.LSR = {};
|
||||
SerialPort.LSR.REG = 5; // Line Status Register
|
||||
SerialPort.LSR.DR = 0x01; // Data Ready (set when new data in RBR.REG; cleared when RBR.REG read)
|
||||
SerialPort.LSR.OE = 0x02; // Overrun Error (set when new data arrives in RBR.REG before previous data read; cleared when LSR.REG read)
|
||||
SerialPort.LSR.PE = 0x04; // Parity Error (set when new data has incorrect parity; cleared when LSR.REG read)
|
||||
SerialPort.LSR.FE = 0x08; // Framing Error (set when new data has invalid stop bit; cleared when LSR.REG read)
|
||||
SerialPort.LSR.BI = 0x10; // Break Interrupt (set when new data exceeded normal transmission time; cleared LSR.REG when read)
|
||||
SerialPort.LSR.THRE = 0x20; // Transmitter Holding Register Empty (set when UART ready to accept new data; cleared when THR.REG written)
|
||||
SerialPort.LSR.TSRE = 0x40; // Transmitter Shift Register Empty (set when the TSR is empty; cleared when the THR is transferred to the TSR)
|
||||
SerialPort.LSR.UNUSED = 0x80; // always zero
|
||||
|
||||
/*
|
||||
* Modem Status Register (MSR.REG, offset 6; eg, 0x3FE or 0x2FE)
|
||||
*/
|
||||
SerialPort.MSR = {};
|
||||
SerialPort.MSR.REG = 6; // Modem Status Register
|
||||
SerialPort.MSR.DCTS = 0x01; // when set, CTS (Clear To Send) has changed since last read
|
||||
SerialPort.MSR.DDSR = 0x02; // when set, DSR (Data Set Ready) has changed since last read
|
||||
SerialPort.MSR.TERI = 0x04; // when set, TERI (Trailing Edge Ring Indicator) indicates RI has changed from 1 to 0
|
||||
SerialPort.MSR.DRLSD = 0x08; // when set, RLSD (Received Line Signal Detector) has changed
|
||||
SerialPort.MSR.CTS = 0x10; // when set, the modem or data set is ready to exchange data (complement of the Clear To Send input signal)
|
||||
SerialPort.MSR.DSR = 0x20; // when set, the modem or data set is ready to establish link (complement of the Data Set Ready input signal)
|
||||
SerialPort.MSR.RI = 0x40; // complement of the RI (Ring Indicator) input
|
||||
SerialPort.MSR.RLSD = 0x80; // complement of the RLSD (Received Line Signal Detect) input
|
||||
|
||||
/*
|
||||
* Scratch Register (SCR.REG, offset 7; eg, 0x3FF or 0x2FF)
|
||||
*/
|
||||
SerialPort.SCR = {REG: 7};
|
||||
|
||||
/**
|
||||
* attachMouse(id, mouse)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {string} id
|
||||
* @param {Mouse} mouse component
|
||||
* @return {Component} this or null, based on whether or not the specified ID matches
|
||||
*/
|
||||
SerialPort.prototype.attachMouse = function(id, mouse) {
|
||||
if (id == this.idComponent) {
|
||||
this.mouse = mouse;
|
||||
return this;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* syncMouse()
|
||||
*
|
||||
* NOTE: This is probably obsolete, but the Mouse component still might discover a need for it. See Mouse.powerUp().
|
||||
*
|
||||
* @this {SerialPort}
|
||||
*
|
||||
SerialPort.prototype.syncMouse = function() {
|
||||
if (this.mouse) this.mouse.notifyMCR(this.bMCR);
|
||||
};
|
||||
*/
|
||||
|
||||
/**
|
||||
* setBinding(sHTMLClass, sHTMLType, sBinding, control)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @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, "buffer")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
SerialPort.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control) {
|
||||
var serial = this;
|
||||
switch (sBinding) {
|
||||
case SerialPort.sIOBuffer:
|
||||
this.bindings[sBinding] = this.controlIOBuffer = control;
|
||||
/*
|
||||
* By establishing an onkeypress handler here, we make it possible for DOS commands like
|
||||
* "CTTY COM1" to more or less work (use "CTTY CON" to restore control to the DOS console).
|
||||
*
|
||||
* WARNING: This isn't really a supported feature yet; very much a work-in-progress.
|
||||
*/
|
||||
control.onkeydown = function onKeyDownSerial(event) {
|
||||
/*
|
||||
* This is required in addition to onkeypress, because it's the only way to prevent
|
||||
* BACKSPACE from being interpreted by the browser as a "Back" operation.
|
||||
*/
|
||||
event = event || window.event;
|
||||
var keyCode = event.keyCode;
|
||||
if (keyCode === 8) {
|
||||
if (event.preventDefault) event.preventDefault();
|
||||
serial.sendRBR([keyCode]);
|
||||
}
|
||||
};
|
||||
control.onkeypress = function onKeyPressSerial(event) {
|
||||
/*
|
||||
* Browser-independent keyCode extraction (refer to keyPress() and the other key
|
||||
* event handlers in keyboard.js).
|
||||
*/
|
||||
event = event || window.event;
|
||||
var keyCode = event.which || event.keyCode;
|
||||
serial.sendRBR([keyCode]);
|
||||
};
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {Computer} cmp
|
||||
* @param {Bus} bus
|
||||
* @param {X86CPU} cpu
|
||||
* @param {Debugger} dbg
|
||||
*/
|
||||
SerialPort.prototype.initBus = function(cmp, bus, cpu, dbg) {
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.chipset = cmp.getComponentByType("ChipSet");
|
||||
bus.addPortInputTable(this, SerialPort.aPortInput, this.portBase);
|
||||
bus.addPortOutputTable(this, SerialPort.aPortOutput, this.portBase);
|
||||
this.setReady();
|
||||
};
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
SerialPort.prototype.powerUp = function(data, fRepower) {
|
||||
if (!fRepower) {
|
||||
if (!data || !this.restore) {
|
||||
this.reset();
|
||||
} else {
|
||||
if (!this.restore(data)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* powerDown(fSave)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {boolean} fSave
|
||||
* @return {Object|boolean}
|
||||
*/
|
||||
SerialPort.prototype.powerDown = function(fSave) {
|
||||
return fSave && this.save ? this.save() : true;
|
||||
};
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* @this {SerialPort}
|
||||
*/
|
||||
SerialPort.prototype.reset = function() {
|
||||
this.initState();
|
||||
};
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* This implements save support for the SerialPort component.
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @return {Object}
|
||||
*/
|
||||
SerialPort.prototype.save = function() {
|
||||
var state = new State(this);
|
||||
state.set(0, this.saveRegisters());
|
||||
return state.data();
|
||||
};
|
||||
|
||||
/**
|
||||
* restore(data)
|
||||
*
|
||||
* This implements restore support for the SerialPort component.
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {Object} data
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
SerialPort.prototype.restore = function(data) {
|
||||
return this.initState(data[0]);
|
||||
};
|
||||
|
||||
/**
|
||||
* initState(data)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {Array} [data]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
SerialPort.prototype.initState = function(data) {
|
||||
/*
|
||||
* The NS8250A spec doesn't explicitly say what the RBR and THR are initialized to on a reset,
|
||||
* but I think we can safely assume zeros. Similarly, we reset the baud rate Divisor Latch (wDL)
|
||||
* to an arbitrary but consistent default (DL_DEFAULT).
|
||||
*/
|
||||
var i = 0;
|
||||
if (data === undefined) {
|
||||
data = [
|
||||
0, // RBR
|
||||
0, // THR
|
||||
SerialPort.DL_DEFAULT, // DL
|
||||
0, // IER
|
||||
SerialPort.IIR.NO_INT, // IIR
|
||||
0, // LCR
|
||||
0, // MCR
|
||||
SerialPort.LSR.THRE | SerialPort.LSR.TSRE, // LSR
|
||||
SerialPort.MSR.CTS | SerialPort.MSR.DSR, // MSR (instead of the normal 0 default, we indicate a state of readiness -- to be revisited)
|
||||
[]
|
||||
];
|
||||
}
|
||||
this.bRBR = data[i++];
|
||||
this.bTHR = data[i++];
|
||||
this.wDL = data[i++];
|
||||
this.bIER = data[i++];
|
||||
this.bIIR = data[i++];
|
||||
this.bLCR = data[i++];
|
||||
this.bMCR = data[i++];
|
||||
this.bLSR = data[i++];
|
||||
this.bMSR = data[i++];
|
||||
this.abReceive = data[i];
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* saveRegisters()
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @return {Array}
|
||||
*/
|
||||
SerialPort.prototype.saveRegisters = function() {
|
||||
var i = 0;
|
||||
var data = [];
|
||||
data[i++] = this.bRBR;
|
||||
data[i++] = this.bTHR;
|
||||
data[i++] = this.wDL;
|
||||
data[i++] = this.bIER;
|
||||
data[i++] = this.bIIR;
|
||||
data[i++] = this.bLCR;
|
||||
data[i++] = this.bMCR;
|
||||
data[i++] = this.bLSR;
|
||||
data[i++] = this.bMSR;
|
||||
data[i] = this.abReceive;
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* sendRBR(ab)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {Array} ab is an array of bytes to propagate to the bRBR (Receiver Buffer Register)
|
||||
*/
|
||||
SerialPort.prototype.sendRBR = function(ab) {
|
||||
this.abReceive = this.abReceive.concat(ab);
|
||||
this.advanceRBR();
|
||||
};
|
||||
|
||||
/**
|
||||
* advanceRBR()
|
||||
*
|
||||
* @this {SerialPort}
|
||||
*/
|
||||
SerialPort.prototype.advanceRBR = function() {
|
||||
if (this.abReceive.length > 0 && !(this.bLSR & SerialPort.LSR.DR)) {
|
||||
this.bRBR = this.abReceive.shift();
|
||||
this.bLSR |= SerialPort.LSR.DR;
|
||||
}
|
||||
this.updateIRR();
|
||||
};
|
||||
|
||||
/**
|
||||
* inRBR(port, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3F8 or 0x2F8)
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
SerialPort.prototype.inRBR = function(port, addrFrom) {
|
||||
var b = ((this.bLCR & SerialPort.LCR.DLAB) ? (this.wDL & 0xff) : this.bRBR);
|
||||
this.messagePort(port, null, addrFrom, (this.bLCR & SerialPort.LCR.DLAB) ? "DLL" : "RBR", b);
|
||||
this.bLSR &= ~SerialPort.LSR.DR;
|
||||
this.advanceRBR();
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inIER(port, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3F9 or 0x2F9)
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
SerialPort.prototype.inIER = function(port, addrFrom) {
|
||||
var b = ((this.bLCR & SerialPort.LCR.DLAB) ? (this.wDL >> 8) : this.bIER);
|
||||
this.messagePort(port, null, addrFrom, (this.bLCR & SerialPort.LCR.DLAB) ? "DLM" : "IER", b);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inIIR(port, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3FA or 0x2FA)
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
SerialPort.prototype.inIIR = function(port, addrFrom) {
|
||||
var b = this.bIIR;
|
||||
this.messagePort(port, null, addrFrom, "IIR", b);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inLCR(port, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3FB or 0x2FB)
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
SerialPort.prototype.inLCR = function(port, addrFrom) {
|
||||
var b = this.bLCR;
|
||||
this.messagePort(port, null, addrFrom, "LCR", b);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inMCR(port, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3FC or 0x2FC)
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
SerialPort.prototype.inMCR = function(port, addrFrom) {
|
||||
var b = this.bMCR;
|
||||
this.messagePort(port, null, addrFrom, "MCR", b);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inLSR(port, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3FD or 0x2FD)
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
SerialPort.prototype.inLSR = function(port, addrFrom) {
|
||||
var b = this.bLSR;
|
||||
this.messagePort(port, null, addrFrom, "LSR", b);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inMSR(port, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3FE or 0x2FE)
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
SerialPort.prototype.inMSR = function(port, addrFrom) {
|
||||
var b = this.bMSR;
|
||||
this.messagePort(port, null, addrFrom, "MSR", b);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* outTHR(port, bOut, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3F8 or 0x2F8)
|
||||
* @param {number} bOut
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
|
||||
*/
|
||||
SerialPort.prototype.outTHR = function(port, bOut, addrFrom) {
|
||||
this.messagePort(port, bOut, addrFrom, (this.bLCR & SerialPort.LCR.DLAB) ? "DLL" : "THR");
|
||||
if (this.bLCR & SerialPort.LCR.DLAB) {
|
||||
this.wDL = (this.wDL & ~0xff) | bOut;
|
||||
} else {
|
||||
this.bTHR = bOut;
|
||||
this.bLSR &= ~(SerialPort.LSR.THRE | SerialPort.LSR.TSRE);
|
||||
if (this.echoByte(bOut)) {
|
||||
this.bLSR |= (SerialPort.LSR.THRE | SerialPort.LSR.TSRE);
|
||||
/*
|
||||
* QUESTION: Does this mean we should also flush/zero bTHR?
|
||||
*/
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* outIER(port, bOut, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3F9 or 0x2F9)
|
||||
* @param {number} bOut
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
|
||||
*/
|
||||
SerialPort.prototype.outIER = function(port, bOut, addrFrom) {
|
||||
this.messagePort(port, bOut, addrFrom, (this.bLCR & SerialPort.LCR.DLAB) ? "DLM" : "IER");
|
||||
if (this.bLCR & SerialPort.LCR.DLAB) {
|
||||
this.wDL = (this.wDL & 0xff) | (bOut << 8);
|
||||
} else {
|
||||
this.bIER = bOut;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* outLCR(port, bOut, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3FB or 0x2FB)
|
||||
* @param {number} bOut
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
|
||||
*/
|
||||
SerialPort.prototype.outLCR = function(port, bOut, addrFrom) {
|
||||
this.messagePort(port, bOut, addrFrom, "LCR");
|
||||
this.bLCR = bOut;
|
||||
};
|
||||
|
||||
/**
|
||||
* outMCR(port, bOut, addrFrom)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port (0x3FC or 0x2FC)
|
||||
* @param {number} bOut
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
|
||||
*/
|
||||
SerialPort.prototype.outMCR = function(port, bOut, addrFrom) {
|
||||
var bPrev = this.bMCR;
|
||||
this.messagePort(port, bOut, addrFrom, "MCR");
|
||||
this.bMCR = bOut;
|
||||
if (this.mouse && (bPrev ^ bOut) & (SerialPort.MCR.DTR | SerialPort.MCR.RTS)) {
|
||||
this.mouse.notifyMCR(this.bMCR);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* updateIRR()
|
||||
*
|
||||
* @this {SerialPort}
|
||||
*/
|
||||
SerialPort.prototype.updateIRR = function() {
|
||||
var bIIR = -1;
|
||||
if ((this.bLSR & SerialPort.LSR.DR) && (this.bIER & SerialPort.IER.RBR_AVAIL)) {
|
||||
bIIR = SerialPort.IIR.INT_RBR;
|
||||
}
|
||||
if (bIIR >= 0) {
|
||||
this.bIIR &= ~(SerialPort.IIR.NO_INT | SerialPort.IIR.INT_BITS);
|
||||
this.bIIR |= bIIR;
|
||||
if (this.chipset && this.nIRQ) this.chipset.setIRR(this.nIRQ);
|
||||
} else {
|
||||
this.bIIR |= SerialPort.IIR.NO_INT;
|
||||
if (this.chipset && this.nIRQ) this.chipset.clearIRR(this.nIRQ);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* echoByte(b)
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} b
|
||||
* @return {boolean} true if echoed, false if not
|
||||
*/
|
||||
SerialPort.prototype.echoByte = function(b) {
|
||||
if (this.controlIOBuffer) {
|
||||
if (b != 0x0D) {
|
||||
if (b == 0x08) {
|
||||
this.controlIOBuffer.value = this.controlIOBuffer.value.slice(0, -1);
|
||||
} else {
|
||||
this.controlIOBuffer.value += String.fromCharCode(b);
|
||||
this.controlIOBuffer.scrollTop = this.controlIOBuffer.scrollHeight;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* messageDebugger(sMessage)
|
||||
*
|
||||
* This is a combination of the Debugger's messageEnabled(MESSAGE_SERIAL) and message() functions, for convenience.
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {string} sMessage is any caller-defined message string
|
||||
*/
|
||||
SerialPort.prototype.messageDebugger = function(sMessage) {
|
||||
if (DEBUGGER && this.dbg) {
|
||||
if (this.dbg.messageEnabled(Debugger.MESSAGE.SERIAL)) {
|
||||
this.dbg.message(sMessage);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* messagePort(port, bOut, addrFrom, name, bIn)
|
||||
*
|
||||
* This is an internal version of the Debugger's messagePort() function, for convenience.
|
||||
*
|
||||
* @this {SerialPort}
|
||||
* @param {number} port
|
||||
* @param {number|null} bOut if an output operation
|
||||
* @param {number|null} [addrFrom]
|
||||
* @param {string|null} [name] of the port, if any
|
||||
* @param {number} [bIn] is the input value, if known, on an input operation
|
||||
*/
|
||||
SerialPort.prototype.messagePort = function(port, bOut, addrFrom, name, bIn) {
|
||||
if (DEBUGGER && this.dbg) {
|
||||
this.dbg.messagePort(this, port, bOut, addrFrom, name, Debugger.MESSAGE.SERIAL, bIn);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Port input notification table
|
||||
*/
|
||||
SerialPort.aPortInput = {
|
||||
0x0: SerialPort.prototype.inRBR, // or DLL if DLAB set
|
||||
0x1: SerialPort.prototype.inIER, // or DLM if DLAB set
|
||||
0x2: SerialPort.prototype.inIIR,
|
||||
0x3: SerialPort.prototype.inLCR,
|
||||
0x4: SerialPort.prototype.inMCR,
|
||||
0x5: SerialPort.prototype.inLSR,
|
||||
0x6: SerialPort.prototype.inMSR
|
||||
};
|
||||
|
||||
/*
|
||||
* Port output notification table
|
||||
*/
|
||||
SerialPort.aPortOutput = {
|
||||
0x0: SerialPort.prototype.outTHR, // or DLL if DLAB set
|
||||
0x1: SerialPort.prototype.outIER, // or DLM if DLAB set
|
||||
0x3: SerialPort.prototype.outLCR,
|
||||
0x4: SerialPort.prototype.outMCR
|
||||
};
|
||||
|
||||
/**
|
||||
* SerialPort.init()
|
||||
*
|
||||
* This function operates on every element (e) of class "serial", and initializes
|
||||
* all the necessary HTML to construct the SerialPort module(s) as spec'ed.
|
||||
*
|
||||
* Note that each element (e) of class "serial" is expected to have a "data-value"
|
||||
* attribute containing the same JSON-encoded parameters that the SerialPort constructor
|
||||
* expects.
|
||||
*/
|
||||
SerialPort.init = function() {
|
||||
var aeSerial = Component.getElementsByClass(window.document, PCJSCLASS, "serial");
|
||||
for (var iSerial = 0; iSerial < aeSerial.length; iSerial++) {
|
||||
var eSerial = aeSerial[iSerial];
|
||||
var parmsSerial = Component.getComponentParms(eSerial);
|
||||
var serial = new SerialPort(parmsSerial);
|
||||
Component.bindComponentControls(serial, eSerial, PCJSCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize every SerialPort module on the page.
|
||||
*/
|
||||
web.onInit(SerialPort.init);
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.SerialPort = SerialPort;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = SerialPort;
|
||||
391
modules/pcjs/lib/state.js
Normal file
391
modules/pcjs/lib/state.js
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
/**
|
||||
* @fileoverview The State class used by C1Pjs and PCjs.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-May-14
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
var web = require("./../../shared/lib/weblib");
|
||||
var Component = require("./../../shared/lib/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* State(component, sVersion, sSuffix)
|
||||
*
|
||||
* @constructor
|
||||
* @param {Component} component
|
||||
* @param {string} [sVersion] is used to append a major version number to the key
|
||||
* @param {string} [sSuffix] is used to append any additional suffixes to the key
|
||||
*
|
||||
* State objects are used by components to save/restore their state.
|
||||
*
|
||||
* During a save operation, components add data to a State object via set(),
|
||||
* and then return the resulting data using data().
|
||||
*
|
||||
* During a restore operation, the Computer component passes the results of each
|
||||
* data() call back to the originating component.
|
||||
*/
|
||||
function State(component, sVersion, sSuffix) {
|
||||
this.id = component.id;
|
||||
this.key = State.key(component, sVersion, sSuffix);
|
||||
this.dbg = component.dbg;
|
||||
this.unload(component.parms);
|
||||
}
|
||||
|
||||
/**
|
||||
* State.key(component, sVersion, sSuffix)
|
||||
*
|
||||
* This encapsulates the key generation code.
|
||||
*
|
||||
* @param {Component} component
|
||||
* @param {string} [sVersion] is used to append a major version number to the key
|
||||
* @param {string} [sSuffix] is used to append any additional suffixes to the key
|
||||
* @return {string} key
|
||||
*/
|
||||
State.key = function(component, sVersion, sSuffix) {
|
||||
var key = component.id;
|
||||
if (sVersion) {
|
||||
var i = sVersion.indexOf('.');
|
||||
if (i > 0) key += ".v" + sVersion.substr(0, i);
|
||||
}
|
||||
if (sSuffix) {
|
||||
key += "." + sSuffix;
|
||||
}
|
||||
return key;
|
||||
};
|
||||
|
||||
/**
|
||||
* State.compress(aSrc)
|
||||
*
|
||||
* @param {Array.<number>|null} aSrc
|
||||
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
|
||||
*/
|
||||
State.compress = function(aSrc) {
|
||||
if (aSrc) {
|
||||
var iSrc = 0;
|
||||
var iComp = 0;
|
||||
var aComp = [];
|
||||
while (iSrc < aSrc.length) {
|
||||
var n = aSrc[iSrc];
|
||||
Component.assert(n !== undefined);
|
||||
var iCompare = iSrc + 1;
|
||||
while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare++;
|
||||
aComp[iComp++] = iCompare - iSrc;
|
||||
aComp[iComp++] = n;
|
||||
iSrc = iCompare;
|
||||
}
|
||||
if (aComp.length < aSrc.length) return aComp;
|
||||
}
|
||||
return aSrc;
|
||||
};
|
||||
|
||||
/**
|
||||
* State.decompress(aComp)
|
||||
*
|
||||
* @param {Array.<number>} aComp
|
||||
* @param {number} nLength is expected length of decompressed data
|
||||
* @return {Array.<number>}
|
||||
*/
|
||||
State.decompress = function(aComp, nLength) {
|
||||
var iDst = 0;
|
||||
var aDst = new Array(nLength);
|
||||
var iComp = 0;
|
||||
while (iComp < aComp.length - 1) {
|
||||
var c = aComp[iComp++];
|
||||
var n = aComp[iComp++];
|
||||
while (c--) {
|
||||
aDst[iDst++] = n;
|
||||
}
|
||||
}
|
||||
Component.assert(aDst.length == nLength);
|
||||
return aDst;
|
||||
};
|
||||
|
||||
/**
|
||||
* State.compressEvenOdd(aSrc)
|
||||
*
|
||||
* This is a very simple variation on compress() that compresses all the EVEN elements of aSrc first, followed by all the ODD
|
||||
* elements. This tends to work better on EGA video memory, because when odd/even addressing is enabled (eg, for text
|
||||
* modes), the DWORD values tend to alternate, which is the worst case for compress(), but the best case for compressEvenOdd().
|
||||
*
|
||||
* One wrinkle we support: if the first element is uninitialized, then we assume the entire array is undefined, and return an
|
||||
* empty compressed array. Conversely, decompressEvenOdd() will take an empty compressed array and return an uninitialized array.
|
||||
*
|
||||
* @param {Array.<number>|null} aSrc
|
||||
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
|
||||
*/
|
||||
State.compressEvenOdd = function(aSrc) {
|
||||
if (aSrc) {
|
||||
var iComp = 0, aComp = [];
|
||||
if (aSrc[0] !== undefined) {
|
||||
for (var off = 0; off < 2; off++) {
|
||||
var iSrc = off;
|
||||
while (iSrc < aSrc.length) {
|
||||
var n = aSrc[iSrc];
|
||||
var iCompare = iSrc + 2;
|
||||
while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare += 2;
|
||||
aComp[iComp++] = (iCompare - iSrc) >> 1;
|
||||
aComp[iComp++] = n;
|
||||
iSrc = iCompare;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aComp.length < aSrc.length) return aComp;
|
||||
}
|
||||
return aSrc;
|
||||
};
|
||||
|
||||
/**
|
||||
* State.decompressEvenOdd(aComp, nLength)
|
||||
*
|
||||
* This is the counterpart to compressEvenOdd(). Note that because there's nothing in the compressed sequence that differentiates
|
||||
* a compress() sequence from a compressEvenOdd() sequence, you simply have to be consistent -- if you used even/odd compression, then
|
||||
* you must use even/odd decompression.
|
||||
*
|
||||
* @param {Array.<number>} aComp
|
||||
* @param {number} nLength is expected length of decompressed data
|
||||
* @return {Array.<number>}
|
||||
*/
|
||||
State.decompressEvenOdd = function(aComp, nLength) {
|
||||
var iDst = 0;
|
||||
var aDst = new Array(nLength);
|
||||
var iComp = 0;
|
||||
while (iComp < aComp.length - 1) {
|
||||
var c = aComp[iComp++];
|
||||
var n = aComp[iComp++];
|
||||
while (c--) {
|
||||
aDst[iDst] = n;
|
||||
iDst += 2;
|
||||
}
|
||||
/*
|
||||
* The output of a "count,value" pair will never exceed the end of the output array, so as soon as we reach it
|
||||
* the first time, we know it's time to switch to ODD elements, and as soon as we reach it again, we should be
|
||||
* done.
|
||||
*/
|
||||
Component.assert(iDst <= nLength || iComp == aComp.length);
|
||||
if (iDst == nLength) iDst = 1;
|
||||
}
|
||||
Component.assert(aDst.length == nLength);
|
||||
return aDst;
|
||||
};
|
||||
|
||||
State.prototype = {
|
||||
constructor: State,
|
||||
/**
|
||||
* set(id, data)
|
||||
*
|
||||
* @this {State}
|
||||
* @param {number|string} id
|
||||
* @param {Object|string} data
|
||||
*/
|
||||
set: function(id, data) {
|
||||
try {
|
||||
this[this.id][id] = data;
|
||||
} catch(e) {
|
||||
Component.log(e.message)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* get(id)
|
||||
*
|
||||
* @this {State}
|
||||
* @param {number|string} id
|
||||
* @return {Object|string|null}
|
||||
*/
|
||||
get: function(id) {
|
||||
return this[this.id][id] || null;
|
||||
},
|
||||
/**
|
||||
* value()
|
||||
*
|
||||
* @this {State}
|
||||
* @return {string}
|
||||
*
|
||||
* Use this instead of data() if you haven't called parse() yet.
|
||||
*/
|
||||
value: function() {
|
||||
return this[this.id];
|
||||
},
|
||||
/**
|
||||
* data()
|
||||
*
|
||||
* @this {State}
|
||||
* @return {Object}
|
||||
*/
|
||||
data: function() {
|
||||
return this[this.id];
|
||||
},
|
||||
/**
|
||||
* load(s)
|
||||
*
|
||||
* @this {State}
|
||||
* @param {Object|string|null} [s]
|
||||
* @return {boolean} true if state exists in localStorage, false if not
|
||||
*
|
||||
* WARNING: Make sure you follow this call with either a call to parse() or unload(),
|
||||
* because any stringified data that we've loaded isn't usable until it's been parsed.
|
||||
*/
|
||||
load: function(s) {
|
||||
if (s) {
|
||||
this[this.id] = s;
|
||||
this.fLoaded = true;
|
||||
return true;
|
||||
}
|
||||
if (this.fLoaded) {
|
||||
/*
|
||||
* This is assumed to be a redundant load().
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
if (web.hasLocalStorage()) {
|
||||
s = web.getLocalStorageItem(this.key);
|
||||
if (s) {
|
||||
this[this.id] = s;
|
||||
this.fLoaded = true;
|
||||
if (DEBUG) this.messageDebugger("localStorage(" + this.key + "): " + s.length + " bytes loaded");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* parse()
|
||||
*
|
||||
* @this {State}
|
||||
* @return {boolean} true if successful, false if error
|
||||
*
|
||||
* This completes the load() operation, by parsing what was loaded, on the assumption there
|
||||
* might be some benefit to deferring parsing until we've given the user a chance to confirm.
|
||||
* Otherwise, load() could have just as easily done this, too.
|
||||
*/
|
||||
parse: function() {
|
||||
var fSuccess = true;
|
||||
try {
|
||||
this[this.id] = JSON.parse(this[this.id]);
|
||||
} catch (e) {
|
||||
Component.error(e.message || e);
|
||||
fSuccess = false;
|
||||
}
|
||||
return fSuccess;
|
||||
},
|
||||
/**
|
||||
* store()
|
||||
*
|
||||
* @this {State}
|
||||
* @return {boolean} true if successful, false if error
|
||||
*/
|
||||
store: function() {
|
||||
var fSuccess = true;
|
||||
if (web.hasLocalStorage()) {
|
||||
var s = JSON.stringify(this[this.id]);
|
||||
if (web.setLocalStorageItem(this.key, s)) {
|
||||
if (DEBUG) this.messageDebugger("localStorage(" + this.key + "): " + s.length + " bytes stored");
|
||||
} else {
|
||||
/*
|
||||
* WARNING: Because browsers tend to disable all alerts() during an "unload" operation,
|
||||
* it's unlikely anyone will ever see the "quota" errors that occur at this point. Need to
|
||||
* think of some way to notify the user that there's a problem, and offer a way of cleaning
|
||||
* up old states.
|
||||
*/
|
||||
Component.error("Unable to store " + s.length + " bytes in browser local storage");
|
||||
fSuccess = false;
|
||||
}
|
||||
}
|
||||
return fSuccess;
|
||||
},
|
||||
/**
|
||||
* toString()
|
||||
*
|
||||
* We can't know whether this might be called before parse() or after parse(), so we check.
|
||||
* If before, then this[this.id] will still be in string form; if after, it will be an Object.
|
||||
*
|
||||
* @this {State}
|
||||
* @return {string} JSON-encoded state
|
||||
*/
|
||||
toString: function() {
|
||||
var value = this[this.id];
|
||||
return (typeof value == "string"? value : JSON.stringify(value));
|
||||
},
|
||||
/**
|
||||
* unload(parms)
|
||||
*
|
||||
* @this {State}
|
||||
* @param {Object} [parms]
|
||||
*
|
||||
* This discards any data saved via set() or loaded via load(), creating an empty State object.
|
||||
* Note that you have to follow this call with an explicit call to store() if you want to remove
|
||||
* the state from localStorage as well.
|
||||
*/
|
||||
unload: function(parms) {
|
||||
this[this.id] = {};
|
||||
if (parms) this.set("parms", parms);
|
||||
this.fLoaded = false;
|
||||
},
|
||||
/**
|
||||
* clear(fAll)
|
||||
*
|
||||
* @this {State}
|
||||
* @param {boolean} [fAll] true to unconditionally clear ALL localStorage for the current domain
|
||||
*
|
||||
* This unloads the current state, and then clears ALL localStorage for the current machine,
|
||||
* independent of version, to reduce the chance of orphaned states wasting part of our limited allocation.
|
||||
*/
|
||||
clear: function(fAll) {
|
||||
this.unload();
|
||||
var aKeys = web.getLocalStorageKeys();
|
||||
for (var i = 0; i < aKeys.length; i++) {
|
||||
var sKey = aKeys[i];
|
||||
if (sKey && (fAll || sKey.substr(0, this.key.length) == this.key)) {
|
||||
web.removeLocalStorageItem(sKey);
|
||||
if (DEBUG) this.messageDebugger("localStorage(" + sKey + ") removed");
|
||||
aKeys.splice(i, 1);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* messageDebugger(sMessage)
|
||||
*
|
||||
* @this {State}
|
||||
* @param {string} sMessage is any caller-defined message string
|
||||
*
|
||||
* This is a combination of the Debugger's messageEnabled(MESSAGE_STATE) and message() functions, for convenience.
|
||||
*/
|
||||
messageDebugger: function(sMessage) {
|
||||
if (DEBUGGER && this.dbg) {
|
||||
if (this.dbg.messageEnabled(Debugger.MESSAGE.STATE)) this.dbg.message(sMessage);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof APP_PCJS !== 'undefined') APP_PCJS.State = State;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = State;
|
||||
5099
modules/pcjs/lib/video.js
Normal file
5099
modules/pcjs/lib/video.js
Normal file
File diff suppressed because it is too large
Load diff
327
modules/pcjs/lib/x86.js
Normal file
327
modules/pcjs/lib/x86.js
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
/**
|
||||
* @fileoverview Defines PCjs x86 constants.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Sep-05
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var X86 = {
|
||||
/*
|
||||
* CPU model numbers
|
||||
*/
|
||||
MODEL_8086: 8086,
|
||||
MODEL_8088: 8088,
|
||||
MODEL_80186: 80186,
|
||||
MODEL_80188: 80188,
|
||||
MODEL_80286: 80286,
|
||||
/*
|
||||
* Processor Status flag definitions (stored in regPS)
|
||||
*/
|
||||
PS: {
|
||||
CF: 0x0001, // bit 0: Carry flag
|
||||
BIT1: 0x0002, // bit 1: reserved, always set
|
||||
PF: 0x0004, // bit 2: Parity flag
|
||||
BIT3: 0x0008, // bit 3: reserved, always clear
|
||||
AF: 0x0010, // bit 4: Auxiliary Carry flag (aka Arithmetic flag)
|
||||
BIT5: 0x0020, // bit 5: reserved, always clear
|
||||
ZF: 0x0040, // bit 6: Zero flag
|
||||
SF: 0x0080, // bit 7: Sign flag
|
||||
TF: 0x0100, // bit 8: Trap flag
|
||||
IF: 0x0200, // bit 9: Interrupt flag
|
||||
DF: 0x0400, // bit 10: Direction flag
|
||||
OF: 0x0800, // bit 11: Overflow flag
|
||||
IOPL: {
|
||||
MASK: 0x3000, // 12-13: I/O Privilege Level (always set on 8086/80186, clear on 80286)
|
||||
SHIFT: 12
|
||||
},
|
||||
NT: 0x4000, // bit 14: Nested Task flag, always set on 8086/80186, clear on 80286
|
||||
BIT15: 0x8000 // bit 15: reserved, always set on 8086/80186, clear otherwise
|
||||
},
|
||||
/*
|
||||
* Machine Status Word definitions (stored in regMSW)
|
||||
*/
|
||||
MSW: {
|
||||
PE: 0x0001, // protected-mode enabled
|
||||
MP: 0x0002, // monitor processor extension (ie, coprocessor)
|
||||
EM: 0x0004, // emulate processor extension
|
||||
TS: 0x0008, // task switch indicator
|
||||
SET: 0xfff0 // on the 80286, these are always set (TODO: Verify)
|
||||
},
|
||||
SEL: {
|
||||
RPL: 0x0003, // requested privilege level (0-3)
|
||||
LDT: 0x0004, // table indicator (0: GDT, 1: LDT)
|
||||
MASK: 0xfff8 // table index
|
||||
},
|
||||
DESC: { // Descriptor Table Entry
|
||||
LIMIT: {
|
||||
OFFSET: 0x0
|
||||
},
|
||||
BASE: {
|
||||
OFFSET: 0x2
|
||||
},
|
||||
ACC: { // bit definitions for the access word (offset 0x4)
|
||||
OFFSET: 0x4,
|
||||
BASE1623: 0x00ff,
|
||||
MASK: 0xff00,
|
||||
TYPE: {
|
||||
MASK: 0x1f00,
|
||||
SEG: 0x1000,
|
||||
NONSEG: 0x0f00,
|
||||
/*
|
||||
* The following bits apply only when SEG is set
|
||||
*/
|
||||
CODE: 0x0800, // set for CODE, clear for DATA
|
||||
ACCESSED: 0x0100, // set if accessed, clear if not accessed
|
||||
READABLE: 0x0200, // CODE: set if readable, clear if exec-only
|
||||
WRITEABLE: 0x0200, // DATA: set if writable, clear if read-only
|
||||
CONFORMING: 0x0400, // CODE: set if conforming, clear if not
|
||||
EXPDOWN: 0x0400, // DATA: set if expand-down, clear if not
|
||||
/*
|
||||
* The following are all the possible (valid) types (well, except for the variations
|
||||
* of DATA and CODE where the ACCESSED bit (0x0100) may also be set)
|
||||
*/
|
||||
TSS: 0x0100,
|
||||
LDT: 0x0200,
|
||||
TSS_LDT: 0x0300,
|
||||
TSS_BUSY: 0x0300,
|
||||
GATE_CALL: 0x0400,
|
||||
GATE_TASK: 0x0500,
|
||||
GATE_INT: 0x0600,
|
||||
GATE_TRAP: 0x0700,
|
||||
DATA_READONLY: 0x1000,
|
||||
DATA_WRITEABLE: 0x1200,
|
||||
DATA_EXPDOWN_READONLY: 0x1400,
|
||||
DATA_EXPDOWN_WRITEABLE: 0x1600,
|
||||
CODE_EXECONLY: 0x1800,
|
||||
CODE_READABLE: 0x1a00,
|
||||
CODE_CONFORMING_EXECONLY: 0x1c00,
|
||||
CODE_CONFORMING_READABLE: 0x1e00
|
||||
},
|
||||
DPL: {
|
||||
MASK: 0x6000,
|
||||
SHIFT: 13
|
||||
},
|
||||
PRESENT: 0x8000
|
||||
},
|
||||
EXT: { // descriptor extension word (reserved on the 80286; "must be zero")
|
||||
OFFSET: 0x6,
|
||||
MASK: 0xffff
|
||||
}
|
||||
},
|
||||
/*
|
||||
* Processor Exception Interrupts
|
||||
*
|
||||
* Of the following exceptions, all are designed to be restartable, except for 0x08 and 0x09 (and 0x0D
|
||||
* after an attempt to write to a read-only segment).
|
||||
*
|
||||
* Error codes are pushed onto the stack for 0x08 (always 0) and 0x0A through 0x0D.
|
||||
*
|
||||
* Priority: Instruction exception, TRAP, NMI, Processor Extension Segment Overrun, and finally INTR.
|
||||
*
|
||||
* All exceptions can also occur in real-mode, except where noted. A GP_FAULT in real-mode can be triggered
|
||||
* by "any memory reference instruction that attempts to reference [a] 16-bit word at offset 0FFFFH".
|
||||
*
|
||||
* Interrupts beyond 0x10 (up through 0x1F) are reserved for future exceptions.
|
||||
*
|
||||
* Implementation Detail: For any opcode we know must generate a UD_FAULT interrupt, we invoke opInvalid().
|
||||
* We reserve the term "undefined" for opcodes that require further investigation, and we invoke opUndefined()
|
||||
* in those cases until an opcode's behavior has been defined; at that point, it's either valid or invalid.
|
||||
*
|
||||
* As for "illegal", that's a silly (and redundant) term in this context, so we don't use it. Similarly,
|
||||
* the term "undocumented" should be limited to operations that are valid but that Intel did not document.
|
||||
*/
|
||||
EXCEPTION: {
|
||||
DIV_ERR: 0x00, // Divide Error Interrupt
|
||||
TRAP: 0x01, // Single Step (aka Trap) Interrupt
|
||||
NMI: 0x02, // Non-Maskable Interrupt
|
||||
BREAKPOINT: 0x03, // Breakpoint Interrupt
|
||||
OVERFLOW: 0x04, // INTO Overflow Interrupt (FYI, return address does NOT point to offending instruction)
|
||||
BOUND_ERR: 0x05, // BOUND Error Interrupt
|
||||
UD_FAULT: 0x06, // Invalid (aka Undefined or Illegal) Opcode (see implementation detail above)
|
||||
NM_FAULT: 0x07, // No Math Unit Available (see ESC or WAIT)
|
||||
DF_FAULT: 0x08, // Double Fault (see LIDT)
|
||||
MP_FAULT: 0x09, // Math Unit Protection Fault (see ESC)
|
||||
TS_FAULT: 0x0A, // Invalid Task State Segment Fault (protected-mode only)
|
||||
NP_FAULT: 0x0B, // Not Present Fault (protected-mode only)
|
||||
SS_FAULT: 0x0C, // Stack Fault (protected-mode only)
|
||||
GP_FAULT: 0x0D, // General Protection Fault
|
||||
MF_FAULT: 0x10 // Math Fault (see ESC or WAIT)
|
||||
},
|
||||
ERRCODE: {
|
||||
EXT: 0x0001,
|
||||
IDT: 0x0002,
|
||||
LDT: 0x0004,
|
||||
MASK: 0xfff8 // index of corresponding entry in GDT, LDT or IDT
|
||||
},
|
||||
RESULT: {
|
||||
SIZE_BYTE: 0x00100, // mask for byte arithmetic instructions (after subtracting 1)
|
||||
SIZE_WORD: 0x10000, // mask for word arithmetic instructions (after subtracting 1)
|
||||
AUXOVF_AF: 0x00010,
|
||||
AUXOVF_OF: 0x08080,
|
||||
AUXOVF_CF: 0x10100
|
||||
},
|
||||
PARITY: [ // 256-byte array with a 1 wherever the number of set bits of the array index is EVEN
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
|
||||
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1
|
||||
],
|
||||
/*
|
||||
* Bit values for opFlags, which are all reset to zero prior to each instruction
|
||||
*/
|
||||
OPFLAG: {
|
||||
NOREAD: 0x0001,
|
||||
NOWRITE: 0x0002,
|
||||
NOINTR: 0x0004, // indicates a segreg has been set, or a prefix, or an STI (delay INTR acknowledgement)
|
||||
SEG: 0x0010,
|
||||
LOCK: 0x0020,
|
||||
REPZ: 0x0040, // repeat while Z (NOTE: this value MUST match PS.ZF; see opCMPSb/opCMPSw/opSCASb/opSCASw)
|
||||
REPNZ: 0x0080, // repeat while NZ
|
||||
REPEAT: 0x0100, // this indicates that an instruction is being repeated (ie, some iteration AFTER the first)
|
||||
PUSHSP: 0x0200 // the SP register is potentially being referenced by a PUSH SP opcode, adjustment may be required
|
||||
},
|
||||
/*
|
||||
* Bit values for intFlags
|
||||
*/
|
||||
INTFLAG: {
|
||||
NONE: 0x00,
|
||||
INTR: 0x01, // h/w interrupt requested
|
||||
TRAP: 0x02, // trap (INT 0x01) requested
|
||||
HALT: 0x04, // halt (HLT) requested
|
||||
DMA: 0x08 // async DMA operation in progress
|
||||
},
|
||||
/*
|
||||
* Common opcodes
|
||||
*/
|
||||
OPCODE: {
|
||||
ES: 0x26, // opES()
|
||||
CS: 0x2E, // opCS()
|
||||
SS: 0x36, // opSS()
|
||||
DS: 0x3E, // opDS()
|
||||
PUSHSP: 0x54,
|
||||
PUSHA: 0x60,
|
||||
POPA: 0x61,
|
||||
BOUND: 0x62,
|
||||
ARPL: 0x63,
|
||||
PUSH16: 0x68,
|
||||
IMUL16: 0x69,
|
||||
PUSH8: 0x6A,
|
||||
IMUL8: 0x6B,
|
||||
INSB: 0x6C,
|
||||
INSW: 0x6D,
|
||||
OUTSB: 0x6E,
|
||||
OUTSW: 0x6F,
|
||||
ENTER: 0xC8,
|
||||
LEAVE: 0xC9,
|
||||
CALLF: 0x9A, // opCALLf()
|
||||
MOVSB: 0xA4, // opMOVSb()
|
||||
MOVSW: 0xA5, // opMOVSw()
|
||||
CMPSB: 0xA6,
|
||||
CMPSW: 0xA7,
|
||||
STOSB: 0xAA,
|
||||
STOSW: 0xAB,
|
||||
LODSB: 0xAC,
|
||||
LODSW: 0xAD,
|
||||
SCASB: 0xAE,
|
||||
SCASW: 0xAF,
|
||||
INT3: 0xCC,
|
||||
INTn: 0xCD,
|
||||
INTO: 0xCE,
|
||||
LOOPNZ: 0xE0,
|
||||
LOOPZ: 0xE1,
|
||||
LOOP: 0xE2,
|
||||
CALL: 0xE8,
|
||||
JMP: 0xE9, // JMP opcode (2-byte displacement)
|
||||
JMPS: 0xEB, // JMP opcode (1-byte displacement)
|
||||
LOCK: 0xF0,
|
||||
REPNZ: 0xF2,
|
||||
REPZ: 0xF3,
|
||||
CALLW: 0x10FF,
|
||||
CALLDW: 0x18FF,
|
||||
UD2: 0x0B0F // UD2 (invalid opcode guaranteed to generate UD_FAULT on all post-8086 processors)
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Some PS flags are stored directly in regPS, hence the "direct" designation.
|
||||
*/
|
||||
X86.PS.DIRECT = (X86.PS.TF | X86.PS.IF | X86.PS.DF);
|
||||
|
||||
/*
|
||||
* However, PS "arithmetic" flags are NOT stored in regPS; they are maintained across
|
||||
* separate result registers, hence the "indirect" designation.
|
||||
*/
|
||||
X86.PS.INDIRECT = (X86.PS.CF | X86.PS.PF | X86.PS.AF | X86.PS.ZF | X86.PS.SF | X86.PS.OF);
|
||||
|
||||
/*
|
||||
* NOTE: These are the default "always set" PS bits for the 8086/8088; other processors must
|
||||
* adjust these bits accordingly. The final adjusted value is then stored in the X86CPU object
|
||||
* as "this.PS_SET"; setPS() must use that value, NOT this one.
|
||||
*
|
||||
* TODO: Verify that PS.BIT1 was always set on reset, even on the 8086/8088.
|
||||
*/
|
||||
X86.PS.SET = (X86.PS.BIT1 | X86.PS.IOPL.MASK | X86.PS.NT | X86.PS.BIT15);
|
||||
|
||||
/*
|
||||
* getPS() brings all the direct and indirect flags together, and setPS() performs the
|
||||
* reverse, setting all the corresponding "result registers" to match the indirect flags.
|
||||
*
|
||||
* These "result registers" are created/reset by an initial call to setPS(0); they include:
|
||||
*
|
||||
* this.resultSize (must be set to one of: SIZE_BYTE or SIZE_WORD)
|
||||
* this.resultValue
|
||||
* this.resultParitySign
|
||||
* this.resultAuxOverflow
|
||||
*
|
||||
* PS.SAHF is a subset of the arithmetic flags, and refers only to those flags that the
|
||||
* SAHF and LAHF "8080 legacy" opcodes affect.
|
||||
*/
|
||||
X86.PS.SAHF = (X86.PS.CF | X86.PS.PF | X86.PS.AF | X86.PS.ZF | X86.PS.SF);
|
||||
|
||||
/*
|
||||
* Before we zero opFlags, we first see if any of the following PREFIX bits were set. If any were set, they are OR'ed
|
||||
* into opPrefixes; otherwise, opPrefixes is zeroed as well. This gives prefix-conscious instructions like LODS, MOVS,
|
||||
* STOS, CMPS, etc, a way of determining which prefixes, if any, immediately preceded them.
|
||||
*/
|
||||
X86.OPFLAG.PREFIXES = (X86.OPFLAG.SEG | X86.OPFLAG.LOCK | X86.OPFLAG.REPZ | X86.OPFLAG.REPNZ);
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = X86;
|
||||
2643
modules/pcjs/lib/x86cpu.js
Normal file
2643
modules/pcjs/lib/x86cpu.js
Normal file
File diff suppressed because it is too large
Load diff
1347
modules/pcjs/lib/x86grps.js
Normal file
1347
modules/pcjs/lib/x86grps.js
Normal file
File diff suppressed because it is too large
Load diff
505
modules/pcjs/lib/x86help.js
Normal file
505
modules/pcjs/lib/x86help.js
Normal file
|
|
@ -0,0 +1,505 @@
|
|||
/**
|
||||
* @fileoverview Implements PCjs 8086 opcode helpers.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Sep-05
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
var X86 = require("./x86");
|
||||
var Debugger = require("./debugger");
|
||||
}
|
||||
|
||||
var X86Help = {
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst (current value, ignored)
|
||||
* @param {number} src (new value)
|
||||
* @return {number} dst (updated value, from src)
|
||||
*/
|
||||
opHelpMOV: function(dst, src) {
|
||||
this.nStepCycles -= (this.regEAWrite < 0? (this.regEA < 0? this.CYCLES.nOpCyclesMovRR : this.CYCLES.nOpCyclesMovRM) : this.CYCLES.nOpCyclesMovMR);
|
||||
return src;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst (current value, ignored)
|
||||
* @param {number} src (new value)
|
||||
* @return {number} dst (src is overridden, replaced with regMD16, as specified by opMOVSegSrc)
|
||||
*/
|
||||
opHelpMOVSegSrc: function(dst, src) {
|
||||
return X86Help.opHelpMOV.call(this, dst, this.regMD16);
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpTESTb: function(dst, src) {
|
||||
this.resultValue = this.resultParitySign = this.resultAuxOverflow = dst & src;
|
||||
this.resultSize = X86.RESULT.SIZE_BYTE;
|
||||
this.nStepCycles -= (this.regEAWrite < 0? (this.regEA < 0? this.CYCLES.nOpCyclesTestRR : this.CYCLES.nOpCyclesTestRM) : this.CYCLES.nOpCyclesTestRM);
|
||||
if (EAFUNCS) this.setEAByte = this.setEAByteDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpTESTw: function(dst, src) {
|
||||
this.resultValue = this.resultParitySign = this.resultAuxOverflow = dst & src;
|
||||
this.resultSize = X86.RESULT.SIZE_WORD;
|
||||
this.nStepCycles -= (this.regEAWrite < 0? (this.regEA < 0? this.CYCLES.nOpCyclesTestRR : this.CYCLES.nOpCyclesTestRM) : this.CYCLES.nOpCyclesTestRM);
|
||||
if (EAFUNCS) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*
|
||||
* 80286_and_80287_Programmers_Reference_Manual_1987.pdf, p.B-44 (p.254) notes that:
|
||||
*
|
||||
* "The low 16 bits of the product of a 16-bit signed multiply are the same as those of an
|
||||
* unsigned multiply. The three operand IMUL instruction can be used for unsigned operands as well."
|
||||
*
|
||||
* However, we still sign-extend the operands before multiplying, making it easier to range-check the result.
|
||||
*
|
||||
* (80186/80188 and up)
|
||||
*/
|
||||
opHelpIMUL8: function(dst, src) {
|
||||
var result = ((src << 16) >> 16) * ((this.getIPByte() << 24) >> 24);
|
||||
this.resultValue = this.resultAuxOverflow = this.resultParitySign = result;
|
||||
this.resultSize = X86.RESULT.SIZE_BYTE;
|
||||
/*
|
||||
* TODO: Look into a more efficient way of setting/synchronizing CF and OF; this code works,
|
||||
* but it somewhat defeats the purpose of the indirect result variables that we've set above.
|
||||
*/
|
||||
if (result > 32767 || result < -32768) {
|
||||
this.setCF(); this.setOF();
|
||||
} else {
|
||||
this.clearCF(); this.clearOF();
|
||||
}
|
||||
result &= 0xffff;
|
||||
if (DEBUG && DEBUGGER) this.traceLog('IMUL8', dst, src, null, this.getPS(), result);
|
||||
/*
|
||||
* NOTE: These are the cycle counts for the 80286; the 80186/80188 have slightly different values (ranges):
|
||||
* 22-25 and 29-32 instead of 21 and 24, respectively. However, accurate cycle counts for the 80186/80188 is
|
||||
* not super-critical. TODO: Fix this someday.
|
||||
*/
|
||||
this.nStepCycles -= (this.regEA < 0? 21 : 24);
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*
|
||||
* 80286_and_80287_Programmers_Reference_Manual_1987.pdf, p.B-44 (p.254) notes that:
|
||||
*
|
||||
* "The low 16 bits of the product of a 16-bit signed multiply are the same as those of an
|
||||
* unsigned multiply. The three operand IMUL instruction can be used for unsigned operands as well."
|
||||
*
|
||||
* However, we still sign-extend the operands before multiplying, making it easier to range-check the result.
|
||||
*
|
||||
* (80186/80188 and up)
|
||||
*/
|
||||
opHelpIMUL16: function(dst, src) {
|
||||
var result = ((src << 16) >> 16) * ((this.getIPWord() << 16) >> 16);
|
||||
this.resultValue = this.resultAuxOverflow = this.resultParitySign = result;
|
||||
this.resultSize = X86.RESULT.SIZE_WORD;
|
||||
/*
|
||||
* TODO: Look into a more efficient way of setting/synchronizing CF and OF; this code works,
|
||||
* but it somewhat defeats the purpose of the indirect result variables that we've set above.
|
||||
*/
|
||||
if (result > 32767 || result < -32768) {
|
||||
this.setCF(); this.setOF();
|
||||
} else {
|
||||
this.clearCF(); this.clearOF();
|
||||
}
|
||||
result &= 0xffff;
|
||||
if (DEBUG && DEBUGGER) this.traceLog('IMUL16', dst, src, null, this.getPS(), result);
|
||||
/*
|
||||
* NOTE: These are the cycle counts for the 80286; the 80186/80188 have slightly different values (ranges):
|
||||
* 22-25 and 29-32 instead of 21 and 24, respectively. However, accurate cycle counts for the 80186/80188 is
|
||||
* not super-critical. TODO: Fix this someday.
|
||||
*/
|
||||
this.nStepCycles -= (this.regEA < 0? 21 : 24);
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number} dst unchanged
|
||||
*/
|
||||
opHelpESC: function(dst, src) {
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpLEA: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
X86Help.opUndefined.call(this);
|
||||
return dst;
|
||||
}
|
||||
this.nStepCycles -= this.CYCLES.nOpCyclesLEA;
|
||||
return this.regEA;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpLDS: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
X86Help.opUndefined.call(this);
|
||||
return dst;
|
||||
}
|
||||
this.setDS(this.getWord(this.regEA + 2));
|
||||
this.nStepCycles -= this.CYCLES.nOpCyclesLS;
|
||||
return src;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpLES: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
X86Help.opUndefined.call(this);
|
||||
return dst;
|
||||
}
|
||||
this.setES(this.getWord(this.regEA + 2));
|
||||
this.nStepCycles -= this.CYCLES.nOpCyclesLS;
|
||||
return src;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpBOUND: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
/*
|
||||
* Generate a #UD fault (INT 0x06: Undefined Opcode) if src is not a memory operand.
|
||||
*/
|
||||
X86Help.opInvalid.call(this);
|
||||
return dst;
|
||||
}
|
||||
/*
|
||||
* Note that BOUND performs signed comparisons, so we must transform all arguments into signed values.
|
||||
*/
|
||||
var wIndex = (dst << 16) >> 16;
|
||||
var wLower = (this.getWord(this.regEA) << 16) >> 16;
|
||||
var wUpper = (this.getWord(this.regEA + 2) << 16) >> 16;
|
||||
this.nStepCycles -= this.CYCLES.nOpCyclesBound;
|
||||
if (wIndex < wLower || wIndex > wUpper) {
|
||||
/*
|
||||
* The INT 0x05 handler must be called with CS:IP pointing to the BOUND instruction.
|
||||
*
|
||||
* TODO: Determine the cycle impact when a BOUND exception is triggered, over and above nOpCyclesBound.
|
||||
*/
|
||||
this.setIP(this.opEA - this.segCS.base);
|
||||
X86Help.opHelpINT.call(this, X86.EXCEPTION.BOUND_ERR, null, 0);
|
||||
}
|
||||
if (EAFUNCS) this.setEAByte = this.setEAByteDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpARPL: function(dst, src) {
|
||||
this.nStepCycles -= (10 + (this.regEA < 0? 0 : 1));
|
||||
if ((dst & X86.SEL.RPL) < (src & X86.SEL.RPL)) {
|
||||
dst = (dst & ~X86.SEL.RPL) | (src & X86.SEL.RPL);
|
||||
this.setZF();
|
||||
return dst;
|
||||
}
|
||||
this.clearZF();
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpLAR: function(dst, src) {
|
||||
this.nStepCycles -= (14 + (this.regEA < 0? 0 : 2));
|
||||
/*
|
||||
* Currently, segVER.load() will return an error only if the selector is beyond the bounds of the
|
||||
* descriptor table or the descriptor is not for a segment.
|
||||
*
|
||||
* TODO: This instruction's 80286 documentation does not discuss conforming code segments; determine
|
||||
* if we need a special check for them.
|
||||
*/
|
||||
if (this.segVER.load(src, true) >= 0) {
|
||||
if (this.segVER.dpl >= this.segCS.cpl && this.segVER.dpl >= (src & X86.SEL.RPL)) {
|
||||
this.setZF();
|
||||
return this.segVER.acc & X86.DESC.ACC.MASK;
|
||||
}
|
||||
}
|
||||
this.clearZF();
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (the selector)
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpLSL: function(dst, src) {
|
||||
/*
|
||||
* TODO: Is this an invalid operation if regEAWrite is set? dst is required to be a register.
|
||||
*/
|
||||
this.nStepCycles -= (14 + (this.regEA < 0? 0 : 2));
|
||||
/*
|
||||
* Currently, segVER.load() will return an error only if the selector is beyond the bounds of the
|
||||
* descriptor table or the descriptor is not for a segment.
|
||||
*
|
||||
* TODO: LSL is explicitly documented as ALSO requiring a non-null selector, so we check X86.SEL.MASK;
|
||||
* are there any other instructions that were, um, less explicit but also require a non-null selector?
|
||||
*/
|
||||
if ((src & X86.SEL.MASK) && this.segVER.load(src, true) >= 0) {
|
||||
var fConforming = ((this.segVER.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING_EXECONLY) == X86.DESC.ACC.TYPE.CODE_CONFORMING_EXECONLY);
|
||||
if ((fConforming || this.segVER.dpl >= this.segCS.cpl) && this.segVER.dpl >= (src & X86.SEL.RPL)) {
|
||||
this.setZF();
|
||||
return this.segVER.limit;
|
||||
}
|
||||
}
|
||||
this.clearZF();
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpXCHGrb: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
switch (this.bModRM & 0x7) {
|
||||
case 0x0: // AL
|
||||
this.regAX = (this.regAX & ~0xff) | dst;
|
||||
break;
|
||||
case 0x1: // CL
|
||||
this.regCX = (this.regCX & ~0xff) | dst;
|
||||
break;
|
||||
case 0x2: // DL
|
||||
this.regDX = (this.regDX & ~0xff) | dst;
|
||||
break;
|
||||
case 0x3: // BL
|
||||
this.regBX = (this.regBX & ~0xff) | dst;
|
||||
break;
|
||||
case 0x4: // AH
|
||||
this.regAX = (this.regAX & 0xff) | (dst << 8);
|
||||
break;
|
||||
case 0x5: // CH
|
||||
this.regCX = (this.regCX & 0xff) | (dst << 8);
|
||||
break;
|
||||
case 0x6: // DH
|
||||
this.regDX = (this.regDX & 0xff) | (dst << 8);
|
||||
break;
|
||||
case 0x7: // BH
|
||||
this.regBX = (this.regBX & 0xff) | (dst << 8);
|
||||
break;
|
||||
default:
|
||||
break; // there IS no other case, but JavaScript inspections don't know that
|
||||
}
|
||||
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRR;
|
||||
} else {
|
||||
/*
|
||||
* This is a case where the ModRM decoder that's calling us didn't know it should have called modEAByte()
|
||||
* instead of getEAByte(), so we compensate by updating regEAWrite.
|
||||
*/
|
||||
this.regEAWrite = this.regEA;
|
||||
this.setEAByte(dst);
|
||||
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRM;
|
||||
}
|
||||
return src;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src
|
||||
* @return {number}
|
||||
*/
|
||||
opHelpXCHGrw: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
switch (this.bModRM & 0x7) {
|
||||
case 0x0: // AX
|
||||
this.regAX = dst;
|
||||
break;
|
||||
case 0x1: // CX
|
||||
this.regCX = dst;
|
||||
break;
|
||||
case 0x2: // DX
|
||||
this.regDX = dst;
|
||||
break;
|
||||
case 0x3: // BX
|
||||
this.regBX = dst;
|
||||
break;
|
||||
case 0x4: // SP
|
||||
this.regSP = dst;
|
||||
break;
|
||||
case 0x5: // BP
|
||||
this.regBP = dst;
|
||||
break;
|
||||
case 0x6: // SI
|
||||
this.regSI = dst;
|
||||
break;
|
||||
case 0x7: // DI
|
||||
this.regDI = dst;
|
||||
break;
|
||||
default:
|
||||
break; // there IS no other case, but JavaScript inspections don't know that
|
||||
}
|
||||
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRR;
|
||||
} else {
|
||||
/*
|
||||
* This is a case where the ModRM decoder that's calling us didn't know it should have called modEAByte()
|
||||
* instead of getEAByte(), so we compensate by updating regEAWrite.
|
||||
*/
|
||||
this.regEAWrite = this.regEA;
|
||||
this.setEAWord(dst);
|
||||
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRM;
|
||||
}
|
||||
return src;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} nIDT
|
||||
* @param {number|null|undefined} nError
|
||||
* @param {number} nCycles (in addition to the default of nOpCyclesInt)
|
||||
*/
|
||||
opHelpINT: function(nIDT, nError, nCycles) {
|
||||
if (this.loadIDTEntry(nIDT)) {
|
||||
this.pushWord(this.getPS());
|
||||
this.regPS &= this.descIDT.maskPS;
|
||||
this.pushWord(this.segCS.sel);
|
||||
this.pushWord(this.regIP);
|
||||
if (nError != null) this.pushWord(nError);
|
||||
this.setCSIP(this.descIDT.off, this.descIDT.sel);
|
||||
this.nStepCycles -= this.CYCLES.nOpCyclesInt + nCycles;
|
||||
}
|
||||
/*
|
||||
* TODO: Now what?
|
||||
*/
|
||||
},
|
||||
/**
|
||||
* opHelpLMSW(w)
|
||||
*
|
||||
* Factored out of x86op0f.js, since both opLMSW and opLOADALL are capable of loading a new MSW.
|
||||
* The caller is responsible for assessing the appropriate cycle cost.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} w
|
||||
*/
|
||||
opHelpLMSW: function(w) {
|
||||
/*
|
||||
* This instruction is always allowed to set MSW.PE, but it cannot clear MSW.PE once set;
|
||||
* therefore, we always OR the previous value of MSW.PE into the new value before loading.
|
||||
*/
|
||||
w |= (this.regMSW & X86.MSW.PE);
|
||||
this.regMSW = (this.regMSW & X86.MSW.SET) | (w & ~X86.MSW.SET);
|
||||
/*
|
||||
* Since the 80286 cannot return to real-mode via this instruction, the only transition we
|
||||
* must worry about is to protected-mode. And don't worry, there's no harm calling setProtMode()
|
||||
* if the CPU is already in protected-mode (we could certainly optimize the call out in that
|
||||
* case, but this instruction isn't used frequently enough to warrant it).
|
||||
*/
|
||||
if (this.regMSW & X86.MSW.PE) this.setProtMode(true);
|
||||
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
*/
|
||||
opHelpDIVOverflow: function() {
|
||||
this.setIP(this.opEA - this.segCS.base);
|
||||
/*
|
||||
* TODO: Determine the proper cycle count
|
||||
*/
|
||||
X86Help.opHelpINT.call(this, X86.EXCEPTION.DIV_ERR, null, 2);
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} nFault
|
||||
* @param {number} [nError]
|
||||
* @param {boolean} [fHalt] will halt the CPU if true *and* a Debugger is loaded
|
||||
*/
|
||||
opHelpFault: function(nFault, nError, fHalt) {
|
||||
if (DEBUGGER && this.dbg) {
|
||||
/*
|
||||
* NOTE: By using Debugger.message(), we have the option of setting "m halt on" and halting on messages like this.
|
||||
*/
|
||||
if (this.dbg.messageEnabled(Debugger.MESSAGE.CPU)) {
|
||||
this.dbg.message("Fault 0x" + str.toHexByte(nFault) + (nError != null? " (0x" + str.toHexWord(nError) + ")" : "") + " on opcode 0x" + str.toHexByte(this.bus.getByteDirect(this.regEIP)) + " at " + str.toHexAddr(this.regIP, this.segCS.sel));
|
||||
}
|
||||
if (fHalt) this.haltCPU();
|
||||
}
|
||||
if (this.model >= X86.MODEL_80186) {
|
||||
this.setIP(this.opEA - this.segCS.base);
|
||||
X86Help.opHelpINT.call(this, nFault, nError, 0);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
*/
|
||||
opInvalid: function() {
|
||||
X86Help.opHelpFault.call(this, X86.EXCEPTION.UD_FAULT);
|
||||
this.haltCPU();
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
*/
|
||||
opUndefined: function() {
|
||||
this.setIP(this.opEA - this.segCS.base);
|
||||
this.setError("Undefined opcode 0x" + str.toHexByte(this.bus.getByteDirect(this.regEIP)) + " at " + str.toHexAddr(this.regIP, this.segCS.sel));
|
||||
this.haltCPU();
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = X86Help;
|
||||
15869
modules/pcjs/lib/x86mods.js
Normal file
15869
modules/pcjs/lib/x86mods.js
Normal file
File diff suppressed because it is too large
Load diff
522
modules/pcjs/lib/x86op0f.js
Normal file
522
modules/pcjs/lib/x86op0f.js
Normal file
|
|
@ -0,0 +1,522 @@
|
|||
/**
|
||||
* @fileoverview Implements PCjs 0x0F two-byte opcodes
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Sep-05
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
var X86 = require("./x86");
|
||||
var X86Grps = require("./x86grps");
|
||||
var X86Help = require("./x86help");
|
||||
var X86Mods = require("./x86mods");
|
||||
}
|
||||
|
||||
var X86Op0F = {
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
*
|
||||
* op=0x0F,0x00 (grp6 rm)
|
||||
*/
|
||||
opGRP6: function() {
|
||||
var bModRM = this.getIPByte();
|
||||
if ((bModRM & 0x38) < 0x10) { // possible reg values: 0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38
|
||||
if (EAFUNCS) this.modEAWord = this.modEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOREAD;
|
||||
}
|
||||
X86Mods.aOpModsGrpWord[bModRM].call(this, X86Op0F.aOpGRP6, X86Grps.opGrpNoSrc);
|
||||
if (EAFUNCS) { this.modEAWord = this.modEAWordEnabled; this.setEAWord = this.setEAWordEnabled; }
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
*
|
||||
* op=0x0F,0x01 (grp7 rm)
|
||||
*/
|
||||
opGRP7: function() {
|
||||
var bModRM = this.getIPByte();
|
||||
if (!(bModRM & 0x10)) {
|
||||
if (EAFUNCS) this.modEAWord = this.modEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOREAD;
|
||||
}
|
||||
X86Mods.aOpModsGrpWord[bModRM].call(this, X86Op0F.aOpGRP7, X86Grps.opGrpNoSrc);
|
||||
if (EAFUNCS) { this.modEAWord = this.modEAWordEnabled; this.setEAWord = this.setEAWordEnabled; }
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
*
|
||||
* op=0x0F,0x02 (lar reg,rm)
|
||||
*/
|
||||
opLAR: function() {
|
||||
X86Mods.aOpModsRegWord[this.getIPByte()].call(this, X86Help.opHelpLAR);
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
*
|
||||
* op=0x0F,0x03 (lsl reg,rm)
|
||||
*/
|
||||
opLSL: function() {
|
||||
X86Mods.aOpModsRegWord[this.getIPByte()].call(this, X86Help.opHelpLSL);
|
||||
},
|
||||
/**
|
||||
* opLOADALL()
|
||||
*
|
||||
* From the "Undocumented iAPX 286 Test Instruction" document at http://www.pcjs.org/pubs/pc/reference/intel/80286/loadall/:
|
||||
*
|
||||
* Physical Address (Hex) Associated CPU Register
|
||||
* 800-805 None
|
||||
* 806-807 MSW
|
||||
* 808-815 None
|
||||
* 816-817 TR
|
||||
* 818-819 Flag word
|
||||
* 81A-81B IP
|
||||
* 81C-81D LDT
|
||||
* 81E-81F DS
|
||||
* 820-821 SS
|
||||
* 822-823 CS
|
||||
* 824-825 ES
|
||||
* 826-827 DI
|
||||
* 828-829 SI
|
||||
* 82A-82B BP
|
||||
* 82C-82D SP
|
||||
* 82E-82F BX
|
||||
* 830-831 DX
|
||||
* 832-833 CX
|
||||
* 834-835 AX
|
||||
* 836-83B ES descriptor cache
|
||||
* 83C-841 CS descriptor cache
|
||||
* 842-847 SS descriptor cache
|
||||
* 848-84D DS descriptor cache
|
||||
* 84E-853 GDTR
|
||||
* 854-859 LDT descriptor cache
|
||||
* 85A-85F IDTR
|
||||
* 860-865 TSS descriptor cache
|
||||
*
|
||||
* Oddly, the above document gives two contradictory cycle counts for LOADALL: 190 and 195. I'll go with 195, for
|
||||
* no particular reason.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
*
|
||||
* op=0x0F,0x05 (loadall)
|
||||
*/
|
||||
opLOADALL: function() {
|
||||
if (this.segCS.cpl) {
|
||||
X86Help.opHelpFault.call(this, X86.EXCEPTION.GP_FAULT, 0, true);
|
||||
return;
|
||||
}
|
||||
X86Help.opHelpLMSW.call(this, this.getWord(0x806));
|
||||
this.regDI = this.getWord(0x826);
|
||||
this.regSI = this.getWord(0x828);
|
||||
this.regBP = this.getWord(0x82A);
|
||||
this.regSP = this.getWord(0x82C);
|
||||
this.regBX = this.getWord(0x82E);
|
||||
this.regDX = this.getWord(0x830);
|
||||
this.regCX = this.getWord(0x832);
|
||||
this.regAX = this.getWord(0x834);
|
||||
this.segES.loadDesc6(this.getWord(0x824), 0x836);
|
||||
this.segCS.loadDesc6(this.getWord(0x822), 0x83C);
|
||||
this.segSS.loadDesc6(this.getWord(0x820), 0x842);
|
||||
this.segDS.loadDesc6(this.getWord(0x81E), 0x848);
|
||||
this.setPS(this.getWord(0x818));
|
||||
this.setIP(this.getWord(0x81A));
|
||||
/*
|
||||
* TODO: The bytes at 0x851 and 0x85D "should be zeroes", but do we rely on that, or do we load zeros ourselves?
|
||||
*/
|
||||
this.addrGDT = this.getWord(0x84E) | (this.getWord(0x850) << 16);
|
||||
this.addrGDTLimit = this.addrGDT + this.getWord(0x852);
|
||||
this.segLDT.loadDesc6(this.getWord(0x81C), 0x854);
|
||||
this.addrIDT = this.getWord(0x85A) | (this.getWord(0x85C) << 16);
|
||||
this.addrIDTLimit = this.addrIDT + this.getWord(0x85E);
|
||||
this.segTSS.loadDesc6(this.getWord(0x816), 0x860);
|
||||
this.nStepCycles -= 195;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opSLDT: function(dst, src) {
|
||||
this.nStepCycles -= (2 + (this.regEA < 0? 0 : 1));
|
||||
return this.segLDT.sel;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opSTR: function(dst, src) {
|
||||
this.nStepCycles -= (2 + (this.regEA < 0? 0 : 1));
|
||||
return this.segTSS.sel;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opLLDT: function(dst, src) {
|
||||
if (EAFUNCS) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
this.segLDT.load(dst);
|
||||
this.nStepCycles -= (17 + (this.regEA < 0? 0 : 2));
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opLTR: function(dst, src) {
|
||||
if (EAFUNCS) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
this.segTSS.load(dst);
|
||||
this.nStepCycles -= (17 + (this.regEA < 0? 0 : 2));
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opVERR: function(dst, src) {
|
||||
if (EAFUNCS) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
/*
|
||||
* Currently, segVER.load() will return an error only if the selector is beyond the bounds of the
|
||||
* descriptor table or the descriptor is not for a segment.
|
||||
*/
|
||||
this.nStepCycles -= (14 + (this.regEA < 0? 0 : 2));
|
||||
if (this.segVER.load(dst, true) >= 0) {
|
||||
/*
|
||||
* Verify that this is a readable segment; that is, of these four combinations (code+readable,
|
||||
* code+nonreadable, data+writeable, date+nonwriteable), make sure we're not the second combination.
|
||||
*/
|
||||
if ((this.segVER.acc & (X86.DESC.ACC.TYPE.READABLE | X86.DESC.ACC.TYPE.CODE)) != X86.DESC.ACC.TYPE.CODE) {
|
||||
/*
|
||||
* For VERR, if the code segment is readable and conforming, the descriptor privilege level
|
||||
* (DPL) can be any value.
|
||||
*
|
||||
* Otherwise, DPL must be greater than or equal to (have less or the same privilege as) both the
|
||||
* current privilege level and the selector's RPL.
|
||||
*/
|
||||
if (this.segVER.dpl >= this.segCS.cpl && this.segVER.dpl >= (dst & X86.SEL.RPL) ||
|
||||
(this.segVER.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING_EXECONLY) == X86.DESC.ACC.TYPE.CODE_CONFORMING_EXECONLY) {
|
||||
this.setZF();
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.clearZF();
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opVERW: function(dst, src) {
|
||||
if (EAFUNCS) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
/*
|
||||
* Currently, segVER.load() will return an error only if the selector is beyond the bounds of the
|
||||
* descriptor table or the descriptor is not for a segment.
|
||||
*/
|
||||
this.nStepCycles -= (14 + (this.regEA < 0? 0 : 2));
|
||||
if (this.segVER.load(dst, true) >= 0) {
|
||||
/*
|
||||
* Verify that this is a writeable data segment
|
||||
*/
|
||||
if ((this.segVER.acc & (X86.DESC.ACC.TYPE.WRITEABLE | X86.DESC.ACC.TYPE.CODE)) == X86.DESC.ACC.TYPE.WRITEABLE) {
|
||||
/*
|
||||
* DPL must be greater than or equal to (have less or the same privilege as) both the current
|
||||
* privilege level and the selector's RPL.
|
||||
*/
|
||||
if (this.segVER.dpl >= this.segCS.cpl && this.segVER.dpl >= (dst & X86.SEL.RPL)) {
|
||||
this.setZF();
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.clearZF();
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opSGDT: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
X86Help.opInvalid.call(this);
|
||||
} else {
|
||||
/*
|
||||
* We don't need to setWord() the first word of the operand, because the ModRM group decoder that calls
|
||||
* us does that automatically with the value we return (dst).
|
||||
*/
|
||||
dst = this.addrGDTLimit - this.addrGDT;
|
||||
this.setWord(this.regEA + 2, this.addrGDT);
|
||||
/*
|
||||
* We previously left the 6th byte of the target operand "undefined". But it turns out we have to set
|
||||
* it to *something*, because there's processor detection in PC-DOS 7.0 (at least in the SETUP portion)
|
||||
* that looks like this:
|
||||
*
|
||||
* 145E:4B84 9C PUSHF
|
||||
* 145E:4B85 55 PUSH BP
|
||||
* 145E:4B86 8BEC MOV BP,SP
|
||||
* 145E:4B88 B80000 MOV AX,0000
|
||||
* 145E:4B8B 50 PUSH AX
|
||||
* 145E:4B8C 9D POPF
|
||||
* 145E:4B8D 9C PUSHF
|
||||
* 145E:4B8E 58 POP AX
|
||||
* 145E:4B8F 2500F0 AND AX,F000
|
||||
* 145E:4B92 3D00F0 CMP AX,F000
|
||||
* 145E:4B95 7511 JNZ 4BA8
|
||||
* 145E:4BA8 C8060000 ENTER 0006,00
|
||||
* 145E:4BAC 0F0146FA SGDT [BP-06]
|
||||
* 145E:4BB0 807EFFFF CMP [BP-01],FF
|
||||
* 145E:4BB4 C9 LEAVE
|
||||
* 145E:4BB5 BA8603 MOV DX,0386
|
||||
* 145E:4BB8 7503 JNZ 4BBD
|
||||
* 145E:4BBA BA8602 MOV DX,0286
|
||||
* 145E:4BBD 89163004 MOV [0430],DX
|
||||
* 145E:4BC1 5D POP BP
|
||||
* 145E:4BC2 9D POPF
|
||||
* 145E:4BC3 CB RETF
|
||||
*
|
||||
* This code is expecting SGDT on an 80286 to set the 6th "undefined" byte to 0xFF. So we use setWord()
|
||||
* instead of setByte() and force the upper byte to 0xFF.
|
||||
*
|
||||
* TODO: Remove the 0xFF00 below on post-80286 processors; also, determine whether this behavior is unique to real-mode.
|
||||
*/
|
||||
this.setWord(this.regEA + 4, 0xFF00 | (this.addrGDT >> 16));
|
||||
this.nStepCycles -= 11;
|
||||
}
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opSIDT: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
X86Help.opInvalid.call(this);
|
||||
} else {
|
||||
/*
|
||||
* We don't need to setWord() the first word of the operand, because the ModRM group decoder that calls
|
||||
* us does that automatically with the value we return (dst).
|
||||
*/
|
||||
dst = this.addrIDTLimit - this.addrIDT;
|
||||
this.setWord(this.regEA + 2, this.addrIDT);
|
||||
/*
|
||||
* As with SGDT, the 6th byte is technically "undefined" on an 80286, but we now set it to 0xFF, for the
|
||||
* same reasons discussed in SGDT (above).
|
||||
*
|
||||
* TODO: Remove the 0xFF00 below on post-80286 processors; also, determine whether this behavior is unique to real-mode.
|
||||
*/
|
||||
this.setWord(this.regEA + 4, 0xFF00 | (this.addrIDT >> 16));
|
||||
this.nStepCycles -= 12;
|
||||
}
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* opLGDT(dst, src)
|
||||
*
|
||||
* The 80286 LGDT instruction expects a 40-bit operand: a 16-bit limit, followed by a 24-bit address;
|
||||
* the ModRM decoder has already supplied the first word of the operand (in dst), which corresponds to the
|
||||
* limit, so we must fetch the remaining 24 bits ourselves.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opLGDT: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
X86Help.opInvalid.call(this);
|
||||
} else {
|
||||
this.addrGDT = this.getWord(this.regEA + 2) | (this.getByte(this.regEA + 4) << 16);
|
||||
this.addrGDTLimit = this.addrGDT + dst;
|
||||
if (EAFUNCS) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
this.nStepCycles -= 11;
|
||||
}
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* opLIDT(dst, src)
|
||||
*
|
||||
* The 80286 LIDT instruction expects a 40-bit operand: a 16-bit limit, followed by a 24-bit address;
|
||||
* the ModRM decoder has already supplied the first word of the operand (in dst), which corresponds to the
|
||||
* limit, so we must fetch the remaining 24 bits ourselves.
|
||||
*
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opLIDT: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
X86Help.opInvalid.call(this);
|
||||
} else {
|
||||
this.addrIDT = this.getWord(this.regEA + 2) | (this.getByte(this.regEA + 4) << 16);
|
||||
this.addrIDTLimit = this.addrIDT + dst;
|
||||
if (EAFUNCS) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
this.nStepCycles -= 12;
|
||||
}
|
||||
return dst;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opSMSW: function(dst, src) {
|
||||
this.nStepCycles -= (2 + (this.regEA < 0? 0 : 1));
|
||||
return this.regMSW;
|
||||
},
|
||||
/**
|
||||
* @this {X86CPU}
|
||||
* @param {number} dst
|
||||
* @param {number} src (null)
|
||||
* @return {number}
|
||||
*/
|
||||
opLMSW: function(dst, src) {
|
||||
X86Help.opHelpLMSW.call(this, dst);
|
||||
this.nStepCycles -= (this.regEA < 0? 3 : 6);
|
||||
if (EAFUNCS) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE;
|
||||
return dst;
|
||||
}
|
||||
};
|
||||
|
||||
X86Op0F.aOps0F = [
|
||||
X86Op0F.opGRP6, X86Op0F.opGRP7, X86Op0F.opLAR, X86Op0F.opLSL, // 0x00-0x03
|
||||
X86Help.opUndefined, X86Op0F.opLOADALL, X86Help.opUndefined, X86Help.opUndefined, // 0x04-0x07
|
||||
/*
|
||||
* On all processors (except the 8086/8088, of course), 0x0F,0x0B is also referred to as "UD2": an
|
||||
* instruction guaranteed to raise a #UD (Invalid Opcode) exception (INT 0x06) on all future x86 processors.
|
||||
*/
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opInvalid, // 0x08-0x0B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x0C-0x0F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x10-0x13
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x14-0x17
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x18-0x1B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x1C-0x1F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x20-0x23
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x24-0x27
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x28-0x2B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x2C-0x2F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x30-0x33
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x34-0x37
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x38-0x3B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x3C-0x3F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x40-0x43
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x44-0x47
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x48-0x4B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x4C-0x4F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x50-0x53
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x54-0x57
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x58-0x5B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x5C-0x5F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x60-0x63
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x64-0x67
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x68-0x6B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x6C-0x6F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x70-0x73
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x74-0x77
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x78-0x7B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x7C-0x7F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x80-0x83
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x84-0x87
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x88-0x8B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x8C-0x8F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x90-0x93
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x94-0x97
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x98-0x9B
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x9C-0x9F
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xA0-0xA3
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xA4-0xA7
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xA8-0xAB
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xAC-0xAF
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xB0-0xB3
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xB4-0xB7
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xB8-0xBB
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xBC-0xBF
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xC0-0xC3
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xC4-0xC7
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xC8-0xCB
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xCC-0xCF
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xD0-0xD3
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xD4-0xD7
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xD8-0xDB
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xDC-0xDF
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xE0-0xE3
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xE4-0xE7
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xE8-0xEB
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xEC-0xEF
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xF0-0xF3
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xF4-0xF7
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0xF8-0xFB
|
||||
X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined // 0xFC-0xFF
|
||||
];
|
||||
|
||||
/*
|
||||
* These instruction groups are not as orthogonal as the original 8086/8088 groups (GRP1 through GRP4): some of
|
||||
* the instructions in GRP6 and GRP7 only read their dst operand (eg, LLDT), which means the ModRM helper function
|
||||
* must insure that setEAWord() is disabled, while others only write their dst operand (eg, SLDT), which means that
|
||||
* getEAWord() should be disabled *prior* to calling the ModRM helper function. This latter case requires that
|
||||
* we decode the reg field of the ModRM byte before dispatching.
|
||||
*/
|
||||
X86Op0F.aOpGRP6Prot = [
|
||||
X86Op0F.opSLDT, X86Op0F.opSTR, X86Op0F.opLLDT, X86Op0F.opLTR, // 0x0F,0x00(reg=0x0-0x3)
|
||||
X86Op0F.opVERR, X86Op0F.opVERW, X86Grps.opGrpUndefined, X86Grps.opGrpUndefined // 0x0F,0x00(reg=0x4-0x7)
|
||||
];
|
||||
|
||||
X86Op0F.aOpGRP6Real = [
|
||||
X86Grps.opGrpInvalid, X86Grps.opGrpInvalid, X86Grps.opGrpInvalid, X86Grps.opGrpInvalid, // 0x0F,0x00(reg=0x0-0x3)
|
||||
X86Grps.opGrpInvalid, X86Grps.opGrpInvalid, X86Grps.opGrpUndefined, X86Grps.opGrpUndefined // 0x0F,0x00(reg=0x4-0x7)
|
||||
];
|
||||
|
||||
/*
|
||||
* setProtMode() will ensure that aOpGRP6 is set to the appropriate group, but it doesn't hurt to statically
|
||||
* initialize to its real-mode default, either.
|
||||
*/
|
||||
X86Op0F.aOpGRP6 = X86Op0F.aOpGRP6Real;
|
||||
|
||||
/*
|
||||
* Unlike GRP6, GRP7 does not require separate real-mode and protected-mode dispatch tables, because all GRP7
|
||||
* instructions are valid in both modes.
|
||||
*/
|
||||
X86Op0F.aOpGRP7 = [
|
||||
X86Op0F.opSGDT, X86Op0F.opSIDT, X86Op0F.opLGDT, X86Op0F.opLIDT, // 0x0F,0x01(reg=0x0-0x3)
|
||||
X86Op0F.opSMSW, X86Grps.opGrpUndefined, X86Op0F.opLMSW, X86Grps.opGrpUndefined // 0x0F,0x01(reg=0x4-0x7)
|
||||
];
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = X86Op0F;
|
||||
3456
modules/pcjs/lib/x86opxx.js
Normal file
3456
modules/pcjs/lib/x86opxx.js
Normal file
File diff suppressed because it is too large
Load diff
420
modules/pcjs/lib/x86seg.js
Normal file
420
modules/pcjs/lib/x86seg.js
Normal file
|
|
@ -0,0 +1,420 @@
|
|||
/**
|
||||
* @fileoverview Implements PCjs X86 Segment objects
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-Sep-10
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var X86 = require("./x86");
|
||||
var X86Help = require("./x86help");
|
||||
}
|
||||
|
||||
/**
|
||||
* X86Seg(cpu, sName)
|
||||
*
|
||||
* @constructor
|
||||
* @param {X86CPU} cpu
|
||||
* @param {string} [sName] segment name
|
||||
* @param {boolean} [fProt] true if segment register used exclusively in protected-mode
|
||||
*/
|
||||
function X86Seg(cpu, sName, fProt)
|
||||
{
|
||||
this.cpu = cpu;
|
||||
this.sel = 0;
|
||||
this.base = 0;
|
||||
this.limit = 0xffff;
|
||||
this.acc = 0;
|
||||
this.fCode = (sName == "CS");
|
||||
this.sName = sName;
|
||||
this.cpl = 0;
|
||||
this.dpl = 0;
|
||||
this.updateAccess(fProt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* loadReal(sel, fSuppress)
|
||||
*
|
||||
* The default segment load() function for real-mode.
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} sel
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} base address of selected segment, or -1 if error
|
||||
*/
|
||||
X86Seg.loadReal = function loadReal(sel, fSuppress)
|
||||
{
|
||||
this.sel = sel;
|
||||
this.limit = 0xffff;
|
||||
this.cpl = this.dpl = 0;
|
||||
return this.base = sel << 4;
|
||||
};
|
||||
|
||||
/**
|
||||
* loadProt(sel, fSuppress)
|
||||
*
|
||||
* This replaces the segment's default load() function whenever the segment is notified via updateAccess() by the
|
||||
* CPU's setProtMode() that the processor is now in protected-mode.
|
||||
*
|
||||
* Segments in protected-mode are referenced by selectors, which are indexes into descriptor tables (GDT, LDT, IDT) whose
|
||||
* descriptors are 4-word (8-byte) entries:
|
||||
*
|
||||
* word 0: segment limit (0-15)
|
||||
* word 1: base address low
|
||||
* word 2: base address high (0-7), segment type (8-11), descriptor type (12), DPL (13-14), present bit (15)
|
||||
* word 3: used only on 80386 and up (should be set to zero for upward compatibility)
|
||||
*
|
||||
* See X86.DESC for offset and bit definitions.
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} sel
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} base address of selected segment, or -1 if error
|
||||
*/
|
||||
X86Seg.loadProt = function loadProt(sel, fSuppress)
|
||||
{
|
||||
var addrDT;
|
||||
var addrDTLimit;
|
||||
if (!(sel & X86.SEL.LDT)) {
|
||||
addrDT = this.cpu.addrGDT;
|
||||
addrDTLimit = this.cpu.addrGDTLimit;
|
||||
} else {
|
||||
addrDT = this.cpu.segLDT.base;
|
||||
addrDTLimit = this.cpu.segLDT.limit;
|
||||
}
|
||||
var addrDesc = addrDT + (sel & X86.SEL.MASK);
|
||||
if (addrDesc + 7 <= addrDTLimit) {
|
||||
/*
|
||||
* TODO: This is only the first of many steps toward accurately counting cycles in protected mode;
|
||||
* I simply noted that "POP segreg" takes 5 cycles in real mode and 20 in protected mode, so I'm
|
||||
* starting with a 15-cycle difference. Obviously the difference will be much greater when the load fails.
|
||||
*/
|
||||
this.cpu.nStepCycles -= 15;
|
||||
return this.loadDesc8(sel, addrDesc);
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* checkReadReal(off, cb, fSuppress)
|
||||
*
|
||||
* TODO: Invoke X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT) if off is 0xffff and cb is 1;
|
||||
* also, whether or not the opHelpFault() call should include an error code, since this is happening in real-mode.
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of extra bytes to check (0 or 1)
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} corresponding physical address if valid, -1 if not
|
||||
*/
|
||||
X86Seg.checkReadReal = function checkReadReal(off, cb, fSuppress)
|
||||
{
|
||||
return this.base + off;
|
||||
};
|
||||
|
||||
/**
|
||||
* checkWriteReal(off, cb, fSuppress)
|
||||
*
|
||||
* TODO: Invoke X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT) if off is 0xffff and cb is 1;
|
||||
* also, whether or not the opHelpFault() call should include an error code, since this is happening in real-mode.
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of extra bytes to check (0 or 1)
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} corresponding physical address if valid, -1 if not
|
||||
*/
|
||||
X86Seg.checkWriteReal = function checkWriteReal(off, cb, fSuppress)
|
||||
{
|
||||
return this.base + off;
|
||||
};
|
||||
|
||||
/**
|
||||
* checkReadProtEnabled(off, cb, fSuppress)
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of extra bytes to check (0 or 1)
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} corresponding physical address if valid, -1 if not
|
||||
*/
|
||||
X86Seg.checkReadProtEnabled = function checkReadProtEnabled(off, cb, fSuppress)
|
||||
{
|
||||
if (off + cb <= this.limit) {
|
||||
return this.base + off;
|
||||
}
|
||||
return X86Seg.checkReadProtDisabled.call(this, off, cb, fSuppress);
|
||||
};
|
||||
|
||||
/**
|
||||
* checkReadProtDisabled(off, cb, fSuppress)
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of extra bytes to check (0 or 1)
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} corresponding physical address if valid, -1 if not
|
||||
*/
|
||||
X86Seg.checkReadProtDisabled = function checkReadProtDisabled(off, cb, fSuppress)
|
||||
{
|
||||
if (!fSuppress) {
|
||||
X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, 0);
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* checkWriteProtEnabled(off, cb, fSuppress)
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of extra bytes to check (0 or 1)
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} corresponding physical address if valid, -1 if not
|
||||
*/
|
||||
X86Seg.checkWriteProtEnabled = function checkWriteProtEnabled(off, cb, fSuppress)
|
||||
{
|
||||
if (off + cb <= this.limit) {
|
||||
return this.base + off;
|
||||
}
|
||||
return X86Seg.checkWriteProtDisabled.call(this, off, cb, fSuppress);
|
||||
};
|
||||
|
||||
/**
|
||||
* checkWriteProtDisabled(off, cb, fSuppress)
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} off is a segment-relative offset
|
||||
* @param {number} cb is number of extra bytes to check (0 or 1)
|
||||
* @param {boolean} [fSuppress] is true to suppress any errors
|
||||
* @return {number} corresponding physical address if valid, -1 if not
|
||||
*/
|
||||
X86Seg.checkWriteProtDisabled = function checkWriteProtDisabled(off, cb, fSuppress)
|
||||
{
|
||||
if (!fSuppress) {
|
||||
X86Help.opHelpFault.call(this.cpu, X86.EXCEPTION.GP_FAULT, 0);
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/*
|
||||
* Object methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* loadDesc6(sel, addrDesc)
|
||||
*
|
||||
* Used to load a protected-mode selector that refers to a 6-byte descriptor "cache" (LOADALL) entry:
|
||||
*
|
||||
* word 0: base address low
|
||||
* word 1: base address high (0-7), segment type (8-11), descriptor type (12), DPL (13-14), present bit (15)
|
||||
* word 2: segment limit (0-15)
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} sel is the selector
|
||||
* @param {number} addrDesc is the offset
|
||||
* @return {number} base address of selected segment, or -1 if error
|
||||
*/
|
||||
X86Seg.prototype.loadDesc6 = function(sel, addrDesc)
|
||||
{
|
||||
var acc = this.cpu.getWord(addrDesc + 2);
|
||||
var base = this.cpu.getWord(addrDesc + 0) | ((acc & 0xff) << 16);
|
||||
var limit = this.cpu.getWord(addrDesc + 4);
|
||||
|
||||
if (DEBUG) {
|
||||
this.cpu.messageDebugger("loadDesc6(" + this.sName + "): base=" + str.toHex(base) + " limit=" + str.toHexWord(limit) + " acc=" + str.toHexWord(acc));
|
||||
}
|
||||
|
||||
this.sel = sel;
|
||||
this.base = base;
|
||||
this.limit = limit;
|
||||
this.acc = acc & X86.DESC.ACC.MASK;
|
||||
this.updateAccess();
|
||||
|
||||
return base;
|
||||
};
|
||||
|
||||
/**
|
||||
* loadDesc8(sel, addrDesc)
|
||||
*
|
||||
* Used to load a protected-mode selector that refers to an 8-byte descriptor table (GDT, LDT, IDT) entry:
|
||||
*
|
||||
* word 0: segment limit (0-15)
|
||||
* word 1: base address low
|
||||
* word 2: base address high (0-7), segment type (8-11), descriptor type (12), DPL (13-14), present bit (15)
|
||||
* word 3: used only on 80386 and up (should be set to zero for upward compatibility)
|
||||
*
|
||||
* See X86.DESC for offset and bit definitions.
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} sel is the selector
|
||||
* @param {number} addrDesc is the offset
|
||||
* @return {number} base address of selected segment, or -1 if error
|
||||
*/
|
||||
X86Seg.prototype.loadDesc8 = function(sel, addrDesc)
|
||||
{
|
||||
var limit = this.cpu.getWord(addrDesc + X86.DESC.LIMIT.OFFSET);
|
||||
var acc = this.cpu.getWord(addrDesc + X86.DESC.ACC.OFFSET);
|
||||
var base = this.cpu.getWord(addrDesc + X86.DESC.BASE.OFFSET) | ((acc & X86.DESC.ACC.BASE1623) << 16);
|
||||
var ext = (DEBUG? this.cpu.getWord(addrDesc + X86.DESC.EXT.OFFSET) : 0);
|
||||
|
||||
if (DEBUG) {
|
||||
this.cpu.messageDebugger("loadDesc8(" + this.sName + "): base=" + str.toHex(base) + " limit=" + str.toHexWord(limit) + " acc=" + str.toHexWord(acc) + (ext? " ext=" + str.toHexWord(ext) : ""));
|
||||
Component.assert(!ext);
|
||||
}
|
||||
|
||||
/*
|
||||
* For LSL (which uses fSuppress), we must support X86.DESC.ACC.TYPE.SEG as well as TSS and LDT.
|
||||
*/
|
||||
var accType;
|
||||
if ((acc & X86.DESC.ACC.TYPE.SEG) || (accType = (acc & X86.DESC.ACC.TYPE.MASK)) && accType <= X86.DESC.ACC.TYPE.TSS_BUSY) {
|
||||
this.sel = sel;
|
||||
this.base = base;
|
||||
this.limit = limit;
|
||||
/*
|
||||
* Note that bits 0-7 of acc will usually contain the BASE1623 bits from the descriptor entry,
|
||||
* but it doesn't matter, because the only acc bits we pay attention to are bits 8-15; however,
|
||||
* to keep things tidy, we zero bits 0-7. Perhaps we'll find other (internal) uses for those bits.
|
||||
*/
|
||||
this.acc = acc & X86.DESC.ACC.MASK;
|
||||
this.type = acc & X86.DESC.ACC.TYPE.MASK;
|
||||
this.updateAccess();
|
||||
}
|
||||
else {
|
||||
base = -1;
|
||||
}
|
||||
return base;
|
||||
};
|
||||
|
||||
/**
|
||||
* setBase(addr)
|
||||
*
|
||||
* This is used in unusual situations where the base must be set independently; normally, the base
|
||||
* is set according to the selector provided to load(), but there are a few cases where setBase() is
|
||||
* required (eg, in resetRegs() where the 80286 wants the real-mode CS selector to be 0xF000 but the
|
||||
* CS base must be 0xFF0000, and LOADALL).
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {number} addr
|
||||
*/
|
||||
X86Seg.prototype.setBase = function(addr)
|
||||
{
|
||||
this.base = addr;
|
||||
};
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* Early versions of PCjs saved only segment selectors, since that's all that mattered in real-mode;
|
||||
* newer versions need to save/restore the entire segment object.
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @return {Array}
|
||||
*/
|
||||
X86Seg.prototype.save = function()
|
||||
{
|
||||
return [this.sel, this.base, this.limit, this.acc, this.fCode, this.sName, this.cpl, this.dpl];
|
||||
};
|
||||
|
||||
/**
|
||||
* restore(a)
|
||||
*
|
||||
* Early versions of PCjs saved only segment selectors, since that's all that mattered in real-mode;
|
||||
* newer versions need to save/restore the entire segment object.
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {Array|number} a
|
||||
*/
|
||||
X86Seg.prototype.restore = function(a)
|
||||
{
|
||||
if (typeof a == "number") {
|
||||
this.load(a);
|
||||
} else {
|
||||
this.sel = a[0];
|
||||
this.base = a[1];
|
||||
this.limit = a[2];
|
||||
this.acc = a[3];
|
||||
this.fCode = a[4];
|
||||
this.sName = a[5];
|
||||
this.cpl = a[6];
|
||||
this.dpl = a[7];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* updateAccess(fProt)
|
||||
*
|
||||
* Ensures that the segment register's access (ie, load and check methods) matches the specified (or current)
|
||||
* operating mode (real or protected).
|
||||
*
|
||||
* @this {X86Seg}
|
||||
* @param {boolean} [fProt] true for protected-mode access, false for real-mode access, undefined for current mode
|
||||
* @return {boolean}
|
||||
*/
|
||||
X86Seg.prototype.updateAccess = function(fProt)
|
||||
{
|
||||
if (fProt === undefined) {
|
||||
fProt = !!(this.cpu.regMSW & X86.MSW.PE);
|
||||
}
|
||||
if (fProt) {
|
||||
this.load = X86Seg.loadProt;
|
||||
this.checkRead = X86Seg.checkReadProtEnabled;
|
||||
this.checkWrite = X86Seg.checkWriteProtEnabled;
|
||||
if (this.acc & X86.DESC.ACC.TYPE.SEG) {
|
||||
/*
|
||||
* If the READABLE bit of CODE_READABLE is not set, then disallow reads
|
||||
*/
|
||||
if ((this.acc & X86.DESC.ACC.TYPE.CODE_READABLE) == X86.DESC.ACC.TYPE.CODE_EXECONLY) {
|
||||
this.checkWrite = X86Seg.checkReadProtDisabled;
|
||||
}
|
||||
/*
|
||||
* If the CODE bit is set, or the the WRITEABLE bit is not set, then disallow writes
|
||||
*/
|
||||
if ((this.acc & X86.DESC.ACC.TYPE.CODE) || !(this.acc & X86.DESC.ACC.TYPE.WRITEABLE)) {
|
||||
this.checkWrite = X86Seg.checkWriteProtDisabled;
|
||||
}
|
||||
}
|
||||
this.cpl = this.sel & X86.SEL.RPL;
|
||||
this.dpl = (this.acc & X86.DESC.ACC.DPL.MASK) >> X86.DESC.ACC.DPL.SHIFT;
|
||||
} else {
|
||||
this.load = X86Seg.loadReal;
|
||||
this.checkRead = X86Seg.checkReadReal;
|
||||
this.checkWrite = X86Seg.checkWriteReal;
|
||||
this.cpl = this.dpl = 0;
|
||||
}
|
||||
return fProt;
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = X86Seg;
|
||||
15
modules/pcjs/package.json
Normal file
15
modules/pcjs/package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "PCjs",
|
||||
"version": "1.15.9",
|
||||
"description": "IBM PC Emulator",
|
||||
"author": "Jeff Parsons <Jeff@pcjs.org>",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "GPLv3",
|
||||
"url": "http://www.gnu.org/licenses/gpl.html"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"example": "./bin/pcjs"
|
||||
}
|
||||
}
|
||||
14
modules/pcjs/templates/README.md
Normal file
14
modules/pcjs/templates/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
PCjs Templates
|
||||
===
|
||||
Template folders contain a variety of XML and HTML templates and supporting files, including:
|
||||
|
||||
- DTD files (Document Type Definitions)
|
||||
- XSD files (XML schemas -- eventually)
|
||||
- XSL files (XML stylesheets)
|
||||
- CSS files (stylesheets that the XSL files rely upon)
|
||||
- HTML files (HTML fragments used to generate part or all of a web page)
|
||||
|
||||
[*components.xsl*](components.xsl) transforms all the elements of a machine XML file into an HTML fragment
|
||||
that includes a series of **DIV** tags with corresponding *id* and *data-value* attributes that allow our
|
||||
JavaScript components to bind themselves to visual elements (eg, virtual screen, virtual keyboard, control
|
||||
panel) on a web page.
|
||||
121
modules/pcjs/templates/components.css
Normal file
121
modules/pcjs/templates/components.css
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
@CHARSET "UTF-8";
|
||||
/* @author Jeff Parsons (@jeffpar)
|
||||
@website http://www.pcjs.org/
|
||||
@created 2013-05-05
|
||||
@modified 2014-03-12
|
||||
@license http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
*:not(input,textarea) {
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.pcjs-embed {
|
||||
}
|
||||
.pcjs-embed:after {
|
||||
clear:both;
|
||||
}
|
||||
.pcjs-name {
|
||||
clear: both;
|
||||
font-weight: bold;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.pcjs-canvas {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.pcjs-container {
|
||||
color: #000000;
|
||||
position: relative;
|
||||
}
|
||||
.pcjs-label {
|
||||
font-size: small;
|
||||
line-height: 19px;
|
||||
vertical-align: middle;
|
||||
float: left;
|
||||
font-family: "Lucida Console", monospace;
|
||||
}
|
||||
.pcjs-control textarea {
|
||||
font-family: Monaco, monospace;
|
||||
font-size: x-small;
|
||||
}
|
||||
.pcjs-fieldset {
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.pcjs-flag {
|
||||
font-family: "Lucida Console", monospace;
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
line-height: 19px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.pcjs-register {
|
||||
font-family: "Lucida Console", monospace;
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
line-height: 19px;
|
||||
vertical-align: middle;
|
||||
border: 1px solid black;
|
||||
}
|
||||
.pcjs-switches {
|
||||
float: left;
|
||||
}
|
||||
.pcjs-bitBucket {
|
||||
float: left;
|
||||
width: 19px;
|
||||
height: 38px;
|
||||
}
|
||||
.pcjs-bitCell {
|
||||
float: left;
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
margin-right: -1px;
|
||||
margin-bottom: -1px;
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
line-height: 19px; /* the equivalent of "vertical-align: middle" for single-line elements */
|
||||
}
|
||||
.pcjs-bitCellLeft {
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
.pcjs-bitLabel {
|
||||
font-size: xx-small;
|
||||
text-align: center;
|
||||
}
|
||||
.pcjs-description, .pcjs-status {
|
||||
font-size: x-small;
|
||||
line-height: 2em;
|
||||
}
|
||||
.pcjs-key {
|
||||
border: 1px solid black;
|
||||
font-size: x-small;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
height: 34px;
|
||||
line-height: 34px; /* the equivalent of "vertical-align: middle" for single-line elements */
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.pcjs-reference {
|
||||
float: left;
|
||||
font-size: x-small;
|
||||
}
|
||||
.pcjs-reference a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.pcjs-copyright {
|
||||
float: right;
|
||||
font-size: x-small;
|
||||
}
|
||||
.pcjs-copyright a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.pcjs-textarea {
|
||||
width: 100% !important;
|
||||
}
|
||||
.pcjs-registers {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
974
modules/pcjs/templates/components.xsl
Normal file
974
modules/pcjs/templates/components.xsl
Normal file
|
|
@ -0,0 +1,974 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- author="Jeff Parsons (@jeffpar)" website="http://www.pcjs.org/" created="2012-05-05" modified="2014-02-23" license="http://www.gnu.org/licenses/gpl.html" -->
|
||||
<!DOCTYPE xsl:stylesheet [
|
||||
<!-- XSLT understands these entities only: lt, gt, apos, quot, and amp. Other required entities may be defined below (see http://www.pcjs.org/modules/shared/templates/entities.dtd). -->
|
||||
]>
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
<xsl:param name="rootDir" select="''"/>
|
||||
<xsl:param name="generator" select="'client'"/>
|
||||
|
||||
<xsl:variable name="MACHINECLASS">pc</xsl:variable>
|
||||
<xsl:variable name="APPCLASS">pcjs</xsl:variable>
|
||||
<xsl:variable name="APPVERSION">1.x.x</xsl:variable>
|
||||
<xsl:variable name="SITEHOST">www.pcjs.org</xsl:variable>
|
||||
|
||||
<xsl:template name="componentStyles">
|
||||
<link rel="stylesheet" type="text/css" href="/versions/{$APPCLASS}/{$APPVERSION}/components.css"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="componentScripts">
|
||||
<xsl:param name="component"/>
|
||||
<script type="text/javascript" src="/versions/{$APPCLASS}/{$APPVERSION}/{$component}.js"> </script>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="componentIncludes">
|
||||
<xsl:param name="component"/>
|
||||
<xsl:call-template name="componentScripts"><xsl:with-param name="component" select="$component"/></xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="machine">
|
||||
<xsl:param name="href">/devices/pc/machine/5150/mda/64kb/machine.xml</xsl:param>
|
||||
<xsl:param name="state" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="$href"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/machine">
|
||||
<xsl:with-param name="machineState" select="$state"/>
|
||||
</xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="machine[@ref]">
|
||||
<xsl:param name="machineState" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/machine">
|
||||
<xsl:with-param name="machine" select="@id"/>
|
||||
<xsl:with-param name="machineState">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$machineState != ''"><xsl:value-of select="$machineState"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="@state"/></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:with-param>
|
||||
</xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="machine[not(@ref)]">
|
||||
<xsl:param name="machine"><xsl:value-of select="@id"/></xsl:param>
|
||||
<xsl:param name="machineState" select="''"/>
|
||||
<xsl:variable name="machineStyle">
|
||||
<xsl:if test="@float">float:<xsl:value-of select="@float"/></xsl:if>
|
||||
</xsl:variable>
|
||||
<div id="{$machine}" class="machine {@class}js" style="{$machineStyle}">
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="machineState">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$machineState != ''"><xsl:value-of select="$machineState"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="@state"/></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:with-param>
|
||||
<xsl:with-param name="component" select="'machine'"/>
|
||||
<xsl:with-param name="class"><xsl:value-of select="@class"/>js</xsl:with-param>
|
||||
<xsl:with-param name="parms"><xsl:if test="@parms">,<xsl:value-of select="@parms"/></xsl:if></xsl:with-param>
|
||||
<xsl:with-param name="url"><xsl:value-of select="@url"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="component[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
</xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="component[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class" select="@class"/>
|
||||
<xsl:with-param name="parms"><xsl:if test="@parms">,<xsl:value-of select="@parms"/></xsl:if></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="component">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:param name="machineState" select="''"/>
|
||||
<xsl:param name="component" select="name(.)"/>
|
||||
<xsl:param name="class" select="''"/>
|
||||
<xsl:param name="parms" select="''"/>
|
||||
<xsl:param name="url" select="''"/>
|
||||
<xsl:variable name="id">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$component = 'machine'"><xsl:value-of select="$machine"/>.machine</xsl:when>
|
||||
<xsl:when test="$machine != '' and @id"><xsl:value-of select="$machine"/>.<xsl:value-of select="@id"/></xsl:when>
|
||||
<xsl:when test="$machine != ''"><xsl:value-of select="$machine"/>.<xsl:value-of select="$component"/></xsl:when>
|
||||
<xsl:when test="@id"><xsl:value-of select="@id"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="$component"/></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="componentURL">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$component = 'machine'">url:'<xsl:value-of select="$url"/>'</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="name">
|
||||
<xsl:choose>
|
||||
<xsl:when test="name"><xsl:value-of select="name"/></xsl:when>
|
||||
<xsl:when test="@name"><xsl:value-of select="@name"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="comment">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@comment">,comment:'<xsl:value-of select="@comment"/>'</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="border">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@border = '1'">border:1px solid black;border-radius:15px;</xsl:when>
|
||||
<xsl:when test="@border">border:<xsl:value-of select="@border"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="left">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@left">left:<xsl:value-of select="@left"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="top">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@top">top:<xsl:value-of select="@top"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="width">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@width">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$left != '' or $top != ''">width:<xsl:value-of select="@width"/>;</xsl:when>
|
||||
<xsl:otherwise>width:auto;max-width:<xsl:value-of select="@width"/>;</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="height">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@height">height:<xsl:value-of select="@height"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="padding">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@padding">padding:<xsl:value-of select="@padding"/>;</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:if test="@padtop">padding-top:<xsl:value-of select="@padtop"/>;</xsl:if>
|
||||
<xsl:if test="@padright">padding-right:<xsl:value-of select="@padright"/>;</xsl:if>
|
||||
<xsl:if test="@padbottom">padding-bottom:<xsl:value-of select="@padbottom"/>;</xsl:if>
|
||||
<xsl:if test="@padleft">padding-left:<xsl:value-of select="@padleft"/>;</xsl:if>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="pos">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@pos = 'left'">float:left;</xsl:when>
|
||||
<xsl:when test="@pos = 'right'">float:right;</xsl:when>
|
||||
<xsl:when test="@pos = 'center'">margin:0 auto;</xsl:when>
|
||||
<xsl:when test="@pos">position:<xsl:value-of select="@pos"/>;</xsl:when>
|
||||
<xsl:when test="$left != '' or $top != ''">position:relative;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="style">
|
||||
<xsl:if test="$component = 'machine'">overflow:auto;width:100%;</xsl:if>
|
||||
<xsl:if test="@style"><xsl:value-of select="@style"/></xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="componentClass">
|
||||
<xsl:value-of select="$APPCLASS"/><xsl:text>-</xsl:text><xsl:value-of select="$component"/><xsl:text> </xsl:text><xsl:value-of select="$APPCLASS"/><xsl:text>-component</xsl:text>
|
||||
</xsl:variable>
|
||||
<div id="{$id}" class="{$componentClass}" style="{$width}{$height}{$pos}{$left}{$top}{$padding}" data-value="{$componentURL}">
|
||||
<xsl:if test="$component = 'machine'">
|
||||
<xsl:apply-templates select="name" mode="machine"/>
|
||||
</xsl:if>
|
||||
<xsl:if test="$component != 'machine'">
|
||||
<xsl:apply-templates select="name" mode="component"/>
|
||||
</xsl:if>
|
||||
<div class="{$APPCLASS}-container" style="{$border}{$style}">
|
||||
<xsl:if test="$class != '' and $component != 'machine'">
|
||||
<div class="{$APPCLASS}-{$class}-object" data-value="id:'{$id}',name:'{$name}'{$comment}{$parms}"> </div>
|
||||
</xsl:if>
|
||||
<xsl:if test="control">
|
||||
<div class="{$APPCLASS}-controls">
|
||||
<xsl:apply-templates select="control" mode="component"/>
|
||||
</div>
|
||||
</xsl:if>
|
||||
<xsl:apply-templates>
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="machineState" select="$machineState"/>
|
||||
</xsl:apply-templates>
|
||||
</div>
|
||||
<xsl:if test="$component = 'machine'">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$url != ''"><div class="{$APPCLASS}-reference">[<a href="{$url}">XML</a>]</div></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
<div class="{$APPCLASS}-copyright">
|
||||
<a href="http://{$SITEHOST}" target="_blank">PCjs</a> v<xsl:value-of select="$APPVERSION"/> © 2012-2014 by <a href="http://twitter.com/jeffpar" target="_blank">@jeffpar</a>
|
||||
</div>
|
||||
<div style="clear:both"> </div>
|
||||
</xsl:if>
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="name" mode="machine">
|
||||
<xsl:variable name="pos">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@pos = 'center'">text-align:center;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<h2 style="{$pos}"><xsl:apply-templates/></h2>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="name" mode="component">
|
||||
<div class="{$APPCLASS}-name"><xsl:apply-templates/></div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="control" mode="component">
|
||||
<xsl:variable name="type">
|
||||
<xsl:text>type:'</xsl:text><xsl:value-of select="@type"/><xsl:text>'</xsl:text>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="binding">
|
||||
<xsl:text>binding:'</xsl:text><xsl:value-of select="@binding"/><xsl:text>'</xsl:text>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="border">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@border = '1'">border:1px solid black;</xsl:when>
|
||||
<xsl:when test="@border">border:<xsl:value-of select="@border"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="width">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@width">width:<xsl:value-of select="@width"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="height">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@height">height:<xsl:value-of select="@height"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="left">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@left">left:<xsl:value-of select="@left"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="top">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@top">top:<xsl:value-of select="@top"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="padding">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@padding">padding:<xsl:value-of select="@padding"/>;</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:if test="@padtop">padding-top:<xsl:value-of select="@padtop"/>;</xsl:if>
|
||||
<xsl:if test="@padright">padding-right:<xsl:value-of select="@padright"/>;</xsl:if>
|
||||
<xsl:if test="@padbottom">padding-bottom:<xsl:value-of select="@padbottom"/>;</xsl:if>
|
||||
<xsl:if test="@padleft">padding-left:<xsl:value-of select="@padleft"/>;</xsl:if>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="pos">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@pos = 'left'">float:left;</xsl:when>
|
||||
<xsl:when test="@pos = 'right'">float:right;</xsl:when>
|
||||
<xsl:when test="@pos = 'center'">margin:0 auto;</xsl:when>
|
||||
<xsl:when test="@pos">position:<xsl:value-of select="@pos"/>;</xsl:when>
|
||||
<xsl:when test="$left != '' or $top != ''">position:relative;</xsl:when>
|
||||
<xsl:otherwise><xsl:if test="$left = ''">float:left;</xsl:if></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="style">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@style"><xsl:value-of select="@style"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="containerClass">
|
||||
<xsl:if test="@type = 'container' and @class"><xsl:text> </xsl:text><xsl:value-of select="@class"/></xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="containerStyle">
|
||||
<xsl:value-of select="$pos"/><xsl:value-of select="$left"/><xsl:value-of select="$top"/><xsl:value-of select="$padding"/>
|
||||
<xsl:choose>
|
||||
<xsl:when test="@type = 'container'"><xsl:value-of select="$border"/><xsl:value-of select="$width"/><xsl:value-of select="$height"/><xsl:value-of select="$style"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<div class="{$APPCLASS}-control{$containerClass}" style="{$containerStyle}">
|
||||
<xsl:variable name="fontsize">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@size = 'large' or @size = 'small'">font-size:<xsl:value-of select="@size"/>;</xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="subClass">
|
||||
<xsl:if test="@label"><xsl:text> </xsl:text><xsl:value-of select="$APPCLASS"/><xsl:text>-label</xsl:text></xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="labelWidth">
|
||||
<xsl:if test="@labelwidth">width:<xsl:value-of select="@labelwidth"/>;</xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="labelStyle">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@labelstyle"><xsl:value-of select="@labelstyle"/></xsl:when>
|
||||
<xsl:otherwise>text-align:right;</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:if test="@label">
|
||||
<xsl:if test="not(@labelpos) or @labelpos = 'left'">
|
||||
<div class="{$APPCLASS}-label" style="{$labelWidth}{$labelStyle}"><xsl:value-of select="@label"/></div>
|
||||
</xsl:if>
|
||||
</xsl:if>
|
||||
<xsl:choose>
|
||||
<xsl:when test="@type = 'button'">
|
||||
<button class="{$APPCLASS}-{@class}" style="-webkit-user-select:none;{$border}{$width}{$height}{$fontsize}{$style}" data-value="{$type},{$binding}"><xsl:apply-templates/></button>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'list'">
|
||||
<select class="{$APPCLASS}-{@class}" style="{$border}{$width}{$height}{$fontsize}{$style}" data-value="{$type},{$binding}">
|
||||
<xsl:apply-templates select="disk|app|manifest" mode="component"/>
|
||||
</select>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'text'">
|
||||
<input class="{$APPCLASS}-{@class}" type="text" style="{$border}{$width}{$height}{$style}" data-value="{$type},{$binding}" value="{.}" autocapitalize="off" autocorrect="off"/>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'submit'">
|
||||
<input class="{$APPCLASS}-{@class}" type="submit" style="{$border}{$fontsize}{$style}" data-value="{$type},{$binding}" value="{.}"/>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'textarea'">
|
||||
<textarea class="{$APPCLASS}-{@class}" style="{$border}{$width}{$height}{$style}" data-value="{$type},{$binding}" readonly="readonly"> </textarea>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'heading'">
|
||||
<div><xsl:value-of select="."/></div>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'file'">
|
||||
<form class="{$APPCLASS}-{@class}" data-value="{$type},{$binding}">
|
||||
<fieldset class="{$APPCLASS}-fieldset">
|
||||
<input type="file"/>
|
||||
<input type="submit" value="Load" disabled="true"/>
|
||||
</fieldset>
|
||||
</form>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'separator'">
|
||||
<hr/>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'container'">
|
||||
<xsl:apply-templates mode="component"/>
|
||||
</xsl:when>
|
||||
<xsl:when test="not(@type)">
|
||||
<div style="clear:both"> </div>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<div class="{$APPCLASS}-{@class}{$subClass} {$APPCLASS}-{@type}" style="-webkit-user-select:none;{$border}{$width}{$height}{$fontsize}{$style}" data-value="{$type},{$binding}"><xsl:apply-templates/></div>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
<xsl:if test="@label">
|
||||
<xsl:if test="@labelpos = 'right'">
|
||||
<div class="{$APPCLASS}-label" style="{$labelWidth}{$labelStyle}"><xsl:value-of select="@label"/></div>
|
||||
</xsl:if>
|
||||
<div style="clear:both"> </div>
|
||||
</xsl:if>
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="disk[@ref]" mode="component">
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/disk" mode="component"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="disk[not(@ref)]" mode="component">
|
||||
<xsl:variable name="desc">
|
||||
<xsl:if test="@desc">
|
||||
<xsl:text>desc:'</xsl:text><xsl:value-of select="@desc"/><xsl:text>'</xsl:text>
|
||||
<xsl:if test="@href">
|
||||
<xsl:text>,href:'</xsl:text><xsl:value-of select="@href"/><xsl:text>'</xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:if>
|
||||
</xsl:variable>
|
||||
<option value="{@path}" data-value="{$desc}"><xsl:if test="name"><xsl:value-of select="name"/></xsl:if><xsl:if test="not(name)"><xsl:value-of select="."/></xsl:if></option>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="app[@ref]" mode="component">
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/app" mode="component"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="app[not(@ref)]" mode="component">
|
||||
<xsl:variable name="desc">
|
||||
<xsl:if test="@desc">
|
||||
<xsl:text>desc:'</xsl:text><xsl:value-of select="@desc"/><xsl:text>'</xsl:text>
|
||||
<xsl:if test="@href">
|
||||
<xsl:text>,href:'</xsl:text><xsl:value-of select="@href"/><xsl:text>'</xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="path">
|
||||
<xsl:if test="@path"><xsl:value-of select="@path"/></xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="files">
|
||||
<xsl:for-each select="file"><xsl:if test="position() = 1"><xsl:value-of select="$path"/></xsl:if><xsl:value-of select="@name"/><xsl:if test="position() != last()">;</xsl:if></xsl:for-each>
|
||||
</xsl:variable>
|
||||
<option value="{$files}" data-value="{$desc}"><xsl:value-of select="@name"/></option>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="manifest[@ref]" mode="component">
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/manifest" mode="component">
|
||||
<xsl:with-param name="disk"><xsl:value-of select="@disk"/></xsl:with-param>
|
||||
</xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="manifest[not(@ref)]" mode="component">
|
||||
<xsl:param name="disk"><xsl:value-of select="@disk"/></xsl:param>
|
||||
<xsl:if test="$disk != ''">
|
||||
<xsl:variable name="prefix">
|
||||
<xsl:if test="title[@prefix]"><xsl:value-of select="title"/><xsl:text>: </xsl:text></xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:for-each select="disk">
|
||||
<xsl:if test="$disk = @id or $disk = '*'">
|
||||
<xsl:variable name="name">
|
||||
<xsl:choose>
|
||||
<xsl:when test="name"><xsl:value-of select="$prefix"/><xsl:value-of select="name"/></xsl:when>
|
||||
<xsl:when test="normalize-space(./text()) != ''">
|
||||
<xsl:value-of select="$prefix"/><xsl:value-of select="normalize-space(./text())"/>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:value-of select="../title"/><xsl:if test="../version != ''"><xsl:text> </xsl:text><xsl:value-of select="../version"/></xsl:if>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="link">
|
||||
<xsl:if test="link">
|
||||
<xsl:text>desc:'</xsl:text><xsl:value-of select="link"/><xsl:text>'</xsl:text>
|
||||
<xsl:if test="link/@href">
|
||||
<xsl:text>,href:'</xsl:text><xsl:value-of select="link/@href"/><xsl:text>'</xsl:text>
|
||||
</xsl:if>
|
||||
</xsl:if>
|
||||
</xsl:variable>
|
||||
<!-- TODO: Think about incorporating the optional "desc" tag into the disk description (see /disks/pc/tools/microsoft/MSC-048014.400/manifest.xml) -->
|
||||
<xsl:if test="@href">
|
||||
<option value="{@href}" data-value="{$link}"><xsl:value-of select="$name"/></option>
|
||||
</xsl:if>
|
||||
<xsl:if test="not(@href)">
|
||||
<xsl:variable name="dir">
|
||||
<xsl:if test="@dir"><xsl:value-of select="@dir"/></xsl:if>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="files">
|
||||
<xsl:for-each select="file"><xsl:if test="position() = 1"><xsl:value-of select="$dir"/></xsl:if><xsl:value-of select="@dir"/><xsl:value-of select="."/><xsl:if test="position() != last()">;</xsl:if></xsl:for-each>
|
||||
</xsl:variable>
|
||||
<option value="{$files}" data-value="{$link}"><xsl:value-of select="$name"/></option>
|
||||
</xsl:if>
|
||||
</xsl:if>
|
||||
</xsl:for-each>
|
||||
</xsl:if>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="name">
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="control">
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="cpu[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/cpu"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="cpu[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="model">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@model"><xsl:value-of select="@model"/></xsl:when>
|
||||
<xsl:otherwise>8088</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="cycles">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@cycles"><xsl:value-of select="@cycles"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="multiplier">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@multiplier"><xsl:value-of select="@multiplier"/></xsl:when>
|
||||
<xsl:otherwise>1</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="autoStart">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@autostart"><xsl:value-of select="@autostart"/></xsl:when>
|
||||
<xsl:otherwise>null</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="csStart">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@csstart"><xsl:value-of select="@csstart"/></xsl:when>
|
||||
<xsl:otherwise>-1</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="csInterval">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@csinterval"><xsl:value-of select="@csinterval"/></xsl:when>
|
||||
<xsl:otherwise>-1</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="csStop">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@csstop"><xsl:value-of select="@csstop"/></xsl:when>
|
||||
<xsl:otherwise>-1</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class" select="'cpu'"/>
|
||||
<xsl:with-param name="parms">,model:<xsl:value-of select="$model"/>,cycles:<xsl:value-of select="$cycles"/>,multiplier:<xsl:value-of select="$multiplier"/>,autoStart:<xsl:value-of select="$autoStart"/>,csStart:<xsl:value-of select="$csStart"/>,csInterval:<xsl:value-of select="$csInterval"/>,csStop:<xsl:value-of select="$csStop"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="chipset[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/chipset"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="chipset[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="model">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@model"><xsl:value-of select="@model"/></xsl:when>
|
||||
<xsl:otherwise>5150</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="sw1">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@sw1"><xsl:value-of select="@sw1"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="sw2">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@sw2"><xsl:value-of select="@sw2"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="sound">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@sound"><xsl:value-of select="@sound"/></xsl:when>
|
||||
<xsl:otherwise>true</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="scaletimers">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@scaletimers"><xsl:value-of select="@scaletimers"/></xsl:when>
|
||||
<xsl:otherwise>false</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="floppies">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@floppies"><xsl:value-of select="@floppies"/></xsl:when>
|
||||
<xsl:otherwise>{}</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="monitor">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@monitor"><xsl:value-of select="@monitor"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="rtcdate">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@rtcdate"><xsl:value-of select="@rtcdate"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">chipset</xsl:with-param>
|
||||
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>',scaleTimers:<xsl:value-of select="$scaletimers"/>,sw1:'<xsl:value-of select="$sw1"/>',sw2:'<xsl:value-of select="$sw2"/>',sound:<xsl:value-of select="$sound"/>,floppies:<xsl:value-of select="$floppies"/>,monitor:'<xsl:value-of select="$monitor"/>',rtcDate:'<xsl:value-of select="$rtcdate"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="keyboard[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/keyboard"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="keyboard[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="model">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@model"><xsl:value-of select="@model"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">keyboard</xsl:with-param>
|
||||
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="serial[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/serial"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="serial[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="adapter">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@adapter"><xsl:value-of select="@adapter"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="binding">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@binding"><xsl:value-of select="@binding"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">serial</xsl:with-param>
|
||||
<xsl:with-param name="parms">,adapter:<xsl:value-of select="$adapter"/>,binding:'<xsl:value-of select="$binding"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="mouse[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/mouse"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="mouse[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="serial">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@serial"><xsl:value-of select="@serial"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">mouse</xsl:with-param>
|
||||
<xsl:with-param name="parms">,serial:'<xsl:value-of select="$serial"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="fdc[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/fdc">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="mount" select="@automount"/>
|
||||
</xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="fdc[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:param name="mount" select="''"/>
|
||||
<xsl:variable name="automount">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$mount != ''"><xsl:value-of select="$mount"/></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="@automount"/></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">fdc</xsl:with-param>
|
||||
<xsl:with-param name="parms">,autoMount:'<xsl:value-of select="$automount"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="hdc[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/hdc"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="hdc[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="drives">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@drives"><xsl:value-of select="@drives"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="type">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@type"><xsl:value-of select="@type"/></xsl:when>
|
||||
<xsl:otherwise>xt</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">hdc</xsl:with-param>
|
||||
<xsl:with-param name="parms">,drives:'<xsl:value-of select="$drives"/>',type:'<xsl:value-of select="$type"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="rom[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/rom"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="rom[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="addr">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@addr"><xsl:value-of select="@addr"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="size">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@size"><xsl:value-of select="@size"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="alias">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@alias"><xsl:value-of select="@alias"/></xsl:when>
|
||||
<xsl:otherwise>null</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="file">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@file"><xsl:value-of select="@file"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="notify">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@notify"><xsl:value-of select="@notify"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">rom</xsl:with-param>
|
||||
<xsl:with-param name="parms">,addr:<xsl:value-of select="$addr"/>,size:<xsl:value-of select="$size"/>,alias:<xsl:value-of select="$alias"/>,file:'<xsl:value-of select="$file"/>',notify:'<xsl:value-of select="$notify"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="ram[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/ram"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="ram[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="addr">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@addr"><xsl:value-of select="@addr"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="size">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@size"><xsl:value-of select="@size"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="test">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@test"><xsl:value-of select="@test"/></xsl:when>
|
||||
<xsl:otherwise>true</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">ram</xsl:with-param>
|
||||
<xsl:with-param name="parms">,addr:<xsl:value-of select="$addr"/>,size:<xsl:value-of select="$size"/>,test:<xsl:value-of select="$test"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="video[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/video"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="video[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="model">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@model"><xsl:value-of select="@model"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="mode">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@mode"><xsl:value-of select="@mode"/></xsl:when>
|
||||
<xsl:otherwise>7</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="screenWidth">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@screenwidth"><xsl:value-of select="@screenwidth"/></xsl:when>
|
||||
<xsl:otherwise>256</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="screenHeight">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@screenheight"><xsl:value-of select="@screenheight"/></xsl:when>
|
||||
<xsl:otherwise>224</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="memory">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@memory"><xsl:value-of select="@memory"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="switches">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@switches"><xsl:value-of select="@switches"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="scale">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@scale"><xsl:value-of select="@scale"/></xsl:when>
|
||||
<xsl:otherwise>false</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="charCols">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@cols"><xsl:value-of select="@cols"/></xsl:when>
|
||||
<xsl:otherwise>80</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="charRows">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@rows"><xsl:value-of select="@rows"/></xsl:when>
|
||||
<xsl:otherwise>25</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="fontROM">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@charset"><xsl:value-of select="@charset"/></xsl:when>
|
||||
<xsl:when test="@fontrom"><xsl:value-of select="@fontrom"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="screenColor">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@screencolor"><xsl:value-of select="@screencolor"/></xsl:when>
|
||||
<xsl:otherwise>black</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="touchScreen">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@touchscreen"><xsl:value-of select="@touchscreen"/></xsl:when>
|
||||
<xsl:otherwise>false</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">video</xsl:with-param>
|
||||
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>',mode:<xsl:value-of select="$mode"/>,screenWidth:<xsl:value-of select="$screenWidth"/>,screenHeight:<xsl:value-of select="$screenHeight"/>,memory:<xsl:value-of select="$memory"/>,switches:'<xsl:value-of select="$switches"/>',scale:<xsl:value-of select="$scale"/>,charCols:<xsl:value-of select="$charCols"/>,charRows:<xsl:value-of select="$charRows"/>,fontROM:'<xsl:value-of select="$fontROM"/>',screenColor:'<xsl:value-of select="$screenColor"/>',touchScreen:<xsl:value-of select="$touchScreen"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="debugger[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/debugger"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="debugger[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="commands">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@commands"><xsl:value-of select="@commands"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="messages">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@messages"><xsl:value-of select="@messages"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">debugger</xsl:with-param>
|
||||
<xsl:with-param name="parms">,commands:'<xsl:value-of select="$commands"/>',messages:'<xsl:value-of select="$messages"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="panel[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/panel"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="panel[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">panel</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="computer[@ref]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:param name="machineState" select="''"/>
|
||||
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
|
||||
<xsl:apply-templates select="document($componentFile)/computer">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="machineState" select="$machineState"/>
|
||||
</xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="computer[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:param name="machineState" select="''"/>
|
||||
<xsl:variable name="buswidth">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@buswidth"><xsl:value-of select="@buswidth"/></xsl:when>
|
||||
<xsl:otherwise>20</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="resume">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@resume and $machineState = ''"><xsl:value-of select="@resume"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="state">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$machineState != ''"><xsl:value-of select="$machineState"/></xsl:when>
|
||||
<xsl:when test="@state"><xsl:value-of select="@state"/></xsl:when>
|
||||
<xsl:otherwise/>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">computer</xsl:with-param>
|
||||
<xsl:with-param name="parms">,buswidth:'<xsl:value-of select="$buswidth"/>',resume:'<xsl:value-of select="$resume"/>',state:'<xsl:value-of select="$state"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
28
modules/shared/lib/README.md
Normal file
28
modules/shared/lib/README.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
Shared Sources
|
||||
===
|
||||
|
||||
This folder contains a mix of shared code, with some files used only by Node (server) modules,
|
||||
some used only by Browser (client) modules, and others used by both.
|
||||
|
||||
At the moment, only a few files are completely agnostic; eg: [strlib.js](strlib.js) and [usrlib.js](usrlib.js).
|
||||
One give-away is that neither contain references to any globals (although references to each other
|
||||
would be fine).
|
||||
|
||||
[netlib.js](netlib.js) is appropriate only for Node modules, because it contains code that relies on Node's
|
||||
global *Buffer* object, as indicated by:
|
||||
|
||||
/* global Buffer: false */
|
||||
|
||||
And [weblib.js](weblib.js) is appropriate only for client modules, because it contains code that relies on the
|
||||
browser's global *window* object, as indicated by:
|
||||
|
||||
/* global window: true */
|
||||
|
||||
We declare *window* modifiable (true) so that [defines.js](defines.js) can set *global.window* to *false*
|
||||
when running within Node, allowing any other code to test the existence of *window* with a simple:
|
||||
|
||||
if (window) {...}
|
||||
|
||||
instead of:
|
||||
|
||||
if (typeof window !== "undefined") {...}
|
||||
896
modules/shared/lib/component.js
Normal file
896
modules/shared/lib/component.js
Normal file
|
|
@ -0,0 +1,896 @@
|
|||
/**
|
||||
* @fileoverview The Component class used by C1Pjs and PCjs.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-May-14
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
/*
|
||||
* All the C1Pjs and PCjs components now use JSDoc types, primarily so that Google's Closure Compiler
|
||||
* will compile everything with ZERO warnings. For more information about the JSDoc types supported by
|
||||
* the Closure Compiler:
|
||||
*
|
||||
* https://developers.google.com/closure/compiler/docs/js-for-compiler#types
|
||||
*
|
||||
* I also attempted to use JSLint, but it's excessively strict for my taste, so this is the only file
|
||||
* I tried massaging for JSLint's sake. I gave up when it complained about my use of "while (true)";
|
||||
* replacing "true" with an assignment expression didn't make it any happier.
|
||||
*
|
||||
* I wasn't thrilled about replacing all "++" and "--" operators with "+= 1" and "-= 1", nor about using
|
||||
* "(s || '')" instead of "(s? s : '')", because while the former may seem simpler, it is NOT more portable.
|
||||
* It's not that I'm trying to write "portable JavaScript", but some of this code was ported from C code
|
||||
* I'd written about 14 years earlier, and portability is good, so I see no reason to rewrite code to make
|
||||
* it less portable.
|
||||
*
|
||||
* UPDATE: I've since switched to JSHint, which seems to have more reasonable defaults.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global window: true, DEBUG: true */
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
require("./defines");
|
||||
var usr = require("./usrlib");
|
||||
var web = require("./weblib");
|
||||
}
|
||||
|
||||
/**
|
||||
* Component(type, parms, constructor)
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} type
|
||||
* @param {Object} [parms]
|
||||
* @param {Object} [constructor]
|
||||
*
|
||||
* A Component object requires:
|
||||
*
|
||||
* type: a user-defined type name (eg, "CPU")
|
||||
*
|
||||
* and accepts any or all of the following (parms) properties:
|
||||
*
|
||||
* id: component ID (default is "")
|
||||
* name: component name (default is ""; if blank, toString() will use the type name only)
|
||||
* comment: component comment string (default is undefined)
|
||||
*
|
||||
* Subclasses that use Component.subclass() to extend Component will likely have additional (parms) properties.
|
||||
*/
|
||||
function Component(type, parms, constructor)
|
||||
{
|
||||
this.type = type;
|
||||
|
||||
if (!parms) {
|
||||
parms = {'id': "", 'name': ""};
|
||||
}
|
||||
|
||||
this.id = parms['id'];
|
||||
this.name = parms['name'];
|
||||
this.comment = parms['comment'];
|
||||
this.parms = parms;
|
||||
if (this.id === undefined) this.id = "";
|
||||
|
||||
var i = this.id.indexOf('.');
|
||||
if (i > 0) {
|
||||
this.idMachine = this.id.substr(0, i);
|
||||
this.idComponent = this.id.substr(i + 1);
|
||||
} else {
|
||||
this.idComponent = this.id;
|
||||
}
|
||||
|
||||
/*
|
||||
* Recording the constructor is really just a debugging aid, because many of our constructors
|
||||
* have class constants, but they're hard to find when the constructors are buried among all the
|
||||
* other globals.
|
||||
*/
|
||||
this[type] = constructor;
|
||||
|
||||
/*
|
||||
* TODO: Decide how to reintegrate this code into the components that still want it....
|
||||
*
|
||||
if (this.initStep) this.initStep(parms);
|
||||
*/
|
||||
this.aFlags = {
|
||||
fReady: false,
|
||||
fBusy: false,
|
||||
fBusyCancel: false,
|
||||
fPowered: false,
|
||||
fError: false
|
||||
};
|
||||
|
||||
this.fnReady = null;
|
||||
this.clearError();
|
||||
this.bindings = {};
|
||||
this.dbg = null; // by default, no connection to a Debugger
|
||||
|
||||
Component.add(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component.parmsURL
|
||||
*
|
||||
* Initialized to the set of URL parameters, if any, for the current web page.
|
||||
*
|
||||
* @type {Object|null}
|
||||
*/
|
||||
Component.parmsURL = web.getURLParameters();
|
||||
|
||||
/**
|
||||
* Component.inherit(p)
|
||||
*
|
||||
* Returns a newly created object that inherits properties from the prototype object p.
|
||||
* It uses the ECMAScript 5 function Object.create() if it is defined, and otherwise falls back to an older technique.
|
||||
*
|
||||
* See: Flanagan, David (2011-04-18). JavaScript: The Definitive Guide: The Definitive Guide (Kindle Locations 9854-9903). OReilly Media - A. Kindle Edition (Example 6-1)
|
||||
*
|
||||
* @param {Object} p
|
||||
*/
|
||||
Component.inherit = function(p)
|
||||
{
|
||||
if (window) { // an alternative to "if (typeof window === 'undefined')" if require("defines") has been invoked
|
||||
if (!p) throw new TypeError(); // TODO: Why does this barf under Node?
|
||||
if (Object.create) {
|
||||
return Object.create(p);
|
||||
}
|
||||
var t = typeof p;
|
||||
if (t !== "object" && t !== "function") throw new TypeError();
|
||||
}
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
function F() {}
|
||||
F.prototype = p;
|
||||
return new F();
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.extend(o, p)
|
||||
*
|
||||
* Copies the enumerable properties of p to o and returns o.
|
||||
* If o and p have a property by the same name, o's property is overwritten.
|
||||
*
|
||||
* See: Flanagan, David (2011-04-18). JavaScript: The Definitive Guide: The Definitive Guide (Kindle Locations 9854-9903). OReilly Media - A. Kindle Edition (Example 6-2)
|
||||
*
|
||||
* @param {Object} o
|
||||
* @param {Object} p
|
||||
*/
|
||||
Component.extend = function(o, p)
|
||||
{
|
||||
for (var prop in p) {
|
||||
o[prop] = p[prop];
|
||||
}
|
||||
return o;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.subclass(superclass, subclass, methods, statics)
|
||||
*
|
||||
* TODO: Determine why every subclass created by this function ends up with a name prefix of "Component.subclass"
|
||||
* in Chrome's call stack, rather than the (more logical) name of the subclass constructor. Is there a different
|
||||
* design pattern I should be using that creates subclasses more to Chrome's liking?
|
||||
*
|
||||
* See: Flanagan, David (2011-04-18). JavaScript: The Definitive Guide: The Definitive Guide (Kindle Locations 9854-9903). OReilly Media - A. Kindle Edition (Example 9-11)
|
||||
*
|
||||
* @param {Object} superclass is the constructor of the superclass
|
||||
* @param {Object} subclass is the constructor for the new subclass
|
||||
* @param {Object} [methods] contains all instance methods
|
||||
* @param {Object} [statics] contains all class properties and methods
|
||||
*/
|
||||
Component.subclass = function(superclass, subclass, methods, statics)
|
||||
{
|
||||
subclass.prototype = Component.inherit(superclass.prototype);
|
||||
subclass.prototype.constructor = subclass;
|
||||
if (methods) {
|
||||
Component.extend(subclass.prototype, methods);
|
||||
}
|
||||
if (statics) {
|
||||
Component.extend(subclass, statics);
|
||||
}
|
||||
return subclass;
|
||||
};
|
||||
|
||||
/*
|
||||
* Every component created on the current page is recorded in this array (see Component.add()).
|
||||
*
|
||||
* This enables any component to locate another component by ID (see Component.getComponentByID())
|
||||
* or by type (see Component.getComponentByType()).
|
||||
*/
|
||||
Component.all = [];
|
||||
|
||||
/**
|
||||
* Component.add(component)
|
||||
*
|
||||
* @param {Component} component
|
||||
*/
|
||||
Component.add = function(component)
|
||||
{
|
||||
/*
|
||||
* This just generates a lot of useless noise, handy in the early days, not so much these days...
|
||||
*
|
||||
* Component.log("Component.add(" + component.type + "," + component.id + ")");
|
||||
*/
|
||||
Component.all[Component.all.length] = component;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.log(s, type)
|
||||
*
|
||||
* For diagnostic output only.
|
||||
*
|
||||
* @param {string} [s] is the message text
|
||||
* @param {string} [type] is the message type
|
||||
*/
|
||||
Component.log = function(s, type)
|
||||
{
|
||||
if (DEBUG) {
|
||||
if (s) {
|
||||
var msElapsed, sMsg = (type? (type + ": ") : "") + s;
|
||||
if (Component.msStart === undefined) {
|
||||
Component.msStart = usr.getTime();
|
||||
}
|
||||
msElapsed = usr.getTime() - Component.msStart;
|
||||
console.log(msElapsed + "ms: " + sMsg.replace(/\n/g, " "));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.assert(f, s)
|
||||
*
|
||||
* Used to verify conditions that must be true (for DEBUG builds only; compiled builds should automatically have all
|
||||
* references to Component.assert() removed).
|
||||
*
|
||||
* @param {boolean} f is the expression we are asserting to be true
|
||||
* @param {string} [s] is description of the assertion on failure
|
||||
*/
|
||||
Component.assert = function(f, s)
|
||||
{
|
||||
if (DEBUG) {
|
||||
if (!f) {
|
||||
/*
|
||||
* TODO: An accompanying source file/line number/function call would be nice, if there was a browser-independent way....
|
||||
*/
|
||||
if (!s) s = "assertion failure";
|
||||
Component.log(s);
|
||||
throw new Error(s);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.println(s, type, id)
|
||||
*
|
||||
* For non-diagnostic messages, which components may override to control the destination/appearance of their output.
|
||||
*
|
||||
* Components that inherit from this class should use the instance method, this.println(), rather than Component.println(),
|
||||
* because if a Control Panel is loaded, it will override only the instance method, not the class method (overriding the class
|
||||
* method would improperly affect any other machines loaded on the same page).
|
||||
*
|
||||
* @param {string} [s] is the message text
|
||||
* @param {string} [type] is the message type
|
||||
* @param {string} [id] is the caller's ID, if any
|
||||
*/
|
||||
Component.println = function(s, type, id)
|
||||
{
|
||||
if (DEBUG) {
|
||||
Component.log((id? (id + ": ") : "") + (s? ("\"" + s + "\"") : ""), type);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.notice(s, fPrintOnly, id)
|
||||
*
|
||||
* notice() is like println() but implies a need for user notification, so we alert() as well.
|
||||
*
|
||||
* @param {string} s is the message text
|
||||
* @param {boolean} [fPrintOnly]
|
||||
* @param {string} [id] is the caller's ID, if any
|
||||
*/
|
||||
Component.notice = function(s, fPrintOnly, id)
|
||||
{
|
||||
if (DEBUG) {
|
||||
Component.println(s, "notice", id);
|
||||
}
|
||||
if (!fPrintOnly) web.alertUser(s);
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.warning(s)
|
||||
*
|
||||
* @param {string} s describes the warning
|
||||
*/
|
||||
Component.warning = function(s)
|
||||
{
|
||||
if (DEBUG) {
|
||||
Component.println(s, "warning");
|
||||
}
|
||||
web.alertUser(s);
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.error(s)
|
||||
*
|
||||
* @param {string} s describes the error; an alert() is displayed as well
|
||||
*/
|
||||
Component.error = function(s)
|
||||
{
|
||||
if (DEBUG) {
|
||||
Component.println(s, "error");
|
||||
}
|
||||
web.alertUser(s);
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.getComponents(idRelated)
|
||||
*
|
||||
* We could store components as properties of an 'all' object, using the component's ID,
|
||||
* and change this linear lookup into a property lookup, but some components may have no ID.
|
||||
*
|
||||
* @param {string} [idRelated] of related component
|
||||
* @return {Array} of components
|
||||
*/
|
||||
Component.getComponents = function(idRelated)
|
||||
{
|
||||
var i;
|
||||
var aComponents = [];
|
||||
/*
|
||||
* getComponentByID(id, idRelated)
|
||||
*
|
||||
* If idRelated is provided, we check it for a machine prefix, and use any
|
||||
* existing prefix to constrain matches to IDs with the same prefix, in order to
|
||||
* avoid matching components belonging to other machines.
|
||||
*/
|
||||
if (idRelated) {
|
||||
if ((i = idRelated.indexOf('.')) > 0)
|
||||
idRelated = idRelated.substr(0, i + 1);
|
||||
else
|
||||
idRelated = "";
|
||||
}
|
||||
for (i = 0; i < Component.all.length; i++) {
|
||||
var component = Component.all[i];
|
||||
if (!idRelated || !component.id.indexOf(idRelated)) {
|
||||
aComponents.push(component);
|
||||
}
|
||||
}
|
||||
return aComponents;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.getComponentByID(id, idRelated)
|
||||
*
|
||||
* We could store components as properties of an 'all' object, using the component's ID,
|
||||
* and change this linear lookup into a property lookup, but some components may have no ID.
|
||||
*
|
||||
* @param {string} id of the desired component
|
||||
* @param {string} [idRelated] of related component
|
||||
* @return {Component|null}
|
||||
*/
|
||||
Component.getComponentByID = function(id, idRelated)
|
||||
{
|
||||
if (id !== undefined) {
|
||||
var i;
|
||||
/*
|
||||
* If idRelated is provided, we check it for a machine prefix, and use any
|
||||
* existing prefix to constrain matches to IDs with the same prefix, in order to
|
||||
* avoid matching components belonging to other machines.
|
||||
*/
|
||||
if (idRelated && (i = idRelated.indexOf('.')) > 0) {
|
||||
id = idRelated.substr(0, i + 1) + id;
|
||||
}
|
||||
for (i = 0; i < Component.all.length; i++) {
|
||||
if (Component.all[i].id === id) {
|
||||
return Component.all[i];
|
||||
}
|
||||
}
|
||||
Component.log('Component.getComponentByID("' + id + '"): no component found', "warning");
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.getComponentByType(sType, idRelated, componentPrev)
|
||||
*
|
||||
* @param {string} sType of the desired component
|
||||
* @param {string} [idRelated] of related component
|
||||
* @param {Component} [componentPrev] of previously returned component, if any
|
||||
* @return {Component|null}
|
||||
*/
|
||||
Component.getComponentByType = function(sType, idRelated, componentPrev)
|
||||
{
|
||||
if (sType !== undefined) {
|
||||
var i;
|
||||
/*
|
||||
* If idRelated is provided, we check it for a machine prefix, and use any
|
||||
* existing prefix to constrain matches to IDs with the same prefix, in order to
|
||||
* avoid matching components belonging to other machines.
|
||||
*/
|
||||
if (idRelated) {
|
||||
if ((i = idRelated.indexOf('.')) > 0) {
|
||||
idRelated = idRelated.substr(0, i + 1);
|
||||
} else {
|
||||
idRelated = "";
|
||||
}
|
||||
}
|
||||
for (i = 0; i < Component.all.length; i++) {
|
||||
if (componentPrev) {
|
||||
if (componentPrev == Component.all[i]) componentPrev = null;
|
||||
continue;
|
||||
}
|
||||
if (sType == Component.all[i].type && (!idRelated || !Component.all[i].id.indexOf(idRelated))) {
|
||||
return Component.all[i];
|
||||
}
|
||||
}
|
||||
Component.log('Component.getComponentByType("' + sType + '"): no component found', "warning");
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.getComponentParms(element)
|
||||
*
|
||||
* @param {Object} element from the DOM
|
||||
*/
|
||||
Component.getComponentParms = function(element)
|
||||
{
|
||||
var parms = null,
|
||||
sParms = element.getAttribute("data-value");
|
||||
if (sParms) {
|
||||
try {
|
||||
parms = eval("({" + sParms + "})"); // jshint ignore:line
|
||||
/*
|
||||
* We can no longer invoke removeAttribute() because some components (eg, Panel) need
|
||||
* to run their initXXX() code more than once, to avoid initialization-order dependencies.
|
||||
*
|
||||
* if (!DEBUG) {
|
||||
* element.removeAttribute("data-value");
|
||||
* }
|
||||
*/
|
||||
} catch (e) {
|
||||
Component.error(e.message + " (" + sParms + ")");
|
||||
}
|
||||
}
|
||||
return parms;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.bindExternalControl(component, sControl, sBinding, sType)
|
||||
*
|
||||
* @param {Component} component
|
||||
* @param {string} sControl
|
||||
* @param {string} sBinding
|
||||
* @param {string} [sType] is the external component type
|
||||
*/
|
||||
Component.bindExternalControl = function(component, sControl, sBinding, sType)
|
||||
{
|
||||
if (sControl) {
|
||||
if (sType === undefined) sType = "Panel";
|
||||
var target = Component.getComponentByType(sType, component.id);
|
||||
if (target) {
|
||||
var eBinding = target.bindings[sControl];
|
||||
if (eBinding) {
|
||||
component.setBinding(null, null, sBinding, eBinding);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.bindComponentControls(component, element, sAppClass)
|
||||
*
|
||||
* @param {Component} component
|
||||
* @param {Object} element from the DOM
|
||||
* @param {string} sAppClass
|
||||
*/
|
||||
Component.bindComponentControls = function(component, element, sAppClass)
|
||||
{
|
||||
var iControl, aeControls;
|
||||
aeControls = Component.getElementsByClass(element.parentNode, sAppClass + "-control");
|
||||
for (iControl = 0; iControl < aeControls.length; iControl++) {
|
||||
var iNode, aeChildNodes;
|
||||
aeChildNodes = aeControls[iControl].childNodes;
|
||||
for (iNode = 0; iNode < aeChildNodes.length; iNode++) {
|
||||
var control = aeChildNodes[iNode];
|
||||
if (control.nodeType !== window.document.ELEMENT_NODE) {
|
||||
continue;
|
||||
}
|
||||
var sClass = control.getAttribute("class");
|
||||
if (!sClass) continue;
|
||||
var iClass, aClasses;
|
||||
aClasses = sClass.split(" ");
|
||||
for (iClass = 0; iClass < aClasses.length; iClass++) {
|
||||
var parms;
|
||||
sClass = aClasses[iClass];
|
||||
switch (sClass) {
|
||||
case sAppClass + "-input":
|
||||
case sAppClass + "-output":
|
||||
parms = Component.getComponentParms(control);
|
||||
if (parms && parms['binding']) {
|
||||
component.setBinding(sClass, parms['type'], parms['binding'], control);
|
||||
} else {
|
||||
Component.log('Component.bindComponentControls("' + component.toString() + '"): missing binding' + (parms? ' for ' + parms['type'] : ''), "warning");
|
||||
}
|
||||
aClasses = [];
|
||||
break;
|
||||
default:
|
||||
// Component.log("Component.bindComponentControls(" + component.toString() + "): unrecognized control class \"" + sClass + "\"", "warning");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.getElementsByClass(element, sClass, sObjClass)
|
||||
*
|
||||
* This is a cross-browser helper function, since not all browser's support getElementsByClassName()
|
||||
*
|
||||
* TODO: This should probably be moved into weblib.js at some point, along with the control binding functions above,
|
||||
* to keep all the browser-related code together.
|
||||
*
|
||||
* @param {Object} element from the DOM
|
||||
* @param {string} sClass
|
||||
* @param {string} [sObjClass]
|
||||
* @return {Array|NodeList}
|
||||
*/
|
||||
Component.getElementsByClass = function(element, sClass, sObjClass)
|
||||
{
|
||||
if (sObjClass) sClass += '-' + sObjClass + "-object";
|
||||
/*
|
||||
* Use the browser's built-in getElementsByClassName() if it appears to be available
|
||||
* (for example, it's not available in IE8, but it should be available in IE9 and up)
|
||||
*/
|
||||
if (element.getElementsByClassName) {
|
||||
return element.getElementsByClassName(sClass);
|
||||
}
|
||||
var i, j, ae = [];
|
||||
var aeAll = element.getElementsByTagName("*");
|
||||
var re = new RegExp('(^| )' + sClass + '( |$)');
|
||||
for (i = 0, j = aeAll.length; i < j; i++) {
|
||||
if (re.test(aeAll[i].className)) {
|
||||
ae.push(aeAll[i]);
|
||||
}
|
||||
}
|
||||
if (!ae.length) {
|
||||
Component.log('no elements of class "' + sClass + '" found');
|
||||
}
|
||||
return ae;
|
||||
};
|
||||
|
||||
Component.prototype = {
|
||||
constructor: Component,
|
||||
/**
|
||||
* toString()
|
||||
*
|
||||
* @this {Component}
|
||||
* @return {string}
|
||||
*/
|
||||
toString: function() {
|
||||
return (this.name? this.name : (this.id || this.type));
|
||||
},
|
||||
/**
|
||||
* getMachineNum()
|
||||
*
|
||||
* @this {Component}
|
||||
* @return {number} unique machine number
|
||||
*/
|
||||
getMachineNum: function() {
|
||||
var nMachine = 1;
|
||||
if (this.idMachine) {
|
||||
var aDigits = this.idMachine.match(/\d+/);
|
||||
if (aDigits !== null)
|
||||
nMachine = parseInt(aDigits[0], 10);
|
||||
}
|
||||
return nMachine;
|
||||
},
|
||||
/**
|
||||
* setBinding(sHTMLClass, sHTMLType, sBinding, control)
|
||||
*
|
||||
* Component's setBinding() method is intended to be overridden by subclasses.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @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)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
setBinding: function(sHTMLClass, sHTMLType, sBinding, control) {
|
||||
switch (sBinding) {
|
||||
case "clear":
|
||||
if (!this.bindings[sBinding]) {
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = (function(component) {
|
||||
return function clearPanel() {
|
||||
if (component.bindings['print']) {
|
||||
component.bindings['print'].value = "";
|
||||
}
|
||||
};
|
||||
}(this));
|
||||
}
|
||||
return true;
|
||||
case "print":
|
||||
if (!this.bindings[sBinding]) {
|
||||
this.bindings[sBinding] = control;
|
||||
/*
|
||||
* HACK: Save this particular HTML element so that the Debugger can access it, too
|
||||
*/
|
||||
this.controlPrint = control;
|
||||
/*
|
||||
* This was added for Firefox (Safari automatically clears the <textarea> on a page reload,
|
||||
* but Firefox does not).
|
||||
*/
|
||||
control.value = "";
|
||||
this.println = (function(control) {
|
||||
return function printPanel(s, type) {
|
||||
s = (type !== undefined? (type + ": ") : "") + (s || "");
|
||||
/*
|
||||
* In COMPILED builds, prevent the <textarea> from getting too large;
|
||||
* otherwise, printing becomes slower and slower.
|
||||
*/
|
||||
if (COMPILED) {
|
||||
if (control.value.length > 8192) {
|
||||
control.value = control.value.substr(control.value.length - 4096);
|
||||
}
|
||||
}
|
||||
control.value += s + "\n";
|
||||
control.scrollTop = control.scrollHeight;
|
||||
if (DEBUG) console.log(s);
|
||||
};
|
||||
}(control));
|
||||
/**
|
||||
* Override this.notice() with a replacement function that eliminates the web.alertUser() call
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {string} s
|
||||
* @param {boolean} [fPrintOnly]
|
||||
* @param {string} [id]
|
||||
*/
|
||||
this.notice = function noticePanel(s, fPrintOnly, id) {
|
||||
this.println(s, "notice", id);
|
||||
};
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* log(s, type)
|
||||
*
|
||||
* For diagnostic output only.
|
||||
*
|
||||
* WARNING: Even though this function's body is completely wrapped in DEBUG, that won't prevent the Closure Compiler
|
||||
* from including it, so all calls must still be prefixed with "if (DEBUG) ....". For this reason, the class method,
|
||||
* Component.log(), is preferred, because the compiler IS smart enough to remove those calls.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {string} [s] is the message text
|
||||
* @param {string} [type] is the message type
|
||||
*/
|
||||
log: function(s, type) {
|
||||
if (DEBUG) {
|
||||
Component.log(s, type || this.id || this.type);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* println(s, type)
|
||||
*
|
||||
* For non-diagnostic messages, which components may override to control the destination/appearance of their output.
|
||||
*
|
||||
* Components using this.println() should wait until after their constructor has run to display any messages, because
|
||||
* if a Control Panel has been loaded, its override will not take effect until its own constructor has run.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {string} [s] is the message text
|
||||
* @param {string} [type] is the message type
|
||||
* @param {string} [id] is the caller's ID, if any
|
||||
*/
|
||||
println: function(s, type, id) {
|
||||
Component.println(s, type, id || this.id);
|
||||
},
|
||||
/**
|
||||
* status(s)
|
||||
*
|
||||
* status() is like println() but it also includes information about the component (ie, the component ID),
|
||||
* which is why there is no corresponding Component.status() function.
|
||||
*
|
||||
* @param {string} s is the message text
|
||||
*/
|
||||
status: function(s) {
|
||||
this.println(this.idComponent + ": " + s);
|
||||
},
|
||||
/**
|
||||
* notice(s, fPrintOnly)
|
||||
*
|
||||
* notice() is like println() but implies a need for user notification, so we alert() as well; however, if this.println()
|
||||
* is overridden, this.notice will be replaced with a similar override, on the assumption that the override is taking care
|
||||
* of alerting the user.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {string} s is the message text
|
||||
* @param {boolean} [fPrintOnly]
|
||||
* @param {string} [id] is the caller's ID, if any
|
||||
*/
|
||||
notice: function(s, fPrintOnly, id) {
|
||||
Component.notice(s, fPrintOnly, id || this.id);
|
||||
},
|
||||
/**
|
||||
* setError(s)
|
||||
*
|
||||
* Set a fatal error condition
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {string} s describes a fatal error condition
|
||||
*/
|
||||
setError: function(s) {
|
||||
this.aFlags.fError = true;
|
||||
this.notice("Fatal error: " + s);
|
||||
},
|
||||
/**
|
||||
* clearError()
|
||||
*
|
||||
* Clear any fatal error condition
|
||||
*
|
||||
* @this {Component}
|
||||
*/
|
||||
clearError: function() {
|
||||
this.aFlags.fError = false;
|
||||
},
|
||||
/**
|
||||
* isError()
|
||||
*
|
||||
* Report any fatal error condition
|
||||
*
|
||||
* @this {Component}
|
||||
* @return {boolean} true if a fatal error condition exists, false if not
|
||||
*/
|
||||
isError: function() {
|
||||
if (this.aFlags.fError) {
|
||||
this.println(this.toString() + " error");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* isReady(fnReady)
|
||||
*
|
||||
* Return the "ready" state of the component; if the component is not ready, it will queue the optional
|
||||
* notification function, otherwise it will immediately call the notification function, if any, without queuing it.
|
||||
*
|
||||
* NOTE: Since only the Computer component actually cares about the "readiness" of other components, the so-called
|
||||
* "queue" of notification functions supports exactly one function. This keeps things nice and simple.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {function()} [fnReady]
|
||||
* @return {boolean} true if the component is in a "ready" state, false if not
|
||||
*/
|
||||
isReady: function(fnReady) {
|
||||
if (fnReady) {
|
||||
if (this.aFlags.fReady) {
|
||||
fnReady();
|
||||
} else {
|
||||
if (DEBUG) this.log("NOT ready");
|
||||
this.fnReady = fnReady;
|
||||
}
|
||||
}
|
||||
return this.aFlags.fReady;
|
||||
},
|
||||
/**
|
||||
* setReady(fReady)
|
||||
*
|
||||
* Set the "ready" state of the component to true, and call any queued notification functions.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {boolean} [fReady] is assumed to indicate "ready" unless EXPLICITLY set to false
|
||||
*/
|
||||
setReady: function(fReady) {
|
||||
if (!this.aFlags.fError) {
|
||||
this.aFlags.fReady = (fReady !== false);
|
||||
if (this.aFlags.fReady) {
|
||||
if (DEBUG || this.name) this.log("ready");
|
||||
var fnReady = this.fnReady;
|
||||
this.fnReady = null;
|
||||
if (fnReady) fnReady();
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* isBusy(fCancel)
|
||||
*
|
||||
* Return the "busy" state of the component
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {boolean} [fCancel] is set to true to cancel a "busy" state
|
||||
* @return {boolean} true if "busy", false if not
|
||||
*/
|
||||
isBusy: function(fCancel) {
|
||||
if (this.aFlags.fBusy) {
|
||||
if (fCancel) {
|
||||
this.aFlags.fBusyCancel = true;
|
||||
} else if (fCancel === undefined) {
|
||||
this.println(this.toString() + " busy");
|
||||
}
|
||||
}
|
||||
return this.aFlags.fBusy;
|
||||
},
|
||||
/**
|
||||
* setBusy(fBusy)
|
||||
*
|
||||
* Update the current busy state; if an fCancel request is pending, it will be honored now.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {boolean} fBusy
|
||||
* @return {boolean}
|
||||
*/
|
||||
setBusy: function(fBusy) {
|
||||
if (this.aFlags.fBusyCancel) {
|
||||
if (this.aFlags.fBusy) {
|
||||
this.aFlags.fBusy = false;
|
||||
}
|
||||
this.aFlags.fBusyCancel = false;
|
||||
return false;
|
||||
}
|
||||
if (this.aFlags.fError) {
|
||||
this.println(this.toString() + " error");
|
||||
return false;
|
||||
}
|
||||
this.aFlags.fBusy = fBusy;
|
||||
return this.aFlags.fBusy;
|
||||
},
|
||||
/**
|
||||
* powerUp(fSave)
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower] is true if this is "repower" notification
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
powerUp: function(data, fRepower) {
|
||||
this.aFlags.fPowered = true;
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
* powerDown(fSave, fShutdown)
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {boolean} fSave
|
||||
* @param {boolean} [fShutdown]
|
||||
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
|
||||
*/
|
||||
powerDown: function(fSave, fShutdown) {
|
||||
if (fShutdown) this.aFlags.fPowered = false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* TODO: What was this work-around for? I forget....
|
||||
*/
|
||||
if (window && !window.document.ELEMENT_NODE) window.document.ELEMENT_NODE = 1;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = Component;
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue