Initial commit (a clone of the jsmachines project as of v1.15.3)
This commit is contained in:
commit
a5e3e6a59d
714 changed files with 130602 additions and 0 deletions
649
my_modules/README.md
Normal file
649
my_modules/README.md
Normal 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.
|
||||
39
my_modules/c1pjs-client/.jshintrc
Normal file
39
my_modules/c1pjs-client/.jshintrc
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"boss": true,
|
||||
"eqnull": true,
|
||||
"evil": true,
|
||||
"loopfunc": true,
|
||||
"sub": true,
|
||||
"globalstrict": true,
|
||||
"globals": {
|
||||
"window": true,
|
||||
"APPNAME": false,
|
||||
"APPVERSION": false,
|
||||
"SITEHOST": false,
|
||||
"DEBUG": 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
|
||||
}
|
||||
}
|
||||
353
my_modules/c1pjs-client/lib/computer.js
Normal file
353
my_modules/c1pjs-client/lib/computer.js
Normal 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);
|
||||
3891
my_modules/c1pjs-client/lib/cpu.js
Normal file
3891
my_modules/c1pjs-client/lib/cpu.js
Normal file
File diff suppressed because it is too large
Load diff
2226
my_modules/c1pjs-client/lib/debugger.js
Normal file
2226
my_modules/c1pjs-client/lib/debugger.js
Normal file
File diff suppressed because it is too large
Load diff
53
my_modules/c1pjs-client/lib/defines.js
Normal file
53
my_modules/c1pjs-client/lib/defines.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* @fileoverview C1Pjs-specific compile-time definitions.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-May-08
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @define {string}
|
||||
*/
|
||||
var C1PJSCLASS = "c1pjs"; // this @define is the default application class (formerly APPCLASS) to use for C1Pjs
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
*
|
||||
* WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
|
||||
* In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate. When it's *false*,
|
||||
* nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
|
||||
* "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
|
||||
* debugger.js from the compilation process altogether.
|
||||
*
|
||||
* However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
|
||||
* I would like to skip loading debugger.js altogether. When doing that, we must ALSO arrange for an additional file
|
||||
* (nodebugger.js) to be loaded immediately after this file, which *explicitly* overrides DEBUGGER with *false*.
|
||||
*/
|
||||
var DEBUGGER = true; // this @define is overridden by the Closure Compiler to remove Debugger-related support
|
||||
1266
my_modules/c1pjs-client/lib/disk.js
Normal file
1266
my_modules/c1pjs-client/lib/disk.js
Normal file
File diff suppressed because it is too large
Load diff
1105
my_modules/c1pjs-client/lib/keyboard.js
Normal file
1105
my_modules/c1pjs-client/lib/keyboard.js
Normal file
File diff suppressed because it is too large
Load diff
47
my_modules/c1pjs-client/lib/nodebugger.js
Normal file
47
my_modules/c1pjs-client/lib/nodebugger.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* @fileoverview Compile-time definitions for Debugger-less configurations.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-May-08
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
|
||||
* In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate; when it's *false*,
|
||||
* nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
|
||||
* "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
|
||||
* debugger.js from the compilation process altogether.
|
||||
*
|
||||
* However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
|
||||
* I would like to skip loading debugger.js altogether. To do that, we must arrange for this additional file
|
||||
* (nodebugger.js) to be loaded as early as possible, which *explicitly* overrides the previously defined value
|
||||
* of DEBUGGER with *false*.
|
||||
*/
|
||||
DEBUGGER = false;
|
||||
97
my_modules/c1pjs-client/lib/outline.xml
Normal file
97
my_modules/c1pjs-client/lib/outline.xml
Normal 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>
|
||||
130
my_modules/c1pjs-client/lib/panel.js
Normal file
130
my_modules/c1pjs-client/lib/panel.js
Normal 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);
|
||||
96
my_modules/c1pjs-client/lib/ram.js
Normal file
96
my_modules/c1pjs-client/lib/ram.js
Normal 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);
|
||||
244
my_modules/c1pjs-client/lib/rom.js
Normal file
244
my_modules/c1pjs-client/lib/rom.js
Normal 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);
|
||||
360
my_modules/c1pjs-client/lib/serial.js
Normal file
360
my_modules/c1pjs-client/lib/serial.js
Normal 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);
|
||||
600
my_modules/c1pjs-client/lib/video.js
Normal file
600
my_modules/c1pjs-client/lib/video.js
Normal 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 <canvas> support; try a new web browser.";
|
||||
return;
|
||||
}
|
||||
eCanvas.setAttribute("class", C1PJSCLASS + "-canvas");
|
||||
eCanvas.setAttribute("width", parmsVideo['screenWidth']);
|
||||
eCanvas.setAttribute("height", parmsVideo['screenHeight']);
|
||||
|
||||
eCanvas.setAttribute("contenteditable", "true");
|
||||
eCanvas.setAttribute("autocapitalize", "off");
|
||||
eCanvas.setAttribute("autocorrect", "off");
|
||||
eCanvas.style.backgroundColor = parmsVideo['screenColor'];
|
||||
|
||||
/*
|
||||
* HACK: A canvas style of "auto" provides for excellent responsive canvas scaling in EVERY browser
|
||||
* except IE9/IE10, so I recalculate the appropriate CSS height every time the parent DIV is resized;
|
||||
* IE11 works without this hack, so we take advantage of the fact that IE11 doesn't report itself as "MSIE".
|
||||
*/
|
||||
eCanvas.style.height = "auto";
|
||||
if (web.getUserAgent().indexOf("MSIE") >= 0) {
|
||||
eCanvas.style.height = (((eVideo.clientWidth * parmsVideo['screenHeight']) / parmsVideo['screenWidth']) | 0) + "px";
|
||||
eVideo.onresize = function(eParent, eChild, cx, cy) {
|
||||
return function() {
|
||||
eChild.style.height = (((eParent.clientWidth * cy) / cx) | 0) + "px";
|
||||
};
|
||||
}(eVideo, eCanvas, parmsVideo['screenWidth'], parmsVideo['screenHeight']);
|
||||
}
|
||||
eVideo.appendChild(eCanvas);
|
||||
|
||||
/*
|
||||
* Now we can create the Video object, record it, and wire it up to the associated document elements.
|
||||
*
|
||||
* Regarding "new Image()", see https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement.Image:
|
||||
*
|
||||
* This constructor exists for historical reasons only and returns an HTMLImageElement instance just as
|
||||
* document.createElement('img') would.
|
||||
*/
|
||||
var imgCharSet = new Image();
|
||||
var contextVideo = eCanvas.getContext("2d");
|
||||
var video = new C1PVideo(parmsVideo, eCanvas, contextVideo, imgCharSet);
|
||||
imgCharSet.onload = function(video, sCharSet) {
|
||||
return function() {
|
||||
if (DEBUG) video.log("onload(): finished loading " + sCharSet);
|
||||
video.setReady();
|
||||
};
|
||||
}(video, parmsVideo['charSet']); // jshint ignore:line
|
||||
imgCharSet.src = parmsVideo['charSet'];
|
||||
|
||||
/*
|
||||
* Bind any video-specific controls (eg, the Refresh button). There are no essential controls, however;
|
||||
* even the "Refresh" button is just a diagnostic tool, to verify that the screen contents are up-to-date.
|
||||
*/
|
||||
Component.bindComponentControls(video, eVideo, C1PJSCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize every Video module on the page.
|
||||
*/
|
||||
web.onInit(C1PVideo.init);
|
||||
14
my_modules/c1pjs-client/templates/README.md
Normal file
14
my_modules/c1pjs-client/templates/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
C1Pjs Templates
|
||||
===
|
||||
Template folders contain a variety of XML and HTML templates and supporting files, including:
|
||||
|
||||
- DTD files (Document Type Definitions)
|
||||
- XSD files (XML schemas -- eventually)
|
||||
- XSL files (XML stylesheets)
|
||||
- CSS files (stylesheets that the XSL files rely upon)
|
||||
- HTML files (HTML fragments used to generate part or all of a web page)
|
||||
|
||||
[*components.xsl*](components.xsl) transforms all the elements of a machine XML file into an HTML fragment
|
||||
that includes a series of **DIV** tags with corresponding *id* and *data-value* attributes that allow our
|
||||
JavaScript components to bind themselves to visual elements (eg, virtual screen, virtual keyboard, control
|
||||
panel) on a web page.
|
||||
106
my_modules/c1pjs-client/templates/components.css
Normal file
106
my_modules/c1pjs-client/templates/components.css
Normal 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;
|
||||
}
|
||||
564
my_modules/c1pjs-client/templates/components.xsl
Normal file
564
my_modules/c1pjs-client/templates/components.xsl
Normal 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>
|
||||
19
my_modules/diskdump/README.md
Normal file
19
my_modules/diskdump/README.md
Normal 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/).
|
||||
40
my_modules/diskdump/bin/diskdump
Normal file
40
my_modules/diskdump/bin/diskdump
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview Implements the DiskDump command-line interface
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Sep-04
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var lib = path.join(path.dirname(fs.realpathSync(__filename)), "../lib/");
|
||||
|
||||
require(lib + "diskdump.js").CLI();
|
||||
2572
my_modules/diskdump/lib/diskdump.js
Normal file
2572
my_modules/diskdump/lib/diskdump.js
Normal file
File diff suppressed because it is too large
Load diff
19
my_modules/diskdump/package.json
Normal file
19
my_modules/diskdump/package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "diskdump",
|
||||
"version": "0.2.0",
|
||||
"description": "Converts disk images to/from JSON",
|
||||
"main": "./lib/diskdump",
|
||||
"scripts": {
|
||||
"test": "echo \"error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Jeff Parsons <Jeff@pcjs.org>",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "GPLv3",
|
||||
"url": "http://www.gnu.org/licenses/gpl.html"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"diskdump": "./bin/diskdump"
|
||||
}
|
||||
}
|
||||
327
my_modules/ecpjs-client/lib/parallelAdder.js
Normal file
327
my_modules/ecpjs-client/lib/parallelAdder.js
Normal 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);
|
||||
583
my_modules/ecpjs-client/lib/register.js
Normal file
583
my_modules/ecpjs-client/lib/register.js
Normal 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);
|
||||
228
my_modules/ecpjs-client/lib/selector.js
Normal file
228
my_modules/ecpjs-client/lib/selector.js
Normal 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);
|
||||
207
my_modules/ecpjs-client/lib/stepper.js
Normal file
207
my_modules/ecpjs-client/lib/stepper.js
Normal 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);
|
||||
36
my_modules/ecpjs-client/templates/component.xsl
Normal file
36
my_modules/ecpjs-client/templates/component.xsl
Normal 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 " "> <!ENTITY sect "§"> <!ENTITY copy "©"> <!ENTITY para "¶"> <!ENTITY ndash "–"> <!ENTITY mdash "—">
|
||||
<!ENTITY lsquo "‘"> <!ENTITY rsquo "’"> <!ENTITY ldquo "“"> <!ENTITY rdquo "”"> <!ENTITY dagger "†"> <!ENTITY Dagger "‡">
|
||||
]>
|
||||
<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>
|
||||
79
my_modules/ecpjs-client/templates/components.css
Normal file
79
my_modules/ecpjs-client/templates/components.css
Normal 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;
|
||||
}
|
||||
143
my_modules/ecpjs-client/templates/components.xsl
Normal file
143
my_modules/ecpjs-client/templates/components.xsl
Normal 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>
|
||||
42
my_modules/ecpjs-client/templates/device.xsl
Normal file
42
my_modules/ecpjs-client/templates/device.xsl
Normal 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 " "> <!ENTITY sect "§"> <!ENTITY copy "©"> <!ENTITY para "¶"> <!ENTITY ndash "–"> <!ENTITY mdash "—">
|
||||
<!ENTITY lsquo "‘"> <!ENTITY rsquo "’"> <!ENTITY ldquo "“"> <!ENTITY rdquo "”"> <!ENTITY dagger "†"> <!ENTITY Dagger "‡">
|
||||
]>
|
||||
<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>
|
||||
149
my_modules/ecpjs-client/templates/document.xsl
Normal file
149
my_modules/ecpjs-client/templates/document.xsl
Normal 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 " "> <!ENTITY sect "§"> <!ENTITY copy "©"> <!ENTITY para "¶"> <!ENTITY ndash "–"> <!ENTITY mdash "—">
|
||||
<!ENTITY lsquo "‘"> <!ENTITY rsquo "’"> <!ENTITY ldquo "“"> <!ENTITY rdquo "”"> <!ENTITY dagger "†"> <!ENTITY Dagger "‡">
|
||||
]>
|
||||
<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>
|
||||
29
my_modules/ecpjs-client/templates/outline.xsl
Normal file
29
my_modules/ecpjs-client/templates/outline.xsl
Normal 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 " "> <!ENTITY sect "§"> <!ENTITY copy "©"> <!ENTITY para "¶"> <!ENTITY ndash "–"> <!ENTITY mdash "—">
|
||||
<!ENTITY lsquo "‘"> <!ENTITY rsquo "’"> <!ENTITY ldquo "“"> <!ENTITY rdquo "”"> <!ENTITY dagger "†"> <!ENTITY Dagger "‡">
|
||||
]>
|
||||
<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>
|
||||
25
my_modules/ecpjs-client/templates/register.css
Normal file
25
my_modules/ecpjs-client/templates/register.css
Normal 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;
|
||||
}
|
||||
58
my_modules/ecpjs-client/templates/register.xsl
Normal file
58
my_modules/ecpjs-client/templates/register.xsl
Normal 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>
|
||||
3
my_modules/filedump/README.md
Normal file
3
my_modules/filedump/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
FileDump
|
||||
===
|
||||
Module (and command-line utility) for converting the contents of files to JSON.
|
||||
40
my_modules/filedump/bin/filedump
Normal file
40
my_modules/filedump/bin/filedump
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview Implements the FileDump command-line interface
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Sep-04
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var lib = path.join(path.dirname(fs.realpathSync(__filename)), "../lib/");
|
||||
|
||||
require(lib + "filedump.js").CLI();
|
||||
655
my_modules/filedump/lib/filedump.js
Normal file
655
my_modules/filedump/lib/filedump.js
Normal 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;
|
||||
19
my_modules/filedump/package.json
Normal file
19
my_modules/filedump/package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "filedump",
|
||||
"version": "0.2.0",
|
||||
"description": "Converts file contents to JSON",
|
||||
"main": "./lib/filedump",
|
||||
"scripts": {
|
||||
"test": "echo \"error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Jeff Parsons <Jeff@pcjs.org>",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "GPLv3",
|
||||
"url": "http://www.gnu.org/licenses/gpl.html"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"filedump": "./bin/filedump"
|
||||
}
|
||||
}
|
||||
3
my_modules/grunts/manifester/.gitignore
vendored
Normal file
3
my_modules/grunts/manifester/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
npm-debug.log
|
||||
tmp
|
||||
13
my_modules/grunts/manifester/.jshintrc
Normal file
13
my_modules/grunts/manifester/.jshintrc
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"curly": true,
|
||||
"eqeqeq": true,
|
||||
"immed": true,
|
||||
"latedef": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"sub": true,
|
||||
"undef": true,
|
||||
"boss": true,
|
||||
"eqnull": true,
|
||||
"node": true
|
||||
}
|
||||
73
my_modules/grunts/manifester/Gruntfile.js
Normal file
73
my_modules/grunts/manifester/Gruntfile.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* grunt-manifester
|
||||
* https://github.com/jeffpar/jsmachines
|
||||
*
|
||||
* Copyright (c) 2014 jeffpar
|
||||
* Licensed under the MIT license.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function (grunt) {
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
jshint: {
|
||||
all: [
|
||||
'Gruntfile.js',
|
||||
'tasks/*.js',
|
||||
'<%= nodeunit.tests %>'
|
||||
],
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
}
|
||||
},
|
||||
|
||||
// Before generating any new files, remove any previously-created files.
|
||||
clean: {
|
||||
tests: ['tmp']
|
||||
},
|
||||
|
||||
// Configuration to be run (and then tested).
|
||||
manifester: {
|
||||
default_options: {
|
||||
options: {
|
||||
},
|
||||
files: {
|
||||
'tmp/default_options': ['test/fixtures/testing', 'test/fixtures/123']
|
||||
}
|
||||
},
|
||||
custom_options: {
|
||||
options: {
|
||||
separator: ': ',
|
||||
punctuation: ' !!!'
|
||||
},
|
||||
files: {
|
||||
'tmp/custom_options': ['test/fixtures/testing', 'test/fixtures/123']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Unit tests.
|
||||
nodeunit: {
|
||||
tests: ['test/*_test.js']
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Actually load this plugin's task(s).
|
||||
grunt.loadTasks('tasks');
|
||||
|
||||
// These plugins provide necessary tasks.
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-nodeunit');
|
||||
|
||||
// Whenever the "test" task is run, first clean the "tmp" dir, then run this
|
||||
// plugin's task(s), then test the result.
|
||||
grunt.registerTask('test', ['clean', 'manifester', 'nodeunit']);
|
||||
|
||||
// By default, lint and run all tests.
|
||||
grunt.registerTask('default', ['jshint', 'test']);
|
||||
|
||||
};
|
||||
22
my_modules/grunts/manifester/LICENSE-MIT
Normal file
22
my_modules/grunts/manifester/LICENSE-MIT
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2014 jeffpar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
89
my_modules/grunts/manifester/README.md
Normal file
89
my_modules/grunts/manifester/README.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# grunt-manifester
|
||||
|
||||
> manifest.xml processor
|
||||
|
||||
## Getting Started
|
||||
This plugin requires Grunt `~0.4.4`
|
||||
|
||||
If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
|
||||
|
||||
```shell
|
||||
npm install grunt-manifester --save-dev
|
||||
```
|
||||
|
||||
Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:
|
||||
|
||||
```js
|
||||
grunt.loadNpmTasks('grunt-manifester');
|
||||
```
|
||||
|
||||
## The "manifester" task
|
||||
|
||||
### Overview
|
||||
In your project's Gruntfile, add a section named `manifester` to the data object passed into `grunt.initConfig()`.
|
||||
|
||||
```js
|
||||
grunt.initConfig({
|
||||
manifester: {
|
||||
options: {
|
||||
// Task-specific options go here.
|
||||
},
|
||||
your_target: {
|
||||
// Target-specific file lists and/or options go here.
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
#### options.separator
|
||||
Type: `String`
|
||||
Default value: `', '`
|
||||
|
||||
A string value that is used to do something with whatever.
|
||||
|
||||
#### options.punctuation
|
||||
Type: `String`
|
||||
Default value: `'.'`
|
||||
|
||||
A string value that is used to do something else with whatever else.
|
||||
|
||||
### Usage Examples
|
||||
|
||||
#### Default Options
|
||||
In this example, the default options are used to do something with whatever. So if the `testing` file has the content `Testing` and the `123` file had the content `1 2 3`, the generated result would be `Testing, 1 2 3.`
|
||||
|
||||
```js
|
||||
grunt.initConfig({
|
||||
manifester: {
|
||||
options: {},
|
||||
files: {
|
||||
'dest/default_options': ['src/testing', 'src/123'],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Custom Options
|
||||
In this example, custom options are used to do something else with whatever else. So if the `testing` file has the content `Testing` and the `123` file had the content `1 2 3`, the generated result in this case would be `Testing: 1 2 3 !!!`
|
||||
|
||||
```js
|
||||
grunt.initConfig({
|
||||
manifester: {
|
||||
options: {
|
||||
separator: ': ',
|
||||
punctuation: ' !!!',
|
||||
},
|
||||
files: {
|
||||
'dest/default_options': ['src/testing', 'src/123'],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Contributing
|
||||
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).
|
||||
|
||||
## Release History
|
||||
_(Nothing yet)_
|
||||
41
my_modules/grunts/manifester/package.json
Normal file
41
my_modules/grunts/manifester/package.json
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "grunt-manifester",
|
||||
"description": "manifest.xml processor",
|
||||
"version": "0.1.0",
|
||||
"homepage": "https://github.com/jeffpar/jsmachines",
|
||||
"author": {
|
||||
"name": "jeffpar",
|
||||
"email": "jeffpar@mac.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/jeffpar/jsmachines.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/jeffpar/jsmachines/issues"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT",
|
||||
"url": "https://github.com/jeffpar/jsmachines/blob/master/LICENSE-MIT"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt-contrib-jshint": "~0.6.0",
|
||||
"grunt-contrib-clean": "~0.4.0",
|
||||
"grunt-contrib-nodeunit": "~0.2.0",
|
||||
"grunt": "~0.4.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"grunt": "~0.4.4"
|
||||
},
|
||||
"keywords": [
|
||||
"gruntplugin"
|
||||
]
|
||||
}
|
||||
134
my_modules/grunts/manifester/tasks/manifester.js
Normal file
134
my_modules/grunts/manifester/tasks/manifester.js
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
/**
|
||||
* grunt-manifester
|
||||
* https://github.com/jeffpar/jsmachines
|
||||
*
|
||||
* Copyright (c) 2014 jeffpar
|
||||
* Licensed under the MIT license.
|
||||
*
|
||||
* TODO: Update this header with our standard header and fix all the JSHint warnings
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var mkdirp = require("mkdirp");
|
||||
var url = require("url");
|
||||
var async = require("async");
|
||||
var parseXML = require("xml2js").parseString; // see: https://github.com/Leonidas-from-XIV/node-xml2js
|
||||
var unzip = require("unzip");
|
||||
var util = require("util");
|
||||
var net = require("../../../shared/lib/netlib");
|
||||
|
||||
module.exports = function (grunt) {
|
||||
|
||||
/*
|
||||
* Please see the Grunt documentation for more information regarding task
|
||||
* creation: http://gruntjs.com/creating-tasks
|
||||
*/
|
||||
grunt.registerMultiTask('manifester', 'manifest.xml processor', function() {
|
||||
|
||||
/*
|
||||
* Merge task-specific and/or target-specific options with these defaults
|
||||
*
|
||||
var options = this.options({
|
||||
});
|
||||
*/
|
||||
|
||||
/*
|
||||
* Tell grunt this task is asynchronous
|
||||
*/
|
||||
var asManifests = [];
|
||||
var doneGrunt = this.async();
|
||||
|
||||
/*
|
||||
* Iterate over all specified file groups
|
||||
*/
|
||||
this.files.forEach(function(file) {
|
||||
|
||||
file.src.filter(function(sFilePath) {
|
||||
/*
|
||||
* Warn on and remove invalid source files (if nonull was set)
|
||||
*/
|
||||
if (!grunt.file.exists(sFilePath)) {
|
||||
grunt.log.warn('Source file "' + sFilePath + '" not found.');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).map(function(sFilePath) {
|
||||
/*
|
||||
* TODO: Given the memory constraints I've run into with Grunt in the past, it would
|
||||
* probably be better queue up the file paths instead of the file contents, and do async
|
||||
* readFile() calls.
|
||||
*/
|
||||
asManifests.push(sFilePath);
|
||||
});
|
||||
});
|
||||
|
||||
async.each(asManifests, function processXML(sManifestFile, doneAsync) {
|
||||
var sManifestXML = grunt.file.read(sManifestFile);
|
||||
parseXML(sManifestXML, function doneParseXML(err, xml) {
|
||||
var cCallbacks = 0;
|
||||
if (xml.manifest) {
|
||||
// console.log(util.inspect(xml, false, null));
|
||||
for (var iRepo in xml.manifest.repo) {
|
||||
var repo = xml.manifest.repo[iRepo];
|
||||
if (repo.src) {
|
||||
var src = repo.src[0];
|
||||
var sURL = src.$['href'];
|
||||
// var sURL = src._ || src; // the former is set if there are any attributes, otherwise the element is just a string
|
||||
console.log('found src URL: "' + sURL + '"');
|
||||
if (sURL.slice(0, 5) == "http:" && sURL.slice(-1) == '/') {
|
||||
for (var iDownload in repo.download) {
|
||||
var download = repo.download[iDownload];
|
||||
var sDownloadFile = download.$['href'];
|
||||
// var sDownloadFile = download._ || download;
|
||||
console.log("processing download " + sDownloadFile);
|
||||
var sRemoteFile = sURL + sDownloadFile;
|
||||
var sLocalDir = path.join(path.dirname(sManifestFile), repo.$['dir']);
|
||||
if (grunt.file.exists(sLocalDir) || mkdirp.sync(sLocalDir)) {
|
||||
var sLocalFile = path.join(sLocalDir, sDownloadFile);
|
||||
if (grunt.file.exists(sLocalFile)) {
|
||||
console.log("file already exists: " + sLocalFile);
|
||||
} else {
|
||||
console.log('downloadFile("' + sRemoteFile + '", "' + sLocalFile + '")...');
|
||||
cCallbacks++;
|
||||
net.downloadFile(sRemoteFile, sLocalFile, function doneDownloadFile(err, status) {
|
||||
console.log('downloadFile("' + sRemoteFile + '") returned ' + status + ': ' + (err? false : true));
|
||||
if (!err && status == 200 && sLocalFile.slice(-4) == ".zip") {
|
||||
var sLocalZipDir = path.join(sLocalDir, path.basename(sLocalFile, ".zip"));
|
||||
if (grunt.file.exists(sLocalZipDir) || mkdirp.sync(sLocalZipDir)) {
|
||||
/*
|
||||
* TODO: As explained here (https://github.com/EvanOxfeld/node-unzip/issues/40), determine why this ZIP
|
||||
* file (http://beej.us/moria/files/pc/zip-arc/mor55-88.zip) causes an "invalid stored block lengths" error.
|
||||
*/
|
||||
fs.createReadStream(sLocalFile).pipe(unzip.Extract({path: sLocalZipDir})).on('close', function() {
|
||||
console.log("unzip complete: " + sLocalZipDir);
|
||||
if (--cCallbacks == 0) doneAsync();
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
grunt.log.warn("unzip directory not available: " + sLocalZipDir);
|
||||
}
|
||||
}
|
||||
if (--cCallbacks == 0) doneAsync();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
grunt.log.warn("download directory not available: " + sLocalDir);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
grunt.log.warn("unsupported repo src: " + sURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!cCallbacks) doneAsync();
|
||||
});
|
||||
}, function(err) {
|
||||
doneGrunt();
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
Testing: 1 2 3 !!!
|
||||
|
|
@ -0,0 +1 @@
|
|||
Testing, 1 2 3.
|
||||
1
my_modules/grunts/manifester/test/fixtures/123
vendored
Normal file
1
my_modules/grunts/manifester/test/fixtures/123
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
1 2 3
|
||||
1
my_modules/grunts/manifester/test/fixtures/testing
vendored
Normal file
1
my_modules/grunts/manifester/test/fixtures/testing
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
Testing
|
||||
48
my_modules/grunts/manifester/test/manifester_test.js
Normal file
48
my_modules/grunts/manifester/test/manifester_test.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
'use strict';
|
||||
|
||||
var grunt = require('grunt');
|
||||
|
||||
/*
|
||||
======== A Handy Little Nodeunit Reference ========
|
||||
https://github.com/caolan/nodeunit
|
||||
|
||||
Test methods:
|
||||
test.expect(numAssertions)
|
||||
test.done()
|
||||
Test assertions:
|
||||
test.ok(value, [message])
|
||||
test.equal(actual, expected, [message])
|
||||
test.notEqual(actual, expected, [message])
|
||||
test.deepEqual(actual, expected, [message])
|
||||
test.notDeepEqual(actual, expected, [message])
|
||||
test.strictEqual(actual, expected, [message])
|
||||
test.notStrictEqual(actual, expected, [message])
|
||||
test.throws(block, [error], [message])
|
||||
test.doesNotThrow(block, [error], [message])
|
||||
test.ifError(value)
|
||||
*/
|
||||
|
||||
exports.manifester = {
|
||||
setUp: function(done) {
|
||||
// setup here if necessary
|
||||
done();
|
||||
},
|
||||
default_options: function(test) {
|
||||
test.expect(1);
|
||||
|
||||
var actual = grunt.file.read('tmp/default_options');
|
||||
var expected = grunt.file.read('test/expected/default_options');
|
||||
test.equal(actual, expected, 'should describe what the default behavior is.');
|
||||
|
||||
test.done();
|
||||
},
|
||||
custom_options: function(test) {
|
||||
test.expect(1);
|
||||
|
||||
var actual = grunt.file.read('tmp/custom_options');
|
||||
var expected = grunt.file.read('test/expected/custom_options');
|
||||
test.equal(actual, expected, 'should describe what the custom option(s) behavior is.');
|
||||
|
||||
test.done();
|
||||
},
|
||||
};
|
||||
13
my_modules/grunts/prepjs/.jshintrc
Normal file
13
my_modules/grunts/prepjs/.jshintrc
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"curly": true,
|
||||
"eqeqeq": true,
|
||||
"immed": true,
|
||||
"latedef": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"sub": true,
|
||||
"undef": true,
|
||||
"boss": true,
|
||||
"eqnull": true,
|
||||
"node": true
|
||||
}
|
||||
143
my_modules/grunts/prepjs/Gruntfile.js
Normal file
143
my_modules/grunts/prepjs/Gruntfile.js
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* grunt-prepjs
|
||||
* https://github.com/jeffpar/jsmachines
|
||||
*
|
||||
* Copyright (c) 2014 jeffpar
|
||||
* Licensed under the MIT license.
|
||||
*
|
||||
* Genesis:
|
||||
*
|
||||
* sudo npm install -g grunt-init
|
||||
* git clone git://github.com/gruntjs/grunt-init-gruntplugin.git ~/.grunt-init/gruntplugin
|
||||
* cd ~/Sites/pcjs/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']);
|
||||
|
||||
};
|
||||
22
my_modules/grunts/prepjs/LICENSE-MIT
Normal file
22
my_modules/grunts/prepjs/LICENSE-MIT
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2014 jeffpar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
89
my_modules/grunts/prepjs/README.md
Normal file
89
my_modules/grunts/prepjs/README.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# grunt-prepjs
|
||||
|
||||
> JS Preprocessor
|
||||
|
||||
## Getting Started
|
||||
This plugin requires Grunt `~0.4.4`
|
||||
|
||||
If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
|
||||
|
||||
```shell
|
||||
npm install grunt-prepjs --save-dev
|
||||
```
|
||||
|
||||
Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:
|
||||
|
||||
```js
|
||||
grunt.loadNpmTasks('grunt-prepjs');
|
||||
```
|
||||
|
||||
## The "prepjs" task
|
||||
|
||||
### Overview
|
||||
In your project's Gruntfile, add a section named `prepjs` to the data object passed into `grunt.initConfig()`.
|
||||
|
||||
```js
|
||||
grunt.initConfig({
|
||||
prepjs: {
|
||||
options: {
|
||||
// Task-specific options go here.
|
||||
},
|
||||
your_target: {
|
||||
// Target-specific file lists and/or options go here.
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
#### options.separator
|
||||
Type: `String`
|
||||
Default value: `', '`
|
||||
|
||||
A string value that is used to do something with whatever.
|
||||
|
||||
#### options.punctuation
|
||||
Type: `String`
|
||||
Default value: `'.'`
|
||||
|
||||
A string value that is used to do something else with whatever else.
|
||||
|
||||
### Usage Examples
|
||||
|
||||
#### Default Options
|
||||
In this example, the default options are used to do something with whatever. So if the `testing` file has the content `Testing` and the `123` file had the content `1 2 3`, the generated result would be `Testing, 1 2 3.`
|
||||
|
||||
```js
|
||||
grunt.initConfig({
|
||||
prepjs: {
|
||||
options: {},
|
||||
files: {
|
||||
'dest/default_options': ['src/testing', 'src/123'],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Custom Options
|
||||
In this example, custom options are used to do something else with whatever else. So if the `testing` file has the content `Testing` and the `123` file had the content `1 2 3`, the generated result in this case would be `Testing: 1 2 3 !!!`
|
||||
|
||||
```js
|
||||
grunt.initConfig({
|
||||
prepjs: {
|
||||
options: {
|
||||
separator: ': ',
|
||||
punctuation: ' !!!',
|
||||
},
|
||||
files: {
|
||||
'dest/default_options': ['src/testing', 'src/123'],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Contributing
|
||||
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).
|
||||
|
||||
## Release History
|
||||
_(Nothing yet)_
|
||||
41
my_modules/grunts/prepjs/package.json
Normal file
41
my_modules/grunts/prepjs/package.json
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "grunt-prepjs",
|
||||
"description": "JS Preprocessor",
|
||||
"version": "0.1.0",
|
||||
"homepage": "https://github.com/jeffpar/jsmachines",
|
||||
"author": {
|
||||
"name": "jeffpar",
|
||||
"email": "jeffpar@mac.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/jeffpar/jsmachines.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/jeffpar/jsmachines/issues"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT",
|
||||
"url": "https://github.com/jeffpar/jsmachines/blob/master/LICENSE-MIT"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt-contrib-jshint": "~0.6.0",
|
||||
"grunt-contrib-clean": "~0.4.0",
|
||||
"grunt-contrib-nodeunit": "~0.2.0",
|
||||
"grunt": "~0.4.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"grunt": "~0.4.4"
|
||||
},
|
||||
"keywords": [
|
||||
"gruntplugin"
|
||||
]
|
||||
}
|
||||
401
my_modules/grunts/prepjs/tasks/prepjs.js
Normal file
401
my_modules/grunts/prepjs/tasks/prepjs.js
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
/**
|
||||
* @fileoverview Pre-process JavaScript file(s) with well-defined constants inlined
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
|
||||
* @version 1.1
|
||||
* Created 2014-Mar-22
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of C1Pjs, PCjs, and other related components written
|
||||
* by Jeff Parsons and originally published at cpusim.org and jsmachines.net.
|
||||
*
|
||||
* C1Pjs and PCjs are free software: you can redistribute them and/or modify
|
||||
* them under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* C1Pjs and PCjs are distributed in the hope that they will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with C1Pjs and PCjs. If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source
|
||||
* code file of every copy or modified version of this work, and to display
|
||||
* that copyright notice on every screen that loads or runs any version
|
||||
* of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some C1Pjs and PCjs files also attempt to load external resource files, such
|
||||
* as character-image files and ROM files. Those external resource files are
|
||||
* not considered part of the PCjs program for purposes of the GNU General Public
|
||||
* License, and the author does not claim any copyright as to their contents.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Options
|
||||
* ---
|
||||
* The 'includeObjectConstants' option allows replacement of constants defined
|
||||
* within objects, instead of only global constants. Use with caution, because
|
||||
* constants defined within an object scope may not be unique. This does attempt
|
||||
* to catch any constant collisions and completely disable their replacement, but
|
||||
* it's not foolproof.
|
||||
*
|
||||
* History
|
||||
* ---
|
||||
* Although we run all our code through Google's Closure Compiler, which does a
|
||||
* great job of inlining not only variables but also code to improve performance,
|
||||
* JavaScript doesn't have the notion of "constants", so we have to define all our
|
||||
* constants as properties and simply trust that the Closure Compiler will inline
|
||||
* them all. I'm not completely trusting, so I've created this script that allows
|
||||
* us to verify the compiler produces substantially the same code whether or not
|
||||
* we inline all our constants first.
|
||||
*
|
||||
* See "inline.php" for the original PHP version of this module.
|
||||
*
|
||||
* Conventions
|
||||
* ---
|
||||
* This script looks for global constant definitions of the form "Component.XXX = YYY;".
|
||||
*
|
||||
* The current approach requires all inlined constants to be defined as properties on
|
||||
* the associated class constructor (ie, as "class constants"). Life might be simpler
|
||||
* if the Closure Compiler honored "@const" references for properties, and who knows,
|
||||
* perhaps the latest version does now; but on the other hand, having my own convention
|
||||
* relieves me from having to annotate every single constant with a "@const" JSDoc tag.
|
||||
*
|
||||
* TODO: Update the C1Pjs sources to use class constants instead of "object constants",
|
||||
* because in order for C1Pjs to benefit from constant inlining, we must reply on the
|
||||
* 'includeObjectConstants' option (hack) to expand the contexts that constants may live
|
||||
* in, which is inherently less safe. Moreover, that option prevents us from removing
|
||||
* the original constant definitions, because constants like "this.PORT_CRA" could be used
|
||||
* in other contexts that this script will NOT catch (eg, "controller.PORT_CRA").
|
||||
*
|
||||
* TODO: Think about adding another quick hack to this tool, to convert all:
|
||||
*
|
||||
* at-param {Debugger} dbg
|
||||
* to:
|
||||
* at-param {Component} dbg
|
||||
*
|
||||
* prior to compilation. The only reason I declared my "dbg" variables generically,
|
||||
* as Component objects rather than Debugger objects, was to work around compilation
|
||||
* errors in the non-Debugger builds.
|
||||
*
|
||||
* Implementation
|
||||
* ---
|
||||
* This script uses a very simplistic replacement approach that doesn't perform any
|
||||
* parsing, tokenizing or other pre-processing of the source code, which would otherwise
|
||||
* be required if we wanted to guarantee that all our replacements precisely mirrored what
|
||||
* JavaScript actually replaces at run-time. For example, JavaScript allows any so-called
|
||||
* constant to be redefined at any point, and we don't attempt to catch modifications.
|
||||
*
|
||||
* This is why it's important that we limit inlining to only those constants described
|
||||
* above, and why such constants must never be altered by the code using them.
|
||||
*
|
||||
* Debugging
|
||||
* ===
|
||||
* Use the following command to debug this task (after making Chrome your default browser):
|
||||
*
|
||||
* node-debug $(which grunt) prepjs
|
||||
*
|
||||
* which required installing "node-inspector" first:
|
||||
*
|
||||
* sudo npm install -g node-inspector
|
||||
*
|
||||
* You may also want to enable the heapdump code below, which required installing "heapdump" first:
|
||||
*
|
||||
* npm install heapdump --save-dev
|
||||
*
|
||||
* TODO: Resolve once and for all the "process out of memory" error that occurs if we don't divide
|
||||
* the src input into smaller chunks. See the WARNING below.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// var heapdump = require("heapdump");
|
||||
|
||||
/**
|
||||
* compareConstants(a, b)
|
||||
*
|
||||
* @param {Array} a
|
||||
* @param {Array} b
|
||||
* @returns {number}
|
||||
*/
|
||||
var compareConstants = function(a, b)
|
||||
{
|
||||
return b[0].length - a[0].length;
|
||||
};
|
||||
|
||||
/**
|
||||
* indexOfConstant(sConstant, aConstants)
|
||||
*
|
||||
* @param {string} sConstant
|
||||
* @param {Array} aConstants
|
||||
* @returns {number} index of aConstants entry, or -1 if not found
|
||||
*/
|
||||
var indexOfConstant = function(sConstant, aConstants)
|
||||
{
|
||||
for (var i = 0; i < aConstants.length; i++) {
|
||||
if (sConstant == aConstants[i][0]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* findConstants(sInput)
|
||||
*
|
||||
* @param {string} sInput
|
||||
* @param {Array} aConstants
|
||||
* @param {boolean} fObjectConstants
|
||||
* @param {boolean} fReplaceConstants
|
||||
* @param {function} [fnLog]
|
||||
* @returns {string} modified input
|
||||
*/
|
||||
var findConstants = function(sInput, aConstants, fObjectConstants, fReplaceConstants, fnLog) {
|
||||
var sWarning = "";
|
||||
var sConstDef = "[A-Z][A-Za-z0-9_]*";
|
||||
if (fObjectConstants) {
|
||||
sConstDef = "(?:" + sConstDef + "|this)";
|
||||
}
|
||||
var aConstDef;
|
||||
var reConstDef = new RegExp("[ \t]*(" + sConstDef + "\\.[A-Z_][A-Z0-9_\\.]*)\\s*=\\s*(.*?)\\s*;[\t ]*(?://[^\n]*|)\n", "g");
|
||||
while (aConstDef = reConstDef.exec(sInput)) {
|
||||
var i;
|
||||
var sFind = aConstDef[1];
|
||||
var sReplace = aConstDef[2];
|
||||
if (fReplaceConstants && !fObjectConstants) {
|
||||
sInput = sInput.substr(0, aConstDef.index) + sInput.substr(aConstDef.index + aConstDef[0].length);
|
||||
reConstDef.lastIndex -= aConstDef[0].length;
|
||||
}
|
||||
if ((i = indexOfConstant(sFind, aConstants)) >= 0) {
|
||||
sWarning += "/*\n * warning: multiple definitions for '" + sFind + "' (" + sReplace + ")\n */\n";
|
||||
aConstants[i][2] = -1; // set a negative replacement count to disable this constant definition
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
* If the replacement string is entirely quoted, or parenthesized, or a single constant, then we can leave
|
||||
* the replacement string as-is; otherwise, let's wrap it with parentheses (we could probably wrap everything
|
||||
* with parentheses, but I like to avoid doing that whenever it's completely unnecessary).
|
||||
*/
|
||||
if (!sReplace.match(/^["'\(].*["'\)]$/) && sReplace.match(/[^A-Za-z0-9\.]/)) {
|
||||
sReplace = "(" + sReplace + ")";
|
||||
}
|
||||
if (fnLog) {
|
||||
fnLog("found '" + sFind + "' => '" + sReplace + "'");
|
||||
/*
|
||||
if (sFind == "Video.CRT.CURSOR_END") {
|
||||
fnLog("here's where we usually run of memory (" + process.cwd() + ")");
|
||||
heapdump.writeSnapshot();
|
||||
}
|
||||
*/
|
||||
}
|
||||
aConstants.push([sFind, sReplace, 0]);
|
||||
}
|
||||
if (sWarning) sInput = sWarning + sInput;
|
||||
return sInput;
|
||||
};
|
||||
|
||||
/**
|
||||
* replaceConstants(sInput, aConstants, fObjectConstants, fInConstant, fnLog)
|
||||
*
|
||||
* @param {string} sInput
|
||||
* @param {Array} aConstants
|
||||
* @param {boolean} fObjectConstants
|
||||
* @param {boolean} [fInConstant]
|
||||
* @param {function} [fnLog]
|
||||
* @returns {string} modified input
|
||||
*/
|
||||
var replaceConstants = function(sInput, aConstants, fObjectConstants, fInConstant, fnLog)
|
||||
{
|
||||
do {
|
||||
var cReplacements = 0;
|
||||
for (var i = 0; i < aConstants.length; i++) {
|
||||
if (aConstants[i][2] < 0) continue; // skip any replacement for which we recorded multiple definitions (ie, negative replacement count)
|
||||
var sFind = aConstants[i][0];
|
||||
var sReplace = aConstants[i][1];
|
||||
var cchFind = sFind.length;
|
||||
var cchReplace = sReplace.length;
|
||||
var iNext = 0;
|
||||
while ((iNext = sInput.indexOf(sFind, iNext)) >= 0) {
|
||||
if (fObjectConstants) {
|
||||
/*
|
||||
* As discussed earlier, the 'includeObjectConstants' option precludes removing any constant definitions,
|
||||
* so we must additionally ensure that we don't inadvertently perform replacements on those definitions.
|
||||
*/
|
||||
if (sInput.substr(iNext, 1024).match(/([A-Za-z][A-Za-z0-9_]*\.[A-Z_][A-Z0-9_\.]*)\s*=\s*(.*?)\s*;[\t ]*(?:\/\/[^\n]*|)\n/)) {
|
||||
iNext += cchFind;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (fnLog) {
|
||||
fnLog("replaced '" + sFind + "' with '" + sReplace + "'");
|
||||
}
|
||||
sInput = sInput.substr(0, iNext) + sReplace + sInput.substr(iNext + cchFind);
|
||||
aConstants[i][2]++;
|
||||
cReplacements++;
|
||||
iNext += cchReplace;
|
||||
}
|
||||
/*
|
||||
* If we've just done any replacements within another constant, then start the process over again
|
||||
* with the longest constant, to ensure we don't perform any partial replacements.
|
||||
*/
|
||||
if (fInConstant && cReplacements) break;
|
||||
}
|
||||
} while (cReplacements);
|
||||
return sInput;
|
||||
};
|
||||
|
||||
module.exports = function(grunt) {
|
||||
|
||||
// Please see the Grunt documentation for more information regarding task
|
||||
// creation: http://gruntjs.com/creating-tasks
|
||||
|
||||
grunt.registerMultiTask('prepjs', 'JS Preprocessor', function() {
|
||||
/*
|
||||
* Merge task-specific and/or target-specific options with these defaults.
|
||||
*/
|
||||
var options = this.options({
|
||||
includeObjectConstants: false,
|
||||
listConstants: true,
|
||||
replaceConstants: true
|
||||
});
|
||||
/*
|
||||
* Iterate over all specified file groups.
|
||||
*
|
||||
* See http://gruntjs.com/inside-tasks#this.files, which says in part:
|
||||
*
|
||||
* Your task should iterate over the this.files array, utilizing the src and dest
|
||||
* properties of each object in that array. The this.files property will always be an array.
|
||||
* The src property will also always be an array, in case your task cares about multiple
|
||||
* source files per destination file.
|
||||
*
|
||||
* And http://gruntjs.com/configuring-tasks#files-array-format, which explains that all files
|
||||
* objects support src and dest but the 'Files Array' format supports a few additional properties:
|
||||
*
|
||||
* filter: Either a valid fs.Stats method name or a function that is passed the matched
|
||||
* src filepath and returns true or false;
|
||||
*
|
||||
* nonull: If set to true then the operation will include non-matching patterns. Combined
|
||||
* with grunt's --verbose flag, this option can help debug file path issues;
|
||||
*
|
||||
* dot: Allow patterns to match filenames starting with a period, even if the pattern does
|
||||
* not explicitly have a period in that spot;
|
||||
*
|
||||
* matchBase: If set, patterns without slashes will be matched against the basename of the path
|
||||
* if it contains slashes. For example, a?b would match the path /xyz/123/acb, but not /xyz/acb/123;
|
||||
*
|
||||
* expand: Process a dynamic src-dest file mapping, see "Building the files object dynamically"
|
||||
* for more information.
|
||||
*/
|
||||
this.files.forEach(function(file) {
|
||||
|
||||
/*
|
||||
* Allow any of our options to be set at the target level as well
|
||||
*/
|
||||
var fObjectConstants = file.includeObjectConstants;
|
||||
if (fObjectConstants === undefined) fObjectConstants = options.includeObjectConstants;
|
||||
|
||||
var fListConstants = file.listConstants;
|
||||
if (fListConstants === undefined) fListConstants = options.listConstants;
|
||||
|
||||
var fReplaceConstants = file.replaceConstants;
|
||||
if (fReplaceConstants === undefined) fReplaceConstants = options.replaceConstants;
|
||||
|
||||
/*
|
||||
* Read all file contents into src
|
||||
*/
|
||||
var src = file.src.filter(function(sFilePath) {
|
||||
/*
|
||||
* Warn on and remove invalid source files (if nonull was set)
|
||||
*/
|
||||
if (!grunt.file.exists(sFilePath)) {
|
||||
grunt.log.warn('Source file "' + sFilePath + '" not found.');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).map(function(sFilePath) {
|
||||
/*
|
||||
* Read a file
|
||||
*/
|
||||
return grunt.file.read(sFilePath);
|
||||
}).join("\n");
|
||||
|
||||
/*
|
||||
* Find all the constants in src, and remove their definitions from src if possible
|
||||
* (ie, if fReplaceConstants is true and fObjectConstants is false).
|
||||
*
|
||||
* aConstants is the array of constant definitions, where each definition is another
|
||||
* 3-element array:
|
||||
*
|
||||
* [0]: the original string (ie, the name of the constant)
|
||||
* [1]: the replacement string (ie, the value of the constant)
|
||||
* [2]: a replacement count, initialized to zero; set to -1 if a duplicate is found
|
||||
*
|
||||
* WARNING: Grunt (ie, Node) fails with the following error:
|
||||
*
|
||||
* FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory
|
||||
*
|
||||
* when processing a large src stream (eg, on the order 1.5Mb), and it always seems to
|
||||
* die in findConstants()'s call to RegExp's exec() method. I couldn't glean any clues
|
||||
* from the heapdump (other than observing that, yes, running Grunt generates a shitload of
|
||||
* objects and is probably not very well-tuned), so I've implemented a simple work-around:
|
||||
* divide the src stream into two parts. If necessary, this work-around can easily be
|
||||
* generalized to N parts. If the problem is a direct side-effect of passing very large
|
||||
* strings to the RegExp library, then this is clearly one way to avoid that problem.
|
||||
*/
|
||||
var aConstants = [];
|
||||
var i = src.length/2;
|
||||
i = src.indexOf("\n", i) + 1; // divide the src after the first linefeed beyond the midpoint
|
||||
var src1 = src.substr(0, i);
|
||||
var src2 = src.substr(i);
|
||||
// grunt.log.writeln("findConstants(src1): " + src1.length + " chars");
|
||||
src1 = findConstants(src1, aConstants, fObjectConstants, fReplaceConstants);
|
||||
// grunt.log.writeln("findConstants(src2): " + src2.length + " chars");
|
||||
src2 = findConstants(src2, aConstants, fObjectConstants, fReplaceConstants);
|
||||
|
||||
/*
|
||||
* Sort the constants in order of longest definition to shortest, because we perform all the replacements
|
||||
* in array order, and we can't allow definitions that are subsets of longer definitions to be replaced first.
|
||||
*/
|
||||
// grunt.log.writeln("aConstants.sort()");
|
||||
aConstants.sort(compareConstants);
|
||||
|
||||
// grunt.log.writeln("replacing constants in other constants");
|
||||
aConstants.forEach(function(constant) {
|
||||
constant[1] = replaceConstants(constant[1], aConstants, fObjectConstants, true);
|
||||
});
|
||||
|
||||
if (fReplaceConstants) {
|
||||
// grunt.log.writeln("replaceConstants(src1)");
|
||||
src1 = replaceConstants(src1, aConstants, fObjectConstants, false);
|
||||
// grunt.log.writeln("replaceConstants(src2)");
|
||||
src2 = replaceConstants(src2, aConstants, fObjectConstants, false);
|
||||
// grunt.log.writeln("replaceConstants() complete");
|
||||
}
|
||||
|
||||
var sListing = "";
|
||||
|
||||
if (fListConstants) {
|
||||
// grunt.log.writeln("listing constants");
|
||||
sListing += "/*\n * List of grunt-prepjs replacements:\n *\n";
|
||||
for (i = 0; i < aConstants.length; i++) {
|
||||
sListing += " * " + aConstants[i][0] + " => " + aConstants[i][1] + " (" + aConstants[i][2] + " occurrences)\n";
|
||||
}
|
||||
sListing += " */\n";
|
||||
}
|
||||
|
||||
/*
|
||||
* Write the destination file
|
||||
*/
|
||||
// grunt.log.writeln("writing " + f.dest);
|
||||
grunt.file.write(file.dest, sListing + src1 + src2);
|
||||
|
||||
/*
|
||||
* Print a success message
|
||||
*/
|
||||
grunt.log.writeln('File "' + file.dest + '" created.');
|
||||
});
|
||||
});
|
||||
};
|
||||
1
my_modules/grunts/prepjs/test/expected/custom_options
Normal file
1
my_modules/grunts/prepjs/test/expected/custom_options
Normal file
|
|
@ -0,0 +1 @@
|
|||
Testing: 1 2 3 !!!
|
||||
1
my_modules/grunts/prepjs/test/expected/default_options
Normal file
1
my_modules/grunts/prepjs/test/expected/default_options
Normal file
|
|
@ -0,0 +1 @@
|
|||
Testing, 1 2 3.
|
||||
1
my_modules/grunts/prepjs/test/fixtures/123
vendored
Normal file
1
my_modules/grunts/prepjs/test/fixtures/123
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
1 2 3
|
||||
1
my_modules/grunts/prepjs/test/fixtures/testing
vendored
Normal file
1
my_modules/grunts/prepjs/test/fixtures/testing
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
Testing
|
||||
48
my_modules/grunts/prepjs/test/prepjs_test.js
Normal file
48
my_modules/grunts/prepjs/test/prepjs_test.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
'use strict';
|
||||
|
||||
var grunt = require('grunt');
|
||||
|
||||
/*
|
||||
======== A Handy Little Nodeunit Reference ========
|
||||
https://github.com/caolan/nodeunit
|
||||
|
||||
Test methods:
|
||||
test.expect(numAssertions)
|
||||
test.done()
|
||||
Test assertions:
|
||||
test.ok(value, [message])
|
||||
test.equal(actual, expected, [message])
|
||||
test.notEqual(actual, expected, [message])
|
||||
test.deepEqual(actual, expected, [message])
|
||||
test.notDeepEqual(actual, expected, [message])
|
||||
test.strictEqual(actual, expected, [message])
|
||||
test.notStrictEqual(actual, expected, [message])
|
||||
test.throws(block, [error], [message])
|
||||
test.doesNotThrow(block, [error], [message])
|
||||
test.ifError(value)
|
||||
*/
|
||||
|
||||
exports.prepjs = {
|
||||
setUp: function(done) {
|
||||
// setup here if necessary
|
||||
done();
|
||||
},
|
||||
default_options: function(test) {
|
||||
test.expect(1);
|
||||
|
||||
var actual = grunt.file.read('tmp/default_options');
|
||||
var expected = grunt.file.read('test/expected/default_options');
|
||||
test.equal(actual, expected, 'should describe what the default behavior is.');
|
||||
|
||||
test.done();
|
||||
},
|
||||
custom_options: function(test) {
|
||||
test.expect(1);
|
||||
|
||||
var actual = grunt.file.read('tmp/custom_options');
|
||||
var expected = grunt.file.read('test/expected/custom_options');
|
||||
test.equal(actual, expected, 'should describe what the custom option(s) behavior is.');
|
||||
|
||||
test.done();
|
||||
},
|
||||
};
|
||||
10
my_modules/htmlout/README.md
Normal file
10
my_modules/htmlout/README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
HTMLOut
|
||||
===
|
||||
This module provides a filter() function for [server.js](../../../server.js),
|
||||
our Express-based web server. The function is installed like so:
|
||||
|
||||
app.use(HTMLOut.filter);
|
||||
|
||||
The filter function examines the URL, and if it corresponds to a directory on the server,
|
||||
the function will generate an "index.html" in that directory, based on the contents of a
|
||||
default template file (currently [common.html](../shared/templates/common.html)).
|
||||
7
my_modules/htmlout/bin/delete_indexes.sh
Executable file
7
my_modules/htmlout/bin/delete_indexes.sh
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# This script was created for the Grunt "delete_indexes" task in /Gruntfile.js; it lives here
|
||||
# because htmlout is the module responsible for "littering" the project with "index.html" files,
|
||||
# therefore it bears responsibility for cleaning them up. This is its "poor man's" solution.
|
||||
#
|
||||
find . -name "index.html" -exec grep -H -l -e "<title>pcjs.org" {} \; | sed -E "s/(.*)/rm -v \"\1\"/" | bash
|
||||
40
my_modules/htmlout/bin/htmlout
Normal file
40
my_modules/htmlout/bin/htmlout
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview Implements the HTMLOut command-line interface
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Sep-04
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var lib = path.join(path.dirname(fs.realpathSync(__filename)), "../lib/");
|
||||
|
||||
require(lib + "htmlout.js").CLI();
|
||||
1863
my_modules/htmlout/lib/htmlout.js
Normal file
1863
my_modules/htmlout/lib/htmlout.js
Normal file
File diff suppressed because it is too large
Load diff
1104
my_modules/htmlout/lib/httpapi.js
Normal file
1104
my_modules/htmlout/lib/httpapi.js
Normal file
File diff suppressed because it is too large
Load diff
19
my_modules/htmlout/package.json
Normal file
19
my_modules/htmlout/package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "htmlout",
|
||||
"version": "0.1.0",
|
||||
"description": "Filters HTTP requests and creates default documents from HTML templates",
|
||||
"main": "./lib/htmlout",
|
||||
"scripts": {
|
||||
"test": "echo \"error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Jeff Parsons <Jeff@pcjs.org>",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "GPLv3",
|
||||
"url": "http://www.gnu.org/licenses/gpl.html"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"example": "./bin/htmlout"
|
||||
}
|
||||
}
|
||||
5
my_modules/markout/README.md
Normal file
5
my_modules/markout/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
MarkOut
|
||||
===
|
||||
This modules transforms a subset of Markdown into HTML. It's used by the
|
||||
[HTMLOut](../htmlout) module to process README.md files and incorporate their
|
||||
contents into the "index.html" files that we generate when browsing directories.
|
||||
40
my_modules/markout/bin/markout
Normal file
40
my_modules/markout/bin/markout
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* @fileoverview Implements the MarkOut command-line interface
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Sep-04
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var lib = path.join(path.dirname(fs.realpathSync(__filename)), "../lib/");
|
||||
|
||||
require(lib + "markout.js").CLI();
|
||||
1091
my_modules/markout/lib/markout.js
Normal file
1091
my_modules/markout/lib/markout.js
Normal file
File diff suppressed because it is too large
Load diff
19
my_modules/markout/package.json
Normal file
19
my_modules/markout/package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "markout",
|
||||
"version": "0.1.0",
|
||||
"description": "Transforms simplified Markdown to HTML",
|
||||
"main": "./lib/markout",
|
||||
"scripts": {
|
||||
"test": "echo \"error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Jeff Parsons <Jeff@pcjs.org>",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "GPLv3",
|
||||
"url": "http://www.gnu.org/licenses/gpl.html"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"example": "./bin/markout"
|
||||
}
|
||||
}
|
||||
58
my_modules/pcjs-client/.jshintrc
Normal file
58
my_modules/pcjs-client/.jshintrc
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"boss": true,
|
||||
"eqnull": true,
|
||||
"evil": true,
|
||||
"loopfunc": true,
|
||||
"sub": true,
|
||||
"globalstrict": true,
|
||||
"globals": {
|
||||
"APP_PCJS": true,
|
||||
"APPNAME": false,
|
||||
"APPVERSION": false,
|
||||
"SITEHOST": false,
|
||||
"DEBUG": 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
|
||||
}
|
||||
}
|
||||
91
my_modules/pcjs-client/bin/machine.json
Normal file
91
my_modules/pcjs-client/bin/machine.json
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"computer": {
|
||||
"id": "ibm5150.pc-mda-64k",
|
||||
"name": "IBM PC",
|
||||
"resume": "1",
|
||||
"state": ""
|
||||
},
|
||||
"ram": [
|
||||
{ "id": "ibm5150.ramLow",
|
||||
"name": "",
|
||||
"addr": 0x00000,
|
||||
"size": 0,
|
||||
"test": true
|
||||
}
|
||||
],
|
||||
"rom": [
|
||||
{ "id": "ibm5150.romBASIC",
|
||||
"name": "",
|
||||
"addr": 0xf6000,
|
||||
"size": 0x8000,
|
||||
"file": "/devices/pc/basic/ibm-basic-1.00.json",
|
||||
"notify": ""
|
||||
},
|
||||
{ "id": "ibm5150.romBIOS",
|
||||
"name": "",
|
||||
"addr": 0xfe000,
|
||||
"size": 0x2000,
|
||||
"file": "/devices/pc/bios/5150/1981-04-24.json",
|
||||
"notify": ""
|
||||
}
|
||||
],
|
||||
"video": [
|
||||
{ "id": "ibm5150.videoMDA",
|
||||
"name": "Monochrome Display",
|
||||
"model": "",
|
||||
"mode": 7,
|
||||
"screenWidth": 720,
|
||||
"screenHeight": 350,
|
||||
"scale": true,
|
||||
"charCols": 80,
|
||||
"charRows": 25,
|
||||
"fontROM": "/devices/pc/video/ibm-mda-cga.json",
|
||||
"screenColor": "black",
|
||||
"touchScreen": false
|
||||
}
|
||||
],
|
||||
"cpu": {
|
||||
"id": "ibm5150.cpu8088",
|
||||
"name": "",
|
||||
"model": 8088,
|
||||
"clock": 0,
|
||||
"multiplier": 1,
|
||||
"autoStart": true,
|
||||
"csStart": -1,
|
||||
"csInterval": -1,
|
||||
"csStop": -1
|
||||
},
|
||||
"keyboard": {
|
||||
"id": "ibm5150.keyboard",
|
||||
"name": "",
|
||||
"model": ""
|
||||
},
|
||||
"fdc": {
|
||||
"id": "ibm5150.fdcNEC",
|
||||
"name": "",
|
||||
"autoMount": {
|
||||
"A": {
|
||||
"name": "PC-DOS 2.00 (Disk 1)",
|
||||
"path": "/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json"
|
||||
},
|
||||
"B": {
|
||||
"name": "PC-DOS 2.00 (Disk 2)",
|
||||
"path": "/disks/pc/dos/ibm/2.00/PCDOS200-DISK2.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chipset": {
|
||||
"id": "ibm5150.chipset",
|
||||
"name": "",
|
||||
"model": "5150",
|
||||
"sw1": "01000001",
|
||||
"sw2": "11110000",
|
||||
"sound": true
|
||||
},
|
||||
"debugger": {
|
||||
"id": "ibm5150.debugger",
|
||||
"name": "",
|
||||
"messages": ""
|
||||
},
|
||||
"xml": "/configs/pc/machines/5150/mda/64kb/machine.xml"
|
||||
}
|
||||
365
my_modules/pcjs-client/bin/pcjs
Normal file
365
my_modules/pcjs-client/bin/pcjs
Normal 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
|
||||
});
|
||||
70
my_modules/pcjs-client/lib/README.md
Normal file
70
my_modules/pcjs-client/lib/README.md
Normal 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.
|
||||
773
my_modules/pcjs-client/lib/bus.js
Normal file
773
my_modules/pcjs-client/lib/bus.js
Normal 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;
|
||||
4072
my_modules/pcjs-client/lib/chipset.js
Normal file
4072
my_modules/pcjs-client/lib/chipset.js
Normal file
File diff suppressed because it is too large
Load diff
1288
my_modules/pcjs-client/lib/computer.js
Normal file
1288
my_modules/pcjs-client/lib/computer.js
Normal file
File diff suppressed because it is too large
Load diff
1125
my_modules/pcjs-client/lib/cpu.js
Normal file
1125
my_modules/pcjs-client/lib/cpu.js
Normal file
File diff suppressed because it is too large
Load diff
4425
my_modules/pcjs-client/lib/debugger.js
Normal file
4425
my_modules/pcjs-client/lib/debugger.js
Normal file
File diff suppressed because it is too large
Load diff
118
my_modules/pcjs-client/lib/defines.js
Normal file
118
my_modules/pcjs-client/lib/defines.js
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
* @fileoverview PCjs-specific compile-time definitions.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-May-08
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* APP_PCJS collects all PCjs application globals in one convenient place
|
||||
*/
|
||||
if (DEBUG) {
|
||||
var APP_PCJS = {Component: null};
|
||||
if (typeof Component === 'function') APP_PCJS.Component = Component;
|
||||
}
|
||||
|
||||
/**
|
||||
* @define {string}
|
||||
*/
|
||||
var PCJSCLASS = "pcjs"; // this @define is the default application class (formerly APPCLASS) to use for PCjs
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
*
|
||||
* WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
|
||||
* In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate. When it's *false*,
|
||||
* nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
|
||||
* "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
|
||||
* debugger.js from the compilation process altogether.
|
||||
*
|
||||
* However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
|
||||
* I would like to skip loading debugger.js altogether. When doing that, we must ALSO arrange for an additional file
|
||||
* (nodebugger.js) to be loaded immediately after this file, which *explicitly* overrides DEBUGGER with *false*.
|
||||
*/
|
||||
var DEBUGGER = true; // this @define is overridden by the Closure Compiler to remove Debugger-related support
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
*
|
||||
* PREFETCH enables the use of a prefetch queue.
|
||||
*
|
||||
* See the Bus component for details.
|
||||
*/
|
||||
var PREFETCH = false;
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
*
|
||||
* 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?
|
||||
*/
|
||||
}
|
||||
1489
my_modules/pcjs-client/lib/disk.js
Normal file
1489
my_modules/pcjs-client/lib/disk.js
Normal file
File diff suppressed because it is too large
Load diff
2026
my_modules/pcjs-client/lib/fdc.js
Normal file
2026
my_modules/pcjs-client/lib/fdc.js
Normal file
File diff suppressed because it is too large
Load diff
1907
my_modules/pcjs-client/lib/hdc.js
Normal file
1907
my_modules/pcjs-client/lib/hdc.js
Normal file
File diff suppressed because it is too large
Load diff
1377
my_modules/pcjs-client/lib/keyboard.js
Normal file
1377
my_modules/pcjs-client/lib/keyboard.js
Normal file
File diff suppressed because it is too large
Load diff
586
my_modules/pcjs-client/lib/mem.js
Normal file
586
my_modules/pcjs-client/lib/mem.js
Normal 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;
|
||||
590
my_modules/pcjs-client/lib/mouse.js
Normal file
590
my_modules/pcjs-client/lib/mouse.js
Normal 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;
|
||||
46
my_modules/pcjs-client/lib/nodebugger.js
Normal file
46
my_modules/pcjs-client/lib/nodebugger.js
Normal 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;
|
||||
163
my_modules/pcjs-client/lib/panel.js
Normal file
163
my_modules/pcjs-client/lib/panel.js
Normal 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;
|
||||
215
my_modules/pcjs-client/lib/ram.js
Normal file
215
my_modules/pcjs-client/lib/ram.js
Normal 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;
|
||||
359
my_modules/pcjs-client/lib/rom.js
Normal file
359
my_modules/pcjs-client/lib/rom.js
Normal 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;
|
||||
795
my_modules/pcjs-client/lib/serial.js
Normal file
795
my_modules/pcjs-client/lib/serial.js
Normal 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;
|
||||
400
my_modules/pcjs-client/lib/state.js
Normal file
400
my_modules/pcjs-client/lib/state.js
Normal 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;
|
||||
5094
my_modules/pcjs-client/lib/video.js
Normal file
5094
my_modules/pcjs-client/lib/video.js
Normal file
File diff suppressed because it is too large
Load diff
311
my_modules/pcjs-client/lib/x86.js
Normal file
311
my_modules/pcjs-client/lib/x86.js
Normal 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;
|
||||
2608
my_modules/pcjs-client/lib/x86cpu.js
Normal file
2608
my_modules/pcjs-client/lib/x86cpu.js
Normal file
File diff suppressed because it is too large
Load diff
1348
my_modules/pcjs-client/lib/x86grps.js
Normal file
1348
my_modules/pcjs-client/lib/x86grps.js
Normal file
File diff suppressed because it is too large
Load diff
471
my_modules/pcjs-client/lib/x86help.js
Normal file
471
my_modules/pcjs-client/lib/x86help.js
Normal 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;
|
||||
15870
my_modules/pcjs-client/lib/x86mods.js
Normal file
15870
my_modules/pcjs-client/lib/x86mods.js
Normal file
File diff suppressed because it is too large
Load diff
419
my_modules/pcjs-client/lib/x86op0f.js
Normal file
419
my_modules/pcjs-client/lib/x86op0f.js
Normal 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
Loading…
Reference in a new issue