Merge branch 'next-release'
This commit is contained in:
commit
7fa19afe3c
410 changed files with 46555 additions and 27752 deletions
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @fileoverview The Component class used by C1Pjs and PCx86.
|
||||
* @fileoverview The Component class used by all PCjs components.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-May-14
|
||||
|
|
@ -30,8 +30,8 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* All the C1Pjs and PCjs components now use JSDoc types, primarily so that Google's Closure Compiler
|
||||
* will compile everything with ZERO warnings. For more information about the JSDoc types supported by
|
||||
* All the PCjs components now use JSDoc types, primarily so that Google's Closure Compiler will
|
||||
* compile everything with ZERO warnings. For more information about the JSDoc types supported by
|
||||
* the Closure Compiler:
|
||||
*
|
||||
* https://developers.google.com/closure/compiler/docs/js-for-compiler#types
|
||||
|
|
@ -110,11 +110,11 @@ function Component(type, parms, constructor, bitsMessage)
|
|||
* subclasses to do the same, to reduce the property clutter we have to wade through while debugging.
|
||||
*/
|
||||
this.flags = {
|
||||
fReady: false,
|
||||
fBusy: false,
|
||||
fBusyCancel: false,
|
||||
fPowered: false,
|
||||
fError: false
|
||||
ready: false,
|
||||
busy: false,
|
||||
busyCancel: false,
|
||||
powered: false,
|
||||
error: false
|
||||
};
|
||||
|
||||
this.fnReady = null;
|
||||
|
|
@ -769,8 +769,8 @@ Component.prototype = {
|
|||
* TODO: Add a task to the build process that "asserts" there are no instances of "assertion failure" in RELEASE builds.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {boolean} f is the expression we are asserting to be true
|
||||
* @param {string} [s] is description of the assertion on failure
|
||||
* @param {boolean|number} f is the expression asserted to be true
|
||||
* @param {string} [s] is a description of the assertion to be displayed or logged on failure
|
||||
*/
|
||||
assert: function(f, s) {
|
||||
if (DEBUG) {
|
||||
|
|
@ -806,7 +806,7 @@ Component.prototype = {
|
|||
}
|
||||
},
|
||||
/**
|
||||
* println(s, type)
|
||||
* println(s, type, id)
|
||||
*
|
||||
* For non-diagnostic messages, which components may override to control the destination/appearance of their output.
|
||||
*
|
||||
|
|
@ -856,7 +856,7 @@ Component.prototype = {
|
|||
* @param {string} s describes a fatal error condition
|
||||
*/
|
||||
setError: function(s) {
|
||||
this.flags.fError = true;
|
||||
this.flags.error = true;
|
||||
this.notice(s); // TODO: Any cases where we should still prefix this string with "Fatal error: "?
|
||||
},
|
||||
/**
|
||||
|
|
@ -867,7 +867,7 @@ Component.prototype = {
|
|||
* @this {Component}
|
||||
*/
|
||||
clearError: function() {
|
||||
this.flags.fError = false;
|
||||
this.flags.error = false;
|
||||
},
|
||||
/**
|
||||
* isError()
|
||||
|
|
@ -878,7 +878,7 @@ Component.prototype = {
|
|||
* @return {boolean} true if a fatal error condition exists, false if not
|
||||
*/
|
||||
isError: function() {
|
||||
if (this.flags.fError) {
|
||||
if (this.flags.error) {
|
||||
this.println(this.toString() + " error");
|
||||
return true;
|
||||
}
|
||||
|
|
@ -899,14 +899,14 @@ Component.prototype = {
|
|||
*/
|
||||
isReady: function(fnReady) {
|
||||
if (fnReady) {
|
||||
if (this.flags.fReady) {
|
||||
if (this.flags.ready) {
|
||||
fnReady();
|
||||
} else {
|
||||
if (MAXDEBUG) this.log("NOT ready");
|
||||
this.fnReady = fnReady;
|
||||
}
|
||||
}
|
||||
return this.flags.fReady;
|
||||
return this.flags.ready;
|
||||
},
|
||||
/**
|
||||
* setReady(fReady)
|
||||
|
|
@ -917,9 +917,9 @@ Component.prototype = {
|
|||
* @param {boolean} [fReady] is assumed to indicate "ready" unless EXPLICITLY set to false
|
||||
*/
|
||||
setReady: function(fReady) {
|
||||
if (!this.flags.fError) {
|
||||
this.flags.fReady = (fReady !== false);
|
||||
if (this.flags.fReady) {
|
||||
if (!this.flags.error) {
|
||||
this.flags.ready = (fReady !== false);
|
||||
if (this.flags.ready) {
|
||||
if (MAXDEBUG /* || this.name */) this.log("ready");
|
||||
var fnReady = this.fnReady;
|
||||
this.fnReady = null;
|
||||
|
|
@ -937,38 +937,36 @@ Component.prototype = {
|
|||
* @return {boolean} true if "busy", false if not
|
||||
*/
|
||||
isBusy: function(fCancel) {
|
||||
if (this.flags.fBusy) {
|
||||
if (this.flags.busy) {
|
||||
if (fCancel) {
|
||||
this.flags.fBusyCancel = true;
|
||||
this.flags.busyCancel = true;
|
||||
} else if (fCancel === undefined) {
|
||||
this.println(this.toString() + " busy");
|
||||
}
|
||||
}
|
||||
return this.flags.fBusy;
|
||||
return this.flags.busy;
|
||||
},
|
||||
/**
|
||||
* setBusy(fBusy)
|
||||
*
|
||||
* Update the current busy state; if an fCancel request is pending, it will be honored now.
|
||||
* Update the current busy state; if a busyCancel request is pending, it will be honored now.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {boolean} fBusy
|
||||
* @return {boolean}
|
||||
*/
|
||||
setBusy: function(fBusy) {
|
||||
if (this.flags.fBusyCancel) {
|
||||
if (this.flags.fBusy) {
|
||||
this.flags.fBusy = false;
|
||||
}
|
||||
this.flags.fBusyCancel = false;
|
||||
if (this.flags.busyCancel) {
|
||||
this.flags.busy = false;
|
||||
this.flags.busyCancel = false;
|
||||
return false;
|
||||
}
|
||||
if (this.flags.fError) {
|
||||
if (this.flags.error) {
|
||||
this.println(this.toString() + " error");
|
||||
return false;
|
||||
}
|
||||
this.flags.fBusy = fBusy;
|
||||
return this.flags.fBusy;
|
||||
this.flags.busy = fBusy;
|
||||
return this.flags.busy;
|
||||
},
|
||||
/**
|
||||
* powerUp(fSave)
|
||||
|
|
@ -979,7 +977,7 @@ Component.prototype = {
|
|||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
powerUp: function(data, fRepower) {
|
||||
this.flags.fPowered = true;
|
||||
this.flags.powered = true;
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
|
|
@ -991,7 +989,7 @@ Component.prototype = {
|
|||
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
|
||||
*/
|
||||
powerDown: function(fSave, fShutdown) {
|
||||
if (fShutdown) this.flags.fPowered = false;
|
||||
if (fShutdown) this.flags.powered = false;
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
|
|
@ -1059,4 +1057,48 @@ Component.prototype = {
|
|||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* The following polyfills provide ES5 functionality that's missing in older browsers (eg, IE8),
|
||||
* allowing PCjs apps to run without slamming into exceptions; however, due to the lack of HTML5 canvas
|
||||
* support in those browsers, all you're likely to see are "soft" errors (eg, "Missing <canvas> support").
|
||||
*
|
||||
* Perhaps we can implement a text-only faux video display for a fun retro-browser experience someday.
|
||||
*
|
||||
* TODO: Come up with a better place to put these polyfills. We will likely have more if we decide to
|
||||
* make the leap from ES5 to ES6 features.
|
||||
*/
|
||||
|
||||
/*
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
|
||||
*/
|
||||
if (!Array.prototype.indexOf) {
|
||||
Array.prototype.indexOf = function(obj, start) {
|
||||
for (var i = (start || 0), j = this.length; i < j; i++) {
|
||||
if (this[i] === obj) { return i; }
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
|
||||
*/
|
||||
if (!Function.prototype.bind) {
|
||||
Function.prototype.bind = function(obj) {
|
||||
if (typeof this != "function") {
|
||||
// Closest thing possible to the ECMAScript 5 internal IsCallable function
|
||||
throw new TypeError("Function.prototype.bind: non-callable object");
|
||||
}
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
var fToBind = this;
|
||||
var fnNOP = /** @constructor */ (function() {});
|
||||
var fnBound = function() {
|
||||
return fToBind.apply(this instanceof fnNOP && obj? this : obj, args.concat(Array.prototype.slice.call(arguments)));
|
||||
};
|
||||
fnNOP.prototype = this.prototype;
|
||||
fnBound.prototype = new fnNOP();
|
||||
return fnBound;
|
||||
};
|
||||
}
|
||||
|
||||
if (NODE) module.exports = Component;
|
||||
|
|
|
|||
705
modules/shared/lib/debugger.js
Normal file
705
modules/shared/lib/debugger.js
Normal file
|
|
@ -0,0 +1,705 @@
|
|||
/**
|
||||
* @fileoverview Common PCjs Debugger support.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Jun-21
|
||||
*
|
||||
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, a computer emulation software project at <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 COPYRIGHT in /modules/shared/lib/defines.js).
|
||||
*
|
||||
* 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 PCjs
|
||||
* for purposes of the GNU General Public License, and the author does not claim any copyright
|
||||
* as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (DEBUGGER) {
|
||||
if (NODE) {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Debugger Address Object
|
||||
*
|
||||
* This is the basic structure; other debuggers may extend it.
|
||||
*
|
||||
* addr address
|
||||
* fTemporary true if this is a temporary breakpoint address
|
||||
* sCmd set for breakpoint addresses if there's an associated command string
|
||||
* aCmds preprocessed commands (from sCmd)
|
||||
*
|
||||
* @typedef {{
|
||||
* addr:(number|undefined),
|
||||
* fTemporary:(boolean|undefined),
|
||||
* sCmd:(string|undefined),
|
||||
* aCmds:(Array.<string>|undefined)
|
||||
* }} DbgAddr
|
||||
*/
|
||||
var DbgAddr;
|
||||
|
||||
/**
|
||||
* Debugger(parmsDbg)
|
||||
*
|
||||
* The Debugger component supports the following optional (parmsDbg) properties:
|
||||
*
|
||||
* base: the base to use for most numeric input/output (default is 16)
|
||||
*
|
||||
* The Debugger component is a shared component containing a subset of functionality used by
|
||||
* the other CPU-specific Debuggers (eg, DebuggerX86). Over time, the goal is to factor out as
|
||||
* much common debugging support as possible from those components into this one.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @param {Object} parmsDbg
|
||||
*/
|
||||
function Debugger(parmsDbg)
|
||||
{
|
||||
if (DEBUGGER) {
|
||||
|
||||
Component.call(this, "Debugger", parmsDbg, Debugger);
|
||||
|
||||
/*
|
||||
* Default base used to display all values; modified with the "s base" command.
|
||||
*/
|
||||
this.nBase = parmsDbg['base'] || 16;
|
||||
this.fParens = false;
|
||||
|
||||
/*
|
||||
* These keep track of instruction activity, but only when tracing or when Debugger checks
|
||||
* have been enabled (eg, one or more breakpoints have been set).
|
||||
*
|
||||
* They are zeroed by the reset() notification handler. cInstructions is advanced by
|
||||
* stepCPU() and checkInstruction() calls. nCycles is updated by every stepCPU() or stop()
|
||||
* call and simply represents the number of cycles performed by the last run of instructions.
|
||||
*/
|
||||
this.nCycles = 0;
|
||||
this.cOpcodes = this.cOpcodesStart = 0;
|
||||
|
||||
/*
|
||||
* fAssemble is true when "assemble mode" is active, false when not.
|
||||
*/
|
||||
this.fAssemble = false;
|
||||
|
||||
/*
|
||||
* This maintains command history. New commands are inserted at index 0 of the array.
|
||||
* When Enter is pressed on an empty input buffer, we default to the command at aPrevCmds[0].
|
||||
*/
|
||||
this.iPrevCmd = -1;
|
||||
this.aPrevCmds = [];
|
||||
|
||||
/*
|
||||
* aVariables is an object with properties that grow as setVariable() assigns more variables;
|
||||
* each property corresponds to one variable, where the property name is the variable name (ie,
|
||||
* a string beginning with a letter or underscore, followed by zero or more additional letters,
|
||||
* digits, or underscores) and the property value is the variable's numeric value. See doVar()
|
||||
* and setVariable() for details.
|
||||
*
|
||||
* Note that parseValue() parses variables before numbers, so any variable that looks like a
|
||||
* unprefixed hex value (eg, "a5" as opposed to "0xa5") will trump the numeric value. Unprefixed
|
||||
* hex values are a convenience of parseValue(), which always calls str.parseInt() with a default
|
||||
* base of 16; however, that default be overridden with a variety of explicit prefixes or suffixes
|
||||
* (eg, a leading "0o" to indicate octal, a trailing period to indicate decimal, etc.)
|
||||
*
|
||||
* See str.parseInt() for more details about supported numbers.
|
||||
*/
|
||||
this.aVariables = {};
|
||||
|
||||
} // endif DEBUGGER
|
||||
}
|
||||
|
||||
if (DEBUGGER) {
|
||||
|
||||
Component.subclass(Debugger);
|
||||
|
||||
Debugger.aBinOpPrecedence = {
|
||||
'||': 0, // logical OR
|
||||
'&&': 1, // logical AND
|
||||
'|': 2, // bitwise OR
|
||||
'^': 3, // bitwise XOR
|
||||
'&': 4, // bitwise AND
|
||||
'!=': 5, // inequality
|
||||
'==': 5, // equality
|
||||
'>=': 6, // greater than or equal to
|
||||
'>': 6, // greater than
|
||||
'<=': 6, // less than or equal to
|
||||
'<': 6, // less than
|
||||
'>>>': 7, // unsigned bitwise right shift
|
||||
'>>': 7, // bitwise right shift
|
||||
'<<': 7, // bitwise left shift
|
||||
'-': 8, // subtraction
|
||||
'+': 8, // addition
|
||||
'%': 9, // remainder
|
||||
'/': 9, // division
|
||||
'*': 9 // multiplication
|
||||
};
|
||||
|
||||
/**
|
||||
* getRegIndex(sReg, off)
|
||||
*
|
||||
* NOTE: This must be implemented by the individual debuggers.
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string} sReg
|
||||
* @param {number} [off] optional offset into sReg
|
||||
* @return {number} register index, or -1 if not found
|
||||
*/
|
||||
Debugger.prototype.getRegIndex = function(sReg, off)
|
||||
{
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* getRegValue(iReg)
|
||||
*
|
||||
* NOTE: This must be implemented by the individual debuggers.
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {number} iReg
|
||||
* @return {number|undefined}
|
||||
*/
|
||||
Debugger.prototype.getRegValue = function(iReg)
|
||||
{
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* parseAddrReference(s, sAddr)
|
||||
*
|
||||
* Returns the given string with the given address reference replaced with the contents of that address.
|
||||
*
|
||||
* NOTE: This must be implemented by the individual debuggers.
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string} s
|
||||
* @param {string} sAddr
|
||||
* @return {string}
|
||||
*/
|
||||
Debugger.prototype.parseAddrReference = function(s, sAddr)
|
||||
{
|
||||
return s.replace('[' + sAddr + ']', "unimplemented");
|
||||
};
|
||||
|
||||
/**
|
||||
* getNextCommand()
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @return {string}
|
||||
*/
|
||||
Debugger.prototype.getNextCommand = function()
|
||||
{
|
||||
var sCmd;
|
||||
if (this.iPrevCmd > 0) {
|
||||
sCmd = this.aPrevCmds[--this.iPrevCmd];
|
||||
} else {
|
||||
sCmd = "";
|
||||
this.iPrevCmd = -1;
|
||||
}
|
||||
return sCmd;
|
||||
};
|
||||
|
||||
/**
|
||||
* getPrevCommand()
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @return {string|null}
|
||||
*/
|
||||
Debugger.prototype.getPrevCommand = function()
|
||||
{
|
||||
var sCmd = null;
|
||||
if (this.iPrevCmd < this.aPrevCmds.length - 1) {
|
||||
sCmd = this.aPrevCmds[++this.iPrevCmd];
|
||||
}
|
||||
return sCmd;
|
||||
};
|
||||
|
||||
/**
|
||||
* parseCommand(sCmd, fSave, chSep)
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string|undefined} sCmd
|
||||
* @param {boolean} [fSave] is true to save the command, false if not
|
||||
* @param {string} [chSep] is the command separator character (default is ';')
|
||||
* @return {Array.<string>}
|
||||
*/
|
||||
Debugger.prototype.parseCommand = function(sCmd, fSave, chSep)
|
||||
{
|
||||
if (fSave) {
|
||||
if (!sCmd) {
|
||||
if (this.fAssemble) {
|
||||
sCmd = "end";
|
||||
} else {
|
||||
sCmd = this.aPrevCmds[this.iPrevCmd+1];
|
||||
}
|
||||
} else {
|
||||
if (this.iPrevCmd < 0 && this.aPrevCmds.length) {
|
||||
this.iPrevCmd = 0;
|
||||
}
|
||||
if (this.iPrevCmd < 0 || sCmd != this.aPrevCmds[this.iPrevCmd]) {
|
||||
this.aPrevCmds.splice(0, 0, sCmd);
|
||||
this.iPrevCmd = 0;
|
||||
}
|
||||
this.iPrevCmd--;
|
||||
}
|
||||
}
|
||||
var a = [];
|
||||
if (sCmd) {
|
||||
/*
|
||||
* With the introduction of breakpoint commands (ie, quoted command sequences
|
||||
* associated with a breakpoint), we can no longer perform simplistic splitting.
|
||||
*
|
||||
* a = sCmd.split(chSep || ';');
|
||||
* for (var i = 0; i < a.length; i++) a[i] = str.trim(a[i]);
|
||||
*
|
||||
* We may now split on semi-colons ONLY if they are outside a quoted sequence.
|
||||
*
|
||||
* Also, to allow quoted strings *inside* breakpoint commands, we first replace all
|
||||
* DOUBLE double-quotes with single quotes.
|
||||
*/
|
||||
sCmd = sCmd.replace(/""/g, "'");
|
||||
|
||||
var iPrev = 0;
|
||||
var chQuote = null;
|
||||
chSep = chSep || ';';
|
||||
/*
|
||||
* NOTE: Processing charAt() up to and INCLUDING length is not a typo; we're taking
|
||||
* advantage of the fact that charAt() with an invalid index returns an empty string,
|
||||
* allowing us to use the same substring() call to capture the final portion of sCmd.
|
||||
*
|
||||
* In a sense, it allows us to pretend that the string ends with a zero terminator.
|
||||
*/
|
||||
for (var i = 0; i <= sCmd.length; i++) {
|
||||
var ch = sCmd.charAt(i);
|
||||
if (ch == '"' || ch == "'") {
|
||||
if (!chQuote) {
|
||||
chQuote = ch;
|
||||
} else if (ch == chQuote) {
|
||||
chQuote = null;
|
||||
}
|
||||
}
|
||||
else if (ch == chSep && !chQuote || !ch) {
|
||||
/*
|
||||
* Recall that substring() accepts starting (inclusive) and ending (exclusive)
|
||||
* indexes, whereas substr() accepts a starting index and a length. We need the former.
|
||||
*/
|
||||
a.push(str.trim(sCmd.substring(iPrev, i)));
|
||||
iPrev = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
/**
|
||||
* evalExpression(aVals, aOps, cOps)
|
||||
*
|
||||
* In Node, if you set a variable to 0x80000001; ie:
|
||||
*
|
||||
* foo=0x80000001|0
|
||||
*
|
||||
* and then calculate foo*foo using "(foo*foo).toString(2)", the result is:
|
||||
*
|
||||
* '11111111111111111111111111111100000000000000000000000000000000'
|
||||
*
|
||||
* which is slightly incorrect because it has overflowed JavaScript's floating-point precision.
|
||||
*
|
||||
* 0x80000001 in decimal is -2147483647, so the product is 4611686014132420609, which is 0x3FFFFFFF00000001.
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {Array.<number>} aVals
|
||||
* @param {Array.<string>} aOps
|
||||
* @param {number} [cOps] (default is all)
|
||||
* @return {boolean} true if successful, false if error
|
||||
*/
|
||||
Debugger.prototype.evalExpression = function(aVals, aOps, cOps)
|
||||
{
|
||||
cOps = cOps || -1;
|
||||
while (cOps-- && aOps.length) {
|
||||
var chOp = aOps.pop();
|
||||
if (aVals.length < 2) return false;
|
||||
var valNew;
|
||||
var val2 = aVals.pop();
|
||||
var val1 = aVals.pop();
|
||||
switch(chOp) {
|
||||
case '*':
|
||||
valNew = val1 * val2;
|
||||
break;
|
||||
case '/':
|
||||
if (!val2) return false;
|
||||
valNew = val1 / val2;
|
||||
break;
|
||||
case '%':
|
||||
if (!val2) return false;
|
||||
valNew = val1 % val2;
|
||||
break;
|
||||
case '+':
|
||||
valNew = val1 + val2;
|
||||
break;
|
||||
case '-':
|
||||
valNew = val1 - val2;
|
||||
break;
|
||||
case '<<':
|
||||
valNew = val1 << val2;
|
||||
break;
|
||||
case '>>':
|
||||
valNew = val1 >> val2;
|
||||
break;
|
||||
case '>>>':
|
||||
valNew = val1 >>> val2;
|
||||
break;
|
||||
case '<':
|
||||
valNew = (val1 < val2? 1 : 0);
|
||||
break;
|
||||
case '<=':
|
||||
valNew = (val1 <= val2? 1 : 0);
|
||||
break;
|
||||
case '>':
|
||||
valNew = (val1 > val2? 1 : 0);
|
||||
break;
|
||||
case '>=':
|
||||
valNew = (val1 >= val2? 1 : 0);
|
||||
break;
|
||||
case '==':
|
||||
valNew = (val1 == val2? 1 : 0);
|
||||
break;
|
||||
case '!=':
|
||||
valNew = (val1 != val2? 1 : 0);
|
||||
break;
|
||||
case '&':
|
||||
valNew = val1 & val2;
|
||||
break;
|
||||
case '^':
|
||||
valNew = val1 ^ val2;
|
||||
break;
|
||||
case '|':
|
||||
valNew = val1 | val2;
|
||||
break;
|
||||
case '&&':
|
||||
valNew = (val1 && val2? 1 : 0);
|
||||
break;
|
||||
case '||':
|
||||
valNew = (val1 || val2? 1 : 0);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
aVals.push(valNew|0);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* parseExpression(sExp, fPrint)
|
||||
*
|
||||
* A quick-and-dirty expression parser. It takes an expression like:
|
||||
*
|
||||
* EDX+EDX*4+12345678
|
||||
*
|
||||
* and builds a value stack in aVals and a "binop" (binary operator) stack in aOps:
|
||||
*
|
||||
* aVals aOps
|
||||
* ----- ----
|
||||
* EDX +
|
||||
* EDX *
|
||||
* 4 +
|
||||
* ...
|
||||
*
|
||||
* We pop 1 "binop" from aOps and 2 values from aVals whenever a "binop" of lower priority than its
|
||||
* predecessor is encountered, evaluate, and push the result back onto aVals.
|
||||
*
|
||||
* Unary operators like '~' and ternary operators like '?:' are not supported; neither are parentheses.
|
||||
*
|
||||
* However, parseReference() now makes it possible to write parenthetical-style sub-expressions by using
|
||||
* {...} (braces), as well as address references by using [...] (brackets).
|
||||
*
|
||||
* Why am I using braces instead of parentheses for sub-expressions? Because parseReference() serves
|
||||
* multiple purposes, the other being reference replacement in message strings passing through replaceRegs(),
|
||||
* and I didn't want parentheses taking on a new meaning in message strings.
|
||||
*
|
||||
* However, a Debugger can override this choice by setting fParens to true, if there's no conflict in its
|
||||
* replaceRegs() implementation.
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string|undefined} sExp
|
||||
* @param {boolean} [fPrint] is true to print all resolved values, false for quiet parsing
|
||||
* @return {number|undefined} numeric value, or undefined if sExp contains any undefined or invalid values
|
||||
*/
|
||||
Debugger.prototype.parseExpression = function(sExp, fPrint)
|
||||
{
|
||||
var value;
|
||||
|
||||
if (sExp) {
|
||||
/*
|
||||
* First process (and eliminate) any references, aka sub-expressions.
|
||||
*/
|
||||
sExp = this.parseReference(sExp);
|
||||
|
||||
var i = 0;
|
||||
var fError = false;
|
||||
var sExpOrig = sExp;
|
||||
var aVals = [], aOps = [];
|
||||
/*
|
||||
* All browsers (including, I believe, IE9 and up) support the following idiosyncrasy of a regexp split():
|
||||
* when the regexp uses a capturing pattern, the resulting array will include entries for all the pattern
|
||||
* matches along with the non-matches. This effectively means that, in the set of expressions that we
|
||||
* support, all even entries in asValues will contain "values" and all odd entries will contain "operators".
|
||||
*
|
||||
* And although I tried to list the supported operators in "precedential" order, bitwise operators must
|
||||
* be out-of-order so that we don't mistakenly match either '>' or '<' when they're part of '>>' or '<<'.
|
||||
*/
|
||||
var regExp = /(\|\||&&|\||^|&|!=|==|>=|>>>|>>|>|<=|<<|<|-|\+|%|\/|\*)/;
|
||||
var asValues = sExp.split(regExp);
|
||||
while (i < asValues.length) {
|
||||
var sValue = asValues[i++];
|
||||
var cchValue = sValue.length;
|
||||
var s = str.trim(sValue);
|
||||
if (!s) {
|
||||
fError = true;
|
||||
break;
|
||||
}
|
||||
var v = this.parseValue(s, null, fPrint === false);
|
||||
if (v === undefined) {
|
||||
fError = true;
|
||||
fPrint = false;
|
||||
break;
|
||||
}
|
||||
aVals.push(v);
|
||||
if (i == asValues.length) break;
|
||||
var sOp = asValues[i++], cchOp = sOp.length;
|
||||
this.assert(Debugger.aBinOpPrecedence[sOp] != null);
|
||||
if (aOps.length && Debugger.aBinOpPrecedence[sOp] < Debugger.aBinOpPrecedence[aOps[aOps.length-1]]) {
|
||||
this.evalExpression(aVals, aOps, 1);
|
||||
}
|
||||
aOps.push(sOp);
|
||||
sExp = sExp.substr(cchValue + cchOp);
|
||||
}
|
||||
if (!this.evalExpression(aVals, aOps) || aVals.length != 1) {
|
||||
fError = true;
|
||||
}
|
||||
if (!fError) {
|
||||
value = aVals.pop();
|
||||
if (fPrint) this.printValue(null, value);
|
||||
} else {
|
||||
if (fPrint) this.println("error parsing '" + sExpOrig + "' at character " + (sExpOrig.length - sExp.length));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* parseReference(s)
|
||||
*
|
||||
* Returns the given string with any "{expression}" sequences replaced with the value of the expression,
|
||||
* and any "[address]" references replaced with the contents of the address. Expressions are parsed BEFORE
|
||||
* addresses. Owing to this function's simplistic parsing, nested braces/brackets are not supported
|
||||
* (define intermediate variables if needed).
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string} s
|
||||
* @return {string}
|
||||
*/
|
||||
Debugger.prototype.parseReference = function(s)
|
||||
{
|
||||
var a;
|
||||
var chOpen = this.fParens? '(' : '{';
|
||||
var chClose = this.fParens? ')' : '}';
|
||||
var reSubExp = new RegExp(this.fParens? "\\((.*?)\\)" : "\\{(.*?)\\}");
|
||||
while (a = s.match(reSubExp)) {
|
||||
if (a[1].indexOf(chOpen) >= 0) break; // unsupported nested brace(s)
|
||||
var value = this.parseExpression(a[1]);
|
||||
s = s.replace(chOpen + a[1] + chClose, value != null? this.toStrBase(value) : "undefined");
|
||||
}
|
||||
while (a = s.match(/\[(.*?)]/)) {
|
||||
if (a[1].indexOf('[') >= 0) break; // unsupported nested bracket(s)
|
||||
s = this.parseAddrReference(s, a[1]);
|
||||
}
|
||||
return this.parseSysVars(s);
|
||||
};
|
||||
|
||||
/**
|
||||
* parseSysVars(s)
|
||||
*
|
||||
* Returns the given string with any recognized "$var" replaced with its value; eg:
|
||||
*
|
||||
* $ops: the number of opcodes executed since the last time it was displayed (or reset)
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string} s
|
||||
* @return {string}
|
||||
*/
|
||||
Debugger.prototype.parseSysVars = function(s)
|
||||
{
|
||||
var a;
|
||||
while (a = s.match(/\$([a-z]+)/i)) {
|
||||
var v = null;
|
||||
switch(a[1].toLowerCase()) {
|
||||
case "ops":
|
||||
v = this.cOpcodes - this.cOpcodesStart;
|
||||
break;
|
||||
}
|
||||
if (v == null) break;
|
||||
s = s.replace(a[0], v.toString());
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
/**
|
||||
* parseValue(sValue, sName, fQuiet)
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string|undefined} sValue
|
||||
* @param {string|null} [sName] is the name of the value, if any
|
||||
* @param {boolean} [fQuiet]
|
||||
* @return {number|undefined} numeric value, or undefined if sValue is either undefined or invalid
|
||||
*/
|
||||
Debugger.prototype.parseValue = function(sValue, sName, fQuiet)
|
||||
{
|
||||
var value;
|
||||
if (sValue != null) {
|
||||
var iReg = this.getRegIndex(sValue);
|
||||
if (iReg >= 0) {
|
||||
value = this.getRegValue(iReg);
|
||||
} else {
|
||||
value = this.getVariable(sValue);
|
||||
if (value == null) value = str.parseInt(sValue, this.nBase);
|
||||
}
|
||||
if (value == null && !fQuiet) this.println("invalid " + (sName? sName : "value") + ": " + sValue);
|
||||
} else {
|
||||
if (!fQuiet) this.println("missing " + (sName || "value"));
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* printValue(sVar, value)
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string|null} sVar
|
||||
* @param {number|undefined} value
|
||||
* @return {boolean} true if value defined, false if not
|
||||
*/
|
||||
Debugger.prototype.printValue = function(sVar, value)
|
||||
{
|
||||
var sValue;
|
||||
var fDefined = false;
|
||||
if (value !== undefined) {
|
||||
fDefined = true;
|
||||
sValue = str.toHex(value, 0, true) + " " + value + ". " + str.toOct(value, 0, true) + " " + str.toBinBytes(value, 0, true);
|
||||
if (value >= 0x20 && value < 0x7F) {
|
||||
sValue += " '" + String.fromCharCode(value) + "'";
|
||||
}
|
||||
}
|
||||
sVar = (sVar != null? (sVar + ": ") : "");
|
||||
this.println(sVar + sValue);
|
||||
return fDefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* printVariable(sVar)
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string} [sVar]
|
||||
* @return {boolean} true if all value(s) defined, false if not
|
||||
*/
|
||||
Debugger.prototype.printVariable = function(sVar)
|
||||
{
|
||||
if (sVar) {
|
||||
return this.printValue(sVar, this.aVariables[sVar]);
|
||||
}
|
||||
var cVariables = 0;
|
||||
for (sVar in this.aVariables) {
|
||||
this.printValue(sVar, this.aVariables[sVar]);
|
||||
cVariables++;
|
||||
}
|
||||
return cVariables > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* delVariable(sVar)
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string} sVar
|
||||
*/
|
||||
Debugger.prototype.delVariable = function(sVar)
|
||||
{
|
||||
delete this.aVariables[sVar];
|
||||
};
|
||||
|
||||
/**
|
||||
* getVariable(sVar)
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string} sVar
|
||||
* @return {number|undefined}
|
||||
*/
|
||||
Debugger.prototype.getVariable = function(sVar)
|
||||
{
|
||||
return this.aVariables[sVar];
|
||||
};
|
||||
|
||||
/**
|
||||
* setVariable(sVar, value)
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string} sVar
|
||||
* @param {number} value
|
||||
*/
|
||||
Debugger.prototype.setVariable = function(sVar, value)
|
||||
{
|
||||
this.aVariables[sVar] = value;
|
||||
};
|
||||
|
||||
/**
|
||||
* toStrBase(n, nBytes, fStripLeadingZeros)
|
||||
*
|
||||
* Use this instead of str.toHex() or str.toOct() to convert bytes/words to the Debugger's default base.
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {number|null|undefined} n
|
||||
* @param {number} [nBytes] is the number of bytes to display, which we translate into a number of characters
|
||||
* @param {boolean} [fStripLeadingZeros]
|
||||
* @return {string}
|
||||
*/
|
||||
Debugger.prototype.toStrBase = function(n, nBytes, fStripLeadingZeros)
|
||||
{
|
||||
var s;
|
||||
switch(this.nBase) {
|
||||
case 8:
|
||||
s = str.toOct(n, nBytes * 3);
|
||||
break;
|
||||
case 10:
|
||||
s = n.toString();
|
||||
break;
|
||||
case 16:
|
||||
default:
|
||||
s = str.toHex(n, nBytes * 2);
|
||||
break;
|
||||
}
|
||||
if (fStripLeadingZeros && s.charAt(0) == '0') {
|
||||
s = s.replace(/^0+([0-9A-F]+)$/i, "$1");
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
} // endif DEBUGGER
|
||||
|
||||
if (NODE) module.exports = Debugger;
|
||||
|
|
@ -35,13 +35,9 @@
|
|||
* @define {string}
|
||||
*/
|
||||
var APPVERSION = "1.x.x"; // this @define is overridden by the Closure Compiler with the version in package.json
|
||||
|
||||
var XMLVERSION = null; // this is set in non-COMPILED builds by embedMachine() if a version number was found in the machine XML
|
||||
|
||||
var COPYRIGHT = "Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>";
|
||||
|
||||
var LICENSE = "License: GPL version 3 or later <http://gnu.org/licenses/gpl.html>";
|
||||
|
||||
var CSSCLASS = "pcjs";
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@
|
|||
|
||||
if (NODE) {
|
||||
var Component = require("./component");
|
||||
var str = require("./strlib");
|
||||
var web = require("./weblib");
|
||||
var str = require("./strlib");
|
||||
var web = require("./weblib");
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -55,7 +55,7 @@ var fAsync = true;
|
|||
var cAsyncMachines = 0;
|
||||
|
||||
/**
|
||||
* loadXML(sFile, idMachine, sAppClass, sParms, fResolve, display, done)
|
||||
* loadXML(sFile, idMachine, sAppName, sAppClass, sParms, fResolve, display, done)
|
||||
*
|
||||
* This is the preferred way to load all XML and XSL files. It uses getResource()
|
||||
* to load them as strings, which parseXML() can massage before parsing/transforming them.
|
||||
|
|
@ -80,13 +80,14 @@ var cAsyncMachines = 0;
|
|||
*
|
||||
* @param {string} sXMLFile
|
||||
* @param {string|null|undefined} idMachine
|
||||
* @param {string|null|undefined} sAppName
|
||||
* @param {string|null|undefined} sAppClass
|
||||
* @param {string|null|undefined} sParms
|
||||
* @param {boolean} fResolve is true to resolve any "ref" attributes
|
||||
* @param {function(string)} display
|
||||
* @param {function(string,Object)} done (string contains the unparsed XML string data, and Object contains a parsed XML object)
|
||||
*/
|
||||
function loadXML(sXMLFile, idMachine, sAppClass, sParms, fResolve, display, done)
|
||||
function loadXML(sXMLFile, idMachine, sAppName, sAppClass, sParms, fResolve, display, done)
|
||||
{
|
||||
var doneLoadXML = function(sURLName, sXML, nErrorCode) {
|
||||
if (nErrorCode) {
|
||||
|
|
@ -94,14 +95,14 @@ function loadXML(sXMLFile, idMachine, sAppClass, sParms, fResolve, display, done
|
|||
done(sXML, null);
|
||||
return;
|
||||
}
|
||||
parseXML(sXML, sXMLFile, idMachine, sAppClass, sParms, fResolve, display, done);
|
||||
parseXML(sXML, sXMLFile, idMachine, sAppName, sAppClass, sParms, fResolve, display, done);
|
||||
};
|
||||
display("Loading " + sXMLFile + "...");
|
||||
web.getResource(sXMLFile, null, fAsync, doneLoadXML);
|
||||
}
|
||||
|
||||
/**
|
||||
* parseXML(sXML, sXMLFile, idMachine, sAppClass, sParms, fResolve, display, done)
|
||||
* parseXML(sXML, sXMLFile, idMachine, sAppName, sAppClass, sParms, fResolve, display, done)
|
||||
*
|
||||
* Generates an XML document from an XML string. This function also provides a work-around for XSLT's
|
||||
* lack of support for the document() function (at least on some browsers), by replacing every reference
|
||||
|
|
@ -110,13 +111,14 @@ function loadXML(sXMLFile, idMachine, sAppClass, sParms, fResolve, display, done
|
|||
* @param {string} sXML
|
||||
* @param {string|null} sXMLFile
|
||||
* @param {string|null|undefined} idMachine
|
||||
* @param {string|null|undefined} sAppName
|
||||
* @param {string|null|undefined} sAppClass
|
||||
* @param {string|null|undefined} sParms
|
||||
* @param {boolean} fResolve is true to resolve any "ref" attributes; default is false
|
||||
* @param {function(string)} display
|
||||
* @param {function(string,Object)} done (string contains the unparsed XML string data, and Object contains a parsed XML object)
|
||||
*/
|
||||
function parseXML(sXML, sXMLFile, idMachine, sAppClass, sParms, fResolve, display, done)
|
||||
function parseXML(sXML, sXMLFile, idMachine, sAppName, sAppClass, sParms, fResolve, display, done)
|
||||
{
|
||||
var buildXML = function(sXML, sError) {
|
||||
if (sError) {
|
||||
|
|
@ -166,6 +168,7 @@ function parseXML(sXML, sXMLFile, idMachine, sAppClass, sParms, fResolve, displa
|
|||
* I'm trying to switch to a shared components.xsl (at least for all PC-class machines),
|
||||
* but in the interim, that means hacking the XSL file on the fly to reflect the actual class.
|
||||
*/
|
||||
sXML = sXML.replace(/(<xsl:variable name="APPNAME">).*?(<\/xsl:variable>)/, "$1" + sAppName + "$2");
|
||||
sXML = sXML.replace(/(<xsl:variable name="APPCLASS">).*?(<\/xsl:variable>)/, "$1" + sAppClass + "$2");
|
||||
|
||||
/*
|
||||
|
|
@ -327,11 +330,12 @@ function resolveXML(sXML, display, done)
|
|||
}
|
||||
|
||||
/**
|
||||
* embedMachine(sName, sVersion, idMachine, sXMLFile, sXSLFile, sParms)
|
||||
* embedMachine(sAppName, sAppClass, sVersion, idMachine, sXMLFile, sXSLFile, sParms)
|
||||
*
|
||||
* This allows to you embed a machine on a web page, by transforming the machine XML into HTML.
|
||||
*
|
||||
* @param {string} sName is the app name (eg, "PCjs")
|
||||
* @param {string} sAppName is the app name (eg, "PCx86")
|
||||
* @param {string} sAppClass is the app class (eg, "pcx86"); also known as the machine class
|
||||
* @param {string} sVersion is the app version (eg, "1.15.7")
|
||||
* @param {string} idMachine
|
||||
* @param {string} sXMLFile
|
||||
|
|
@ -339,7 +343,7 @@ function resolveXML(sXML, display, done)
|
|||
* @param {string} [sParms]
|
||||
* @return {boolean} true if successful, false if error
|
||||
*/
|
||||
function embedMachine(sName, sVersion, idMachine, sXMLFile, sXSLFile, sParms)
|
||||
function embedMachine(sAppName, sAppClass, sVersion, idMachine, sXMLFile, sXSLFile, sParms)
|
||||
{
|
||||
var eMachine, eWarning, fSuccess = true;
|
||||
|
||||
|
|
@ -400,7 +404,6 @@ function embedMachine(sName, sVersion, idMachine, sXMLFile, sXSLFile, sParms)
|
|||
head.appendChild(style);
|
||||
}
|
||||
|
||||
var sAppClass = sName.toLowerCase(); // eg, "pcx86" or "c1pjs"
|
||||
if (!sXSLFile) {
|
||||
/*
|
||||
* Now that PCjs is an open-source project, we can make the following test more flexible,
|
||||
|
|
@ -516,13 +519,13 @@ function embedMachine(sName, sVersion, idMachine, sXMLFile, sXSLFile, sParms)
|
|||
displayError("unable to transform XML: unsupported browser");
|
||||
}
|
||||
};
|
||||
loadXML(sXSLFile, null, sAppClass, null, false, displayMessage, transformXML);
|
||||
loadXML(sXSLFile, null, sAppName, sAppClass, null, false, displayMessage, transformXML);
|
||||
};
|
||||
|
||||
if (sXMLFile.charAt(0) != '<') {
|
||||
loadXML(sXMLFile, idMachine, sAppClass, sParms, true, displayMessage, processXML);
|
||||
loadXML(sXMLFile, idMachine, sAppName, sAppClass, sParms, true, displayMessage, processXML);
|
||||
} else {
|
||||
parseXML(sXMLFile, null, idMachine, sAppClass, sParms, false, displayMessage, processXML);
|
||||
parseXML(sXMLFile, null, idMachine, sAppName, sAppClass, sParms, false, displayMessage, processXML);
|
||||
}
|
||||
} else {
|
||||
displayError("missing machine element: " + idMachine);
|
||||
|
|
@ -544,7 +547,7 @@ function embedMachine(sName, sVersion, idMachine, sXMLFile, sXSLFile, sParms)
|
|||
function embedC1P(idMachine, sXMLFile, sXSLFile)
|
||||
{
|
||||
if (fAsync) web.enablePageEvents(false);
|
||||
return embedMachine("C1Pjs", APPVERSION, idMachine, sXMLFile, sXSLFile);
|
||||
return embedMachine("C1Pjs", "c1pjs", APPVERSION, idMachine, sXMLFile, sXSLFile);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -559,7 +562,7 @@ function embedC1P(idMachine, sXMLFile, sXSLFile)
|
|||
function embedPCx86(idMachine, sXMLFile, sXSLFile, sParms)
|
||||
{
|
||||
if (fAsync) web.enablePageEvents(false);
|
||||
return embedMachine("PCx86", APPVERSION, idMachine, sXMLFile, sXSLFile, sParms);
|
||||
return embedMachine("PCx86", "pcx86", APPVERSION, idMachine, sXMLFile, sXSLFile, sParms);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -574,12 +577,26 @@ function embedPCx86(idMachine, sXMLFile, sXSLFile, sParms)
|
|||
function embedPC8080(idMachine, sXMLFile, sXSLFile, sParms)
|
||||
{
|
||||
if (fAsync) web.enablePageEvents(false);
|
||||
return embedMachine("PC8080", APPVERSION, idMachine, sXMLFile, sXSLFile, sParms);
|
||||
return embedMachine("PC8080", "pc8080", APPVERSION, idMachine, sXMLFile, sXSLFile, sParms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent the Closure Compiler from renaming functions we want to export,
|
||||
* by adding them as (named) properties of a global object.
|
||||
* embedPDP11(idMachine, sXMLFile, sXSLFile, sParms)
|
||||
*
|
||||
* @param {string} idMachine
|
||||
* @param {string} sXMLFile
|
||||
* @param {string} sXSLFile
|
||||
* @param {string} [sParms]
|
||||
* @return {boolean} true if successful, false if error
|
||||
*/
|
||||
function embedPDP11(idMachine, sXMLFile, sXSLFile, sParms)
|
||||
{
|
||||
if (fAsync) web.enablePageEvents(false);
|
||||
return embedMachine("PDPjs", "pdp11", APPVERSION, idMachine, sXMLFile, sXSLFile, sParms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent the Closure Compiler from renaming functions we want to export, by adding them as global properties.
|
||||
*/
|
||||
if (APPNAME == "C1Pjs") {
|
||||
window['embedC1P'] = embedC1P;
|
||||
|
|
@ -591,6 +608,9 @@ if (APPNAME == "PCx86") {
|
|||
if (APPNAME == "PC8080") {
|
||||
window['embedPC8080'] = embedPC8080;
|
||||
}
|
||||
if (APPNAME == "PDPjs") {
|
||||
window['embedPDP11'] = embedPDP11;
|
||||
}
|
||||
|
||||
window['enableEvents'] = web.enablePageEvents;
|
||||
window['sendEvent'] = web.sendPageEvent;
|
||||
|
|
|
|||
247
modules/shared/lib/keys.js
Normal file
247
modules/shared/lib/keys.js
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
/**
|
||||
* @fileoverview Defines browser keyboard constants.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Jun-20
|
||||
*
|
||||
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, a computer emulation software project at <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 COPYRIGHT in /modules/shared/lib/defines.js).
|
||||
*
|
||||
* 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 PCjs
|
||||
* for purposes of the GNU General Public License, and the author does not claim any copyright
|
||||
* as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var Keys = {
|
||||
/*
|
||||
* Alphanumeric and other common (printable) ASCII codes.
|
||||
*
|
||||
* TODO: Determine what we can do to get ALL constants like these inlined (enum doesn't seem to
|
||||
* get the job done); the problem seems to be limited to property references that use quotes, which
|
||||
* is why I've 'unquoted' as many of them as possible.
|
||||
*/
|
||||
ASCII: {
|
||||
CTRL_A: 1, CTRL_C: 3, CTRL_Z: 26,
|
||||
' ': 32, '!': 33, '"': 34, '#': 35, '$': 36, '%': 37, '&': 38, "'": 39,
|
||||
'(': 40, ')': 41, '*': 42, '+': 43, ',': 44, '-': 45, '.': 46, '/': 47,
|
||||
'0': 48, '1': 49, '2': 50, '3': 51, '4': 52, '5': 53, '6': 54, '7': 55,
|
||||
'8': 56, '9': 57, ':': 58, ';': 59, '<': 60, '=': 61, '>': 62, '?': 63,
|
||||
'@': 64, A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71,
|
||||
H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, N: 78, O: 79,
|
||||
P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87,
|
||||
X: 88, Y: 89, Z: 90, '[': 91, '\\':92, ']': 93, '^': 94, '_': 95,
|
||||
'`': 96, a: 97, b: 98, c: 99, d: 100, e: 101, f: 102, g: 103,
|
||||
h: 104, i: 105, j: 106, k: 107, l: 108, m: 109, n: 110, o: 111,
|
||||
p: 112, q: 113, r: 114, s: 115, t: 116, u: 117, v: 118, w: 119,
|
||||
x: 120, y: 121, z: 122, '{':123, '|':124, '}':125, '~':126
|
||||
},
|
||||
/*
|
||||
* Browser keyCodes we must pay particular attention to. For the most part, these are non-alphanumeric
|
||||
* or function keys, some which may require special treatment (eg, preventDefault() if returning false on
|
||||
* the initial keyDown event is insufficient).
|
||||
*
|
||||
* keyCodes for most common ASCII keys can simply use the appropriate ASCII code above.
|
||||
*
|
||||
* Most of these represent non-ASCII keys (eg, the LEFT arrow key), yet for some reason, browsers defined
|
||||
* them using ASCII codes (eg, the LEFT arrow key uses the ASCII code for '%' or 37).
|
||||
*/
|
||||
KEYCODE: {
|
||||
/* 0x08 */ BS: 8,
|
||||
/* 0x09 */ TAB: 9,
|
||||
/* 0x0A */ LF: 10, // TODO: Determine if any key actually generates this
|
||||
/* 0x0D */ CR: 13,
|
||||
/* 0x10 */ SHIFT: 16,
|
||||
/* 0x11 */ CTRL: 17,
|
||||
/* 0x12 */ ALT: 18,
|
||||
/* 0x13 */ PAUSE: 19, // PAUSE/BREAK
|
||||
/* 0x14 */ CAPS_LOCK: 20,
|
||||
/* 0x1B */ ESC: 27,
|
||||
/* 0x20 */ SPACE: 32,
|
||||
/* 0x21 */ PGUP: 33,
|
||||
/* 0x22 */ PGDN: 34,
|
||||
/* 0x23 */ END: 35,
|
||||
/* 0x24 */ HOME: 36,
|
||||
/* 0x25 */ LEFT: 37,
|
||||
/* 0x26 */ UP: 38,
|
||||
/* 0x27 */ RIGHT: 39,
|
||||
/* 0x27 */ FF_QUOTE: 39,
|
||||
/* 0x28 */ DOWN: 40,
|
||||
/* 0x2C */ FF_COMMA: 44,
|
||||
/* 0x2C */ PRTSC: 44,
|
||||
/* 0x2D */ INS: 45,
|
||||
/* 0x2E */ DEL: 46,
|
||||
/* 0x2E */ FF_PERIOD: 46,
|
||||
/* 0x2F */ FF_SLASH: 47,
|
||||
/* 0x30 */ ZERO: 48,
|
||||
/* 0x31 */ ONE: 49,
|
||||
/* 0x32 */ TWO: 50,
|
||||
/* 0x33 */ THREE: 51,
|
||||
/* 0x34 */ FOUR: 52,
|
||||
/* 0x35 */ FIVE: 53,
|
||||
/* 0x36 */ SIX: 54,
|
||||
/* 0x37 */ SEVEN: 55,
|
||||
/* 0x38 */ EIGHT: 56,
|
||||
/* 0x39 */ NINE: 57,
|
||||
/* 0x3B */ FF_SEMI: 59,
|
||||
/* 0x3D */ FF_EQUALS: 61,
|
||||
/* 0x5B */ CMD: 91, // aka WIN
|
||||
/* 0x5B */ FF_LBRACK: 91,
|
||||
/* 0x5C */ FF_BSLASH: 92,
|
||||
/* 0x5D */ RCMD: 93, // aka MENU
|
||||
/* 0x5D */ FF_RBRACK: 93,
|
||||
/* 0x60 */ NUM_0: 96,
|
||||
/* 0x60 */ NUM_INS: 96,
|
||||
/* 0x60 */ FF_BQUOTE: 96,
|
||||
/* 0x61 */ NUM_1: 97,
|
||||
/* 0x61 */ NUM_END: 97,
|
||||
/* 0x62 */ NUM_2: 98,
|
||||
/* 0x62 */ NUM_DOWN: 98,
|
||||
/* 0x63 */ NUM_3: 99,
|
||||
/* 0x63 */ NUM_PGDN: 99,
|
||||
/* 0x64 */ NUM_4: 100,
|
||||
/* 0x64 */ NUM_LEFT: 100,
|
||||
/* 0x65 */ NUM_5: 101,
|
||||
/* 0x65 */ NUM_CENTER: 101,
|
||||
/* 0x66 */ NUM_6: 102,
|
||||
/* 0x66 */ NUM_RIGHT: 102,
|
||||
/* 0x67 */ NUM_7: 103,
|
||||
/* 0x67 */ NUM_HOME: 103,
|
||||
/* 0x68 */ NUM_8: 104,
|
||||
/* 0x68 */ NUM_UP: 104,
|
||||
/* 0x69 */ NUM_9: 105,
|
||||
/* 0x69 */ NUM_PGUP: 105,
|
||||
/* 0x6A */ NUM_MUL: 106,
|
||||
/* 0x6B */ NUM_ADD: 107,
|
||||
/* 0x6D */ NUM_SUB: 109,
|
||||
/* 0x6E */ NUM_DEL: 110, // aka PERIOD
|
||||
/* 0x6F */ NUM_DIV: 111,
|
||||
/* 0x70 */ F1: 112,
|
||||
/* 0x71 */ F2: 113,
|
||||
/* 0x72 */ F3: 114,
|
||||
/* 0x73 */ F4: 115,
|
||||
/* 0x74 */ F5: 116,
|
||||
/* 0x75 */ F6: 117,
|
||||
/* 0x76 */ F7: 118,
|
||||
/* 0x77 */ F8: 119,
|
||||
/* 0x78 */ F9: 120,
|
||||
/* 0x79 */ F10: 121,
|
||||
/* 0x7A */ F11: 122,
|
||||
/* 0x7B */ F12: 123,
|
||||
/* 0x90 */ NUM_LOCK: 144,
|
||||
/* 0x91 */ SCROLL_LOCK: 145,
|
||||
/* 0xAD */ FF_DASH: 173,
|
||||
/* 0xBA */ SEMI: 186, // Firefox: 59
|
||||
/* 0xBB */ EQUALS: 187, // Firefox: 61
|
||||
/* 0xBC */ COMMA: 188, // Firefox: 44
|
||||
/* 0xBD */ DASH: 189, // Firefox: 173
|
||||
/* 0xBE */ PERIOD: 190, // Firefox: 46
|
||||
/* 0xBF */ SLASH: 191, // Firefox: 47
|
||||
/* 0xC0 */ BQUOTE: 192, // Firefox: 96
|
||||
/* 0xDB */ LBRACK: 219, // Firefox: 91
|
||||
/* 0xDC */ BSLASH: 220, // Firefox: 92
|
||||
/* 0xDD */ RBRACK: 221, // Firefox: 93
|
||||
/* 0xDE */ QUOTE: 222, // Firefox: 39
|
||||
/* 0xE0 */ FF_CMD: 224, // Firefox only (used for both CMD and RCMD)
|
||||
//
|
||||
// The following biases use what I'll call Decimal Coded Binary or DCB (the opposite of BCD),
|
||||
// where the thousands digit is used to store the sum of "binary" digits 1 and/or 2 and/or 4.
|
||||
//
|
||||
// Technically, that makes it DCO (Decimal Coded Octal), but then again, BCD should have really
|
||||
// been called HCD (Hexadecimal Coded Decimal), so if "they" can take liberties, so can I.
|
||||
//
|
||||
// ONDOWN is a bias we add to browser keyCodes that we want to handle on "down" rather than on "press".
|
||||
//
|
||||
ONDOWN: 1000,
|
||||
//
|
||||
// ONRIGHT is a bias we add to browser keyCodes that need to check for a "right" location (default is "left")
|
||||
//
|
||||
ONRIGHT: 2000,
|
||||
//
|
||||
// FAKE is a bias we add to signal these are fake keyCodes corresponding to internal keystroke combinations.
|
||||
// The actual values are for internal use only and merely need to be unique and used consistently.
|
||||
//
|
||||
FAKE: 4000
|
||||
},
|
||||
/*
|
||||
* The set of values that a browser may store in the 'location' property of a keyboard event object
|
||||
* which we also support.
|
||||
*/
|
||||
LOCATION: {
|
||||
LEFT: 1,
|
||||
RIGHT: 2,
|
||||
NUMPAD: 3
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Check the event object's 'location' property for a non-zero value for the following ONRIGHT keys.
|
||||
*/
|
||||
Keys.KEYCODE.NUM_CR = Keys.KEYCODE.CR + Keys.KEYCODE.ONRIGHT;
|
||||
|
||||
/*
|
||||
* Maps "stupid" keyCodes to their "non-stupid" counterparts
|
||||
*/
|
||||
Keys.STUPID_KEYCODES = {};
|
||||
Keys.STUPID_KEYCODES[Keys.KEYCODE.SEMI] = Keys.ASCII[';']; // 186 -> 59
|
||||
Keys.STUPID_KEYCODES[Keys.KEYCODE.EQUALS] = Keys.ASCII['=']; // 187 -> 61
|
||||
Keys.STUPID_KEYCODES[Keys.KEYCODE.COMMA] = Keys.ASCII[',']; // 188 -> 44
|
||||
Keys.STUPID_KEYCODES[Keys.KEYCODE.DASH] = Keys.ASCII['-']; // 189 -> 45
|
||||
Keys.STUPID_KEYCODES[Keys.KEYCODE.PERIOD] = Keys.ASCII['.']; // 190 -> 46
|
||||
Keys.STUPID_KEYCODES[Keys.KEYCODE.SLASH] = Keys.ASCII['/']; // 191 -> 47
|
||||
Keys.STUPID_KEYCODES[Keys.KEYCODE.BQUOTE] = Keys.ASCII['`']; // 192 -> 96
|
||||
Keys.STUPID_KEYCODES[Keys.KEYCODE.LBRACK] = Keys.ASCII['[']; // 219 -> 91
|
||||
Keys.STUPID_KEYCODES[Keys.KEYCODE.BSLASH] = Keys.ASCII['\\']; // 220 -> 92
|
||||
Keys.STUPID_KEYCODES[Keys.KEYCODE.RBRACK] = Keys.ASCII[']']; // 221 -> 93
|
||||
Keys.STUPID_KEYCODES[Keys.KEYCODE.QUOTE] = Keys.ASCII["'"]; // 222 -> 39
|
||||
Keys.STUPID_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.ASCII['-'];
|
||||
|
||||
/*
|
||||
* Maps unshifted keyCodes to their shifted counterparts; to be used when a shift-key is down.
|
||||
* Alphabetic characters are handled in code, since they must also take CAPS_LOCK into consideration.
|
||||
*/
|
||||
Keys.SHIFTED_KEYCODES = {};
|
||||
Keys.SHIFTED_KEYCODES[Keys.ASCII['1']] = Keys.ASCII['!'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.ASCII['2']] = Keys.ASCII['@'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.ASCII['3']] = Keys.ASCII['#'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.ASCII['4']] = Keys.ASCII['$'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.ASCII['5']] = Keys.ASCII['%'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.ASCII['6']] = Keys.ASCII['^'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.ASCII['7']] = Keys.ASCII['&'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.ASCII['8']] = Keys.ASCII['*'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.ASCII['9']] = Keys.ASCII['('];
|
||||
Keys.SHIFTED_KEYCODES[Keys.ASCII['0']] = Keys.ASCII[')'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.KEYCODE.SEMI] = Keys.ASCII[':'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.KEYCODE.EQUALS] = Keys.ASCII['+'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.KEYCODE.COMMA] = Keys.ASCII['<'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.KEYCODE.DASH] = Keys.ASCII['_'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.KEYCODE.PERIOD] = Keys.ASCII['>'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.KEYCODE.SLASH] = Keys.ASCII['?'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.KEYCODE.BQUOTE] = Keys.ASCII['~'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.KEYCODE.LBRACK] = Keys.ASCII['{'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.KEYCODE.BSLASH] = Keys.ASCII['|'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.KEYCODE.RBRACK] = Keys.ASCII['}'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.KEYCODE.QUOTE] = Keys.ASCII['"'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.ASCII['_'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.KEYCODE.FF_EQUALS] = Keys.ASCII['+'];
|
||||
Keys.SHIFTED_KEYCODES[Keys.KEYCODE.FF_SEMI] = Keys.ASCII[':'];
|
||||
|
||||
if (NODE) module.exports = Keys;
|
||||
|
|
@ -136,12 +136,13 @@ net.propagateParms = function(sURL, req)
|
|||
/**
|
||||
* encodeURL(sURL, req, fDebug)
|
||||
*
|
||||
* Used to encodes any URLs presented on the current page, using this 3-step (um, 4-step) process:
|
||||
* Used to encodes any URLs presented on the current page, using this, um, simple 5-step process:
|
||||
*
|
||||
* 1) Replace any backslashes with slashes, in case the URL was derived from a file system path
|
||||
* 2) Remap links that begin with "archive/" to the corresponding URL at "http://archive.pcjs.org/"
|
||||
* 3) Transform any "htmlspecialchars" into the corresponding entities, using encodeURI()
|
||||
* 4) Massage the result with net.propagateParms(), so that any special parameters are passed along
|
||||
* 3) Use decodeURI() to eliminate escape sequences (like "%20") so that encodeURI() won't re-encode the "%"
|
||||
* 4) Use encodeURI() to transform all "htmlspecialchars" and reserved characters into the appropriate sequences
|
||||
* 5) Massage the result with net.propagateParms(), so that any special parameters are passed along
|
||||
*
|
||||
* @param {string} sURL
|
||||
* @param {Object} req is the web server's (ie, Express) request object, if any
|
||||
|
|
@ -158,7 +159,12 @@ net.encodeURL = function(sURL, req, fDebug)
|
|||
sURL = "http://archive.pcjs.org" + sURL.replace("/archive/", "/");
|
||||
}
|
||||
}
|
||||
return net.propagateParms(encodeURI(sURL), req);
|
||||
/*
|
||||
* If the incoming URL already contains URI-style escape sequences (eg, "%20" instead of spaces),
|
||||
* calling decodeURI() first will eliminate them, preventing encodeURI() from converting leading
|
||||
* "%" into "%25" and corrupting sequences like "%20" by turning them into "%2520".
|
||||
*/
|
||||
return net.propagateParms(encodeURI(decodeURI(sURL)), req);
|
||||
}
|
||||
return sURL;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,56 +38,101 @@ var str = {};
|
|||
*
|
||||
* The built-in parseInt() function has the annoying feature of returning a partial value (ie,
|
||||
* up to the point where it encounters an invalid character); eg, parseInt("foo", 16) returns 0xf.
|
||||
* So use this function to validate the entire string.
|
||||
*
|
||||
* So it's best to use our own str.parseInt() function, which will in turn use this function to
|
||||
* validate the entire string.
|
||||
*
|
||||
* @param {string} s is the string representation of some number
|
||||
* @param {number} [base] is the radix of the number represented above (only 2, 10 and 16 are supported)
|
||||
* @param {number} [base] is the radix to use (default is 10); only 2, 8, 10 and 16 are supported
|
||||
* @return {boolean} true if valid, false if invalid (or the specified base isn't supported)
|
||||
*/
|
||||
str.isValidInt = function(s, base)
|
||||
{
|
||||
if (!base || base == 10) return s.match(/^[0-9]+$/) !== null;
|
||||
if (base == 16) return s.match(/^[0-9a-f]+$/i) !== null;
|
||||
if (base == 2) return s.match(/^[01]+$/i) !== null;
|
||||
if (base == 8) return s.match(/^[0-7]+$/) !== null;
|
||||
if (base == 2) return s.match(/^[01]+$/) !== null;
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* parseInt(s, base)
|
||||
*
|
||||
* This is a wrapper around the built-in parseInt() function, which recognizes certain prefixes (eg,
|
||||
* '$' or "0x" for hex) and suffixes (eg, 'h' for hex, or '.' for decimal), and then calls isValidInt()
|
||||
* to ensure we don't convert strings that contain partial values (see isValidInt() for details).
|
||||
* This is a wrapper around the built-in parseInt() function. Our wrapper recognizes certain prefixes
|
||||
* ('$' or "0x" for hex, '#' or "0o" for octal) and suffixes ('.' for decimal, 'h' for hex, 'y' for
|
||||
* binary), and then calls isValidInt() to ensure we don't convert strings that contain partial values;
|
||||
* see isValidInt() for details.
|
||||
*
|
||||
* We don't support multiple prefix/suffix combinations, nor do we support the "0b" prefix (or "b" suffix)
|
||||
* for binary, because 1) it's not commonly used, and 2) it conflicts with valid hex sequences.
|
||||
* The use of multiple prefix/suffix combinations is undefined (although for the record, we process
|
||||
* prefixes first). We do NOT support the "0b" prefix to indicate binary UNLESS one or more commas are
|
||||
* also present (because "0b" is also a valid hex sequence), and we do NOT support a single leading zero
|
||||
* to indicate octal (because such a number could also be decimal or hex). Any number of commas are
|
||||
* allowed; we remove them all before calling the built-in parseInt().
|
||||
*
|
||||
* To summarize our non-standard alternatives: a 'y' suffix indicates binary, a '#' prefix indicates
|
||||
* octal, a '$' prefix indicates hex, and a "0b" prefix indicates binary IF at least one comma is present.
|
||||
* Commas are useful for grouping binary digits, but if you don't want to use them, then you must use a
|
||||
* 'y' suffix for binary numbers.
|
||||
*
|
||||
* @param {string} s is the string representation of some number
|
||||
* @param {number} [base] is the default radix to use (default is 16); can be overridden by prefixes/suffixes
|
||||
* @param {number} [base] is the radix to use (default is 10); can be overridden by prefixes/suffixes
|
||||
* @return {number|undefined} corresponding value, or undefined if invalid
|
||||
*/
|
||||
str.parseInt = function(s, base)
|
||||
{
|
||||
var value;
|
||||
|
||||
if (s) {
|
||||
if (!base) base = 16;
|
||||
if (s.charAt(0) == '$') {
|
||||
if (!base) base = 10;
|
||||
var chPrefix = s.charAt(0);
|
||||
var fCommas = (s.indexOf(',') > 0);
|
||||
if (fCommas) s = s.replace(/,/g, '');
|
||||
if (chPrefix == '#') {
|
||||
base = 8;
|
||||
chPrefix = null;
|
||||
}
|
||||
else if (chPrefix == '$') {
|
||||
base = 16;
|
||||
chPrefix = null;
|
||||
}
|
||||
if (chPrefix == null) {
|
||||
s = s.substr(1);
|
||||
} else if (s.substr(0, 2) == "0x") {
|
||||
base = 16;
|
||||
s = s.substr(2);
|
||||
} else {
|
||||
var chSuffix = s.charAt(s.length-1).toLowerCase();
|
||||
if (chSuffix == 'h') {
|
||||
base = 16;
|
||||
chSuffix = null;
|
||||
}
|
||||
else {
|
||||
if (chPrefix == '0') {
|
||||
chPrefix = s.charAt(1);
|
||||
if (chPrefix == 'b' && fCommas) {
|
||||
base = 2;
|
||||
chPrefix = null;
|
||||
}
|
||||
if (chPrefix == 'o') {
|
||||
base = 8;
|
||||
chPrefix = null;
|
||||
}
|
||||
else if (chPrefix == 'x') {
|
||||
base = 16;
|
||||
chPrefix = null;
|
||||
}
|
||||
}
|
||||
else if (chSuffix == '.') {
|
||||
base = 10;
|
||||
chSuffix = null;
|
||||
if (chPrefix == null) {
|
||||
s = s.substr(2);
|
||||
}
|
||||
else {
|
||||
var chSuffix = s.charAt(s.length-1).toLowerCase();
|
||||
if (chSuffix == 'y') {
|
||||
base = 2;
|
||||
chSuffix = null;
|
||||
}
|
||||
else if (chSuffix == '.') {
|
||||
base = 10;
|
||||
chSuffix = null;
|
||||
}
|
||||
else if (chSuffix == 'h') {
|
||||
base = 16;
|
||||
chSuffix = null;
|
||||
}
|
||||
if (chSuffix == null) s = s.substr(0, s.length-1);
|
||||
}
|
||||
if (chSuffix == null) s = s.substr(0, s.length-1);
|
||||
}
|
||||
var v;
|
||||
if (str.isValidInt(s, base) && !isNaN(v = parseInt(s, base))) {
|
||||
|
|
@ -109,7 +154,7 @@ str.parseInt = function(s, base)
|
|||
str.toBin = function(n, cch)
|
||||
{
|
||||
var s = "";
|
||||
if (cch === undefined) {
|
||||
if (!cch) {
|
||||
cch = 32;
|
||||
} else {
|
||||
if (cch > 32) cch = 32;
|
||||
|
|
@ -134,30 +179,74 @@ str.toBin = function(n, cch)
|
|||
};
|
||||
|
||||
/**
|
||||
* toBinBytes(n, cb)
|
||||
* toBinBytes(n, cb, fPrefix)
|
||||
*
|
||||
* Converts an integer to binary, with the specified number of bytes (up to the default of 4).
|
||||
*
|
||||
* @param {number|null|undefined} n is a 32-bit value
|
||||
* @param {number} [cb] is the desired number of binary bytes (4 is both the default and the maximum)
|
||||
* @param {boolean} [fPrefix]
|
||||
* @return {string} the binary representation of n
|
||||
*/
|
||||
str.toBinBytes = function(n, cb)
|
||||
str.toBinBytes = function(n, cb, fPrefix)
|
||||
{
|
||||
var s = "";
|
||||
if (!cb || cb > 4) cb = 4;
|
||||
for (var i = 0; i < cb; i++) {
|
||||
if (s) s = ',' + s;
|
||||
s = str.toBin(n & 0xff, 8) + 'b' + s;
|
||||
s = str.toBin(n & 0xff, 8) + s;
|
||||
n >>= 8;
|
||||
}
|
||||
return s;
|
||||
return (fPrefix? "0b" : "") + s;
|
||||
};
|
||||
|
||||
/**
|
||||
* toHex(n, cch)
|
||||
* toOct(n, cch, fPrefix)
|
||||
*
|
||||
* Converts an integer to hex, with the specified number of digits (up to the default of 8).
|
||||
* Converts an integer to octal, with the specified number of digits (default of 6; max of 11)
|
||||
*
|
||||
* You might be tempted to use the built-in n.toString(8) instead, but it doesn't zero-pad and it
|
||||
* doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw
|
||||
* an exception, whereas this function will return '?' characters.
|
||||
*
|
||||
* @param {number|null|undefined} n is a 32-bit value
|
||||
* @param {number} [cch] is the desired number of octal digits (0 or undefined for default of either 6 or 11)
|
||||
* @param {boolean} [fPrefix]
|
||||
* @return {string} the octal representation of n
|
||||
*/
|
||||
str.toOct = function(n, cch, fPrefix)
|
||||
{
|
||||
var s = "";
|
||||
|
||||
if (cch) {
|
||||
if (cch > 11) cch = 11;
|
||||
} else {
|
||||
cch = (n & ~0xffff)? 11 : 6;
|
||||
}
|
||||
/*
|
||||
* An initial "falsey" check for null takes care of both null and undefined;
|
||||
* we can't rely entirely on isNaN(), because isNaN(null) returns false, oddly enough.
|
||||
*
|
||||
* Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN,
|
||||
* since JavaScript coerces such operands to zero, but I think there's "value" in seeing those
|
||||
* values displayed differently.
|
||||
*/
|
||||
if (n == null || isNaN(n)) {
|
||||
while (cch-- > 0) s = '?' + s;
|
||||
} else {
|
||||
while (cch-- > 0) {
|
||||
var d = (n & 7) + 0x30;
|
||||
s = String.fromCharCode(d) + s;
|
||||
n >>= 3;
|
||||
}
|
||||
}
|
||||
return (fPrefix? "0o" : "") + s;
|
||||
};
|
||||
|
||||
/**
|
||||
* toHex(n, cch, fPrefix)
|
||||
*
|
||||
* Converts an integer to hex, with the specified number of digits (default of 4 or 8, max of 8).
|
||||
*
|
||||
* You might be tempted to use the built-in n.toString(16) instead, but it doesn't zero-pad and it
|
||||
* doesn't properly convert negative values; for example, if n is -2147483647, then n.toString(16)
|
||||
|
|
@ -172,16 +261,18 @@ str.toBinBytes = function(n, cb)
|
|||
* s = s.substr(0, cch).toUpperCase();
|
||||
*
|
||||
* @param {number|null|undefined} n is a 32-bit value
|
||||
* @param {number} [cch] is the desired number of hex digits (8 is both the default and the maximum)
|
||||
* @param {number} [cch] is the desired number of hex digits (0 or undefined for default of either 4 or 8)
|
||||
* @param {boolean} [fPrefix]
|
||||
* @return {string} the hex representation of n
|
||||
*/
|
||||
str.toHex = function(n, cch)
|
||||
str.toHex = function(n, cch, fPrefix)
|
||||
{
|
||||
var s = "";
|
||||
if (cch === undefined) {
|
||||
cch = 8;
|
||||
} else {
|
||||
|
||||
if (cch) {
|
||||
if (cch > 8) cch = 8;
|
||||
} else {
|
||||
cch = (n & ~0xffff)? 8 : 4;
|
||||
}
|
||||
/*
|
||||
* An initial "falsey" check for null takes care of both null and undefined;
|
||||
|
|
@ -201,46 +292,46 @@ str.toHex = function(n, cch)
|
|||
n >>= 4;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
return (fPrefix? "0x" : "") + s;
|
||||
};
|
||||
|
||||
/**
|
||||
* toHexByte(b)
|
||||
*
|
||||
* Alias for "0x" + str.toHex(b, 2)
|
||||
* Alias for str.toHex(b, 2, true)
|
||||
*
|
||||
* @param {number|null|undefined} b is a byte value
|
||||
* @return {string} the hex representation of b
|
||||
*/
|
||||
str.toHexByte = function(b)
|
||||
{
|
||||
return "0x" + str.toHex(b, 2);
|
||||
return str.toHex(b, 2, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* toHexWord(w)
|
||||
*
|
||||
* Alias for "0x" + str.toHex(w, 4)
|
||||
* Alias for str.toHex(w, 4, true)
|
||||
*
|
||||
* @param {number|null|undefined} w is a word (16-bit) value
|
||||
* @return {string} the hex representation of w
|
||||
*/
|
||||
str.toHexWord = function(w)
|
||||
{
|
||||
return "0x" + str.toHex(w, 4);
|
||||
return str.toHex(w, 4, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* toHexLong(l)
|
||||
*
|
||||
* Alias for "0x" + toHex(l)
|
||||
* Alias for str.toHex(l, 8, true)
|
||||
*
|
||||
* @param {number|null|undefined} l is a dword (32-bit) value
|
||||
* @return {string} the hex representation of w
|
||||
*/
|
||||
str.toHexLong = function(l)
|
||||
{
|
||||
return "0x" + str.toHex(l);
|
||||
return str.toHex(l, 8, true);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
<p class="common-copyright">
|
||||
<!-- When using AWS, the pcjs.org domain must redirect to www.pcjs.org, so let's avoid unnecessary redirects in any absolute URLs -->
|
||||
<span class="common-copyright"><a href="http://www.pcjs.org/">pcjs.org</a> website © 2012-<!-- pcjs:year --> by <a href="http://twitter.com/jeffpar">@jeffpar</a></span><br/>
|
||||
<span class="common-copyright">PCjs and C1Pjs released under <a href="http://gnu.org/licenses/gpl.html">GPL version 3 or later</a></span>
|
||||
<span class="common-copyright"><a href="http://github.com/jeffpar/pcjs">PCjs Project</a> released under <a href="http://gnu.org/licenses/gpl.html">GPL version 3 or later</a></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
<p class="common-reference"></p>
|
||||
<p class="common-copyright">
|
||||
<span class="common-copyright"><a href="http://www.pcjs.org/">pcjs.org</a> website © 2012-2016 by <a href="http://twitter.com/jeffpar">@jeffpar</a></span><br/>
|
||||
<span class="common-copyright">PCjs and C1Pjs released under <a href="http://gnu.org/licenses/gpl.html">GPL version 3 or later</a></span>
|
||||
<span class="common-copyright"><a href="http://github.com/jeffpar/pcjs">PCjs Project</a> released under <a href="http://gnu.org/licenses/gpl.html">GPL version 3 or later</a></span>
|
||||
</p>
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
|
|
|||
|
|
@ -37,10 +37,10 @@
|
|||
line-height: 19px;
|
||||
vertical-align: middle;
|
||||
float: left;
|
||||
font-family: "Lucida Console", monospace;
|
||||
font-family: Monaco, "Lucida Console", monospace;
|
||||
}
|
||||
.pcjs-controls textarea {
|
||||
font-family: Monaco, monospace;
|
||||
font-family: Monaco, "Lucida Console", monospace;
|
||||
font-size: x-small;
|
||||
}
|
||||
.pcjs-fieldset {
|
||||
|
|
@ -49,14 +49,14 @@
|
|||
padding: 0;
|
||||
}
|
||||
.pcjs-flag {
|
||||
font-family: "Lucida Console", monospace;
|
||||
font-family: Monaco, "Lucida Console", monospace;
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
line-height: 19px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.pcjs-register {
|
||||
font-family: "Lucida Console", monospace;
|
||||
font-family: Monaco, "Lucida Console", monospace;
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
line-height: 19px;
|
||||
|
|
|
|||
|
|
@ -580,6 +580,12 @@
|
|||
<xsl:otherwise>null</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="resetAddr">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@resetAddr"><xsl:value-of select="@resetAddr"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="csStart">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@csstart"><xsl:value-of select="@csstart"/></xsl:when>
|
||||
|
|
@ -604,7 +610,7 @@
|
|||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class" select="'cpu'"/>
|
||||
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>',stepping:'<xsl:value-of select="$stepping"/>',fpu:<xsl:value-of select="$fpu"/>,cycles:<xsl:value-of select="$cycles"/>,multiplier:<xsl:value-of select="$multiplier"/>,autoStart:<xsl:value-of select="$autoStart"/>,csStart:<xsl:value-of select="$csStart"/>,csInterval:<xsl:value-of select="$csInterval"/>,csStop:<xsl:value-of select="$csStop"/></xsl:with-param>
|
||||
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>',stepping:'<xsl:value-of select="$stepping"/>',fpu:<xsl:value-of select="$fpu"/>,cycles:<xsl:value-of select="$cycles"/>,multiplier:<xsl:value-of select="$multiplier"/>,autoStart:<xsl:value-of select="$autoStart"/>,resetAddr:<xsl:value-of select="$resetAddr"/>,csStart:<xsl:value-of select="$csStart"/>,csInterval:<xsl:value-of select="$csInterval"/>,csStop:<xsl:value-of select="$csStop"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
|
|
@ -698,6 +704,27 @@
|
|||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="device[@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)/device"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="device[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="name">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@name"><xsl:value-of select="@name"/></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">device</xsl:with-param>
|
||||
<xsl:with-param name="parms">,name:'<xsl:value-of select="$name"/>'</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>
|
||||
|
|
@ -1162,6 +1189,12 @@
|
|||
|
||||
<xsl:template match="debugger[not(@ref)]">
|
||||
<xsl:param name="machine" select="''"/>
|
||||
<xsl:variable name="base">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@base"><xsl:value-of select="@base"/></xsl:when>
|
||||
<xsl:otherwise>16</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="commands">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@commands"><xsl:value-of select="@commands"/></xsl:when>
|
||||
|
|
@ -1177,7 +1210,7 @@
|
|||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">debugger</xsl:with-param>
|
||||
<xsl:with-param name="parms">,commands:'<xsl:value-of select="$commands"/>',messages:'<xsl:value-of select="$messages"/>'</xsl:with-param>
|
||||
<xsl:with-param name="parms">,base:<xsl:value-of select="$base"/>,commands:'<xsl:value-of select="$commands"/>',messages:'<xsl:value-of select="$messages"/>'</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue