boot.js 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188
  1. /* ====================================================================
  2. |
  3. | Amber Smalltalk
  4. | http://amber-lang.net
  5. |
  6. ======================================================================
  7. ======================================================================
  8. |
  9. | Copyright (c) 2010-2014
  10. | Nicolas Petton <petton.nicolas@gmail.com>
  11. |
  12. | Copyright (c) 2012-2016
  13. | The Amber team https://lolg.it/org/amber/members
  14. | Amber contributors (see /CONTRIBUTORS)
  15. |
  16. | Amber is released under the MIT license
  17. |
  18. | Permission is hereby granted, free of charge, to any person obtaining
  19. | a copy of this software and associated documentation files (the
  20. | 'Software'), to deal in the Software without restriction, including
  21. | without limitation the rights to use, copy, modify, merge, publish,
  22. | distribute, sublicense, and/or sell copies of the Software, and to
  23. | permit persons to whom the Software is furnished to do so, subject to
  24. | the following conditions:
  25. |
  26. | The above copyright notice and this permission notice shall be
  27. | included in all copies or substantial portions of the Software.
  28. |
  29. | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
  30. | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  31. | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  32. | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  33. | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  34. | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  35. | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  36. |
  37. ==================================================================== */
  38. //jshint eqnull:true
  39. define(['require', './brikz.umd', './compatibility'], function (require, Brikz) {
  40. function inherits(child, parent) {
  41. child.prototype = Object.create(parent.prototype, {
  42. constructor: {
  43. value: child,
  44. enumerable: false, configurable: true, writable: true
  45. }
  46. });
  47. return child;
  48. }
  49. function depends(deps, brik) {
  50. brik.deps = deps;
  51. return brik;
  52. }
  53. function SmalltalkGlobalsBrik(brikz, st) {
  54. var jsGlobals = new Function("return this")();
  55. var globals = Object.create(jsGlobals);
  56. globals.SmalltalkSettings = {};
  57. this.globals = globals;
  58. }
  59. function RootBrik(brikz, st) {
  60. /* Smalltalk foundational objects */
  61. /* SmalltalkRoot is the hidden root of the Amber hierarchy.
  62. All objects including `Object` inherit from SmalltalkRoot */
  63. function SmalltalkRoot() {
  64. }
  65. function SmalltalkProtoObject() {
  66. }
  67. inherits(SmalltalkProtoObject, SmalltalkRoot);
  68. function SmalltalkObject() {
  69. }
  70. inherits(SmalltalkObject, SmalltalkProtoObject);
  71. function SmalltalkNil() {
  72. }
  73. inherits(SmalltalkNil, SmalltalkObject);
  74. this.Object = SmalltalkObject;
  75. this.nil = new SmalltalkNil();
  76. // Adds an `isNil` property to the `nil` object. When sending
  77. // nil objects from one environment to another, doing
  78. // `anObject == nil` (in JavaScript) does not always answer
  79. // true as the referenced nil object might come from the other
  80. // environment.
  81. Object.defineProperty(this.nil, 'isNil', {
  82. value: true,
  83. enumerable: false, configurable: false, writable: false
  84. });
  85. // Hidden root class of the system.
  86. this.rootAsClass = {fn: SmalltalkRoot};
  87. this.__init__ = function () {
  88. var globals = brikz.smalltalkGlobals.globals;
  89. st.addPackage("Kernel-Objects");
  90. st.addCoupledClass("ProtoObject", undefined, "Kernel-Objects", SmalltalkProtoObject);
  91. st.addCoupledClass("Object", globals.ProtoObject, "Kernel-Objects", SmalltalkObject);
  92. st.addCoupledClass("UndefinedObject", globals.Object, "Kernel-Objects", SmalltalkNil);
  93. };
  94. }
  95. var OrganizeBrik = depends(["augments", "root"], function (brikz, st) {
  96. var SmalltalkObject = brikz.root.Object;
  97. function SmalltalkOrganizer() {
  98. }
  99. function SmalltalkPackageOrganizer() {
  100. this.elements = [];
  101. }
  102. function SmalltalkClassOrganizer() {
  103. this.elements = [];
  104. }
  105. inherits(SmalltalkOrganizer, SmalltalkObject);
  106. inherits(SmalltalkPackageOrganizer, SmalltalkOrganizer);
  107. inherits(SmalltalkClassOrganizer, SmalltalkOrganizer);
  108. this.__init__ = function () {
  109. var globals = brikz.smalltalkGlobals.globals;
  110. st.addPackage("Kernel-Infrastructure");
  111. st.addCoupledClass("Organizer", globals.Object, "Kernel-Infrastructure", SmalltalkOrganizer);
  112. st.addCoupledClass("PackageOrganizer", globals.Organizer, "Kernel-Infrastructure", SmalltalkPackageOrganizer);
  113. st.addCoupledClass("ClassOrganizer", globals.Organizer, "Kernel-Infrastructure", SmalltalkClassOrganizer);
  114. };
  115. this.setupClassOrganization = function (klass) {
  116. klass.organization = new SmalltalkClassOrganizer();
  117. klass.organization.theClass = klass;
  118. };
  119. this.setupPackageOrganization = function (pkg) {
  120. pkg.organization = new SmalltalkPackageOrganizer();
  121. };
  122. this.addOrganizationElement = function (owner, element) {
  123. owner.organization.elements.addElement(element);
  124. };
  125. this.removeOrganizationElement = function (owner, element) {
  126. owner.organization.elements.removeElement(element);
  127. };
  128. });
  129. var SelectorsBrik = depends(["selectorConversion"], function (brikz, st) {
  130. var selectorSet = Object.create(null);
  131. var selectors = this.selectors = [];
  132. var selectorPairs = this.selectorPairs = [];
  133. this.registerSelector = function (stSelector) {
  134. if (selectorSet[stSelector]) return null;
  135. var jsSelector = st.st2js(stSelector);
  136. selectorSet[stSelector] = true;
  137. selectors.push(stSelector);
  138. selectorPairs.push({st: stSelector, js: jsSelector});
  139. return jsSelector;
  140. };
  141. /* Answer all method selectors based on dnu handlers */
  142. st.allSelectors = function () {
  143. return selectors;
  144. };
  145. });
  146. var DNUBrik = depends(["selectors", "messageSend", "manipulation", "root"], function (brikz, st) {
  147. var registerSelector = brikz.selectors.registerSelector;
  148. var installJSMethod = brikz.manipulation.installJSMethod;
  149. var RootProto = brikz.root.rootAsClass.fn.prototype;
  150. /* Method not implemented handlers */
  151. this.makeDnuHandler = function (stSelector, targetClasses) {
  152. var jsSelector = registerSelector(stSelector);
  153. if (!jsSelector) return;
  154. var fn = createHandler(stSelector);
  155. installJSMethod(RootProto, jsSelector, fn);
  156. targetClasses.forEach(function (target) {
  157. installJSMethod(target.fn.prototype, jsSelector, fn);
  158. });
  159. };
  160. /* Dnu handler method */
  161. function createHandler(stSelector) {
  162. return function () {
  163. return brikz.messageSend.messageNotUnderstood(this, stSelector, arguments);
  164. };
  165. }
  166. });
  167. var ClassInitBrik = depends(["selectors", "manipulation"], function (brikz, st) {
  168. var selectors = brikz.selectors;
  169. var wireKlass = brikz.manipulation.wireKlass;
  170. var installMethod = brikz.manipulation.installMethod;
  171. var installJSMethod = brikz.manipulation.installJSMethod;
  172. /* Initialize a class in its class hierarchy. Handle both classes and
  173. metaclasses. */
  174. st.init = function (klass) {
  175. initClass(klass);
  176. if (klass.klass && !klass.meta) {
  177. initClass(klass.klass);
  178. }
  179. };
  180. function initClass(klass) {
  181. wireKlass(klass);
  182. if (klass.detachedRoot) {
  183. copySuperclass(klass);
  184. }
  185. }
  186. this.initClass = initClass;
  187. function copySuperclass(klass) {
  188. var superclass = klass.superclass,
  189. localMethods = klass.methods,
  190. localMethodsByJsSelector = {};
  191. Object.keys(localMethods).forEach(function (each) {
  192. var localMethod = localMethods[each];
  193. localMethodsByJsSelector[localMethod.jsSelector] = localMethod;
  194. });
  195. var myproto = klass.fn.prototype,
  196. superproto = superclass.fn.prototype;
  197. selectors.selectorPairs.forEach(function (selectorPair) {
  198. var jsSelector = selectorPair.js,
  199. method = localMethodsByJsSelector[jsSelector];
  200. if (!method) {
  201. installJSMethod(myproto, jsSelector, superproto[jsSelector]);
  202. } else if (method.fn !== myproto[jsSelector]) {
  203. if (myproto[jsSelector]) {
  204. console.warn("Amber forcefully rewriting method " + jsSelector + " of " + klass.className + ".");
  205. }
  206. installMethod(method, klass);
  207. }
  208. });
  209. }
  210. });
  211. function ManipulationBrik(brikz, st) {
  212. function wireKlass(klass) {
  213. Object.defineProperty(klass.fn.prototype, "klass", {
  214. value: klass,
  215. enumerable: false, configurable: true, writable: true
  216. });
  217. }
  218. function installJSMethod(obj, jsSelector, fn) {
  219. Object.defineProperty(obj, jsSelector, {
  220. value: fn,
  221. enumerable: false, configurable: true, writable: true
  222. });
  223. }
  224. function installMethod(method, klass) {
  225. installJSMethod(klass.fn.prototype, method.jsSelector, method.fn);
  226. }
  227. this.wireKlass = wireKlass;
  228. this.installMethod = installMethod;
  229. this.installJSMethod = installJSMethod;
  230. }
  231. var PackagesBrik = depends(["organize", "root"], function (brikz, st) {
  232. var setupPackageOrganization = brikz.organize.setupPackageOrganization;
  233. var SmalltalkObject = brikz.root.Object;
  234. function SmalltalkPackage() {
  235. }
  236. inherits(SmalltalkPackage, SmalltalkObject);
  237. this.__init__ = function () {
  238. var globals = brikz.smalltalkGlobals.globals;
  239. st.addPackage("Kernel-Infrastructure");
  240. st.addCoupledClass("Package", globals.Object, "Kernel-Infrastructure", SmalltalkPackage);
  241. };
  242. st.packages = {};
  243. /* Smalltalk package creation. To add a Package, use smalltalk.addPackage() */
  244. function pkg(spec) {
  245. var that = new SmalltalkPackage();
  246. that.pkgName = spec.pkgName;
  247. setupPackageOrganization(that);
  248. that.properties = spec.properties || {};
  249. return that;
  250. }
  251. /* Add a package to the smalltalk.packages object, creating a new one if needed.
  252. If pkgName is null or empty we return nil, which is an allowed package for a class.
  253. If package already exists we still update the properties of it. */
  254. st.addPackage = function (pkgName, properties) {
  255. if (!pkgName) {
  256. return null;
  257. }
  258. if (!(st.packages[pkgName])) {
  259. st.packages[pkgName] = pkg({
  260. pkgName: pkgName,
  261. properties: properties
  262. });
  263. } else {
  264. if (properties) {
  265. st.packages[pkgName].properties = properties;
  266. }
  267. }
  268. return st.packages[pkgName];
  269. };
  270. });
  271. var ClassesBrik = depends(["organize", "manipulation", "root", "smalltalkGlobals", "classInit"], function (brikz, st) {
  272. var setupClassOrganization = brikz.organize.setupClassOrganization;
  273. var addOrganizationElement = brikz.organize.addOrganizationElement;
  274. var removeOrganizationElement = brikz.organize.removeOrganizationElement;
  275. var wireKlass = brikz.manipulation.wireKlass;
  276. var globals = brikz.smalltalkGlobals.globals;
  277. var initClass = brikz.classInit.initClass;
  278. var rootAsClass = brikz.root.rootAsClass;
  279. var SmalltalkObject = brikz.root.Object;
  280. rootAsClass.klass = {fn: SmalltalkClass};
  281. function SmalltalkBehavior() {
  282. }
  283. function SmalltalkClass() {
  284. }
  285. function SmalltalkMetaclass() {
  286. }
  287. inherits(SmalltalkBehavior, SmalltalkObject);
  288. inherits(SmalltalkClass, SmalltalkBehavior);
  289. inherits(SmalltalkMetaclass, SmalltalkBehavior);
  290. SmalltalkBehavior.prototype.toString = function () {
  291. return 'Smalltalk ' + this.className;
  292. };
  293. SmalltalkMetaclass.prototype.meta = true;
  294. this.__init__ = function () {
  295. var globals = brikz.smalltalkGlobals.globals;
  296. st.addPackage("Kernel-Classes");
  297. st.addCoupledClass("Behavior", globals.Object, "Kernel-Classes", SmalltalkBehavior);
  298. st.addCoupledClass("Metaclass", globals.Behavior, "Kernel-Classes", SmalltalkMetaclass);
  299. st.addCoupledClass("Class", globals.Behavior, "Kernel-Classes", SmalltalkClass);
  300. // Manually bootstrap the metaclass hierarchy
  301. globals.ProtoObject.klass.superclass = rootAsClass.klass = globals.Class;
  302. addSubclass(globals.ProtoObject.klass);
  303. };
  304. /* Smalltalk classes */
  305. var classes = [];
  306. /* Smalltalk class creation. A class is an instance of an automatically
  307. created metaclass object. Newly created classes (not their metaclass)
  308. should be added to the smalltalk object, see smalltalk.addClass().
  309. Superclass linking is *not* handled here, see smalltalk.init() */
  310. function klass(spec) {
  311. var setSuperClass = spec.superclass;
  312. if (!spec.superclass) {
  313. spec.superclass = rootAsClass;
  314. }
  315. var meta = metaclass(spec);
  316. var that = meta.instanceClass;
  317. that.superclass = setSuperClass;
  318. that.fn = spec.fn || inherits(function () {
  319. }, spec.superclass.fn);
  320. that.subclasses = [];
  321. setupClass(that, spec);
  322. that.className = spec.className;
  323. meta.className = spec.className + ' class';
  324. meta.superclass = spec.superclass.klass;
  325. return that;
  326. }
  327. function metaclass(spec) {
  328. var that = new SmalltalkMetaclass();
  329. that.fn = inherits(function () {
  330. }, spec.superclass.klass.fn);
  331. wireKlass(that);
  332. that.instanceClass = new that.fn();
  333. setupClass(that, {});
  334. return that;
  335. }
  336. function setupClass(klass, spec) {
  337. klass.iVarNames = spec.iVarNames || [];
  338. if (spec.pkg) {
  339. klass.pkg = spec.pkg;
  340. }
  341. setupClassOrganization(klass);
  342. Object.defineProperty(klass, "methods", {
  343. value: Object.create(null),
  344. enumerable: false, configurable: true, writable: true
  345. });
  346. }
  347. /* Add a class to the smalltalk object, creating a new one if needed.
  348. A Package is lazily created if it does not exist with given name. */
  349. st.addClass = function (className, superclass, iVarNames, pkgName) {
  350. // While subclassing nil is allowed, it might be an error, so
  351. // warn about it.
  352. if (typeof superclass == 'undefined' || superclass && superclass.isNil) {
  353. console.warn('Compiling ' + className + ' as a subclass of `nil`. A dependency might be missing.');
  354. }
  355. return rawAddClass(pkgName, className, superclass, iVarNames, null);
  356. };
  357. function rawAddClass(pkgName, className, superclass, iVarNames, fn) {
  358. var pkg = st.packages[pkgName];
  359. if (!pkg) {
  360. throw new Error("Missing package " + pkgName);
  361. }
  362. if (superclass == null || superclass.isNil) {
  363. superclass = null;
  364. }
  365. var theClass = globals.hasOwnProperty(className) && globals[className];
  366. if (theClass && theClass.superclass == superclass) {
  367. if (iVarNames) theClass.iVarNames = iVarNames;
  368. if (pkg) theClass.pkg = pkg;
  369. if (fn && theClass.fn !== fn) {
  370. fn.prototype = theClass.fn.prototype;
  371. theClass.fn = fn;
  372. fn.prototype.constructor = fn;
  373. }
  374. } else {
  375. if (theClass) {
  376. iVarNames = iVarNames || theClass.iVarNames;
  377. st.removeClass(theClass);
  378. }
  379. theClass = globals[className] = klass({
  380. className: className,
  381. superclass: superclass,
  382. pkg: pkg,
  383. iVarNames: iVarNames,
  384. fn: fn
  385. });
  386. addSubclass(theClass);
  387. }
  388. classes.addElement(theClass);
  389. addOrganizationElement(pkg, theClass);
  390. return theClass;
  391. }
  392. st.removeClass = function (klass) {
  393. removeOrganizationElement(klass.pkg, klass);
  394. classes.removeElement(klass);
  395. removeSubclass(klass);
  396. delete globals[klass.className];
  397. };
  398. function addSubclass(klass) {
  399. if (klass.superclass) {
  400. klass.superclass.subclasses.addElement(klass);
  401. }
  402. }
  403. function removeSubclass(klass) {
  404. if (klass.superclass) {
  405. klass.superclass.subclasses.removeElement(klass);
  406. }
  407. }
  408. var detachedRootClasses = [];
  409. /* Create a new class coupling with a JavaScript constructor,
  410. optionally detached root, and add it to the global smalltalk object.*/
  411. st.addDetachedRootClass = function (className, superclass, pkgName, fn) {
  412. var klass = rawAddClass(pkgName, className, superclass, null, fn);
  413. markClassDetachedRoot(klass);
  414. return klass;
  415. };
  416. function markClassDetachedRoot(klass) {
  417. detachedRootClasses.addElement(klass);
  418. klass.detachedRoot = true;
  419. }
  420. st.addCoupledClass = function (className, superclass, pkgName, fn) {
  421. return rawAddClass(pkgName, className, superclass, null, fn);
  422. };
  423. /* Manually set the constructor of an existing Smalltalk klass, making it a detached root class. */
  424. st.setClassConstructor = function (klass, constructor) {
  425. markClassDetachedRoot(klass);
  426. klass.fn = constructor;
  427. initClass(klass);
  428. };
  429. /* Create an alias for an existing class */
  430. st.alias = function (klass, alias) {
  431. globals[alias] = klass;
  432. };
  433. /* Answer all registered Smalltalk classes */
  434. //TODO: remove the function and make smalltalk.classes an array
  435. st.classes = function () {
  436. return classes;
  437. };
  438. this.detachedRootClasses = function () {
  439. return detachedRootClasses;
  440. };
  441. function metaSubclasses(metaclass) {
  442. return metaclass.instanceClass.subclasses
  443. .filter(function (each) {
  444. return !each.meta;
  445. })
  446. .map(function (each) {
  447. return each.klass;
  448. });
  449. }
  450. st.metaSubclasses = metaSubclasses;
  451. st.traverseClassTree = function (klass, fn) {
  452. var queue = [klass];
  453. for (var i = 0; i < queue.length; ++i) {
  454. var item = queue[i];
  455. fn(item);
  456. var subclasses = item.meta ? metaSubclasses(item) : item.subclasses;
  457. queue.push.apply(queue, subclasses);
  458. }
  459. }
  460. });
  461. var MethodsBrik = depends(["manipulation", "organize", "stInit", "dnu", "root", "selectorConversion", "classes"], function (brikz, st) {
  462. var installMethod = brikz.manipulation.installMethod;
  463. var installJSMethod = brikz.manipulation.installJSMethod;
  464. var addOrganizationElement = brikz.organize.addOrganizationElement;
  465. var initialized = brikz.stInit.initialized;
  466. var makeDnuHandler = brikz.dnu.makeDnuHandler;
  467. var SmalltalkObject = brikz.root.Object;
  468. var detachedRootClasses = brikz.classes.detachedRootClasses;
  469. function SmalltalkMethod() {
  470. }
  471. inherits(SmalltalkMethod, SmalltalkObject);
  472. this.__init__ = function () {
  473. var globals = brikz.smalltalkGlobals.globals;
  474. st.addPackage("Kernel-Methods");
  475. st.addCoupledClass("CompiledMethod", globals.Object, "Kernel-Methods", SmalltalkMethod);
  476. };
  477. /* Smalltalk method object. To add a method to a class,
  478. use smalltalk.addMethod() */
  479. st.method = function (spec) {
  480. var that = new SmalltalkMethod();
  481. that.selector = spec.selector;
  482. that.jsSelector = spec.jsSelector;
  483. that.args = spec.args || {};
  484. that.protocol = spec.protocol;
  485. that.source = spec.source;
  486. that.messageSends = spec.messageSends || [];
  487. that.referencedClasses = spec.referencedClasses || [];
  488. that.fn = spec.fn;
  489. return that;
  490. };
  491. function ensureJsSelector(method) {
  492. if (!(method.jsSelector)) {
  493. method.jsSelector = st.st2js(method.selector);
  494. }
  495. }
  496. /* Add/remove a method to/from a class */
  497. st.addMethod = function (method, klass) {
  498. ensureJsSelector(method);
  499. installMethod(method, klass);
  500. klass.methods[method.selector] = method;
  501. method.methodClass = klass;
  502. // During the bootstrap, #addCompiledMethod is not used.
  503. // Therefore we populate the organizer here too
  504. addOrganizationElement(klass, method.protocol);
  505. propagateMethodChange(klass, method);
  506. var usedSelectors = method.messageSends,
  507. targetClasses = initialized() ? detachedRootClasses() : [];
  508. makeDnuHandler(method.selector, targetClasses);
  509. for (var i = 0; i < usedSelectors.length; i++) {
  510. makeDnuHandler(usedSelectors[i], targetClasses);
  511. }
  512. };
  513. function propagateMethodChange(klass, method) {
  514. // If already initialized (else it will be done later anyway),
  515. // re-initialize all subclasses to ensure the method change
  516. // propagation (for detached root classes, not using the prototype
  517. // chain).
  518. if (initialized()) {
  519. st.traverseClassTree(klass, function (subclass) {
  520. if (subclass !== klass) initMethodInClass(subclass, method);
  521. });
  522. }
  523. }
  524. function initMethodInClass(klass, method) {
  525. if (klass.detachedRoot && !klass.methods[method.selector]) {
  526. var jsSelector = method.jsSelector;
  527. installJSMethod(klass.fn.prototype, jsSelector, klass.superclass.fn.prototype[jsSelector]);
  528. }
  529. }
  530. st.removeMethod = function (method, klass) {
  531. if (klass !== method.methodClass) {
  532. throw new Error(
  533. "Refusing to remove method " +
  534. method.methodClass.className + ">>" + method.selector +
  535. " from different class " +
  536. klass.className);
  537. }
  538. ensureJsSelector(method);
  539. delete klass.fn.prototype[method.jsSelector];
  540. delete klass.methods[method.selector];
  541. initMethodInClass(klass, method);
  542. propagateMethodChange(klass, method);
  543. // Do *not* delete protocols from here.
  544. // This is handled by #removeCompiledMethod
  545. };
  546. });
  547. function AugmentsBrik(brikz, st) {
  548. /* Array extensions */
  549. Array.prototype.addElement = function (el) {
  550. if (typeof el === 'undefined') {
  551. return;
  552. }
  553. if (this.indexOf(el) == -1) {
  554. this.push(el);
  555. }
  556. };
  557. Array.prototype.removeElement = function (el) {
  558. var i = this.indexOf(el);
  559. if (i !== -1) {
  560. this.splice(i, 1);
  561. }
  562. };
  563. }
  564. var SmalltalkInitBrik = depends(["classInit", "classes"], function (brikz, st) {
  565. var initialized = false;
  566. /* Smalltalk initialization. Called on page load */
  567. st.initialize = function () {
  568. if (initialized) {
  569. return;
  570. }
  571. st.classes().forEach(function (klass) {
  572. st.init(klass);
  573. });
  574. runnable();
  575. st.classes().forEach(function (klass) {
  576. klass._initialize();
  577. });
  578. initialized = true;
  579. };
  580. this.initialized = function () {
  581. return initialized;
  582. };
  583. this.__init__ = function () {
  584. var globals = brikz.smalltalkGlobals.globals;
  585. st.addPackage("Kernel-Methods");
  586. st.addDetachedRootClass("Number", globals.Object, "Kernel-Objects", Number);
  587. st.addDetachedRootClass("BlockClosure", globals.Object, "Kernel-Methods", Function);
  588. st.addDetachedRootClass("Boolean", globals.Object, "Kernel-Objects", Boolean);
  589. st.addDetachedRootClass("Date", globals.Object, "Kernel-Objects", Date);
  590. st.addPackage("Kernel-Collections");
  591. st.addClass("Collection", globals.Object, null, "Kernel-Collections");
  592. st.addClass("IndexableCollection", globals.Collection, null, "Kernel-Collections");
  593. st.addClass("SequenceableCollection", globals.IndexableCollection, null, "Kernel-Collections");
  594. st.addClass("CharacterArray", globals.SequenceableCollection, null, "Kernel-Collections");
  595. st.addDetachedRootClass("String", globals.CharacterArray, "Kernel-Collections", String);
  596. st.addDetachedRootClass("Array", globals.SequenceableCollection, "Kernel-Collections", Array);
  597. st.addDetachedRootClass("RegularExpression", globals.Object, "Kernel-Collections", RegExp);
  598. st.addPackage("Kernel-Exceptions");
  599. st.addDetachedRootClass("Error", globals.Object, "Kernel-Exceptions", Error);
  600. st.addPackage("Kernel-Promises");
  601. st.addClass("Thenable", globals.Object, null, "Kernel-Promises");
  602. st.addDetachedRootClass("Promise", globals.Thenable, "Kernel-Promises", Promise);
  603. /* Alias definitions */
  604. st.alias(globals.Array, "OrderedCollection");
  605. st.alias(globals.Date, "Time");
  606. };
  607. });
  608. var PrimitivesBrik = depends(["smalltalkGlobals"], function (brikz, st) {
  609. var globals = brikz.smalltalkGlobals.globals;
  610. var oid = 0;
  611. /* Unique ID number generator */
  612. st.nextId = function () {
  613. oid += 1;
  614. return oid;
  615. };
  616. /* Converts a JavaScript object to valid Smalltalk Object */
  617. st.readJSObject = function (js) {
  618. if (js == null)
  619. return null;
  620. var readObject = js.constructor === Object;
  621. var readArray = js.constructor === Array;
  622. var object = readObject ? globals.Dictionary._new() : readArray ? [] : js;
  623. for (var i in js) {
  624. if (readObject) {
  625. object._at_put_(i, st.readJSObject(js[i]));
  626. }
  627. if (readArray) {
  628. object[i] = st.readJSObject(js[i]);
  629. }
  630. }
  631. return object;
  632. };
  633. /* Boolean assertion */
  634. st.assert = function (shouldBeBoolean) {
  635. if (typeof shouldBeBoolean === "boolean") return shouldBeBoolean;
  636. else if (shouldBeBoolean != null && typeof shouldBeBoolean === "object") {
  637. shouldBeBoolean = shouldBeBoolean.valueOf();
  638. if (typeof shouldBeBoolean === "boolean") return shouldBeBoolean;
  639. }
  640. globals.NonBooleanReceiver._new()._object_(shouldBeBoolean)._signal();
  641. };
  642. /* List of all reserved words in JavaScript. They may not be used as variables
  643. in Smalltalk. */
  644. // list of reserved JavaScript keywords as of
  645. // http://es5.github.com/#x7.6.1.1
  646. // and
  647. // http://people.mozilla.org/~jorendorff/es6-draft.html#sec-7.6.1
  648. st.reservedWords = ['break', 'case', 'catch', 'continue', 'debugger',
  649. 'default', 'delete', 'do', 'else', 'finally', 'for', 'function',
  650. 'if', 'in', 'instanceof', 'new', 'return', 'switch', 'this', 'throw',
  651. 'try', 'typeof', 'var', 'void', 'while', 'with',
  652. // Amber protected words: these should not be compiled as-is when in code
  653. 'arguments',
  654. // ES5: future use: http://es5.github.com/#x7.6.1.2
  655. 'class', 'const', 'enum', 'export', 'extends', 'import', 'super',
  656. // ES5: future use in strict mode
  657. 'implements', 'interface', 'let', 'package', 'private', 'protected',
  658. 'public', 'static', 'yield'];
  659. st.globalJsVariables = ['window', 'document', 'process', 'global'];
  660. });
  661. var RuntimeBrik = depends(["selectorConversion", "smalltalkGlobals", "root"], function (brikz, st) {
  662. var globals = brikz.smalltalkGlobals.globals;
  663. var SmalltalkObject = brikz.root.Object;
  664. function SmalltalkMethodContext(home, setup) {
  665. this.sendIdx = {};
  666. this.homeContext = home;
  667. this.setup = setup || function () {
  668. };
  669. this.supercall = false;
  670. }
  671. inherits(SmalltalkMethodContext, SmalltalkObject);
  672. this.__init__ = function () {
  673. var globals = brikz.smalltalkGlobals.globals;
  674. st.addPackage("Kernel-Methods");
  675. st.addCoupledClass("MethodContext", globals.Object, "Kernel-Methods", SmalltalkMethodContext);
  676. // Fallbacks
  677. SmalltalkMethodContext.prototype.locals = {};
  678. SmalltalkMethodContext.prototype.receiver = null;
  679. SmalltalkMethodContext.prototype.selector = null;
  680. SmalltalkMethodContext.prototype.lookupClass = null;
  681. SmalltalkMethodContext.prototype.fill = function (receiver, selector, locals, lookupClass) {
  682. this.receiver = receiver;
  683. this.selector = selector;
  684. this.locals = locals || {};
  685. this.lookupClass = lookupClass;
  686. if (this.homeContext) {
  687. this.homeContext.evaluatedSelector = selector;
  688. }
  689. };
  690. SmalltalkMethodContext.prototype.fillBlock = function (locals, ctx, index) {
  691. this.locals = locals || {};
  692. this.outerContext = ctx;
  693. this.index = index || 0;
  694. };
  695. SmalltalkMethodContext.prototype.init = function () {
  696. var home = this.homeContext;
  697. if (home) {
  698. home.init();
  699. }
  700. this.setup(this);
  701. };
  702. SmalltalkMethodContext.prototype.method = function () {
  703. var method;
  704. var lookup = this.lookupClass || this.receiver.klass;
  705. while (!method && lookup) {
  706. method = lookup.methods[st.js2st(this.selector)];
  707. lookup = lookup.superclass;
  708. }
  709. return method;
  710. };
  711. };
  712. /* This is the current call context object. While it is publicly available,
  713. Use smalltalk.getThisContext() instead which will answer a safe copy of
  714. the current context */
  715. var thisContext = null;
  716. st.withContext = function (worker, setup) {
  717. if (thisContext) {
  718. return inContext(worker, setup);
  719. } else {
  720. return inContextWithErrorHandling(worker, setup);
  721. }
  722. };
  723. /*
  724. Runs worker function so that error handler is not set up
  725. if there isn't one. This is accomplished by unconditional
  726. wrapping inside a context of a simulated `nil seamlessDoIt` call,
  727. which then stops error handler setup (see st.withContext above).
  728. The effect is, $core.seamless(fn)'s exceptions are not
  729. handed into ST error handler and caller should process them.
  730. */
  731. st.seamless = function (worker) {
  732. return inContext(worker, function (ctx) {
  733. ctx.fill(null, "seamlessDoIt", {}, globals.UndefinedObject);
  734. });
  735. };
  736. function inContextWithErrorHandling(worker, setup) {
  737. try {
  738. return inContext(worker, setup);
  739. } catch (error) {
  740. handleError(error);
  741. thisContext = null;
  742. // Rethrow the error in any case.
  743. error.amberHandled = true;
  744. throw error;
  745. }
  746. }
  747. function inContext(worker, setup) {
  748. var oldContext = thisContext;
  749. thisContext = new SmalltalkMethodContext(thisContext, setup);
  750. var result = worker(thisContext);
  751. thisContext = oldContext;
  752. return result;
  753. }
  754. /* Wrap a JavaScript exception in a Smalltalk Exception.
  755. In case of a RangeError, stub the stack after 100 contexts to
  756. avoid another RangeError later when the stack is manipulated. */
  757. function wrappedError(error) {
  758. var errorWrapper = globals.JavaScriptException._on_(error);
  759. // Add the error to the context, so it is visible in the stack
  760. try {
  761. errorWrapper._signal();
  762. } catch (ex) {
  763. }
  764. var context = st.getThisContext();
  765. if (isRangeError(error)) {
  766. stubContextStack(context);
  767. }
  768. errorWrapper._context_(context);
  769. return errorWrapper;
  770. }
  771. /* Stub the context stack after 100 contexts */
  772. function stubContextStack(context) {
  773. var currentContext = context;
  774. var contexts = 0;
  775. while (contexts < 100) {
  776. if (currentContext) {
  777. currentContext = currentContext.homeContext;
  778. }
  779. contexts++;
  780. }
  781. if (currentContext) {
  782. currentContext.homeContext = undefined;
  783. }
  784. }
  785. function isRangeError(error) {
  786. return error instanceof RangeError;
  787. }
  788. /* Handles Smalltalk errors. Triggers the registered ErrorHandler
  789. (See the Smalltalk class ErrorHandler and its subclasses */
  790. function handleError(error) {
  791. if (!error.smalltalkError) {
  792. error = wrappedError(error);
  793. }
  794. globals.ErrorHandler._handleError_(error);
  795. }
  796. /* Handle thisContext pseudo variable */
  797. st.getThisContext = function () {
  798. if (thisContext) {
  799. thisContext.init();
  800. return thisContext;
  801. } else {
  802. return null;
  803. }
  804. };
  805. });
  806. var MessageSendBrik = depends(["smalltalkGlobals", "selectorConversion", "root"], function (brikz, st) {
  807. var globals = brikz.smalltalkGlobals.globals;
  808. var nil = brikz.root.nil;
  809. /* Handles unhandled errors during message sends */
  810. // simply send the message and handle #dnu:
  811. st.send2 = function (receiver, selector, args, klass) {
  812. var method, jsSelector = st.st2js(selector);
  813. if (receiver == null) {
  814. receiver = nil;
  815. }
  816. method = klass ? klass.fn.prototype[jsSelector] : receiver.klass && receiver[jsSelector];
  817. if (method) {
  818. return method.apply(receiver, args || []);
  819. } else {
  820. return messageNotUnderstood(receiver, selector, args);
  821. }
  822. };
  823. function invokeDnuMethod(receiver, stSelector, args) {
  824. return receiver._doesNotUnderstand_(
  825. globals.Message._new()
  826. ._selector_(stSelector)
  827. ._arguments_([].slice.call(args))
  828. );
  829. }
  830. /* Handles #dnu: *and* JavaScript method calls.
  831. if the receiver has no klass, we consider it a JS object (outside of the
  832. Amber system). Else assume that the receiver understands #doesNotUnderstand: */
  833. function messageNotUnderstood(receiver, stSelector, args) {
  834. if (receiver.klass != null && !receiver.allowJavaScriptCalls) {
  835. return invokeDnuMethod(receiver, stSelector, args);
  836. }
  837. /* Call a method of a JS object, or answer a property if it exists.
  838. Else try wrapping a JSObjectProxy around the receiver. */
  839. var propertyName = st.st2prop(stSelector);
  840. if (!(propertyName in receiver)) {
  841. return invokeDnuMethod(globals.JSObjectProxy._on_(receiver), stSelector, args);
  842. }
  843. return accessJavaScript(receiver, propertyName, args);
  844. }
  845. /* If the object property is a function, then call it, except if it starts with
  846. an uppercase character (we probably want to answer the function itself in this
  847. case and send it #new from Amber).
  848. Converts keyword-based selectors by using the first
  849. keyword only, but keeping all message arguments.
  850. Example:
  851. "self do: aBlock with: anObject" -> "self.do(aBlock, anObject)" */
  852. function accessJavaScript(receiver, propertyName, args) {
  853. var propertyValue = receiver[propertyName];
  854. if (typeof propertyValue === "function" && !/^[A-Z]/.test(propertyName)) {
  855. return propertyValue.apply(receiver, args || []);
  856. } else if (args.length > 0) {
  857. receiver[propertyName] = args[0];
  858. return receiver;
  859. } else {
  860. return propertyValue;
  861. }
  862. }
  863. st.accessJavaScript = accessJavaScript;
  864. this.messageNotUnderstood = messageNotUnderstood;
  865. });
  866. function SelectorConversionBrik(brikz, st) {
  867. /* Convert a Smalltalk selector into a JS selector */
  868. st.st2js = function (string) {
  869. return '_' + string
  870. .replace(/:/g, '_')
  871. .replace(/[\&]/g, '_and')
  872. .replace(/[\|]/g, '_or')
  873. .replace(/[+]/g, '_plus')
  874. .replace(/-/g, '_minus')
  875. .replace(/[*]/g, '_star')
  876. .replace(/[\/]/g, '_slash')
  877. .replace(/[\\]/g, '_backslash')
  878. .replace(/[\~]/g, '_tild')
  879. .replace(/>/g, '_gt')
  880. .replace(/</g, '_lt')
  881. .replace(/=/g, '_eq')
  882. .replace(/,/g, '_comma')
  883. .replace(/[@]/g, '_at');
  884. };
  885. /* Convert a string to a valid smalltalk selector.
  886. if you modify the following functions, also change st2js
  887. accordingly */
  888. st.js2st = function (selector) {
  889. if (selector.match(/^__/)) {
  890. return binaryJsToSt(selector);
  891. } else {
  892. return keywordJsToSt(selector);
  893. }
  894. };
  895. function keywordJsToSt(selector) {
  896. return selector.replace(/^_/, '').replace(/_/g, ':');
  897. }
  898. function binaryJsToSt(selector) {
  899. return selector
  900. .replace(/^_/, '')
  901. .replace(/_and/g, '&')
  902. .replace(/_or/g, '|')
  903. .replace(/_plus/g, '+')
  904. .replace(/_minus/g, '-')
  905. .replace(/_star/g, '*')
  906. .replace(/_slash/g, '/')
  907. .replace(/_backslash/g, '\\')
  908. .replace(/_tild/g, '~')
  909. .replace(/_gt/g, '>')
  910. .replace(/_lt/g, '<')
  911. .replace(/_eq/g, '=')
  912. .replace(/_comma/g, ',')
  913. .replace(/_at/g, '@');
  914. }
  915. st.st2prop = function (stSelector) {
  916. var colonPosition = stSelector.indexOf(':');
  917. return colonPosition === -1 ? stSelector : stSelector.slice(0, colonPosition);
  918. };
  919. }
  920. /* Adds AMD and requirejs related methods to the smalltalk object */
  921. function AMDBrik(brikz, st) {
  922. this.__init__ = function () {
  923. st.amdRequire = require;
  924. st.defaultTransportType = st.defaultTransportType || "amd";
  925. st.defaultAmdNamespace = st.defaultAmdNamespace || "amber_core";
  926. };
  927. }
  928. /* Defines asReceiver to be present at load time */
  929. /* (logically it belongs more to PrimitiveBrik) */
  930. var AsReceiverBrik = depends(["smalltalkGlobals", "root"], function (brikz, st) {
  931. var globals = brikz.smalltalkGlobals.globals;
  932. var nil = brikz.root.nil;
  933. /**
  934. * This function is used all over the compiled amber code.
  935. * It takes any value (JavaScript or Smalltalk)
  936. * and returns a proper Amber Smalltalk receiver.
  937. *
  938. * null or undefined -> nil,
  939. * plain JS object -> wrapped JS object,
  940. * otherwise unchanged
  941. */
  942. this.asReceiver = function (o) {
  943. if (o == null) return nil;
  944. if (typeof o === "object" || typeof o === "function") {
  945. return o.klass != null ? o : globals.JSObjectProxy._on_(o);
  946. }
  947. // IMPORTANT: This optimization (return o if typeof !== "object")
  948. // assumes all primitive types are coupled with some
  949. // (detached root) Smalltalk class so they can be returned as-is,
  950. // without boxing and looking for .klass.
  951. // KEEP THE primitives-are-coupled INVARIANT!
  952. return o;
  953. };
  954. });
  955. var api = {};
  956. var brikz = new Brikz(api);
  957. /* Making smalltalk that can load */
  958. brikz.smalltalkGlobals = SmalltalkGlobalsBrik;
  959. brikz.root = RootBrik;
  960. brikz.selectors = SelectorsBrik;
  961. brikz.dnu = DNUBrik;
  962. brikz.organize = OrganizeBrik;
  963. brikz.selectorConversion = SelectorConversionBrik;
  964. brikz.classInit = ClassInitBrik;
  965. brikz.manipulation = ManipulationBrik;
  966. brikz.packages = PackagesBrik;
  967. brikz.classes = ClassesBrik;
  968. brikz.methods = MethodsBrik;
  969. brikz.stInit = SmalltalkInitBrik;
  970. brikz.augments = AugmentsBrik;
  971. brikz.asReceiver = AsReceiverBrik;
  972. brikz.amd = AMDBrik;
  973. brikz.rebuild();
  974. /* Making smalltalk that can run */
  975. function runnable() {
  976. brikz.messageSend = MessageSendBrik;
  977. brikz.runtime = RuntimeBrik;
  978. brikz.primitives = PrimitivesBrik;
  979. brikz.rebuild();
  980. }
  981. return {
  982. api: api,
  983. nil: brikz.root.nil,
  984. dnu: brikz.root.rootAsClass,
  985. globals: brikz.smalltalkGlobals.globals,
  986. asReceiver: brikz.asReceiver.asReceiver
  987. };
  988. });