/* ==================================================================== | | Amber Smalltalk | http://amber-lang.net | ====================================================================== ====================================================================== | | Copyright (c) 2010-2014 | Nicolas Petton | | Copyright (c) 2012-2017 | The Amber team https://lolg.it/org/amber/members | Amber contributors (see /CONTRIBUTORS) | | Amber is released under the MIT license | | Permission is hereby granted, free of charge, to any person obtaining | a copy of this software and associated documentation files (the | 'Software'), to deal in the Software without restriction, including | without limitation the rights to use, copy, modify, merge, publish, | distribute, sublicense, and/or sell copies of the Software, and to | permit persons to whom the Software is furnished to do so, subject to | the following conditions: | | The above copyright notice and this permission notice shall be | included in all copies or substantial portions of the Software. | | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ==================================================================== */ //jshint eqnull:true define(['./compatibility' /* TODO remove */], function () { "use strict"; function inherits (child, parent) { child.prototype = Object.create(parent.prototype, { constructor: { value: child, enumerable: false, configurable: true, writable: true } }); return child; } function defineMethod (klass, name, method) { Object.defineProperty(klass.prototype, name, { value: method, enumerable: false, configurable: true, writable: true }); } TraitsBrik.deps = ["behaviors", "methods", "composition", "arraySet", "root"]; function TraitsBrik (brikz, st) { var coreFns = brikz.root.coreFns; var SmalltalkObject = brikz.root.Object; var setupMethods = brikz.methods.setupMethods; var traitMethodChanged = brikz.composition.traitMethodChanged; var buildTraitOrClass = brikz.behaviors.buildTraitOrClass; var addElement = brikz.arraySet.addElement; var removeElement = brikz.arraySet.removeElement; function SmalltalkTrait () { } coreFns.Trait = inherits(SmalltalkTrait, SmalltalkObject); SmalltalkTrait.prototype.trait = true; defineMethod(SmalltalkTrait, "toString", function () { return 'Smalltalk Trait ' + this.className; }); defineMethod(SmalltalkTrait, "added", function () { if (st._traitAdded) st._traitAdded(this); }); defineMethod(SmalltalkTrait, "removed", function () { if (st._traitRemoved) st._traitRemoved(this); }); defineMethod(SmalltalkTrait, "methodAdded", function (method) { var self = this; this.traitUsers.forEach(function (each) { traitMethodChanged(method.selector, method, self, each); }); if (st._traitMethodAdded) st._traitMethodAdded(method, this); }); defineMethod(SmalltalkTrait, "methodRemoved", function (method) { var self = this; this.traitUsers.forEach(function (each) { traitMethodChanged(method.selector, null, self, each); }); if (st._traitMethodRemoved) st._traitMethodRemoved(method, this); }); defineMethod(SmalltalkTrait, "addUser", function (traitOrBehavior) { addElement(this.traitUsers, traitOrBehavior); }); defineMethod(SmalltalkTrait, "removeUser", function (traitOrBehavior) { removeElement(this.traitUsers, traitOrBehavior); }); function traitBuilder (className) { return { className: className, make: function () { var that = new SmalltalkTrait(); that.className = className; that.traitUsers = []; setupMethods(that); return that; }, updateExisting: function (trait) { } }; } st.addTrait = function (className, pkgName) { return buildTraitOrClass(pkgName, traitBuilder(className)); }; } MethodCompositionBrik.deps = ["methods"]; function MethodCompositionBrik (brikz, st) { var updateMethod = brikz.methods.updateMethod; function aliased (selector, method) { if (method.selector === selector) return method; var result = st.method({ selector: selector, args: method.args, protocol: method.protocol, source: '"Aliased as ' + selector + '"\n' + method.source, messageSends: method.messageSends, referencesClasses: method.referencedClasses, fn: method.fn }); result.methodClass = method.methodClass; 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] = 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, newLocalMethods = Object.create(buildCompositionChain(traitComposition)); Object.keys(oldLocalMethods).forEach(function (selector) { newLocalMethods[selector] = oldLocalMethods[selector]; }); var selector; traitOrBehavior.localMethods = newLocalMethods; for (selector in newLocalMethods) { updateMethod(selector, traitOrBehavior); } for (selector in oldLocalMethods) { updateMethod(selector, traitOrBehavior); } (traitOrBehavior.traitComposition || []).forEach(function (each) { each.trait.removeUser(traitOrBehavior); }); traitOrBehavior.traitComposition = traitComposition && traitComposition.length ? traitComposition : null; (traitOrBehavior.traitComposition || []).forEach(function (each) { each.trait.addUser(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; } ClassesBrik.deps = ["root", "behaviors", "methods", "arraySet", "smalltalkGlobals"]; function ClassesBrik (brikz, st) { var SmalltalkRoot = brikz.root.Root; var coreFns = brikz.root.coreFns; var globals = brikz.smalltalkGlobals.globals; var SmalltalkObject = brikz.root.Object; var buildTraitOrClass = brikz.behaviors.buildTraitOrClass; var setupMethods = brikz.methods.setupMethods; var removeTraitOrClass = brikz.behaviors.removeTraitOrClass; var addElement = brikz.arraySet.addElement; var removeElement = brikz.arraySet.removeElement; function SmalltalkBehavior () { } function SmalltalkClass () { } function SmalltalkMetaclass () { } coreFns.Behavior = inherits(SmalltalkBehavior, SmalltalkObject); coreFns.Class = inherits(SmalltalkClass, SmalltalkBehavior); coreFns.Metaclass = inherits(SmalltalkMetaclass, SmalltalkBehavior); // Fake root class of the system. // Effective superclass of all classes created with `nil subclass: ...`. var nilAsClass = this.nilAsClass = { fn: SmalltalkRoot, a$cls: {fn: SmalltalkClass}, klass: {fn: SmalltalkClass} }; SmalltalkMetaclass.prototype.meta = true; defineMethod(SmalltalkClass, "toString", function () { return 'Smalltalk ' + this.className; }); defineMethod(SmalltalkMetaclass, "toString", function () { return 'Smalltalk Metaclass ' + this.instanceClass.className; }); defineMethod(SmalltalkClass, "added", function () { addSubclass(this); if (st._classAdded) st._classAdded(this); }); defineMethod(SmalltalkClass, "removed", function () { if (st._classRemoved) st._classRemoved(this); removeSubclass(this); }); defineMethod(SmalltalkBehavior, "methodAdded", function (method) { if (st._behaviorMethodAdded) st._behaviorMethodAdded(method, this); }); defineMethod(SmalltalkBehavior, "methodRemoved", function (method) { if (st._behaviorMethodRemoved) st._behaviorMethodRemoved(method, this); }); this.bootstrapHierarchy = function () { var nilSubclasses = [globals.ProtoObject]; nilAsClass.a$cls = nilAsClass.klass = globals.Class; nilSubclasses.forEach(function (each) { each.a$cls.superclass = globals.Class; addSubclass(each.a$cls); }); }; /* 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, iVarNames, 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.iVarNames = iVarNames || []; that.className = className; that.subclasses = []; setupMethods(that); return that; } function metaclass () { var that = new SmalltalkMetaclass(); that.superclass = superclass.a$cls; that.fn = inherits(function () { }, that.superclass.fn); that.iVarNames = []; that.instanceClass = new that.fn(); wireKlass(that); setupMethods(that); return that; } return { className: className, make: klass, updateExisting: function (klass) { if (klass.superclass == logicalSuperclass && (!fn || fn === klass.fn)) { if (iVarNames) klass.iVarNames = iVarNames; } else throw new Error("Incompatible change of class: " + klass.className); } }; } function wireKlass (klass) { Object.defineProperty(klass.fn.prototype, "a$cls", { value: klass, enumerable: false, configurable: true, writable: true }); Object.defineProperty(klass.fn.prototype, "klass", { 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, iVarNames, pkgName) { // 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(pkgName, classBuilder(className, superclass, iVarNames, coreFns[className])); }; st.removeClass = removeTraitOrClass; function addSubclass (klass) { if (klass.superclass) { addElement(klass.superclass.subclasses, klass); } } function removeSubclass (klass) { if (klass.superclass) { removeElement(klass.superclass.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); } }; } /* Making smalltalk that can load */ function configureWithHierarchy (brikz) { brikz.traits = TraitsBrik; brikz.composition = MethodCompositionBrik; brikz.classes = ClassesBrik; brikz.rebuild(); } return configureWithHierarchy; });