1
0
mirror of https://github.com/pkimpel/retro-b5500.git synced 2026-02-14 20:16:18 +00:00
Files
pkimpel.retro-b5500/tools/misc/compose.js
Paul Kimpel c7d278d8d2 Commit retro-b5500 release 1.05:
1. Implement new method to open pop-up windows that schedules pop-up opens through a queue, delaying between opens, and retrying with longer delays when the opens fail. This is to satisfy new restrictions on pop-ups introduced in Apple Safari 11.0, even when the browser is configured to enable pop-ups.
2. Remove the Application Cache facility. This API has been deprecated and will be removed from browsers.
3. Implement improved setCallback() asynchronous timing mechanism with a new Promise-based approach developed for the retro-220 emulator.
4. Correct Processor memory access logic to avoid causing an Invalid Address interrupt in Control State.
5. Correct Processor Exit Character Mode (XX00) by implementing it separately from Word Mode exits.
6. Implement separate jump mechanisms for Word and Character Mode to correct edge-case errors.
7. Make slight correction to clock counting in singlePrecisionAdd.
8. Fix bug in double-clicking the "stacker" of the card punch to extract its contents.
9. Disable I/O descriptor word count in IOUnit for card reader/punch operations; remove extraneous blank card between card-load programs from the COLDSTART-XIII.card and COOLSTART-XIII.card decks that was previously required to prevent "eating" the second program's boot card.
10. Remove extraneous whitespace from B5500FramePaper used for most text-only windows and frames.
11. Compute HPT disk latency based on current timestamp rather than a random number.
12. Correct top-of-form handling in B5500LinePrinter.
13. Correct annimation of tape reel in B5500MagTapeDrive.
14. Replace the emulator's custom bindMethod() utility routine with Function.bind().
15. Replace the emulator's custom DOM className utility routines by DOM classList methods.
16. Add P1 S and F register values to the internal tape dump caption; improve the dump tape's label records.
17. Give the annunciators on the ConsolePanel a dark gray presence when they are not lit.
18. Correct the method to "focus" the ConsolePanel window after the SPO initializes and becomes ready.
19. Clean up line delimiters in source and make consistent for Windows systems.
2018-03-17 15:27:51 -07:00

291 lines
12 KiB
JavaScript

/*
* ComposeJS, object composition for JavaScript, featuring
* JavaScript-style prototype inheritance and composition, multiple inheritance,
* mixin and traits-inspired conflict resolution and composition
*/
(function(define){
"use strict";
define([], function(){
// function for creating instances from a prototype
function Create(){
}
var delegate = Object.create ?
function(proto){
return Object.create(typeof proto == "function" ? proto.prototype : proto || Object.prototype);
} :
function(proto){
Create.prototype = typeof proto == "function" ? proto.prototype : proto;
var instance = new Create();
Create.prototype = null;
return instance;
};
function validArg(arg){
if(!arg){
throw new Error("Compose arguments must be functions or objects");
}
return arg;
}
// this does the work of combining mixins/prototypes
function mixin(instance, args, i){
// use prototype inheritance for first arg
var value, argsLength = args.length;
for(; i < argsLength; i++){
var arg = args[i];
if(typeof arg == "function"){
// the arg is a function, use the prototype for the properties
var prototype = arg.prototype;
for(var key in prototype){
value = prototype[key];
var own = prototype.hasOwnProperty(key);
if(typeof value == "function" && key in instance && value !== instance[key]){
var existing = instance[key];
if(value == required){
// it is a required value, and we have satisfied it
value = existing;
}
else if(!own){
// if it is own property, it is considered an explicit override
// TODO: make faster calls on this, perhaps passing indices and caching
if(isInMethodChain(value, key, getBases([].slice.call(args, 0, i), true))){
// this value is in the existing method's override chain, we can use the existing method
value = existing;
}else if(!isInMethodChain(existing, key, getBases([arg], true))){
// the existing method is not in the current override chain, so we are left with a conflict
console.error("Conflicted method " + key + ", final composer must explicitly override with correct method.");
}
}
}
if(value && value.install && own && !isInMethodChain(existing, key, getBases([arg], true))){
// apply modifier
value.install.call(instance, key);
}else{
instance[key] = value;
}
}
}else{
// it is an object, copy properties, looking for modifiers
for(var key in validArg(arg)){
var value = arg[key];
if(typeof value == "function"){
if(value.install){
// apply modifier
value.install.call(instance, key);
continue;
}
if(key in instance){
if(value == required){
// required requirement met
continue;
}
}
}
// add it to the instance
instance[key] = value;
}
}
}
return instance;
}
// allow for override (by es5 module)
Compose._setMixin = function(newMixin){
mixin = newMixin;
};
function isInMethodChain(method, name, prototypes){
// searches for a method in the given prototype hierarchy
for(var i = 0; i < prototypes.length;i++){
var prototype = prototypes[i];
if(prototype[name] == method){
// found it
return true;
}
}
}
// Decorator branding
function Decorator(install, direct){
function Decorator(){
if(direct){
return direct.apply(this, arguments);
}
throw new Error("Decorator not applied");
}
Decorator.install = install;
return Decorator;
}
Compose.Decorator = Decorator;
// aspect applier
function aspect(handler){
return function(advice){
return Decorator(function install(key){
var baseMethod = this[key];
(advice = this[key] = baseMethod ? handler(this, baseMethod, advice) : advice).install = install;
}, advice);
};
};
// around advice, useful for calling super methods too
Compose.around = aspect(function(target, base, advice){
return advice.call(target, base);
});
Compose.before = aspect(function(target, base, advice){
return function(){
var results = advice.apply(this, arguments);
if(results !== stop){
return base.apply(this, results || arguments);
}
};
});
var stop = Compose.stop = {};
var undefined;
Compose.after = aspect(function(target, base, advice){
return function(){
var results = base.apply(this, arguments);
var adviceResults = advice.apply(this, arguments);
return adviceResults === undefined ? results : adviceResults;
};
});
// rename Decorator for calling super methods
Compose.from = function(trait, fromKey){
if(fromKey){
return (typeof trait == "function" ? trait.prototype : trait)[fromKey];
}
return Decorator(function(key){
if(!(this[key] = (typeof trait == "string" ? this[trait] :
(typeof trait == "function" ? trait.prototype : trait)[fromKey || key]))){
throw new Error("Source method " + fromKey + " was not available to be renamed to " + key);
}
});
};
// Composes an instance
Compose.create = function(base){
// create the instance
var instance = mixin(delegate(base), arguments, 1);
var argsLength = arguments.length;
// for go through the arguments and call the constructors (with no args)
for(var i = 0; i < argsLength; i++){
var arg = arguments[i];
if(typeof arg == "function"){
instance = arg.call(instance) || instance;
}
}
return instance;
}
// The required function, just throws an error if not overriden
function required(){
throw new Error("This method is required and no implementation has been provided");
};
Compose.required = required;
// get the value of |this| for direct function calls for this mode (strict in ES5)
function extend(){
var args = [this];
args.push.apply(args, arguments);
return Compose.apply(0, args);
}
// Compose a constructor
function Compose(base){
var args = arguments;
var prototype = (args.length < 2 && typeof args[0] != "function") ?
args[0] : // if there is just a single argument object, just use that as the prototype
mixin(delegate(validArg(base)), args, 1); // normally create a delegate to start with
function Constructor(){
var instance;
if(this instanceof Constructor){
// called with new operator, can proceed as is
instance = this;
}else{
// we allow for direct calls without a new operator, in this case we need to
// create the instance ourself.
Create.prototype = prototype;
instance = new Create();
}
// call all the constructors with the given arguments
for(var i = 0; i < constructorsLength; i++){
var constructor = constructors[i];
var result = constructor.apply(instance, arguments);
if(typeof result == "object"){
if(result instanceof Constructor){
instance = result;
}else{
for(var j in result){
if(result.hasOwnProperty(j)){
instance[j] = result[j];
}
}
}
}
}
return instance;
}
// create a function that can retrieve the bases (constructors or prototypes)
Constructor._getBases = function(prototype){
return prototype ? prototypes : constructors;
};
// now get the prototypes and the constructors
var constructors = getBases(args),
constructorsLength = constructors.length;
if(typeof args[args.length - 1] == "object"){
args[args.length - 1] = prototype;
}
var prototypes = getBases(args, true);
Constructor.extend = extend;
if(!Compose.secure){
prototype.constructor = Constructor;
}
Constructor.prototype = prototype;
return Constructor;
};
Compose.apply = function(thisObject, args){
// apply to the target
return thisObject ?
mixin(thisObject, args, 0) : // called with a target object, apply the supplied arguments as mixins to the target object
extend.apply.call(Compose, 0, args); // get the Function.prototype apply function, call() it to apply arguments to Compose (the extend doesn't matter, just a handle way to grab apply, since we can't get it off of Compose)
};
Compose.call = function(thisObject){
// call() should correspond with apply behavior
return mixin(thisObject, arguments, 1);
};
function getBases(args, prototype){
// this function registers a set of constructors for a class, eliminating duplicate
// constructors that may result from diamond construction for classes (B->A, C->A, D->B&C, then D() should only call A() once)
var bases = [];
function iterate(args, checkChildren){
outer:
for(var i = 0; i < args.length; i++){
var arg = args[i];
var target = prototype && typeof arg == "function" ?
arg.prototype : arg;
if(prototype || typeof arg == "function"){
var argGetBases = checkChildren && arg._getBases;
if(argGetBases){
iterate(argGetBases(prototype)); // don't need to check children for these, this should be pre-flattened
}else{
for(var j = 0; j < bases.length; j++){
if(target == bases[j]){
continue outer;
}
}
bases.push(target);
}
}
}
}
iterate(args, true);
return bases;
}
// returning the export of the module
return Compose;
});
})(typeof define != "undefined" ?
define: // AMD/RequireJS format if available
function(deps, factory){
if(typeof module !="undefined"){
module.exports = factory(); // CommonJS environment, like NodeJS
// require("./configure");
}else{
Compose = factory(); // raw script, assign to Compose global
}
});