123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- //jshint eqnull:true
- define(['./junk-drawer'], function ($goodies) {
- "use strict";
- var inherits = $goodies.inherits;
- var declareJsMethod = $goodies.declareJsMethod;
- var addElement = $goodies.addElement;
- var removeElement = $goodies.removeElement;
- var extend = $goodies.extend;
- MethodCompositionBrik.deps = ["methods"];
- function MethodCompositionBrik (brikz, st) {
- var setLocalMethods = brikz.methods.setLocalMethods;
- var updateMethod = brikz.methods.updateMethod;
- function aliased (selector, method) {
- var result = st.method(method, method.instantiateFn);
- if (method.selector !== selector) {
- result.selector = selector;
- result.source = '"Aliased as ' + selector + '"\n' + method.source;
- }
- result.owner = method.owner;
- return result;
- }
- function deleteKeysFrom (keys, obj) {
- keys.forEach(function (each) {
- delete obj[each];
- });
- }
- function fillTraitTransformation (traitTransformation, obj) {
- // assert(Object.getOwnProperties(obj).length === 0)
- var traitMethods = traitTransformation.trait.methods;
- Object.keys(traitMethods).forEach(function (selector) {
- obj[selector] = aliased(selector, traitMethods[selector]);
- });
- var traitAliases = traitTransformation.aliases;
- if (traitAliases) {
- Object.keys(traitAliases).forEach(function (aliasSelector) {
- var aliasedMethod = traitMethods[traitAliases[aliasSelector]];
- if (aliasedMethod) obj[aliasSelector] = aliased(aliasSelector, aliasedMethod);
- // else delete obj[aliasSelector]; // semantically correct; optimized away
- });
- }
- var traitExclusions = traitTransformation.exclusions;
- if (traitExclusions) {
- deleteKeysFrom(traitExclusions, obj);
- }
- return obj;
- }
- function buildCompositionChain (traitComposition) {
- return traitComposition.reduce(function (soFar, each) {
- return fillTraitTransformation(each, Object.create(soFar));
- }, null);
- }
- st.setTraitComposition = function (traitComposition, traitOrBehavior) {
- var oldLocalMethods = traitOrBehavior.localMethods,
- newLocalMethodsTemplate = Object.create(buildCompositionChain(traitComposition));
- setLocalMethods(traitOrBehavior, extend(newLocalMethodsTemplate, oldLocalMethods));
- (traitOrBehavior.traitComposition || []).forEach(function (each) {
- removeElement(each.trait.traitUsers, traitOrBehavior);
- });
- traitOrBehavior.traitComposition = traitComposition && traitComposition.length ? traitComposition : null;
- (traitOrBehavior.traitComposition || []).forEach(function (each) {
- addElement(each.trait.traitUsers, traitOrBehavior);
- });
- };
- function aliasesOfSelector (selector, traitAliases) {
- if (!traitAliases) return [selector];
- var result = Object.keys(traitAliases).filter(function (aliasSelector) {
- return traitAliases[aliasSelector] === selector
- });
- if (!traitAliases[selector]) result.push(selector);
- return result;
- }
- function applyTraitMethodAddition (selector, method, traitTransformation, obj) {
- var changes = aliasesOfSelector(selector, traitTransformation.aliases);
- changes.forEach(function (aliasSelector) {
- obj[aliasSelector] = aliased(aliasSelector, method);
- });
- var traitExclusions = traitTransformation.exclusions;
- if (traitExclusions) {
- deleteKeysFrom(traitExclusions, obj);
- }
- return changes;
- }
- function applyTraitMethodDeletion (selector, traitTransformation, obj) {
- var changes = aliasesOfSelector(selector, traitTransformation.aliases);
- deleteKeysFrom(changes, obj);
- return changes;
- }
- function traitMethodChanged (selector, method, trait, traitOrBehavior) {
- var traitComposition = traitOrBehavior.traitComposition,
- chain = traitOrBehavior.localMethods,
- changes = [];
- for (var i = traitComposition.length - 1; i >= 0; --i) {
- chain = Object.getPrototypeOf(chain);
- var traitTransformation = traitComposition[i];
- if (traitTransformation.trait !== trait) continue;
- changes.push.apply(changes, method ?
- applyTraitMethodAddition(selector, method, traitTransformation, chain) :
- applyTraitMethodDeletion(selector, traitTransformation, chain));
- }
- // assert(chain === null);
- changes.forEach(function (each) {
- updateMethod(each, traitOrBehavior);
- });
- }
- this.traitMethodChanged = traitMethodChanged;
- }
- function LanguageFactory (specialConstructors, emit) {
- function declareEvent (name) {
- declareJsMethod(emit, name);
- }
- TraitsBrik.deps = ["behaviorals", "methods", "composition", "root"];
- function TraitsBrik (brikz, st) {
- var SmalltalkObject = brikz.root.Object;
- var setupMethods = brikz.methods.setupMethods;
- var traitMethodChanged = brikz.composition.traitMethodChanged;
- var buildTraitOrClass = brikz.behaviorals.buildTraitOrClass;
- function SmalltalkTrait () {
- }
- specialConstructors.Trait = inherits(SmalltalkTrait, SmalltalkObject);
- SmalltalkTrait.prototype.trait = true;
- declareJsMethod(SmalltalkTrait.prototype, "toString");
- declareJsMethod(SmalltalkTrait.prototype, "added");
- declareJsMethod(SmalltalkTrait.prototype, "removed");
- declareJsMethod(SmalltalkTrait.prototype, "methodAdded");
- declareJsMethod(SmalltalkTrait.prototype, "methodRemoved");
- SmalltalkTrait.prototype.toString = function () {
- return 'Smalltalk Trait ' + this.name;
- };
- SmalltalkTrait.prototype.methodAdded = function (method) {
- propagateMethodChange(this, method.selector, method);
- };
- SmalltalkTrait.prototype.methodRemoved = function (method) {
- propagateMethodChange(this, method.selector, null);
- };
- function propagateMethodChange (trait, selector, method) {
- trait.traitUsers.forEach(function (each) {
- traitMethodChanged(selector, method, trait, each);
- });
- }
- function traitBuilder (traitName, category) {
- return {
- name: traitName,
- make: function () {
- var that = new SmalltalkTrait();
- that.name = traitName;
- that.category = category;
- that.traitUsers = [];
- setupMethods(that);
- return that;
- },
- updateExisting: function (trait) {
- }
- };
- }
- st.addTrait = function (className, category) {
- return buildTraitOrClass(traitBuilder(className, category));
- };
- }
- ClassModelBrik.deps = ["root", "nil"];
- function ClassModelBrik (brikz, st) {
- var SmalltalkRoot = brikz.root.Root;
- var SmalltalkObject = brikz.root.Object;
- var nilAsReceiver = brikz.nil.nilAsReceiver;
- function SmalltalkBehavior () {
- }
- function SmalltalkClass () {
- }
- function SmalltalkMetaclass () {
- }
- this.newMetaclass = function () {
- return new SmalltalkMetaclass();
- };
- specialConstructors.Behavior = inherits(SmalltalkBehavior, SmalltalkObject);
- specialConstructors.Class = inherits(SmalltalkClass, SmalltalkBehavior);
- specialConstructors.Metaclass = inherits(SmalltalkMetaclass, SmalltalkBehavior);
- SmalltalkMetaclass.prototype.meta = true;
- declareJsMethod(SmalltalkClass.prototype, "toString");
- declareJsMethod(SmalltalkMetaclass.prototype, "toString");
- declareJsMethod(SmalltalkClass.prototype, "added");
- declareJsMethod(SmalltalkClass.prototype, "removed");
- declareJsMethod(SmalltalkBehavior.prototype, "methodAdded");
- declareJsMethod(SmalltalkBehavior.prototype, "methodRemoved");
- SmalltalkClass.prototype.toString = function () {
- return 'Smalltalk ' + this.name;
- };
- SmalltalkMetaclass.prototype.toString = function () {
- return 'Smalltalk Metaclass ' + this.instanceClass.name;
- };
- declareEvent("classCreated");
- SmalltalkClass.prototype.added = function () {
- registerToSuperclass(this);
- emit.classCreated(this);
- };
- SmalltalkClass.prototype.removed = function () {
- unregisterFromSuperclass(this);
- };
- declareEvent("behaviorMethodAdded");
- SmalltalkBehavior.prototype.methodAdded = function (method) {
- emit.behaviorMethodAdded(method, this);
- };
- declareEvent("behaviorMethodRemoved");
- SmalltalkBehavior.prototype.methodRemoved = function (method) {
- emit.behaviorMethodRemoved(method, this);
- };
- // Fake root class of the system.
- // Effective superclass of all classes created with `nil subclass: ...`.
- var nilAsClass = this.nilAsClass = {
- fn: SmalltalkRoot,
- subclasses: [],
- a$cls: {fn: SmalltalkClass}
- };
- this.bootstrapHierarchy = function (realClass) {
- nilAsClass.a$cls = realClass;
- nilAsClass.subclasses.forEach(function (each) {
- each.a$cls.superclass = realClass;
- registerToSuperclass(each.a$cls);
- });
- };
- function registerToSuperclass (klass) {
- addElement((klass.superclass || nilAsClass).subclasses, klass);
- }
- function unregisterFromSuperclass (klass) {
- removeElement((klass.superclass || nilAsClass).subclasses, klass);
- }
- function metaSubclasses (metaclass) {
- return metaclass.instanceClass.subclasses
- .filter(function (each) {
- return !each.meta;
- })
- .map(function (each) {
- return each.a$cls;
- });
- }
- st.metaSubclasses = metaSubclasses;
- st.traverseClassTree = function (klass, fn) {
- var queue = [klass], sentinel = {};
- for (var i = 0; i < queue.length; ++i) {
- var item = queue[i];
- if (fn(item, sentinel) === sentinel) continue;
- var subclasses = item.meta ? metaSubclasses(item) : item.subclasses;
- queue.push.apply(queue, subclasses);
- }
- };
- /**
- * This function is used all over the compiled amber code.
- * It takes any value (JavaScript or Smalltalk)
- * and returns a proper Amber Smalltalk receiver.
- *
- * null or undefined -> nilAsReceiver,
- * object having Smalltalk signature -> unchanged,
- * otherwise wrapped foreign (JS) object
- */
- this.asReceiver = function (o) {
- if (o == null) return nilAsReceiver;
- else if (o.a$cls != null) return o;
- else return st.wrapJavaScript(o);
- };
- // TODO remove, .iVarNames backward compatibility
- this.__init__ = function () {
- brikz.classConstruction.iVarNamesCompat(SmalltalkBehavior);
- };
- }
- ClassConstructionBrik.deps = ["classModel", "behaviorals", "methods"];
- function ClassConstructionBrik (brikz, st) {
- var nilAsClass = brikz.classModel.nilAsClass;
- var newMetaclass = brikz.classModel.newMetaclass;
- var buildTraitOrClass = brikz.behaviorals.buildTraitOrClass;
- var setupMethods = brikz.methods.setupMethods;
- var removeTraitOrClass = brikz.behaviorals.removeTraitOrClass;
- declareEvent("slotsChanged");
- function setSlots (klass, slots) {
- slots.forEach(function (name) {
- if (!name.match(/^[a-zA-Z][a-zA-Z0-9]*$/))
- throw new Error("Wrong identifier name: " + name);
- });
- klass.slots = slots;
- emit.slotsChanged(klass);
- }
- st.setSlots = setSlots;
- // TODO remove, .iVarNames backward compatibility
- this.iVarNamesCompat = function (SmalltalkBehavior) {
- Object.defineProperty(SmalltalkBehavior.prototype, "iVarNames", {
- enumerable: true,
- configurable: true,
- get: function () {
- return this.slots;
- },
- set: function (instanceVariableNames) {
- setSlots(this, instanceVariableNames);
- }
- });
- };
- /* Smalltalk class creation. A class is an instance of an automatically
- created metaclass object. Newly created classes (not their metaclass)
- should be added to the system, see smalltalk.addClass().
- Superclass linking is *not* handled here, see api.initialize() */
- function classBuilder (className, superclass, category, fn) {
- var logicalSuperclass = superclass;
- if (superclass == null || superclass.a$nil) {
- superclass = nilAsClass;
- logicalSuperclass = null;
- }
- function klass () {
- var that = metaclass().instanceClass;
- that.superclass = logicalSuperclass;
- that.fn = fn || inherits(function () {
- }, superclass.fn);
- that.slots = [];
- that.name = className;
- that.category = category;
- that.subclasses = [];
- setupMethods(that);
- return that;
- }
- function metaclass () {
- var that = newMetaclass();
- that.superclass = superclass.a$cls;
- that.fn = inherits(function () {
- }, that.superclass.fn);
- that.slots = [];
- that.instanceClass = new that.fn();
- wireKlass(that);
- setupMethods(that);
- return that;
- }
- return {
- name: className,
- make: klass,
- updateExisting: function (klass) {
- if (logicalSuperclass == null && klass.superclass != null || logicalSuperclass != null && klass.superclass !== logicalSuperclass || fn != null && fn !== klass.fn)
- throw new Error("Incompatible change of class: " + klass.name);
- }
- };
- }
- function wireKlass (klass) {
- Object.defineProperty(klass.fn.prototype, "a$cls", {
- value: klass,
- enumerable: false, configurable: true, writable: true
- });
- }
- this.wireKlass = wireKlass;
- /* Add a class to the system, creating a new one if needed.
- A Package is lazily created if one with given name does not exist. */
- st.addClass = function (className, superclass, category) {
- // TODO remove, backward compatibility
- if (arguments[3]) {
- var added = st.addClass(className, superclass, arguments[3]);
- setSlots(added, category);
- return added;
- }
- // While subclassing nil is allowed, it might be an error, so
- // warn about it.
- if (typeof superclass === 'undefined' || superclass && superclass.a$nil) {
- console.warn('Compiling ' + className + ' as a subclass of `nil`. A dependency might be missing.');
- }
- return buildTraitOrClass(classBuilder(className, superclass, category, specialConstructors[className]));
- };
- st.removeClass = removeTraitOrClass;
- }
- /* Making smalltalk that can load */
- function configure (brikz) {
- brikz.traits = TraitsBrik;
- brikz.composition = MethodCompositionBrik;
- brikz.classModel = ClassModelBrik;
- brikz.classConstruction = ClassConstructionBrik;
- brikz();
- }
- return {configure: configure};
- }
- return LanguageFactory;
- });
|