kernel-language.js 18 KB

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