1
0

kernel-runtime.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  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. console.warn("$core.nextId() deprecated. Use your own unique counter.");
  167. oid += 1;
  168. return oid;
  169. };
  170. /* Converts a JavaScript object to valid Smalltalk Object */
  171. st.readJSObject = function (js) {
  172. if (js == null) return null;
  173. else if (Array.isArray(js)) return js.map(st.readJSObject);
  174. else if (js.constructor !== Object) return js;
  175. var pairs = [];
  176. for (var i in js) {
  177. pairs.push(i, st.readJSObject(js[i]));
  178. }
  179. return globals.Dictionary._newFromPairs_(pairs);
  180. };
  181. /* Boolean assertion */
  182. st.assert = function (shouldBeBoolean) {
  183. if (typeof shouldBeBoolean === "boolean") return shouldBeBoolean;
  184. else if (shouldBeBoolean != null && typeof shouldBeBoolean === "object") {
  185. shouldBeBoolean = shouldBeBoolean.valueOf();
  186. if (typeof shouldBeBoolean === "boolean") return shouldBeBoolean;
  187. }
  188. globals.NonBooleanReceiver._new()._object_(shouldBeBoolean)._signal();
  189. };
  190. /* List of all reserved words in JavaScript. They may not be used as variables
  191. in Smalltalk. */
  192. st.reservedWords = [
  193. // http://www.ecma-international.org/ecma-262/6.0/#sec-keywords
  194. 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
  195. 'default', 'delete', 'do', 'else', 'export', 'extends', 'finally',
  196. 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new',
  197. 'return', 'super', 'switch', 'this', 'throw', 'try', 'typeof',
  198. 'var', 'void', 'while', 'with', 'yield',
  199. // in strict mode
  200. 'let', 'static',
  201. // Amber protected words: these should not be compiled as-is when in code
  202. 'arguments',
  203. // http://www.ecma-international.org/ecma-262/6.0/#sec-future-reserved-words
  204. 'await', 'enum',
  205. // in strict mode
  206. 'implements', 'interface', 'package', 'private', 'protected', 'public'
  207. ];
  208. st.globalJsVariables = ['window', 'document', 'process', 'global'];
  209. }
  210. RuntimeBrik.deps = ["selectorConversion", "smalltalkGlobals", "runtimeClasses"];
  211. function RuntimeBrik (brikz, st) {
  212. var globals = brikz.smalltalkGlobals.globals;
  213. var setClassConstructor = brikz.runtimeClasses.setClassConstructor;
  214. function SmalltalkMethodContext (home, setup) {
  215. this.sendIdx = {};
  216. this.homeContext = home;
  217. this.setup = setup || function () {
  218. };
  219. this.supercall = false;
  220. }
  221. // Fallbacks
  222. SmalltalkMethodContext.prototype.locals = {};
  223. SmalltalkMethodContext.prototype.receiver = null;
  224. SmalltalkMethodContext.prototype.selector = null;
  225. SmalltalkMethodContext.prototype.lookupClass = null;
  226. defineMethod(SmalltalkMethodContext, "fill", function (receiver, selector, locals, lookupClass) {
  227. this.receiver = receiver;
  228. this.selector = selector;
  229. this.locals = locals || {};
  230. this.lookupClass = lookupClass;
  231. if (this.homeContext) {
  232. this.homeContext.evaluatedSelector = selector;
  233. }
  234. });
  235. defineMethod(SmalltalkMethodContext, "fillBlock", function (locals, ctx, index) {
  236. this.locals = locals || {};
  237. this.outerContext = ctx;
  238. this.index = index || 0;
  239. });
  240. defineMethod(SmalltalkMethodContext, "init", function () {
  241. var home = this.homeContext;
  242. if (home) {
  243. home.init();
  244. }
  245. this.setup(this);
  246. });
  247. defineMethod(SmalltalkMethodContext, "method", function () {
  248. var method;
  249. var lookup = this.lookupClass || this.receiver.a$cls;
  250. while (!method && lookup) {
  251. method = lookup.methods[st.js2st(this.selector)];
  252. lookup = lookup.superclass;
  253. }
  254. return method;
  255. });
  256. setClassConstructor(globals.MethodContext, SmalltalkMethodContext);
  257. /* This is the current call context object.
  258. In Smalltalk code, it is accessible just by using 'thisContext' variable.
  259. In JS code, use api.getThisContext() (see below).
  260. */
  261. var thisContext = null;
  262. st.withContext = function (worker, setup) {
  263. return thisContext ?
  264. inContext(worker, setup) :
  265. inContextWithErrorHandling(worker, setup);
  266. };
  267. /*
  268. Runs worker function so that error handler is not set up
  269. if there isn't one. This is accomplished by unconditional
  270. wrapping inside a context of a simulated `nil seamlessDoIt` call,
  271. which then stops error handler setup (see st.withContext above).
  272. The effect is, $core.seamless(fn)'s exceptions are not
  273. handed into ST error handler and caller should process them.
  274. */
  275. st.seamless = function (worker) {
  276. return inContext(worker, function (ctx) {
  277. ctx.fill(null, "seamlessDoIt", {}, globals.UndefinedObject);
  278. });
  279. };
  280. function inContextWithErrorHandling (worker, setup) {
  281. try {
  282. return inContext(worker, setup);
  283. } catch (error) {
  284. handleError(error);
  285. thisContext = null;
  286. // Rethrow the error in any case.
  287. error.amberHandled = true;
  288. throw error;
  289. }
  290. }
  291. function inContext (worker, setup) {
  292. var oldContext = thisContext;
  293. thisContext = new SmalltalkMethodContext(thisContext, setup);
  294. var result = worker(thisContext);
  295. thisContext = oldContext;
  296. return result;
  297. }
  298. /* Wrap a JavaScript exception in a Smalltalk Exception.
  299. In case of a RangeError, stub the stack after 100 contexts to
  300. avoid another RangeError later when the stack is manipulated. */
  301. function wrappedError (error) {
  302. var errorWrapper = globals.JavaScriptException._on_(error);
  303. // Add the error to the context, so it is visible in the stack
  304. try {
  305. errorWrapper._signal();
  306. } catch (ex) {
  307. }
  308. var context = st.getThisContext();
  309. if (isRangeError(error)) {
  310. stubContextStack(context);
  311. }
  312. errorWrapper._context_(context);
  313. return errorWrapper;
  314. }
  315. /* Stub the context stack after 100 contexts */
  316. function stubContextStack (context) {
  317. var currentContext = context;
  318. var contexts = 0;
  319. while (contexts < 100) {
  320. if (currentContext) {
  321. currentContext = currentContext.homeContext;
  322. }
  323. contexts++;
  324. }
  325. if (currentContext) {
  326. currentContext.homeContext = undefined;
  327. }
  328. }
  329. function isRangeError (error) {
  330. return error instanceof RangeError;
  331. }
  332. /* Handles Smalltalk errors. Triggers the registered ErrorHandler
  333. (See the Smalltalk class ErrorHandler and its subclasses */
  334. function handleError (error) {
  335. if (!error.smalltalkError) {
  336. error = wrappedError(error);
  337. }
  338. globals.ErrorHandler._handleError_(error);
  339. }
  340. /* Handle thisContext pseudo variable */
  341. st.getThisContext = function () {
  342. if (thisContext) {
  343. thisContext.init();
  344. return thisContext;
  345. } else {
  346. return null;
  347. }
  348. };
  349. }
  350. MessageSendBrik.deps = ["smalltalkGlobals", "selectorConversion", "root"];
  351. function MessageSendBrik (brikz, st) {
  352. var globals = brikz.smalltalkGlobals.globals;
  353. var nilAsReceiver = brikz.root.nilAsReceiver;
  354. /* Send message programmatically. Used to implement #perform: & Co. */
  355. st.send2 = function (self, selector, args, klass) {
  356. if (self == null) {
  357. self = nilAsReceiver;
  358. }
  359. var method = klass ? klass.fn.prototype[st.st2js(selector)] : self.a$cls && self[st.st2js(selector)];
  360. if (method) {
  361. return method.apply(self, args || []);
  362. } else {
  363. return messageNotUnderstood(self.a$cls ? self : wrapJavaScript(self), selector, args);
  364. }
  365. };
  366. function wrapJavaScript (o) {
  367. return globals.JSObjectProxy._on_(o);
  368. }
  369. st.wrapJavaScript = wrapJavaScript;
  370. /* Handles #dnu:. Calls #doesNotUnderstand:. */
  371. function messageNotUnderstood (receiver, stSelector, args) {
  372. return receiver._doesNotUnderstand_(
  373. globals.Message._new()
  374. ._selector_(stSelector)
  375. ._arguments_([].slice.call(args))
  376. );
  377. }
  378. /* If the object property is a function, then call it, except if it starts with
  379. an uppercase character (we probably want to answer the function itself in this
  380. case and send it #new from Amber).
  381. */
  382. st.accessJavaScript = function accessJavaScript (self, propertyName, args) {
  383. var propertyValue = self[propertyName];
  384. if (typeof propertyValue === "function" && !/^[A-Z]/.test(propertyName)) {
  385. return propertyValue.apply(self, args || []);
  386. } else if (args.length > 0) {
  387. self[propertyName] = args[0];
  388. return self;
  389. } else {
  390. return propertyValue;
  391. }
  392. };
  393. this.messageNotUnderstood = messageNotUnderstood;
  394. }
  395. StartImageBrik.deps = ["frameBinding", "runtimeMethods", "runtime", "primitives"];
  396. function StartImageBrik (brikz, st) {
  397. this.__init__ = function () {
  398. var classes = brikz.behaviors.classes;
  399. classes().forEach(function (klass) {
  400. klass._initialize();
  401. });
  402. };
  403. this.__init__.once = true;
  404. }
  405. /* Making smalltalk that can run */
  406. function configureWithRuntime (brikz) {
  407. brikz.dnu = DNUBrik;
  408. brikz.manipulation = ManipulationBrik;
  409. brikz.runtimeClasses = RuntimeClassesBrik;
  410. brikz.frameBinding = FrameBindingBrik;
  411. brikz.runtimeMethods = RuntimeMethodsBrik;
  412. brikz.messageSend = MessageSendBrik;
  413. brikz.runtime = RuntimeBrik;
  414. brikz.primitives = PrimitivesBrik;
  415. brikz.startImage = StartImageBrik;
  416. brikz.rebuild();
  417. }
  418. return configureWithRuntime;
  419. });