kernel-runtime.js 18 KB

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