my_modules is simply modules now

This commit is contained in:
Jeff Parsons 2014-11-11 08:19:48 -08:00 committed by jeffpar
commit e0012c3c62
139 changed files with 187 additions and 197 deletions

8
modules/README.md Normal file
View 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.

View 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
}
}

View 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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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;

View 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
View 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
View 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
View 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
View 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
View 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 &lt;canvas&gt; 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);

View 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.

View 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;
}

View 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>

View 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.

View 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();

File diff suppressed because it is too large Load diff

View 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"
}
}

View file

@ -0,0 +1,3 @@
FileDump
===
Module (and command-line utility) for converting the contents of files to JSON.

View 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();

View 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;

View 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
View 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
View file

@ -0,0 +1,3 @@
node_modules
npm-debug.log
tmp

View 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
}

View 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']);
};

View 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.

View 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)_

View 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"
]
}

View 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();
});
});
};

View file

@ -0,0 +1 @@
Testing: 1 2 3 !!!

View file

@ -0,0 +1 @@
Testing, 1 2 3.

View file

@ -0,0 +1 @@
1 2 3

View file

@ -0,0 +1 @@
Testing

View 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();
},
};

View 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
}

View 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']);
};

View 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.

View 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)_

View 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"
]
}

View 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.');
});
});
};

View file

@ -0,0 +1 @@
Testing: 1 2 3 !!!

View file

@ -0,0 +1 @@
Testing, 1 2 3.

View file

@ -0,0 +1 @@
1 2 3

View file

@ -0,0 +1 @@
Testing

View 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
View 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)).

View 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

View 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();

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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"
}
}

View 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.

View 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();

File diff suppressed because it is too large Load diff

View 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"
}
}

View file

@ -0,0 +1,10 @@
{
"globalstrict": true,
"sub": true,
"globals": {
"global": true,
"console": true,
"module": true,
"require": true
}
}

View 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));

View 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
View 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
View 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();
}

View 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
}
}

View 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
View 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

File diff suppressed because it is too large Load diff

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

File diff suppressed because it is too large Load diff

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
View 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

File diff suppressed because it is too large Load diff

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

File diff suppressed because it is too large Load diff

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
View 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
View 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;

View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

327
modules/pcjs/lib/x86.js Normal file
View 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

File diff suppressed because it is too large Load diff

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
View 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

File diff suppressed because it is too large Load diff

522
modules/pcjs/lib/x86op0f.js Normal file
View 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

File diff suppressed because it is too large Load diff

420
modules/pcjs/lib/x86seg.js Normal file
View 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
View 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"
}
}

View 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.

View 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;
}
}

View 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>

View 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") {...}

View 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