kernel-language.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. //jshint eqnull:true
  2. define(['./junk-drawer'], function ($goodies) {
  3. "use strict";
  4. var inherits = $goodies.inherits;
  5. var declareJsMethod = $goodies.declareJsMethod;
  6. var addElement = $goodies.addElement;
  7. var removeElement = $goodies.removeElement;
  8. var extend = $goodies.extend;
  9. MethodCompositionBrik.deps = ["methods"];
  10. function MethodCompositionBrik (brikz, st) {
  11. var setLocalMethods = brikz.methods.setLocalMethods;
  12. var updateMethod = brikz.methods.updateMethod;
  13. function aliased (selector, method) {
  14. var result = st.method(method, method.instantiateFn);
  15. if (method.selector !== selector) {
  16. result.selector = selector;
  17. result.source = '"Aliased as ' + selector + '"\n' + method.source;
  18. }
  19. result.owner = method.owner;
  20. return result;
  21. }
  22. function deleteKeysFrom (keys, obj) {
  23. keys.forEach(function (each) {
  24. delete obj[each];
  25. });
  26. }
  27. function fillTraitTransformation (traitTransformation, obj) {
  28. // assert(Object.getOwnProperties(obj).length === 0)
  29. var traitMethods = traitTransformation.trait.methods;
  30. Object.keys(traitMethods).forEach(function (selector) {
  31. obj[selector] = aliased(selector, traitMethods[selector]);
  32. });
  33. var traitAliases = traitTransformation.aliases;
  34. if (traitAliases) {
  35. Object.keys(traitAliases).forEach(function (aliasSelector) {
  36. var aliasedMethod = traitMethods[traitAliases[aliasSelector]];
  37. if (aliasedMethod) obj[aliasSelector] = aliased(aliasSelector, aliasedMethod);
  38. // else delete obj[aliasSelector]; // semantically correct; optimized away
  39. });
  40. }
  41. var traitExclusions = traitTransformation.exclusions;
  42. if (traitExclusions) {
  43. deleteKeysFrom(traitExclusions, obj);
  44. }
  45. return obj;
  46. }
  47. function buildCompositionChain (traitComposition) {
  48. return traitComposition.reduce(function (soFar, each) {
  49. return fillTraitTransformation(each, Object.create(soFar));
  50. }, null);
  51. }
  52. st.setTraitComposition = function (traitComposition, traitOrBehavior) {
  53. var oldLocalMethods = traitOrBehavior.localMethods,
  54. newLocalMethodsTemplate = Object.create(buildCompositionChain(traitComposition));
  55. setLocalMethods(traitOrBehavior, extend(newLocalMethodsTemplate, oldLocalMethods));
  56. (traitOrBehavior.traitComposition || []).forEach(function (each) {
  57. removeElement(each.trait.traitUsers, traitOrBehavior);
  58. });
  59. traitOrBehavior.traitComposition = traitComposition && traitComposition.length ? traitComposition : null;
  60. (traitOrBehavior.traitComposition || []).forEach(function (each) {
  61. addElement(each.trait.traitUsers, traitOrBehavior);
  62. });
  63. };
  64. function aliasesOfSelector (selector, traitAliases) {
  65. if (!traitAliases) return [selector];
  66. var result = Object.keys(traitAliases).filter(function (aliasSelector) {
  67. return traitAliases[aliasSelector] === selector
  68. });
  69. if (!traitAliases[selector]) result.push(selector);
  70. return result;
  71. }
  72. function applyTraitMethodAddition (selector, method, traitTransformation, obj) {
  73. var changes = aliasesOfSelector(selector, traitTransformation.aliases);
  74. changes.forEach(function (aliasSelector) {
  75. obj[aliasSelector] = aliased(aliasSelector, method);
  76. });
  77. var traitExclusions = traitTransformation.exclusions;
  78. if (traitExclusions) {
  79. deleteKeysFrom(traitExclusions, obj);
  80. }
  81. return changes;
  82. }
  83. function applyTraitMethodDeletion (selector, traitTransformation, obj) {
  84. var changes = aliasesOfSelector(selector, traitTransformation.aliases);
  85. deleteKeysFrom(changes, obj);
  86. return changes;
  87. }
  88. function traitMethodChanged (selector, method, trait, traitOrBehavior) {
  89. var traitComposition = traitOrBehavior.traitComposition,
  90. chain = traitOrBehavior.localMethods,
  91. changes = [];
  92. for (var i = traitComposition.length - 1; i >= 0; --i) {
  93. chain = Object.getPrototypeOf(chain);
  94. var traitTransformation = traitComposition[i];
  95. if (traitTransformation.trait !== trait) continue;
  96. changes.push.apply(changes, method ?
  97. applyTraitMethodAddition(selector, method, traitTransformation, chain) :
  98. applyTraitMethodDeletion(selector, traitTransformation, chain));
  99. }
  100. // assert(chain === null);
  101. changes.forEach(function (each) {
  102. updateMethod(each, traitOrBehavior);
  103. });
  104. }
  105. this.traitMethodChanged = traitMethodChanged;
  106. }
  107. function LanguageFactory (specialConstructors, emit) {
  108. function declareEvent (name) {
  109. declareJsMethod(emit, name);
  110. }
  111. TraitsBrik.deps = ["behaviorals", "methods", "composition", "root"];
  112. function TraitsBrik (brikz, st) {
  113. var SmalltalkObject = brikz.root.Object;
  114. var setupMethods = brikz.methods.setupMethods;
  115. var traitMethodChanged = brikz.composition.traitMethodChanged;
  116. var buildTraitOrClass = brikz.behaviorals.buildTraitOrClass;
  117. function SmalltalkTrait () {
  118. }
  119. specialConstructors.Trait = inherits(SmalltalkTrait, SmalltalkObject);
  120. SmalltalkTrait.prototype.trait = true;
  121. declareJsMethod(SmalltalkTrait.prototype, "toString");
  122. declareJsMethod(SmalltalkTrait.prototype, "added");
  123. declareJsMethod(SmalltalkTrait.prototype, "removed");
  124. declareJsMethod(SmalltalkTrait.prototype, "methodAdded");
  125. declareJsMethod(SmalltalkTrait.prototype, "methodRemoved");
  126. SmalltalkTrait.prototype.toString = function () {
  127. return 'Smalltalk Trait ' + this.name;
  128. };
  129. SmalltalkTrait.prototype.methodAdded = function (method) {
  130. propagateMethodChange(this, method.selector, method);
  131. };
  132. SmalltalkTrait.prototype.methodRemoved = function (method) {
  133. propagateMethodChange(this, method.selector, null);
  134. };
  135. function propagateMethodChange (trait, selector, method) {
  136. trait.traitUsers.forEach(function (each) {
  137. traitMethodChanged(selector, method, trait, each);
  138. });
  139. }
  140. function traitBuilder (traitName, category) {
  141. return {
  142. name: traitName,
  143. make: function () {
  144. var that = new SmalltalkTrait();
  145. that.name = traitName;
  146. that.category = category;
  147. that.traitUsers = [];
  148. setupMethods(that);
  149. return that;
  150. },
  151. updateExisting: function (trait) {
  152. }
  153. };
  154. }
  155. st.addTrait = function (className, category) {
  156. return buildTraitOrClass(traitBuilder(className, category));
  157. };
  158. }
  159. ClassModelBrik.deps = ["root", "nil"];
  160. function ClassModelBrik (brikz, st) {
  161. var SmalltalkRoot = brikz.root.Root;
  162. var SmalltalkObject = brikz.root.Object;
  163. var nilAsReceiver = brikz.nil.nilAsReceiver;
  164. function SmalltalkBehavior () {
  165. }
  166. function SmalltalkClass () {
  167. }
  168. function SmalltalkMetaclass () {
  169. }
  170. this.newMetaclass = function () {
  171. return new SmalltalkMetaclass();
  172. };
  173. specialConstructors.Behavior = inherits(SmalltalkBehavior, SmalltalkObject);
  174. specialConstructors.Class = inherits(SmalltalkClass, SmalltalkBehavior);
  175. specialConstructors.Metaclass = inherits(SmalltalkMetaclass, SmalltalkBehavior);
  176. SmalltalkMetaclass.prototype.meta = true;
  177. declareJsMethod(SmalltalkClass.prototype, "toString");
  178. declareJsMethod(SmalltalkMetaclass.prototype, "toString");
  179. declareJsMethod(SmalltalkClass.prototype, "added");
  180. declareJsMethod(SmalltalkClass.prototype, "removed");
  181. declareJsMethod(SmalltalkBehavior.prototype, "methodAdded");
  182. declareJsMethod(SmalltalkBehavior.prototype, "methodRemoved");
  183. SmalltalkClass.prototype.toString = function () {
  184. return 'Smalltalk ' + this.name;
  185. };
  186. SmalltalkMetaclass.prototype.toString = function () {
  187. return 'Smalltalk Metaclass ' + this.instanceClass.name;
  188. };
  189. declareEvent("classCreated");
  190. SmalltalkClass.prototype.added = function () {
  191. registerToSuperclass(this);
  192. emit.classCreated(this);
  193. };
  194. SmalltalkClass.prototype.removed = function () {
  195. unregisterFromSuperclass(this);
  196. };
  197. declareEvent("behaviorMethodAdded");
  198. SmalltalkBehavior.prototype.methodAdded = function (method) {
  199. emit.behaviorMethodAdded(method, this);
  200. };
  201. declareEvent("behaviorMethodRemoved");
  202. SmalltalkBehavior.prototype.methodRemoved = function (method) {
  203. emit.behaviorMethodRemoved(method, this);
  204. };
  205. // Fake root class of the system.
  206. // Effective superclass of all classes created with `nil subclass: ...`.
  207. var nilAsClass = this.nilAsClass = {
  208. fn: SmalltalkRoot,
  209. subclasses: [],
  210. a$cls: {fn: SmalltalkClass}
  211. };
  212. this.bootstrapHierarchy = function (realClass) {
  213. nilAsClass.a$cls = realClass;
  214. nilAsClass.subclasses.forEach(function (each) {
  215. each.a$cls.superclass = realClass;
  216. registerToSuperclass(each.a$cls);
  217. });
  218. };
  219. function registerToSuperclass (klass) {
  220. addElement((klass.superclass || nilAsClass).subclasses, klass);
  221. }
  222. function unregisterFromSuperclass (klass) {
  223. removeElement((klass.superclass || nilAsClass).subclasses, klass);
  224. }
  225. function metaSubclasses (metaclass) {
  226. return metaclass.instanceClass.subclasses
  227. .filter(function (each) {
  228. return !each.meta;
  229. })
  230. .map(function (each) {
  231. return each.a$cls;
  232. });
  233. }
  234. st.metaSubclasses = metaSubclasses;
  235. st.traverseClassTree = function (klass, fn) {
  236. var queue = [klass], sentinel = {};
  237. for (var i = 0; i < queue.length; ++i) {
  238. var item = queue[i];
  239. if (fn(item, sentinel) === sentinel) continue;
  240. var subclasses = item.meta ? metaSubclasses(item) : item.subclasses;
  241. queue.push.apply(queue, subclasses);
  242. }
  243. };
  244. /**
  245. * This function is used all over the compiled amber code.
  246. * It takes any value (JavaScript or Smalltalk)
  247. * and returns a proper Amber Smalltalk receiver.
  248. *
  249. * null or undefined -> nilAsReceiver,
  250. * object having Smalltalk signature -> unchanged,
  251. * otherwise wrapped foreign (JS) object
  252. */
  253. this.asReceiver = function (o) {
  254. if (o == null) return nilAsReceiver;
  255. else if (o.a$cls != null) return o;
  256. else return st.wrapJavaScript(o);
  257. };
  258. // TODO remove, .iVarNames backward compatibility
  259. this.__init__ = function () {
  260. brikz.classConstruction.iVarNamesCompat(SmalltalkBehavior);
  261. };
  262. }
  263. ClassConstructionBrik.deps = ["classModel", "behaviorals", "methods"];
  264. function ClassConstructionBrik (brikz, st) {
  265. var nilAsClass = brikz.classModel.nilAsClass;
  266. var newMetaclass = brikz.classModel.newMetaclass;
  267. var buildTraitOrClass = brikz.behaviorals.buildTraitOrClass;
  268. var setupMethods = brikz.methods.setupMethods;
  269. var removeTraitOrClass = brikz.behaviorals.removeTraitOrClass;
  270. declareEvent("slotsChanged");
  271. function setSlots (klass, slots) {
  272. slots.forEach(function (name) {
  273. if (!name.match(/^[a-zA-Z][a-zA-Z0-9]*$/))
  274. throw new Error("Wrong identifier name: " + name);
  275. });
  276. klass.slots = slots;
  277. emit.slotsChanged(klass);
  278. }
  279. st.setSlots = setSlots;
  280. // TODO remove, .iVarNames backward compatibility
  281. this.iVarNamesCompat = function (SmalltalkBehavior) {
  282. Object.defineProperty(SmalltalkBehavior.prototype, "iVarNames", {
  283. enumerable: true,
  284. configurable: true,
  285. get: function () {
  286. return this.slots;
  287. },
  288. set: function (instanceVariableNames) {
  289. setSlots(this, instanceVariableNames);
  290. }
  291. });
  292. };
  293. /* Smalltalk class creation. A class is an instance of an automatically
  294. created metaclass object. Newly created classes (not their metaclass)
  295. should be added to the system, see smalltalk.addClass().
  296. Superclass linking is *not* handled here, see api.initialize() */
  297. function classBuilder (className, superclass, category, fn) {
  298. var logicalSuperclass = superclass;
  299. if (superclass == null || superclass.a$nil) {
  300. superclass = nilAsClass;
  301. logicalSuperclass = null;
  302. }
  303. function klass () {
  304. var that = metaclass().instanceClass;
  305. that.superclass = logicalSuperclass;
  306. that.fn = fn || inherits(function () {
  307. }, superclass.fn);
  308. that.slots = [];
  309. that.name = className;
  310. that.category = category;
  311. that.subclasses = [];
  312. setupMethods(that);
  313. return that;
  314. }
  315. function metaclass () {
  316. var that = newMetaclass();
  317. that.superclass = superclass.a$cls;
  318. that.fn = inherits(function () {
  319. }, that.superclass.fn);
  320. that.slots = [];
  321. that.instanceClass = new that.fn();
  322. wireKlass(that);
  323. setupMethods(that);
  324. return that;
  325. }
  326. return {
  327. name: className,
  328. make: klass,
  329. updateExisting: function (klass) {
  330. if (logicalSuperclass == null && klass.superclass != null || logicalSuperclass != null && klass.superclass !== logicalSuperclass || fn != null && fn !== klass.fn)
  331. throw new Error("Incompatible change of class: " + klass.name);
  332. }
  333. };
  334. }
  335. function wireKlass (klass) {
  336. Object.defineProperty(klass.fn.prototype, "a$cls", {
  337. value: klass,
  338. enumerable: false, configurable: true, writable: true
  339. });
  340. }
  341. this.wireKlass = wireKlass;
  342. /* Add a class to the system, creating a new one if needed.
  343. A Package is lazily created if one with given name does not exist. */
  344. st.addClass = function (className, superclass, category) {
  345. // TODO remove, backward compatibility
  346. if (arguments[3]) {
  347. var added = st.addClass(className, superclass, arguments[3]);
  348. setSlots(added, category);
  349. return added;
  350. }
  351. // While subclassing nil is allowed, it might be an error, so
  352. // warn about it.
  353. if (typeof superclass === 'undefined' || superclass && superclass.a$nil) {
  354. console.warn('Compiling ' + className + ' as a subclass of `nil`. A dependency might be missing.');
  355. }
  356. return buildTraitOrClass(classBuilder(className, superclass, category, specialConstructors[className]));
  357. };
  358. st.removeClass = removeTraitOrClass;
  359. }
  360. /* Making smalltalk that can load */
  361. function configure (brikz) {
  362. brikz.traits = TraitsBrik;
  363. brikz.composition = MethodCompositionBrik;
  364. brikz.classModel = ClassModelBrik;
  365. brikz.classConstruction = ClassConstructionBrik;
  366. brikz();
  367. }
  368. return {configure: configure};
  369. }
  370. return LanguageFactory;
  371. });