kernel-runtime.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. //jshint eqnull:true
  2. define(function () {
  3. "use strict";
  4. function defineMethod (klass, name, method) {
  5. Object.defineProperty(klass.prototype, name, {
  6. value: method,
  7. enumerable: false, configurable: true, writable: true
  8. });
  9. }
  10. function noop () {
  11. }
  12. DNUBrik.deps = ["selectors", "smalltalkGlobals", "manipulation", "classes"];
  13. function DNUBrik (brikz, st) {
  14. var selectorsBrik = brikz.selectors;
  15. var globals = brikz.smalltalkGlobals.globals;
  16. var installJSMethod = brikz.manipulation.installJSMethod;
  17. var nilAsClass = brikz.classes.nilAsClass;
  18. /* Method not implemented handlers */
  19. function makeDnuHandler (pair, targetClasses) {
  20. var jsSelector = pair.js;
  21. var fn = createHandler(pair.st);
  22. installJSMethod(nilAsClass.fn.prototype, jsSelector, fn);
  23. targetClasses.forEach(function (target) {
  24. installJSMethod(target.fn.prototype, jsSelector, fn);
  25. });
  26. }
  27. this.makeDnuHandler = makeDnuHandler;
  28. /* Dnu handler method */
  29. function createHandler (stSelector) {
  30. return function () {
  31. return globals.Message._selector_arguments_notUnderstoodBy_(
  32. stSelector, [].slice.call(arguments), this
  33. );
  34. };
  35. }
  36. selectorsBrik.selectorPairs.forEach(function (pair) {
  37. makeDnuHandler(pair, []);
  38. });
  39. }
  40. function ManipulationBrik (brikz, st) {
  41. function installJSMethod (obj, jsSelector, fn) {
  42. Object.defineProperty(obj, jsSelector, {
  43. value: fn,
  44. enumerable: false, configurable: true, writable: true
  45. });
  46. }
  47. function installMethod (method, klass) {
  48. installJSMethod(klass.fn.prototype, method.jsSelector, method.fn);
  49. }
  50. this.installMethod = installMethod;
  51. this.installJSMethod = installJSMethod;
  52. }
  53. RuntimeClassesBrik.deps = ["selectors", "dnu", "behaviors", "classes", "manipulation"];
  54. function RuntimeClassesBrik (brikz, st) {
  55. var selectors = brikz.selectors;
  56. var classes = brikz.behaviors.classes;
  57. var wireKlass = brikz.classes.wireKlass;
  58. var installMethod = brikz.manipulation.installMethod;
  59. var installJSMethod = brikz.manipulation.installJSMethod;
  60. var detachedRootClasses = [];
  61. function markClassDetachedRoot (klass) {
  62. klass.detachedRoot = true;
  63. detachedRootClasses = classes().filter(function (klass) {
  64. return klass.detachedRoot;
  65. });
  66. }
  67. this.detachedRootClasses = function () {
  68. return detachedRootClasses;
  69. };
  70. /* Initialize a class in its class hierarchy. Handle both classes and
  71. metaclasses. */
  72. function initClassAndMetaclass (klass) {
  73. initClass(klass);
  74. if (klass.a$cls && !klass.meta) {
  75. initClass(klass.a$cls);
  76. }
  77. }
  78. classes().forEach(function (klass) {
  79. if (!klass.trait) initClassAndMetaclass(klass);
  80. });
  81. st._classAdded = initClassAndMetaclass;
  82. function initClass (klass) {
  83. wireKlass(klass);
  84. if (klass.detachedRoot) {
  85. copySuperclass(klass);
  86. }
  87. installMethods(klass);
  88. }
  89. function copySuperclass (klass) {
  90. var myproto = klass.fn.prototype,
  91. superproto = klass.superclass.fn.prototype;
  92. selectors.selectorPairs.forEach(function (selectorPair) {
  93. var jsSelector = selectorPair.js;
  94. installJSMethod(myproto, jsSelector, superproto[jsSelector]);
  95. });
  96. }
  97. function installMethods (klass) {
  98. var methods = klass.methods;
  99. Object.keys(methods).forEach(function (selector) {
  100. installMethod(methods[selector], klass);
  101. });
  102. }
  103. /* Manually set the constructor of an existing Smalltalk klass, making it a detached root class. */
  104. st.setClassConstructor = this.setClassConstructor = function (klass, constructor) {
  105. markClassDetachedRoot(klass);
  106. klass.fn = constructor;
  107. initClass(klass);
  108. };
  109. }
  110. FrameBindingBrik.deps = ["smalltalkGlobals", "runtimeClasses"];
  111. function FrameBindingBrik (brikz, st) {
  112. var globals = brikz.smalltalkGlobals.globals;
  113. var setClassConstructor = brikz.runtimeClasses.setClassConstructor;
  114. setClassConstructor(globals.Number, Number);
  115. setClassConstructor(globals.BlockClosure, Function);
  116. setClassConstructor(globals.Boolean, Boolean);
  117. setClassConstructor(globals.Date, Date);
  118. setClassConstructor(globals.String, String);
  119. setClassConstructor(globals.Array, Array);
  120. setClassConstructor(globals.RegularExpression, RegExp);
  121. setClassConstructor(globals.Error, Error);
  122. setClassConstructor(globals.Promise, Promise);
  123. this.__init__ = function () {
  124. st.alias(globals.Array, "OrderedCollection");
  125. st.alias(globals.Date, "Time");
  126. }
  127. }
  128. RuntimeMethodsBrik.deps = ["manipulation", "dnu", "runtimeClasses"];
  129. function RuntimeMethodsBrik (brikz, st) {
  130. var installMethod = brikz.manipulation.installMethod;
  131. var installJSMethod = brikz.manipulation.installJSMethod;
  132. var makeDnuHandler = brikz.dnu.makeDnuHandler;
  133. var detachedRootClasses = brikz.runtimeClasses.detachedRootClasses;
  134. st._methodAdded = function (method, klass) {
  135. installMethod(method, klass);
  136. propagateMethodChange(klass, method, klass);
  137. };
  138. st._selectorsAdded = function (newSelectors) {
  139. var targetClasses = detachedRootClasses();
  140. newSelectors.forEach(function (pair) {
  141. makeDnuHandler(pair, targetClasses);
  142. });
  143. };
  144. st._methodRemoved = function (method, klass) {
  145. delete klass.fn.prototype[method.jsSelector];
  146. propagateMethodChange(klass, method, null);
  147. };
  148. function propagateMethodChange (klass, method, exclude) {
  149. var selector = method.selector;
  150. var jsSelector = method.jsSelector;
  151. st.traverseClassTree(klass, function (subclass, sentinel) {
  152. if (subclass !== exclude) {
  153. if (initMethodInClass(subclass, selector, jsSelector)) return sentinel;
  154. }
  155. });
  156. }
  157. function initMethodInClass (klass, selector, jsSelector) {
  158. if (klass.methods[selector]) return true;
  159. if (klass.detachedRoot) {
  160. installJSMethod(klass.fn.prototype, jsSelector, klass.superclass.fn.prototype[jsSelector]);
  161. }
  162. }
  163. }
  164. PrimitivesBrik.deps = ["smalltalkGlobals"];
  165. function PrimitivesBrik (brikz, st) {
  166. var globals = brikz.smalltalkGlobals.globals;
  167. var oid = 0;
  168. /* Unique ID number generator */
  169. st.nextId = function () {
  170. console.warn("$core.nextId() deprecated. Use your own unique counter.");
  171. oid += 1;
  172. return oid;
  173. };
  174. /* Converts a JavaScript object to valid Smalltalk Object */
  175. st.readJSObject = function (js) {
  176. if (js == null) return null;
  177. else if (Array.isArray(js)) return js.map(st.readJSObject);
  178. else if (js.constructor !== Object) return js;
  179. var pairs = [];
  180. for (var i in js) {
  181. pairs.push(i, st.readJSObject(js[i]));
  182. }
  183. return globals.Dictionary._newFromPairs_(pairs);
  184. };
  185. /* Boolean assertion */
  186. st.assert = function (shouldBeBoolean) {
  187. if (typeof shouldBeBoolean === "boolean") return shouldBeBoolean;
  188. else if (shouldBeBoolean != null && typeof shouldBeBoolean === "object") {
  189. shouldBeBoolean = shouldBeBoolean.valueOf();
  190. if (typeof shouldBeBoolean === "boolean") return shouldBeBoolean;
  191. }
  192. globals.NonBooleanReceiver._new()._object_(shouldBeBoolean)._signal();
  193. };
  194. /* List of all reserved words in JavaScript. They may not be used as variables
  195. in Smalltalk. */
  196. st.reservedWords = [
  197. // http://www.ecma-international.org/ecma-262/6.0/#sec-keywords
  198. 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
  199. 'default', 'delete', 'do', 'else', 'export', 'extends', 'finally',
  200. 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new',
  201. 'return', 'super', 'switch', 'this', 'throw', 'try', 'typeof',
  202. 'var', 'void', 'while', 'with', 'yield',
  203. // in strict mode
  204. 'let', 'static',
  205. // Amber protected words: these should not be compiled as-is when in code
  206. 'arguments',
  207. // http://www.ecma-international.org/ecma-262/6.0/#sec-future-reserved-words
  208. 'await', 'enum',
  209. // in strict mode
  210. 'implements', 'interface', 'package', 'private', 'protected', 'public'
  211. ];
  212. st.globalJsVariables = ['window', 'document', 'process', 'global'];
  213. }
  214. RuntimeBrik.deps = ["selectorConversion", "smalltalkGlobals", "runtimeClasses"];
  215. function RuntimeBrik (brikz, st) {
  216. var globals = brikz.smalltalkGlobals.globals;
  217. var setClassConstructor = brikz.runtimeClasses.setClassConstructor;
  218. function SmalltalkMethodContext (home, setup) {
  219. this.sendIdx = {};
  220. this.homeContext = home;
  221. this.setup = setup || function () {
  222. };
  223. this.supercall = false;
  224. }
  225. // Fallbacks
  226. SmalltalkMethodContext.prototype.locals = Object.freeze({});
  227. SmalltalkMethodContext.prototype.receiver = null;
  228. SmalltalkMethodContext.prototype.selector = null;
  229. SmalltalkMethodContext.prototype.lookupClass = null;
  230. SmalltalkMethodContext.prototype.outerContext = null;
  231. SmalltalkMethodContext.prototype.index = 0;
  232. defineMethod(SmalltalkMethodContext, "fill", function (receiver, selector, locals, lookupClass) {
  233. this.receiver = receiver;
  234. this.selector = selector;
  235. if (locals != null) this.locals = locals;
  236. this.lookupClass = lookupClass;
  237. if (this.homeContext) {
  238. this.homeContext.evaluatedSelector = selector;
  239. }
  240. });
  241. defineMethod(SmalltalkMethodContext, "fillBlock", function (locals, ctx, index) {
  242. if (locals != null) this.locals = locals;
  243. this.outerContext = ctx;
  244. if (index) this.index = index;
  245. });
  246. defineMethod(SmalltalkMethodContext, "init", function () {
  247. var frame = this;
  248. while (frame) {
  249. if (frame.init !== this.init) return frame.init();
  250. frame.init = noop;
  251. frame.setup(frame);
  252. if (frame.outerContext) {
  253. frame.outerContext.init();
  254. }
  255. frame = frame.homeContext;
  256. }
  257. });
  258. defineMethod(SmalltalkMethodContext, "method", function () {
  259. var method;
  260. var lookup = this.lookupClass || this.receiver.a$cls;
  261. while (!method && lookup) {
  262. method = lookup.methods[st.js2st(this.selector)];
  263. lookup = lookup.superclass;
  264. }
  265. return method;
  266. });
  267. setClassConstructor(globals.MethodContext, SmalltalkMethodContext);
  268. /* This is the current call context object.
  269. In Smalltalk code, it is accessible just by using 'thisContext' variable.
  270. In JS code, use api.getThisContext() (see below).
  271. */
  272. var thisContext = null;
  273. st.withContext = function (worker, setup) {
  274. return thisContext ?
  275. inContext(worker, setup) :
  276. inContextWithErrorHandling(worker, setup);
  277. };
  278. /*
  279. Runs worker function so that error handler is not set up
  280. if there isn't one. This is accomplished by unconditional
  281. wrapping inside a context of a simulated `nil seamlessDoIt` call,
  282. which then stops error handler setup (see st.withContext above).
  283. The effect is, $core.seamless(fn)'s exceptions are not
  284. handed into ST error handler and caller should process them.
  285. */
  286. st.seamless = function (worker) {
  287. return inContext(worker, function (ctx) {
  288. ctx.fill(null, "seamlessDoIt", {}, globals.UndefinedObject);
  289. });
  290. };
  291. function inContextWithErrorHandling (worker, setup) {
  292. try {
  293. return inContext(worker, setup);
  294. } catch (error) {
  295. globals.ErrorHandler._handleError_(error);
  296. thisContext = null;
  297. // Rethrow the error in any case.
  298. throw error;
  299. }
  300. }
  301. function inContext (worker, setup) {
  302. var oldContext = thisContext;
  303. thisContext = new SmalltalkMethodContext(thisContext, setup);
  304. var result = worker(thisContext);
  305. thisContext = oldContext;
  306. return result;
  307. }
  308. /* Handle thisContext pseudo variable */
  309. st.getThisContext = function () {
  310. if (thisContext) {
  311. thisContext.init();
  312. return thisContext;
  313. } else {
  314. return null;
  315. }
  316. };
  317. }
  318. MessageSendBrik.deps = ["smalltalkGlobals", "selectorConversion", "root"];
  319. function MessageSendBrik (brikz, st) {
  320. var globals = brikz.smalltalkGlobals.globals;
  321. var nilAsReceiver = brikz.root.nilAsReceiver;
  322. /* Send message programmatically. Used to implement #perform: & Co. */
  323. st.send2 = function (self, selector, args, klass) {
  324. if (self == null) {
  325. self = nilAsReceiver;
  326. }
  327. var method = klass ? klass.fn.prototype[st.st2js(selector)] : self.a$cls && self[st.st2js(selector)];
  328. return method ?
  329. method.apply(self, args || []) :
  330. globals.Message._selector_arguments_notUnderstoodBy_(
  331. selector, [].slice.call(args), self.a$cls ? self : wrapJavaScript(self)
  332. );
  333. };
  334. function wrapJavaScript (o) {
  335. return globals.JSObjectProxy._on_(o);
  336. }
  337. st.wrapJavaScript = wrapJavaScript;
  338. /* If the object property is a function, then call it, except if it starts with
  339. an uppercase character (we probably want to answer the function itself in this
  340. case and send it #new from Amber).
  341. */
  342. st.accessJavaScript = function accessJavaScript (self, propertyName, args) {
  343. var propertyValue = self[propertyName];
  344. if (typeof propertyValue === "function" && !/^[A-Z]/.test(propertyName)) {
  345. return propertyValue.apply(self, args || []);
  346. } else if (args.length > 0) {
  347. self[propertyName] = args[0];
  348. return self;
  349. } else {
  350. return propertyValue;
  351. }
  352. };
  353. }
  354. /* Making smalltalk that can run */
  355. function configureWithRuntime (brikz) {
  356. brikz.dnu = DNUBrik;
  357. brikz.manipulation = ManipulationBrik;
  358. brikz.runtimeClasses = RuntimeClassesBrik;
  359. brikz.frameBinding = FrameBindingBrik;
  360. brikz.runtimeMethods = RuntimeMethodsBrik;
  361. brikz.messageSend = MessageSendBrik;
  362. brikz.runtime = RuntimeBrik;
  363. brikz.primitives = PrimitivesBrik;
  364. brikz.rebuild();
  365. }
  366. return configureWithRuntime;
  367. });