Unforked the shared modules -- ES6 is now the default
This commit is contained in:
parent
323a42be37
commit
822ab39cbe
108 changed files with 3310 additions and 9776 deletions
|
|
@ -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
|
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`.
|
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),
|
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),
|
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,
|
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.
|
the new shared modules will become the default, and the old ones will fade away.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
(function(){/*
|
(function(){/*
|
||||||
http://pcjs.org/modules/shared/es6/diskapi.js (C) Jeff Parsons 2012-2017
|
http://pcjs.org/modules/shared/lib/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/lib/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/lib/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/lib/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/lib/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/lib/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/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/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/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/messages.js (C) Jeff Parsons 2012-2017
|
||||||
http://pcjs.org/modules/pcx86/lib/debugger.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/lib/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/lib/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/lib/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/lib/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/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/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/panel.js (C) Jeff Parsons 2012-2017
|
||||||
http://pcjs.org/modules/pcx86/lib/bus.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/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/fdc.js (C) Jeff Parsons 2012-2017
|
||||||
http://pcjs.org/modules/pcx86/lib/hdc.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/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 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,
|
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,
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
(function(){/*
|
(function(){/*
|
||||||
http://pcjs.org/modules/shared/es6/diskapi.js (C) Jeff Parsons 2012-2017
|
http://pcjs.org/modules/shared/lib/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/lib/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/lib/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/lib/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/lib/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/lib/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/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/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/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/messages.js (C) Jeff Parsons 2012-2017
|
||||||
http://pcjs.org/modules/pcx86/lib/debugger.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/lib/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/lib/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/lib/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/lib/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/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/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/panel.js (C) Jeff Parsons 2012-2017
|
||||||
http://pcjs.org/modules/pcx86/lib/bus.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/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/fdc.js (C) Jeff Parsons 2012-2017
|
||||||
http://pcjs.org/modules/pcx86/lib/hdc.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/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 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,
|
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,
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,9 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,9 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var DumpAPI = require("../../shared/es6/dumpapi");
|
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,9 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var Memory8080 = require("./memory");
|
var Memory8080 = require("./memory");
|
||||||
var Messages8080 = require("./messages");
|
var Messages8080 = require("./messages");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PC8080 = require("./defines");
|
var PC8080 = require("./defines");
|
||||||
var CPUDef8080 = require("./cpudef");
|
var CPUDef8080 = require("./cpudef");
|
||||||
var Messages8080 = require("./messages");
|
var Messages8080 = require("./messages");
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,13 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var UserAPI = require("../../shared/es6/userapi");
|
var UserAPI = require("../../shared/lib/userapi");
|
||||||
var ReportAPI = require("../../shared/es6/reportapi");
|
var ReportAPI = require("../../shared/lib/reportapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PC8080 = require("./defines");
|
var PC8080 = require("./defines");
|
||||||
var Bus8080 = require("./bus");
|
var Bus8080 = require("./bus");
|
||||||
var Messages8080 = require("./messages");
|
var Messages8080 = require("./messages");
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var Messages8080 = require("./messages");
|
var Messages8080 = require("./messages");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,9 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PC8080 = require("./defines");
|
var PC8080 = require("./defines");
|
||||||
var CPUDef8080 = require("./cpudef");
|
var CPUDef8080 = require("./cpudef");
|
||||||
var CPU8080 = require("./cpu");
|
var CPU8080 = require("./cpu");
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,13 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var Debugger = require("../../shared/es6/debugger");
|
var Debugger = require("../../shared/lib/debugger");
|
||||||
var Keys = require("../../shared/es6/keys");
|
var Keys = require("../../shared/lib/keys");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PC8080 = require("./defines");
|
var PC8080 = require("./defines");
|
||||||
var CPUDef8080 = require("./cpudef");
|
var CPUDef8080 = require("./cpudef");
|
||||||
var CPU8080 = require("./cpu");
|
var CPU8080 = require("./cpu");
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var Keys = require("../../shared/es6/keys");
|
var Keys = require("../../shared/lib/keys");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PC8080 = require("./defines");
|
var PC8080 = require("./defines");
|
||||||
var ChipSet8080 = require("./chipset");
|
var ChipSet8080 = require("./chipset");
|
||||||
var Messages8080 = require("./messages");
|
var Messages8080 = require("./messages");
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var CPUDef8080 = require("./cpudef");
|
var CPUDef8080 = require("./cpudef");
|
||||||
var Messages8080 = require("./messages");
|
var Messages8080 = require("./messages");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var PC8080 = require("./defines");
|
var PC8080 = require("./defines");
|
||||||
var Bus8080 = require("./bus");
|
var Bus8080 = require("./bus");
|
||||||
var CPUDef8080 = require("./cpudef");
|
var CPUDef8080 = require("./cpudef");
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var DumpAPI = require("../../shared/es6/dumpapi");
|
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PC8080 = require("./defines");
|
var PC8080 = require("./defines");
|
||||||
var CPUDef8080 = require("./cpudef");
|
var CPUDef8080 = require("./cpudef");
|
||||||
var Memory8080 = require("./memory");
|
var Memory8080 = require("./memory");
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var DumpAPI = require("../../shared/es6/dumpapi");
|
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var PC8080 = require("./defines");
|
var PC8080 = require("./defines");
|
||||||
var Memory8080 = require("./memory");
|
var Memory8080 = require("./memory");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PC8080 = require("./defines");
|
var PC8080 = require("./defines");
|
||||||
var Messages8080 = require("./messages");
|
var Messages8080 = require("./messages");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var DumpAPI = require("../../shared/es6/dumpapi");
|
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PC8080 = require("./defines");
|
var PC8080 = require("./defines");
|
||||||
var ChipSet8080 = require("./chipset");
|
var ChipSet8080 = require("./chipset");
|
||||||
var Memory8080 = require("./memory");
|
var Memory8080 = require("./memory");
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var Memory = require("./memory");
|
var Memory = require("./memory");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PCX86 = require("./defines");
|
var PCX86 = require("./defines");
|
||||||
var Interrupts = require("./interrupts");
|
var Interrupts = require("./interrupts");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
|
|
|
||||||
|
|
@ -58,13 +58,13 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var UserAPI = require("../../shared/es6/userapi");
|
var UserAPI = require("../../shared/lib/userapi");
|
||||||
var ReportAPI = require("../../shared/es6/reportapi");
|
var ReportAPI = require("../../shared/lib/reportapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PCX86 = require("./defines");
|
var PCX86 = require("./defines");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
var Bus = require("./bus");
|
var Bus = require("./bus");
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,9 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,13 @@
|
||||||
|
|
||||||
if (DEBUGGER) {
|
if (DEBUGGER) {
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var Debugger = require("../../shared/es6/debugger");
|
var Debugger = require("../../shared/lib/debugger");
|
||||||
var Keys = require("../../shared/es6/keys");
|
var Keys = require("../../shared/lib/keys");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PCX86 = require("./defines");
|
var PCX86 = require("./defines");
|
||||||
var CPU = require("./cpu");
|
var CPU = require("./cpu");
|
||||||
var X86 = require("./x86");
|
var X86 = require("./x86");
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,12 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var DiskAPI = require("../../shared/es6/diskapi");
|
var DiskAPI = require("../../shared/lib/diskapi");
|
||||||
var DumpAPI = require("../../shared/es6/dumpapi");
|
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var DiskAPI = require("../../shared/es6/diskapi");
|
var DiskAPI = require("../../shared/lib/diskapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PCX86 = require("./defines");
|
var PCX86 = require("./defines");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
var ChipSet = require("./chipset");
|
var ChipSet = require("./chipset");
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var DiskAPI = require("../../shared/es6/diskapi");
|
var DiskAPI = require("../../shared/lib/diskapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PCX86 = require("./defines");
|
var PCX86 = require("./defines");
|
||||||
var Interrupts = require("./interrupts");
|
var Interrupts = require("./interrupts");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var Keys = require("../../shared/es6/keys");
|
var Keys = require("../../shared/lib/keys");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PCX86 = require("./defines");
|
var PCX86 = require("./defines");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
var ChipSet = require("./chipset");
|
var ChipSet = require("./chipset");
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
var X86 = require("./x86");
|
var X86 = require("./x86");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PCX86 = require("./defines");
|
var PCX86 = require("./defines");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
var SerialPort = require("./serial");
|
var SerialPort = require("./serial");
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var PCX86 = require("./defines");
|
var PCX86 = require("./defines");
|
||||||
var Bus = require("./bus");
|
var Bus = require("./bus");
|
||||||
var Memory = require("./memory");
|
var Memory = require("./memory");
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PCX86 = require("./defines");
|
var PCX86 = require("./defines");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
var ChipSet = require("./chipset");
|
var ChipSet = require("./chipset");
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PCX86 = require("./defines");
|
var PCX86 = require("./defines");
|
||||||
var Memory = require("./memory");
|
var Memory = require("./memory");
|
||||||
var ROM = require("./rom");
|
var ROM = require("./rom");
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var DumpAPI = require("../../shared/es6/dumpapi");
|
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var PCX86 = require("./defines");
|
var PCX86 = require("./defines");
|
||||||
var Memory = require("./memory");
|
var Memory = require("./memory");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PCX86 = require("./defines");
|
var PCX86 = require("./defines");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
var ChipSet = require("./chipset");
|
var ChipSet = require("./chipset");
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var DumpAPI = require("../../shared/es6/dumpapi");
|
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PCX86 = require("./defines");
|
var PCX86 = require("./defines");
|
||||||
var Memory = require("./memory");
|
var Memory = require("./memory");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PCX86 = require("./defines");
|
var PCX86 = require("./defines");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
var Memory = require("./memory");
|
var Memory = require("./memory");
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PCX86 = require("./defines");
|
var PCX86 = require("./defines");
|
||||||
var X86 = require("./x86");
|
var X86 = require("./x86");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
var X86 = require("./x86");
|
var X86 = require("./x86");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var X86 = require("./x86");
|
var X86 = require("./x86");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
var X86 = require("./x86");
|
var X86 = require("./x86");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Messages = require("./messages");
|
var Messages = require("./messages");
|
||||||
var X86 = require("./x86");
|
var X86 = require("./x86");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,21 +60,6 @@ or generate both versions and use a loader that detects the browser's capabiliti
|
||||||
Caveats
|
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*
|
### *import* and *export*
|
||||||
|
|
||||||
With regard to *import* and *export* statements, the main reason I use them is to inform my development environment
|
With regard to *import* and *export* statements, the main reason I use them is to inform my development environment
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PDP11 = require("./defines");
|
var PDP11 = require("./defines");
|
||||||
var MemoryPDP11 = require("./memory");
|
var MemoryPDP11 = require("./memory");
|
||||||
var MessagesPDP11 = require("./messages");
|
var MessagesPDP11 = require("./messages");
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,13 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var UserAPI = require("../../shared/es6/userapi");
|
var UserAPI = require("../../shared/lib/userapi");
|
||||||
var ReportAPI = require("../../shared/es6/reportapi");
|
var ReportAPI = require("../../shared/lib/reportapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PDP11 = require("./defines");
|
var PDP11 = require("./defines");
|
||||||
var BusPDP11 = require("./bus");
|
var BusPDP11 = require("./bus");
|
||||||
var MessagesPDP11 = require("./messages");
|
var MessagesPDP11 = require("./messages");
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var MessagesPDP11 = require("./messages");
|
var MessagesPDP11 = require("./messages");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PDP11 = require("./defines");
|
var PDP11 = require("./defines");
|
||||||
var BusPDP11 = require("./bus");
|
var BusPDP11 = require("./bus");
|
||||||
var CPUPDP11 = require("./cpu");
|
var CPUPDP11 = require("./cpu");
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,13 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Usr = require("../../shared/es6/usrlib");
|
var Usr = require("../../shared/lib/usrlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var Debugger = require("../../shared/es6/debugger");
|
var Debugger = require("../../shared/lib/debugger");
|
||||||
var Keys = require("../../shared/es6/keys");
|
var Keys = require("../../shared/lib/keys");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PDP11 = require("./defines");
|
var PDP11 = require("./defines");
|
||||||
var BusPDP11 = require("./bus");
|
var BusPDP11 = require("./bus");
|
||||||
var MemoryPDP11 = require("./memory");
|
var MemoryPDP11 = require("./memory");
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PDP11 = require("./defines");
|
var PDP11 = require("./defines");
|
||||||
var BusPDP11 = require("./bus");
|
var BusPDP11 = require("./bus");
|
||||||
var MemoryPDP11 = require("./memory");
|
var MemoryPDP11 = require("./memory");
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var DiskAPI = require("../../shared/es6/diskapi");
|
var DiskAPI = require("../../shared/lib/diskapi");
|
||||||
var DumpAPI = require("../../shared/es6/dumpapi");
|
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var MessagesPDP11 = require("./messages");
|
var MessagesPDP11 = require("./messages");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var DiskAPI = require("../../shared/es6/diskapi");
|
var DiskAPI = require("../../shared/lib/diskapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PDP11 = require("./defines");
|
var PDP11 = require("./defines");
|
||||||
var MessagesPDP11 = require("./messages");
|
var MessagesPDP11 = require("./messages");
|
||||||
var DiskPDP11 = require("./disk");
|
var DiskPDP11 = require("./disk");
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var PDP11 = require("./defines");
|
var PDP11 = require("./defines");
|
||||||
var MessagesPDP11 = require("./messages");
|
var MessagesPDP11 = require("./messages");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var PDP11 = require("./defines");
|
var PDP11 = require("./defines");
|
||||||
var MessagesPDP11 = require("./messages");
|
var MessagesPDP11 = require("./messages");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PDP11 = require("./defines");
|
var PDP11 = require("./defines");
|
||||||
var BusPDP11 = require("./bus");
|
var BusPDP11 = require("./bus");
|
||||||
var MessagesPDP11 = require("./messages");
|
var MessagesPDP11 = require("./messages");
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var DumpAPI = require("../../shared/es6/dumpapi");
|
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PDP11 = require("./defines");
|
var PDP11 = require("./defines");
|
||||||
var MessagesPDP11 = require("./messages");
|
var MessagesPDP11 = require("./messages");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var DumpAPI = require("../../shared/es6/dumpapi");
|
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var PDP11 = require("./defines");
|
var PDP11 = require("./defines");
|
||||||
var MemoryPDP11 = require("./memory");
|
var MemoryPDP11 = require("./memory");
|
||||||
var MessagesPDP11 = require("./messages");
|
var MessagesPDP11 = require("./messages");
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var PDP11 = require("./defines");
|
var PDP11 = require("./defines");
|
||||||
var MessagesPDP11 = require("./messages");
|
var MessagesPDP11 = require("./messages");
|
||||||
var DriveController = require("./drive");
|
var DriveController = require("./drive");
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var PDP11 = require("./defines");
|
var PDP11 = require("./defines");
|
||||||
var MessagesPDP11 = require("./messages");
|
var MessagesPDP11 = require("./messages");
|
||||||
var DriveController = require("./drive");
|
var DriveController = require("./drive");
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var DumpAPI = require("../../shared/es6/dumpapi");
|
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var PDP11 = require("./defines");
|
var PDP11 = require("./defines");
|
||||||
var BusPDP11 = require("./bus");
|
var BusPDP11 = require("./bus");
|
||||||
var MemoryPDP11 = require("./memory");
|
var MemoryPDP11 = require("./memory");
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,11 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Str = require("../../shared/es6/strlib");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var Web = require("../../shared/es6/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("../../shared/es6/component");
|
var Component = require("../../shared/lib/component");
|
||||||
var Keys = require("../../shared/es6/keys");
|
var Keys = require("../../shared/lib/keys");
|
||||||
var State = require("../../shared/es6/state");
|
var State = require("../../shared/lib/state");
|
||||||
var PDP11 = require("./defines");
|
var PDP11 = require("./defines");
|
||||||
var MessagesPDP11 = require("./messages");
|
var MessagesPDP11 = require("./messages");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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;
|
|
||||||
|
|
@ -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?
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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, "©");
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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 = {
|
|
||||||
'&': '&',
|
|
||||||
'<': '<',
|
|
||||||
'>': '>',
|
|
||||||
'"': '"',
|
|
||||||
"'": '''
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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
|
|
@ -28,11 +28,9 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (DEBUGGER) {
|
if (NODE) {
|
||||||
if (NODE) {
|
var Str = require("../../shared/lib/strlib");
|
||||||
var str = require("../../shared/lib/strlib");
|
var Component = require("../../shared/lib/component");
|
||||||
var Component = require("../../shared/lib/component");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -50,105 +48,102 @@ if (DEBUGGER) {
|
||||||
* fTemporary:(boolean|undefined),
|
* fTemporary:(boolean|undefined),
|
||||||
* sCmd:(string|undefined),
|
* sCmd:(string|undefined),
|
||||||
* aCmds:(Array.<string>|undefined)
|
* aCmds:(Array.<string>|undefined)
|
||||||
* }} DbgAddr
|
* }}
|
||||||
*/
|
*/
|
||||||
var 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
|
* Cannot do '[]' access on a struct
|
||||||
* the other CPU-specific Debuggers (eg, DebuggerX86). Over time, the goal is to factor out as
|
|
||||||
* much common debugging support as possible from those components into this one.
|
|
||||||
*
|
*
|
||||||
* @constructor
|
* So, in order to define 'exports', we must override the @struct assumption by annotating
|
||||||
* @extends Component
|
* the class as @unrestricted (or @dict). Note that this must be done both here and in the
|
||||||
* @param {Object} parmsDbg
|
* 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)
|
class Debugger extends Component {
|
||||||
{
|
/**
|
||||||
if (DEBUGGER) {
|
* 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.
|
* Default base used to display all values; modified with the "s base" command.
|
||||||
*/
|
*/
|
||||||
this.nBase = parmsDbg['base'] || 16;
|
this.nBase = +parmsDbg['base'] || 16;
|
||||||
this.fParens = false;
|
this.fParens = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* These keep track of instruction activity, but only when tracing or when Debugger checks
|
* 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).
|
* have been enabled (eg, one or more breakpoints have been set).
|
||||||
*
|
*
|
||||||
* They are zeroed by the reset() notification handler. cInstructions is advanced by
|
* They are zeroed by the reset() notification handler. cInstructions is advanced by
|
||||||
* stepCPU() and checkInstruction() calls. nCycles is updated by every stepCPU() or stop()
|
* 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.
|
* call and simply represents the number of cycles performed by the last run of instructions.
|
||||||
*/
|
*/
|
||||||
this.nCycles = 0;
|
this.nCycles = 0;
|
||||||
this.cOpcodes = this.cOpcodesStart = 0;
|
this.cOpcodes = this.cOpcodesStart = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* fAssemble is true when "assemble mode" is active, false when not.
|
* fAssemble is true when "assemble mode" is active, false when not.
|
||||||
*/
|
*/
|
||||||
this.fAssemble = false;
|
this.fAssemble = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This maintains command history. New commands are inserted at index 0 of the array.
|
* 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].
|
* When Enter is pressed on an empty input buffer, we default to the command at aPrevCmds[0].
|
||||||
*/
|
*/
|
||||||
this.iPrevCmd = -1;
|
this.iPrevCmd = -1;
|
||||||
this.aPrevCmds = [];
|
this.aPrevCmds = [];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* aVariables is an object with properties that grow as setVariable() assigns more variables;
|
* 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,
|
* 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,
|
* 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()
|
* digits, or underscores) and the property value is the variable's numeric value. See doVar()
|
||||||
* and setVariable() for details.
|
* and setVariable() for details.
|
||||||
*
|
*
|
||||||
* Note that parseValue() parses variables before numbers, so any variable that looks like a
|
* 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
|
* 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
|
* 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
|
* 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.)
|
* (eg, a leading "0o" to indicate octal, a trailing period to indicate decimal, etc.)
|
||||||
*
|
*
|
||||||
* See str.parseInt() for more details about supported numbers.
|
* See Str.parseInt() for more details about supported numbers.
|
||||||
*/
|
*/
|
||||||
this.aVariables = {};
|
this.aVariables = {};
|
||||||
|
|
||||||
} // endif DEBUGGER
|
} // endif DEBUGGER
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DEBUGGER) {
|
|
||||||
|
|
||||||
Component.subclass(Debugger);
|
|
||||||
|
|
||||||
Debugger.aBinOpPrecedence = {
|
|
||||||
'||': 0, // logical OR
|
|
||||||
'&&': 1, // logical AND
|
|
||||||
'|': 2, // bitwise OR
|
|
||||||
'^': 3, // bitwise XOR
|
|
||||||
'&': 4, // bitwise AND
|
|
||||||
'!=': 5, // inequality
|
|
||||||
'==': 5, // equality
|
|
||||||
'>=': 6, // greater than or equal to
|
|
||||||
'>': 6, // greater than
|
|
||||||
'<=': 6, // less than or equal to
|
|
||||||
'<': 6, // less than
|
|
||||||
'>>>': 7, // unsigned bitwise right shift
|
|
||||||
'>>': 7, // bitwise right shift
|
|
||||||
'<<': 7, // bitwise left shift
|
|
||||||
'-': 8, // subtraction
|
|
||||||
'+': 8, // addition
|
|
||||||
'%': 9, // remainder
|
|
||||||
'/': 9, // division
|
|
||||||
'*': 9 // multiplication
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getRegIndex(sReg, off)
|
* getRegIndex(sReg, off)
|
||||||
|
|
@ -160,10 +155,9 @@ if (DEBUGGER) {
|
||||||
* @param {number} [off] optional offset into sReg
|
* @param {number} [off] optional offset into sReg
|
||||||
* @return {number} register index, or -1 if not found
|
* @return {number} register index, or -1 if not found
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.getRegIndex = function(sReg, off)
|
getRegIndex(sReg, off) {
|
||||||
{
|
|
||||||
return -1;
|
return -1;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getRegValue(iReg)
|
* getRegValue(iReg)
|
||||||
|
|
@ -174,10 +168,9 @@ if (DEBUGGER) {
|
||||||
* @param {number} iReg
|
* @param {number} iReg
|
||||||
* @return {number|undefined}
|
* @return {number|undefined}
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.getRegValue = function(iReg)
|
getRegValue(iReg) {
|
||||||
{
|
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parseAddrReference(s, sAddr)
|
* parseAddrReference(s, sAddr)
|
||||||
|
|
@ -191,10 +184,9 @@ if (DEBUGGER) {
|
||||||
* @param {string} sAddr
|
* @param {string} sAddr
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.parseAddrReference = function(s, sAddr)
|
parseAddrReference(s, sAddr) {
|
||||||
{
|
|
||||||
return s.replace('[' + sAddr + ']', "unimplemented");
|
return s.replace('[' + sAddr + ']', "unimplemented");
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getNextCommand()
|
* getNextCommand()
|
||||||
|
|
@ -202,8 +194,7 @@ if (DEBUGGER) {
|
||||||
* @this {Debugger}
|
* @this {Debugger}
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.getNextCommand = function()
|
getNextCommand() {
|
||||||
{
|
|
||||||
var sCmd;
|
var sCmd;
|
||||||
if (this.iPrevCmd > 0) {
|
if (this.iPrevCmd > 0) {
|
||||||
sCmd = this.aPrevCmds[--this.iPrevCmd];
|
sCmd = this.aPrevCmds[--this.iPrevCmd];
|
||||||
|
|
@ -212,7 +203,7 @@ if (DEBUGGER) {
|
||||||
this.iPrevCmd = -1;
|
this.iPrevCmd = -1;
|
||||||
}
|
}
|
||||||
return sCmd;
|
return sCmd;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getPrevCommand()
|
* getPrevCommand()
|
||||||
|
|
@ -220,14 +211,13 @@ if (DEBUGGER) {
|
||||||
* @this {Debugger}
|
* @this {Debugger}
|
||||||
* @return {string|null}
|
* @return {string|null}
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.getPrevCommand = function()
|
getPrevCommand() {
|
||||||
{
|
|
||||||
var sCmd = null;
|
var sCmd = null;
|
||||||
if (this.iPrevCmd < this.aPrevCmds.length - 1) {
|
if (this.iPrevCmd < this.aPrevCmds.length - 1) {
|
||||||
sCmd = this.aPrevCmds[++this.iPrevCmd];
|
sCmd = this.aPrevCmds[++this.iPrevCmd];
|
||||||
}
|
}
|
||||||
return sCmd;
|
return sCmd;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parseCommand(sCmd, fSave, chSep)
|
* parseCommand(sCmd, fSave, chSep)
|
||||||
|
|
@ -238,8 +228,7 @@ if (DEBUGGER) {
|
||||||
* @param {string} [chSep] is the command separator character (default is ';')
|
* @param {string} [chSep] is the command separator character (default is ';')
|
||||||
* @return {Array.<string>}
|
* @return {Array.<string>}
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.parseCommand = function(sCmd, fSave, chSep)
|
parseCommand(sCmd, fSave, chSep) {
|
||||||
{
|
|
||||||
if (fSave) {
|
if (fSave) {
|
||||||
if (!sCmd) {
|
if (!sCmd) {
|
||||||
if (this.fAssemble) {
|
if (this.fAssemble) {
|
||||||
|
|
@ -265,7 +254,7 @@ if (DEBUGGER) {
|
||||||
* associated with a breakpoint), we can no longer perform simplistic splitting.
|
* associated with a breakpoint), we can no longer perform simplistic splitting.
|
||||||
*
|
*
|
||||||
* a = sCmd.split(chSep || ';');
|
* 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.
|
* 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)
|
* Recall that substring() accepts starting (inclusive) and ending (exclusive)
|
||||||
* indexes, whereas substr() accepts a starting index and a length. We need the former.
|
* 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;
|
iPrev = i + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return a;
|
return a;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* evalExpression(aVals, aOps, cOps)
|
* evalExpression(aVals, aOps, cOps)
|
||||||
|
|
@ -327,8 +316,7 @@ if (DEBUGGER) {
|
||||||
* @param {number} [cOps] (default is all)
|
* @param {number} [cOps] (default is all)
|
||||||
* @return {boolean} true if successful, false if error
|
* @return {boolean} true if successful, false if error
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.evalExpression = function(aVals, aOps, cOps)
|
evalExpression(aVals, aOps, cOps) {
|
||||||
{
|
|
||||||
cOps = cOps || -1;
|
cOps = cOps || -1;
|
||||||
while (cOps-- && aOps.length) {
|
while (cOps-- && aOps.length) {
|
||||||
var chOp = aOps.pop();
|
var chOp = aOps.pop();
|
||||||
|
|
@ -402,7 +390,7 @@ if (DEBUGGER) {
|
||||||
aVals.push(valNew|0);
|
aVals.push(valNew|0);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parseExpression(sExp, fPrint)
|
* parseExpression(sExp, fPrint)
|
||||||
|
|
@ -440,8 +428,7 @@ if (DEBUGGER) {
|
||||||
* @param {boolean} [fPrint] is true to print all resolved values, false for quiet parsing
|
* @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
|
* @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;
|
var value;
|
||||||
|
|
||||||
if (sExp) {
|
if (sExp) {
|
||||||
|
|
@ -468,7 +455,7 @@ if (DEBUGGER) {
|
||||||
while (i < asValues.length) {
|
while (i < asValues.length) {
|
||||||
var sValue = asValues[i++];
|
var sValue = asValues[i++];
|
||||||
var cchValue = sValue.length;
|
var cchValue = sValue.length;
|
||||||
var s = str.trim(sValue);
|
var s = Str.trim(sValue);
|
||||||
if (!s) {
|
if (!s) {
|
||||||
fError = true;
|
fError = true;
|
||||||
break;
|
break;
|
||||||
|
|
@ -500,7 +487,7 @@ if (DEBUGGER) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parseReference(s)
|
* parseReference(s)
|
||||||
|
|
@ -514,8 +501,7 @@ if (DEBUGGER) {
|
||||||
* @param {string} s
|
* @param {string} s
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.parseReference = function(s)
|
parseReference(s) {
|
||||||
{
|
|
||||||
var a;
|
var a;
|
||||||
var chOpen = this.fParens? '(' : '{';
|
var chOpen = this.fParens? '(' : '{';
|
||||||
var chClose = this.fParens? ')' : '}';
|
var chClose = this.fParens? ')' : '}';
|
||||||
|
|
@ -530,7 +516,7 @@ if (DEBUGGER) {
|
||||||
s = this.parseAddrReference(s, a[1]);
|
s = this.parseAddrReference(s, a[1]);
|
||||||
}
|
}
|
||||||
return this.parseSysVars(s);
|
return this.parseSysVars(s);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parseSysVars(s)
|
* parseSysVars(s)
|
||||||
|
|
@ -543,8 +529,7 @@ if (DEBUGGER) {
|
||||||
* @param {string} s
|
* @param {string} s
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.parseSysVars = function(s)
|
parseSysVars(s) {
|
||||||
{
|
|
||||||
var a;
|
var a;
|
||||||
while (a = s.match(/\$([a-z]+)/i)) {
|
while (a = s.match(/\$([a-z]+)/i)) {
|
||||||
var v = null;
|
var v = null;
|
||||||
|
|
@ -557,7 +542,7 @@ if (DEBUGGER) {
|
||||||
s = s.replace(a[0], v.toString());
|
s = s.replace(a[0], v.toString());
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parseValue(sValue, sName, fQuiet)
|
* parseValue(sValue, sName, fQuiet)
|
||||||
|
|
@ -568,8 +553,7 @@ if (DEBUGGER) {
|
||||||
* @param {boolean} [fQuiet]
|
* @param {boolean} [fQuiet]
|
||||||
* @return {number|undefined} numeric value, or undefined if sValue is either undefined or invalid
|
* @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;
|
var value;
|
||||||
if (sValue != null) {
|
if (sValue != null) {
|
||||||
var iReg = this.getRegIndex(sValue);
|
var iReg = this.getRegIndex(sValue);
|
||||||
|
|
@ -578,7 +562,7 @@ if (DEBUGGER) {
|
||||||
} else {
|
} else {
|
||||||
value = this.getVariable(sValue);
|
value = this.getVariable(sValue);
|
||||||
if (value == null) {
|
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);
|
if (value == null && !fQuiet) this.println("invalid " + (sName? sName : "value") + ": " + sValue);
|
||||||
|
|
@ -586,7 +570,7 @@ if (DEBUGGER) {
|
||||||
if (!fQuiet) this.println("missing " + (sName || "value"));
|
if (!fQuiet) this.println("missing " + (sName || "value"));
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* printValue(sVar, value)
|
* printValue(sVar, value)
|
||||||
|
|
@ -596,13 +580,12 @@ if (DEBUGGER) {
|
||||||
* @param {number|undefined} value
|
* @param {number|undefined} value
|
||||||
* @return {boolean} true if value defined, false if not
|
* @return {boolean} true if value defined, false if not
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.printValue = function(sVar, value)
|
printValue(sVar, value) {
|
||||||
{
|
|
||||||
var sValue;
|
var sValue;
|
||||||
var fDefined = false;
|
var fDefined = false;
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
fDefined = true;
|
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) {
|
if (value >= 0x20 && value < 0x7F) {
|
||||||
sValue += " '" + String.fromCharCode(value) + "'";
|
sValue += " '" + String.fromCharCode(value) + "'";
|
||||||
}
|
}
|
||||||
|
|
@ -610,7 +593,7 @@ if (DEBUGGER) {
|
||||||
sVar = (sVar != null? (sVar + ": ") : "");
|
sVar = (sVar != null? (sVar + ": ") : "");
|
||||||
this.println(sVar + sValue);
|
this.println(sVar + sValue);
|
||||||
return fDefined;
|
return fDefined;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* printVariable(sVar)
|
* printVariable(sVar)
|
||||||
|
|
@ -619,8 +602,7 @@ if (DEBUGGER) {
|
||||||
* @param {string} [sVar]
|
* @param {string} [sVar]
|
||||||
* @return {boolean} true if all value(s) defined, false if not
|
* @return {boolean} true if all value(s) defined, false if not
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.printVariable = function(sVar)
|
printVariable(sVar) {
|
||||||
{
|
|
||||||
if (sVar) {
|
if (sVar) {
|
||||||
return this.printValue(sVar, this.aVariables[sVar]);
|
return this.printValue(sVar, this.aVariables[sVar]);
|
||||||
}
|
}
|
||||||
|
|
@ -630,7 +612,7 @@ if (DEBUGGER) {
|
||||||
cVariables++;
|
cVariables++;
|
||||||
}
|
}
|
||||||
return cVariables > 0;
|
return cVariables > 0;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* delVariable(sVar)
|
* delVariable(sVar)
|
||||||
|
|
@ -638,10 +620,9 @@ if (DEBUGGER) {
|
||||||
* @this {Debugger}
|
* @this {Debugger}
|
||||||
* @param {string} sVar
|
* @param {string} sVar
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.delVariable = function(sVar)
|
delVariable(sVar) {
|
||||||
{
|
|
||||||
delete this.aVariables[sVar];
|
delete this.aVariables[sVar];
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getVariable(sVar)
|
* getVariable(sVar)
|
||||||
|
|
@ -650,10 +631,9 @@ if (DEBUGGER) {
|
||||||
* @param {string} sVar
|
* @param {string} sVar
|
||||||
* @return {number|undefined}
|
* @return {number|undefined}
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.getVariable = function(sVar)
|
getVariable(sVar) {
|
||||||
{
|
|
||||||
return this.aVariables[sVar];
|
return this.aVariables[sVar];
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* setVariable(sVar, value)
|
* setVariable(sVar, value)
|
||||||
|
|
@ -662,15 +642,14 @@ if (DEBUGGER) {
|
||||||
* @param {string} sVar
|
* @param {string} sVar
|
||||||
* @param {number} value
|
* @param {number} value
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.setVariable = function(sVar, value)
|
setVariable(sVar, value) {
|
||||||
{
|
|
||||||
this.aVariables[sVar] = value;
|
this.aVariables[sVar] = value;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* toStrBase(n, nBytes, fStripLeadingZeros)
|
* 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}
|
* @this {Debugger}
|
||||||
* @param {number|null|undefined} n
|
* @param {number|null|undefined} n
|
||||||
|
|
@ -678,22 +657,46 @@ if (DEBUGGER) {
|
||||||
* @param {boolean} [fStripLeadingZeros]
|
* @param {boolean} [fStripLeadingZeros]
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.toStrBase = function(n, nBytes, fStripLeadingZeros)
|
toStrBase(n, nBytes, fStripLeadingZeros) {
|
||||||
{
|
|
||||||
var s;
|
var s;
|
||||||
switch(this.nBase) {
|
switch(this.nBase) {
|
||||||
case 8:
|
case 8:
|
||||||
s = str.toOct(n, nBytes * 3 - (nBytes > 2? 1 : 0));
|
s = Str.toOct(n, nBytes * 3 - (nBytes > 2? 1 : 0));
|
||||||
break;
|
break;
|
||||||
case 10:
|
case 10:
|
||||||
s = n.toString();
|
s = n.toString();
|
||||||
break;
|
break;
|
||||||
case 16:
|
case 16:
|
||||||
default:
|
default:
|
||||||
s = str.toHex(n, nBytes * 2);
|
s = Str.toHex(n, nBytes * 2);
|
||||||
break;
|
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
|
} // endif DEBUGGER
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,13 @@
|
||||||
* @define {string}
|
* @define {string}
|
||||||
*/
|
*/
|
||||||
var APPVERSION = "1.x.x"; // this @define is overridden by the Closure Compiler with the version in package.json
|
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 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 COPYRIGHT = "Copyright © 2012-2017 Jeff Parsons <Jeff@pcjs.org>";
|
||||||
|
|
||||||
var LICENSE = "License: GPL version 3 or later <http://gnu.org/licenses/gpl.html>";
|
var LICENSE = "License: GPL version 3 or later <http://gnu.org/licenses/gpl.html>";
|
||||||
|
|
||||||
var CSSCLASS = "pcjs";
|
var CSSCLASS = "pcjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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]
|
* [# 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
|
* If the 4th value is omitted, the sector size is assumed to be 512. The order of these "geometric" values mirrors
|
||||||
* values mirrors the structure of our JSON-encoded disk images, which consist of an array of cylinders, each of which
|
* the structure of our JSON-encoded disk images, which consist of an array of cylinders, each of which is an array of
|
||||||
* is an array of heads, each of which is an array of sector objects.
|
* 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.
|
|
||||||
*/
|
*/
|
||||||
DiskAPI.DISK_FORMATS = {
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
21368320:[615,4,17], // PC AT 20Mb hard drive (type 2)
|
||||||
/*
|
/*
|
||||||
* Assorted DEC disk pack formats.
|
* 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
|
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
|
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
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,10 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/* global document: true, window: true, XSLTProcessor: false, APPNAME: false, APPVERSION: false, XMLVERSION: true, DEBUG: true */
|
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Component = require("./component");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var str = require("./strlib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var web = require("./weblib");
|
var Component = require("../../shared/lib/component");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -45,8 +43,8 @@ if (NODE) {
|
||||||
* machine component init() handlers.
|
* machine component init() handlers.
|
||||||
*
|
*
|
||||||
* Also, to prevent those init() handlers from running prematurely, we must disable all page
|
* 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
|
* notification events at the start of the embedding process (Web.enablePageEvents(false)) and
|
||||||
* re-enable them at the end (web.enablePageEvents(true)).
|
* re-enable them at the end (Web.enablePageEvents(true)).
|
||||||
*/
|
*/
|
||||||
var fAsync = true;
|
var fAsync = true;
|
||||||
var cAsyncMachines = 0;
|
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);
|
parseXML(sXML, sXMLFile, idMachine, sAppName, sAppClass, sParms, fResolve, display, done);
|
||||||
};
|
};
|
||||||
display("Loading " + sXMLFile + "...");
|
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,
|
* 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).
|
* since fResolve is false) with whatever XMLVERSION we extracted from the XML file (see corresponding kludge below).
|
||||||
|
*
|
||||||
|
* ES6 ALERT: Template strings.
|
||||||
*/
|
*/
|
||||||
if (!COMPILED && XMLVERSION) {
|
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
|
* 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.
|
* 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 '<').
|
* is verify that the requested resource "looks like" valid XML (in other words, it begins with a '<').
|
||||||
*/
|
*/
|
||||||
var xmlDoc = null;
|
var xmlDoc = null;
|
||||||
|
|
@ -320,7 +320,7 @@ function resolveXML(sXML, display, done)
|
||||||
};
|
};
|
||||||
|
|
||||||
display("Loading " + sRefFile + "...");
|
display("Loading " + sRefFile + "...");
|
||||||
web.getResource(sRefFile, null, fAsync, doneReadXML);
|
Web.getResource(sRefFile, null, fAsync, doneReadXML);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
done(sXML, null);
|
done(sXML, null);
|
||||||
|
|
@ -350,7 +350,7 @@ function embedMachine(sAppName, sAppClass, sVersion, idMachine, sXMLFile, sXSLFi
|
||||||
var doneMachine = function() {
|
var doneMachine = function() {
|
||||||
Component.assert(cAsyncMachines > 0);
|
Component.assert(cAsyncMachines > 0);
|
||||||
if (!--cAsyncMachines) {
|
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"));
|
var aeWarning = (eMachine && Component.getElementsByClass(eMachine, "machine-warning"));
|
||||||
eWarning = (aeWarning && aeWarning[0]) || eMachine;
|
eWarning = (aeWarning && aeWarning[0]) || eMachine;
|
||||||
}
|
}
|
||||||
if (eWarning) eWarning.innerHTML = str.escapeHTML(sMessage);
|
if (eWarning) eWarning.innerHTML = Str.escapeHTML(sMessage);
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -545,7 +545,7 @@ function embedMachine(sAppName, sAppClass, sVersion, idMachine, sXMLFile, sXSLFi
|
||||||
*/
|
*/
|
||||||
function embedC1P(idMachine, sXMLFile, sXSLFile)
|
function embedC1P(idMachine, sXMLFile, sXSLFile)
|
||||||
{
|
{
|
||||||
if (fAsync) web.enablePageEvents(false);
|
if (fAsync) Web.enablePageEvents(false);
|
||||||
return embedMachine("C1Pjs", "c1pjs", APPVERSION, idMachine, sXMLFile, sXSLFile);
|
return embedMachine("C1Pjs", "c1pjs", APPVERSION, idMachine, sXMLFile, sXSLFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -560,7 +560,7 @@ function embedC1P(idMachine, sXMLFile, sXSLFile)
|
||||||
*/
|
*/
|
||||||
function embedPCx86(idMachine, sXMLFile, sXSLFile, sParms)
|
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);
|
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)
|
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);
|
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)
|
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);
|
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.
|
* 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['embedPDP11'] = embedPDP11;
|
||||||
}
|
}
|
||||||
|
|
||||||
window['enableEvents'] = web.enablePageEvents;
|
window['findMachineComponent'] = findMachineComponent;
|
||||||
window['sendEvent'] = web.sendPageEvent;
|
|
||||||
|
window['enableEvents'] = Web.enablePageEvents;
|
||||||
|
window['sendEvent'] = Web.sendPageEvent;
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ var Keys = {
|
||||||
/*
|
/*
|
||||||
* Keys and/or key combinations that generate common ASCII codes.
|
* 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.
|
* 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
|
* 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;
|
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
|
* 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
|
* are no longer valid (if indeed they ever were), so they've been commented out. It's likely that I
|
||||||
|
|
|
||||||
|
|
@ -28,18 +28,345 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/* global Buffer: false */
|
|
||||||
|
|
||||||
var net = {};
|
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var sServerRoot;
|
|
||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
var http = require("http");
|
var http = require("http");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var url = require("url");
|
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.
|
* 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
|
* http://www.pcjs.org/?gort=debug
|
||||||
*
|
*
|
||||||
* hasParm() detects the presence of the specified command, and propagateParms() is a URL filter that ensures
|
* 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
|
* 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).
|
* contain/expose any of these commands).
|
||||||
*/
|
*/
|
||||||
net.GORT_COMMAND = "gort";
|
Net.GORT_COMMAND = "gort";
|
||||||
net.GORT_DEBUG = "debug"; // use this to force uncompiled JavaScript even on a Release server
|
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_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_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_REBUILD = "rebuild"; // use this to force the "index.html" in the current directory to be rebuilt
|
||||||
|
|
||||||
net.REVEAL_COMMAND = "reveal";
|
Net.REVEAL_COMMAND = "reveal";
|
||||||
net.REVEAL_PDFS = "pdfs";
|
Net.REVEAL_PDFS = "pdfs";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is a list of the URL parameters that propagateParms() will propagate from the requester's URL to
|
* This is a list of the URL parameters that propagateParms() will propagate from the requester's URL to
|
||||||
* other URLs provided by the requester.
|
* other URLs provided by the requester.
|
||||||
*/
|
*/
|
||||||
var asPropagate = [net.GORT_COMMAND, "autostart"];
|
Net.asPropagate = [Net.GORT_COMMAND, "autostart"];
|
||||||
|
Net.sServerRoot = null;
|
||||||
|
|
||||||
/**
|
if (NODE) module.exports = 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 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;
|
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,6 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/* global DEBUG: true */
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In the compiled case, we rely on the Closure Compiler to override DEBUG, setting it to false,
|
* 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.
|
* so that all DEBUG-only code will be removed by the compiler.
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,6 @@
|
||||||
|
|
||||||
"use strict";
|
"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 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
|
* PRIVATE is false. For Jekyll configurations, if site.pcjs.private is set, this file gets included, overriding
|
||||||
|
|
|
||||||
|
|
@ -28,59 +28,60 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var proc = {};
|
class Proc {
|
||||||
|
/**
|
||||||
/**
|
* getArgs()
|
||||||
* getArgs()
|
*
|
||||||
*
|
* Processes command-line arguments. Arguments may be introduced by either
|
||||||
* Processes command-line arguments. Arguments may be introduced by either
|
* a double-hyphen (--) or a long dash (—), and argument values, if any, must be
|
||||||
* a double-hyphen (--) or a long dash (—), and argument values, if any, must be
|
* separated by an "=" without any intervening whitespace. Arguments without
|
||||||
* separated by an "=" without any intervening whitespace. Arguments without
|
* an explicit value default to true, and any argument appearing more than once
|
||||||
* an explicit value default to true, and any argument appearing more than once
|
* is automatically converted to an Array.
|
||||||
* is automatically converted to an Array.
|
*
|
||||||
*
|
* Single-hyphen (-) arguments are allowed as well; they are treated as a series
|
||||||
* 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
|
||||||
* of single-character arguments, each set to true, and any of these arguments
|
* appearing more than once is discarded.
|
||||||
* appearing more than once is discarded.
|
*
|
||||||
*
|
* @return {{argc:number, argv:{}}}
|
||||||
* @return {{argc:number, argv:{}}}
|
*/
|
||||||
*/
|
static getArgs()
|
||||||
proc.getArgs = function() {
|
{
|
||||||
var argc = 0;
|
var argc = 0;
|
||||||
var argv = {};
|
var argv = {};
|
||||||
for (var i = 2; i < process.argv.length; i++) {
|
for (var i = 2; i < process.argv.length; i++) {
|
||||||
var j, sSep;
|
var j, sSep;
|
||||||
var sArg = process.argv[i];
|
var sArg = process.argv[i];
|
||||||
if (!sArg.indexOf(sSep = "--") || !sArg.indexOf(sSep = "—")) {
|
if (!sArg.indexOf(sSep = "--") || !sArg.indexOf(sSep = "—")) {
|
||||||
sArg = sArg.substr(sSep.length);
|
sArg = sArg.substr(sSep.length);
|
||||||
var sValue = true;
|
var sValue = true;
|
||||||
j = sArg.indexOf("=");
|
j = sArg.indexOf("=");
|
||||||
if (j > 0) {
|
if (j > 0) {
|
||||||
sValue = sArg.substr(j+1);
|
sValue = sArg.substr(j + 1);
|
||||||
sArg = sArg.substr(0, j);
|
sArg = sArg.substr(0, j);
|
||||||
sValue = (sValue == "true")? true : ((sValue == "false")? false : sValue);
|
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);
|
if (argv[sArg] === undefined) {
|
||||||
}
|
|
||||||
} else if (!sArg.indexOf("-")) {
|
|
||||||
for (j = 1; j < sArg.length; j++) {
|
|
||||||
var ch = sArg.charAt(j);
|
|
||||||
if (argv[ch] === undefined) {
|
|
||||||
argv[ch] = true;
|
|
||||||
argc++;
|
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;
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,10 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/* global window: true, APPVERSION: false, XMLVERSION: true, DEBUG: true */
|
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var Component = require("./component");
|
var Str = require("../../shared/lib/strlib");
|
||||||
var str = require("./strlib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var web = require("./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;
|
if (callback && callback({ state: sState, parms: sParms })) return true;
|
||||||
web.getResource(sPCJSFile, null, true, function(sURL, sResponse, nErrorCode) {
|
Web.getResource(sPCJSFile, null, true, function(sURL, sResponse, nErrorCode) {
|
||||||
downloadCSS(sURL, sResponse, nErrorCode, [idMachine, str.getBaseName(sPCJSFile, true), sParms, sState]);
|
downloadCSS(sURL, sResponse, nErrorCode, [idMachine, Str.getBaseName(sPCJSFile, true), sParms, sState]);
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
web.alertUser("Unable to identify machine '" + idMachine + "'");
|
Component.alertUser("Unable to identify machine '" + idMachine + "'");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,7 +81,7 @@ function downloadCSS(sURL, sPCJS, nErrorCode, aMachineInfo)
|
||||||
var res = Component.getMachineResources(aMachineInfo[0]);
|
var res = Component.getMachineResources(aMachineInfo[0]);
|
||||||
var sCSSFile = null;
|
var sCSSFile = null;
|
||||||
for (var sName in res) {
|
for (var sName in res) {
|
||||||
if (str.endsWith(sName, "components.xsl")) {
|
if (Str.endsWith(sName, "components.xsl")) {
|
||||||
sCSSFile = sName.replace(".xsl", ".css");
|
sCSSFile = sName.replace(".xsl", ".css");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -94,13 +92,13 @@ function downloadCSS(sURL, sPCJS, nErrorCode, aMachineInfo)
|
||||||
*/
|
*/
|
||||||
downloadPC(sURL, null, 0, aMachineInfo);
|
downloadPC(sURL, null, 0, aMachineInfo);
|
||||||
} else {
|
} else {
|
||||||
web.getResource(sCSSFile, null, true, function(sURL, sResponse, nErrorCode) {
|
Web.getResource(sCSSFile, null, true, function(sURL, sResponse, nErrorCode) {
|
||||||
downloadPC(sURL, sResponse, nErrorCode, aMachineInfo);
|
downloadPC(sURL, sResponse, nErrorCode, aMachineInfo);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return;
|
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 = {};
|
var resOld = Component.getMachineResources(idMachine), resNew = {};
|
||||||
for (var sName in resOld) {
|
for (var sName in resOld) {
|
||||||
var data = resOld[sName];
|
var data = resOld[sName];
|
||||||
var sExt = str.getExtension(sName);
|
var sExt = Str.getExtension(sName);
|
||||||
if (sExt == "xml") {
|
if (sExt == "xml") {
|
||||||
/*
|
/*
|
||||||
* Look through this resource for <disk> entries whose paths do not appear as one of the
|
* 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") {
|
else if (sExt == "xsl") {
|
||||||
sXSLFile = sName = str.getBaseName(sName);
|
sXSLFile = sName = Str.getBaseName(sName);
|
||||||
}
|
}
|
||||||
Component.log("saving resource: '" + sName + "' (" + data.length + " bytes)");
|
Component.log("saving resource: '" + sName + "' (" + data.length + " bytes)");
|
||||||
resNew[sName] = data;
|
resNew[sName] = data;
|
||||||
|
|
@ -202,7 +200,7 @@ function downloadPC(sURL, sCSS, nErrorCode, aMachineInfo)
|
||||||
|
|
||||||
sPCJS = sPCJS.replace(/\u00A9/g, "©");
|
sPCJS = sPCJS.replace(/\u00A9/g, "©");
|
||||||
|
|
||||||
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 += ', 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 += '<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" src="' + sScript + '"></script>\n';
|
||||||
sAlert += '<script type="text/javascript">embedPC("' + idMachine + '","' + sXMLFile + '","' + sXSLFile + '");</script>\n\n';
|
sAlert += '<script type="text/javascript">embedPC("' + idMachine + '","' + sXMLFile + '","' + sXSLFile + '");</script>\n\n';
|
||||||
sAlert += 'The machine should appear where the <div> is located.';
|
sAlert += 'The machine should appear where the <div> is located.';
|
||||||
web.alertUser(sAlert);
|
Component.alertUser(sAlert);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
web.alertUser("Missing XML/XSL resources");
|
Component.alertUser("Missing XML/XSL resources");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -29,183 +29,46 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
if (NODE) {
|
if (NODE) {
|
||||||
var web = require("./../../shared/lib/weblib");
|
var Web = require("../../shared/lib/weblib");
|
||||||
var Component = require("./../../shared/lib/component");
|
var Component = require("../../shared/lib/component");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
class State {
|
||||||
* State(component, sVersion, sSuffix)
|
/**
|
||||||
*
|
* State(component, sVersion, sSuffix)
|
||||||
* State objects are used by components to save/restore their state.
|
*
|
||||||
*
|
* 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 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.
|
* 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
|
* WARNING: Since State objects are low-level objects that have no UI requirements, they do not
|
||||||
* Component.assert() (or Debugger methods if the Debugger is available).
|
* 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
|
* NOTE: 1.01 is the first version to provide limited save/restore support using localStorage.
|
||||||
* previous localStorage data be released with a version number that is at least 1 greater,
|
* From that point on, care must be taken to insure that any new version that's incompatible with
|
||||||
* since we're tagging the localStorage data with the integer portion of the version string.
|
* 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 {Component} component
|
||||||
* @param {string} [sVersion] is used to append a major version number to the key
|
* @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
|
* @param {string} [sSuffix] is used to append any additional suffixes to the key
|
||||||
*/
|
*/
|
||||||
function State(component, sVersion, sSuffix) {
|
constructor(component, sVersion, sSuffix)
|
||||||
this.id = component.id;
|
{
|
||||||
this.key = State.key(component, sVersion, sSuffix);
|
this.id = component.id;
|
||||||
this.dbg = component.dbg;
|
this.dbg = component.dbg;
|
||||||
this.unload(component.parms);
|
this.json = "";
|
||||||
}
|
this.state = {};
|
||||||
|
this.fLoaded = this.fParsed = false;
|
||||||
/**
|
this.key = State.key(component, sVersion, sSuffix);
|
||||||
* State.key(component, sVersion, sSuffix)
|
this.unload(component.parms);
|
||||||
*
|
|
||||||
* This encapsulates the key generation code.
|
|
||||||
*
|
|
||||||
* @param {Component} component
|
|
||||||
* @param {string} [sVersion] is used to append a major version number to the key
|
|
||||||
* @param {string} [sSuffix] is used to append any additional suffixes to the key
|
|
||||||
* @return {string} key
|
|
||||||
*/
|
|
||||||
State.key = function(component, sVersion, sSuffix) {
|
|
||||||
var key = component.id;
|
|
||||||
if (sVersion) {
|
|
||||||
var i = sVersion.indexOf('.');
|
|
||||||
if (i > 0) key += ".v" + sVersion.substr(0, i);
|
|
||||||
}
|
}
|
||||||
if (sSuffix) {
|
|
||||||
key += "." + sSuffix;
|
|
||||||
}
|
|
||||||
return key;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* State.compress(aSrc)
|
|
||||||
*
|
|
||||||
* @param {Array.<number>|null} aSrc
|
|
||||||
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
|
|
||||||
*/
|
|
||||||
State.compress = function(aSrc) {
|
|
||||||
if (aSrc) {
|
|
||||||
var iSrc = 0;
|
|
||||||
var iComp = 0;
|
|
||||||
var aComp = [];
|
|
||||||
while (iSrc < aSrc.length) {
|
|
||||||
var n = aSrc[iSrc];
|
|
||||||
Component.assert(n !== undefined);
|
|
||||||
var iCompare = iSrc + 1;
|
|
||||||
while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare++;
|
|
||||||
aComp[iComp++] = iCompare - iSrc;
|
|
||||||
aComp[iComp++] = n;
|
|
||||||
iSrc = iCompare;
|
|
||||||
}
|
|
||||||
if (aComp.length < aSrc.length) return aComp;
|
|
||||||
}
|
|
||||||
return aSrc;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* State.decompress(aComp)
|
|
||||||
*
|
|
||||||
* @param {Array.<number>} aComp
|
|
||||||
* @param {number} nLength is expected length of decompressed data
|
|
||||||
* @return {Array.<number>}
|
|
||||||
*/
|
|
||||||
State.decompress = function(aComp, nLength) {
|
|
||||||
var iDst = 0;
|
|
||||||
var aDst = new Array(nLength);
|
|
||||||
var iComp = 0;
|
|
||||||
while (iComp < aComp.length - 1) {
|
|
||||||
var c = aComp[iComp++];
|
|
||||||
var n = aComp[iComp++];
|
|
||||||
while (c--) {
|
|
||||||
aDst[iDst++] = n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Component.assert(aDst.length == nLength);
|
|
||||||
return aDst;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* State.compressEvenOdd(aSrc)
|
|
||||||
*
|
|
||||||
* This is a very simple variation on compress() that compresses all the EVEN elements of aSrc first,
|
|
||||||
* followed by all the ODD elements. This tends to work better on EGA video memory, because when odd/even
|
|
||||||
* addressing is enabled (eg, for text modes), the DWORD values tend to alternate, which is the worst case
|
|
||||||
* for compress(), but the best case for compressEvenOdd().
|
|
||||||
*
|
|
||||||
* One wrinkle we support: if the first element is uninitialized, then we assume the entire array is undefined,
|
|
||||||
* and return an empty compressed array. Conversely, decompressEvenOdd() will take an empty compressed array
|
|
||||||
* and return an uninitialized array.
|
|
||||||
*
|
|
||||||
* @param {Array.<number>|null} aSrc
|
|
||||||
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
|
|
||||||
*/
|
|
||||||
State.compressEvenOdd = function(aSrc) {
|
|
||||||
if (aSrc) {
|
|
||||||
var iComp = 0, aComp = [];
|
|
||||||
if (aSrc[0] !== undefined) {
|
|
||||||
for (var off = 0; off < 2; off++) {
|
|
||||||
var iSrc = off;
|
|
||||||
while (iSrc < aSrc.length) {
|
|
||||||
var n = aSrc[iSrc];
|
|
||||||
var iCompare = iSrc + 2;
|
|
||||||
while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare += 2;
|
|
||||||
aComp[iComp++] = (iCompare - iSrc) >> 1;
|
|
||||||
aComp[iComp++] = n;
|
|
||||||
iSrc = iCompare;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (aComp.length < aSrc.length) return aComp;
|
|
||||||
}
|
|
||||||
return aSrc;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* State.decompressEvenOdd(aComp, nLength)
|
|
||||||
*
|
|
||||||
* This is the counterpart to compressEvenOdd(). Note that because there's nothing in the compressed sequence
|
|
||||||
* that differentiates a compress() sequence from a compressEvenOdd() sequence, you simply have to be consistent:
|
|
||||||
* if you used even/odd compression, then you must use even/odd decompression.
|
|
||||||
*
|
|
||||||
* @param {Array.<number>} aComp
|
|
||||||
* @param {number} nLength is expected length of decompressed data
|
|
||||||
* @return {Array.<number>}
|
|
||||||
*/
|
|
||||||
State.decompressEvenOdd = function(aComp, nLength) {
|
|
||||||
var iDst = 0;
|
|
||||||
var aDst = new Array(nLength);
|
|
||||||
var iComp = 0;
|
|
||||||
while (iComp < aComp.length - 1) {
|
|
||||||
var c = aComp[iComp++];
|
|
||||||
var n = aComp[iComp++];
|
|
||||||
while (c--) {
|
|
||||||
aDst[iDst] = n;
|
|
||||||
iDst += 2;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* The output of a "count,value" pair will never exceed the end of the output array, so as soon as we reach it
|
|
||||||
* the first time, we know it's time to switch to ODD elements, and as soon as we reach it again, we should be
|
|
||||||
* done.
|
|
||||||
*/
|
|
||||||
Component.assert(iDst <= nLength || iComp == aComp.length);
|
|
||||||
if (iDst == nLength) iDst = 1;
|
|
||||||
}
|
|
||||||
Component.assert(aDst.length == nLength);
|
|
||||||
return aDst;
|
|
||||||
};
|
|
||||||
|
|
||||||
State.prototype = {
|
|
||||||
constructor: State,
|
|
||||||
/**
|
/**
|
||||||
* set(id, data)
|
* set(id, data)
|
||||||
*
|
*
|
||||||
|
|
@ -213,13 +76,15 @@ State.prototype = {
|
||||||
* @param {number|string} id
|
* @param {number|string} id
|
||||||
* @param {Object|string} data
|
* @param {Object|string} data
|
||||||
*/
|
*/
|
||||||
set: function(id, data) {
|
set(id, data)
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
this[this.id][id] = data;
|
this.state[id] = data;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
Component.log(e.message);
|
Component.log(e.message);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get(id)
|
* get(id)
|
||||||
*
|
*
|
||||||
|
|
@ -227,43 +92,38 @@ State.prototype = {
|
||||||
* @param {number|string} id
|
* @param {number|string} id
|
||||||
* @return {Object|string|null}
|
* @return {Object|string|null}
|
||||||
*/
|
*/
|
||||||
get: function(id) {
|
get(id)
|
||||||
return this[this.id][id] || null;
|
{
|
||||||
},
|
return this.state[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];
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* data()
|
* data()
|
||||||
*
|
*
|
||||||
* @this {State}
|
* @this {State}
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
data: function() {
|
data()
|
||||||
return this[this.id];
|
{
|
||||||
},
|
return this.state;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* load(s)
|
* load(json)
|
||||||
*
|
*
|
||||||
* WARNING: Make sure you follow this call with either a call to parse() or unload(),
|
* 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.
|
* because any stringified data that we've loaded isn't usable until it's been parsed.
|
||||||
*
|
*
|
||||||
* @this {State}
|
* @this {State}
|
||||||
* @param {Object|string|null} [s]
|
* @param {string|null} [json]
|
||||||
* @return {boolean} true if state exists in localStorage, false if not
|
* @return {boolean} true if state exists in localStorage, false if not
|
||||||
*/
|
*/
|
||||||
load: function(s) {
|
load(json)
|
||||||
if (s) {
|
{
|
||||||
this[this.id] = s;
|
if (json) {
|
||||||
|
this.json = json;
|
||||||
this.fLoaded = true;
|
this.fLoaded = true;
|
||||||
|
this.fParsed = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.fLoaded) {
|
if (this.fLoaded) {
|
||||||
|
|
@ -272,17 +132,18 @@ State.prototype = {
|
||||||
*/
|
*/
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (web.hasLocalStorage()) {
|
if (Web.hasLocalStorage()) {
|
||||||
s = web.getLocalStorageItem(this.key);
|
var s = Web.getLocalStorageItem(this.key);
|
||||||
if (s) {
|
if (s) {
|
||||||
this[this.id] = s;
|
this.json = s;
|
||||||
this.fLoaded = true;
|
this.fLoaded = true;
|
||||||
if (DEBUG) Component.log("localStorage(" + this.key + "): " + s.length + " bytes loaded");
|
if (DEBUG) Component.log("localStorage(" + this.key + "): " + s.length + " bytes loaded");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parse()
|
* parse()
|
||||||
*
|
*
|
||||||
|
|
@ -293,27 +154,33 @@ State.prototype = {
|
||||||
* @this {State}
|
* @this {State}
|
||||||
* @return {boolean} true if successful, false if error
|
* @return {boolean} true if successful, false if error
|
||||||
*/
|
*/
|
||||||
parse: function() {
|
parse()
|
||||||
|
{
|
||||||
var fSuccess = true;
|
var fSuccess = true;
|
||||||
try {
|
if (!this.fParsed) {
|
||||||
this[this.id] = JSON.parse(this[this.id]);
|
try {
|
||||||
} catch (e) {
|
this.state = JSON.parse(this.json);
|
||||||
Component.error(e.message || e);
|
this.fParsed = true;
|
||||||
fSuccess = false;
|
} catch (e) {
|
||||||
|
Component.error(e.message || e);
|
||||||
|
fSuccess = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return fSuccess;
|
return fSuccess;
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* store()
|
* store()
|
||||||
*
|
*
|
||||||
* @this {State}
|
* @this {State}
|
||||||
* @return {boolean} true if successful, false if error
|
* @return {boolean} true if successful, false if error
|
||||||
*/
|
*/
|
||||||
store: function() {
|
store()
|
||||||
|
{
|
||||||
var fSuccess = true;
|
var fSuccess = true;
|
||||||
if (web.hasLocalStorage()) {
|
if (Web.hasLocalStorage()) {
|
||||||
var s = JSON.stringify(this[this.id]);
|
var s = JSON.stringify(this.state);
|
||||||
if (web.setLocalStorageItem(this.key, s)) {
|
if (Web.setLocalStorageItem(this.key, s)) {
|
||||||
if (DEBUG) Component.log("localStorage(" + this.key + "): " + s.length + " bytes stored");
|
if (DEBUG) Component.log("localStorage(" + this.key + "): " + s.length + " bytes stored");
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
|
|
@ -327,20 +194,19 @@ State.prototype = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fSuccess;
|
return fSuccess;
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* toString()
|
* 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}
|
* @this {State}
|
||||||
* @return {string} JSON-encoded state
|
* @return {string} JSON-encoded state
|
||||||
*/
|
*/
|
||||||
toString: function() {
|
toString()
|
||||||
var value = this[this.id];
|
{
|
||||||
return (typeof value == "string"? value : JSON.stringify(value));
|
return this.state? JSON.stringify(this.state) : this.json;
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* unload(parms)
|
* unload(parms)
|
||||||
*
|
*
|
||||||
|
|
@ -351,11 +217,14 @@ State.prototype = {
|
||||||
* @this {State}
|
* @this {State}
|
||||||
* @param {Object} [parms]
|
* @param {Object} [parms]
|
||||||
*/
|
*/
|
||||||
unload: function(parms) {
|
unload(parms)
|
||||||
this[this.id] = {};
|
{
|
||||||
|
this.json = "";
|
||||||
|
this.state = {};
|
||||||
|
this.fLoaded = this.fParsed = false;
|
||||||
if (parms) this.set("parms", parms);
|
if (parms) this.set("parms", parms);
|
||||||
this.fLoaded = false;
|
}
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* clear(fAll)
|
* clear(fAll)
|
||||||
*
|
*
|
||||||
|
|
@ -365,19 +234,164 @@ State.prototype = {
|
||||||
* @this {State}
|
* @this {State}
|
||||||
* @param {boolean} [fAll] true to unconditionally clear ALL localStorage for the current domain
|
* @param {boolean} [fAll] true to unconditionally clear ALL localStorage for the current domain
|
||||||
*/
|
*/
|
||||||
clear: function(fAll) {
|
clear(fAll)
|
||||||
|
{
|
||||||
this.unload();
|
this.unload();
|
||||||
var aKeys = web.getLocalStorageKeys();
|
var aKeys = Web.getLocalStorageKeys();
|
||||||
for (var i = 0; i < aKeys.length; i++) {
|
for (var i = 0; i < aKeys.length; i++) {
|
||||||
var sKey = aKeys[i];
|
var sKey = aKeys[i];
|
||||||
if (sKey && (fAll || sKey.substr(0, this.key.length) == this.key)) {
|
if (sKey && (fAll || sKey.substr(0, this.key.length) == this.key)) {
|
||||||
web.removeLocalStorageItem(sKey);
|
Web.removeLocalStorageItem(sKey);
|
||||||
if (DEBUG) Component.log("localStorage(" + sKey + ") removed");
|
if (DEBUG) Component.log("localStorage(" + sKey + ") removed");
|
||||||
aKeys.splice(i, 1);
|
aKeys.splice(i, 1);
|
||||||
i = 0;
|
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;
|
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
Loading…
Reference in a new issue