Initial commit (a clone of the jsmachines project as of v1.15.3)

This commit is contained in:
jeffpar 2014-09-27 14:52:57 -07:00
commit a5e3e6a59d
714 changed files with 130602 additions and 0 deletions

649
my_modules/README.md Normal file
View file

@ -0,0 +1,649 @@
Private Node Modules
===
**my_modules** contains all the *private* **JSMachines** Node modules. It is the counterpart to
**node_modules**, where all the *public* Node modules are installed. Only private Node modules are checked
into GitHub; if we later decide to publish any or all of these on [npmjs.org](http://npmjs.org), then they will
be moved to **node_modules** and removed from this folder.
The rest of this file documents some of the early steps involved in porting the **JSMachines** project,
which required a web server running PHP, to the new **PCjs** web server running Node. That includes setting up
my first Node web server and then extending its functionality with these private Node modules.
For more details on the private Node modules stored here, refer to the README.md for each module (which, alas,
may be nothing more than placeholders at this point, but I have good intentions).
Creating My First Node Project
===
Plan A: Fork jsmachines.net -> pcjs.org
---
The original **JSMachines** project assumed a web server running PHP
(eg, [http://jsmachines.net](http://jsmachines.net)). However, to improve client-server integration, reduce
the number of languages/frameworks used, and implement some new features, such as socket-based disk I/O interfaces,
I decided to create a fork, **PCjs**, and port all the server-side PHP to JavaScript, running on a Node web server.
Unfortunately, when I tried to fork my private **JSMachines** project on GitHub, I discovered that you're
not allowed to [fork](https://help.github.com/articles/fork-a-repo) your own projects. Adding to the confusion: GitHub
displays the "Fork" button on my project's home page, and even lets me click it, but then quietly does nothing.
The recommended work-around (as per [this post](http://bitdrift.com/post/4534738938/fork-your-own-project-on-github))
seemed to be:
- Create new project on GitHub
- Clone old project with new name
- Replace new project's "origin URL" with URL for new GitHub project
- Add original project as an "upstream source"
- Push new project to GitHub
Plan B: Branch jsmachines -> node_dev
---
The above seemed a bit clunky, so I decided to branch instead of fork. This new development branch was called
**node_pcjs**, but I've since branched to **node_dev**. I should probably remove the original **node_pcjs** branch,
but for now, it's still there.
Then I pushed the **node_dev** branch to the remote, as per [GitHub](https://help.github.com/articles/pushing-to-a-remote):
[~/Sites/jsmachines] git push origin node_dev
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:jeffpar/jsmachines.git
* [new branch] node_dev -> node_dev
[~/Sites/jsmachines]
That seemed to work fine, so I then created a new folder and cloned the project, as per
[GitHub](https://help.github.com/articles/fetching-a-remote):
[~/Sites] git clone git@github.com:jeffpar/jsmachines.git pcjs
Cloning into 'pcjs'...
remote: Reusing existing pack: 2725, done.
remote: Total 2725 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (2725/2725), 59.48 MiB | 2.67 MiB/s, done.
Resolving deltas: 100% (1543/1543), done.
Checking connectivity... done
Then I checked-out the new branch:
[~/Sites] cd pcjs
[~/Sites/pcjs] git checkout node_dev
Branch node_dev set up to track remote branch node_dev from origin.
Switched to a new branch 'node_dev'
If I have to make any changes to the "master" branch (ie, the original jsmachines project) in the interim,
then after I've pushed those changes to GitHub, I can pull them into the **node_dev** branch like so:
[~/Sites/pcjs] git pull origin master
Installing Node (and NPM)
---
I downloaded the latest Node installation package for my MacBook from [nodejs.org](http://nodejs.org/),
which provided the usual `node` and `npm` commands. Here are the versions they reported:
[~/Sites/pcjs] node -v
v0.10.26
[~/Sites/pcjs] npm -v
1.4.3
I also installed a Node helper on my MacBook called `n` that makes it easy to keep Node up-to-date, per this tip on
[stackoverflow](http://stackoverflow.com/questions/8191459/how-to-update-node-js-npm-and-all-other-dependencies):
1) Clear NPM's cache:
[~/Sites/pcjs] sudo npm cache clean -f
2) Install a little helper called 'n':
[~/Sites/pcjs] sudo npm install -g n
3) Install latest stable NodeJS version:
[~/Sites/pcjs] sudo n stable
Alternatively pick a specific version and install like so:
[~/Sites/pcjs] sudo n 0.8.20
I'm not sure I like the generic name for this command ("n"); what would have been wrong with "node-update"
or "update-node"? Oh well.
Next, I wanted to make sure `npm` was up-to-date. According to
[FAQ on npmjs.org](https://npmjs.org/doc/faq.html#How-do-I-update-npm), that's as easy as:
[~/Sites/pcjs] sudo npm update npm -g
although occasionally `npm` won't be able to update itself, in which case the fallback is:
[~/Sites/pcjs] curl https://npmjs.org/install.sh | sh
So, I made sure `npm` itself, along with `n` and all my other globally installed packages, were up-to-date,
and then I used `n` to make sure I was running the latest stable version of Node:
[~/Sites/pcjs] sudo npm update -g
[~/Sites/pcjs] sudo n stable
[~/Sites/pcjs] node -v
v0.10.26
I've also seen suggestions to run this command before the preceding commands, but I don't know how often this is
recommended:
[~/Sites/pcjs] sudo npm cache clean -f
Next, I ran `npm init`:
[~/Sites/pcjs] npm adduser
Username: jeffpar
Password:
Email: (this IS public) jeff@pcjs.org
npm http PUT http://registry.npmjs.org/-/user/org.couchdb.user:jeffpar
npm http 201 http://registry.npmjs.org/-/user/org.couchdb.user:jeffpar
[~/Sites/pcjs] npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sane defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
name: (pcjs)
version: (0.0.0) 0.1.0
description: Node-enabled version of PCjs
entry point: (server.js)
test command:
git repository: (git://github.com/jeffpar/jsmachines.git)
keywords: pcjs,ibm pc,emulator
author: Jeff Parsons <Jeff@pcjs.org>
license: (ISC)
About to write to /Users/Jeff/Sites/pcjs/package.json:
{
"name": "pcjs",
"version": "0.1.0",
"description": "Node-enabled version of PCjs",
"main": "server.js",
"directories": {
"doc": "docs",
"test": "tests"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git://github.com/jeffpar/jsmachines.git"
},
"keywords": [
"pcjs",
"ibm",
"pc",
"emulator"
],
"author": "Jeff Parsons <Jeff@pcjs.org>",
"license": "ISC",
"bugs": {
"url": "https://github.com/jeffpar/jsmachines/issues"
},
"homepage": "https://github.com/jeffpar/jsmachines"
}
Is this ok? (yes)
Next, I installed `express` using `npm install --save express`:
[~/Sites/pcjs] npm install --save express
npm http GET http://registry.npmjs.org/express
...
express@3.4.8 node_modules/express
├── methods@0.1.0
├── merge-descriptors@0.0.1
├── range-parser@0.0.4
├── cookie-signature@1.0.1
├── fresh@0.2.0
├── debug@0.7.4
├── buffer-crc32@0.2.1
├── cookie@0.1.0
├── mkdirp@0.3.5
├── commander@1.3.2 (keypress@0.1.0)
├── send@0.1.4 (mime@1.2.11)
└── connect@2.12.0 (uid2@0.0.3, pause@0.0.1, qs@0.6.6, bytes@0.2.1, raw-body@1.1.2, batch@0.5.0, negotiator@0.3.0, multiparty@2.2.0)
This added the following lines to my "package.json" file:
"dependencies": {
"express": "~3.4.8"
}
Then I created a test "server.js" in the root of the project:
var http = require("http");
var express = require("express");
var app = express();
app.get("/", function(req, res) {
res.end("Testing");
});
http.createServer(app).listen(3000);
and ran `node server.js` and verified that all was working as expected.
Creating My First Node Module
---
The first module I decided to write was a port of the diskconv.php script I'd written to convert disk images to/from
JSON. I decided to call this new module **DiskDump**, since it will output disk images in a variety of formats, although
initially only as JSON.
[~/Sites/pcjs/my_modules] mkdir diskdump
[~/Sites/pcjs/my_modules] cd diskdump
[~/Sites/pcjs/my_modules/diskdump] npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sane defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
name: (diskdump)
version: (0.0.0) 0.1.0
description: Convert disk images to/from JSON
entry point: (index.js)
test command:
git repository:
keywords:
author: Jeff Parsons <Jeff@pcjs.org>
license: (ISC)
About to write to /Users/Jeff/Sites/pcjs/my_modules/diskdump/package.json:
{
"name": "diskdump",
"version": "0.1.0",
"description": "Convert disk images to/from JSON",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Jeff Parsons <Jeff@pcjs.org>",
"license": "ISC"
}
Is this ok? (yes)
Next, I followed the steps outlined [here](http://www.anupshinde.com/posts/how-to-create-nodejs-npm-package/) to
create a package skeleton with a CLI (command-line interface).
Filtering Web Server Requests
---
The next module I created was [HTMLOut](/my_modules/htmlout/), which provides a filter() function for the Express module,
taking an early crack at the server's HTTP requests.
If the request is for a directory, **HTMLOut** builds an "index.html" from a common HTML template file, and then saves
the file in that directory, so that future requests can be served by the Express static() function. The HTML template
file can specify a variety of replacement tokens, including a reference to a "README.md" file in the same directory,
which **HTMLOut** will load and pass to the private **MarkOut** Node module for conversion from Markdown to HTML.
Similarly, if the URL is an API request, **HTMLOut** will direct the request to the appropriate private Node module
(eg, **DiskDump** or **FileDump**).
What's The Right Way To Incorporate Node Module Fixes?
---
My current version of Express was 3.4.8, and looking at its dependencies in package.json, it wanted "fresh" v0.2.0.
Unfortunately, there seemed to be a problem with Safari and that version of "fresh", occasionally resulting in
blank pages, as discussed [here](http://stackoverflow.com/questions/18811286/nodejs-express-cache-and-304-status-code):
Safari sends Cache-Control: max-age=0 on reload. Express (or more specifically, Express's dependency, node-fresh)
considers the cache stale when Cache-Control: no-cache headers are received, but it doesn't do the same for
Cache-Control: max-age=0.
Further investigation revealed that [node-fresh](https://github.com/visionmedia/node-fresh) v0.2.1 added a fix for
that problem. But what was the right (or best) way to incorporate that fix into my local Express installation?
I decided to hand-edit the "fresh" dependency in Express' package.json and then run "npm update" in the Express folder.
That pulled in the newer "fresh" module. But I noticed that other modules that Express depends on include their OWN
version of "fresh", which were also locked at v0.2.0. Even after creating an "npm-shrinkwrap.json" and hand-editing
it, "npm update" refused to upgrade them from v0.2.0. I could edit the package.json files in each of the Express
"connect" and "send" folders, run "npm update" in those folders, and force v0.2.1 that way, but as soon as I ran another
"npm update" in the root, those folders reverted to v0.2.0 again.
However, this all turned out to be moot. The "Cache-Control: max-age=0" work-around added to "fresh" v0.2.1 was later
backed-out in v0.2.2. And I discovered that the "blank page" problem went away at the same time I added trailing-slash
redirects (see below), so there was likely a connection. The underlying issue with Safari is discussed in more detail
[here](https://github.com/visionmedia/node-fresh/issues/8); it seems likely the problem wasn't related to "fresh" after all.
Problems With Directory URLs and Slashes
---
I discovered that I needed to install "express-slash", enable Express "strict routing", and make HTMLOut.filter()
pass on directory requests that didn't include a trailing slash, so that the Node web server could call slash() and
trigger a trailing-slash redirect. This is apparently what apache's "mod_dir" module does automatically, and why
I never had a problem with apache. Without trailing slashes on directories, client-side requests for files relative to
those "slash-less" directories can fail. More details on this issue can be found in [htmlout.js](htmlout/lib/htmlout.js).
npm install express-slash --save
Moving from Make to Grunt
---
Step 1:
sudo npm install grunt-cli -g
Step 2:
npm install grunt --save-dev
which automatically adds the following to my package.json:
"devDependencies": {
"grunt": "^0.4.3"
}
Step 3: Create a basic Gruntfile.js:
module.exports = function(grunt) {
grunt.initConfig({
});
grunt.registerTask("default", []);
};
Step 4: Install some grunt modules; eg:
npm install grunt-contrib-uglify --save-dev
Step 5: Add a task (eg, uglify):
module.exports = function(grunt) {
grunt.initConfig({
uglify: {
dist: {
src: "dist/myfile.js",
dest: "dist/myfile.min.js"
}
}
});
grunt.loadNpmTasks("grunt-contrib-uglify");
grunt.registerTask("default", ["uglify"]);
};
However, while all of the above is a very nice example, I'm not really interested in uglify.
I use Google's Closure Compiler, so I needed to do this instead:
npm install grunt-closure-tools --save-dev
There was also an NPM module called "grunt-closure-compiler", but it hadn't been updated as recently, and from
the examples it provided, it wasn't clear that it included support for all the Closure Compiler options, like
"output_wrapper", so I went with "grunt-closure-tools", even though it also contains support for other tools that
I'm not currently using.
So now my package.json includes:
"devDependencies": {
"grunt": "^0.4.3",
"grunt-closure-tools": "^0.9.4"
}
Optional Node Modules
===
replace
---
I use the Node "replace" module to perform project-wide search-and-replace operations on files; eg:
node node_modules/replace/bin/replace.js '(<machine.*class)="c1pjs"' '$1="c1p"' . -r --include=*.xml --preview
node node_modules/replace/bin/replace.js '(<machine.*class)="pcjs"' '$1="pc"' . -r --include=*.xml --preview
node node_modules/replace/bin/replace.js '(href="/versions/c1pjs)/[^/]*/(.*)' '$1/1.12.1/$2' . -r --include=*.xml --preview
node node_modules/replace/bin/replace.js '(href="/versions/pcjs)/[^/]*/(.*)' '$1/1.12.1/$2' . -r --include=*.xml --preview
Most people probably install modules like this globally, but I'd rather just define a command-line alias
("replace" => "node node_modules/replace/bin/replace.js") instead of cluttering my system-level directories.
Testing with Azure
===
> NOTE: I've since abandoned Azure in favor of AWS Elastic Beanstalk, because Amazon's service has a simpler UI,
provides more control over the virtual machine environment (you can even log into the machine via SSH), and
the logging services for Node on AWS are more reliable. I also wrote a short [blog post](/blog/2014/03/30/)
on some of my frustrations with Azure.
I subscribed to Azure (formerly Windows Azure, now [Microsoft Azure](http://azure.microsoft.com/)), and started
watching this YouTube video "[Lightning nodejs dev in Windows Azure](http://www.youtube.com/watch?v=WbtV1bX_m2I)"
by Glenn Block ([@gblock](http://twitter.com/gblock)). I was glad to see that he used a Mac, because a number of
other Azure videos on Youtube were made using Windows.
The first recommendation was to install the "azure-cli" command-line tools:
sudo npm install azure-cli -g
To display available commands:
azure
To connect the command-line tools to my Azure account (after setting my default browser to Chrome, since I used
Chrome to set up my Azure account and browse the Azure portal):
azure account download
To import my acount info:
azure account import ~/Downloads/Pay-As-You-Go-3-30-2014-credentials.publishsettings
To create a site, the video recommends:
azure site create pcjs --git
However, I'm holding off until I understand how to link a new Azure site deployment with an existing GitHub repository,
as discussed [here](http://www.windowsazure.com/en-us/documentation/articles/web-sites-publish-source-control/),
because the above command appears intended for new project folders, which are then associated with a remote Azure-hosted
Git repository -- not what I need or want.
The following command would show the remote Azure repository:
git remote -v
In my case, that command shows:
origin git@github.com:jeffpar/jsmachines.git (fetch)
origin git@github.com:jeffpar/jsmachines.git (push)
The following command would then be used to push updates to Azure:
git push azure master
Although in my case, I'm guessing it would be:
git push origin master
Or, rather:
git push origin node_dev
Since my local "pcjs" project is based on the **node_dev** branch.
If I was using an Azure-hosted Git repository, I could then run:
azure site browse
because the `git push azure master` command would have triggered a website update.
The video goes on to show how I can specify the version Node I want Azure to use, by adding the
following to my "package.json":
"engines": {
"node": "0.8.x"
}
This command:
azure site deployment list
will list all the deployments so far, any one of which can be redeployed like so:
azure site deployment redeploy xxx
The video finally gets that what I *really* care about, which is deploying via GitHub. This command
lists some of the commands we care about:
azure site create --help
To create a site from a repository on GitHub:
azure site create pcjs --github
JavaScript Coding Conventions
===
This is not intended to be an exhaustive list. It just lists a few things that came to mind, and I'll
probably update it over time.
Tabs
---
I've configured my IDE (PhpStorm) to NEVER use tab characters in .js files (spaces only) and to
ALWAYS use tab characters in almost every other type of text file. This is largely because when a web
browser displays a JavaScript file (either in the main window or in the Developer Tools window), tabs
usually screw up the formatting, which I find annoying when I'm debugging. XML files, on the other hand,
are usually reformatted by the browser, so in those cases, I opt for smaller files and use real tabs.
Moreover, most of the JavaScript delivered by a production server will have been pre-compiled by Google's
Closure Compiler, which completely eliminates all non-essential whitespace, so this is really a development
preference, with little to no impact on production files.
Regardless of the choice of tab character however, I almost always use 4-column tab stops, except
in legacy .asm files, where 8-column tab stops were the norm. I know that 2-column tab stops have
recently become "all the rage," especially in Node projects; NPM, for example, will rewrite package.json
files, replacing all the 4-column tabs with 2-column tabs, and I don't fight that trend -- I just ignore it.
JSDoc
---
I've taken a great deal of care to "[JSDoc](http://usejsdoc.org/)-ify" nearly all my JavaScript code, not
because I want to be able to generate documentation (although that's something to think about), but because
it's the only way to tell both Google's Closure Compiler and my IDE (PhpStorm) exactly what data
types are passed into/out of every function. The goals are to minimize the number of "code inspection"
warnings in the IDE and produce warning-free compilations.
In order to use the Closure Compiler's ADVANCED_OPTIMIZATIONS option and get maximum performance
(and maximum "minification", sometimes called "uglification"), every function and its parameters needs to
be fully typed; otherwise, the Compiler will generate WAY too many warnings/errors -- at least, that was the
case when I first started using it a couple of years ago.
So, I've adopted a zero-tolerance policy for warnings: no check-ins allowed if the Closure Compiler generates
even a SINGLE warning.
I'm not sure the [JSDoc](http://usejsdoc.org/) folks and the
[Closure Compiler](https://developers.google.com/closure/compiler/docs/js-for-compiler) are totally in sync on
everything. And then there's [PhpStorm](http://www.jetbrains.com/phpstorm/webhelp/creating-jsdoc-comments.html),
whose code inspections occasionally fail; sometimes a bogus code inspection warning can be fixed with some
additional JSDoc @name or @class annotations, but not always.
In any case, the subset of variable and function type declarations I use works pretty well across the board;
code inspection warnings are allowed in the IDE, as long as they are clearly erroneous (or clearly innocuous).
Braces, Parentheses, etc.
---
Most of my opening braces appear at the end of the line containing the associated "if", "while", "for", "switch",
"function" etc, preceded by a single space. And most of my opening parentheses are also preceded by a single space,
except when following "function" or a function name, in which case there is NO space. This is just an historical
preference, dating back to my BASIC and C programming days; it's not that those preferences matter anymore, it's just
that I see no reason to change them.
I'm not obsessed with these conventions. Sometimes, for example, I'll write a bunch of code where the opening
brace of every function body begins underneath the "function" keyword, because the extra whitespace can make the
file a bit more readable. It may sometimes depend on my mood, but I do try to be consistent within a given file
at least.
Variable Names
---
I still tend to follow Charles Simonyi's "[Hungarian](http://en.wikipedia.org/wiki/Hungarian_notation)" naming
conventions -- or rather, a naming convention loosely inspired by Hungarian. I know lots of people sneer at
those conventions and think they're useless, and all I can say is, they're wrong: they are not useless to ME.
I admit they may be useless to anyone who has a phenomenal memory and can remember that an obscure variable named
"foo" was initialized with a string or a number, or who has an IDE that can answer that question with the press
of a key (or two), but for me, with my non-phenomenal memory and lazy fingers, I prefer being able to simply
look at a variable to immediately know what *type* of data it contains, if nothing else.
I rarely name a string or numeric variable something vague like "foo." At worst, I would name it "sFoo" if it
was a string or "iFoo" if it was a number (or possibly "nFoo" or "cFoo" if it represented a total of Foos or a
counter of Foos). And if a string or numeric variable has a very short-term use, I often just call it "s" or "i"
(or "n").
As I mention [below](./#quotation-marks), I still tend to distinguish single characters from strings too,
which means I may sometimes prefix character variables with "ch" and character counters with "cch".
Of course, these letter prefixes like "s" and "n" are irrelevant if you already give your variables meaningful
names like "nameOfPerson" or "numberOfPeople". And that's fine -- I do that sometimes, too. But in general,
I still prefer variable names like "sPerson" and "nPeople".
I don't try to come up with special prefixes for Objects. If there's a Person object, for example, I'll probably
use colloquial names like "personHere" or "personThere". I am stricter about Arrays though: I prefix array variables
with "a", arrays of strings and numbers with "as" and "ai" (or "an"), and arrays of arrays with "aa". As for Arrays
of anything else, I usually don't bother with anything more than an "a" prefix.
Quotation Marks
---
Coming from a long C background, I prefer to use double-quotes around multi-character strings and single quotes
around single-character strings. While the reasons for doing so are largely historical and currently irrelevant,
characters are STILL the building blocks of strings, and even the JavaScript String class contains methods that
deal with individual characters (eg, charCodeAt() and fromCharCode()). So for any code that knowingly deals with
single characters, I like to reinforce that distinction with single quotes.
Also, to emphasize that an object property name isn't really a string, I tend to use single quotes around property
names as well. I realize that the JSON standard insists on property names with double-quotes, but functions like
JSON.stringify() take care of that for us -- besides, I find that aspect of the JSON standard to be rather annoying
(not to mention JSON's disapproval of comments).
Obviously, it's possible to set and get object properties without any quotes at all, as in:
obj.prop = true;
instead of:
obj['prop'] = true;
but that works only if the property name conforms to the same syntax that variable names use. Also, the "dot"
syntax can create problems for code compiled with Google's Closure Compiler (using ADVANCED_OPTIMIZATIONS),
because it likes to rename "dot" property names to smaller "minified" property names. Which means if you
need to export your objects as JSON later, or if you ever need to access a property using an externally defined
string, you must avoid the "dot" syntax.
However, if an object and all its properties are purely internal, there's usually no reason not to use it:
obj = {foo: "old", modified: false};
obj.foo = "new";
obj.modified = true;
I break my own quoting rules slightly when dealing with strings that *contain* double-quotes, since it's more readable
to put double-quotes inside single-quoted strings than to "escape" every double-quote with a backslash.
For code that was originally written in PHP and later ported to JavaScript, there was a tendency in the original
code to always use double-quotes around strings and "escape" double-quotes regardless, and that tendency may linger
in code I didn't feel like rewriting much, but the tendency was due more to idiosyncrasies of PHP than any convention
of mine; for example:
- single-quoted PHP strings may not include any escaped characters (except for single-quote and backslash)
- single-quoted PHP strings cannot resolve references to string variables (eg, "the value of foo is {$foo}")
Because of PHP's restrictions on single-quoted strings, I tended to avoid them. However, in JavaScript, the
first restriction isn't true, and the second isn't even a feature of the language, so they have no bearing on the use
of single-quoted strings in JavaScript.
Arrays
---
In [this article](http://stackoverflow.com/questions/8668174/indexof-method-in-an-object-array), it's asserted,
without disagreement, that searching arrays of objects for a particular object is best performed with code that looks
like this:
pos = myArray.map(function(e) { return e.hello; }).indexOf('stevie');
I disagree. Depending on how often that function is called, creating a new array by iterating over every element
of the original array, and doing that every time you want to search for an array object with a property (eg, `hello`)
equal to some value (eg, 'stevie'), is a bad idea; I don't care how fast your JavaScript engine is or how fantastic
your garbage collector is.
My preferred approach is to create parallel arrays: one array for each property that you might want to search on, and
another array of objects that contain all the other object properties. Then searching doesn't involve creating new
arrays:
pos = myArrayOfHello.indexOf('stevie');
Yes, it's not quite as *clean* as one, unified array of objects, and yes, it would be nicer if the JavaScript Array
class had better search methods, but I think this is the right trade-off given the current reality.

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": false,
"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,353 @@
/**
* @fileoverview This file implements the C1Pjs Computer component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* 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} c is the class of the HTML control (eg, "input", "output")
* @param {string|null} t is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
* @param {string} s is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
* @param {Object} e is the HTML control DOM object (eg, HTMLButtonElement)
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
C1PComputer.prototype.setBinding = function(c, t, s, e)
{
switch(s) {
case "reset":
this.bindings[s] = e;
e.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]);
}
}
/*
* 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".
*/
component = Component.getComponentByID('panel', parmsComputer['id']);
if (component) {
modules['panel'] = [component];
}
var computer = new C1PComputer(parmsComputer, modules);
/*
* 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);

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

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="/my_modules/c1pjs-client/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/roms/basic-gcpatch.hex"/>
<rom id="romSystem" size="0x0800" image="/devices/c1p/roms/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="/configs/c1p/machines/8kb/large/debugger/">Challenger 1P w/Debugger</a></li>
<li><a href="/configs/c1p/machines/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/roms/basic-gcpatch.hex"/<gt/>
<lt/>rom id="romSystem" size="0x0800" image="/devices/c1p/roms/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>

View file

@ -0,0 +1,130 @@
/**
* @fileoverview This file implements the C1Pjs Panel component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* 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.fPower = 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} c is the class of the HTML control (eg, "input", "output")
* @param {string|null} t is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
* @param {string} s is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
* @param {Object} e is the HTML control DOM object (eg, HTMLButtonElement)
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
C1PPanel.prototype.setBinding = function(c, t, s, e)
{
if (this.cmp && this.cmp.setBinding(c, t, s, e)) return true;
if (this.cpu && this.cpu.setBinding(c, t, s, e)) return true;
if (this.kbd && this.kbd.setBinding(c, t, s, e)) return true;
if (DEBUGGER && this.dbg && this.dbg.setBinding(c, t, s, e)) return true;
return Component.prototype.setBinding.call(this, c, t, s, e);
};
/**
* @this {C1PPanel}
* @param {boolean} fOn
* @param {C1PComputer} cmp
*/
C1PPanel.prototype.setPower = function(fOn, cmp)
{
if (fOn && !this.fPower) {
this.fPower = 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);

View file

@ -0,0 +1,96 @@
/**
* @fileoverview This file implements the C1Pjs RAM component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* 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);

View file

@ -0,0 +1,244 @@
/**
* @fileoverview This file implements the C1Pjs ROM component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* 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.fPower) {
this.fPower = 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("ROM load error (" + 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) {
//
// System exceptions throw an object with a message property, whereas exceptions I throw myself do not (they're just strings)
//
this.println("ROM data error: " + (e.message || e));
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);

View file

@ -0,0 +1,360 @@
/**
* @fileoverview This file implements the C1Pjs SerialPort component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* 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.fPower = 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} c is the class of the HTML control (eg, "input", "output")
* @param {string|null} t is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
* @param {string} s is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listSerial")
* @param {Object} e is the HTML control DOM object (eg, HTMLButtonElement)
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
C1PSerialPort.prototype.setBinding = function(c, t, s, e)
{
switch(s) {
case "listSerial":
this.bindings[s] = e;
return true;
case "loadSerial":
this.bindings[s] = e;
e.onclick = function(serial) {
return function() {
if (serial.bindings["listSerial"]) {
var sFile = serial.bindings["listSerial"].value;
// serial.println("loading " + sFile + "...");
web.loadResource(sFile, true, null, serial, serial.loadFile);
}
};
}(this);
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.fPower) {
this.fPower = 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);

View file

@ -0,0 +1,600 @@
/**
* @fileoverview This file implements the C1Pjs Video component
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* 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} c is the class of the HTML control (eg, "input", "output")
* @param {string|null} t is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
* @param {string} s is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "refresh")
* @param {Object} e is the HTML control DOM object (eg, HTMLButtonElement)
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
C1PVideo.prototype.setBinding = function(c, t, s, e)
{
switch(s) {
case "refresh":
this.bindings[s] = e;
e.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.fPower && this.isReady()) {
this.fPower = 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.fPower) {
this.fPower = 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.fPower) {
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,106 @@
@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-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,564 @@
<?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/my_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.0.0</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 = '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,19 @@
DiskDump
===
Module (and command-line utility) for converting disk images to/from various formats (eg, JSON,
commented JSON, and IMG files).
Building images from folders/files
---
I finally ported the code in [convdisk.php](/bin/convdisk.php) that creates disk images
from the contents of local files/folders, which you can now access via the *diskdump* API.
For example:
http://www.pcjs.org/api/v1/dump?path=/apps/pc/1981/visicalc/bin/vc.com;../README.md&format=json
would be equivalent to the older PHP script operation:
http://jsmachines.net/bin/convdisk.php?file=/apps/pc/1981/visicalc/bin/vc.com&format=json&download=true
These commands produce a "disk.json", a copy of which is stored at [/apps/pc/1981/visicalc](/apps/pc/1981/visicalc/).

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,327 @@
/*
* parallelAdder.js
* by Jeff Parsons, May 11, 2012
*/
/*
* Creation of a ParallelAdder object is controlled by the following properties of the
* parmsAdder object:
*
* idResident: ID of the resident register
* idIncident: ID of the incident register
* idCarries: ID of the carries register
*
* However, by the time we're called, initParallelAdders() has already looked up the
* corresponding Register components by the above IDs (so that we don't have to be
* involved in the "wiring" process) and passes them to us as:
*
* regResident
* regIncident
* regCarries
*
* Our constructor also creates an internal Register object (regScratch) that's used
* to make a copy of the carry bits in the "carries" register after each internal add cycle.
*/
function ParallelAdder(parmsAdder, regResident, regIncident, regCarries)
{
Component.call(this, "ParallelAdder", parmsAdder);
this.regResident = regResident;
this.regIncident = regIncident;
this.regCarries = regCarries;
this.aBitsResident = regResident.getBits();
this.aBitsIncident = regIncident.getBits();
this.aBitsCarries = regCarries.getBits();
this.regScratch = new Register({nBits:regCarries.count()});
this.aBitsScratch = this.regScratch.getBits();
}
Component.subclass(Component, ParallelAdder, {
/*
* These functions implement the necessary Register interfaces that other components,
* like the Selector, expect us to support.
*/
count: function() {
return this.regResident.count();
},
readBit: function(iBit) {
return this.regResident.readBit(iBit);
},
writeBit: function(iBit, b) {
this.regResident.writeBit(iBit, b);
},
/*
* These are the functions unique to ParallelAdder.
*/
add: function(fnNotify) {
this.stopSteps();
this.cCarryCycles = 0;
this.regResident.writeUndefined(false);
this.regResident.updateAll();
this.regIncident.writeUndefined(false);
this.regIncident.updateAll();
this.firstStep(this.stepAddRegisters, fnNotify);
},
stop: function() {
this.stopSteps();
this.regResident.stopSteps();
this.regIncident.stopSteps();
},
stepAddRegisters: function(n) {
this.cCarryCycles++;
/*
* We call the next step directly, to give this step something concrete to do;
* we could have incorporated that code into this step, but it seems cleaner this way.
*/
this.stepClearCarries(n);
if (this.cCarryCycles == 1)
this.addStep(this.stepAddIncidentBits);
else
this.addStep(this.stepAddScratchBits);
this.addStep(this.stepCheckCarries);
return true;
},
stepClearCarries: function(n) {
this.printStep(n, "Clearing carry bits");
this.regCarries.writeAll(false);
if (n !== undefined)
this.regCarries.updateAll();
return true;
},
stepCheckCarries: function(n) {
if (!this.cCarries) {
this.printStep(n, "No carries, addition complete");
return false;
}
this.printStep(n, "Copying carry bits to scratch");
this.regScratch.copyAll(this.regCarries);
return true;
},
stepAddIncidentBits: function(n) {
this.cCarries = 0;
/*
* The following table describes the 4 possible cases of resident(i) and incident(i) bits,
* and what result(i) and carry(i+1) bits must be generated in each of those cases to simulate
* the addition of a resident and incident bit:
*
* case resident(i) incident(i) result(i) carry(i+1)
* ---- ----------- ----------- --------- ----------
* 1 false "+" false "=" false false
* 2 false "+" true "=" true false
* 3 true "+" false "=" true false
* 4 true "+" true "=" false true
*
* Prior to adding the incident bits, we cleared all the carry bits, so we only have to *set* carry bits,
* never *clear* them. As for the result bits, they must be written back to the resident bits register,
* so we must always write the resident result bit -- which you can tell from the table above is equivalent
* to an XOR operation (ie, true if only ONE of either the resident or incident bits is true, false if BOTH
* are false or true).
*/
this.printStep(n, "Adding incident bits");
for (var i=0; i < this.aBitsIncident.length; i++) {
bIncident = this.aBitsIncident[i];
bResident = this.aBitsResident[i];
if (bResident && bIncident) {
if (i+1 < this.aBitsCarries.length) {
this.aBitsCarries[i+1] = true;
this.cCarries++;
}
}
this.aBitsResident[i] = (bResident? !bIncident : bIncident); // the closest thing we have to a logical XOR operation
}
if (n !== undefined) {
this.regResident.updateAll();
this.regCarries.updateAll();
}
return true;
},
stepAddScratchBits: function(n) {
this.cCarries = 0;
/*
* We take advantage of the fact that on iteration i, all scratch bits below bit index i must be zero,
* so we can start checking scratch bits (ie, the carry bits from the previous iteration) at bit index "cCarryCycles".
*
* NOTE: We should assert that all bits below that index are indeed zero, and that we never end up here when
* cCarryCycles == aBitsScratch.length (because there shouldn't have been any saved carries from the length-1 iteration).
*/
this.printStep(n, "Adding previous carry bits");
for (var i=this.cCarryCycles; i < this.aBitsScratch.length; i++) {
bScratch = this.aBitsScratch[i];
bResident = this.aBitsResident[i];
if (bResident && bScratch) {
if (i+1 < this.aBitsCarries.length) {
this.aBitsCarries[i+1] = true;
this.cCarries++;
}
}
this.aBitsResident[i] = (bResident? !bScratch : bScratch); // the closest thing we have to a logical XOR operation
}
if (n !== undefined) {
this.regResident.updateAll();
this.regCarries.updateAll();
}
return true;
}
});
/*
* initParallelAdders()
*
* Initializes all the necessary HTML to construct the component as spec'ed.
*
* Note that each element (e) of class "parallelAdder" is expected to have a "data-value"
* attribute containing the same JSON-encoded parameters that the ParallelAdder constructor
* expects; specifically:
*
* idResident: ID of register containing "resident" binary digits
* idIncident: ID of register containing "incident" binary digits
* idCarries: ID of register used for recording resulting carries
*/
function initParallelAdders()
{
var aeAdders = Component.getElementsByClass(window.document, "parallelAdder");
for (var iAdder=0; iAdder < aeAdders.length; iAdder++) {
var eAdder = aeAdders[iAdder];
var parmsAdder = Component.getComponentParms(eAdder);
//
// Let's find all the prerequisite register components next....
//
var regResident = Component.getComponentByID(parmsAdder.idResident);
var regIncident = Component.getComponentByID(parmsAdder.idIncident);
var regCarries = Component.getComponentByID(parmsAdder.idCarries);
if (!regResident) regResident = new Register();
if (!regIncident) regIncident = new Register();
if (!regCarries) regCarries = new Register();
//
// Now we can create the ParallelAdder object, record it, and wire it up to the associated document elements.
//
var adder = new ParallelAdder(parmsAdder, regResident, regIncident, regCarries);
initParallelAdderControls(adder, eAdder);
}
}
/*
* initParallelAdderControls(reg, eReg)
*
* For each Adder object created by initParallelAdders(), this function looks for any controls that have been defined
* along with the adder element in the current document, and "wires" them as needed.
*
* The following controls are supported:
*
* One optional 'output' control of class "status"
* One optional 'button' control of class "add"
* One optional 'button' control of class "test"
* One optional 'button' control of class "step" (if this exists, it will override any "step" setting above)
*/
function initParallelAdderControls(adder, eAdder)
{
adder.eAdder = eAdder;
var aeControls = Component.getElementsByClass(eAdder.parentNode, "controls");
for (var iControl = 0; iControl < aeControls.length; iControl++) {
var aeChildren = aeControls[iControl].childNodes;
for (var i=0; i < aeChildren.length; i++) {
var e = aeChildren[i];
if (e.nodeType != document.ELEMENT_NODE)
continue;
var sClass = e.getAttribute("class");
if (e.nodeName == "BUTTON" && sClass == "add") {
e.onclick = function() {
adder.stop();
addParallelValues(adder);
};
continue;
}
if (e.nodeName == "BUTTON" && sClass == "test") {
adder.eTest = e;
e.onclick = function() {addStartParallelTest(adder);};
continue;
}
if (e.nodeName == "BUTTON" && sClass == "step") {
adder.setStep(e);
continue;
}
if (e.nodeName == "DIV" && sClass == "status") {
adder.setStatusUpdate(
function(e) {
return function(s) {
e.innerHTML = (s? "<span>"+s+"</span>" : "");
};
}(e)
);
continue;
}
}
}
}
/*
* Function called by the anonymous click handler for the "Add" button.
*/
function addParallelValues(adder)
{
adder.stop();
var vResident = adder.regResident.getValue();
// adder.printStatus("Resident register loaded: " + vResident);
var vIncident = adder.regIncident.getValue();
// adder.printStatus("Incident register loaded: " + vIncident);
adder.add(function() {
var s1 = "Success", s2 = "==";
var vResult = adder.regResident.getValue();
if (vResult != vResident + vIncident) {s1 = "Error"; s2 = "!=";}
adder.printStatus(s1 + ": result (" + vResult + ") " + s2 + " resident (" + vResident + ") + incident (" + vIncident + ")");
});
}
/*
* Function called by the anonymous click handler for the "Test" button.
*/
function addParallelTestValues(adder)
{
adder.cTests++;
var vResident = Math.random() * 0.5;
var vIncident = Math.random() * 0.5;
adder.regResident.writeValue(vResident, function() {
vResident = adder.regResident.getValue();
// adder.printStatus("Resident register loaded: " + vResident);
adder.regIncident.writeValue(vIncident, function() {
vIncident = adder.regIncident.getValue();
// adder.printStatus("Incident register loaded: " + vIncident);
adder.add(function() {
var vResult = adder.regResident.getValue();
adder.cTotalCycles += adder.cCarryCycles - 1; // we don't want to count the initial addition, just the addition of carries
adder.printStatus("Addition complete: " + vResult + " (" + adder.cCarryCycles + " carry cycles, " + (adder.cTotalCycles/adder.cTests) + " average)");
if (vResult != vResident + vIncident)
adder.printStatus("Error: result (" + vResult + ") != resident (" + vResident + ") + incident (" + vIncident + "): " + (vResident + vIncident));
else
if (adder.cTests < 1000) {
addParallelTestValues(adder);
return;
}
addStopParallelTest(adder);
});
});
});
}
function addStartParallelTest(adder)
{
adder.stop();
adder.eTest.innerHTML = "Stop";
adder.eTest.onclick = function() {addStopParallelTest(adder);};
adder.cTests = 0;
adder.cTotalCycles = 0;
addParallelTestValues(adder);
}
function addStopParallelTest(adder)
{
adder.stop();
adder.eTest.innerHTML = "Test";
adder.eTest.onclick = function() {addStartParallelTest(adder);};
}
/*
* Initialize all the components on the page.
*/
web.onInit(initParallelAdders);

View file

@ -0,0 +1,583 @@
/*
* register.js
* by Jeff Parsons, May 7, 2012
*/
/*
* Creation of a Register object is controlled by the following properties of the
* parmsReg object:
*
* nBits: number of bits
* signed: true if signed (default), false otherwise
* bit0Exp: the power-of-two for bit 0 (default is zero)
* labels: true for labels, false otherwise (default)
*
* A Register object can be as small as a single bit, and in fact, a 1-bit Register
* is exactly how you would create the equivalent of a Bit object. However, the more
* common use of this class is to create a bit array.
*
* Internally, the bit indexes of a register correspond to the array indexes of aBits
* (ie, aBits[0] contains the value for bit 0, aBits[1] is bit 1, etc). And the lowest
* bit index represents the lowest power-of-two of the value represented by the register.
*
* The display of a Register object "cell" is handled by the given updateBit() function:
*
* updateBit(iBit, f)
*
* If f is undefined, the cell will be blanked; otherwise, either a "0" or a "1" will be
* displayed. However, that's just the standard implementation; the caller is free to
* define any other behavior (Remember: a register shouldn't care what it looks like).
*
* Internally, there are also "helper" properties (eg, decimalValue) and methods
* (eg, writeValue()) used, for example, to help write data into the register.
* Here's a list of some of them (it's difficult to promise that this list will be kept
* up-to-date):
*
* decimalValue: a decimal floating-point value being written to the register
* decimalPower: a power-of-two used to help convert decimalValue to binary
* decimalBit: a bit index used to help convert decimalValue to binary
* decimalSave: saves the initial decimal value, for visual comparison purposes
*/
var MAX_FRACTIONAL_DIGITS = 12;
function Register(parmsReg, updateBit) {
Component.call(this, "Reg", parmsReg);
if (parmsReg === undefined) {
parmsReg = {nBits:40, signed:true, bit0Exp:0, labels:true};
}
this.aBits = new Array(parmsReg.nBits);
this.signed = parmsReg.signed;
this.bit0Exp = parmsReg.bit0Exp;
this.labels = parmsReg.labels;
/*
* BUGBUG: Compute a reasonable value for this based on how many significant decimal digits
* (ie, to the right of the decimal point) correspond to the smallest given negative power-of-two.
*/
this.fixedDigits = (this.bit0Exp < 0? MAX_FRACTIONAL_DIGITS : 0);
this.upperBound = Math.pow(2, this.bit0Exp + this.aBits.length - (this.signed? 1 : 0));
this.lowerBound = (this.signed? -this.upperBound : 0);
this.updateBit = (updateBit === undefined? function(iBit, f) {} : updateBit);
}
Component.subclass(Component, Register, {
/*
* getBits() is used for "direct" access to the bits; use readBit() and writeBit() to
* access and change individual bits when speed isn't important. Note that changing bits
* directly, as well as calling the write or copy functions, bypasses display updates, so
* use updateBit() or updateAll() to update the display of one bit or the entire register
* as needed. Alternatively, use modifyBit() or modifyAll() to both change and display a
* single bit or the entire register.
*/
count: function() {
return this.aBits.length;
},
getBits: function() {
return this.aBits;
},
readBit: function(iBit) {
return this.aBits[iBit];
},
writeBit: function(iBit, b) {
this.aBits[iBit] = b;
},
writeAll: function(b) {
for (var iBit=0; iBit < this.aBits.length; iBit++)
this.aBits[iBit] = b;
},
writeUndefined: function(b) {
for (var iBit=0; iBit < this.aBits.length; iBit++)
if (this.aBits[iBit] === undefined)
this.aBits[iBit] = b;
},
notBit: function(iBit) {
this.aBits[iBit] = !this.aBits[iBit];
},
notAll: function() {
for (var iBit=0; iBit < this.aBits.length; iBit++)
this.aBits[iBit] = !this.aBits[iBit];
},
copyAll: function(reg) {
for (var iBit=0; iBit < this.aBits.length; iBit++)
this.aBits[iBit] = reg.aBits[iBit];
},
updateAll: function() {
for (var iBit=0; iBit < this.aBits.length; iBit++)
this.updateBit(iBit, this.aBits[iBit]);
this.refreshLiveValue();
},
modifyBit: function(iBit, b) {
this.writeBit(iBit, b);
this.updateBit(iBit, b);
this.refreshLiveValue();
},
modifyAll: function(b) {
this.writeAll(b);
this.updateAll();
},
readValue: function() {
this.stopSteps();
this.printDecimal();
this.writeUndefined(false);
this.updateAll();
this.fPostOp = 0;
this.decimalSave = undefined;
this.decimalValue = 0;
this.decimalBit = this.aBits.length - 1;
if (this.signed && this.aBits[this.decimalBit]) {
this.fPostOp = -1;
}
this.decimalExp = this.bit0Exp + this.decimalBit;
this.decimalPower = Math.pow(2, this.decimalExp);
this.firstStep(this.stepCompareBitToPower);
return this.decimalValue; // NOTE: this return value is valid ONLY if single-stepping has been disabled
},
writeValue: function(v, fnNotify) {
this.stopSteps();
this.modifyAll(undefined);
/*
* There are two obvious ways to handle negative values: one is to negate at the beginning,
* producing a positive value, and convert as we would any other positive value; when done,
* flip all the bits and add a bit at index 0 (ie, a traditional two's-complement conversion).
*
* However, this variation is better: make the value positive, subtract a bit at index 0,
* convert as before, and then flip all the bits. It doesn't matter what order we perform the
* two's-complement conversion steps, and performing a "pre-subtraction" against the input value
* is cheaper for us than performing a "post-addition" on the output value (because we can
* use internal math operations on the input value, whereas the output value is stored only as
* an array of bits).
*
* One downside: when stepping through the conversion process, it may seem odd to see the
* initial value modified ever so slightly (eg, -0.5 converted to 0.499999999998181). We could
* add an additional explicit step to clear up any potential confusion.
*/
this.fPostOp = 0;
this.decimalSave = v;
if (v < 0) {
v = -v;
v -= Math.pow(2, this.bit0Exp);
// BUGBUG: Assert that v is still positive (for tiny negative values of v, this will be a concern)
this.fPostOp = 1;
}
this.decimalValue = v;
this.decimalBit = this.aBits.length - 1;
if (this.signed) {
this.modifyBit(this.decimalBit, 0);
this.decimalBit--;
}
this.decimalExp = this.bit0Exp + this.decimalBit;
this.decimalPower = Math.pow(2, this.decimalExp);
this.firstStep(this.stepCompareDecimalToPower, fnNotify);
},
getValue: function() {
var decimalValue = 0;
var decimalBit = this.aBits.length - 1;
var fPostNegate = this.signed && this.aBits[decimalBit];
var decimalExp = this.bit0Exp + decimalBit;
var decimalPower = Math.pow(2, decimalExp);
do {
if (this.aBits[decimalBit])
decimalValue += decimalPower;
if (decimalBit == 0) break;
decimalBit--;
decimalPower /= 2;
} while (true);
if (fPostNegate) {
decimalValue = -(Math.pow(2, this.bit0Exp + this.aBits.length) - decimalValue);
}
return decimalValue;
},
setLiveUpdate: function(updateLiveValue) {
this.updateLiveValue = updateLiveValue;
this.refreshLiveValue();
},
refreshLiveValue: function() {
if (this.updateLiveValue) {
this.updateLiveValue(this.getValue());
}
},
printDecimal: function(v) {
if (this.updateDecimal !== undefined) {
/*
* We allow v to be undefined, as way as signalling that we are beginning a fresh
* conversion; we will be calling printDecimal() again at the completion of the conversion,
* and v will be defined at that point.
*/
this.updateDecimal(v);
if (v !== undefined && this.log)
console.log(this.toString() + ": updated decimal value to " + v.toFixed(this.fixedDigits));
}
},
setDecimalUpdate: function(updateDecimal) {
this.updateDecimal = updateDecimal;
},
/*
* The following "step" functions implement writeValue().
*
* Once writeValue() has initialized all the internal decimal variables, it calls
* the first step indirectly, via firstStep(), which in turns invokes other
* steps, based on whether the current decimal power is greater than or equal to
* the current decimal value.
*/
stepCompareDecimalToPower: function(n) {
this.printStep(n, "Comparing decimal value (" + this.decimalValue + ") to 2<sup>" + this.decimalExp + "</sup> (" + this.decimalPower.toFixed(20) + ")");
if (this.decimalValue >= this.decimalPower) {
this.addStep(this.stepSetDecimalBit);
this.addStep(this.stepReduceDecimalValue);
}
else {
this.addStep(this.stepClearDecimalBit);
}
if (!this.addStep(this.stepReduceDecimalPower))
return false;
return true;
},
stepSetDecimalBit: function(n) {
this.printStep(n, "Setting bit " + this.decimalBit);
this.writeBit(this.decimalBit, true);
if (n !== undefined) {
this.updateBit(this.decimalBit, true);
this.refreshLiveValue();
}
return true;
},
stepClearDecimalBit: function(n) {
this.printStep(n, "Clearing bit " + this.decimalBit);
this.writeBit(this.decimalBit, false);
if (n !== undefined) {
this.updateBit(this.decimalBit, false);
this.refreshLiveValue();
}
return true;
},
stepReduceDecimalValue: function(n) {
this.printStep(n, "Reducing decimal value by 2<sup>" + this.decimalExp + "</sup> (" + this.decimalPower.toFixed(20) + ")"); // this.decimalPower.toFixed(this.fixedDigits));
this.decimalValue -= this.decimalPower;
if (n !== undefined) this.printDecimal(this.decimalValue);
return true;
},
stepReduceDecimalPower: function(n) {
if (this.decimalBit == 0) {
var sStep = "Processed bit 0";
if (this.fPostOp > 0) {
sStep = "Inverting all bits";
this.notAll();
this.updateAll();
}
else if (this.fPostOp < 0) {
sStep = "Negating result";
this.decimalValue = -(Math.pow(2, this.bit0Exp + this.aBits.length) - this.decimalValue);
}
this.printStep(n, sStep + ", conversion" + (this.decimalSave !== undefined? " of " + this.decimalSave.toFixed(this.fixedDigits) : "") + " complete");
if (n === undefined) {
/*
* Since the conversion was performed without single-stepping, we need to update the register via
* updateAll() if this was a writeValue() operation (ie, decimalSave is defined); similarly, we need
* to update the decimal value via printDecimal() if this was a readValue() operation.
*/
if (this.decimalSave !== undefined)
this.updateAll();
}
/*
* printDecimal() may have never been called during the conversion, since we only call it when the value has been
* reduced. So we always print at the end.
*/
this.printDecimal(this.decimalValue);
return false;
}
this.printStep(n, "Reducing power-of-two");
this.decimalBit--;
this.decimalExp--;
this.decimalPower /= 2;
return true;
},
/*
* The following "step" functions implement readValue().
*
* Because we take the same "top-down" approach that writeValue() took (ie, from highest power/left-most bit down to
* lowest power/right-most bit), we can use the same stepReduceDecimalPower step function that writeValue() used; both
* procedures stop after they've processed bit 0.
*/
stepCompareBitToPower: function(n) {
this.printStep(n, "Testing bit " + this.decimalBit);
if (this.aBits[this.decimalBit])
this.addStep(this.stepIncreaseDecimalValue);
if (!this.addStep(this.stepReduceDecimalPower))
return false;
return true;
},
stepIncreaseDecimalValue: function(n) {
this.printStep(n, "Increasing decimal value by 2<sup>" + this.decimalExp + "</sup> (" + this.decimalPower.toFixed(20) + ")"); // this.decimalPower.toFixed(this.fixedDigits));
this.decimalValue += this.decimalPower;
if (n !== undefined) this.printDecimal(this.decimalValue);
return true;
}
});
/*
* initRegisters()
*
* Initializes all the necessary HTML to construct every register as spec'ed.
*
* This function operates on every element (e) of class "register" and inserts
* the appropriate HTML child elements of class "bitCell".
*
* Note that each element (e) of class "register" is expected to have a "data-value"
* attribute containing the same JSON-encoded parameters that the Register constructor
* expects.
*/
function initRegisters()
{
var aeRegs = Component.getElementsByClass(window.document, "register");
for (var iReg=0; iReg < aeRegs.length; iReg++) {
var eReg = aeRegs[iReg];
var parmsReg = Component.getComponentParms(eReg);
var sHTML = "";
var nExp = parmsReg.bit0Exp + parmsReg.nBits - 1;
for (var iCell=0; iCell < parmsReg.nBits; iCell++,nExp--) {
var sLabel = "";
sBitClass = "bitCell";
if (iCell == 0) {
sBitClass += " bitCellLeft";
if (parmsReg.signed) sLabel = "+/-";
}
var sCellID = "r" + iReg + "c" + iCell;
var sCell = "<div id=\"" + sCellID + "\" class=\"" + sBitClass + "\"></div>\n";
if (!parmsReg.labels) {
sHTML += sCell;
}
else {
if (!sLabel) sLabel = "2<sup>" + nExp + "</sup>";
sHTML += "<div class=\"bitBucket\">\n" + sCell + "<div class=\"bitLabel\">" + sLabel + "</div>\n</div>\n";
}
}
eReg.innerHTML = sHTML;
if (parmsReg.id) {
eReg.setAttribute("id", "reg" + parmsReg.id);
}
//
// Now that all the document elements have been defined, create an array that refers
// to all "bitCell" elements in bit index order (ie, reverse of display order).
//
var aeBits = [];
var aeCells = Component.getElementsByClass(eReg, "bitCell");
for (var i=aeCells.length-1; i >= 0; i--) {
aeBits.push(aeCells[i]);
}
//
// Now we can create the Register object, record it, and wire it up to the associated document elements.
//
var reg = new Register(parmsReg, function(aeBitsParm) {
return function(iBit, f) {
var s = (f===undefined? " " : (f? "1":"0"));
aeBitsParm[iBit].innerHTML = s;
};
}(aeBits)
);
for (var i=0; i < aeBits.length; i++) {
aeBits[i].onclick = function(regParm, iParm) {
//
// If we defined the onclick handler below as "function(e)" instead of simply "function()", then we could
// also receive an event object (e); however, IE reportedly requires that we examine a global (window.event)
// instead. If that's true, and if we ever care to get more details about the click event, then we might
// have to worry about that (eg, define a local var: "var event = window.event || e").
//
return function() {
toggleRegisterBit(regParm, iParm);
};
}(reg, i);
}
initRegisterControls(reg, eReg);
//
// For testing purposes, we could tweak a few of the bits, just to see if all the "wiring" works.
//
// reg.modifyBit(7, true);
// reg.modifyBit(9, false);
//
}
}
/*
* initRegisterControls(reg, eReg)
*
* For each Register object created by initRegisters(), this function looks for any controls that have been defined
* along with the register element in the current document, and "wires" them as needed.
*
* The following controls are supported:
*
* One optional 'input' control of class "value"
* One optional 'output' control of class "value"
* One optional 'output' control of class "status"
* One optional 'button' control of class "random"
* One optional 'button' control of class "write"
* One optional 'button' control of class "read"
* One optional 'button' control of class "clear"
* One optional 'button' control of class "step" (if this exists, it will override any "step" setting above)
*/
function initRegisterControls(reg, eReg)
{
var aeControls = Component.getElementsByClass(eReg.parentNode, "controls");
for (var iControl = 0; iControl < aeControls.length; iControl++) {
var aeChildren = aeControls[iControl].childNodes;
var eDecimal = null, eRandom = null, eWrite = null, eRead = null;
for (var i=0; i < aeChildren.length; i++) {
var e = aeChildren[i];
if (e.nodeType != document.ELEMENT_NODE)
continue;
var sClass = e.getAttribute("class");
if (e.nodeName == "INPUT" && sClass == "value") {
eDecimal = e;
reg.setDecimalUpdate(
function(e) {
return function(v) {
e.value = (v !== undefined? v.toFixed(reg.fixedDigits) : "");
};
}(e)
);
continue;
}
if (e.nodeName == "DIV" && sClass == "value") {
reg.setLiveUpdate(
function(e) {
return function(v) {
e.innerHTML = (v !== undefined? "<span>Live value: " + v.toFixed(reg.fixedDigits) + "</span>" : "");
};
}(e)
);
continue;
}
if (e.nodeName == "DIV" && sClass == "status") {
reg.setStatusUpdate(
function(e) {
return function(s) {
e.innerHTML = (s? "<span>" + s + "</span>" : "");
};
}(e)
);
continue;
}
if (e.nodeName == "BUTTON" && sClass == "random") {
eRandom = e;
continue;
}
if (e.nodeName == "BUTTON" && sClass == "write") {
eWrite = e;
continue;
}
if (e.nodeName == "BUTTON" && sClass == "read") {
eRead = e;
continue;
}
if (e.nodeName == "BUTTON" && sClass == "clear") {
e.onclick = function() { clearRegisterValue(reg); };
continue;
}
if (e.nodeName == "BUTTON" && sClass == "step") {
reg.setStep(e);
continue;
}
}
if (eRandom && eDecimal) {
eRandom.onclick = function(eDecimal) {
return function() { randomizeRegisterValue(reg, eDecimal); };
}(eDecimal);
}
if (eWrite && eDecimal) {
eWrite.onclick = function(eDecimal) {
return function() { writeRegisterValue(reg, eDecimal); };
}(eDecimal);
}
if (eRead) {
eRead.onclick = function(eDecimal) {
return function() { readRegisterValue(reg, eDecimal); };
}(eDecimal);
}
}
}
/*
* Function called by the anonymous click handler for all of the individual register "bitCell" elements.
*/
function toggleRegisterBit(reg, iBit)
{
reg.modifyBit(iBit, reg.readBit(iBit) === false);
}
/*
* Function called by the anonymous click handler for the "Random" button.
*
* It calls reg.stopSteps() to stop any internal operation currently in progress (eg,
* a previous writeValue() or readValue() operation), but it doesn't attempt to write the new value into
* the register; that's the "Write" button's job, once the user chooses to accept the new value.
*/
function randomizeRegisterValue(reg, eDecimal)
{
/*
* NOTE: eDecimal is an <input> element, so set the "value" property rather than the "innerHTML" property.
*/
reg.stopSteps();
eDecimal.value = Math.random().toFixed(reg.fixedDigits);
}
/*
* Function called by the anonymous click handler for the "Write" button.
*
* This takes whatever input value the user has entered (either manually or by clicking the "Random" button)
* and calls reg.writeValue() to begin the process of converting the decimal floating-point value to binary and
* writing the result to the register.
*
* If we ever add a user control (eg, a drop-down list) to select a step delay, then we can call setStep()
* to change the delay from whatever default delay was selected. Note that a setStep() delay of 0 disables all
* single-step output, allowing the operation to run at full speed.
*/
function writeRegisterValue(reg, eDecimal)
{
reg.stopSteps();
var v = parseFloat(eDecimal.value);
if (isNaN(v))
v = 0;
if (v >= reg.lowerBound && v < reg.upperBound) {
reg.writeValue(v);
}
else {
reg.printStatus("Error: decimal value " + v + " out of bounds (" + reg.lowerBound + " <= v < " + reg.upperBound + ")");
}
}
/*
* Function called by the anonymous click handler for the "Read" button.
*
* This starts the reg.readValue() procedure, which does return a value, but it's meaningful only if
* single-stepping has been disabled. It doesn't matter, because either way, reg.readValue() insures that
* that the decimal field is zeroed at the beginning of the procedure and updated at the end (not to mention
* intermediate intervals if single-stepping is enabled), so there's no need to update the field here.
*
* If we ever add a user control (eg, a drop-down list) to select a step delay, then we can call setStep()
* to change the delay from whatever default delay was selected. Note that a setStep() delay of 0 disables all
* single-step output, allowing the operation to run at full speed.
*/
function readRegisterValue(reg, eDecimal)
{
reg.stopSteps();
reg.readValue();
}
/*
* Function called by the anonymous click handler for the "Clear" button.
*/
function clearRegisterValue(reg)
{
reg.stopSteps();
reg.modifyAll(false);
reg.printDecimal();
reg.printStatus();
}
/*
* Initialize all the registers on the page.
*/
web.onInit(initRegisters);

View file

@ -0,0 +1,228 @@
/*
* selector.js
* by Jeff Parsons, May 24, 2012
*/
/*
* Creation of a Selector object is controlled by the following properties of the
* parmsSel object:
*
* nGates: number of gates
* color: color of the gate(s)
* single: true if single gate image to be used, false otherwise (default)
* idSource: ID of the source (eg, component with matching number of bits)
* idTarget: ID of the target (eg, component with matching number of bits)
* sourceStart: starting bit index of source corresponding to gate index 0
* targetStart: starting bit index of target corresponding to gate index 0
*
* However, by the time we're called, initSelectors() has already looked up the
* corresponding source and target components by the above IDs (so that we don't
* have to be involved in the "wiring" process) and passes them to us as:
*
* regSource
* regTarget
*
* A Selector object can be a single gate or an array of gates. The behavior of
* these gates is extensible, but by default, gates do nothing more than propagate
* bits from a "source" input to a "target" output whenever they are "selected".
*
* Internally, the gate indexes of a Selector correspond to the array indexes of
* aGates (ie, aGates[0] contains the [source, target, selected] triplet for gate
* index 0). The source, target and selected values for each gate triplet are stored
* at positions GATE_SOURCE, GATE_TARGET, and GATE_SELECTED.
*
* The display of individual gates is handled by the given updateGate() function:
*
* updateGate(iGate, f)
*
* If f is true, the gate should be displayed in an "selected" state, otherwise it
* should be displayed in a "deselected" state.
*/
function Selector(parmsSel, regSource, regTarget, updateGate) {
Component.call(this, "Sel", parmsSel);
this.aGates = new Array(parmsSel.nGates);
this.selected = false; // the selected state of the entire selector
this.single = (parmsSel.single === true);
this.source = regSource;
this.target = regTarget;
var sourceIndex = (parmsSel.sourceStart? parmsSel.sourceStart : 0);
var targetIndex = (parmsSel.targetStart? parmsSel.targetStart : 0);
var sourceCount = (this.source? this.source.count() : -1); // bit index limit for the given source
var targetCount = (this.target? this.target.count() : -1); // bit index limit for the given target
for (var iGate=0; iGate < this.aGates.length; iGate++) {
var s = sourceIndex;
var t = targetIndex;
if (sourceIndex < 0 || sourceIndex >= sourceCount) s = -1;
if (targetIndex < 0 || targetIndex >= targetCount) t = -1;
this.aGates[iGate] = [s, t, false];
sourceIndex++;
targetIndex++;
}
this.updateGate = (updateGate === undefined? function(iGate, f) {} : updateGate);
this.updateAll();
}
var GATE_SOURCE = 0;
var GATE_TARGET = 1;
var GATE_SELECTED = 2;
Component.subclass(Component, Selector, {
count: function() {
return this.aGates.length;
},
selectGate: function(iGate, f) {
this.aGates[iGate][GATE_SELECTED] = f;
if (f) {
var sourceIndex = this.aGates[iGate][GATE_SOURCE];
var targetIndex = this.aGates[iGate][GATE_TARGET];
if (sourceIndex >= 0 && targetIndex >= 0)
this.target.writeBit(targetIndex, this.source.readBit(sourceIndex));
}
},
selectAll: function(f) {
this.selected = f;
for (var iGate=0; iGate < this.aGates.length; iGate++) {
this.selectGate(iGate, f);
}
},
updateAll: function() {
for (var iGate=0; iGate < this.aGates.length; iGate++)
this.updateGate(iGate, this.aGates[iGate][GATE_SELECTED]);
this.target.updateAll();
}
});
/*
* initSelectors()
*
* Initializes all the necessary HTML to construct every Selector gate or
* gate-array as spec'ed.
*
* This function operates on every element (e) of class "selector" and inserts
* the appropriate HTML child elements of class "gate".
*
* Note that each element (e) of class "selector" is expected to have a "data-value"
* attribute containing the same JSON-encoded parameters that the Selector constructor
* expects.
*/
function initSelectors()
{
var aeSels = Component.getElementsByClass(window.document, "selector");
for (var iSel=0; iSel < aeSels.length; iSel++) {
var eSel = aeSels[iSel];
var parmsSel = Component.getComponentParms(eSel);
//
// Let's find the specified source/target components next, because if nGates isn't defined,
// then we will set a default value equal to the number of bits in the source component, if any.
//
var regSource = Component.getComponentByID(parmsSel.idSource);
var regTarget = Component.getComponentByID(parmsSel.idTarget);
if (parmsSel.nGates === undefined) {
parmsSel.nGates = (regSource? regSource.count() : 1);
}
var nGate = 1;
var sHTML = "";
if (parmsSel.single) {
sHTML += "<div class=\"gate gateLarge\"><img src=\"/my_modules/shared/images/selector.png\"/></div>\n";
}
else {
for (var iGate=0; iGate < parmsSel.nGates; iGate++,nGate++) {
sHTML += "<div class=\"gate gateSmall\"><img src=\"/my_modules/shared/images/selector.png\"/></div>\n";
}
}
eSel.innerHTML = sHTML;
if (parmsSel.id) {
eSel.setAttribute("id", "sel" + parmsSel.id);
}
//
// Now that all the document elements have been defined, we can
// create an array that refers to all "gate" elements in bit index order
// (ie, reverse of display order).
//
var aeGates = [];
var aeCells = Component.getElementsByClass(eSel, "gate");
for (var i=aeCells.length-1; i >= 0; i--) {
aeGates.push(aeCells[i]);
}
//
// Now we can create the Selector object, record it, and wire it up to the associated document elements.
//
var sel = new Selector(parmsSel, regSource, regTarget, function(ae, color, single) {
return function(iGate, f) {
var s = (f===true? "white": color);
ae[single?0:iGate].style.backgroundColor = s;
};
}(aeGates, parmsSel.color, parmsSel.single)
);
for (var i=0; i < aeGates.length; i++) {
aeGates[i].onclick = function (selParm) {
return function() {
selectSelector(selParm);
};
}(sel);
}
initSelectorControls(sel, eSel);
//
// For testing purposes, we could tweak a few of the gates, just to see if all the "wiring" works.
//
// sel.selectGate(7, true);
// sel.selectGate(9, false);
//
}
if (aeSels.length == 0)
console.log("warning: no selectors on page");
}
/*
* initSelectorControls(sel, eSel)
*
* For each Selector object created by initSelectors(), this function looks for any controls that have been defined
* along with the selector element in the current document, and "wires" them as needed.
*
* The following controls are supported:
*
* One optional 'button' control of class "select"
*/
function initSelectorControls(sel, eSel)
{
var aeControls = Component.getElementsByClass(eSel.parentNode, "controls");
for (var iControl = 0; iControl < aeControls.length; iControl++) {
var aeChildren = aeControls[iControl].childNodes;
for (var i=0; i < aeChildren.length; i++) {
var e = aeChildren[i];
if (e.nodeType != document.ELEMENT_NODE)
continue;
var sClass = e.getAttribute("class");
if (e.nodeName == "BUTTON" && sClass == "select") {
e.onclick = function(e) {
return function() { selectSelector(sel, e); };
}(e);
continue;
}
}
}
}
/*
* Function called by the anonymous click handlers for the "Select" button.
*/
function selectSelector(sel, e)
{
sel.selectAll(!sel.selected);
sel.updateAll();
if (e !== undefined) {
e.innerHTML = (f? "Deselect" : "Select");
}
}
/*
* Initialize all the selectors on the page.
*/
web.onInit(initSelectors);

View file

@ -0,0 +1,207 @@
/*
* stepper.js
* by Jeff Parsons, June 24, 2012
*
* Stepper methods extracted from the original Component class.
*/
/*
* The Stepper class defines a set of stepping functions used to help drive multi-step
* operations that a component may want to "single-step." The setStep() function determines
* the delay, if any, between the steps. If there is no delay, then all the supplied
* "step" functions are called directly. Otherwise, any delay (even a minimal delay of 0ms)
* results in the creation and queuing of step objects. Every step object contains:
*
* fn: step function pointer
* n: step number
*
* and is stored in a simple array (aStep) used to enqueue/dequeue the step objects.
*
* firstStep() gets the ball rolling by queuing the first "step" function. That initial
* "step" function, as well as any or all subsequent "step" functions, call addStep()
* to add more steps to the queue as needed. As each "step" function successfully finishes
* (by returning true), the next "step" function is dequeued and called. As soon as a
* "step" function returns false, the dequeuing process stops, and any remaining steps
* in the queue are ignored. If the queue empties before that happens, then we essentially
* repeat firstStep(), and the process continues.
*
* If a negative millisecond delay (-1) has been set via setStep(), then both firstStep()
* and addStep() call their respective "step" functions directly, instead of queuing them.
* That option should be used only for very brief steps, otherwise the browser will appear
* to hang if the stepping functions never yield.
*
* Also, when "step" functions are called directly, the step number (n) is omitted from the
* calls, so that the "step" function can quickly determine whether to bypass its internal print
* operations. printStep() will automatically bypass, since it has access to the step number,
* but if you want to maximize speed, then either always check for an undefined step number, or
* use separate "step" functions that omit both the step number and the print operations altogether.
*
* NOTE: the stepping functions use printStatus() indirectly, via printStep(). However,
* there may be times when a component wants to reserve its "status" control for more pertinent
* messages. In those cases, the component can pass a "quiet" setting to setStep().
*/
function Stepper() {
}
Stepper.prototype = {
/*
* initStep(parms) accepts any or all of the following parameter (parms) properties:
*
* step: millisecond step setting (0 for minimum delay when single-stepping, -1 for direct calls instead of steps)
*/
initStep: function(parms) {
this.msStep = parms.step;
this.quiet = (this.msStep == 0);
},
/*
* setStep(ms, quiet)
*
* ms can be any of:
*
* 1) non-negative number of milliseconds
* 2) -1 to call all step functions directly without delay
* 3) an HTML element (eg, a button) that will control stepping via its "onclick" handler
*
* Note that even 0 is a supported millisecond delay, albeit a minimal one, insuring that scripts
* don't run too long without yielding.
*
* If quiet, then printStep() messages will not be passed to printStatus(); however, if logging is
* enabled (refer to this.fLog), printStep() messages will still be logged.
*/
setStep: function(ms, quiet) {
this.msStep = ms;
this.quiet = quiet;
},
stopSteps: function() {
if (this.timerSteps !== undefined)
clearTimeout(this.timerSteps);
if (this.timerNotify !== undefined)
clearTimeout(this.timerNotify);
this.aSteps = [];
this.cSteps = 0;
this.timerSteps = undefined;
this.fnNotify = undefined;
this.timerNotify = undefined;
if (!this.quiet) this.printStatus(); // "clear" the status field, if any, as well
},
firstStep: function(fn, fnNotify) {
this.fnNotify = fnNotify;
this.timerNotify = undefined;
if (this.msStep == -1) {
while (fn.call(this))
;
if (this.fnNotify) this.timerNotify = setTimeout(this.fnNotify, 0);
return;
}
this.kickStep(fn);
this.nextStep(0);
},
kickStep: function(fn) {
this.addStep(function(n) {
// this.log(this.toString() + ": Step " + n + ": automatic kickStep");
if (!fn.call(this, n))
return false;
return this.kickStep(fn);
});
return true;
},
addStep: function(fn) {
if (this.msStep == -1) {
return fn.call(this);
}
var step = {fn:fn, n:++this.cSteps};
this.aSteps.push(step);
return true;
},
removeStep: function($this) {
$this.timerSteps = undefined;
if (typeof $this.msStep == "object")
$this.msStep.onclick = null;
var step = $this.aSteps.shift();
if (step === undefined)
return false;
return $this.doStep(step);
},
doStep: function(step) {
if (!step.fn.call(this, step.n)) {
if (this.fnNotify) this.timerNotify = setTimeout(this.fnNotify, 0);
return false;
}
return this.nextStep(this.msStep);
},
nextStep: function(ms) {
if (this.aSteps.length == 0) {
console.warn("Component.nextStep(): unexpected end of steps");
return false;
}
if (typeof ms == "number") {
/*
* IE doesn't support:
*
* setTimeout(this.removeStep, ms, this);
*
* so we're forced to do this instead:
*
* var thisParm = this;
* setTimeout(function() {thisParm.removeStep(thisParm);}, ms);
*/
var thisParm = this;
this.timerSteps = setTimeout(function() {thisParm.removeStep(thisParm);}, ms);
}
else
if (typeof ms == "object") {
ms.onclick = function(regParm) {
/*
* If we defined the onclick handler below as "function(e)" instead of simply "function()", then we could
* also receive an event object (e); however, IE reportedly requires that we examine a global (window.event)
* instead. If that's true, and if we ever care to get more details about the click event, then we might
* have to worry about that (eg, define a local var: "var event = window.event || e").
*/
return function() {
return regParm.removeStep(regParm);
};
}(this);
}
else
alert("unexpected step parameter (" + ms + ")");
return true;
},
printStep: function(n, s) {
if (n !== undefined) {
if (!this.quiet)
this.printStatus("Step " + n + ": " + s);
else if (this.fLog)
this.log(this.toString() + ": Step " + n + ": " + s);
}
},
/*
* printStatus(s)
*
* Passes any string (s) to the associated HTML element of class "status", if any;
* pass a blank string, or nothing at all, to clear the contents of the associated
* "status" element.
*
* If there's no "status" element associated with this component, then status messages
* are simply thrown away. However, if logging is enabled (ie, parms.log was true),
* all status messages are still logged.
*/
printStatus: function(s) {
if (this.updateStatus !== undefined) {
this.updateStatus(s);
}
if (this.fLog && s) {
this.log(this.toString() + ": " + s);
}
},
/*
* setStatusUpdate(s)
*
* Sets the "updateStatus" handler that printStatus() uses to display any status messages.
*/
setStatusUpdate: function(updateStatus) {
this.updateStatus = updateStatus;
}
};
Component.extend(Component.prototype, Stepper.prototype);

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- author="Jeff Parsons (@jeffpar)" website="http://www.pcjs.org/" created="2012-05-05" modified="2012-08-28" license="http://www.gnu.org/licenses/gpl.html" -->
<!DOCTYPE xsl:stylesheet [
<!-- XSLT understands these entities only: lt, gt, apos, quot, and amp. Other useful entities are defined below (see entities.dtd). -->
<!ENTITY nbsp "&#160;"> <!ENTITY sect "&#167;"> <!ENTITY copy "&#169;"> <!ENTITY para "&#182;"> <!ENTITY ndash "&#8211;"> <!ENTITY mdash "&#8212;">
<!ENTITY lsquo "&#8216;"> <!ENTITY rsquo "&#8217;"> <!ENTITY ldquo "&#8220;"> <!ENTITY rdquo "&#8221;"> <!ENTITY dagger "&#8224;"> <!ENTITY Dagger "&#8225;">
]>
<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:output doctype-system="about:legacy-compat"/>
<xsl:include href="../../../my_modules/shared/templates/common.xsl"/>
<xsl:include href="components.xsl"/>
<xsl:include href="register.xsl"/>
<xsl:template match="/">
<html lang="en">
<head>
<title>jsmachines.net</title>
<xsl:call-template name="commonStyles"/>
<xsl:call-template name="componentIncludes"><xsl:with-param name="component" select="'components'"/></xsl:call-template>
<xsl:if test="//register">
<xsl:call-template name="registerIncludes"/>
</xsl:if>
</head>
<body>
<div class="page justified">
<xsl:apply-templates/>
<h4>Return to <a href="/outline.xml">Outline</a></h4>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

View file

@ -0,0 +1,79 @@
.component:before, .component:after {
content:"";
display:table;
}
.component:after {
clear:both;
}
.component {
zoom:1; /* For IE 6/7 (trigger hasLayout) */
font-size: medium;
}
.component .label {
margin-bottom: 2px;
}
.label.left {
float: left;
margin-right: 1em;
width: 2em;
}
.component .container {
display: inline-block;
}
.controls, .controls div, .controls input, .controls button {
float: left;
margin: 2px;
margin-left: 0px;
}
input.value {
width: 16em;
height: 1em;
font-size: medium;
border: 1px solid black;
line-height: 1em; /* the equivalent of "vertical-align: middle" for single-line elements */
text-align: right;
}
.status, .value {
vertical-align: middle;
}
.status span, .value span {
font-size: small;
vertical-align: 10%;
}
.status sup {
vertical-align: baseline;
position: relative;
bottom: .33em;
}
.status sub {
vertical-align: baseline;
position: relative;
bottom: -.33em;
}
.selector {
float: left;
border: 1px solid black;
background-color: white;
}
.selector:before, .selector:after {
content:"";
display:table;
}
.selector:after {
clear:both;
}
/* For IE 6/7 (trigger hasLayout) */
.selector {
zoom:1;
}
.gateSmall, .gateLarge {
float: left;
}
.gateSmall, .gateSmall img {
width: 19px;
height: 19px;
}
.gateLarge, .gateLarge img {
width: 38px;
height: 38px;
}

View file

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- author="Jeff Parsons (@jeffpar)" website="http://www.pcjs.org/" created="2012-05-05" modified="2014-03-28" 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 entities.dtd). -->
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="componentStyles">
<xsl:param name="component"></xsl:param>
<link rel="stylesheet" type="text/css" href="/my_modules/ecpjs-client/templates/{$component}.css"/>
</xsl:template>
<xsl:template name="componentScripts">
<xsl:param name="component"></xsl:param>
<xsl:choose>
<xsl:when test="$component = 'components'">
<script type="text/javascript" src="/my_modules/shared/lib/component.js"></script>
<script type="text/javascript" src="/my_modules/ecpjs-client/lib/stepper.js"></script>
</xsl:when>
<xsl:otherwise>
<script type="text/javascript" src="/my_modules/ecpjs-client/lib/{$component}.js"></script>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="componentIncludes">
<xsl:param name="component"></xsl:param>
<xsl:call-template name="componentStyles"><xsl:with-param name="component" select="$component"/></xsl:call-template>
<xsl:call-template name="componentScripts"><xsl:with-param name="component" select="$component"/></xsl:call-template>
</xsl:template>
<xsl:template match="component[@ref]">
<xsl:variable name="component" select="name(.)"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)"/>
</xsl:template>
<xsl:template match="component[not(@ref)]">
<xsl:call-template name="component">
<xsl:with-param name="class" select="@class"/>
<xsl:with-param name="parms" select="@parms"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="component">
<xsl:param name="class"></xsl:param>
<xsl:param name="parms">none:null</xsl:param>
<xsl:variable name="id">
<!-- Values allowed: ID of component, blank if none (default) -->
<xsl:choose>
<xsl:when test="@id"><xsl:value-of select="@id"/></xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="name">
<!-- Values allowed: name of component, blank if none (default) -->
<xsl:choose>
<xsl:when test="name"><xsl:value-of select="name"/></xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="pos">
<!-- Values allowed: left (default), center, or blank for no relative positional preference -->
<xsl:choose>
<xsl:when test="@pos"><xsl:value-of select="@pos"/></xsl:when>
<xsl:otherwise>left</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="step">
<!-- Values allowed: number of milliseconds per step, 0 for quick/quiet stepping with yields (default), -1 for no yields -->
<xsl:choose>
<xsl:when test="@step"><xsl:value-of select="@step"/></xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="log">
<!-- Values allowed: true to enable console logging, false to disable (default) -->
<xsl:choose>
<xsl:when test="@log"><xsl:value-of select="@log"/></xsl:when>
<xsl:otherwise>false</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<div class="component {$pos}">
<xsl:apply-templates select="name"/>
<div class="container">
<xsl:if test="control[@pos = 'top']">
<div class="controls">
<xsl:apply-templates select="control[@pos = 'top']" mode="component"/>
</div>
<div style="clear:both"></div>
</xsl:if>
<xsl:if test="control[@pos = 'left']">
<div class="controls">
<xsl:apply-templates select="control[@pos = 'left']" mode="component"/>
</div>
</xsl:if>
<div class="{$class}" data-value="id:'{$id}',name:'{$name}',step:{$step},log:{$log},{$parms}"></div>
<xsl:if test="control[@pos = 'right']">
<div class="controls">
<xsl:apply-templates select="control[@pos = 'right']" mode="component"/>
</div>
</xsl:if>
<xsl:if test="control[not(@pos)]">
<div style="clear:both"></div>
<div class="controls">
<xsl:apply-templates select="control[not(@pos)]" mode="component"/>
</div>
</xsl:if>
<xsl:if test="control[@pos = 'bottom']">
<div style="clear:both"></div>
<div class="controls">
<xsl:apply-templates select="control[@pos = 'bottom']" mode="component"/>
</div>
</xsl:if>
</div>
</div>
</xsl:template>
<xsl:template match="name">
<xsl:variable name="pos">
<xsl:choose>
<xsl:when test="@pos"><xsl:value-of select="@pos"/></xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<div class="label {$pos}"><xsl:apply-templates/></div>
</xsl:template>
<xsl:template match="control" mode="component">
<xsl:choose>
<xsl:when test="@type = 'input'">
<input class="{@class}" type="text" value=""/>
</xsl:when>
<xsl:when test="@type = 'output'">
<div class="{@class}"></div>
</xsl:when>
<xsl:when test="@type = 'button'">
<button class="{@class}"><xsl:value-of select="."/></button>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- author="Jeff Parsons (@jeffpar)" website="http://www.pcjs.org/" created="2012-05-05" modified="2012-08-28" license="http://www.gnu.org/licenses/gpl.html" -->
<!DOCTYPE xsl:stylesheet [
<!-- XSLT understands these entities only: lt, gt, apos, quot, and amp. Other useful entities are defined below (see entities.dtd). -->
<!ENTITY nbsp "&#160;"> <!ENTITY sect "&#167;"> <!ENTITY copy "&#169;"> <!ENTITY para "&#182;"> <!ENTITY ndash "&#8211;"> <!ENTITY mdash "&#8212;">
<!ENTITY lsquo "&#8216;"> <!ENTITY rsquo "&#8217;"> <!ENTITY ldquo "&#8220;"> <!ENTITY rdquo "&#8221;"> <!ENTITY dagger "&#8224;"> <!ENTITY Dagger "&#8225;">
]>
<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:output doctype-system="about:legacy-compat"/>
<xsl:include href="../../../my_modules/shared/templates/common.xsl"/>
<xsl:include href="components.xsl"/>
<xsl:include href="register.xsl"/>
<xsl:template match="/device">
<html lang="en">
<head>
<title>jsmachines.net</title>
<xsl:call-template name="commonStyles"/>
<xsl:call-template name="componentIncludes"><xsl:with-param name="component" select="'components'"/></xsl:call-template>
<xsl:if test="//register">
<xsl:call-template name="registerIncludes"/>
</xsl:if>
<xsl:if test="//component[@class='parallelAdder']">
<xsl:call-template name="componentScripts"><xsl:with-param name="component" select="'parallelAdder'"/></xsl:call-template>
</xsl:if>
<xsl:if test="//component[@class='selector']">
<xsl:call-template name="componentScripts"><xsl:with-param name="component" select="'selector'"/></xsl:call-template>
</xsl:if>
</head>
<body>
<div class="page justified">
<xsl:apply-templates/>
<h4>Return to <a href="/outline.xml">Outline</a></h4>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

View file

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- author="Jeff Parsons" creator="http://www.pcjs.org/" created="2012-05-05T19:53:00" modified="2012-05-05T19:53:00" license="http://creativecommons.org/licenses/by-nc-sa/3.0/us/" -->
<!DOCTYPE xsl:stylesheet [
<!-- XSLT understands these entities only: lt, gt, apos, quot, and amp. Other useful entities are defined below (see entities.dtd). -->
<!ENTITY nbsp "&#160;"> <!ENTITY sect "&#167;"> <!ENTITY copy "&#169;"> <!ENTITY para "&#182;"> <!ENTITY ndash "&#8211;"> <!ENTITY mdash "&#8212;">
<!ENTITY lsquo "&#8216;"> <!ENTITY rsquo "&#8217;"> <!ENTITY ldquo "&#8220;"> <!ENTITY rdquo "&#8221;"> <!ENTITY dagger "&#8224;"> <!ENTITY Dagger "&#8225;">
]>
<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:output doctype-system="about:legacy-compat"/>
<xsl:include href="../../../my_modules/shared/templates/common.xsl"/>
<!-- The next two lines were added to support embedding components in documents -->
<xsl:include href="components.xsl"/>
<xsl:include href="register.xsl"/>
<xsl:template match="/document">
<html lang="en">
<head>
<title><xsl:value-of select="title"/><xsl:text> | jsmachines.net</xsl:text></title>
<xsl:call-template name="commonStyles"/>
<!-- The next two lines were added to support embedding components in documents -->
<xsl:call-template name="componentIncludes"><xsl:with-param name="component" select="'components'"/></xsl:call-template>
<xsl:call-template name="registerIncludes"/>
</head>
<body>
<div class="page justified">
<h4 style="float:right">Return to <a href="/manuals/ecp/outline.xml">Outline</a></h4>
<xsl:call-template name="document"><xsl:with-param name="parent" select="'true'"/></xsl:call-template>
<h4>Return to <a href="/manuals/ecp/outline.xml">Outline</a></h4>
</div>
</body>
</html>
</xsl:template>
<xsl:template name="document">
<xsl:param name="parent">false</xsl:param>
<xsl:param name="ref"></xsl:param>
<xsl:if test="not(parent)">
<h1><xsl:value-of select="title"/><xsl:apply-templates select="title/footlink"/></h1>
<xsl:if test="author"><p><xsl:text>By </xsl:text><xsl:call-template name="authors"/></p></xsl:if>
<xsl:apply-templates select="date"/>
<xsl:apply-templates select="synopsis"/>
</xsl:if>
<xsl:if test="parent">
<xsl:choose>
<xsl:when test="$parent = 'true'">
<h2><xsl:apply-templates select="parent"/></h2>
<h3><xsl:apply-templates select="title"/></h3>
</xsl:when>
<xsl:otherwise>
<h2>
<xsl:choose>
<xsl:when test="$ref = ''"><xsl:apply-templates select="title"/></xsl:when>
<xsl:otherwise><a href="{$ref}"><xsl:apply-templates select="title"/></a></xsl:otherwise>
</xsl:choose>
</h2>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
<xsl:if test="excerpt">
<h4><xsl:apply-templates select="excerpt"/></h4>
</xsl:if>
<xsl:if test="@ref">
<xsl:choose>
<xsl:when test="contains(@ref,'.pdf')">
<a href="{@ref}"><img src="/my_modules/shared/images/pdf-192.jpg"/></a>
</xsl:when>
<xsl:otherwise>
<p>[<a href="{@ref}">Link</a>]</p>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
<xsl:if test="content">
<xsl:if test="content/p[@name]">
<h4><xsl:text>Discusses: </xsl:text>
<xsl:for-each select="content/p[@name]">
<xsl:if test="position() != 1"><xsl:text>, </xsl:text></xsl:if>
<a href="#{@id}"><xsl:value-of select="@name"/></a>
</xsl:for-each>
</h4>
</xsl:if>
<xsl:apply-templates select="content"/>
</xsl:if>
<xsl:apply-templates select="include"/>
<xsl:apply-templates select="video"/>
</xsl:template>
<xsl:template match="date">
<p><xsl:call-template name="formatDate"><xsl:with-param name="date" select="."/></xsl:call-template></p>
</xsl:template>
<xsl:template match="author">
<p><xsl:text>By </xsl:text><xsl:value-of select="."/></p>
</xsl:template>
<xsl:template match="synopsis">
<p><xsl:text>Synopsis: </xsl:text><em><xsl:value-of select="."/></em></p>
</xsl:template>
<xsl:template match="content">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="video">
<xsl:variable name="controls"><xsl:if test="@controls">controls</xsl:if></xsl:variable>
<div class="center">
<video width="{@width}" height="{@height}" controls="{$controls}">
<xsl:apply-templates select="source"/>
Your browser does not support HTML5 video play-back.
</video>
</div>
</xsl:template>
<xsl:template match="source">
<source src="{@src}" type="{@type}"/>
</xsl:template>
<xsl:template match="include">
<xsl:if test="@ref">
<xsl:variable name="documentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($documentFile)/document" mode="include">
<xsl:with-param name="parent" select="@parent"/>
<xsl:with-param name="link" select="@link"/>
<xsl:with-param name="ref" select="@ref"/>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template match="document" mode="include">
<xsl:param name="parent">false</xsl:param>
<xsl:param name="link">false</xsl:param>
<xsl:param name="ref"></xsl:param>
<xsl:choose>
<xsl:when test="$link = 'true'">
<xsl:if test="$parent = 'true'">
<h2><xsl:apply-templates select="parent"/></h2>
</xsl:if>
<h3><a href="{$ref}"><xsl:value-of select="title"/></a></h3>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="document"><xsl:with-param name="parent" select="$parent"/><xsl:with-param name="ref" select="$ref"/></xsl:call-template>
</xsl:otherwise>
</xsl:choose>
<hr/>
</xsl:template>
</xsl:stylesheet>

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- author="Jeff Parsons" creator="http://www.pcjs.org/" created="2012-05-05T19:53:00" modified="2012-05-05T19:53:00" license="http://creativecommons.org/licenses/by-nc-sa/3.0/us/" -->
<!DOCTYPE xsl:stylesheet [
<!-- XSLT understands these entities only: lt, gt, apos, quot, and amp. Other useful entities are defined below (see entities.dtd). -->
<!ENTITY nbsp "&#160;"> <!ENTITY sect "&#167;"> <!ENTITY copy "&#169;"> <!ENTITY para "&#182;"> <!ENTITY ndash "&#8211;"> <!ENTITY mdash "&#8212;">
<!ENTITY lsquo "&#8216;"> <!ENTITY rsquo "&#8217;"> <!ENTITY ldquo "&#8220;"> <!ENTITY rdquo "&#8221;"> <!ENTITY dagger "&#8224;"> <!ENTITY Dagger "&#8225;">
]>
<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:output doctype-system="about:legacy-compat"/>
<xsl:include href="../../../my_modules/shared/templates/common.xsl"/>
<xsl:template match="/outline">
<html lang="en">
<head>
<title><xsl:value-of select="title"/><xsl:text> | jsmachines.net</xsl:text></title>
<xsl:call-template name="commonStyles"/>
</head>
<body>
<div class="page">
<xsl:apply-templates/>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

View file

@ -0,0 +1,25 @@
.register {
float: left;
}
.bitBucket {
float: left;
width: 19px;
height: 38px;
}
.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 */
}
.bitCellLeft {
border-left: 1px solid black;
}
.bitLabel {
font-size: xx-small;
text-align: center;
}

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- author="Jeff Parsons (@jeffpar)" website="http://www.pcjs.org/" created="2012-05-05" modified="2014-03-28" 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 entities.dtd). -->
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="registerIncludes">
<link rel="stylesheet" type="text/css" href="/my_modules/ecpjs-client/templates/register.css"/>
<script type="text/javascript" src="/my_modules/ecpjs-client/lib/register.js"></script>
</xsl:template>
<xsl:template match="register[@ref]">
<xsl:variable name="component" select="name(.)"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)"/>
</xsl:template>
<xsl:template match="register[not(@ref)]">
<xsl:variable name="nBits">
<!-- Values allowed: positive integer defining number of bits, default of 40 (ECP default) -->
<xsl:choose>
<xsl:when test="nBits"><xsl:value-of select="nBits"/></xsl:when>
<xsl:when test="@nBits"><xsl:value-of select="@nBits"/></xsl:when>
<xsl:otherwise>40</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="signed">
<!-- Values allowed: true if left-most bit is sign bit (ECP default), false if not -->
<xsl:choose>
<xsl:when test="signed"><xsl:value-of select="signed"/></xsl:when>
<xsl:when test="@signed"><xsl:value-of select="@signed"/></xsl:when>
<xsl:otherwise>true</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="bit0Exp">
<!-- Values allowed: integer power of two corresponding to right-most bit, default of -39 (ECP default) -->
<xsl:choose>
<xsl:when test="bit0Exp"><xsl:value-of select="bit0Exp"/></xsl:when>
<xsl:when test="@bit0Exp"><xsl:value-of select="@bit0Exp"/></xsl:when>
<xsl:otherwise>-39</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="labels">
<!-- Values allowed: true if bits should be labeled, false if not (default)-->
<xsl:choose>
<xsl:when test="labels"><xsl:value-of select="labels"/></xsl:when>
<xsl:when test="@labels"><xsl:value-of select="@labels"/></xsl:when>
<xsl:otherwise>false</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:call-template name="component">
<xsl:with-param name="class">register</xsl:with-param>
<xsl:with-param name="parms">nBits:<xsl:value-of select="$nBits"/>,signed:<xsl:value-of select="$signed"/>,bit0Exp:<xsl:value-of select="$bit0Exp"/>,labels:<xsl:value-of select="$labels"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>

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,655 @@
/**
* @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()
*
* @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)";
/*
* 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.logError(new Error("no filename specified"));
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.
*
* @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
*
* @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)
*
* @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)
*
* @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)
*
* @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()).
*/
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.
*
* @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.
*
* @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)
*
* @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"
}
}

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/my_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 my_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("my_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();
},
};

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,58 @@
{
"boss": true,
"eqnull": true,
"evil": true,
"loopfunc": true,
"sub": true,
"globalstrict": true,
"globals": {
"APP_PCJS": true,
"APPNAME": false,
"APPVERSION": false,
"SITEHOST": false,
"DEBUG": false,
"MAXDEBUG": false,
"PCJSCLASS": true,
"DEBUGGER": true,
"PREFETCH": true,
"FASTDISABLE": 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,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": "/configs/pc/machines/5150/mda/64kb/machine.xml"
}

View file

@ -0,0 +1,365 @@
#!/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");
if (fDebug) {
/*
* TODO: Determine why a simple "console.log(obj)" call fails on the Video object
*/
console.log(JSON.stringify(obj));
}
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
});

View file

@ -0,0 +1,70 @@
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 objects (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 [x86code.js](x86code.js), [x86mode.js](x86mode.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-client/defines.js](defines.js)
* [pcjs-client/panel.js](panel.js)
* [pcjs-client/bus.js](bus.js)
* [pcjs-client/mem.js](mem.js)
* [pcjs-client/cpu.js](cpu.js)
* [pcjs-client/x86defs.js](x86defs.js)
* [pcjs-client/x86help.js](x86help.js)
* [pcjs-client/x86code.js](x86code.js)
* [pcjs-client/x86mode.js](x86mode.js)
* [pcjs-client/x86.js](x86.js)
* [pcjs-client/chipset.js](chipset.js)
* [pcjs-client/rom.js](rom.js)
* [pcjs-client/ram.js](ram.js)
* [pcjs-client/keyboard.js](keyboard.js)
* [pcjs-client/video.js](video.js)
* [pcjs-client/serial.js](serial.js)
* [pcjs-client/mouse.js](mouse.js)
* [pcjs-client/disk.js](disk.js)
* [pcjs-client/fdc.js](fdc.js)
* [pcjs-client/hdc.js](hdc.js)
* [pcjs-client/debugger.js](debugger.js)
* [pcjs-client/state.js](state.js)
* [pcjs-client/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) should 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.
What's Next
---
EGA support. I've added the infrastructure for EGA I/O operations, but there's no "meat" yet. All the EGA
code is in [video.js](video.js), alongside the CGA and MDA support, but EGA functionality is pretty isolated; you have
to set the "model" attribute of the video component to "ega" to enable it.

View file

@ -0,0 +1,773 @@
/**
* @fileoverview Implements the PCjs Bus component.
* @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";
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);
};
/**
* 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).
*/
this.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) {
this.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) {
this.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)
{
this.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;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

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}
*
* FASTDISABLE turns on memory-function switching to dynamically disable
* memory accesses whenever the CPU wants to disable spurious memory reads
* (or stale memory writes, which are worse). If FASTDISABLE is false, then
* the code falls back to setting/testing internal OP_NOREAD and OP_NOWRITE
* CPU opFlags as needed.
*
* At the moment, it seems that FASTDISABLE is a bit slower than relying on
* the OP_NOREAD/OP_NOWRITE flags, so it's turned off.
*
* See the Bus component for details.
*/
var FASTDISABLE = false;
/**
* @define {boolean}
*
* FATARRAYS is a Closure Compiler compile-time option that allocates 1 number per byte
* for Memory blocks; wasteful, but slightly faster.
*
* See the Memory component for details.
*/
var FATARRAYS = false;
/**
* TYPEDARRAYS enables use of typed arrays for Memory blocks. This used to be a compile-time
* option, but since I've added memory access functions for typed arrays (see Memory.afnTArray),
* I can turn the support on dynamically now. Originally, I didn't see much of a speed increase
* over the original (non-typed) implementation, but that will probably change over time.
*
* See the Memory component for details.
*/
var TYPEDARRAYS = (typeof window.ArrayBuffer !== 'undefined');
if (typeof module !== 'undefined') {
global.PCJSCLASS = PCJSCLASS;
global.DEBUGGER = DEBUGGER;
global.PREFETCH = PREFETCH;
global.FASTDISABLE = FASTDISABLE;
global.FATARRAYS = FATARRAYS;
global.TYPEDARRAYS = TYPEDARRAYS;
/*
* TODO: When we're "required" by Node, should we return anything via module.exports?
*/
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,586 @@
/**
* @fileoverview Implements the PCjs "physical" Memory component.
* @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.
*/
/*
* 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");
}
/**
* @class DataView
* @property {function(number,boolean):number} getUint16
* @property {function(number,number,boolean)} setUint16
*/
/**
* 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 print(), for example, you must
* rely on class methods like Component.println() rather than object methods like this.println().
*
* 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
* 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;
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 window.ArrayBuffer(size);
this.ab = new window.Uint8Array(this.buffer, 0, size);
/**
* @type {DataView}
*/
this.dv = new window.DataView(this.buffer, 0, size);
this.adw = new window.Int32Array(this.buffer, 0, size >> 2);
this.setAccess(Memory.afnTArray);
} 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) {
/*
* This can happen so frequently that the browser can't come up for air, so it's best to do this only under special circumstances...
*/
// if (DEBUG) Component.println("readNone(" + str.toHexWord(this.addr + off) + ")");
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) {
/*
* This can happen so frequently that the browser can't come up for air, so it's best to do this only under special circumstances...
*/
// if (DEBUG) Component.println("writeNone(" + str.toHexWord(this.addr + off) + "): " + str.toHexWord(v));
},
/**
* readByteTArray(off)
*
* @this {Memory}
* @param {number} off
* @return {number}
*/
readByteTArray: function(off) {
Component.assert(off >= 0 && off < this.cb);
return this.ab[off];
},
/**
* readWordTArray(off)
*
* @this {Memory}
* @param {number} off
* @return {number}
*/
readWordTArray: function(off) {
Component.assert(off >= 0 && off < this.cb - 1);
return this.dv.getUint16(off, true);
},
/**
* writeByteTArray(off, b)
*
* @this {Memory}
* @param {number} off
* @param {number} b
*/
writeByteTArray: function(off, b) {
Component.assert(off >= 0 && off < this.cb && (b & 0xff) == b);
this.ab[off] = b;
this.fDirty = true;
},
/**
* writeWordTArray(off, w)
*
* @this {Memory}
* @param {number} off
* @param {number} w
*/
writeWordTArray: function(off, w) {
Component.assert(off >= 0 && off < this.cb - 1 && (w & 0xffff) == w);
this.dv.setUint16(off, w, true);
this.fDirty = true;
},
/**
* readByteMemory(off)
*
* @this {Memory}
* @param {number} off
* @return {number}
*/
readByteMemory: function(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(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(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(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;
},
/**
* readByteVerify(off)
*
* @this {Memory}
* @param {number} off
* @return {number}
*/
readByteVerify: function(off) {
if (DEBUGGER) this.dbg.checkMemoryRead(this.addr + off);
return this.readByteDirect(off);
},
/**
* readWordVerify(off)
*
* @this {Memory}
* @param {number} off
* @return {number}
*/
readWordVerify: function(off) {
if (DEBUGGER) {
/*
* Shut up, JSHint -- I don't need to make the second call if the first returned true.
*/
this.dbg.checkMemoryRead(this.addr + off) || this.dbg.checkMemoryRead(this.addr + off + 1); // jshint ignore:line
}
return this.readWordDirect(off);
},
/**
* writeByteVerify(off, b)
*
* @this {Memory}
* @param {number} off
* @param {number} b
*/
writeByteVerify: function(off, b) {
if (DEBUGGER) this.dbg.checkMemoryWrite(this.addr + off);
this.writeByteDirect(off, b);
},
/**
* writeWordVerify(off, w)
*
* @this {Memory}
* @param {number} off
* @param {number} w
*/
writeWordVerify: function(off, w) {
if (DEBUGGER) {
/*
* Shut up, JSHint -- I don't need to make the second call if the first returned true.
*/
this.dbg.checkMemoryWrite(this.addr + off) || this.dbg.checkMemoryWrite(this.addr + off + 1); // jshint ignore:line
}
this.writeWordDirect(off, w);
},
/**
* 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) {
/*
* While it might seem that we could get away with returning "this.adw", the fact that
* it's a Int32Array rather than a normal Array causes problems with the way JSON.stringify()
* and JSON.parse() interpret these buffers in State.store() and State.parse(): basically, the
* buffers are deserialized as Objects rather than Arrays, so they lack a "length" property,
* and then we get confused.
*
* Rather than trying to solve that problem on the deserialization side, we solve it here by
* ensuring the caller always gets an Array (which also ensures consistency in our serialization
* format).
*/
adw = new Array(this.cb >> 2);
for (i = 0; i < adw.length; i++) {
adw[i] = this.adw[i];
}
}
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);
}
if (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 < this.adw.length; i++) {
this.adw[i] = adw[i];
}
} 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.afnVerify);
}
if (DEBUG) Component.println("read breakpoint added to memory block " + str.toHex(this.addr));
}
else {
if (this.cWriteBreakpoints++ === 0) {
this.setWriteAccess(Memory.afnVerify);
}
if (DEBUG) Component.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) Component.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) Component.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.afnVerify = [Memory.prototype.readByteVerify, Memory.prototype.readWordVerify, Memory.prototype.writeByteVerify, Memory.prototype.writeWordVerify];
if (TYPEDARRAYS) {
Memory.afnTArray = [Memory.prototype.readByteTArray, Memory.prototype.readWordTArray, Memory.prototype.writeByteTArray, Memory.prototype.writeWordTArray];
}
if (typeof APP_PCJS !== 'undefined') APP_PCJS.Memory = Memory;
if (typeof module !== 'undefined') module.exports = Memory;

View file

@ -0,0 +1,590 @@
/**
* @fileoverview Implements the PCjs Mouse component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* 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");
}
/**
* 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);
/**
* 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;
if (DEBUGGER && dbg) {
dbg.messageInit(Mouse);
}
};
/**
* 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 {
this.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;
}
}
};
/**
* 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 (0x4D).
*
* 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) {
this.componentAdapter.sendRBR([0x4D]);
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(this.dbg.MESSAGE_MOUSE)) {
this.dbg.message(sMessage);
}
}
};
/**
* 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,46 @@
/**
* @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;

View file

@ -0,0 +1,163 @@
/**
* @fileoverview Implements the PCjs Panel component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* 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;
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;

View file

@ -0,0 +1,215 @@
/**
* @fileoverview Implements the PCjs RAM component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* 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.cmp = cmp;
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);
}
} else {
this.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;

View file

@ -0,0 +1,359 @@
/**
* @fileoverview Implements the PCjs ROM component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* 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) {
this.error("Empty ROM: " + sROMFile);
return;
}
else if (this.abROM.length == 1) {
this.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;

View file

@ -0,0 +1,795 @@
/**
* @fileoverview Implements the PCjs SerialPort component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* 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 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.RBR = {REG: 0}; // Receiver Buffer Register (read)
SerialPort.DLL = {REG: 0}; // Divisor Latch LSB (only when SerialPort.LCR.DLAB is set)
SerialPort.THR = {REG: 0}; // Transmitter Holding Register (write)
SerialPort.DLM = {REG: 1}; // Divisor Latch MSB (only when SerialPort.LCR.DLAB is set)
SerialPort.SCR = {REG: 7}; // Scratch Register
SerialPort.DL_DEFAULT = 0x180; // we select an arbitrary default Divisor Latch equivalent to 300 baud
/*
* Interrupt Enable Register (IER.REG, offset 1)
*/
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
/*
* Interrupt ID Register (IIR.REG, offset 2)
*
* 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)
*/
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)
*/
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)
*
* 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)
*/
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
/**
* 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.charCode || event.keyCode;
if (keyCode === 8) {
if (event.preventDefault) event.preventDefault();
serial.sendRBR([keyCode]);
}
};
control.onkeypress = function onKeyPressSerial(event) {
/*
* Browser-independent charCode extraction (refer to keyPress() and the other key
* event handlers in keyboard.js).
*/
event = event || window.event;
var charCode = event.which || event.keyCode;
serial.sendRBR([charCode]);
};
return true;
}
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");
if (DEBUGGER && dbg) {
dbg.messageInit(SerialPort);
}
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|undefined} 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|undefined} 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|undefined} 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|undefined} 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|undefined} 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|undefined} 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|undefined} 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|undefined} 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|undefined} 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|undefined} 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|undefined} 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(this.dbg.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, this.dbg.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;

View file

@ -0,0 +1,400 @@
/**
* @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 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.localStorage()
*
* @return {boolean} true if localStorage available, false if not
*/
State.localStorage = function() {
return window.localStorage? true : false;
};
/**
* 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) {
this[this.id][id] = data;
},
/**
* 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 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 (State.localStorage()) {
s = window.localStorage.getItem(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 (State.localStorage()) {
var s = JSON.stringify(this[this.id]);
try {
window.localStorage.setItem(this.key, s);
if (DEBUG) this.messageDebugger("localStorage(" + this.key + "): " + s.length + " bytes stored");
} catch (e) {
/*
* 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.log(e.message || e, "error");
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();
if (State.localStorage()) {
var i = 0;
// var fRemoved = false;
while (i < window.localStorage.length) {
var key = window.localStorage.key(i++);
if (key && (fAll || key.substr(0, this.key.length) == this.key)) {
window.localStorage.removeItem(key);
// fRemoved = true;
if (DEBUG) this.messageDebugger("localStorage(" + key + ") removed");
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(this.dbg.MESSAGE_STATE)) this.dbg.message(sMessage);
}
}
};
if (typeof APP_PCJS !== 'undefined') APP_PCJS.State = State;
if (typeof module !== 'undefined') module.exports = State;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,311 @@
/**
* @fileoverview Defines PCjs x86 constants.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* 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: 0x3000, // 12-13: I/O Privilege Level, always set on 8086/80186, clear on 80286
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: {
LEVEL: 0x0003, // selector 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,
/*
* The rest of these apply only when SEG is set
*/
ACCESSED: 0x0100,
READABLE: 0x0200, // CODE: set if readable, clear if execute-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
CODE: 0x0800, // set for CODE, clear for DATA
CODE_READABLE: 0x0A00, // both CODE and READABLE
CODE_CONFORMING: 0x0C00, // both CODE and CONFORMING
CODE_CONFORMING_READABLE: 0x0E00, // all of CODE and CONFORMING and READABLE
/*
* The rest of these apply only when SEG is clear
*/
TSS: 0x0100,
LDT: 0x0200,
TSS_LDT: 0x0300,
TSS_BUSY: 0x0300,
GATE_CALL: 0x0400,
GATE_TASK: 0x0500,
GATE_INT: 0x0600,
GATE_TRAP: 0x0700
},
LEVEL: {
MASK: 0x6000,
SHIFT: 13
},
PRESENT: 0x8000
}
},
/*
* 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: This is the default for 8086/8088; other processors must tweak these bits before
* calling setPS(). TODO: Verify that PS_1 was always set on reset, even on the 8086/8088.
*/
X86.PS.SET = (X86.PS.BIT1 | X86.PS.IOPL | 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;

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,471 @@
/**
* @fileoverview Implements PCjs 8086 opcode helpers.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* 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.nOpCyclesMovRR : this.nOpCyclesMovRM) : this.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.nOpCyclesTestRR : this.nOpCyclesTestRM) : this.nOpCyclesTestRM);
if (FASTDISABLE) 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.nOpCyclesTestRR : this.nOpCyclesTestRM) : this.nOpCyclesTestRM);
if (FASTDISABLE) 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.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.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.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.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 (FASTDISABLE) 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.LEVEL) < (src & X86.SEL.LEVEL)) {
dst = (dst & ~X86.SEL.LEVEL) | (src & X86.SEL.LEVEL);
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.level >= (this.segCS.sel & X86.SEL.LEVEL) && this.segVER.level >= (src & X86.SEL.LEVEL)) {
this.setZF();
return this.segVER.acc & X86.DESC.ACC.MASK;
}
}
this.clearZF();
return dst;
},
/**
* @this {X86CPU}
* @param {number} dst
* @param {number} src
* @return {number}
*/
opHelpLSL: 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: 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) == X86.DESC.ACC.TYPE.CODE_CONFORMING);
if ((fConforming || this.segVER.level >= (this.segCS.sel & X86.SEL.LEVEL)) && this.segVER.level >= (src & X86.SEL.LEVEL)) {
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;
}
this.nStepCycles -= this.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.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;
}
this.nStepCycles -= this.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.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.nOpCyclesInt + nCycles;
}
/*
* TODO: Now what?
*/
},
/**
* @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]
*/
opHelpFault: function(nFault, nError) {
if (this.dbg) {
/*
* By using Debugger.message(), you have the option of setting "m halt on" and halting on messages
* like this.
*/
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 (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;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,419 @@
/**
* @fileoverview Implements PCjs 0x0F two-byte opcodes
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* 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 (FASTDISABLE) this.modEAWord = this.modEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOREAD;
}
X86Mods.aOpModsGrpWord[bModRM].call(this, X86Op0F.aOpGRP6, X86Grps.opGrpNoSrc);
if (FASTDISABLE) { 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 (FASTDISABLE) this.modEAWord = this.modEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOREAD;
}
X86Mods.aOpModsGrpWord[bModRM].call(this, X86Op0F.aOpGRP7, X86Grps.opGrpNoSrc);
if (FASTDISABLE) { 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);
},
/**
* @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 (FASTDISABLE) 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 (FASTDISABLE) 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 (FASTDISABLE) 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.
*
* TODO: Consider making a CPL (current privilege level) variable that tracks segCS.sel, so that we
* don't have to mask segCS.sel every time.
*/
if ((this.segVER.acc & X86.DESC.ACC.TYPE.CODE_CONFORMING) == X86.DESC.ACC.TYPE.CODE_CONFORMING ||
this.segVER.level >= (this.segCS.sel & X86.SEL.LEVEL) && this.segVER.level >= (dst & X86.SEL.LEVEL)) {
this.setZF();
return dst;
}
}
}
this.clearZF();
return dst;
},
/**
* @this {X86CPU}
* @param {number} dst
* @param {number} src (null)
* @return {number}
*/
opVERW: function(dst, src) {
if (FASTDISABLE) 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.
*
* TODO: Consider making a CPL (current privilege level) variable that tracks segCS.sel, so that we
* don't have to mask segCS.sel every time.
*/
if (this.segVER.level >= (this.segCS.sel & X86.SEL.LEVEL) && this.segVER.level >= (dst & X86.SEL.LEVEL)) {
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 {
this.setWord(this.regEA + 2, this.addrGDT);
this.setByte(this.regEA + 4, this.addrGDT >> 16);
dst = this.addrGDTLimit - this.addrGDT;
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 {
this.setWord(this.regEA + 2, this.addrIDT);
this.setByte(this.regEA + 4, this.addrIDT >> 16);
dst = this.addrIDTLimit - this.addrIDT;
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 (FASTDISABLE) 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 (FASTDISABLE) 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) {
this.regMSW = (this.regMSW & X86.MSW.SET) | (dst & ~X86.MSW.SET);
this.nStepCycles -= (3 + (this.regEA < 0? 0 : 3));
/*
* Since the 80286 did not allow you to disable protected-mode (ie, return to real-mode) by
* CLEARING the X86.MSW.PE bit, we need only check for the bit being SET. And the only functions
* that call setProtMode() are resetRegs() and this function, so there's no danger of the mode
* getting out of sync with the X86.MSW.PE bit.
*/
if (this.regMSW & X86.MSW.PE) {
this.setProtMode(true);
}
if (FASTDISABLE) 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, X86Help.opUndefined, 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;

Some files were not shown because too many files have changed in this diff Show more