Unforked the shared modules -- ES6 is now the default

This commit is contained in:
Jeff Parsons 2017-01-31 13:50:55 -08:00 committed by Jeff Parsons
commit 822ab39cbe
108 changed files with 3310 additions and 9776 deletions

View file

@ -84,10 +84,10 @@ apparently Google's Closure Compiler seized the ES6 opportunity to try to enforc
the default is `@struct`. Needless to say, when it came time to convert the next machine (PC8080) to classes, I prefaced
all my classes with `@unrestricted`.
One downside of switching to ES6 one machine at a time is that, for now, I've had to fork the shared modules into
One downside of switching to ES6 one machine at a time is that I had to temporarily fork the shared modules into
separate ES5 and ES6 folders. For example, one of the shared modules, [Component](/modules/shared/lib/component.js),
is the base class underlying most other machine components; ES5 objects *subclass* [Component](/modules/shared/lib/component.js),
whereas ES6 classes *extend* [Component](/modules/shared/es6/component.js). Not all the shared modules needed to be forked,
whereas ES6 classes *extend* [Component](/modules/shared/lib/component.js). Not all the shared modules needed to be forked,
but creating a new shared folder was the simplest solution. Once all the machines have been converted to use ES6 classes,
the new shared modules will become the default, and the old ones will fade away.

View file

@ -1,20 +1,20 @@
(function(){/*
http://pcjs.org/modules/shared/es6/diskapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/dumpapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/reportapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/userapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/keys.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/strlib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/weblib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/diskapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/dumpapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/reportapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/userapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/keys.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/strlib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/weblib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/x86.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/interrupts.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/messages.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/debugger.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/state.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/embed.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/defines.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/usrlib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/component.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/state.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/embed.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/defines.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/usrlib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/component.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/defines.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/panel.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/bus.js (C) Jeff Parsons 2012-2017
@ -39,9 +39,9 @@
http://pcjs.org/modules/pcx86/lib/disk.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/fdc.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/hdc.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/debugger.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/debugger.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/computer.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/save.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/save.js (C) Jeff Parsons 2012-2017
*/
var l,aa;function ba(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a;for(var d in b)if(Object.defineProperties){var e=Object.getOwnPropertyDescriptor(b,d);e&&Object.defineProperty(a,d,e)}else a[d]=b[d]}
var da={163840:[40,1,8,,254],184320:[40,1,9,,252],327680:[40,2,8,,255],368640:[40,2,9,,253],737280:[80,2,9,,249],1228800:[80,2,15,,249],1474560:[80,2,18,,240],2949120:[80,2,36,,240],21368320:[615,4,17],2494464:[203,2,12,512],5242880:[256,2,40,256],10485760:[512,2,40,256]},n={jp:0,lp:1,mp:2,hl:3,np:4,op:5,pp:6,qp:7,rp:8,sp:9,tp:10,up:11,vp:12,wp:13,xp:14,yp:15,zp:16,Ap:17,Bp:18,Cp:19,Dp:20,Ep:21,Fp:22,Gp:23,Hp:24,Ip:25,Jp:26," ":32,"!":33,'"':34,"#":35,$:36,"%":37,"&":38,"'":39,"(":40,")":41,"*":42,

View file

@ -1,20 +1,20 @@
(function(){/*
http://pcjs.org/modules/shared/es6/diskapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/dumpapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/reportapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/userapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/keys.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/strlib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/weblib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/diskapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/dumpapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/reportapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/userapi.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/keys.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/strlib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/weblib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/x86.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/interrupts.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/messages.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/debugger.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/state.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/embed.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/defines.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/usrlib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/component.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/state.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/embed.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/defines.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/usrlib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/component.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/defines.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/panel.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/bus.js (C) Jeff Parsons 2012-2017
@ -39,9 +39,9 @@
http://pcjs.org/modules/pcx86/lib/disk.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/fdc.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/hdc.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/debugger.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/debugger.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/computer.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/es6/save.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/shared/lib/save.js (C) Jeff Parsons 2012-2017
*/
var k,aa;function ba(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a;for(var d in b)if(Object.defineProperties){var e=Object.getOwnPropertyDescriptor(b,d);e&&Object.defineProperty(a,d,e)}else a[d]=b[d]}
var da={163840:[40,1,8,,254],184320:[40,1,9,,252],327680:[40,2,8,,255],368640:[40,2,9,,253],737280:[80,2,9,,249],1228800:[80,2,15,,249],1474560:[80,2,18,,240],2949120:[80,2,36,,240],21368320:[615,4,17],2494464:[203,2,12,512],5242880:[256,2,40,256],10485760:[512,2,40,256]},n={Ln:0,Nn:1,On:2,Zj:3,Pn:4,Qn:5,Rn:6,Sn:7,Tn:8,Un:9,Vn:10,Wn:11,Xn:12,Yn:13,Zn:14,$n:15,ao:16,bo:17,co:18,eo:19,fo:20,ho:21,io:22,jo:23,ko:24,lo:25,mo:26," ":32,"!":33,'"':34,"#":35,$:36,"%":37,"&":38,"'":39,"(":40,")":41,"*":42,

View file

@ -29,8 +29,8 @@
"use strict";
if (NODE) {
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/**

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/**

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/**

View file

@ -29,9 +29,9 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/**

View file

@ -29,9 +29,9 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/**

View file

@ -29,8 +29,8 @@
"use strict";
if (NODE) {
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/**

View file

@ -29,8 +29,8 @@
"use strict";
if (NODE) {
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/**

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var DumpAPI = require("../../shared/es6/dumpapi");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
}
/**

View file

@ -29,9 +29,9 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/**

View file

@ -29,8 +29,8 @@
"use strict";
if (NODE) {
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/**

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var Memory8080 = require("./memory");
var Messages8080 = require("./messages");
}

View file

@ -29,11 +29,11 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PC8080 = require("./defines");
var CPUDef8080 = require("./cpudef");
var Messages8080 = require("./messages");

View file

@ -29,13 +29,13 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Web = require("../../shared/es6/weblib");
var UserAPI = require("../../shared/es6/userapi");
var ReportAPI = require("../../shared/es6/reportapi");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Web = require("../../shared/lib/weblib");
var UserAPI = require("../../shared/lib/userapi");
var ReportAPI = require("../../shared/lib/reportapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PC8080 = require("./defines");
var Bus8080 = require("./bus");
var Messages8080 = require("./messages");

View file

@ -29,8 +29,8 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Component = require("../../shared/lib/component");
var Messages8080 = require("./messages");
}

View file

@ -29,9 +29,9 @@
"use strict";
if (NODE) {
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PC8080 = require("./defines");
var CPUDef8080 = require("./cpudef");
var CPU8080 = require("./cpu");

View file

@ -29,13 +29,13 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Debugger = require("../../shared/es6/debugger");
var Keys = require("../../shared/es6/keys");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var Debugger = require("../../shared/lib/debugger");
var Keys = require("../../shared/lib/keys");
var State = require("../../shared/lib/state");
var PC8080 = require("./defines");
var CPUDef8080 = require("./cpudef");
var CPU8080 = require("./cpu");

View file

@ -29,11 +29,11 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Keys = require("../../shared/es6/keys");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var Keys = require("../../shared/lib/keys");
var State = require("../../shared/lib/state");
var PC8080 = require("./defines");
var ChipSet8080 = require("./chipset");
var Messages8080 = require("./messages");

View file

@ -29,8 +29,8 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Component = require("../../shared/lib/component");
var CPUDef8080 = require("./cpudef");
var Messages8080 = require("./messages");
}

View file

@ -29,8 +29,8 @@
"use strict";
if (NODE) {
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var PC8080 = require("./defines");
var Bus8080 = require("./bus");
var CPUDef8080 = require("./cpudef");

View file

@ -29,11 +29,11 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var DumpAPI = require("../../shared/es6/dumpapi");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PC8080 = require("./defines");
var CPUDef8080 = require("./cpudef");
var Memory8080 = require("./memory");

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var DumpAPI = require("../../shared/es6/dumpapi");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var PC8080 = require("./defines");
var Memory8080 = require("./memory");
}

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PC8080 = require("./defines");
var Messages8080 = require("./messages");
}

View file

@ -29,11 +29,11 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var DumpAPI = require("../../shared/es6/dumpapi");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PC8080 = require("./defines");
var ChipSet8080 = require("./chipset");
var Memory8080 = require("./memory");

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var Memory = require("./memory");
var Messages = require("./messages");
}

View file

@ -29,11 +29,11 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Interrupts = require("./interrupts");
var Messages = require("./messages");

View file

@ -58,13 +58,13 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Web = require("../../shared/es6/weblib");
var UserAPI = require("../../shared/es6/userapi");
var ReportAPI = require("../../shared/es6/reportapi");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Web = require("../../shared/lib/weblib");
var UserAPI = require("../../shared/lib/userapi");
var ReportAPI = require("../../shared/lib/reportapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var Bus = require("./bus");

View file

@ -29,9 +29,9 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Component = require("../../shared/lib/component");
var Messages = require("./messages");
}

View file

@ -30,13 +30,13 @@
if (DEBUGGER) {
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Debugger = require("../../shared/es6/debugger");
var Keys = require("../../shared/es6/keys");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var Debugger = require("../../shared/lib/debugger");
var Keys = require("../../shared/lib/keys");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var CPU = require("./cpu");
var X86 = require("./x86");

View file

@ -29,12 +29,12 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Web = require("../../shared/es6/weblib");
var DiskAPI = require("../../shared/es6/diskapi");
var DumpAPI = require("../../shared/es6/dumpapi");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Web = require("../../shared/lib/weblib");
var DiskAPI = require("../../shared/lib/diskapi");
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var Messages = require("./messages");
}

View file

@ -29,11 +29,11 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var DiskAPI = require("../../shared/es6/diskapi");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var DiskAPI = require("../../shared/lib/diskapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var ChipSet = require("./chipset");

View file

@ -29,11 +29,11 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var DiskAPI = require("../../shared/es6/diskapi");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var DiskAPI = require("../../shared/lib/diskapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Interrupts = require("./interrupts");
var Messages = require("./messages");

View file

@ -29,11 +29,11 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Keys = require("../../shared/es6/keys");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var Keys = require("../../shared/lib/keys");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var ChipSet = require("./chipset");

View file

@ -29,8 +29,8 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Component = require("../../shared/lib/component");
var Messages = require("./messages");
var X86 = require("./x86");
}

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var SerialPort = require("./serial");

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var PCX86 = require("./defines");
var Bus = require("./bus");
var Memory = require("./memory");

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var ChipSet = require("./chipset");

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Memory = require("./memory");
var ROM = require("./rom");

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var DumpAPI = require("../../shared/es6/dumpapi");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var PCX86 = require("./defines");
var Memory = require("./memory");
}

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var ChipSet = require("./chipset");

View file

@ -29,11 +29,11 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var DumpAPI = require("../../shared/es6/dumpapi");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Memory = require("./memory");
var Messages = require("./messages");

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var Memory = require("./memory");

View file

@ -32,10 +32,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var X86 = require("./x86");
}

View file

@ -29,7 +29,7 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Str = require("../../shared/lib/strlib");
var Messages = require("./messages");
var X86 = require("./x86");
}

View file

@ -29,7 +29,7 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Str = require("../../shared/lib/strlib");
var X86 = require("./x86");
}

View file

@ -29,7 +29,7 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Str = require("../../shared/lib/strlib");
var Messages = require("./messages");
var X86 = require("./x86");
}

View file

@ -29,7 +29,7 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Str = require("../../shared/lib/strlib");
var Messages = require("./messages");
var X86 = require("./x86");
}

View file

@ -60,21 +60,6 @@ or generate both versions and use a loader that detects the browser's capabiliti
Caveats
-------
### Shared modules
All PCjs machines rely on shared modules that are normally stored in [/shared/lib](/shared/lib/). However, until
ALL the machines have been converted to use ES6 classes, shared code must now exist in two flavors:
[/shared/lib](/shared/lib/) and [/shared/es6](/shared/es6/).
Once all the other machines have been converted to use ES6 classes, the shared ES6 code will be folded back into
[/shared/lib](/shared/lib/), and the temporary ES6 folder will go away. Obviously, there is incentive for me to do
this sooner rather than later, since in the interim, I must make any changes to shared code in both places.
There's also a less obvious problem: if you load a web page that attempts to load two or more PCjs machines, one of
which uses [/shared/lib](/shared/lib/) and another of which uses [/shared/es6](/shared/es6/), at least one of them will
fail to start, because the two sets of shared code cannot coexist. Well, they *could* have coexisted if I had been
willing to change the names of all the shared global objects (like **Component**), but I wasn't.
### *import* and *export*
With regard to *import* and *export* statements, the main reason I use them is to inform my development environment

View file

@ -33,10 +33,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PDP11 = require("./defines");
var MemoryPDP11 = require("./memory");
var MessagesPDP11 = require("./messages");

View file

@ -29,13 +29,13 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Web = require("../../shared/es6/weblib");
var UserAPI = require("../../shared/es6/userapi");
var ReportAPI = require("../../shared/es6/reportapi");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Web = require("../../shared/lib/weblib");
var UserAPI = require("../../shared/lib/userapi");
var ReportAPI = require("../../shared/lib/reportapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PDP11 = require("./defines");
var BusPDP11 = require("./bus");
var MessagesPDP11 = require("./messages");

View file

@ -29,8 +29,8 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Component = require("../../shared/lib/component");
var MessagesPDP11 = require("./messages");
}

View file

@ -33,10 +33,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PDP11 = require("./defines");
var BusPDP11 = require("./bus");
var CPUPDP11 = require("./cpu");

View file

@ -29,13 +29,13 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Usr = require("../../shared/es6/usrlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Debugger = require("../../shared/es6/debugger");
var Keys = require("../../shared/es6/keys");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var Debugger = require("../../shared/lib/debugger");
var Keys = require("../../shared/lib/keys");
var State = require("../../shared/lib/state");
var PDP11 = require("./defines");
var BusPDP11 = require("./bus");
var MemoryPDP11 = require("./memory");

View file

@ -33,10 +33,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PDP11 = require("./defines");
var BusPDP11 = require("./bus");
var MemoryPDP11 = require("./memory");

View file

@ -46,11 +46,11 @@
*/
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var DiskAPI = require("../../shared/es6/diskapi");
var DumpAPI = require("../../shared/es6/dumpapi");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var DiskAPI = require("../../shared/lib/diskapi");
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var MessagesPDP11 = require("./messages");
}

View file

@ -29,11 +29,11 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var DiskAPI = require("../../shared/es6/diskapi");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var DiskAPI = require("../../shared/lib/diskapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PDP11 = require("./defines");
var MessagesPDP11 = require("./messages");
var DiskPDP11 = require("./disk");

View file

@ -29,8 +29,8 @@
"use strict";
if (NODE) {
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var PDP11 = require("./defines");
var MessagesPDP11 = require("./messages");
}

View file

@ -29,7 +29,7 @@
"use strict";
if (NODE) {
var Component = require("../../shared/es6/component");
var Component = require("../../shared/lib/component");
var PDP11 = require("./defines");
var MessagesPDP11 = require("./messages");
}

View file

@ -33,10 +33,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PDP11 = require("./defines");
var BusPDP11 = require("./bus");
var MessagesPDP11 = require("./messages");

View file

@ -29,11 +29,11 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var DumpAPI = require("../../shared/es6/dumpapi");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PDP11 = require("./defines");
var MessagesPDP11 = require("./messages");
}

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var DumpAPI = require("../../shared/es6/dumpapi");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var PDP11 = require("./defines");
var MemoryPDP11 = require("./memory");
var MessagesPDP11 = require("./messages");

View file

@ -33,7 +33,7 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Str = require("../../shared/lib/strlib");
var PDP11 = require("./defines");
var MessagesPDP11 = require("./messages");
var DriveController = require("./drive");

View file

@ -33,7 +33,7 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Str = require("../../shared/lib/strlib");
var PDP11 = require("./defines");
var MessagesPDP11 = require("./messages");
var DriveController = require("./drive");

View file

@ -29,10 +29,10 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var DumpAPI = require("../../shared/es6/dumpapi");
var Component = require("../../shared/es6/component");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var PDP11 = require("./defines");
var BusPDP11 = require("./bus");
var MemoryPDP11 = require("./memory");

View file

@ -33,11 +33,11 @@
"use strict";
if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var Keys = require("../../shared/es6/keys");
var State = require("../../shared/es6/state");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var Keys = require("../../shared/lib/keys");
var State = require("../../shared/lib/state");
var PDP11 = require("./defines");
var MessagesPDP11 = require("./messages");
}

View file

@ -1,28 +0,0 @@
Shared (ES6) Sources
====================
This folder contains a mix of shared code, with some files used only by Node (server) modules,
some used only by Browser (client) modules, and others used by both.
At the moment, only a few files are completely agnostic; eg: [strlib.js](strlib.js) and [usrlib.js](usrlib.js).
One give-away is that neither contain references to any globals (although references to each other
would be fine).
[netlib.js](netlib.js) is appropriate only for Node modules, because it contains code that relies on Node's
global *Buffer* object, as indicated by:
/* global Buffer: false */
[weblib.js](weblib.js) is appropriate only for client modules, because it contains code that relies on the
browser's global *window* object, as indicated by:
/* global window: true */
We declare *window* modifiable (true) so that [defines.js](defines.js) can set *global.window* to *false*
when running within Node, allowing any other code to test the existence of *window* with a simple:
if (window) {...}
instead of:
if (typeof window !== "undefined") {...}

File diff suppressed because it is too large Load diff

View file

@ -1,704 +0,0 @@
/**
* @fileoverview Common PCjs Debugger support.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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 (NODE) {
var Str = require("../../shared/es6/strlib");
var Component = require("../../shared/es6/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)
* }}
*/
var DbgAddr;
/**
* Since the Closure Compiler treats ES6 classes as @struct rather than @dict by default,
* it deters us from defining named properties on our components; eg:
*
* this['exports'] = {...}
*
* results in an error:
*
* Cannot do '[]' access on a struct
*
* So, in order to define 'exports', we must override the @struct assumption by annotating
* the class as @unrestricted (or @dict). Note that this must be done both here and in the
* subclass (eg, SerialPort), because otherwise the Compiler won't allow us to *reference*
* the named property either.
*
* TODO: Consider marking ALL our classes unrestricted, because otherwise it forces us to
* define every single property the class uses in its constructor, which results in a fair
* bit of redundant initialization, since many properties aren't (and don't need to be) fully
* initialized until the appropriate init(), reset(), restore(), etc. function is called.
*
* The upside, however, may be that since the structure of the class is completely defined by
* the constructor, JavaScript engines may be able to optimize and run more efficiently.
*
* @unrestricted
*/
class Debugger extends Component {
/**
* 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.
*
* @param {Object} parmsDbg
*/
constructor(parmsDbg) {
if (DEBUGGER) {
super("Debugger", parmsDbg);
/*
* 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
}
/**
* 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
*/
getRegIndex(sReg, off) {
return -1;
}
/**
* getRegValue(iReg)
*
* NOTE: This must be implemented by the individual debuggers.
*
* @this {Debugger}
* @param {number} iReg
* @return {number|undefined}
*/
getRegValue(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}
*/
parseAddrReference(s, sAddr) {
return s.replace('[' + sAddr + ']', "unimplemented");
}
/**
* getNextCommand()
*
* @this {Debugger}
* @return {string}
*/
getNextCommand() {
var sCmd;
if (this.iPrevCmd > 0) {
sCmd = this.aPrevCmds[--this.iPrevCmd];
} else {
sCmd = "";
this.iPrevCmd = -1;
}
return sCmd;
}
/**
* getPrevCommand()
*
* @this {Debugger}
* @return {string|null}
*/
getPrevCommand() {
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>}
*/
parseCommand(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
*/
evalExpression(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
*/
parseExpression(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}
*/
parseReference(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}
*/
parseSysVars(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
*/
parseValue(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
*/
printValue(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, 4, 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
*/
printVariable(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
*/
delVariable(sVar) {
delete this.aVariables[sVar];
}
/**
* getVariable(sVar)
*
* @this {Debugger}
* @param {string} sVar
* @return {number|undefined}
*/
getVariable(sVar) {
return this.aVariables[sVar];
}
/**
* setVariable(sVar, value)
*
* @this {Debugger}
* @param {string} sVar
* @param {number} value
*/
setVariable(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}
*/
toStrBase(n, nBytes, fStripLeadingZeros) {
var s;
switch(this.nBase) {
case 8:
s = Str.toOct(n, nBytes * 3 - (nBytes > 2? 1 : 0));
break;
case 10:
s = n.toString();
break;
case 16:
default:
s = Str.toHex(n, nBytes * 2);
break;
}
return (fStripLeadingZeros? Str.stripLeadingZeros(s) : s);
}
}
if (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
};
} // endif DEBUGGER
if (NODE) module.exports = Debugger;

View file

@ -1,139 +0,0 @@
/**
* @fileoverview Compile-time definitions used by C1Pjs and PCjs.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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";
/**
* @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-2017 Jeff Parsons <Jeff@pcjs.org>";
var LICENSE = "License: GPL version 3 or later <http://gnu.org/licenses/gpl.html>";
var CSSCLASS = "pcjs";
/**
* @define {string}
*/
var SITEHOST = "localhost:8088";// this @define is overridden by the Closure Compiler with "www.pcjs.org"
/**
* @define {boolean}
*/
var COMPILED = false; // this @define is overridden by the Closure Compiler (to true)
/**
* @define {boolean}
*/
var DEBUG = true; // this @define is overridden by the Closure Compiler (to false) to remove DEBUG-only code
/**
* @define {boolean}
*/
var MAXDEBUG = false; // this @define is overridden by the Closure Compiler (to false) to remove MAXDEBUG-only code
/**
* @define {boolean}
*/
var PRIVATE = false; // this @define is overridden by the Closure Compiler (to false) to enable PRIVATE code
/*
* RS-232 DB-25 Pin Definitions, mapped to bits 1-25 in a 32-bit status value.
*
* SerialPorts in PCjs machines are considered DTE (Data Terminal Equipment), which means they should be "virtually"
* connected to each other via a null-modem cable, which assumes the following cross-wiring:
*
* G 1 <-> 1 G (Ground)
* TD 2 <-> 3 RD (Received Data)
* RD 3 <-> 2 TD (Transmitted Data)
* RTS 4 <-> 5 CTS (Clear To Send)
* CTS 5 <-> 4 RTS (Request To Send)
* DSR 6+8 <-> 20 DTR (Data Terminal Ready)
* SG 7 <-> 7 SG (Signal Ground)
* DTR 20 <-> 6+8 DSR (Data Set Ready + Carrier Detect)
* RI 22 <-> 22 RI (Ring Indicator)
*
* TODO: Move these definitions to a more appropriate shared file at some point.
*/
var RS232 = {
RTS: {
PIN: 4,
MASK: 0x00000010
},
CTS: {
PIN: 5,
MASK: 0x00000020
},
DSR: {
PIN: 6,
MASK: 0x00000040
},
CD: {
PIN: 8,
MASK: 0x00000100
},
DTR: {
PIN: 20,
MASK: 0x00100000
},
RI: {
PIN: 22,
MASK: 0x00400000
}
};
/*
* NODE should be true if we're running under NodeJS (eg, command-line), false if not (eg, web browser)
*/
var NODE = false;
if (typeof module !== 'undefined') {
NODE = true;
}
if (NODE) {
global.window = false; // provides an alternative "if (typeof window === 'undefined')" (ie, "if (window) ...")
global.APPVERSION = APPVERSION;
global.XMLVERSION = XMLVERSION;
global.COPYRIGHT = COPYRIGHT;
global.LICENSE = LICENSE;
global.CSSCLASS = CSSCLASS;
global.SITEHOST = SITEHOST;
global.COMPILED = COMPILED;
global.DEBUG = DEBUG;
global.MAXDEBUG = MAXDEBUG;
global.PRIVATE = PRIVATE;
global.RS232 = RS232;
global.NODE = NODE;
/*
* TODO: When we're "required" by Node, should we return anything via module.exports?
*/
}

View file

@ -1,242 +0,0 @@
/**
* @fileoverview Disk APIs, as defined by httpapi.js and consumed by disk.js
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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";
/*
* Our "DiskIO API" looks like:
*
* http://www.pcjs.org/api/v1/disk?action=open&volume=*10mb.img&mode=demandrw&chs=c:h:s&machine=xxx&user=yyy
*/
var DiskAPI = {
ENDPOINT: "/api/v1/disk",
QUERY: {
ACTION: "action", // value is one of DiskAPI.ACTION.*
VOLUME: "volume", // value is path of a disk image
MODE: "mode", // value is one of DiskAPI.MODE.*
CHS: "chs", // value is cylinders:heads:sectors:bytes
ADDR: "addr", // value is cylinder:head:sector:count
MACHINE: "machine", // value is machine token
USER: "user", // value is user ID
DATA: "data" // value is data to be written
},
ACTION: {
OPEN: "open",
READ: "read",
WRITE: "write",
CLOSE: "close"
},
MODE: {
LOCAL: "local", // this mode implies no API (at best, localStorage backing only)
PRELOAD: "preload", // this mode implies use of the DumpAPI
DEMANDRW: "demandrw",
DEMANDRO: "demandro"
},
FAIL: {
BADACTION: "invalid action",
BADUSER: "invalid user",
BADVOL: "invalid volume",
OPENVOL: "unable to open volume",
CREATEVOL: "unable to create volume",
WRITEVOL: "unable to write volume",
REVOKED: "access revoked"
}
};
/*
* Common (supported) diskette formats
*
* For no particular reason that I can recall, each entry in DISK_FORMATS is an array of values in "CHS" order:
*
* [# cylinders, # heads, # sectors/track, # bytes/sector, media type]
*
* If the 4th value is omitted, the sector size is assumed to be 512. The order of these "geometric" values mirrors
* the structure of our JSON-encoded disk images, which consist of an array of cylinders, each of which is an array of
* heads, each of which is an array of sector objects.
*/
DiskAPI.DISK_FORMATS = {
163840: [40,1,8,,0xFE], // media type 0xFE: 40 cylinders, 1 head (single-sided), 8 sectors/track, ( 320 total sectors x 512 bytes/sector == 163840)
184320: [40,1,9,,0xFC], // media type 0xFC: 40 cylinders, 1 head (single-sided), 9 sectors/track, ( 360 total sectors x 512 bytes/sector == 184320)
327680: [40,2,8,,0xFF], // media type 0xFF: 40 cylinders, 2 heads (double-sided), 8 sectors/track, ( 640 total sectors x 512 bytes/sector == 327680)
368640: [40,2,9,,0xFD], // media type 0xFD: 40 cylinders, 2 heads (double-sided), 9 sectors/track, ( 720 total sectors x 512 bytes/sector == 368640)
737280: [80,2,9,,0xF9], // media type 0xF9: 80 cylinders, 2 heads (double-sided), 9 sectors/track, (1440 total sectors x 512 bytes/sector == 737280)
1228800: [80,2,15,,0xF9], // media type 0xF9: 80 cylinders, 2 heads (double-sided), 15 sectors/track, (2400 total sectors x 512 bytes/sector == 1228800)
1474560: [80,2,18,,0xF0], // media type 0xF0: 80 cylinders, 2 heads (double-sided), 18 sectors/track, (2880 total sectors x 512 bytes/sector == 1474560)
2949120: [80,2,36,,0xF0], // media type 0xF0: 80 cylinders, 2 heads (double-sided), 36 sectors/track, (5760 total sectors x 512 bytes/sector == 2949120)
/*
* The following are common early hard drive sizes, which we explicitly map to CHS values, since the BPB can mislead us when attempting to calculate total cylinders
*/
21368320:[615,4,17], // PC AT 20Mb hard drive (type 2)
/*
* Assorted DEC disk pack formats.
*/
2494464: [203,2,12,512], // RK03 single-platter disk cartridge: 203 tracks, 2 heads, 12 sectors/track, 512 bytes/sector, for a total of 2494464 bytes
5242880: [256,2,40,256], // RL01K single-platter disk cartridge: 256 tracks, 2 heads, 40 sectors/track, 256 bytes/sector, for a total of 5242880 bytes
10485760:[512,2,40,256] // RL02K single-platter disk cartridge: 512 tracks, 2 heads, 40 sectors/track, 256 bytes/sector, for a total of 10485760 bytes
};
/*
* TODO: Eventually, our tools will need to support looking up disk formats by "model" rather than by raw disk size,
* because obviously multiple disk geometries can yield the same raw disk size. For each conflict that arises, I'll
* probably create a fake (approximate) disk size entry above, and then create a mapping to that approximate size below.
*/
DiskAPI.DISK_MODELS = {
"RL01": 5242880,
"RL02": 10485760
};
DiskAPI.MBR = {
PARTITIONS: {
OFFSET: 0x1BE,
ENTRY: {
STATUS: 0x00, // 0x80 if active
CHS_FIRST: 0x01, // 3-byte CHS specifier
TYPE: 0x04, // see TYPE.*
CHS_LAST: 0x05, // 3-byte CHS specifier
LBA_FIRST: 0x08,
LBA_TOTAL: 0x0C,
LENGTH: 0x10
},
STATUS: {
ACTIVE: 0x80
},
TYPE: {
EMPTY: 0x00,
FAT12_PRIMARY: 0x01, // DOS 2.0 and up (12-bit FAT)
FAT16_PRIMARY: 0x04 // DOS 3.0 and up (16-bit FAT)
}
},
SIG_OFFSET: 0x1FE,
SIGNATURE: 0xAA55 // to be clear, the low byte (at offset 0x1FE) is 0x55 and the high byte (at offset 0x1FF) is 0xAA
};
/*
* Boot sector offsets (and assorted constants) in DOS-compatible boot sectors (DOS 2.0 and up)
*
* WARNING: I've heard apocryphal stories about SIGNATURE being improperly reversed on some systems
* (ie, 0x55AA instead 0xAA55) -- perhaps by a dyslexic programmer -- so be careful out there.
*/
DiskAPI.BOOT = {
JMP_OPCODE: 0x000, // 1 byte for a JMP opcode, followed by a 1 or 2-byte offset
OEM_STRING: 0x003, // 8 bytes
SIG_OFFSET: 0x1FE,
SIGNATURE: 0xAA55 // to be clear, the low byte (at offset 0x1FE) is 0x55 and the high byte (at offset 0x1FF) is 0xAA
};
/*
* BIOS Parameter Block (BPB) offsets in DOS-compatible boot sectors (DOS 2.0 and up)
*/
DiskAPI.BPB = {
SECTOR_BYTES: 0x00B, // 2 bytes: bytes per sector (eg, 0x200 or 512)
CLUSTER_SECS: 0x00D, // 1 byte: sectors per cluster (eg, 1)
RESERVED_SECS: 0x00E, // 2 bytes: reserved sectors; ie, # sectors preceding the first FAT--usually just the boot sector (eg, 1)
TOTAL_FATS: 0x010, // 1 byte: FAT copies (eg, 2)
ROOT_DIRENTS: 0x011, // 2 bytes: root directory entries (eg, 0x40 or 64) 0x40 * 0x20 = 0x800 (1 sector is 0x200 bytes, total of 4 sectors)
TOTAL_SECS: 0x013, // 2 bytes: number of sectors (eg, 0x140 or 320); if zero, refer to LARGE_SECS
MEDIA_TYPE: 0x015, // 1 byte: media type (see DiskAPI.FAT.MEDIA_*)
FAT_SECS: 0x016, // 2 bytes: sectors per FAT (eg, 1)
TRACK_SECS: 0x018, // 2 bytes: sectors per track (eg, 8)
TOTAL_HEADS: 0x01A, // 2 bytes: number of heads (eg, 1)
HIDDEN_SECS: 0x01C, // 4 bytes: number of hidden sectors (always 0 for non-partitioned media)
LARGE_SECS: 0x020 // 4 bytes: number of sectors if TOTAL_SECS is zero
};
/*
* Media descriptor bytes for DOS-compatible FAT-formatted disks (stored in the first byte of the FAT)
*/
DiskAPI.FAT = {
MEDIA_160KB: 0xFE, // 5.25-inch, 1-sided, 8-sector, 40-track
MEDIA_180KB: 0xFC, // 5.25-inch, 1-sided, 9-sector, 40-track
MEDIA_320KB: 0xFF, // 5.25-inch, 2-sided, 8-sector, 40-track
MEDIA_360KB: 0xFD, // 5.25-inch, 2-sided, 9-sector, 40-track
MEDIA_720KB: 0xF9, // 3.5-inch, 2-sided, 9-sector, 80-track
MEDIA_1200KB: 0xF9, // 3.5-inch, 2-sided, 15-sector, 80-track
MEDIA_1440KB: 0xF0, // 3.5-inch, 2-sided, 18-sector, 80-track
MEDIA_2880KB: 0xF0 // 3.5-inch, 2-sided, 36-sector, 80-track
};
/*
* Cluster constants for 12-bit FATs (CLUSNUM_FREE, CLUSNUM_RES and CLUSNUM_MIN are the same for all FATs)
*/
DiskAPI.FAT12 = {
MAX_CLUSTERS: 4084,
CLUSNUM_FREE: 0, // this should NEVER appear in cluster chain (except at the start of an empty chain)
CLUSNUM_RES: 1, // reserved; this should NEVER appear in cluster chain
CLUSNUM_MIN: 2, // smallest valid cluster number
CLUSNUM_MAX: 0xFF6, // largest valid cluster number
CLUSNUM_BAD: 0xFF7, // bad cluster; this should NEVER appear in cluster chain
CLUSNUM_EOC: 0xFF8 // end of chain (actually, anything from 0xFF8-0xFFF indicates EOC)
};
/*
* Cluster constants for 16-bit FATs (CLUSNUM_FREE, CLUSNUM_RES and CLUSNUM_MIN are the same for all FATs)
*/
DiskAPI.FAT16 = {
MAX_CLUSTERS: 65524,
CLUSNUM_FREE: 0, // this should NEVER appear in cluster chain (except at the start of an empty chain)
CLUSNUM_RES: 1, // reserved; this should NEVER appear in cluster chain
CLUSNUM_MIN: 2, // smallest valid cluster number
CLUSNUM_MAX: 0xFFF6, // largest valid cluster number
CLUSNUM_BAD: 0xFFF7, // bad cluster; this should NEVER appear in cluster chain
CLUSNUM_EOC: 0xFFF8 // end of chain (actually, anything from 0xFFF8-0xFFFF indicates EOC)
};
/*
* Directory Entry offsets (and assorted constants) in FAT disk images
*
* NOTE: Versions of DOS prior to 2.0 use INVALID exclusively to mark available directory entries; any entry marked
* UNUSED will actually be considered USED. In DOS 2.0 and up, UNUSED was added to indicate that all remaining entries
* are unused, relieving it from having to initialize the rest of the sectors in the directory cluster(s). And in fact,
* you WILL encounter garbage in subsequent directory sectors if you attempt to read past an UNUSED entry.
*/
DiskAPI.DIRENT = {
NAME: 0x000, // 8 bytes
EXT: 0x008, // 3 bytes
ATTR: 0x00B, // 1 byte
MODTIME: 0x016, // 2 bytes
MODDATE: 0x018, // 2 bytes
CLUSTER: 0x01A, // 2 bytes
SIZE: 0x01C, // 4 bytes (typically zero for subdirectories)
LENGTH: 0x20, // 32 bytes total
UNUSED: 0x00, // indicates this and all subsequent directory entries are unused
INVALID: 0xE5 // indicates this directory entry is unused
};
/*
* Possible values for DIRENT.ATTR
*/
DiskAPI.ATTR = {
READONLY: 0x01, // PC-DOS 2.0 and up
HIDDEN: 0x02,
SYSTEM: 0x04,
LABEL: 0x08, // PC-DOS 2.0 and up
SUBDIR: 0x10, // PC-DOS 2.0 and up
ARCHIVE: 0x20 // PC-DOS 2.0 and up
};
if (NODE) module.exports = DiskAPI;

View file

@ -1,85 +0,0 @@
/**
* @fileoverview Disk APIs, as defined by diskdump.js and consumed by disk.js
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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";
/*
* Our "DiskDump API", such as it was, used to look like:
*
* http://jsmachines.net/bin/convdisk.php?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img
*
* To make it (a bit) more "REST-like", the above request now looks like:
*
* http://www.pcjs.org/api/v1/dump?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img
*
* Similarly, our "FileDump API" used to look like:
*
* http://jsmachines.net/bin/convrom.php?rom=/devices/pc/rom/5150/1981-04-24/PCBIOS-REV1.rom&format=json
*
* and that request now looks like:
*
* http://www.pcjs.org/api/v1/dump?file=/devices/pc/rom/5150/1981-04-24/PCBIOS-REV1.rom&format=json
*
* I don't think it makes sense to avoid "query" parameters, because blending the path of a disk image with the
* the rest of the URL would be (a) confusing, and (b) more work to parse.
*/
var DumpAPI = {
ENDPOINT: "/api/v1/dump",
QUERY: {
DIR: "dir", // value is path of a directory (DiskDump only)
DISK: "disk", // value is path of a disk image (DiskDump only)
FILE: "file", // value is path of a ROM image file (FileDump only)
IMG: "img", // alias for DISK
PATH: "path", // value is path of a one or more files (DiskDump only)
FORMAT: "format", // value is one of FORMAT values below
COMMENTS: "comments", // value is either "true" or "false"
DECIMAL: "decimal", // value is either "true" to force all numbers to decimal, "false" or undefined otherwise
MBHD: "mbhd", // value is hard drive size in Mb (formerly "mbsize") (DiskDump only) (DEPRECATED)
SIZE: "size" // value is target disk size in Kb (supersedes "mbhd") (DiskDump only)
},
FORMAT: {
JSON: "json", // default
JSON_GZ: "gz", // gzip is currently used ONLY for compressed JSON
DATA: "data", // same as "json", but built without JSON.stringify() (DiskDump only)
HEX: "hex", // deprecated
OCTAL: "octal", // displays data as octal words
BYTES: "bytes", // displays data as hex bytes; normally used only when comments are enabled
WORDS: "words", // displays data as hex words; normally used only when comments are enabled
IMG: "img", // returns the raw disk data (ie, using a Buffer object) (DiskDump only)
ROM: "rom" // returns the raw file data (ie, using a Buffer object) (FileDump only)
}
};
/*
* Because we use an overloaded API endpoint (ie, one that's shared with the FileDump module), we must
* also provide a list of commands which, when combined with the endpoint, define a unique request.
*/
DumpAPI.asDiskCommands = [DumpAPI.QUERY.DIR, DumpAPI.QUERY.DISK, DumpAPI.QUERY.PATH];
DumpAPI.asFileCommands = [DumpAPI.QUERY.FILE];
if (NODE) module.exports = DumpAPI;

View file

@ -1,629 +0,0 @@
/**
* @fileoverview C1Pjs and PCjs embedding functionality.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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 (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
}
/*
* We now support asynchronous XML and XSL file loads; simply set fAsync (below) to true.
*
* NOTE: For that support to work, we have to keep track of the number of machines on the page
* (ie, how many embedMachine() calls were issued), reduce the count as each machine XML file
* is fully transformed into HTML, and when the count finally returns to zero, notify all the
* machine component init() handlers.
*
* Also, to prevent those init() handlers from running prematurely, we must disable all page
* notification events at the start of the embedding process (Web.enablePageEvents(false)) and
* re-enable them at the end (Web.enablePageEvents(true)).
*/
var fAsync = true;
var cAsyncMachines = 0;
/**
* 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.
*
* For example, since I've been unable to get the XSLT document() function to work inside any
* XSL document loaded by JavaScript's XSLT processor, that has prevented me from dynamically
* loading any XML machine file that uses the "ref" attribute to refer to and incorporate
* another XML document.
*
* To solve that, I've added an fResolve parameter that tells parseXML() to fetch any
* referenced documents ITSELF and insert them into the XML string prior to parsing, instead
* of relying on the XSLT template to pull them in. That fetching is handled by resolveXML(),
* which iterates over the XML until all "refs" have been resolved (including any nested
* references).
*
* Also, XSL files with a <!DOCTYPE [...]> cause MSIE's Microsoft.XMLDOM.loadXML() function
* to choke, so I strip that out prior to parsing as well.
*
* TODO: Figure out why the XSLT document() function works great when the web browser loads an
* XML file (and the associated XSL file) itself, but does not work when loading documents via
* JavaScript XSLT support. Is it broken, is it a security issue, or am I just calling it wrong?
*
* @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, sAppName, sAppClass, sParms, fResolve, display, done)
{
var doneLoadXML = function(sURLName, sXML, nErrorCode) {
if (nErrorCode) {
if (!sXML) sXML = "unable to load " + sXMLFile + " (" + nErrorCode + ")";
done(sXML, null);
return;
}
parseXML(sXML, sXMLFile, idMachine, sAppName, sAppClass, sParms, fResolve, display, done);
};
display("Loading " + sXMLFile + "...");
Web.getResource(sXMLFile, null, fAsync, doneLoadXML);
}
/**
* 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
* tag (ie, a tag with a "ref" attribute) with the contents of the referenced file.
*
* @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, sAppName, sAppClass, sParms, fResolve, display, done)
{
var buildXML = function(sXML, sError) {
if (sError) {
done(sError, null);
return;
}
if (idMachine) {
/*
* A more sensible place to record the machine XML would be embedMachine(), like we do for the
* XSL file, but since we're about to modify the original machine XML, it's best to record it now.
*/
Component.addMachineResource(idMachine, sXMLFile, sXML);
var sURL = sXMLFile;
if (sURL && sURL.indexOf('/') < 0 && window.location.pathname.slice(-1) == '/') {
sURL = window.location.pathname + sURL;
}
/*
* We embed the URL of the XML file both as a separate "xml" attribute for easy access from the
* XSL file, and as part of the "parms" attribute for easy access from machines (see getMachineParm()).
*/
if (!sParms) {
sParms = '{';
} else if (sParms.slice(-1) == '}') {
sParms = sParms.slice(0, -1);
if (sParms.length > 1) sParms += ',';
} else { // sParms must just be a "state" file, so encode it as a "state" property
sParms = '{state:"' + sParms + '",';
}
sParms += 'url:"' + sURL + '"}';
/*
* Note that while we no longer generate a machine XML file with a "state" attribute (because it's
* encoded inside the "parms" attribute), the XSL file must still cope with "state" attributes inside
* other XML files; for example, manifest XML files like /apps/pc/1981/visicalc/manifest.xml contain
* machine elements with "state" attributes that must still be passed down to the computer element
* "the old fashioned way".
*
* Until/unless that changes, components.xsl cannot be simplified as much as I might have hoped.
*/
if (typeof resources == 'object') sURL = null; // turn off URL inclusion if we have embedded resources
sXML = sXML.replace(/(<machine[^>]*\sid=)(['"]).*?\2/, "$1$2" + idMachine + "$2" + (sParms? " parms='" + sParms + "'" : "") + (sURL? ' url="' + sURL + '"' : ''));
}
if (!fResolve) {
/*
* 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");
/*
* Non-COMPILED kludge to replace the version number template in the XSL file (which we assume we're reading,
* since fResolve is false) with whatever XMLVERSION we extracted from the XML file (see corresponding kludge below).
*
* ES6 ALERT: Template strings.
*/
if (!COMPILED && XMLVERSION) {
sXML = sXML.replace(/<xsl:variable name="APPVERSION">1.x.x<\/xsl:variable>/, `<xsl:variable name="APPVERSION">${XMLVERSION}</xsl:variable>`);
}
}
/*
* If the resource we requested is not really an XML file (or the file didn't exist and the server simply returned
* a message like "Cannot GET /devices/pc/machine/5150/cga/64kb/donkey/machine.xml"), we'd like to display a more
* meaningful message, because the XML DOM parsers will blithely return a document that contains nothing useful; eg:
*
* This page contains the following errors:error on line 1 at column 1:
* Document is empty Below is a rendering of the page up to the first error.
*
* Supposedly, the IE XML DOM parser will throw an exception, but I haven't tested that, and unless all other
* browsers do that, that's not helpful.
*
* The best I can do at this stage (assuming Web.getResource() didn't drop any error information on the floor)
* is verify that the requested resource "looks like" valid XML (in other words, it begins with a '<').
*/
var xmlDoc = null;
if (sXML.charAt(0) == '<') {
try {
/*
* Another hack for MSIE, which fails to load XSL documents containing a <!DOCTYPE [...]> tag.
*
* This is also why the XSLTProcessor 'transformToFragment' method in Microsoft Edge silently failed,
* so I had pull this hack out of the "ActiveXObject" code. And rather than add yet-another Microsoft
* browser check, I'm going to try doing this across the board, and hope that none of the other XSLT
* processors fail *without* the DOCTYPE tag.
*/
if (!fResolve) {
sXML = sXML.replace(/<!DOCTYPE(.|[\r\n])*]>\s*/g, "");
}
/*
* Beginning with Microsoft Edge and the corresponding release of Windows 10, all the
* 'ActiveXObject' crud has gone away; but of course, this code must remain in place if
* we want to continue supporting older Internet Explorer browsers (ie, back to IE9).
*/
/** @namespace window.ActiveXObject */
if (window.ActiveXObject || 'ActiveXObject' in window) { // second test is required for IE11 on Windows 8.1
xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = false;
xmlDoc['loadXML'](sXML);
} else {
/** @namespace window.DOMParser */
xmlDoc = (new window.DOMParser()).parseFromString(sXML, "text/xml");
}
} catch(e) {
xmlDoc = null;
sXML = e.message;
}
} else {
sXML = "unrecognized XML: " + (sXML.length > 255? sXML.substr(0, 255) + "..." : sXML);
}
done(sXML, xmlDoc);
};
if (sXML) {
if (PRIVATE) sXML = sXML.replace(/\/library.xml/, "/private/library.xml");
if (fResolve) {
resolveXML(sXML, display, buildXML);
return;
}
buildXML(sXML, null);
return;
}
done("no data" + (sXMLFile? " for file: " + sXMLFile : ""), null);
}
/**
* resolveXML(sXML, display, done)
*
* Replaces every tag with a "ref" attribute with the contents of the corresponding file.
*
* TODO: Fix some of the limitations of this code, such as: 1) requiring the "ref" attribute
* to appear as the tag's first attribute, 2) requiring the "ref" attribute to be double-quoted,
* and 3) requiring the "ref" tag to be self-closing.
*
* @param {string} sXML
* @param {function(string)} display
* @param {function(string,(string|null))} done (the first string contains the resolved XML data, the second is for any error message)
*/
function resolveXML(sXML, display, done)
{
var matchRef;
var reRef = /<([a-z]+)\s+ref="(.*?)"(.*?)\/>/g;
if ((matchRef = reRef.exec(sXML))) {
var sRefFile = matchRef[2];
var doneReadXML = function(sURLName, sXMLRef, nErrorCode) {
if (nErrorCode || !sXMLRef) {
done(sXML, "unable to resolve XML reference: " + matchRef[0] + " (" + nErrorCode + ")");
return;
}
/*
* If there are additional attributes in the "referring" XML tag, we want to insert them
* into the "referred" XML tag; attributes that don't exist in the referred tag should be
* appended, and attributes that DO exist should be overwritten.
*/
var sRefAttrs = matchRef[3];
if (sRefAttrs) {
var aXMLRefTag = sXMLRef.match(new RegExp("<" + matchRef[1] + "[^>]*>"));
if (aXMLRefTag) {
var sXMLNewTag = aXMLRefTag[0];
/*
* Iterate over all the attributes in the "referring" XML tag (sRefAttrs)
*/
var matchAttr;
var reAttr = /( [a-z]+=)(['"])(.*?)\2/g;
while ((matchAttr = reAttr.exec(sRefAttrs))) {
if (sXMLNewTag.indexOf(matchAttr[1]) < 0) {
/*
* This is the append case
*/
sXMLNewTag = sXMLNewTag.replace(">", matchAttr[0] + ">");
} else {
/*
* This is the overwrite case
*/
sXMLNewTag = sXMLNewTag.replace(new RegExp(matchAttr[1] + "(['\"])(.*?)\\1"), matchAttr[0]);
}
}
if (aXMLRefTag[0] != sXMLNewTag) {
sXMLRef = sXMLRef.replace(aXMLRefTag[0], sXMLNewTag);
}
} else {
done(sXML, "missing <" + matchRef[1] + "> in " + sRefFile);
return;
}
}
/*
* Apparently when a Windows Azure server delivers one of my XML files, it may modify the first line:
*
* <?xml version="1.0" encoding="UTF-8"?>\n
*
* I didn't determine exactly what it was doing at this point (probably just changing the \n to \r\n),
* but in any case, relaxing the following replace() solved it.
*/
sXMLRef = sXMLRef.replace(/<\?xml[^>]*>[\r\n]*/, "");
sXML = sXML.replace(matchRef[0], sXMLRef);
resolveXML(sXML, display, done);
};
display("Loading " + sRefFile + "...");
Web.getResource(sRefFile, null, fAsync, doneReadXML);
return;
}
done(sXML, null);
}
/**
* 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} 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
* @param {string} sXSLFile
* @param {string} [sParms]
* @return {boolean} true if successful, false if error
*/
function embedMachine(sAppName, sAppClass, sVersion, idMachine, sXMLFile, sXSLFile, sParms)
{
var eMachine, eWarning, fSuccess = true;
cAsyncMachines++;
Component.addMachine(idMachine);
var doneMachine = function() {
Component.assert(cAsyncMachines > 0);
if (!--cAsyncMachines) {
if (fAsync) Web.enablePageEvents(true);
}
};
var displayError = function(sError) {
Component.log(sError);
displayMessage("Error: " + sError);
if (fSuccess) doneMachine();
fSuccess = false;
};
var displayMessage = function(sMessage) {
if (eWarning === undefined) {
/*
* Our MarkOut module (in convertMDMachineLinks()) creates machine containers that look like:
*
* <div id="' + sMachineID + '" class="machine-placeholder"><p>Embedded PC</p><p class="machine-warning">...</p></div>
*
* with the "machine-warning" paragraph pre-populated with a warning message that the user will
* see if nothing at all happens. But hopefully, in the normal case (and especially the error case),
* *something* will have happened.
*
* Note that it is the HTMLOut module (in processMachines()) that ultimately decides which scripts to
* include and then generates the embedXXX() call.
*/
var aeWarning = (eMachine && Component.getElementsByClass(eMachine, "machine-warning"));
eWarning = (aeWarning && aeWarning[0]) || eMachine;
}
if (eWarning) eWarning.innerHTML = Str.escapeHTML(sMessage);
};
try {
eMachine = document.getElementById(idMachine);
if (eMachine) {
/*
* If we have a 'css' resource, add it to the page first.
*/
var css;
if (typeof resources == "object" && (css = resources['css'])) {
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
head.appendChild(style);
}
if (!sXSLFile) {
/*
* Now that PCjs is an open-source project, we can make the following test more flexible,
* and revert to the internal template if DEBUG *or* internal version (instead of *and*).
*
* Third-party sites that don't use the PCjs server will ALWAYS want to specify a fully-qualified
* path to the XSL file, unless they choose to mirror our folder structure.
*/
var sAppFolder = sAppClass;
if (DEBUG || sVersion == "1.x.x") {
sXSLFile = "/modules/" + sAppFolder + "/templates/components.xsl";
} else {
if (sAppClass.substr(0, 3) == "pdp") sAppFolder = "pdpjs";
sXSLFile = "/versions/" + sAppFolder + "/" + sVersion + "/components.xsl";
}
}
var processXML = function(sXML, xml) {
if (!xml) {
displayError(sXML);
return;
}
/*
* Non-COMPILED kludge to extract the version number from the stylesheet path in the machine XML file;
* we don't need this code in COMPILED (non-DEBUG) releases, because APPVERSION is hard-coded into them.
*/
if (!COMPILED) {
var aMatch = sXML.match(/<\?xml-stylesheet[^>]* href=(['"])[^'"]*?\/([0-9.]*)\/([^'"]*)\1/);
if (aMatch) XMLVERSION = aMatch[2];
}
var transformXML = function(sXSL, xsl) {
if (!xsl) {
displayError(sXSL);
return;
}
/*
* Record the XSL file, in case someone wants to save the entire machine later.
*/
Component.addMachineResource(idMachine, sXSLFile, sXSL);
/*
* The <machine> template in components.xsl now generates a "machine div" that makes
* the div we required the caller of embedMachine() to provide redundant, so instead
* of appending this fragment to the caller's node, we REPLACE the caller's node.
* This works only because because we ALSO inject the caller's "machine div" ID into
* the fragment's ID during parseXML().
*
* eMachine.innerHTML = sFragment;
*
* Also, if the transform function fails, make sure you're using the appropriate
* "components.xsl" and not a "machine.xsl", because the latter will not produce valid
* embeddable HTML (and is the most common cause of failure at this final stage).
*/
displayMessage("Processing " + sXMLFile + "...");
/*
* Beginning with Microsoft Edge and the corresponding release of Windows 10, all the
* 'ActiveXObject' crud has gone away; but of course, this code must remain in place if
* we want to continue supporting older Internet Explorer browsers (ie, back to IE9).
*/
if (window.ActiveXObject || 'ActiveXObject' in window) { // second test is required for IE11 on Windows 8.1
var sFragment = xml['transformNode'](xsl);
if (sFragment) {
eMachine.outerHTML = sFragment;
doneMachine();
} else {
displayError("transformNodeToObject failed");
}
}
else if (document.implementation && document.implementation.createDocument) {
var xsltProcessor = new XSLTProcessor();
xsltProcessor['importStylesheet'](xsl);
var eFragment = xsltProcessor['transformToFragment'](xml, document);
if (eFragment) {
/*
* This fails in Microsoft Edge...
*
var machine = eFragment.getElementById(idMachine);
if (!machine) {
displayError("machine generation failed: " + idMachine);
} else
*/
if (eMachine.parentNode) {
eMachine.parentNode.replaceChild(eFragment, eMachine);
doneMachine();
} else {
/*
* NOTE: This error can occur if our Node web server, when processing a folder with
* both a manifest.xml with a machine.xml reference AND a README.md containing a
* machine link, generates duplicate embedXXX() calls for the same machine; if the
* first embedXXX() call finds its target, subsequent calls for the same target will
* fail.
*
* Technically, such a folder is in a misconfigured state, but it happens, in part
* because when we switched to the Jekyll web server, we had to add machine links to
* all README.md files where we had previously relied on manifest.xml or machine.xml
* processing. This is because the Jekyll web server currently doesn't process XML
* files, nor is support for that likely to be added any time soon; it was a nice
* feature of the Node web server, but it's not clear that it's worth doing for Jekyll.
*/
displayError("invalid machine element: " + idMachine);
}
} else {
displayError("transformToFragment failed");
}
} else {
/*
* Perhaps I should have performed this test at the outset; on the other hand, I'm
* not aware of any browsers don't support one or both of the above XSLT transformation
* methods, so treat this as a bug.
*/
displayError("unable to transform XML: unsupported browser");
}
};
loadXML(sXSLFile, null, sAppName, sAppClass, null, false, displayMessage, transformXML);
};
if (sXMLFile.charAt(0) != '<') {
loadXML(sXMLFile, idMachine, sAppName, sAppClass, sParms, true, displayMessage, processXML);
} else {
parseXML(sXMLFile, null, idMachine, sAppName, sAppClass, sParms, false, displayMessage, processXML);
}
} else {
displayError("missing machine element: " + idMachine);
}
} catch(e) {
displayError(e.message);
}
return fSuccess;
}
/**
* embedC1P(idMachine, sXMLFile, sXSLFile)
*
* @param {string} idMachine
* @param {string} sXMLFile
* @param {string} sXSLFile
* @return {boolean} true if successful, false if error
*/
function embedC1P(idMachine, sXMLFile, sXSLFile)
{
if (fAsync) Web.enablePageEvents(false);
return embedMachine("C1Pjs", "c1pjs", APPVERSION, idMachine, sXMLFile, sXSLFile);
}
/**
* embedPCx86(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 embedPCx86(idMachine, sXMLFile, sXSLFile, sParms)
{
if (fAsync) Web.enablePageEvents(false);
return embedMachine("PCx86", "pcx86", APPVERSION, idMachine, sXMLFile, sXSLFile, sParms);
}
/**
* embedPC8080(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 embedPC8080(idMachine, sXMLFile, sXSLFile, sParms)
{
if (fAsync) Web.enablePageEvents(false);
return embedMachine("PC8080", "pc8080", APPVERSION, idMachine, sXMLFile, sXSLFile, sParms);
}
/**
* 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);
}
/**
* findMachineComponent(idMachine, sType)
*
* @param {string} idMachine
* @param {string} sType
* @return {Component|null}
*/
function findMachineComponent(idMachine, sType)
{
return Component.getComponentByType(sType, idMachine + ".machine");
}
/**
* Prevent the Closure Compiler from renaming functions we want to export, by adding them as global properties.
*/
if (APPNAME == "C1Pjs") {
window['embedC1P'] = embedC1P;
}
if (APPNAME == "PCx86") {
window['embedPC'] = embedPCx86; // WARNING: embedPC() deprecated as of v1.23.0
window['embedPCx86'] = embedPCx86;
}
if (APPNAME == "PC8080") {
window['embedPC8080'] = embedPC8080;
}
if (APPNAME == "PDPjs") {
window['embedPDP11'] = embedPDP11;
}
window['findMachineComponent'] = findMachineComponent;
window['enableEvents'] = Web.enablePageEvents;
window['sendEvent'] = Web.sendPageEvent;

View file

@ -1,34 +0,0 @@
/**
* @fileoverview Externs used by PCjs and C1Pjs (for the Closure Compiler)
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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 global;
var resources;
// var webkitAudioContext;

View file

@ -1,272 +0,0 @@
/**
* @fileoverview Defines browser keyboard constants.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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 = {
/*
* Keys and/or key combinations that generate common ASCII codes.
*
* NOTE: If you're looking for a general-purpose ASCII code table, see Str.ASCII in strlib.js;
* if something's missing, that's probably the more appropriate table to add it to.
*
* TODO: The Closure Compiler doesn't inline all references to these values, at least those with
* quoted property names, which is why I've 'unquoted' as many of them as possible. One solution
* would be to add mnemonics for all of them, not just the non-printable ones (eg, SPACE instead
* of ' ', AMP instead of '&', etc.)
*/
ASCII: {
BREAK: 0, CTRL_A: 1, CTRL_B: 2, CTRL_C: 3, CTRL_D: 4, CTRL_E: 5, CTRL_F: 6, CTRL_G: 7,
CTRL_H: 8, CTRL_I: 9, CTRL_J: 10, CTRL_K: 11, CTRL_L: 12, CTRL_M: 13, CTRL_N: 14, CTRL_O: 15,
CTRL_P: 16, CTRL_Q: 17, CTRL_R: 18, CTRL_S: 19, CTRL_T: 20, CTRL_U: 21, CTRL_V: 22, CTRL_W: 23,
CTRL_X: 24, CTRL_Y: 25, 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, DEL: 127
},
/*
* 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, // BACKSPACE (ASCII.CTRL_H)
/* 0x09 */ TAB: 9, // TAB (ASCII.CTRL_I)
/* 0x0A */ LF: 10, // LINE FEED (ASCII.CTRL_J) (TODO: Determine if any key actually generates this)
/* 0x0D */ CR: 13, // CARRIAGE RETURN (ASCII.CTRL_M)
/* 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 (FF_SEMI)
/* 0xBB */ EQUALS: 187, // Firefox: 61 (FF_EQUALS)
/* 0xBC */ COMMA: 188,
/* 0xBD */ DASH: 189, // Firefox: 173 (FF_DASH)
/* 0xBE */ PERIOD: 190,
/* 0xBF */ SLASH: 191,
/* 0xC0 */ BQUOTE: 192,
/* 0xDB */ LBRACK: 219,
/* 0xDC */ BSLASH: 220,
/* 0xDD */ RBRACK: 221,
/* 0xDE */ QUOTE: 222,
/* 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 Firefox keyCodes to their more common keyCode counterparts; a number of entries in this table
* are no longer valid (if indeed they ever were), so they've been commented out. It's likely that I
* simply extended this table to resolve additional differences in other browsers (ie, Opera), but without
* browser-specific checks, it's not safe to perform all the mappings shown below.
*/
Keys.FF_KEYCODES = {};
Keys.FF_KEYCODES[Keys.KEYCODE.FF_SEMI] = Keys.KEYCODE.SEMI; // 59 -> 186
Keys.FF_KEYCODES[Keys.KEYCODE.FF_EQUALS] = Keys.KEYCODE.EQUALS; // 61 -> 187
Keys.FF_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.KEYCODE.DASH; // 173 -> 189
Keys.FF_KEYCODES[Keys.KEYCODE.FF_CMD] = Keys.KEYCODE.CMD; // 224 -> 91
// Keys.FF_KEYCODES[Keys.KEYCODE.FF_COMMA] = Keys.KEYCODE.COMMA; // 44 -> 188
// Keys.FF_KEYCODES[Keys.KEYCODE.FF_PERIOD] = Keys.KEYCODE.PERIOD; // 46 -> 190
// Keys.FF_KEYCODES[Keys.KEYCODE.FF_SLASH] = Keys.KEYCODE.SLASH; // 47 -> 191
// Keys.FF_KEYCODES[Keys.KEYCODE.FF_BQUOTE] = Keys.KEYCODE.BQUOTE; // 96 -> 192
// Keys.FF_KEYCODES[Keys.KEYCODE.FF_LBRACK = Keys.KEYCODE.LBRACK; // 91 -> 219
// Keys.FF_KEYCODES[Keys.KEYCODE.FF_BSLASH] = Keys.KEYCODE.BSLASH; // 92 -> 220
// Keys.FF_KEYCODES[Keys.KEYCODE.FF_RBRACK] = Keys.KEYCODE.RBRACK; // 93 -> 221
// Keys.FF_KEYCODES[Keys.KEYCODE.FF_QUOTE] = Keys.KEYCODE.QUOTE; // 39 -> 222
/*
* Maps non-ASCII keyCodes to their ASCII counterparts
*/
Keys.NONASCII_KEYCODES = {};
Keys.NONASCII_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.ASCII['-']; // 173 -> 45
Keys.NONASCII_KEYCODES[Keys.KEYCODE.SEMI] = Keys.ASCII[';']; // 186 -> 59
Keys.NONASCII_KEYCODES[Keys.KEYCODE.EQUALS] = Keys.ASCII['=']; // 187 -> 61
Keys.NONASCII_KEYCODES[Keys.KEYCODE.DASH] = Keys.ASCII['-']; // 189 -> 45
Keys.NONASCII_KEYCODES[Keys.KEYCODE.COMMA] = Keys.ASCII[',']; // 188 -> 44
Keys.NONASCII_KEYCODES[Keys.KEYCODE.PERIOD] = Keys.ASCII['.']; // 190 -> 46
Keys.NONASCII_KEYCODES[Keys.KEYCODE.SLASH] = Keys.ASCII['/']; // 191 -> 47
Keys.NONASCII_KEYCODES[Keys.KEYCODE.BQUOTE] = Keys.ASCII['`']; // 192 -> 96
Keys.NONASCII_KEYCODES[Keys.KEYCODE.LBRACK] = Keys.ASCII['[']; // 219 -> 91
Keys.NONASCII_KEYCODES[Keys.KEYCODE.BSLASH] = Keys.ASCII['\\']; // 220 -> 92
Keys.NONASCII_KEYCODES[Keys.KEYCODE.RBRACK] = Keys.ASCII[']']; // 221 -> 93
Keys.NONASCII_KEYCODES[Keys.KEYCODE.QUOTE] = Keys.ASCII["'"]; // 222 -> 39
/*
* 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;

View file

@ -1,399 +0,0 @@
/**
* @fileoverview Net-related functions
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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 (NODE) {
var fs = require("fs");
var http = require("http");
var path = require("path");
var url = require("url");
}
class Net {
/**
* hasParm(sParm, sValue, req)
*
* @param {string} sParm
* @param {string|null} sValue (pass null to check for the presence of ANY sParm)
* @param {Object} [req] is the web server's (ie, Express) request object, if any
* @return {boolean} true if the request Object contains the specified parameter/value, false if not
*
* TODO: Consider whether sParm === null should check for the presence of ANY parameter in Net.asPropagate.
*/
static hasParm(sParm, sValue, req)
{
return (req && req.query && req.query[sParm] && (!sValue || req.query[sParm] == sValue));
}
/**
* propagateParms(sURL, req)
*
* Propagates any "special" query parameters (as listed in Net.asPropagate) from the given
* request object (req) to the given URL (sURL).
*
* We do not modify an sURL that already contains a '?' OR that begins with a protocol
* (eg, http:, mailto:, etc), in order to keep this function simple, since it's only for
* debugging purposes anyway. I also considered blowing off any URLs with a '#' for the
* same reason, since any hash string must follow any query parameters, but stripping
* and re-appending the hash string is pretty trivial, so we do handle that.
*
* TODO: Make propagateParms() more general-purpose (eg, capable of detecting any URL
* to the same site, and capable of merging any of our "special" query parameters with any
* existing query parameters.
*
* @param {string|null} sURL
* @param {Object} [req] is the web server's (ie, Express) request object, if any
* @return {string} massaged sURL
*/
static propagateParms(sURL, req)
{
if (sURL !== null && sURL.indexOf('?') < 0) {
var i;
var sHash = "";
if ((i = sURL.indexOf('#')) >= 0) {
sHash = sURL.substr(i);
sURL = sURL.substr(0, i);
}
var match = sURL.match(/^([a-z])+:(.*)/);
if (!match && req && req.query) {
for (i = 0; i < Net.asPropagate.length; i++) {
var sQuery = Net.asPropagate[i];
var sValue;
if ((sValue = req.query[sQuery])) {
var sParm = (sURL.indexOf('?') < 0 ? '?' : '&');
sParm += sQuery + '=';
if (sURL.indexOf(sParm) < 0) sURL += sParm + encodeURIComponent(sValue);
}
}
}
sURL += sHash;
}
return sURL;
}
/**
* encodeURL(sURL, req, fDebug)
*
* 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) 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
* @param {boolean} [fDebug]
* @return {string} encoded URL
*/
static encodeURL(sURL, req, fDebug)
{
if (sURL) {
sURL = sURL.replace(/\\/g, '/');
if (!fDebug) {
if (sURL.match(/^[^:?]*archive\//)) {
if (sURL.charAt(0) != '/') sURL = path.join(req.path, sURL);
sURL = "http://archive.pcjs.org" + sURL.replace("/archive/", "/");
}
}
/*
* 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;
}
/**
* isRemote(sPath)
*
* TODO: Add support for FTP? HTTPS? Anything else?
*
* @param {string} sPath
* @return {boolean} true if sPath is a (supported) remote path, false if not
*/
static isRemote(sPath)
{
return (sPath.indexOf("http:") === 0);
}
/**
* getStat(sURL, done)
*
* @param {string} sURL
* @param {function(Error,Object)} done
*/
static getStat(sURL, done)
{
var options = url.parse(sURL);
options.method = "HEAD";
options.path = options.pathname; // TODO: Determine the necessity of aliasing this
var req = http.request(options, function(res)
{
var err = null;
var stat = null;
// console.log(JSON.stringify(res.headers));
if (res.statusCode == 200) {
/*
* Apparently Node lower-cases response headers (at least incoming headers, despite
* lots of amusing whining by certain people in the Node community), which seems like
* a good thing, because that means I can do two simple key look-ups.
*/
var sLength = res.headers['content-length'];
var sModified = res.headers['last-modified'];
stat = {
size: sLength ? parseInt(sLength, 10) : -1,
mtime: sModified ? new Date(sModified) : null,
remote: true // an additional property we provide to indicate this is not your normal stats object
};
} else {
err = new Error("unexpected response code: " + res.statusCode);
}
done(err, stat);
});
req.on('error', function(err)
{
done(err, null);
});
req.end();
}
/**
* getFile(sURL, sEncoding, done)
*
* TODO: Add support for FTP? HTTPS? Anything else?
*
* @param {string} sURL is the source file
* @param {string|null} sEncoding is the encoding to assume, if any
* @param {function(Error,number,(string|Buffer))} done receives an Error, an HTTP status code, and a Buffer (if any)
*/
static getFile(sURL, sEncoding, done)
{
/*
* Buffer objects are a fixed size, so my choices are: 1) call getStat() first, hope it returns
* the true size, and then preallocate a buffer; or 2) create a new, larger buffer every time a new
* chunk arrives. The latter seems best.
*
* However, if an encoding is given, we'll simply concatenate all the data into a String and return
* that instead. Note that the incoming data is always a Buffer, but concatenation with a String
* performs an implied "toString()" on the Buffer.
*
* WARNING: Even when an encoding is provided, we don't make any attempt to verify that the incoming
* data matches that encoding.
*/
var sFile = "";
var bufFile = null;
http.get(sURL, function(res)
{
res.on('data', function(data)
{
if (sEncoding) {
sFile += data;
return;
}
if (!bufFile) {
bufFile = data;
return;
}
/*
* We need to grow bufFile. I used to do this myself, using the "copy" method:
*
* buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd])
*
* which defaults to 0 for [targetStart] and [sourceStart], but the docs don't clearly
* define the default value for [sourceEnd]. They say "buffer.length", but there is no
* parameter here named "buffer". Let's hope that in the case of "bufFile.copy(buf)"
* they meant "bufFile.length".
*
* However, it turns out this is moot, because there's a new kid in town: Buffer.concat().
*
* buf = new Buffer(bufFile.length + data.length);
* bufFile.copy(buf);
* data.copy(buf, bufFile.length);
* bufFile = buf;
*/
bufFile = Buffer.concat([bufFile, data], bufFile.length + data.length);
}).on('end', function()
{
/*
* TODO: Decide what to do when res.statusCode is actually an error code (eg, 404), because
* in such cases, the file content will likely just be an HTML error page.
*/
if (res.statusCode < 400) {
done(null, res.statusCode, sEncoding? sFile : bufFile);
} else {
done(new Error(sEncoding? sFile : bufFile), res.statusCode, null);
}
}).on('error', function(err)
{
done(err, res.statusCode, null);
});
});
}
/**
* downloadFile(sURL, sFile, done)
*
* @param {string} sURL is the source file
* @param {string} sFile is a fully-qualified target file
* @param {function(Error,number)} done is a callback that receives an Error and a HTTP status code
*/
static downloadFile(sURL, sFile, done)
{
var file = fs.createWriteStream(sFile);
/*
* http.get() accepts a "url" string in lieu of an "options" object; it automatically builds
* the latter from the former using url.parse(). This is good, because it relieves me from
* building my own "options" object, and also from wondering why http functions expect "options"
* to contain a "path" property, whereas url.parse() returns a "pathname" property.
*
* Either the documentation isn't quite right for url.parse() or http.request() (the big brother
* of http.get), or one of those "options" properties is aliased to the other, or...?
*/
http.get(sURL, function(res)
{
res.on('data', function(data)
{
file.write(data);
}).on('end', function()
{
file.end();
/*
* TODO: We should try to update the file's modification time to match the 'last-modified'
* response header value, if any.
*
* TODO: Decide what to do when res.statusCode is actually an error code (eg, 404), because
* in such cases, the file content will likely just be an HTML error page.
*/
done(null, res.statusCode);
}).on('error', function(err)
{
done(err, res.statusCode);
});
});
}
/**
* getResource(sURL, dataPost, fAsync, done)
*
* Request the specified resource (sURL), and once the request is complete, notify done().
*
* @param {string} sURL
* @param {Object|null} [dataPost] for a POST request (default is a GET request)
* @param {boolean} [fAsync] is true for an asynchronous request
* @param {function(string,string|null,number)} [done]
* @return {Array|null} Array containing [sResource, nErrorCode], or null if no response yet
*/
static getResource(sURL, dataPost, fAsync, done)
{
var nErrorCode = -1, sResource = null, response = null;
if (Net.isRemote(sURL)) {
/*
* TODO: This code is nothing more than a band-aid. It assumes the URL uses "http:"
* (hence the call to getFile(), which only supports HTTP GET operations), it assumes
* the requested data is UTF-8 string data (which is normally the case, because nearly
* all our requests are for JSON files), it doesn't deal with dataPost, it assumes
* that fAsync is true, and it performs very simplistic error code mapping.
*
* But, it gets the job done for what little we actually ask of it, when our machines
* are running in the Node environment.
*/
Net.getFile(sURL, "utf8", function(err, status, data) {
if (done) done(sURL, data, err? status : 0);
});
} else {
if (!Net.sServerRoot) {
Net.sServerRoot = path.join(path.dirname(fs.realpathSync(__filename)), "../../../");
}
var sFile = path.join(Net.sServerRoot, sURL);
if (fAsync) {
fs.readFile(sFile, {encoding: "utf8"}, function(err, s)
{
/*
* TODO: If err is set, is there an error code we should return (instead of -1)?
*/
if (!err) {
sResource = s;
nErrorCode = 0;
}
if (done) done(sURL, sResource, nErrorCode);
});
} else {
try {
sResource = fs.readFileSync(sFile, {encoding: "utf8"});
nErrorCode = 0;
} catch (err) {
/*
* TODO: If err is set, is there an error code we should return (instead of -1)?
*/
console.log(err.message);
}
if (done) done(sURL, sResource, nErrorCode);
response = [sResource, nErrorCode];
}
}
return response;
}
}
/*
* The following are (super-secret) commands that can be added to the URL to enable special features.
*
* Our super-secret command processor is affectionately call Gort, and while Gort doesn't understand commands
* like "Klaatu barada nikto", it does understand commands like "debug" and "rebuild"; eg:
*
* http://www.pcjs.org/?gort=debug
*
* hasParm() detects the presence of the specified command, and propagateParms() is a URL filter that ensures
* any commands listed in Net.asPropagate are passed through to all other URLs on the same page; using any of these
* commands also forces the page to be rebuilt and not cached (since we would never want a cached "index.html" to
* contain/expose any of these commands).
*/
Net.GORT_COMMAND = "gort";
Net.GORT_DEBUG = "debug"; // use this to force uncompiled JavaScript even on a Release server
Net.GORT_NODEBUG = "nodebug"; // use this to force uncompiled JavaScript but with DEBUG code disabled
Net.GORT_RELEASE = "release"; // use this to force the use of compiled JavaScript even on a Debug server
Net.GORT_REBUILD = "rebuild"; // use this to force the "index.html" in the current directory to be rebuilt
Net.REVEAL_COMMAND = "reveal";
Net.REVEAL_PDFS = "pdfs";
/*
* This is a list of the URL parameters that propagateParms() will propagate from the requester's URL to
* other URLs provided by the requester.
*/
Net.asPropagate = [Net.GORT_COMMAND, "autostart"];
Net.sServerRoot = null;
if (NODE) module.exports = Net;

View file

@ -1,39 +0,0 @@
/**
* @fileoverview Compile-time definitions for non-DEBUG configurations.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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";
/*
* In the compiled case, we rely on the Closure Compiler to override DEBUG, setting it to false,
* so that all DEBUG-only code will be removed by the compiler.
*
* However, when we're in "development mode" and want to run uncompiled code without any DEBUG-only
* code, we must arrange for this additional file (nodebug.js) to be loaded immediately after defines.js,
* which will then set DEBUG to false at runtime.
*/
DEBUG = false;

View file

@ -1,39 +0,0 @@
/**
* @fileoverview Definitions for PRIVATE configurations.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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";
/*
* PRIVATE enables certain private features that we don't want enabled on the public web server, so by default,
* PRIVATE is false. For Jekyll configurations, if site.pcjs.private is set, this file gets included, overriding
* the default.
*
* Currently, the only (checked-in) use of the private setting is to set the client's PRIVATE global and trigger
* the loading of alternate (ie, private) XML files in embed.js.
*/
PRIVATE = true;

View file

@ -1,87 +0,0 @@
/**
* @fileoverview Process-related helper functions
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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";
class Proc {
/**
* getArgs()
*
* Processes command-line arguments. Arguments may be introduced by either
* a double-hyphen (--) or a long dash (), and argument values, if any, must be
* separated by an "=" without any intervening whitespace. Arguments without
* an explicit value default to true, and any argument appearing more than once
* is automatically converted to an Array.
*
* Single-hyphen (-) arguments are allowed as well; they are treated as a series
* of single-character arguments, each set to true, and any of these arguments
* appearing more than once is discarded.
*
* @return {{argc:number, argv:{}}}
*/
static getArgs()
{
var argc = 0;
var argv = {};
for (var i = 2; i < process.argv.length; i++) {
var j, sSep;
var sArg = process.argv[i];
if (!sArg.indexOf(sSep = "--") || !sArg.indexOf(sSep = "—")) {
sArg = sArg.substr(sSep.length);
var sValue = true;
j = sArg.indexOf("=");
if (j > 0) {
sValue = sArg.substr(j + 1);
sArg = sArg.substr(0, j);
sValue = (sValue == "true") ? true : ((sValue == "false") ? false : sValue);
}
if (argv[sArg] === undefined) {
argc++;
argv[sArg] = sValue;
} else {
// console.log("too many '" + sArg + "' arguments");
if (typeof argv[sArg] == "string") {
argv[sArg] = [argv[sArg]];
}
argv[sArg].push(sValue);
}
} else if (!sArg.indexOf("-")) {
for (j = 1; j < sArg.length; j++) {
var ch = sArg.charAt(j);
if (argv[ch] === undefined) {
argv[ch] = true;
argc++;
}
}
}
}
return {argc: argc, argv: argv};
}
}
if (NODE) module.exports = Proc;

View file

@ -1,49 +0,0 @@
/**
* @fileoverview Report API, as defined by httpapi.js
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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 ReportAPI = {
ENDPOINT: "/api/v1/report",
QUERY: {
APP: "app",
VER: "ver",
URL: "url",
USER: "user",
TYPE: "type",
DATA: "data"
},
TYPE: {
BUG: "bug"
},
RES: {
OK: "Thank you"
}
};
if (NODE) module.exports = ReportAPI;

View file

@ -1,221 +0,0 @@
/**
* @fileoverview PCjs save functionality.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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 (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
}
/**
* savePC(idMachine, sPCJSFile, callback)
*
* @param {string} idMachine
* @param {string} sPCJSFile
* @param {function(Object)} [callback]
* @return {boolean} true if successful, false if error
*/
function savePC(idMachine, sPCJSFile, callback)
{
var cmp = /** @type {Computer} */ (Component.getComponentByType("Computer", idMachine));
var dbg = /** @type {Debugger} */ (Component.getComponentByType("Debugger", idMachine));
if (cmp) {
var sState = cmp.powerOff(true);
var sParms = cmp.saveMachineParms();
if (!sPCJSFile) {
if (DEBUG) {
sPCJSFile = "/tmp/pcx86/" + (XMLVERSION || APPVERSION) + "/pcx86.js"
} else {
sPCJSFile = "/versions/pcx86/" + (XMLVERSION || APPVERSION) + "/pcx86" + (dbg? "-dbg" : "") + ".js";
}
}
if (callback && callback({ state: sState, parms: sParms })) return true;
Web.getResource(sPCJSFile, null, true, function(sURL, sResponse, nErrorCode) {
downloadCSS(sURL, sResponse, nErrorCode, [idMachine, Str.getBaseName(sPCJSFile, true), sParms, sState]);
});
return true;
}
Component.alertUser("Unable to identify machine '" + idMachine + "'");
return false;
}
/**
* downloadCSS(sURL, sPCJS, nErrorCode, aMachineInfo)
*
* @param {string} sURL
* @param {string} sPCJS
* @param {number} nErrorCode
* @param {Array} aMachineInfo ([0] = idMachine, [1] = sScript, [2] = sParms, [3] = sState)
*/
function downloadCSS(sURL, sPCJS, nErrorCode, aMachineInfo)
{
if (!nErrorCode && sPCJS) {
aMachineInfo.push(sPCJS);
var res = Component.getMachineResources(aMachineInfo[0]);
var sCSSFile = null;
for (var sName in res) {
if (Str.endsWith(sName, "components.xsl")) {
sCSSFile = sName.replace(".xsl", ".css");
break;
}
}
if (!sCSSFile) {
/*
* This is probably a bad idea (ie, allowing downloadPC() to proceed with our stylesheet)...
*/
downloadPC(sURL, null, 0, aMachineInfo);
} else {
Web.getResource(sCSSFile, null, true, function(sURL, sResponse, nErrorCode) {
downloadPC(sURL, sResponse, nErrorCode, aMachineInfo);
});
}
return;
}
Component.alertUser("Error (" + nErrorCode + ") requesting " + sURL);
}
/**
* downloadPC(sURL, sCSS, nErrorCode, aMachineInfo)
*
* @param {string} sURL
* @param {string|null} sCSS
* @param {number} nErrorCode
* @param {Array} aMachineInfo ([0] = idMachine, [1] = sScript, [2] = sParms, [3] = sState, [4] = sPCJS)
*/
function downloadPC(sURL, sCSS, nErrorCode, aMachineInfo)
{
var matchScript, sXMLFile, sXSLFile;
var idMachine = aMachineInfo[0], sScript = aMachineInfo[1], sPCJS = aMachineInfo[4];
/*
* sPCJS is supposed to contain the entire PCjs script, which has been wrapped with:
*
* (function(){...
*
* at the top and:
*
* ...})();
*
* at the bottom, thanks to the following Closure Compiler option:
*
* --output_wrapper "(function(){%output%})();"
*
* Immediately inside that wrapping, we want to embed all the specified machine's resources, using:
*
* var resources = {"xml": "...", "xsl": "...", ...};
*
* Note that the "resources" variable has been added to our externs.js, to prevent it from being renamed
* by the Closure Compiler.
*/
matchScript = sPCJS.match(/^(\s*\(function\(\)\{)([\s\S]*)(}\)\(\);\s*)$/);
if (!matchScript) {
/*
* If the match failed, we assume that a DEBUG (uncompiled) script is being used,
* so we'll provide a fake match that should work with whatever script was provided.
*/
if (DEBUG) {
matchScript = [sPCJS, "", sPCJS, ""];
} else {
sPCJS = "";
}
}
var resOld = Component.getMachineResources(idMachine), resNew = {};
for (var sName in resOld) {
var data = resOld[sName];
var sExt = Str.getExtension(sName);
if (sExt == "xml") {
/*
* Look through this resource for <disk> entries whose paths do not appear as one of the
* other machine resources, and remove those entries.
*/
var matchDisk, reDisk = /[ \t]*<disk [^>]*path=(['"])(.*?)\1.*?<\/disk>\n?/g;
while (matchDisk = reDisk.exec(resOld[sName])) {
var path = matchDisk[2];
if (path) {
if (resOld[path]) {
Component.log("recording disk: '" + path + "'");
} else {
data = data.replace(matchDisk[0], "");
}
}
}
sXMLFile = sName = Str.getBaseName(sName);
}
else if (sExt == "xsl") {
sXSLFile = sName = Str.getBaseName(sName);
}
Component.log("saving resource: '" + sName + "' (" + data.length + " bytes)");
resNew[sName] = data;
}
if (sCSS) {
resNew[sName = 'css'] = sCSS;
Component.log("saving resource: '" + sName + "' (" + sCSS.length + " bytes)");
}
if (aMachineInfo[2]) {
var sParms = resNew[sName = 'parms'] = aMachineInfo[2];
Component.log("saving resource: '" + sName + "' (" + sParms.length + " bytes)");
}
if (aMachineInfo[3]) {
var sState = resNew[sName = 'state'] = aMachineInfo[3];
Component.log("saving resource: '" + sName + "' (" + sState.length + " bytes)");
}
if (sXMLFile && sXSLFile) {
var sResources = JSON.stringify(resNew);
sScript += ".js";
sPCJS = matchScript[1] + "var resources=" + sResources + ";" + matchScript[2] + matchScript[3];
Component.log("saving machine: '" + idMachine + "' (" + sPCJS.length + " bytes)");
sPCJS = sPCJS.replace(/\u00A9/g, "&#xA9;");
var sAlert = Web.downloadFile(sPCJS, "javascript", false, sScript);
sAlert += ', copy it to your web server as "' + sScript + '", and then add the following to your web page:\n\n';
sAlert += '<div id="' + idMachine + '"></div>\n';
sAlert += '...\n';
sAlert += '<script type="text/javascript" src="' + sScript + '"></script>\n';
sAlert += '<script type="text/javascript">embedPC("' + idMachine + '","' + sXMLFile + '","' + sXSLFile + '");</script>\n\n';
sAlert += 'The machine should appear where the <div> is located.';
Component.alertUser(sAlert);
return;
}
Component.alertUser("Missing XML/XSL resources");
}
/**
* Prevent the Closure Compiler from renaming functions we want to export, by adding them
* as (named) properties of a global object.
*/
window['savePC'] = savePC;

View file

@ -1,397 +0,0 @@
/**
* @fileoverview The State class used by PCjs machines.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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 (NODE) {
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
}
class State {
/**
* State(component, sVersion, sSuffix)
*
* 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.
*
* WARNING: Since State objects are low-level objects that have no UI requirements, they do not
* inherit from the Component class, so you should only use class methods of Component, such as
* Component.assert() (or Debugger methods if the Debugger is available).
*
* NOTE: 1.01 is the first version to provide limited save/restore support using localStorage.
* From that point on, care must be taken to insure that any new version that's incompatible with
* previous localStorage data be released with a version number that is at least 1 greater,
* since we're tagging the localStorage data with the integer portion of the version string.
*
* @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
*/
constructor(component, sVersion, sSuffix)
{
this.id = component.id;
this.dbg = component.dbg;
this.json = "";
this.state = {};
this.fLoaded = this.fParsed = false;
this.key = State.key(component, sVersion, sSuffix);
this.unload(component.parms);
}
/**
* set(id, data)
*
* @this {State}
* @param {number|string} id
* @param {Object|string} data
*/
set(id, data)
{
try {
this.state[id] = data;
} catch(e) {
Component.log(e.message);
}
}
/**
* get(id)
*
* @this {State}
* @param {number|string} id
* @return {Object|string|null}
*/
get(id)
{
return this.state[id] || null;
}
/**
* data()
*
* @this {State}
* @return {Object}
*/
data()
{
return this.state;
}
/**
* load(json)
*
* WARNING: Make sure you follow this call with either a call to parse() or unload(),
* because any stringified data that we've loaded isn't usable until it's been parsed.
*
* @this {State}
* @param {string|null} [json]
* @return {boolean} true if state exists in localStorage, false if not
*/
load(json)
{
if (json) {
this.json = json;
this.fLoaded = true;
this.fParsed = false;
return true;
}
if (this.fLoaded) {
/*
* This is assumed to be a redundant load().
*/
return true;
}
if (Web.hasLocalStorage()) {
var s = Web.getLocalStorageItem(this.key);
if (s) {
this.json = s;
this.fLoaded = true;
if (DEBUG) Component.log("localStorage(" + this.key + "): " + s.length + " bytes loaded");
return true;
}
}
return false;
}
/**
* parse()
*
* 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.
*
* @this {State}
* @return {boolean} true if successful, false if error
*/
parse()
{
var fSuccess = true;
if (!this.fParsed) {
try {
this.state = JSON.parse(this.json);
this.fParsed = true;
} catch (e) {
Component.error(e.message || e);
fSuccess = false;
}
}
return fSuccess;
}
/**
* store()
*
* @this {State}
* @return {boolean} true if successful, false if error
*/
store()
{
var fSuccess = true;
if (Web.hasLocalStorage()) {
var s = JSON.stringify(this.state);
if (Web.setLocalStorageItem(this.key, s)) {
if (DEBUG) Component.log("localStorage(" + this.key + "): " + s.length + " bytes stored");
} else {
/*
* WARNING: Because browsers tend to disable all alerts() during an "unload" operation,
* it's unlikely anyone will ever see the "quota" errors that occur at this point. Need to
* think of some way to notify the user that there's a problem, and offer a way of cleaning
* up old states.
*/
Component.error("Unable to store " + s.length + " bytes in browser local storage");
fSuccess = false;
}
}
return fSuccess;
}
/**
* toString()
*
* @this {State}
* @return {string} JSON-encoded state
*/
toString()
{
return this.state? JSON.stringify(this.state) : this.json;
}
/**
* unload(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.
*
* @this {State}
* @param {Object} [parms]
*/
unload(parms)
{
this.json = "";
this.state = {};
this.fLoaded = this.fParsed = false;
if (parms) this.set("parms", parms);
}
/**
* clear(fAll)
*
* 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.
*
* @this {State}
* @param {boolean} [fAll] true to unconditionally clear ALL localStorage for the current domain
*/
clear(fAll)
{
this.unload();
var aKeys = Web.getLocalStorageKeys();
for (var i = 0; i < aKeys.length; i++) {
var sKey = aKeys[i];
if (sKey && (fAll || sKey.substr(0, this.key.length) == this.key)) {
Web.removeLocalStorageItem(sKey);
if (DEBUG) Component.log("localStorage(" + sKey + ") removed");
aKeys.splice(i, 1);
i = 0;
}
}
}
/**
* 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
*/
static key(component, sVersion, sSuffix)
{
var key = component.id;
if (sVersion) {
var i = sVersion.indexOf('.');
if (i > 0) key += ".v" + sVersion.substr(0, i);
}
if (sSuffix) {
key += "." + sSuffix;
}
return key;
}
/**
* State.compress(aSrc)
*
* @param {Array.<number>|null} aSrc
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
*/
static compress(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>}
*/
static decompress(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)
*/
static compressEvenOdd(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>}
*/
static decompressEvenOdd(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;
}
}
if (NODE) module.exports = State;

View file

@ -1,635 +0,0 @@
/**
* @fileoverview String-related helper functions
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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";
class Str {
/**
* isValidInt(s, base)
*
* 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 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 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)
*/
static isValidInt(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 == 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. 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.
*
* 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 radix to use (default is 10); can be overridden by prefixes/suffixes
* @return {number|undefined} corresponding value, or undefined if invalid
*/
static parseInt(s, base)
{
var value;
if (s) {
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 (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;
}
}
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);
}
}
var v;
if (Str.isValidInt(s, base) && !isNaN(v = parseInt(s, base))) {
value = v | 0;
}
}
return value;
}
/**
* toBin(n, cch, grouping)
*
* Converts an integer to binary, with the specified number of digits (up to the default of 32).
*
* @param {number|null|undefined} n is a 32-bit value
* @param {number} [cch] is the desired number of binary digits (32 is both the default and the maximum)
* @param {number} [grouping]
* @return {string} the binary representation of n
*/
static toBin(n, cch, grouping)
{
var s = "";
if (!cch) {
cch = 32;
} else {
if (cch > 32) cch = 32;
}
/*
* 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.
*/
var fInvalid = (n == null || isNaN(n));
var group = (grouping = grouping || cch);
while (cch-- > 0) {
if (!group) {
s = "," + s;
group = grouping;
}
s = (fInvalid ? '?' : ((n & 0x1) ? '1' : '0')) + s;
n >>= 1;
group--;
}
return s;
}
/**
* 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
*/
static toBinBytes(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) + s;
n >>= 8;
}
return (fPrefix ? "0b" : "") + s;
}
/**
* toOct(n, cch, fPrefix)
*
* 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
*/
static toOct(n, cch, fPrefix)
{
var s = "";
if (cch) {
if (cch > 11) cch = 11;
} else {
cch = (n & ~0xffffff) ? 11 : ((n & ~0xffff) ? 8 : 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;
}
/**
* toDec(n, cch)
*
* Converts an integer to decimal, with the specified number of digits (default of 5; max of 10)
*
* You might be tempted to use the built-in n.toString(10) 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 decimal digits (0 or undefined for default of either 5 or 10)
* @return {string} the octal representation of n
*/
static toDec(n, cch)
{
var s = "";
if (cch) {
if (cch > 10) cch = 10;
} else {
cch = (n & ~0xffff) ? 10 : 5;
}
/*
* 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 % 10) + 0x30;
s = String.fromCharCode(d) + s;
n /= 10;
}
}
return 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)
* will return "-7fffffff" instead of "80000001". Moreover, if n is undefined, n.toString() will
* throw an exception, whereas this function will return '?' characters.
*
* NOTE: The following work-around (adapted from code found on StackOverflow) would be another solution,
* taking care of negative values, zero-padding, and upper-casing, but not null/undefined/NaN values:
*
* s = (n < 0? n + 0x100000000 : n).toString(16);
* s = "00000000".substr(0, 8 - s.length) + s;
* 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 (0 or undefined for default of either 4 or 8)
* @param {boolean} [fPrefix]
* @return {string} the hex representation of n
*/
static toHex(n, cch, fPrefix)
{
var s = "";
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;
* 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 & 0xf;
d += (d >= 0 && d <= 9 ? 0x30 : 0x41 - 10);
s = String.fromCharCode(d) + s;
n >>= 4;
}
}
return (fPrefix ? "0x" : "") + s;
}
/**
* toHexByte(b)
*
* Alias for Str.toHex(b, 2, true)
*
* @param {number|null|undefined} b is a byte value
* @return {string} the hex representation of b
*/
static toHexByte(b)
{
return Str.toHex(b, 2, true);
}
/**
* toHexWord(w)
*
* 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
*/
static toHexWord(w)
{
return Str.toHex(w, 4, true);
}
/**
* toHexLong(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
*/
static toHexLong(l)
{
return Str.toHex(l, 8, true);
}
/**
* getBaseName(sFileName, fStripExt)
*
* This is a poor-man's version of Node's path.basename(), which Node-only components should use instead.
*
* Note that if fStripExt is true, this strips ANY extension, whereas path.basename() strips the extension only
* if it matches the second parameter (eg, path.basename("/foo/bar/baz/asdf/quux.html", ".html") returns "quux").
*
* @param {string} sFileName
* @param {boolean} [fStripExt]
* @return {string}
*/
static getBaseName(sFileName, fStripExt)
{
var sBaseName = sFileName;
var i = sFileName.lastIndexOf('/');
if (i >= 0) sBaseName = sFileName.substr(i + 1);
/*
* This next bit is a kludge to clean up names that are part of a URL that includes unsightly query parameters.
*/
i = sBaseName.indexOf('&');
if (i > 0) sBaseName = sBaseName.substr(0, i);
if (fStripExt) {
i = sBaseName.lastIndexOf(".");
if (i > 0) {
sBaseName = sBaseName.substring(0, i);
}
}
return sBaseName;
}
/**
* getExtension(sFileName)
*
* This is a poor-man's version of Node's path.extname(), which Node-only components should use instead.
*
* Note that we EXCLUDE the period from the returned extension, whereas path.extname() includes it.
*
* @param {string} sFileName
* @return {string} the filename's extension (in lower-case and EXCLUDING the "."), or an empty string
*/
static getExtension(sFileName)
{
var sExtension = "";
var i = sFileName.lastIndexOf(".");
if (i >= 0) {
sExtension = sFileName.substr(i + 1).toLowerCase();
}
return sExtension;
}
/**
* endsWith(s, sSuffix)
*
* @param {string} s
* @param {string} sSuffix
* @return {boolean} true if s ends with sSuffix, false if not
*/
static endsWith(s, sSuffix)
{
return s.indexOf(sSuffix, s.length - sSuffix.length) !== -1;
}
/**
* escapeHTML(sHTML)
*
* @param {string} sHTML
* @return {string} with HTML entities "escaped", similar to PHP's htmlspecialchars()
*/
static escapeHTML(sHTML)
{
return sHTML.replace(/[&<>"']/g, function(m)
{
return Str.aHTMLEscapeMap[m];
});
}
/**
* replaceAll(sFind, sReplace, s)
*
* @param {string} sFind
* @param {string} sReplace
* @param {string} s
* @return {string}
*/
static replaceAll(sFind, sReplace, s)
{
var a = {};
a[sFind] = sReplace;
return Str.replaceArray(a, s);
}
/**
* replaceArray(a, s)
*
* @param {Object} a
* @param {string} s
* @return {string}
*/
static replaceArray(a, s)
{
var sMatch = "";
for (var k in a) {
/*
* As noted in:
*
* http://www.regexguru.com/2008/04/escape-characters-only-when-necessary/
*
* inside character classes, only backslash, caret, hyphen and the closing bracket need to be
* escaped. And in fact, if you ensure that the closing bracket is first, the caret is not first,
* and the hyphen is last, you can avoid escaping those as well.
*/
k = k.replace(/([\\[\]*{}().+?])/g, "\\$1");
sMatch += (sMatch ? '|' : '') + k;
}
return s.replace(new RegExp('(' + sMatch + ')', "g"), function(m)
{
return a[m];
});
}
/**
* pad(s, cch, fPadLeft)
*
* NOTE: the maximum amount of padding currently supported is 40 spaces.
*
* @param {string} s is a string
* @param {number} cch is desired length
* @param {boolean} [fPadLeft] (default is padding on the right)
* @return {string} the original string (s) with spaces padding it to the specified length
*/
static pad(s, cch, fPadLeft)
{
var sPadding = " ";
return fPadLeft ? (sPadding + s).slice(-cch) : (s + sPadding).slice(0, cch);
}
/**
* stripLeadingZeros(s, fPad)
*
* @param {string} s
* @param {boolean} [fPad]
* @return {string}
*/
static stripLeadingZeros(s, fPad)
{
var cch = s.length;
s = s.replace(/^0+([0-9A-F]+)$/i, "$1");
if (fPad) s = Str.pad(s, cch, true);
return s;
}
/**
* trim(s)
*
* @param {string} s
* @return {string}
*/
static trim(s)
{
if (String.prototype.trim) {
return s.trim();
}
return s.replace(/^\s+|\s+$/g, "");
}
/**
* toASCIICode(b)
*
* @param {number} b
* @return {string}
*/
static toASCIICode(b)
{
var s;
if (b != Str.ASCII.CR && b != Str.ASCII.LF) {
s = Str.aASCIICodes[b];
}
if (s) {
s = '<' + s + '>';
} else {
s = String.fromCharCode(b);
}
return s;
}
}
Str.aHTMLEscapeMap = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
/*
* Future home of a general-purpose ASCII table. TODO: Flesh it out.
*/
Str.ASCII = {
LF: 0x0A,
CR: 0x0D
};
/*
* Table for converting "unprintable" ASCII codes into mnemonics, to more clearly see what's being printed.
*/
Str.aASCIICodes = {
0x00: "NUL",
0x01: "SOH", // (CTRL_A) Start of Heading
0x02: "STX", // (CTRL_B) Start of Text
0x03: "ETX", // (CTRL_C) End of Text
0x04: "EOT", // (CTRL_D) End of Transmission
0x05: "ENQ", // (CTRL_E) Enquiry
0x06: "ACK", // (CTRL_F) Acknowledge
0x07: "BEL", // (CTRL_G) Bell
0x08: "BS", // (CTRL_H) Backspace
0x09: "TAB", // (CTRL_I) Horizontal Tab
0x0A: "LF", // (CTRL_J) Line Feed (New Line)
0x0B: "VT", // (CTRL_K) Vertical Tab
0x0C: "FF", // (CTRL_L) Form Feed (New Page)
0x0D: "CR", // (CTRL_M) Carriage Return
0x0E: "SO", // (CTRL_N) Shift Out
0x0F: "SI", // (CTRL_O) Shift In
0x10: "DLE", // (CTRL_P) Data Link Escape
0x11: "XON", // (CTRL_Q) Device Control 1 (aka DC1)
0x12: "DC2", // (CTRL_R) Device Control 2
0x13: "XOFF", // (CTRL_S) Device Control 3 (aka DC3)
0x14: "DC4", // (CTRL_T) Device Control 4
0x15: "NAK", // (CTRL_U) Negative Acknowledge
0x16: "SYN", // (CTRL_V) Synchronous Idle
0x17: "ETB", // (CTRL_W) End of Transmission Block
0x18: "CAN", // (CTRL_X) Cancel
0x19: "EM", // (CTRL_Y) End of Medium
0x1A: "SUB", // (CTRL_Z) Substitute
0x1B: "ESC", // Escape
0x1C: "FS", // File Separator
0x1D: "GS", // Group Separator
0x1E: "RS", // Record Separator
0x1F: "US" // Unit Separator
};
Str.TYPES = {
NULL: 0,
BYTE: 1,
WORD: 2,
DWORD: 3,
NUMBER: 4,
STRING: 5,
BOOLEAN: 6,
OBJECT: 7,
ARRAY: 8
};
if (NODE) module.exports = Str;

View file

@ -1,68 +0,0 @@
/**
* @fileoverview User API, as defined by httpapi.js
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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";
/*
* Examples of User API requests:
*
* web.getHost() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.VERIFY + '&' + UserAPI.QUERY.USER + '=' + sUser;
*/
var UserAPI = {
ENDPOINT: "/api/v1/user",
QUERY: {
REQ: "req", // specifies a request
USER: "user", // specifies a user ID
STATE: "state", // specifies a state ID
DATA: "data" // specifies state data
},
REQ: {
CREATE: "create", // creates a user ID
VERIFY: "verify", // requests verification of a user ID
STORE: "store", // stores a machine state on the server
LOAD: "load" // loads a machine state from the server
},
RES: {
CODE: "code",
DATA: "data"
},
CODE: {
OK: "ok",
FAIL: "error"
},
FAIL: {
DUPLICATE: "user already exists",
VERIFY: "unable to verify user",
BADSTATE: "invalid state parameter",
NOSTATE: "no machine state",
BADLOAD: "unable to load machine state",
BADSTORE: "unable to save machine state"
}
};
if (NODE) module.exports = UserAPI;

View file

@ -1,319 +0,0 @@
/**
* @fileoverview Assorted helper functions
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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";
/**
* @typedef {{
* mask: number,
* shift: number
* }}
*/
var BitField;
/**
* @typedef {Object.<BitField>}
*/
var BitFields;
class Usr {
/**
* binarySearch(a, v, fnCompare)
*
* @param {Array} a is an array
* @param {number|string|Array|Object} v
* @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare]
* @return {number} the index of matching entry if non-negative, otherwise the index of the insertion point
*/
static binarySearch(a, v, fnCompare)
{
var left = 0;
var right = a.length;
var found = 0;
if (fnCompare === undefined) {
fnCompare = function(a, b)
{
return a > b ? 1 : a < b ? -1 : 0;
};
}
while (left < right) {
var middle = (left + right) >> 1;
var compareResult;
compareResult = fnCompare(v, a[middle]);
if (compareResult > 0) {
left = middle + 1;
} else {
right = middle;
found = !compareResult;
}
}
return found ? left : ~left;
}
/**
* binaryInsert(a, v, fnCompare)
*
* If element v already exists in array a, the array is unchanged (we don't allow duplicates); otherwise, the
* element is inserted into the array at the appropriate index.
*
* @param {Array} a is an array
* @param {number|string|Array|Object} v is the value to insert
* @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare]
*/
static binaryInsert(a, v, fnCompare)
{
var index = Usr.binarySearch(a, v, fnCompare);
if (index < 0) {
a.splice(-(index + 1), 0, v);
}
}
/**
* getTimestamp()
*
* @return {string} timestamp containing the current date and time ("yyyy-mm-dd hh:mm:ss")
*/
static getTimestamp()
{
var date = new Date();
var padNum = function(n)
{
return (n < 10 ? "0" : "") + n;
};
return date.getFullYear() + "-" + padNum(date.getMonth() + 1) + "-" + padNum(date.getDate()) + " " + padNum(date.getHours()) + ":" + padNum(date.getMinutes()) + ":" + padNum(date.getSeconds());
}
/**
* getMonthDays(nMonth, nYear)
*
* Note that if we're being called on behalf of the RTC, its year is always truncated to two digits (mod 100),
* so we have no idea what century the year 0 might refer to. When using the normal leap-year formula, 0 fails
* the mod 100 test but passes the mod 400 test, so as far as the RTC is concerned, every century year is a leap
* year. Since we're most likely dealing with the year 2000, that's fine, since 2000 was also a leap year.
*
* TODO: There IS a separate CMOS byte that's supposed to be set to CMOS_ADDR.CENTURY_DATE; it's always BCD,
* so theoretically it will contain values like 0x19 or 0x20 (for the 20th and 21st centuries, respectively), and
* we could add that as another parameter to this function, to improve the accuracy, but that would go beyond what
* a real RTC actually does.
*
* @param {number} nMonth (1-12)
* @param {number} nYear (normally a 4-digit year, but it may also be mod 100)
* @return {number} the maximum (1-based) day allowed for the specified month and year
*/
static getMonthDays(nMonth, nYear)
{
var nDays = Usr.aMonthDays[nMonth - 1];
if (nDays == 28) {
if ((nYear % 4) === 0 && ((nYear % 100) || (nYear % 400) === 0)) {
nDays++;
}
}
return nDays;
}
/**
* formatDate(sFormat, date)
*
* @param {string} sFormat (eg, "F j, Y", "Y-m-d H:i:s")
* @param {Date} [date] (default is the current time)
* @return {string}
*
* Supported identifiers in sFormat include:
*
* a: lowercase ante meridiem and post meridiem (am or pm)
* d: day of the month, 2 digits with leading zeros (01,...,31)
* g: hour in 12-hour format, without leading zeros (1,...,12)
* i: minutes, with leading zeros (00,...,59)
* j: day of the month, without leading zeros (1,...,31)
* l: day of the week ("Sunday",...,"Saturday")
* m: month, with leading zeros (01,...,12)
* s: seconds, with leading zeros (00,...,59)
* F: month ("January",...,"December")
* H: hour in 24-hour format, with leading zeros (00,...,23)
* Y: year (eg, 2014)
*
* For more inspiration, see: http://php.net/manual/en/function.date.php
*/
static formatDate(sFormat, date)
{
var sDate = "";
if (!date) date = new Date();
var iHour = date.getHours();
var iDay = date.getDate();
var iMonth = date.getMonth() + 1;
for (var i = 0; i < sFormat.length; i++) {
var ch;
switch ((ch = sFormat.charAt(i))) {
case 'a':
sDate += (iHour < 12 ? "am" : "pm");
break;
case 'd':
sDate += ('0' + iDay).slice(-2);
break;
case 'g':
sDate += (!iHour ? 12 : (iHour > 12 ? iHour - 12 : iHour));
break;
case 'i':
sDate += ('0' + date.getMinutes()).slice(-2);
break;
case 'j':
sDate += iDay;
break;
case 'l':
sDate += Usr.asDays[date.getDay()];
break;
case 'm':
sDate += ('0' + iMonth).slice(-2);
break;
case 's':
sDate += ('0' + date.getSeconds()).slice(-2);
break;
case 'F':
sDate += Usr.asMonths[iMonth - 1];
break;
case 'H':
sDate += ('0' + iHour).slice(-2);
break;
case 'Y':
sDate += date.getFullYear();
break;
default:
sDate += ch;
break;
}
}
return sDate;
}
/**
* defineBitFields(bfs)
*
* Prepares a bit field definition for use with getBitField() and setBitField(); eg:
*
* var bfs = Usr.defineBitFields({num:20, count:8, btmod:1, type:3});
*
* The above defines a set of bit fields containing four fields: num (bits 0-19), count (bits 20-27), btmod (bit 28), and type (bits 29-31).
*
* Usr.setBitField(bfs.num, n, 1);
*
* The above set bit field "bfs.num" in numeric variable "n" to the value 1.
*
* @param {Object} bfs
* @return {BitFields}
*/
static defineBitFields(bfs)
{
var bit = 0;
for (var f in bfs) {
var width = bfs[f];
var mask = ((1 << width) - 1) << bit;
bfs[f] = {mask: mask, shift: bit};
bit += width;
}
return bfs;
}
/**
* initBitFields(bfs, ...)
*
* @param {BitFields} bfs
* @param {...number} var_args
* @return {number} a value containing all supplied bit fields
*/
static initBitFields(bfs, var_args)
{
var v = 0, i = 1;
for (var f in bfs) {
if (i >= arguments.length) break;
v = Usr.setBitField(bfs[f], v, arguments[i++]);
}
return v;
}
/**
* getBitField(bf, v)
*
* @param {BitField} bf
* @param {number} v is a value containing bit fields
* @return {number} the value of the bit field in v defined by bf
*/
static getBitField(bf, v)
{
return (v & bf.mask) >> bf.shift;
}
/**
* setBitField(bf, v, n)
*
* @param {BitField} bf
* @param {number} v is a value containing bit fields
* @param {number} n is a value to store in v in the bit field defined by bf
* @return {number} updated v
*/
static setBitField(bf, v, n)
{
return (v & ~bf.mask) | ((n << bf.shift) & bf.mask);
}
/**
* indexOf(a, t, i)
*
* Use this instead of Array.prototype.indexOf() if you can't be sure the browser supports it.
*
* @param {Array} a
* @param {*} t
* @param {number} [i]
* @returns {number}
*/
static indexOf(a, t, i)
{
if (Array.prototype.indexOf) {
return a.indexOf(t, i);
}
i = i || 0;
if (i < 0) i += a.length;
if (i < 0) i = 0;
for (var n = a.length; i < n; i++) {
if (i in a && a[i] === t) return i;
}
return -1;
}
}
Usr.asDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
Usr.asMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
Usr.aMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
/**
* getTime()
*
* @return {number} the current time, in milliseconds
*/
Usr.getTime = Date.now || function() { return +new Date(); };
if (NODE) module.exports = Usr;

View file

@ -1,973 +0,0 @@
/**
* @fileoverview Browser-related helper functions
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
* @copyright © Jeff Parsons 2012-2017
*
* 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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/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 (NODE) {
var Component = require("../../shared/es6/component");
var ReportAPI = require("../../shared/es6/reportapi");
}
/*
* According to http://www.w3schools.com/jsref/jsref_obj_global.asp, these are the *global* properties
* and functions of JavaScript-in-the-Browser:
*
* Property Description
* ---
* Infinity A numeric value that represents positive/negative infinity
* NaN "Not-a-Number" value
* undefined Indicates that a variable has not been assigned a value
*
* Function Description
* ---
* decodeURI() Decodes a URI
* decodeURIComponent() Decodes a URI component
* encodeURI() Encodes a URI
* encodeURIComponent() Encodes a URI component
* escape() Deprecated in version 1.5. Use encodeURI() or encodeURIComponent() instead
* eval() Evaluates a string and executes it as if it was script code
* isFinite() Determines whether a value is a finite, legal number
* isNaN() Determines whether a value is an illegal number
* Number() Converts an object's value to a number
* parseFloat() Parses a string and returns a floating point number
* parseInt() Parses a string and returns an integer
* String() Converts an object's value to a string
* unescape() Deprecated in version 1.5. Use decodeURI() or decodeURIComponent() instead
*
* And according to http://www.w3schools.com/jsref/obj_window.asp, these are the properties and functions
* of the *window* object.
*
* Property Description
* ---
* closed Returns a Boolean value indicating whether a window has been closed or not
* defaultStatus Sets or returns the default text in the statusbar of a window
* document Returns the Document object for the window (See Document object)
* frames Returns an array of all the frames (including iframes) in the current window
* history Returns the History object for the window (See History object)
* innerHeight Returns the inner height of a window's content area
* innerWidth Returns the inner width of a window's content area
* length Returns the number of frames (including iframes) in a window
* location Returns the Location object for the window (See Location object)
* name Sets or returns the name of a window
* navigator Returns the Navigator object for the window (See Navigator object)
* opener Returns a reference to the window that created the window
* outerHeight Returns the outer height of a window, including toolbars/scrollbars
* outerWidth Returns the outer width of a window, including toolbars/scrollbars
* pageXOffset Returns the pixels the current document has been scrolled (horizontally) from the upper left corner of the window
* pageYOffset Returns the pixels the current document has been scrolled (vertically) from the upper left corner of the window
* parent Returns the parent window of the current window
* screen Returns the Screen object for the window (See Screen object)
* screenLeft Returns the x coordinate of the window relative to the screen
* screenTop Returns the y coordinate of the window relative to the screen
* screenX Returns the x coordinate of the window relative to the screen
* screenY Returns the y coordinate of the window relative to the screen
* self Returns the current window
* status Sets or returns the text in the statusbar of a window
* top Returns the topmost browser window
*
* Method Description
* ---
* alert() Displays an alert box with a message and an OK button
* atob() Decodes a base-64 encoded string
* blur() Removes focus from the current window
* btoa() Encodes a string in base-64
* clearInterval() Clears a timer set with setInterval()
* clearTimeout() Clears a timer set with setTimeout()
* close() Closes the current window
* confirm() Displays a dialog box with a message and an OK and a Cancel button
* createPopup() Creates a pop-up window
* focus() Sets focus to the current window
* moveBy() Moves a window relative to its current position
* moveTo() Moves a window to the specified position
* open() Opens a new browser window
* print() Prints the content of the current window
* prompt() Displays a dialog box that prompts the visitor for input
* resizeBy() Resizes the window by the specified pixels
* resizeTo() Resizes the window to the specified width and height
* scroll() This method has been replaced by the scrollTo() method.
* scrollBy() Scrolls the content by the specified number of pixels
* scrollTo() Scrolls the content to the specified coordinates
* setInterval() Calls a function or evaluates an expression at specified intervals (in milliseconds)
* setTimeout() Calls a function or evaluates an expression after a specified number of milliseconds
* stop() Stops the window from loading
*/
class Web {
/**
* log(s, type)
*
* For diagnostic output only. DEBUG must be true (or "--debug" specified via the command-line)
* for Component.log() to display anything.
*
* @param {string} [s] is the message text
* @param {string} [type] is the message type
*/
static log(s, type)
{
Component.log(s, type);
}
/**
* notice(s, fPrintOnly, id)
*
* @param {string} s is the message text
* @param {boolean} [fPrintOnly]
* @param {string} [id] is the caller's ID, if any
*/
static notice(s, fPrintOnly, id)
{
Component.notice(s, fPrintOnly, id);
}
/**
* getResource(sURL, dataPost, fAsync, done)
*
* Request the specified resource (sURL), and once the request is complete, notify done().
*
* Also, if dataPost is set to a string, that string can be used to control the response format;
* by default, the response format is plain text, but you can specify "bytes" to request arbitrary
* binary data, which should come back as a string of bytes.
*
* TODO: The "bytes" option works by calling overrideMimeType(), which was never a best practice.
* Instead, we should implement supported response types ("text" and "arraybuffer", at a minimum)
* by setting xmlHTTP.responseType to one of those values before calling xmlHTTP.send().
*
* ES6 ALERT: Default parameters.
*
* @param {string} sURL
* @param {string|Object|null} [dataPost] for a POST request (default is a GET request)
* @param {boolean} [fAsync] is true for an asynchronous request
* @param {function(string,string,number)} [done]
* @return {Array|null} Array containing [sResource, nErrorCode], or null if no response yet
*/
static getResource(sURL, dataPost, fAsync = false, done)
{
var nErrorCode = 0, sResource = null, response = null;
if (typeof resources == 'object' && (sResource = resources[sURL])) {
if (done) done(sURL, sResource, nErrorCode);
return [sResource, nErrorCode];
}
else if (fAsync && typeof resources == 'function') {
resources(sURL, function(sResource, nErrorCode)
{
if (done) done(sURL, sResource, nErrorCode);
});
return response;
}
if (DEBUG) {
/*
* The larger resources we put on archive.pcjs.org should also be available locally...
*/
sURL = sURL.replace(/^http:\/\/archive.pcjs.org(\/.*)\/([^\/]*)$/, "$1/archive/$2");
}
if (NODE) {
/*
* We don't even need to load Component, because we can't use any of the code below
* within Node anyway. Instead, we must hand this request off to our network library.
*
* if (!Component) Component = require("./component");
*/
var Net = require("./netlib");
return Net.getResource(sURL, dataPost, fAsync, done);
}
var xmlHTTP = (window.XMLHttpRequest? new window.XMLHttpRequest() : new window.ActiveXObject("Microsoft.XMLHTTP"));
if (fAsync) {
xmlHTTP.onreadystatechange = function()
{
if (xmlHTTP.readyState === 4) {
/*
* The following line was recommended for WebKit, as a work-around to prevent the handler firing multiple
* times when debugging. Unfortunately, that's not the only XMLHttpRequest problem that occurs when
* debugging, so I think the WebKit problem is deeper than that. When we have multiple XMLHttpRequests
* pending, any debugging activity means most of them simply get dropped on floor, so what may actually be
* happening are mis-notifications rather than redundant notifications.
*
* xmlHTTP.onreadystatechange = undefined;
*/
sResource = xmlHTTP.responseText;
/*
* The normal "success" case is an HTTP status code of 200, but when testing with files loaded
* from the local file system (ie, when using the "file:" protocol), we have to be a bit more "flexible".
*/
if (xmlHTTP.status == 200 || !xmlHTTP.status && sResource.length && Web.getHostProtocol() == "file:") {
if (MAXDEBUG) Web.log("xmlHTTP.onreadystatechange(" + sURL + "): returned " + sResource.length + " bytes");
}
else {
nErrorCode = xmlHTTP.status || -1;
Web.log("xmlHTTP.onreadystatechange(" + sURL + "): error code " + nErrorCode);
}
if (done) done(sURL, sResource, nErrorCode);
}
};
}
if (dataPost && typeof dataPost == "object") {
var sDataPost = "";
for (var p in dataPost) {
if (!dataPost.hasOwnProperty(p)) continue;
if (sDataPost) sDataPost += "&";
sDataPost += p + '=' + encodeURIComponent(dataPost[p]);
}
sDataPost = sDataPost.replace(/%20/g, '+');
if (MAXDEBUG) Web.log("Web.getResource(POST " + sURL + "): " + sDataPost.length + " bytes");
xmlHTTP.open("POST", sURL, fAsync); // ensure that fAsync is a valid boolean (Internet Explorer xmlHTTP functions insist on it)
xmlHTTP.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xmlHTTP.send(sDataPost);
} else {
if (MAXDEBUG) Web.log("Web.getResource(GET " + sURL + ")");
xmlHTTP.open("GET", sURL, fAsync); // ensure that fAsync is a valid boolean (Internet Explorer xmlHTTP functions insist on it)
if (dataPost == "bytes") {
xmlHTTP.overrideMimeType("text/plain; charset=x-user-defined");
}
xmlHTTP.send();
}
if (!fAsync) {
sResource = xmlHTTP.responseText;
if (xmlHTTP.status == 200) {
if (MAXDEBUG) Web.log("Web.getResource(" + sURL + "): returned " + sResource.length + " bytes");
} else {
nErrorCode = xmlHTTP.status || -1;
Web.log("Web.getResource(" + sURL + "): error code " + nErrorCode);
}
if (done) done(sURL, sResource, nErrorCode);
response = [sResource, nErrorCode];
}
return response;
}
/**
* parseMemoryResource(sURL, sData)
*
* @param {string} sURL
* @param {string} sData
* @return {Object|null} (resource)
*/
static parseMemoryResource(sURL, sData)
{
var i;
var resource = {
aBytes: null,
aSymbols: null,
addrLoad: null,
addrExec: null
};
if (sData.charAt(0) == "[" || sData.charAt(0) == "{") {
try {
var a, ib, data;
if (sData.substr(0, 1) == "<") { // if the "data" begins with a "<"...
/*
* Early server configs reported an error (via the nErrorCode parameter) if a tape URL was invalid,
* but more recent server configs now display a somewhat friendlier HTML error page. The downside,
* however, is that the original error has been buried, and we've received "data" that isn't actually
* tape data. So if the data we've received appears to be "HTML-like", we treat it as an error message.
*/
throw new Error(sData);
}
/*
* TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to
* eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in
* IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks
* (perhaps one track per chunk, using regular expressions) and then manually re-assemble it.
*
* However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")")
* is a much easier fix. The only drawback is that we must first quote any unquoted property names
* and remove any comments, because while eval() was cool with them, JSON.parse() is more particular;
* the following RegExp replacements take care of those requirements.
*
* The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and
* while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified),
* I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement
* for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect
* any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so:
*
* ["unrecognized disk path: test.img"]
*/
if (sData.indexOf("0x") < 0 && sData.substr(0, 2) != "[\"") {
data = JSON.parse(sData.replace(/([a-z]+):/gm, "\"$1\":").replace(/\/\/[^\n]*/gm, ""));
} else {
data = eval("(" + sData + ")");
}
resource.addrLoad = data['load'];
resource.addrExec = data['exec'];
if (a = data['bytes']) {
resource.aBytes = a;
}
else if (a = data['words']) {
/*
* Convert all words into bytes
*/
resource.aBytes = new Array(a.length * 2);
for (i = 0, ib = 0; i < a.length; i++) {
resource.aBytes[ib++] = a[i] & 0xff;
resource.aBytes[ib++] = (a[i] >> 8) & 0xff;
Component.assert(!(a[i] & ~0xffff));
}
}
else if (a = data['data']) {
/*
* Convert all dwords (longs) into bytes
*/
resource.aBytes = new Array(a.length * 4);
for (i = 0, ib = 0; i < a.length; i++) {
resource.aBytes[ib++] = a[i] & 0xff;
resource.aBytes[ib++] = (a[i] >> 8) & 0xff;
resource.aBytes[ib++] = (a[i] >> 16) & 0xff;
resource.aBytes[ib++] = (a[i] >> 24) & 0xff;
}
}
else {
resource.aBytes = data;
}
resource.aSymbols = data['symbols'];
if (!resource.aBytes.length) {
Component.error("Empty resource: " + sURL);
resource = null;
}
else if (resource.aBytes.length == 1) {
Component.error(resource.aBytes[0]);
resource = null;
}
} catch (e) {
Component.error("Resource data error (" + sURL + "): " + e.message);
resource = null;
}
}
else {
/*
* Parse the data manually; we assume it's a series of hex byte-values separated by whitespace.
*/
var ab = [];
var sHexData = sData.replace(/\n/gm, " ").replace(/ +$/, "");
var asHexData = sHexData.split(" ");
for (i = 0; i < asHexData.length; i++) {
var n = parseInt(asHexData[i], 16);
if (isNaN(n)) {
Component.error("Resource data error (" + sURL + "): invalid hex byte (" + asHexData[i] + ")");
break;
}
ab.push(n & 0xff);
}
if (i == asHexData.length) resource.aBytes = ab;
}
return resource;
}
/**
* sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName)
*
* Send a report (eg, bug report) to the server.
*
* @param {string} sApp (eg, "PCjs")
* @param {string} sVer (eg, "1.02")
* @param {string} sURL (eg, "/devices/pc/machine/5150/mda/64kb/machine.xml")
* @param {string} sUser (ie, the user key, if any)
* @param {string} sType (eg, "bug"); one of ReportAPI.TYPE.*
* @param {string} sReport (eg, unparsed state data)
* @param {string} [sHostName] (default is http://SITEHOST)
*/
static sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName)
{
var dataPost = {};
dataPost[ReportAPI.QUERY.APP] = sApp;
dataPost[ReportAPI.QUERY.VER] = sVer;
dataPost[ReportAPI.QUERY.URL] = sURL;
dataPost[ReportAPI.QUERY.USER] = sUser;
dataPost[ReportAPI.QUERY.TYPE] = sType;
dataPost[ReportAPI.QUERY.DATA] = sReport;
var sReportURL = (sHostName? sHostName : "http://" + SITEHOST) + ReportAPI.ENDPOINT;
Web.getResource(sReportURL, dataPost, true);
}
/**
* getHost()
*
* @return {string}
*/
static getHost()
{
return ("http://" + (window? window.location.host : SITEHOST));
}
/**
* getHostURL()
*
* @return {string|null}
*/
static getHostURL()
{
return (window? window.location.href : null);
}
/**
* getHostProtocol()
*
* @return {string}
*/
static getHostProtocol()
{
return (window? window.location.protocol : "file:");
}
/**
* getUserAgent()
*
* @return {string}
*/
static getUserAgent()
{
return (window? window.navigator.userAgent : "");
}
/**
* hasLocalStorage
*
* true if localStorage support exists, is enabled, and works; false otherwise
*
* @return {boolean}
*/
static hasLocalStorage()
{
if (Web.fLocalStorage == null) {
var f = false;
if (window) {
try {
window.localStorage.setItem(Web.sLocalStorageTest, Web.sLocalStorageTest);
f = (window.localStorage.getItem(Web.sLocalStorageTest) == Web.sLocalStorageTest);
window.localStorage.removeItem(Web.sLocalStorageTest);
} catch (e) {
Web.logLocalStorageError(e);
f = false;
}
}
Web.fLocalStorage = f;
}
return Web.fLocalStorage;
}
/**
* logLocalStorageError(e)
*
* @param {Error} e is an exception
*/
static logLocalStorageError(e)
{
Web.log(e.message, "localStorage error");
}
/**
* getLocalStorageItem(sKey)
*
* Returns the requested key value, or null if the key does not exist, or undefined if localStorage is not available
*
* @param {string} sKey
* @return {string|null|undefined} sValue
*/
static getLocalStorageItem(sKey)
{
var sValue;
if (window) {
try {
sValue = window.localStorage.getItem(sKey);
} catch (e) {
Web.logLocalStorageError(e);
}
}
return sValue;
}
/**
* setLocalStorageItem(sKey, sValue)
*
* @param {string} sKey
* @param {string} sValue
* @return {boolean} true if localStorage is available, false if not
*/
static setLocalStorageItem(sKey, sValue)
{
try {
window.localStorage.setItem(sKey, sValue);
return true;
} catch (e) {
Web.logLocalStorageError(e);
}
return false;
}
/**
* removeLocalStorageItem(sKey)
*
* @param {string} sKey
*/
static removeLocalStorageItem(sKey)
{
try {
window.localStorage.removeItem(sKey);
} catch (e) {
Web.logLocalStorageError(e);
}
}
/**
* getLocalStorageKeys()
*
* @return {Array}
*/
static getLocalStorageKeys()
{
var a = [];
try {
for (var i = 0, c = window.localStorage.length; i < c; i++) {
a.push(window.localStorage.key(i));
}
} catch (e) {
Web.logLocalStorageError(e);
}
return a;
}
/**
* reloadPage()
*/
static reloadPage()
{
if (window) window.location.reload();
}
/**
* isUserAgent(s)
*
* Check the browser's user-agent string for the given substring; "iOS" and "MSIE" are special values you can
* use that will match any iOS or MSIE browser, respectively (even IE11, in the case of "MSIE").
*
* 2013-11-06: In a questionable move, MSFT changed the user-agent reported by IE11 on Windows 8.1, eliminating
* the "MSIE" string (which MSDN calls a "version token"; see http://msdn.microsoft.com/library/ms537503.aspx);
* they say "public websites should rely on feature detection, rather than browser detection, in order to design
* their sites for browsers that don't support the features used by the website." So, in IE11, we get a user-agent
* that tries to fool apps into thinking the browser is more like WebKit or Gecko:
*
* Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko
*
* That's a nice idea, but in the meantime, they hosed the XSL transform code in embed.js, which contained
* some very critical browser-specific code; turning on IE's "Compatibility Mode" didn't help either, because
* that's a sledgehammer solution which restores the old user-agent string but also disables other features like
* HTML5 canvas support. As an interim solution, I'm treating any "MSIE" check as a check for either "MSIE" or
* "Trident".
*
* UPDATE: I've since found ways to make the code in embed.js more browser-agnostic, so for now, there's isn't
* any code that cares about "MSIE", but I've left the change in place, because I wouldn't be surprised if I'll
* need more IE-specific code in the future, perhaps for things like copy/paste functionality, or mouse capture.
*
* @param {string} s is a substring to search for in the user-agent; as noted above, "iOS" and "MSIE" are special values
* @return {boolean} is true if the string was found, false if not
*/
static isUserAgent(s)
{
if (window) {
var userAgent = Web.getUserAgent();
/*
* Here's one case where we have to be careful with Component, because when isUserAgent() is called by
* the init code below, component.js hasn't been loaded yet. The simple solution for now is to remove the call.
*
* Web.log("agent: " + userAgent);
*
* And yes, it would be pointless to use the conditional (?) operator below, if not for the Google Closure
* Compiler (v20130823) failing to detect the entire expression as a boolean.
*/
return s == "iOS" && !!userAgent.match(/(iPod|iPhone|iPad)/) && !!userAgent.match(/AppleWebKit/) || s == "MSIE" && !!userAgent.match(/(MSIE|Trident)/) || (userAgent.indexOf(s) >= 0);
}
return false;
}
/**
* isMobile()
*
* Check the browser's user-agent string for the substring "Mobi", as per Mozilla recommendation:
*
* https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent
*
* @return {boolean} is true if the browser appears to be a mobile (ie, non-desktop) web browser, false if not
*/
static isMobile()
{
return Web.isUserAgent("Mobi");
}
/**
* getURLParm(sParm)
*
* @param {string} sParm
* @return {string|undefined}
*/
static getURLParm(sParm)
{
if (!Web.parmsURL) {
Web.parmsURL = Web.parseURLParms();
}
return Web.parmsURL[sParm];
}
/**
* parseURLParms(sParms)
*
* @param {string} [sParms] containing the parameter portion of a URL (ie, after the '?')
* @return {Object} containing properties for each parameter found
*/
static parseURLParms(sParms)
{
var aParms = {};
if (window) { // an alternative to "if (typeof module === 'undefined')" if require("defines") was used
if (!sParms) {
/*
* Note that window.location.href returns the entire URL, whereas window.location.search
* returns only the parameters, if any (starting with the '?', which we skip over with a substr() call).
*/
sParms = window.location.search.substr(1);
}
var match;
var pl = /\+/g; // RegExp for replacing addition symbol with a space
var search = /([^&=]+)=?([^&]*)/g;
var decode = function(s)
{
return decodeURIComponent(s.replace(pl, " "));
};
while ((match = search.exec(sParms))) {
aParms[decode(match[1])] = decode(match[2]);
}
}
return aParms;
}
/**
* downloadFile(sData, sType, fBase64, sFileName)
*
* @param {string} sData
* @param {string} sType
* @param {boolean} [fBase64]
* @param {string} [sFileName]
*/
static downloadFile(sData, sType, fBase64, sFileName)
{
var link = null, sAlert;
var sURI = "data:application/" + sType + (fBase64? ";base64" : "") + ",";
if (!Web.isUserAgent("Firefox")) {
sURI += (fBase64? sData : encodeURI(sData));
} else {
sURI += (fBase64? sData : encodeURIComponent(sData));
}
if (sFileName) {
link = document.createElement('a');
if (typeof link.download != 'string') link = null;
}
if (link) {
link.href = sURI;
link.download = sFileName;
document.body.appendChild(link); // Firefox allegedly requires the link to be in the body
link.click();
document.body.removeChild(link);
sAlert = 'Check your Downloads folder for ' + sFileName + '.';
} else {
window.open(sURI);
sAlert = 'Check your browser for a new window/tab containing the requested data' + (sFileName? (' (' + sFileName + ')') : '') + '.';
}
return sAlert;
}
/**
* onCountRepeat(n, fnRepeat, fnComplete, msDelay)
*
* Call fnRepeat() n times with an msDelay millisecond delay between calls,
* then call fnComplete() when n has been exhausted OR fnRepeat() returns false.
*
* @param {number} n
* @param {function()} fnRepeat
* @param {function()} fnComplete
* @param {number} [msDelay]
*/
static onCountRepeat(n, fnRepeat, fnComplete, msDelay)
{
var fnTimeout = function doCountRepeat()
{
n -= 1;
if (n >= 0) {
if (!fnRepeat()) n = 0;
}
if (n > 0) {
setTimeout(fnTimeout, msDelay || 0);
return;
}
fnComplete();
};
fnTimeout();
}
/**
* onClickRepeat(e, msDelay, msRepeat, fn)
*
* Repeatedly call fn() with an initial msDelay, and an msRepeat delay thereafter,
* as long as HTML control Object e has an active "down" event and fn() returns true.
*
* @param {Object} e
* @param {number} msDelay
* @param {number} msRepeat
* @param {function(boolean)} fn is passed false on the first call, true on all repeated calls
*/
static onClickRepeat(e, msDelay, msRepeat, fn)
{
var ms = 0, timer = null, fIgnoreMouseEvents = false;
var fnRepeat = function doClickRepeat()
{
if (fn(ms === msRepeat)) {
timer = setTimeout(fnRepeat, ms);
ms = msRepeat;
}
};
e.onmousedown = function()
{
// Web.log("onMouseDown()");
if (!fIgnoreMouseEvents) {
if (!timer) {
ms = msDelay;
fnRepeat();
}
}
};
e.ontouchstart = function()
{
// Web.log("onTouchStart()");
if (!timer) {
ms = msDelay;
fnRepeat();
}
};
e.onmouseup = e.onmouseout = function()
{
// Web.log("onMouseUp()/onMouseOut()");
if (timer) {
clearTimeout(timer);
timer = null;
}
};
e.ontouchend = e.ontouchcancel = function()
{
// Web.log("onTouchEnd()/onTouchCancel()");
if (timer) {
clearTimeout(timer);
timer = null;
}
/*
* Devices that generate ontouch* events ALSO generate onmouse* events,
* and generally do so immediately after all the touch events are complete,
* so unless we want double the action, we need to ignore mouse events.
*/
fIgnoreMouseEvents = true;
};
}
/**
* onPageEvent(sName, fn)
*
* For 'onload', 'onunload', and 'onpageshow' events, most callers should NOT use this function, but
* instead use Web.onInit(), Web.onShow(), and Web.onExit(), respectively.
*
* The only components that should still use onPageEvent() are THIS component (see the bottom of this file)
* and components that need to capture other events (eg, the 'onresize' event in the Video component).
*
* This function creates a chain of callbacks, allowing multiple JavaScript modules to define handlers
* for the same event, which wouldn't be possible if everyone modified window['onload'], window['onunload'],
* etc, themselves. However, that's less of a concern now, because assuming everyone else is now using
* onInit(), onExit(), etc, then there really IS only one component setting the window callback: this one.
*
* NOTE: It's risky to refer to obscure event handlers with "dot" names, because the Closure Compiler may
* erroneously replace them (eg, window.onpageshow is a good example).
*
* @param {string} sFunc
* @param {function()} fn
*/
static onPageEvent(sFunc, fn)
{
if (window) {
var fnPrev = window[sFunc];
if (typeof fnPrev !== 'function') {
window[sFunc] = fn;
} else {
/*
* TODO: Determine whether there's any value in receiving/sending the Event object that the
* browser provides when it generates the original event.
*/
window[sFunc] = function onWindowEvent()
{
if (fnPrev) fnPrev();
fn();
};
}
}
};
/**
* onInit(fn)
*
* Use this instead of setting window.onload. Allows multiple JavaScript modules to define their own 'onload' event handler.
*
* @param {function()} fn
*/
static onInit(fn)
{
Web.aPageEventHandlers['init'].push(fn);
};
/**
* onShow(fn)
*
* @param {function()} fn
*
* Use this instead of setting window.onpageshow. Allows multiple JavaScript modules to define their own 'onpageshow' event handler.
*/
static onShow(fn)
{
Web.aPageEventHandlers['show'].push(fn);
};
/**
* onExit(fn)
*
* @param {function()} fn
*
* Use this instead of setting window.onunload. Allows multiple JavaScript modules to define their own 'onunload' event handler.
*/
static onExit(fn)
{
Web.aPageEventHandlers['exit'].push(fn);
};
/**
* doPageEvent(afn)
*
* @param {Array.<function()>} afn
*/
static doPageEvent(afn)
{
if (Web.fPageEventsEnabled) {
try {
for (var i = 0; i < afn.length; i++) {
afn[i]();
}
} catch (e) {
Web.notice("An unexpected exception occurred:\n\n" + e.message + "\n\nPlease send this information to support@pcjs.org. Thanks.");
}
}
};
/**
* enablePageEvents(fEnable)
*
* @param {boolean} fEnable is true to enable page events, false to disable (they're enabled by default)
*/
static enablePageEvents(fEnable)
{
if (!Web.fPageEventsEnabled && fEnable) {
Web.fPageEventsEnabled = true;
if (Web.fPageLoaded) Web.sendPageEvent('init');
if (Web.fPageShowed) Web.sendPageEvent('show');
return;
}
Web.fPageEventsEnabled = fEnable;
}
/**
* sendPageEvent(sEvent)
*
* This allows us to manually trigger page events.
*
* @param {string} sEvent (one of 'init', 'show' or 'exit')
*/
static sendPageEvent(sEvent)
{
if (Web.aPageEventHandlers[sEvent]) {
Web.doPageEvent(Web.aPageEventHandlers[sEvent]);
}
}
}
Web.parmsURL = null; // initialized on first call to parseURLParms()
Web.aPageEventHandlers = {
'init': [], // list of window 'onload' handlers
'show': [], // list of window 'onpageshow' handlers
'exit': [] // list of window 'onunload' handlers (although we prefer to use 'onbeforeunload' if possible)
};
Web.fPageLoaded = false; // set once the page's first 'onload' event has occurred
Web.fPageShowed = false; // set once the page's first 'onpageshow' event has occurred
Web.fPageEventsEnabled = true; // default is true, set to false (or true) by enablePageEvents()
/**
* fLocalStorage
*
* true if localStorage support exists, is enabled, and works; "falsey" otherwise
*
* @type {boolean|null}
*/
Web.fLocalStorage = null;
/**
* TODO: Is there any way to get the Closure Compiler to stop inlining this string? This isn't cutting it.
*
* @const {string}
*/
Web.sLocalStorageTest = "PCjs.localStorage";
Web.onPageEvent('onload', function onPageLoad() {
Web.fPageLoaded = true;
Web.doPageEvent(Web.aPageEventHandlers['init']);
});
Web.onPageEvent('onpageshow', function onPageShow() {
Web.fPageShowed = true;
Web.doPageEvent(Web.aPageEventHandlers['show']);
});
Web.onPageEvent(Web.isUserAgent("Opera") || Web.isUserAgent("iOS")? 'onunload' : 'onbeforeunload', function onPageUnload() {
Web.doPageEvent(Web.aPageEventHandlers['exit']);
});
if (NODE) module.exports = Web;

File diff suppressed because it is too large Load diff

View file

@ -28,11 +28,9 @@
"use strict";
if (DEBUGGER) {
if (NODE) {
var str = require("../../shared/lib/strlib");
var Component = require("../../shared/lib/component");
}
if (NODE) {
var Str = require("../../shared/lib/strlib");
var Component = require("../../shared/lib/component");
}
/**
@ -50,105 +48,102 @@ if (DEBUGGER) {
* fTemporary:(boolean|undefined),
* sCmd:(string|undefined),
* aCmds:(Array.<string>|undefined)
* }} DbgAddr
* }}
*/
var DbgAddr;
/**
* Debugger(parmsDbg)
* Since the Closure Compiler treats ES6 classes as @struct rather than @dict by default,
* it deters us from defining named properties on our components; eg:
*
* The Debugger component supports the following optional (parmsDbg) properties:
* this['exports'] = {...}
*
* base: the base to use for most numeric input/output (default is 16)
* results in an error:
*
* 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.
* Cannot do '[]' access on a struct
*
* @constructor
* @extends Component
* @param {Object} parmsDbg
* So, in order to define 'exports', we must override the @struct assumption by annotating
* the class as @unrestricted (or @dict). Note that this must be done both here and in the
* subclass (eg, SerialPort), because otherwise the Compiler won't allow us to *reference*
* the named property either.
*
* TODO: Consider marking ALL our classes unrestricted, because otherwise it forces us to
* define every single property the class uses in its constructor, which results in a fair
* bit of redundant initialization, since many properties aren't (and don't need to be) fully
* initialized until the appropriate init(), reset(), restore(), etc. function is called.
*
* The upside, however, may be that since the structure of the class is completely defined by
* the constructor, JavaScript engines may be able to optimize and run more efficiently.
*
* @unrestricted
*/
function Debugger(parmsDbg)
{
if (DEBUGGER) {
class Debugger extends Component {
/**
* 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.
*
* @param {Object} parmsDbg
*/
constructor(parmsDbg) {
if (DEBUGGER) {
Component.call(this, "Debugger", parmsDbg, Debugger);
super("Debugger", parmsDbg);
/*
* Default base used to display all values; modified with the "s base" command.
*/
this.nBase = parmsDbg['base'] || 16;
this.fParens = false;
/*
* 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;
/*
* 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;
/*
* 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 = [];
/*
* 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 = {};
/*
* 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
};
} // endif DEBUGGER
}
/**
* getRegIndex(sReg, off)
@ -160,10 +155,9 @@ if (DEBUGGER) {
* @param {number} [off] optional offset into sReg
* @return {number} register index, or -1 if not found
*/
Debugger.prototype.getRegIndex = function(sReg, off)
{
getRegIndex(sReg, off) {
return -1;
};
}
/**
* getRegValue(iReg)
@ -174,10 +168,9 @@ if (DEBUGGER) {
* @param {number} iReg
* @return {number|undefined}
*/
Debugger.prototype.getRegValue = function(iReg)
{
getRegValue(iReg) {
return undefined;
};
}
/**
* parseAddrReference(s, sAddr)
@ -191,10 +184,9 @@ if (DEBUGGER) {
* @param {string} sAddr
* @return {string}
*/
Debugger.prototype.parseAddrReference = function(s, sAddr)
{
parseAddrReference(s, sAddr) {
return s.replace('[' + sAddr + ']', "unimplemented");
};
}
/**
* getNextCommand()
@ -202,8 +194,7 @@ if (DEBUGGER) {
* @this {Debugger}
* @return {string}
*/
Debugger.prototype.getNextCommand = function()
{
getNextCommand() {
var sCmd;
if (this.iPrevCmd > 0) {
sCmd = this.aPrevCmds[--this.iPrevCmd];
@ -212,7 +203,7 @@ if (DEBUGGER) {
this.iPrevCmd = -1;
}
return sCmd;
};
}
/**
* getPrevCommand()
@ -220,14 +211,13 @@ if (DEBUGGER) {
* @this {Debugger}
* @return {string|null}
*/
Debugger.prototype.getPrevCommand = function()
{
getPrevCommand() {
var sCmd = null;
if (this.iPrevCmd < this.aPrevCmds.length - 1) {
sCmd = this.aPrevCmds[++this.iPrevCmd];
}
return sCmd;
};
}
/**
* parseCommand(sCmd, fSave, chSep)
@ -238,8 +228,7 @@ if (DEBUGGER) {
* @param {string} [chSep] is the command separator character (default is ';')
* @return {Array.<string>}
*/
Debugger.prototype.parseCommand = function(sCmd, fSave, chSep)
{
parseCommand(sCmd, fSave, chSep) {
if (fSave) {
if (!sCmd) {
if (this.fAssemble) {
@ -265,7 +254,7 @@ if (DEBUGGER) {
* 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]);
* 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.
*
@ -298,13 +287,13 @@ if (DEBUGGER) {
* 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)));
a.push(Str.trim(sCmd.substring(iPrev, i)));
iPrev = i + 1;
}
}
}
return a;
};
}
/**
* evalExpression(aVals, aOps, cOps)
@ -327,8 +316,7 @@ if (DEBUGGER) {
* @param {number} [cOps] (default is all)
* @return {boolean} true if successful, false if error
*/
Debugger.prototype.evalExpression = function(aVals, aOps, cOps)
{
evalExpression(aVals, aOps, cOps) {
cOps = cOps || -1;
while (cOps-- && aOps.length) {
var chOp = aOps.pop();
@ -402,7 +390,7 @@ if (DEBUGGER) {
aVals.push(valNew|0);
}
return true;
};
}
/**
* parseExpression(sExp, fPrint)
@ -440,8 +428,7 @@ if (DEBUGGER) {
* @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)
{
parseExpression(sExp, fPrint) {
var value;
if (sExp) {
@ -468,7 +455,7 @@ if (DEBUGGER) {
while (i < asValues.length) {
var sValue = asValues[i++];
var cchValue = sValue.length;
var s = str.trim(sValue);
var s = Str.trim(sValue);
if (!s) {
fError = true;
break;
@ -500,7 +487,7 @@ if (DEBUGGER) {
}
}
return value;
};
}
/**
* parseReference(s)
@ -514,8 +501,7 @@ if (DEBUGGER) {
* @param {string} s
* @return {string}
*/
Debugger.prototype.parseReference = function(s)
{
parseReference(s) {
var a;
var chOpen = this.fParens? '(' : '{';
var chClose = this.fParens? ')' : '}';
@ -530,7 +516,7 @@ if (DEBUGGER) {
s = this.parseAddrReference(s, a[1]);
}
return this.parseSysVars(s);
};
}
/**
* parseSysVars(s)
@ -543,8 +529,7 @@ if (DEBUGGER) {
* @param {string} s
* @return {string}
*/
Debugger.prototype.parseSysVars = function(s)
{
parseSysVars(s) {
var a;
while (a = s.match(/\$([a-z]+)/i)) {
var v = null;
@ -557,7 +542,7 @@ if (DEBUGGER) {
s = s.replace(a[0], v.toString());
}
return s;
};
}
/**
* parseValue(sValue, sName, fQuiet)
@ -568,8 +553,7 @@ if (DEBUGGER) {
* @param {boolean} [fQuiet]
* @return {number|undefined} numeric value, or undefined if sValue is either undefined or invalid
*/
Debugger.prototype.parseValue = function(sValue, sName, fQuiet)
{
parseValue(sValue, sName, fQuiet) {
var value;
if (sValue != null) {
var iReg = this.getRegIndex(sValue);
@ -578,7 +562,7 @@ if (DEBUGGER) {
} else {
value = this.getVariable(sValue);
if (value == null) {
value = str.parseInt(sValue, this.nBase);
value = Str.parseInt(sValue, this.nBase);
}
}
if (value == null && !fQuiet) this.println("invalid " + (sName? sName : "value") + ": " + sValue);
@ -586,7 +570,7 @@ if (DEBUGGER) {
if (!fQuiet) this.println("missing " + (sName || "value"));
}
return value;
};
}
/**
* printValue(sVar, value)
@ -596,13 +580,12 @@ if (DEBUGGER) {
* @param {number|undefined} value
* @return {boolean} true if value defined, false if not
*/
Debugger.prototype.printValue = function(sVar, value)
{
printValue(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, 4, true);
sValue = Str.toHex(value, 0, true) + " " + value + ". " + Str.toOct(value, 0, true) + " " + Str.toBinBytes(value, 4, true);
if (value >= 0x20 && value < 0x7F) {
sValue += " '" + String.fromCharCode(value) + "'";
}
@ -610,7 +593,7 @@ if (DEBUGGER) {
sVar = (sVar != null? (sVar + ": ") : "");
this.println(sVar + sValue);
return fDefined;
};
}
/**
* printVariable(sVar)
@ -619,8 +602,7 @@ if (DEBUGGER) {
* @param {string} [sVar]
* @return {boolean} true if all value(s) defined, false if not
*/
Debugger.prototype.printVariable = function(sVar)
{
printVariable(sVar) {
if (sVar) {
return this.printValue(sVar, this.aVariables[sVar]);
}
@ -630,7 +612,7 @@ if (DEBUGGER) {
cVariables++;
}
return cVariables > 0;
};
}
/**
* delVariable(sVar)
@ -638,10 +620,9 @@ if (DEBUGGER) {
* @this {Debugger}
* @param {string} sVar
*/
Debugger.prototype.delVariable = function(sVar)
{
delVariable(sVar) {
delete this.aVariables[sVar];
};
}
/**
* getVariable(sVar)
@ -650,10 +631,9 @@ if (DEBUGGER) {
* @param {string} sVar
* @return {number|undefined}
*/
Debugger.prototype.getVariable = function(sVar)
{
getVariable(sVar) {
return this.aVariables[sVar];
};
}
/**
* setVariable(sVar, value)
@ -662,15 +642,14 @@ if (DEBUGGER) {
* @param {string} sVar
* @param {number} value
*/
Debugger.prototype.setVariable = function(sVar, value)
{
setVariable(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.
* 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
@ -678,22 +657,46 @@ if (DEBUGGER) {
* @param {boolean} [fStripLeadingZeros]
* @return {string}
*/
Debugger.prototype.toStrBase = function(n, nBytes, fStripLeadingZeros)
{
toStrBase(n, nBytes, fStripLeadingZeros) {
var s;
switch(this.nBase) {
case 8:
s = str.toOct(n, nBytes * 3 - (nBytes > 2? 1 : 0));
s = Str.toOct(n, nBytes * 3 - (nBytes > 2? 1 : 0));
break;
case 10:
s = n.toString();
break;
case 16:
default:
s = str.toHex(n, nBytes * 2);
s = Str.toHex(n, nBytes * 2);
break;
}
return (fStripLeadingZeros? str.stripLeadingZeros(s) : s);
return (fStripLeadingZeros? Str.stripLeadingZeros(s) : s);
}
}
if (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
};
} // endif DEBUGGER

View file

@ -32,9 +32,13 @@
* @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-2017 Jeff Parsons <Jeff@pcjs.org>";
var LICENSE = "License: GPL version 3 or later <http://gnu.org/licenses/gpl.html>";
var CSSCLASS = "pcjs";
/**

View file

@ -69,36 +69,33 @@ var DiskAPI = {
};
/*
* Common (supported) disk formats
* Common (supported) diskette formats
*
* Each entry in DISK_FORMATS begins with an array of three "CHS" values:
* For no particular reason that I can recall, each entry in DISK_FORMATS is an array of values in "CHS" order:
*
* [# cylinders, # heads, # sectors/track, # bytes/sector, media type]
*
* The 4th value is optional; if omitted, the sector size is assumed to be 512 bytes. The order of these geometric
* values mirrors the structure of our JSON-encoded disk images, which consist of an array of cylinders, each of which
* is an array of heads, each of which is an array of sector objects.
*
* The 5th value is also optional and is used only with PC-DOS diskettes, to help us verify that the logical format
* matches the physical format; it should be one of the values in DiskAPI.FAT. TODO: Actually use the DiskAPI.FAT values.
* If the 4th value is omitted, the sector size is assumed to be 512. The order of these "geometric" values mirrors
* the structure of our JSON-encoded disk images, which consist of an array of cylinders, each of which is an array of
* heads, each of which is an array of sector objects.
*/
DiskAPI.DISK_FORMATS = {
163840: [ 40,1, 8,,0xFE], // media type 0xFE: 40 cylinders, 1 head (single-sided), 8 sectors/track, ( 320 total sectors x 512 bytes/sector == 163840)
184320: [ 40,1, 9,,0xFC], // media type 0xFC: 40 cylinders, 1 head (single-sided), 9 sectors/track, ( 360 total sectors x 512 bytes/sector == 184320)
327680: [ 40,2, 8,,0xFF], // media type 0xFF: 40 cylinders, 2 heads (double-sided), 8 sectors/track, ( 640 total sectors x 512 bytes/sector == 327680)
368640: [ 40,2, 9,,0xFD], // media type 0xFD: 40 cylinders, 2 heads (double-sided), 9 sectors/track, ( 720 total sectors x 512 bytes/sector == 368640)
737280: [ 80,2, 9,,0xF9], // media type 0xF9: 80 cylinders, 2 heads (double-sided), 9 sectors/track, (1440 total sectors x 512 bytes/sector == 737280)
1228800: [ 80,2,15,,0xF9], // media type 0xF9: 80 cylinders, 2 heads (double-sided), 15 sectors/track, (2400 total sectors x 512 bytes/sector == 1228800)
1474560: [ 80,2,18,,0xF0], // media type 0xF0: 80 cylinders, 2 heads (double-sided), 18 sectors/track, (2880 total sectors x 512 bytes/sector == 1474560)
2949120: [ 80,2,36,,0xF0], // media type 0xF0: 80 cylinders, 2 heads (double-sided), 36 sectors/track, (5760 total sectors x 512 bytes/sector == 2949120)
163840: [40,1,8,,0xFE], // media type 0xFE: 40 cylinders, 1 head (single-sided), 8 sectors/track, ( 320 total sectors x 512 bytes/sector == 163840)
184320: [40,1,9,,0xFC], // media type 0xFC: 40 cylinders, 1 head (single-sided), 9 sectors/track, ( 360 total sectors x 512 bytes/sector == 184320)
327680: [40,2,8,,0xFF], // media type 0xFF: 40 cylinders, 2 heads (double-sided), 8 sectors/track, ( 640 total sectors x 512 bytes/sector == 327680)
368640: [40,2,9,,0xFD], // media type 0xFD: 40 cylinders, 2 heads (double-sided), 9 sectors/track, ( 720 total sectors x 512 bytes/sector == 368640)
737280: [80,2,9,,0xF9], // media type 0xF9: 80 cylinders, 2 heads (double-sided), 9 sectors/track, (1440 total sectors x 512 bytes/sector == 737280)
1228800: [80,2,15,,0xF9], // media type 0xF9: 80 cylinders, 2 heads (double-sided), 15 sectors/track, (2400 total sectors x 512 bytes/sector == 1228800)
1474560: [80,2,18,,0xF0], // media type 0xF0: 80 cylinders, 2 heads (double-sided), 18 sectors/track, (2880 total sectors x 512 bytes/sector == 1474560)
2949120: [80,2,36,,0xF0], // media type 0xF0: 80 cylinders, 2 heads (double-sided), 36 sectors/track, (5760 total sectors x 512 bytes/sector == 2949120)
/*
* The following are common early hard drive sizes, which we explicitly map to CHS values, since the BPB can mislead us when attempting to calculate total cylinders.
* The following are common early hard drive sizes, which we explicitly map to CHS values, since the BPB can mislead us when attempting to calculate total cylinders
*/
21368320:[615,4,17], // PC AT 20Mb hard drive (type 2)
/*
* Assorted DEC disk pack formats.
*/
2494464: [203,2,12,512], // RK03 single-platter disk cartridge: 203 tracks, 2 heads, 12 sectors/track, 512 bytes/sector, for a total of 2494464 bytes
2494464: [203,2,12,512], // RK03 single-platter disk cartridge: 203 tracks, 2 heads, 12 sectors/track, 512 bytes/sector, for a total of 2494464 bytes
5242880: [256,2,40,256], // RL01K single-platter disk cartridge: 256 tracks, 2 heads, 40 sectors/track, 256 bytes/sector, for a total of 5242880 bytes
10485760:[512,2,40,256] // RL02K single-platter disk cartridge: 512 tracks, 2 heads, 40 sectors/track, 256 bytes/sector, for a total of 10485760 bytes
};

View file

@ -28,12 +28,10 @@
"use strict";
/* global document: true, window: true, XSLTProcessor: false, APPNAME: false, APPVERSION: false, XMLVERSION: true, DEBUG: true */
if (NODE) {
var Component = require("./component");
var str = require("./strlib");
var web = require("./weblib");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/*
@ -45,8 +43,8 @@ if (NODE) {
* machine component init() handlers.
*
* Also, to prevent those init() handlers from running prematurely, we must disable all page
* notification events at the start of the embedding process (web.enablePageEvents(false)) and
* re-enable them at the end (web.enablePageEvents(true)).
* notification events at the start of the embedding process (Web.enablePageEvents(false)) and
* re-enable them at the end (Web.enablePageEvents(true)).
*/
var fAsync = true;
var cAsyncMachines = 0;
@ -95,7 +93,7 @@ function loadXML(sXMLFile, idMachine, sAppName, sAppClass, sParms, fResolve, dis
parseXML(sXML, sXMLFile, idMachine, sAppName, sAppClass, sParms, fResolve, display, done);
};
display("Loading " + sXMLFile + "...");
web.getResource(sXMLFile, null, fAsync, doneLoadXML);
Web.getResource(sXMLFile, null, fAsync, doneLoadXML);
}
/**
@ -171,9 +169,11 @@ function parseXML(sXML, sXMLFile, idMachine, sAppName, sAppClass, sParms, fResol
/*
* Non-COMPILED kludge to replace the version number template in the XSL file (which we assume we're reading,
* since fResolve is false) with whatever XMLVERSION we extracted from the XML file (see corresponding kludge below).
*
* ES6 ALERT: Template strings.
*/
if (!COMPILED && XMLVERSION) {
sXML = sXML.replace(/<xsl:variable name="APPVERSION">1.x.x<\/xsl:variable>/, '<xsl:variable name="APPVERSION">' + XMLVERSION + '</xsl:variable>');
sXML = sXML.replace(/<xsl:variable name="APPVERSION">1.x.x<\/xsl:variable>/, `<xsl:variable name="APPVERSION">${XMLVERSION}</xsl:variable>`);
}
}
@ -188,7 +188,7 @@ function parseXML(sXML, sXMLFile, idMachine, sAppName, sAppClass, sParms, fResol
* Supposedly, the IE XML DOM parser will throw an exception, but I haven't tested that, and unless all other
* browsers do that, that's not helpful.
*
* The best I can do at this stage (assuming web.getResource() didn't drop any error information on the floor)
* The best I can do at this stage (assuming Web.getResource() didn't drop any error information on the floor)
* is verify that the requested resource "looks like" valid XML (in other words, it begins with a '<').
*/
var xmlDoc = null;
@ -320,7 +320,7 @@ function resolveXML(sXML, display, done)
};
display("Loading " + sRefFile + "...");
web.getResource(sRefFile, null, fAsync, doneReadXML);
Web.getResource(sRefFile, null, fAsync, doneReadXML);
return;
}
done(sXML, null);
@ -350,7 +350,7 @@ function embedMachine(sAppName, sAppClass, sVersion, idMachine, sXMLFile, sXSLFi
var doneMachine = function() {
Component.assert(cAsyncMachines > 0);
if (!--cAsyncMachines) {
if (fAsync) web.enablePageEvents(true);
if (fAsync) Web.enablePageEvents(true);
}
};
@ -378,7 +378,7 @@ function embedMachine(sAppName, sAppClass, sVersion, idMachine, sXMLFile, sXSLFi
var aeWarning = (eMachine && Component.getElementsByClass(eMachine, "machine-warning"));
eWarning = (aeWarning && aeWarning[0]) || eMachine;
}
if (eWarning) eWarning.innerHTML = str.escapeHTML(sMessage);
if (eWarning) eWarning.innerHTML = Str.escapeHTML(sMessage);
};
try {
@ -545,7 +545,7 @@ function embedMachine(sAppName, sAppClass, sVersion, idMachine, sXMLFile, sXSLFi
*/
function embedC1P(idMachine, sXMLFile, sXSLFile)
{
if (fAsync) web.enablePageEvents(false);
if (fAsync) Web.enablePageEvents(false);
return embedMachine("C1Pjs", "c1pjs", APPVERSION, idMachine, sXMLFile, sXSLFile);
}
@ -560,7 +560,7 @@ function embedC1P(idMachine, sXMLFile, sXSLFile)
*/
function embedPCx86(idMachine, sXMLFile, sXSLFile, sParms)
{
if (fAsync) web.enablePageEvents(false);
if (fAsync) Web.enablePageEvents(false);
return embedMachine("PCx86", "pcx86", APPVERSION, idMachine, sXMLFile, sXSLFile, sParms);
}
@ -575,7 +575,7 @@ function embedPCx86(idMachine, sXMLFile, sXSLFile, sParms)
*/
function embedPC8080(idMachine, sXMLFile, sXSLFile, sParms)
{
if (fAsync) web.enablePageEvents(false);
if (fAsync) Web.enablePageEvents(false);
return embedMachine("PC8080", "pc8080", APPVERSION, idMachine, sXMLFile, sXSLFile, sParms);
}
@ -590,10 +590,22 @@ function embedPC8080(idMachine, sXMLFile, sXSLFile, sParms)
*/
function embedPDP11(idMachine, sXMLFile, sXSLFile, sParms)
{
if (fAsync) web.enablePageEvents(false);
if (fAsync) Web.enablePageEvents(false);
return embedMachine("PDPjs", "pdp11", APPVERSION, idMachine, sXMLFile, sXSLFile, sParms);
}
/**
* findMachineComponent(idMachine, sType)
*
* @param {string} idMachine
* @param {string} sType
* @return {Component|null}
*/
function findMachineComponent(idMachine, sType)
{
return Component.getComponentByType(sType, idMachine + ".machine");
}
/**
* Prevent the Closure Compiler from renaming functions we want to export, by adding them as global properties.
*/
@ -611,5 +623,7 @@ if (APPNAME == "PDPjs") {
window['embedPDP11'] = embedPDP11;
}
window['enableEvents'] = web.enablePageEvents;
window['sendEvent'] = web.sendPageEvent;
window['findMachineComponent'] = findMachineComponent;
window['enableEvents'] = Web.enablePageEvents;
window['sendEvent'] = Web.sendPageEvent;

View file

@ -32,7 +32,7 @@ var Keys = {
/*
* Keys and/or key combinations that generate common ASCII codes.
*
* NOTE: If you're looking for a general-purpose ASCII code table, see str.ASCII in strlib.js;
* NOTE: If you're looking for a general-purpose ASCII code table, see Str.ASCII in strlib.js;
* if something's missing, that's probably the more appropriate table to add it to.
*
* TODO: The Closure Compiler doesn't inline all references to these values, at least those with
@ -201,6 +201,7 @@ var Keys = {
*/
Keys.KEYCODE.NUM_CR = Keys.KEYCODE.CR + Keys.KEYCODE.ONRIGHT;
/*
* Maps Firefox keyCodes to their more common keyCode counterparts; a number of entries in this table
* are no longer valid (if indeed they ever were), so they've been commented out. It's likely that I

View file

@ -28,18 +28,345 @@
"use strict";
/* global Buffer: false */
var net = {};
if (NODE) {
var sServerRoot;
var fs = require("fs");
var http = require("http");
var path = require("path");
var url = require("url");
}
class Net {
/**
* hasParm(sParm, sValue, req)
*
* @param {string} sParm
* @param {string|null} sValue (pass null to check for the presence of ANY sParm)
* @param {Object} [req] is the web server's (ie, Express) request object, if any
* @return {boolean} true if the request Object contains the specified parameter/value, false if not
*
* TODO: Consider whether sParm === null should check for the presence of ANY parameter in Net.asPropagate.
*/
static hasParm(sParm, sValue, req)
{
return (req && req.query && req.query[sParm] && (!sValue || req.query[sParm] == sValue));
}
/**
* propagateParms(sURL, req)
*
* Propagates any "special" query parameters (as listed in Net.asPropagate) from the given
* request object (req) to the given URL (sURL).
*
* We do not modify an sURL that already contains a '?' OR that begins with a protocol
* (eg, http:, mailto:, etc), in order to keep this function simple, since it's only for
* debugging purposes anyway. I also considered blowing off any URLs with a '#' for the
* same reason, since any hash string must follow any query parameters, but stripping
* and re-appending the hash string is pretty trivial, so we do handle that.
*
* TODO: Make propagateParms() more general-purpose (eg, capable of detecting any URL
* to the same site, and capable of merging any of our "special" query parameters with any
* existing query parameters.
*
* @param {string|null} sURL
* @param {Object} [req] is the web server's (ie, Express) request object, if any
* @return {string} massaged sURL
*/
static propagateParms(sURL, req)
{
if (sURL !== null && sURL.indexOf('?') < 0) {
var i;
var sHash = "";
if ((i = sURL.indexOf('#')) >= 0) {
sHash = sURL.substr(i);
sURL = sURL.substr(0, i);
}
var match = sURL.match(/^([a-z])+:(.*)/);
if (!match && req && req.query) {
for (i = 0; i < Net.asPropagate.length; i++) {
var sQuery = Net.asPropagate[i];
var sValue;
if ((sValue = req.query[sQuery])) {
var sParm = (sURL.indexOf('?') < 0 ? '?' : '&');
sParm += sQuery + '=';
if (sURL.indexOf(sParm) < 0) sURL += sParm + encodeURIComponent(sValue);
}
}
}
sURL += sHash;
}
return sURL;
}
/**
* encodeURL(sURL, req, fDebug)
*
* 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) 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
* @param {boolean} [fDebug]
* @return {string} encoded URL
*/
static encodeURL(sURL, req, fDebug)
{
if (sURL) {
sURL = sURL.replace(/\\/g, '/');
if (!fDebug) {
if (sURL.match(/^[^:?]*archive\//)) {
if (sURL.charAt(0) != '/') sURL = path.join(req.path, sURL);
sURL = "http://archive.pcjs.org" + sURL.replace("/archive/", "/");
}
}
/*
* 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;
}
/**
* isRemote(sPath)
*
* TODO: Add support for FTP? HTTPS? Anything else?
*
* @param {string} sPath
* @return {boolean} true if sPath is a (supported) remote path, false if not
*/
static isRemote(sPath)
{
return (sPath.indexOf("http:") === 0);
}
/**
* getStat(sURL, done)
*
* @param {string} sURL
* @param {function(Error,Object)} done
*/
static getStat(sURL, done)
{
var options = url.parse(sURL);
options.method = "HEAD";
options.path = options.pathname; // TODO: Determine the necessity of aliasing this
var req = http.request(options, function(res)
{
var err = null;
var stat = null;
// console.log(JSON.stringify(res.headers));
if (res.statusCode == 200) {
/*
* Apparently Node lower-cases response headers (at least incoming headers, despite
* lots of amusing whining by certain people in the Node community), which seems like
* a good thing, because that means I can do two simple key look-ups.
*/
var sLength = res.headers['content-length'];
var sModified = res.headers['last-modified'];
stat = {
size: sLength ? parseInt(sLength, 10) : -1,
mtime: sModified ? new Date(sModified) : null,
remote: true // an additional property we provide to indicate this is not your normal stats object
};
} else {
err = new Error("unexpected response code: " + res.statusCode);
}
done(err, stat);
});
req.on('error', function(err)
{
done(err, null);
});
req.end();
}
/**
* getFile(sURL, sEncoding, done)
*
* TODO: Add support for FTP? HTTPS? Anything else?
*
* @param {string} sURL is the source file
* @param {string|null} sEncoding is the encoding to assume, if any
* @param {function(Error,number,(string|Buffer))} done receives an Error, an HTTP status code, and a Buffer (if any)
*/
static getFile(sURL, sEncoding, done)
{
/*
* Buffer objects are a fixed size, so my choices are: 1) call getStat() first, hope it returns
* the true size, and then preallocate a buffer; or 2) create a new, larger buffer every time a new
* chunk arrives. The latter seems best.
*
* However, if an encoding is given, we'll simply concatenate all the data into a String and return
* that instead. Note that the incoming data is always a Buffer, but concatenation with a String
* performs an implied "toString()" on the Buffer.
*
* WARNING: Even when an encoding is provided, we don't make any attempt to verify that the incoming
* data matches that encoding.
*/
var sFile = "";
var bufFile = null;
http.get(sURL, function(res)
{
res.on('data', function(data)
{
if (sEncoding) {
sFile += data;
return;
}
if (!bufFile) {
bufFile = data;
return;
}
/*
* We need to grow bufFile. I used to do this myself, using the "copy" method:
*
* buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd])
*
* which defaults to 0 for [targetStart] and [sourceStart], but the docs don't clearly
* define the default value for [sourceEnd]. They say "buffer.length", but there is no
* parameter here named "buffer". Let's hope that in the case of "bufFile.copy(buf)"
* they meant "bufFile.length".
*
* However, it turns out this is moot, because there's a new kid in town: Buffer.concat().
*
* buf = new Buffer(bufFile.length + data.length);
* bufFile.copy(buf);
* data.copy(buf, bufFile.length);
* bufFile = buf;
*/
bufFile = Buffer.concat([bufFile, data], bufFile.length + data.length);
}).on('end', function()
{
/*
* TODO: Decide what to do when res.statusCode is actually an error code (eg, 404), because
* in such cases, the file content will likely just be an HTML error page.
*/
if (res.statusCode < 400) {
done(null, res.statusCode, sEncoding? sFile : bufFile);
} else {
done(new Error(sEncoding? sFile : bufFile), res.statusCode, null);
}
}).on('error', function(err)
{
done(err, res.statusCode, null);
});
});
}
/**
* downloadFile(sURL, sFile, done)
*
* @param {string} sURL is the source file
* @param {string} sFile is a fully-qualified target file
* @param {function(Error,number)} done is a callback that receives an Error and a HTTP status code
*/
static downloadFile(sURL, sFile, done)
{
var file = fs.createWriteStream(sFile);
/*
* http.get() accepts a "url" string in lieu of an "options" object; it automatically builds
* the latter from the former using url.parse(). This is good, because it relieves me from
* building my own "options" object, and also from wondering why http functions expect "options"
* to contain a "path" property, whereas url.parse() returns a "pathname" property.
*
* Either the documentation isn't quite right for url.parse() or http.request() (the big brother
* of http.get), or one of those "options" properties is aliased to the other, or...?
*/
http.get(sURL, function(res)
{
res.on('data', function(data)
{
file.write(data);
}).on('end', function()
{
file.end();
/*
* TODO: We should try to update the file's modification time to match the 'last-modified'
* response header value, if any.
*
* TODO: Decide what to do when res.statusCode is actually an error code (eg, 404), because
* in such cases, the file content will likely just be an HTML error page.
*/
done(null, res.statusCode);
}).on('error', function(err)
{
done(err, res.statusCode);
});
});
}
/**
* getResource(sURL, dataPost, fAsync, done)
*
* Request the specified resource (sURL), and once the request is complete, notify done().
*
* @param {string} sURL
* @param {Object|null} [dataPost] for a POST request (default is a GET request)
* @param {boolean} [fAsync] is true for an asynchronous request
* @param {function(string,string|null,number)} [done]
* @return {Array|null} Array containing [sResource, nErrorCode], or null if no response yet
*/
static getResource(sURL, dataPost, fAsync, done)
{
var nErrorCode = -1, sResource = null, response = null;
if (Net.isRemote(sURL)) {
/*
* TODO: This code is nothing more than a band-aid. It assumes the URL uses "http:"
* (hence the call to getFile(), which only supports HTTP GET operations), it assumes
* the requested data is UTF-8 string data (which is normally the case, because nearly
* all our requests are for JSON files), it doesn't deal with dataPost, it assumes
* that fAsync is true, and it performs very simplistic error code mapping.
*
* But, it gets the job done for what little we actually ask of it, when our machines
* are running in the Node environment.
*/
Net.getFile(sURL, "utf8", function(err, status, data) {
if (done) done(sURL, data, err? status : 0);
});
} else {
if (!Net.sServerRoot) {
Net.sServerRoot = path.join(path.dirname(fs.realpathSync(__filename)), "../../../");
}
var sFile = path.join(Net.sServerRoot, sURL);
if (fAsync) {
fs.readFile(sFile, {encoding: "utf8"}, function(err, s)
{
/*
* TODO: If err is set, is there an error code we should return (instead of -1)?
*/
if (!err) {
sResource = s;
nErrorCode = 0;
}
if (done) done(sURL, sResource, nErrorCode);
});
} else {
try {
sResource = fs.readFileSync(sFile, {encoding: "utf8"});
nErrorCode = 0;
} catch (err) {
/*
* TODO: If err is set, is there an error code we should return (instead of -1)?
*/
console.log(err.message);
}
if (done) done(sURL, sResource, nErrorCode);
response = [sResource, nErrorCode];
}
}
return response;
}
}
/*
* The following are (super-secret) commands that can be added to the URL to enable special features.
*
@ -49,342 +376,24 @@ if (NODE) {
* http://www.pcjs.org/?gort=debug
*
* hasParm() detects the presence of the specified command, and propagateParms() is a URL filter that ensures
* any commands listed in asPropagate are passed through to all other URLs on the same page; using any of these
* any commands listed in Net.asPropagate are passed through to all other URLs on the same page; using any of these
* commands also forces the page to be rebuilt and not cached (since we would never want a cached "index.html" to
* contain/expose any of these commands).
*/
net.GORT_COMMAND = "gort";
net.GORT_DEBUG = "debug"; // use this to force uncompiled JavaScript even on a Release server
net.GORT_NODEBUG = "nodebug"; // use this to force uncompiled JavaScript but with DEBUG code disabled
net.GORT_RELEASE = "release"; // use this to force the use of compiled JavaScript even on a Debug server
net.GORT_REBUILD = "rebuild"; // use this to force the "index.html" in the current directory to be rebuilt
Net.GORT_COMMAND = "gort";
Net.GORT_DEBUG = "debug"; // use this to force uncompiled JavaScript even on a Release server
Net.GORT_NODEBUG = "nodebug"; // use this to force uncompiled JavaScript but with DEBUG code disabled
Net.GORT_RELEASE = "release"; // use this to force the use of compiled JavaScript even on a Debug server
Net.GORT_REBUILD = "rebuild"; // use this to force the "index.html" in the current directory to be rebuilt
net.REVEAL_COMMAND = "reveal";
net.REVEAL_PDFS = "pdfs";
Net.REVEAL_COMMAND = "reveal";
Net.REVEAL_PDFS = "pdfs";
/*
* This is a list of the URL parameters that propagateParms() will propagate from the requester's URL to
* other URLs provided by the requester.
*/
var asPropagate = [net.GORT_COMMAND, "autostart"];
Net.asPropagate = [Net.GORT_COMMAND, "autostart"];
Net.sServerRoot = null;
/**
* hasParm(sParm, sValue, req)
*
* @param {string} sParm
* @param {string|null} sValue (pass null to check for the presence of ANY sParm)
* @param {Object} [req] is the web server's (ie, Express) request object, if any
* @return {boolean} true if the request Object contains the specified parameter/value, false if not
*
* TODO: Consider whether sParm === null should check for the presence of ANY parameter in asPropagate.
*/
net.hasParm = function(sParm, sValue, req)
{
return (req && req.query && req.query[sParm] && (!sValue || req.query[sParm] == sValue));
};
/**
* propagateParms(sURL, req)
*
* Propagates any "special" query parameters (as listed in asPropagate) from the given
* request object (req) to the given URL (sURL).
*
* We do not modify an sURL that already contains a '?' OR that begins with a protocol
* (eg, http:, mailto:, etc), in order to keep this function simple, since it's only for
* debugging purposes anyway. I also considered blowing off any URLs with a '#' for the
* same reason, since any hash string must follow any query parameters, but stripping
* and re-appending the hash string is pretty trivial, so we do handle that.
*
* TODO: Make propagateParms() more general-purpose (eg, capable of detecting any URL
* to the same site, and capable of merging any of our "special" query parameters with any
* existing query parameters.
*
* @param {string|null} sURL
* @param {Object} [req] is the web server's (ie, Express) request object, if any
* @return {string} massaged sURL
*/
net.propagateParms = function(sURL, req)
{
if (sURL !== null && sURL.indexOf('?') < 0) {
var i;
var sHash = "";
if ((i = sURL.indexOf('#')) >= 0) {
sHash = sURL.substr(i);
sURL = sURL.substr(0, i);
}
var match = sURL.match(/^([a-z])+:(.*)/);
if (!match && req && req.query) {
for (i = 0; i < asPropagate.length; i++) {
var sQuery = asPropagate[i];
var sValue;
if ((sValue = req.query[sQuery])) {
var sParm = (sURL.indexOf('?') < 0? '?' : '&');
sParm += sQuery + '=';
if (sURL.indexOf(sParm) < 0) sURL += sParm + encodeURIComponent(sValue);
}
}
}
sURL += sHash;
}
return sURL;
};
/**
* encodeURL(sURL, req, fDebug)
*
* 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) 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
* @param {boolean} [fDebug]
* @return {string} encoded URL
*/
net.encodeURL = function(sURL, req, fDebug)
{
if (sURL) {
sURL = sURL.replace(/\\/g, '/');
if (!fDebug) {
if (sURL.match(/^[^:?]*archive\//)) {
if (sURL.charAt(0) != '/') sURL = path.join(req.path, sURL);
sURL = "http://archive.pcjs.org" + sURL.replace("/archive/", "/");
}
}
/*
* 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;
};
/**
* isRemote(sPath)
*
* @param {string} sPath
* @return {boolean} true if sPath is a (supported) remote path, false if not
*
* TODO: Add support for FTP? HTTPS? Anything else?
*/
net.isRemote = function(sPath)
{
return (sPath.indexOf("http:") === 0);
};
/**
* getStat(sURL, done)
*
* @param {string} sURL
* @param {function(Error,Object)} done
*/
net.getStat = function(sURL, done)
{
var options = url.parse(sURL);
options.method = "HEAD";
options.path = options.pathname; // TODO: Determine the necessity of aliasing this
var req = http.request(options, function(res) {
var err = null;
var stat = null;
// console.log(JSON.stringify(res.headers));
if (res.statusCode == 200) {
/*
* Apparently Node lower-cases response headers (at least incoming headers, despite
* lots of amusing whining by certain people in the Node community), which seems like
* a good thing, because that means I can do two simple key look-ups.
*/
var sLength = res.headers['content-length'];
var sModified = res.headers['last-modified'];
stat = {
size: sLength? parseInt(sLength, 10) : -1,
mtime: sModified? new Date(sModified) : null,
remote: true // an additional property we provide to indicate this is not your normal stats object
};
} else {
err = new Error("unexpected response code: " + res.statusCode);
}
done(err, stat);
});
req.on('error', function(err) {
done(err, null);
});
req.end();
};
/**
* getFile(sURL, sEncoding, done)
*
* @param {string} sURL is the source file
* @param {string|null} sEncoding is the encoding to assume, if any
* @param {function(Error,number,(string|Buffer))} done receives an Error, an HTTP status code, and a Buffer (if any)
*
* TODO: Add support for FTP? HTTPS? Anything else?
*/
net.getFile = function(sURL, sEncoding, done)
{
/*
* Buffer objects are a fixed size, so my choices are: 1) call getStat() first, hope it returns
* the true size, and then preallocate a buffer; or 2) create a new, larger buffer every time a new
* chunk arrives. The latter seems best.
*
* However, if an encoding is given, we'll simply concatenate all the data into a String and return
* that instead. Note that the incoming data is always a Buffer, but concatenation with a String
* performs an implied "toString()" on the Buffer.
*
* WARNING: Even when an encoding is provided, we don't make any attempt to verify that the incoming
* data matches that encoding.
*/
var sFile = "";
var bufFile = null;
http.get(sURL, function(res) {
res.on('data', function(data) {
if (sEncoding) {
sFile += data;
return;
}
if (!bufFile) {
bufFile = data;
return;
}
/*
* We need to grow bufFile. I used to do this myself, using the "copy" method:
*
* buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd])
*
* which defaults to 0 for [targetStart] and [sourceStart], but the docs don't clearly
* define the default value for [sourceEnd]. They say "buffer.length", but there is no
* parameter here named "buffer". Let's hope that in the case of "bufFile.copy(buf)"
* they meant "bufFile.length".
*
* However, it turns out this is moot, because there's a new kid in town: Buffer.concat().
*
* buf = new Buffer(bufFile.length + data.length);
* bufFile.copy(buf);
* data.copy(buf, bufFile.length);
* bufFile = buf;
*/
bufFile = Buffer.concat([bufFile, data], bufFile.length + data.length);
}).on('end', function() {
/*
* TODO: Decide what to do when res.statusCode is actually an error code (eg, 404), because
* in such cases, the file content will likely just be an HTML error page.
*/
if (res.statusCode < 400) {
done(null, res.statusCode, sEncoding? sFile : bufFile);
} else {
done(new Error(sEncoding? sFile : bufFile), res.statusCode, null);
}
}).on('error', function(err) {
done(err, res.statusCode, null);
});
});
};
/**
* downloadFile(sURL, sFile, done)
*
* @param {string} sURL is the source file
* @param {string} sFile is a fully-qualified target file
* @param {function(Error,number)} done is a callback that receives an Error and a HTTP status code
*/
net.downloadFile = function(sURL, sFile, done)
{
var file = fs.createWriteStream(sFile);
/*
* http.get() accepts a "url" string in lieu of an "options" object; it automatically builds
* the latter from the former using url.parse(). This is good, because it relieves me from
* building my own "options" object, and also from wondering why http functions expect "options"
* to contain a "path" property, whereas url.parse() returns a "pathname" property.
*
* Either the documentation isn't quite right for url.parse() or http.request() (the big brother
* of http.get), or one of those "options" properties is aliased to the other, or...?
*/
http.get(sURL, function(res) {
res.on('data', function(data) {
file.write(data);
}).on('end', function() {
file.end();
/*
* TODO: We should try to update the file's modification time to match the 'last-modified'
* response header value, if any.
*
* TODO: Decide what to do when res.statusCode is actually an error code (eg, 404), because
* in such cases, the file content will likely just be an HTML error page.
*/
done(null, res.statusCode);
}).on('error', function(err) {
done(err, res.statusCode);
});
});
};
/**
* getResource(sURL, dataPost, fAsync, done)
*
* Request the specified resource (sURL), and once the request is complete, notify done().
*
* @param {string} sURL
* @param {Object|null} [dataPost] for a POST request (default is a GET request)
* @param {boolean} [fAsync] is true for an asynchronous request
* @param {function(string,string,number)} [done]
* @return {Array|null} Array containing [sResource, nErrorCode], or null if no response yet
*/
net.getResource = function(sURL, dataPost, fAsync, done)
{
var nErrorCode = -1, sResource = null, response = null;
if (net.isRemote(sURL)) {
/*
* TODO: This code is nothing more than a band-aid. It assumes the URL uses "http:"
* (hence the call to getFile(), which only supports HTTP GET operations), it assumes
* the requested data is UTF-8 string data (which is normally the case, because nearly
* all our requests are for JSON files), it doesn't deal with dataPost, it assumes
* that fAsync is true, and it performs very simplistic error code mapping.
*
* But, it gets the job done for what little we actually ask of it, when our machines
* are running in the Node environment.
*/
Net.getFile(sURL, "utf8", function(err, status, data) {
if (done) done(sURL, data, err? status : 0);
});
} else {
if (!sServerRoot) {
sServerRoot = path.join(path.dirname(fs.realpathSync(__filename)), "../../../");
}
var sFile = path.join(sServerRoot, sURL);
if (fAsync) {
fs.readFile(sFile, {encoding: "utf8"}, function(err, s) {
/*
* TODO: If err is set, is there an error code we should return (instead of -1)?
*/
if (!err) {
sResource = s;
nErrorCode = 0;
}
if (done) done(sURL, sResource, nErrorCode);
});
} else {
try {
sResource = fs.readFileSync(sFile, {encoding: "utf8"});
nErrorCode = 0;
} catch(err) {
/*
* TODO: If err is set, is there an error code we should return (instead of -1)?
*/
console.log(err.message);
}
if (done) done(sURL, sResource, nErrorCode);
response = [sResource, nErrorCode];
}
}
return response;
};
if (NODE) module.exports = net;
if (NODE) module.exports = Net;

View file

@ -28,8 +28,6 @@
"use strict";
/* global DEBUG: true */
/*
* In the compiled case, we rely on the Closure Compiler to override DEBUG, setting it to false,
* so that all DEBUG-only code will be removed by the compiler.

View file

@ -28,8 +28,6 @@
"use strict";
/* global PRIVATE: true */
/*
* PRIVATE enables certain private features that we don't want enabled on the public web server, so by default,
* PRIVATE is false. For Jekyll configurations, if site.pcjs.private is set, this file gets included, overriding

View file

@ -28,59 +28,60 @@
"use strict";
var proc = {};
/**
* getArgs()
*
* Processes command-line arguments. Arguments may be introduced by either
* a double-hyphen (--) or a long dash (), and argument values, if any, must be
* separated by an "=" without any intervening whitespace. Arguments without
* an explicit value default to true, and any argument appearing more than once
* is automatically converted to an Array.
*
* Single-hyphen (-) arguments are allowed as well; they are treated as a series
* of single-character arguments, each set to true, and any of these arguments
* appearing more than once is discarded.
*
* @return {{argc:number, argv:{}}}
*/
proc.getArgs = function() {
var argc = 0;
var argv = {};
for (var i = 2; i < process.argv.length; i++) {
var j, sSep;
var sArg = process.argv[i];
if (!sArg.indexOf(sSep = "--") || !sArg.indexOf(sSep = "—")) {
sArg = sArg.substr(sSep.length);
var sValue = true;
j = sArg.indexOf("=");
if (j > 0) {
sValue = sArg.substr(j+1);
sArg = sArg.substr(0, j);
sValue = (sValue == "true")? true : ((sValue == "false")? false : sValue);
}
if (argv[sArg] === undefined) {
argc++;
argv[sArg] = sValue;
} else {
// console.log("too many '" + sArg + "' arguments");
if (typeof argv[sArg] == "string") {
argv[sArg] = [argv[sArg]];
class Proc {
/**
* getArgs()
*
* Processes command-line arguments. Arguments may be introduced by either
* a double-hyphen (--) or a long dash (), and argument values, if any, must be
* separated by an "=" without any intervening whitespace. Arguments without
* an explicit value default to true, and any argument appearing more than once
* is automatically converted to an Array.
*
* Single-hyphen (-) arguments are allowed as well; they are treated as a series
* of single-character arguments, each set to true, and any of these arguments
* appearing more than once is discarded.
*
* @return {{argc:number, argv:{}}}
*/
static getArgs()
{
var argc = 0;
var argv = {};
for (var i = 2; i < process.argv.length; i++) {
var j, sSep;
var sArg = process.argv[i];
if (!sArg.indexOf(sSep = "--") || !sArg.indexOf(sSep = "—")) {
sArg = sArg.substr(sSep.length);
var sValue = true;
j = sArg.indexOf("=");
if (j > 0) {
sValue = sArg.substr(j + 1);
sArg = sArg.substr(0, j);
sValue = (sValue == "true") ? true : ((sValue == "false") ? false : sValue);
}
argv[sArg].push(sValue);
}
} else if (!sArg.indexOf("-")) {
for (j = 1; j < sArg.length; j++) {
var ch = sArg.charAt(j);
if (argv[ch] === undefined) {
argv[ch] = true;
if (argv[sArg] === undefined) {
argc++;
argv[sArg] = sValue;
} else {
// console.log("too many '" + sArg + "' arguments");
if (typeof argv[sArg] == "string") {
argv[sArg] = [argv[sArg]];
}
argv[sArg].push(sValue);
}
} else if (!sArg.indexOf("-")) {
for (j = 1; j < sArg.length; j++) {
var ch = sArg.charAt(j);
if (argv[ch] === undefined) {
argv[ch] = true;
argc++;
}
}
}
}
return {argc: argc, argv: argv};
}
return {argc: argc, argv: argv};
};
}
if (NODE) module.exports = proc;
if (NODE) module.exports = Proc;

View file

@ -28,12 +28,10 @@
"use strict";
/* global window: true, APPVERSION: false, XMLVERSION: true, DEBUG: true */
if (NODE) {
var Component = require("./component");
var str = require("./strlib");
var web = require("./weblib");
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/**
@ -59,12 +57,12 @@ function savePC(idMachine, sPCJSFile, callback)
}
}
if (callback && callback({ state: sState, parms: sParms })) return true;
web.getResource(sPCJSFile, null, true, function(sURL, sResponse, nErrorCode) {
downloadCSS(sURL, sResponse, nErrorCode, [idMachine, str.getBaseName(sPCJSFile, true), sParms, sState]);
Web.getResource(sPCJSFile, null, true, function(sURL, sResponse, nErrorCode) {
downloadCSS(sURL, sResponse, nErrorCode, [idMachine, Str.getBaseName(sPCJSFile, true), sParms, sState]);
});
return true;
}
web.alertUser("Unable to identify machine '" + idMachine + "'");
Component.alertUser("Unable to identify machine '" + idMachine + "'");
return false;
}
@ -83,7 +81,7 @@ function downloadCSS(sURL, sPCJS, nErrorCode, aMachineInfo)
var res = Component.getMachineResources(aMachineInfo[0]);
var sCSSFile = null;
for (var sName in res) {
if (str.endsWith(sName, "components.xsl")) {
if (Str.endsWith(sName, "components.xsl")) {
sCSSFile = sName.replace(".xsl", ".css");
break;
}
@ -94,13 +92,13 @@ function downloadCSS(sURL, sPCJS, nErrorCode, aMachineInfo)
*/
downloadPC(sURL, null, 0, aMachineInfo);
} else {
web.getResource(sCSSFile, null, true, function(sURL, sResponse, nErrorCode) {
Web.getResource(sCSSFile, null, true, function(sURL, sResponse, nErrorCode) {
downloadPC(sURL, sResponse, nErrorCode, aMachineInfo);
});
}
return;
}
web.alertUser("Error (" + nErrorCode + ") requesting " + sURL);
Component.alertUser("Error (" + nErrorCode + ") requesting " + sURL);
}
/**
@ -152,7 +150,7 @@ function downloadPC(sURL, sCSS, nErrorCode, aMachineInfo)
var resOld = Component.getMachineResources(idMachine), resNew = {};
for (var sName in resOld) {
var data = resOld[sName];
var sExt = str.getExtension(sName);
var sExt = Str.getExtension(sName);
if (sExt == "xml") {
/*
* Look through this resource for <disk> entries whose paths do not appear as one of the
@ -169,10 +167,10 @@ function downloadPC(sURL, sCSS, nErrorCode, aMachineInfo)
}
}
}
sXMLFile = sName = str.getBaseName(sName);
sXMLFile = sName = Str.getBaseName(sName);
}
else if (sExt == "xsl") {
sXSLFile = sName = str.getBaseName(sName);
sXSLFile = sName = Str.getBaseName(sName);
}
Component.log("saving resource: '" + sName + "' (" + data.length + " bytes)");
resNew[sName] = data;
@ -202,7 +200,7 @@ function downloadPC(sURL, sCSS, nErrorCode, aMachineInfo)
sPCJS = sPCJS.replace(/\u00A9/g, "&#xA9;");
var sAlert = web.downloadFile(sPCJS, "javascript", false, sScript);
var sAlert = Web.downloadFile(sPCJS, "javascript", false, sScript);
sAlert += ', copy it to your web server as "' + sScript + '", and then add the following to your web page:\n\n';
sAlert += '<div id="' + idMachine + '"></div>\n';
@ -210,10 +208,10 @@ function downloadPC(sURL, sCSS, nErrorCode, aMachineInfo)
sAlert += '<script type="text/javascript" src="' + sScript + '"></script>\n';
sAlert += '<script type="text/javascript">embedPC("' + idMachine + '","' + sXMLFile + '","' + sXSLFile + '");</script>\n\n';
sAlert += 'The machine should appear where the <div> is located.';
web.alertUser(sAlert);
Component.alertUser(sAlert);
return;
}
web.alertUser("Missing XML/XSL resources");
Component.alertUser("Missing XML/XSL resources");
}
/**

View file

@ -29,183 +29,46 @@
"use strict";
if (NODE) {
var web = require("./../../shared/lib/weblib");
var Component = require("./../../shared/lib/component");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/**
* State(component, sVersion, sSuffix)
*
* 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.
*
* WARNING: Since State objects are low-level objects that have no UI requirements, they do not
* inherit from the Component class, so you should only use class methods of Component, such as
* Component.assert() (or Debugger methods if the Debugger is available).
*
* NOTE: 1.01 is the first version to provide limited save/restore support using localStorage.
* From that point on, care must be taken to insure that any new version that's incompatible with
* previous localStorage data be released with a version number that is at least 1 greater,
* since we're tagging the localStorage data with the integer portion of the version string.
*
* @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
*/
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);
class State {
/**
* State(component, sVersion, sSuffix)
*
* 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.
*
* WARNING: Since State objects are low-level objects that have no UI requirements, they do not
* inherit from the Component class, so you should only use class methods of Component, such as
* Component.assert() (or Debugger methods if the Debugger is available).
*
* NOTE: 1.01 is the first version to provide limited save/restore support using localStorage.
* From that point on, care must be taken to insure that any new version that's incompatible with
* previous localStorage data be released with a version number that is at least 1 greater,
* since we're tagging the localStorage data with the integer portion of the version string.
*
* @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
*/
constructor(component, sVersion, sSuffix)
{
this.id = component.id;
this.dbg = component.dbg;
this.json = "";
this.state = {};
this.fLoaded = this.fParsed = false;
this.key = State.key(component, sVersion, sSuffix);
this.unload(component.parms);
}
if (sSuffix) {
key += "." + sSuffix;
}
return key;
};
/**
* State.compress(aSrc)
*
* @param {Array.<number>|null} aSrc
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
*/
State.compress = function(aSrc) {
if (aSrc) {
var iSrc = 0;
var iComp = 0;
var aComp = [];
while (iSrc < aSrc.length) {
var n = aSrc[iSrc];
Component.assert(n !== undefined);
var iCompare = iSrc + 1;
while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare++;
aComp[iComp++] = iCompare - iSrc;
aComp[iComp++] = n;
iSrc = iCompare;
}
if (aComp.length < aSrc.length) return aComp;
}
return aSrc;
};
/**
* State.decompress(aComp)
*
* @param {Array.<number>} aComp
* @param {number} nLength is expected length of decompressed data
* @return {Array.<number>}
*/
State.decompress = function(aComp, nLength) {
var iDst = 0;
var aDst = new Array(nLength);
var iComp = 0;
while (iComp < aComp.length - 1) {
var c = aComp[iComp++];
var n = aComp[iComp++];
while (c--) {
aDst[iDst++] = n;
}
}
Component.assert(aDst.length == nLength);
return aDst;
};
/**
* State.compressEvenOdd(aSrc)
*
* This is a very simple variation on compress() that compresses all the EVEN elements of aSrc first,
* followed by all the ODD elements. This tends to work better on EGA video memory, because when odd/even
* addressing is enabled (eg, for text modes), the DWORD values tend to alternate, which is the worst case
* for compress(), but the best case for compressEvenOdd().
*
* One wrinkle we support: if the first element is uninitialized, then we assume the entire array is undefined,
* and return an empty compressed array. Conversely, decompressEvenOdd() will take an empty compressed array
* and return an uninitialized array.
*
* @param {Array.<number>|null} aSrc
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
*/
State.compressEvenOdd = function(aSrc) {
if (aSrc) {
var iComp = 0, aComp = [];
if (aSrc[0] !== undefined) {
for (var off = 0; off < 2; off++) {
var iSrc = off;
while (iSrc < aSrc.length) {
var n = aSrc[iSrc];
var iCompare = iSrc + 2;
while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare += 2;
aComp[iComp++] = (iCompare - iSrc) >> 1;
aComp[iComp++] = n;
iSrc = iCompare;
}
}
}
if (aComp.length < aSrc.length) return aComp;
}
return aSrc;
};
/**
* State.decompressEvenOdd(aComp, nLength)
*
* This is the counterpart to compressEvenOdd(). Note that because there's nothing in the compressed sequence
* that differentiates a compress() sequence from a compressEvenOdd() sequence, you simply have to be consistent:
* if you used even/odd compression, then you must use even/odd decompression.
*
* @param {Array.<number>} aComp
* @param {number} nLength is expected length of decompressed data
* @return {Array.<number>}
*/
State.decompressEvenOdd = function(aComp, nLength) {
var iDst = 0;
var aDst = new Array(nLength);
var iComp = 0;
while (iComp < aComp.length - 1) {
var c = aComp[iComp++];
var n = aComp[iComp++];
while (c--) {
aDst[iDst] = n;
iDst += 2;
}
/*
* The output of a "count,value" pair will never exceed the end of the output array, so as soon as we reach it
* the first time, we know it's time to switch to ODD elements, and as soon as we reach it again, we should be
* done.
*/
Component.assert(iDst <= nLength || iComp == aComp.length);
if (iDst == nLength) iDst = 1;
}
Component.assert(aDst.length == nLength);
return aDst;
};
State.prototype = {
constructor: State,
/**
* set(id, data)
*
@ -213,13 +76,15 @@ State.prototype = {
* @param {number|string} id
* @param {Object|string} data
*/
set: function(id, data) {
set(id, data)
{
try {
this[this.id][id] = data;
this.state[id] = data;
} catch(e) {
Component.log(e.message);
}
},
}
/**
* get(id)
*
@ -227,43 +92,38 @@ State.prototype = {
* @param {number|string} id
* @return {Object|string|null}
*/
get: function(id) {
return this[this.id][id] || null;
},
/**
* value()
*
* Use this instead of data() if you haven't called parse() yet.
*
* @this {State}
* @return {string}
*/
value: function() {
return this[this.id];
},
get(id)
{
return this.state[id] || null;
}
/**
* data()
*
* @this {State}
* @return {Object}
*/
data: function() {
return this[this.id];
},
data()
{
return this.state;
}
/**
* load(s)
* load(json)
*
* WARNING: Make sure you follow this call with either a call to parse() or unload(),
* because any stringified data that we've loaded isn't usable until it's been parsed.
*
* @this {State}
* @param {Object|string|null} [s]
* @param {string|null} [json]
* @return {boolean} true if state exists in localStorage, false if not
*/
load: function(s) {
if (s) {
this[this.id] = s;
load(json)
{
if (json) {
this.json = json;
this.fLoaded = true;
this.fParsed = false;
return true;
}
if (this.fLoaded) {
@ -272,17 +132,18 @@ State.prototype = {
*/
return true;
}
if (web.hasLocalStorage()) {
s = web.getLocalStorageItem(this.key);
if (Web.hasLocalStorage()) {
var s = Web.getLocalStorageItem(this.key);
if (s) {
this[this.id] = s;
this.json = s;
this.fLoaded = true;
if (DEBUG) Component.log("localStorage(" + this.key + "): " + s.length + " bytes loaded");
return true;
}
}
return false;
},
}
/**
* parse()
*
@ -293,27 +154,33 @@ State.prototype = {
* @this {State}
* @return {boolean} true if successful, false if error
*/
parse: function() {
parse()
{
var fSuccess = true;
try {
this[this.id] = JSON.parse(this[this.id]);
} catch (e) {
Component.error(e.message || e);
fSuccess = false;
if (!this.fParsed) {
try {
this.state = JSON.parse(this.json);
this.fParsed = true;
} catch (e) {
Component.error(e.message || e);
fSuccess = false;
}
}
return fSuccess;
},
}
/**
* store()
*
* @this {State}
* @return {boolean} true if successful, false if error
*/
store: function() {
store()
{
var fSuccess = true;
if (web.hasLocalStorage()) {
var s = JSON.stringify(this[this.id]);
if (web.setLocalStorageItem(this.key, s)) {
if (Web.hasLocalStorage()) {
var s = JSON.stringify(this.state);
if (Web.setLocalStorageItem(this.key, s)) {
if (DEBUG) Component.log("localStorage(" + this.key + "): " + s.length + " bytes stored");
} else {
/*
@ -327,20 +194,19 @@ State.prototype = {
}
}
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));
},
toString()
{
return this.state? JSON.stringify(this.state) : this.json;
}
/**
* unload(parms)
*
@ -351,11 +217,14 @@ State.prototype = {
* @this {State}
* @param {Object} [parms]
*/
unload: function(parms) {
this[this.id] = {};
unload(parms)
{
this.json = "";
this.state = {};
this.fLoaded = this.fParsed = false;
if (parms) this.set("parms", parms);
this.fLoaded = false;
},
}
/**
* clear(fAll)
*
@ -365,19 +234,164 @@ State.prototype = {
* @this {State}
* @param {boolean} [fAll] true to unconditionally clear ALL localStorage for the current domain
*/
clear: function(fAll) {
clear(fAll)
{
this.unload();
var aKeys = web.getLocalStorageKeys();
var aKeys = Web.getLocalStorageKeys();
for (var i = 0; i < aKeys.length; i++) {
var sKey = aKeys[i];
if (sKey && (fAll || sKey.substr(0, this.key.length) == this.key)) {
web.removeLocalStorageItem(sKey);
Web.removeLocalStorageItem(sKey);
if (DEBUG) Component.log("localStorage(" + sKey + ") removed");
aKeys.splice(i, 1);
i = 0;
}
}
}
};
/**
* 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
*/
static key(component, sVersion, sSuffix)
{
var key = component.id;
if (sVersion) {
var i = sVersion.indexOf('.');
if (i > 0) key += ".v" + sVersion.substr(0, i);
}
if (sSuffix) {
key += "." + sSuffix;
}
return key;
}
/**
* State.compress(aSrc)
*
* @param {Array.<number>|null} aSrc
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
*/
static compress(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>}
*/
static decompress(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)
*/
static compressEvenOdd(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>}
*/
static decompressEvenOdd(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;
}
}
if (NODE) module.exports = State;

File diff suppressed because it is too large Load diff

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