boot.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. /* ====================================================================
  2. |
  3. | Jtalk Smalltalk
  4. | http://jtalk-project.org
  5. |
  6. ======================================================================
  7. ======================================================================
  8. |
  9. | Copyright (c) 2010-2011
  10. | Nicolas Petton <petton.nicolas@gmail.com>
  11. |
  12. | Jtalk is released under the MIT license
  13. |
  14. | Permission is hereby granted, free of charge, to any person obtaining
  15. | a copy of this software and associated documentation files (the
  16. | 'Software'), to deal in the Software without restriction, including
  17. | without limitation the rights to use, copy, modify, merge, publish,
  18. | distribute, sublicense, and/or sell copies of the Software, and to
  19. | permit persons to whom the Software is furnished to do so, subject to
  20. | the following conditions:
  21. |
  22. | The above copyright notice and this permission notice shall be
  23. | included in all copies or substantial portions of the Software.
  24. |
  25. | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
  26. | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  27. | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  28. | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  29. | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  30. | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  31. | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  32. |
  33. ==================================================================== */
  34. /* Smalltalk constructors definition */
  35. function SmalltalkObject(){};
  36. function SmalltalkBehavior(){};
  37. function SmalltalkClass(){};
  38. function SmalltalkMetaclass(){
  39. this.meta = true;
  40. };
  41. function SmalltalkMethod(){};
  42. function SmalltalkNil(){};
  43. function Smalltalk(){
  44. var st = this;
  45. /* Smalltalk class creation. A class is an instance of an automatically
  46. created metaclass object. Newly created classes (not their metaclass)
  47. should be added to the smalltalk object, see smalltalk.addClass().
  48. Superclass linking is *not* handled here, see smalltalk.init() */
  49. function klass(spec) {
  50. var spec = spec || {};
  51. var that;
  52. if(spec.meta) {
  53. that = new SmalltalkMetaclass();
  54. } else {
  55. that = new (klass({meta: true})).fn;
  56. that.klass.instanceClass = that;
  57. that.className = spec.className;
  58. that.klass.className = that.className + ' class';
  59. }
  60. that.fn = spec.fn || function(){};
  61. that.superclass = spec.superclass;
  62. that.iVarNames = spec.iVarNames || [];
  63. if(that.superclass) {
  64. that.klass.superclass = that.superclass.klass;
  65. }
  66. that.category = spec.category || "";
  67. that.fn.prototype.methods = {};
  68. that.fn.prototype.inheritedMethods = {};
  69. that.fn.prototype.klass = that;
  70. return that;
  71. };
  72. /* Smalltalk method object. To add a method to a class,
  73. use smalltalk.addMethod() */
  74. st.method = function(spec) {
  75. var that = new SmalltalkMethod();
  76. that.selector = spec.selector;
  77. that.jsSelector = spec.jsSelector;
  78. that.category = spec.category;
  79. that.source = spec.source;
  80. that.messageSends = spec.messageSends || [];
  81. that.referencedClasses = spec.referencedClasses || [];
  82. that.fn = spec.fn;
  83. return that
  84. };
  85. /* Initialize a class in its class hierarchy. Handle both class and
  86. metaclasses. */
  87. st.init = function(klass) {
  88. var subclasses = st.subclasses(klass);
  89. var methods;
  90. if(klass.superclass && klass.superclass !== nil) {
  91. methods = st.methods(klass.superclass);
  92. //Methods linking
  93. for(var i in methods) {
  94. if(!klass.fn.prototype.methods[i]) {
  95. klass.fn.prototype.inheritedMethods[i] = methods[i];
  96. klass.fn.prototype[methods[i].jsSelector] = methods[i].fn;
  97. }
  98. }
  99. }
  100. for(var i=0;i<subclasses.length;i++) {
  101. st.init(subclasses[i]);
  102. }
  103. if(klass.klass && !klass.meta) {
  104. st.init(klass.klass);
  105. }
  106. };
  107. /* Answer all registered Smalltalk classes */
  108. st.classes = function() {
  109. var classes = [];
  110. for(var i in st) {
  111. if(i.search(/^[A-Z]/g) != -1) {
  112. classes.push(st[i]);
  113. }
  114. }
  115. return classes
  116. };
  117. /* Answer all methods (included inherited ones) of klass. */
  118. st.methods = function(klass) {
  119. var methods = {};
  120. for(var i in klass.fn.prototype.methods) {
  121. methods[i] = klass.fn.prototype.methods[i]
  122. }
  123. for(var i in klass.fn.prototype.inheritedMethods) {
  124. methods[i] = klass.fn.prototype.inheritedMethods[i]
  125. }
  126. return methods;
  127. }
  128. /* Answer the direct subclasses of klass. */
  129. st.subclasses = function(klass) {
  130. var subclasses = [];
  131. var classes = st.classes();
  132. for(var i in classes) {
  133. if(classes[i].fn) {
  134. //Metaclasses
  135. if(classes[i].klass && classes[i].klass.superclass === klass) {
  136. subclasses.push(classes[i].klass);
  137. }
  138. //Classes
  139. if(classes[i].superclass === klass) {
  140. subclasses.push(classes[i]);
  141. }
  142. }
  143. }
  144. return subclasses;
  145. };
  146. /* Create a new class wrapping a JavaScript constructor, and add it to the
  147. global smalltalk object. */
  148. st.mapClassName = function(className, category, fn, superclass) {
  149. st[className] = klass({
  150. className: className,
  151. category: category,
  152. superclass: superclass,
  153. fn: fn
  154. });
  155. };
  156. /* Add a class to the smalltalk object, creating a new one if needed. */
  157. st.addClass = function(className, superclass, iVarNames, category) {
  158. if(st[className]) {
  159. st[className].superclass = superclass;
  160. st[className].iVarNames = iVarNames;
  161. st[className].category = category || st[className].category;
  162. } else {
  163. st[className] = klass({
  164. className: className,
  165. iVarNames: iVarNames,
  166. superclass: superclass
  167. });
  168. st[className].category = category || '';
  169. }
  170. };
  171. /* Add a method to a class */
  172. st.addMethod = function(jsSelector, method, klass) {
  173. klass.fn.prototype[jsSelector] = method.fn;
  174. klass.fn.prototype.methods[method.selector] = method;
  175. method.methodClass = klass;
  176. method.jsSelector = jsSelector;
  177. };
  178. /* Handles Smalltalk message send. Automatically converts undefined to the nil object.
  179. If the receiver does not understand the selector, call its #doesNotUnderstand: method */
  180. sendWithoutContext = function(receiver, selector, args, klass) {
  181. if(typeof receiver === "undefined") {
  182. receiver = nil;
  183. }
  184. if(!klass && receiver.klass && receiver[selector]) {
  185. return receiver[selector].apply(receiver, args);
  186. } else if(klass && klass.fn.prototype[selector]) {
  187. return klass.fn.prototype[selector].apply(receiver, args)
  188. }
  189. return messageNotUnderstood(receiver, selector, args);
  190. };
  191. /* Handles unhandled errors during message sends */
  192. sendWithContext = function(receiver, selector, args, klass) {
  193. if(thisContext) {
  194. return withContextSend(receiver, selector, args, klass);
  195. } else {
  196. try {return withContextSend(receiver, selector, args, klass)}
  197. catch(error) {
  198. // Reset the context stack in any case
  199. thisContext = undefined;
  200. if(error.smalltalkError) {
  201. handleError(error);
  202. } else {
  203. throw(error);
  204. }
  205. }
  206. }
  207. };
  208. /* Same as sendWithoutContext but creates a methodContext. */
  209. withContextSend = function(receiver, selector, args, klass) {
  210. var call, context;
  211. if(typeof receiver === "undefined") {
  212. receiver = nil;
  213. }
  214. if(!klass && receiver.klass && receiver[selector]) {
  215. context = pushContext(receiver, selector, args);
  216. call = receiver[selector].apply(receiver, args);
  217. popContext(context);
  218. return call;
  219. } else if(klass && klass.fn.prototype[selector]) {
  220. context = pushContext(receiver, selector, args);
  221. call = klass.fn.prototype[selector].apply(receiver, args);
  222. popContext(context);
  223. return call;
  224. }
  225. return messageNotUnderstood(receiver, selector, args);
  226. };
  227. /* Handles Smalltalk errors. Triggers the registered ErrorHandler
  228. (See the Smalltalk class ErrorHandler and its subclasses */
  229. function handleError(error) {
  230. thisContext = undefined;
  231. smalltalk.ErrorHandler._current()._handleError_(error);
  232. }
  233. /* Handles #dnu: *and* JavaScript method calls.
  234. if the receiver has no klass, we consider it a JS object (outside of the
  235. Jtalk system). Else assume that the receiver understands #doesNotUnderstand: */
  236. function messageNotUnderstood(receiver, selector, args) {
  237. /* Handles JS method calls. */
  238. if(receiver.klass === undefined || receiver.allowJavaScriptCalls) {
  239. return callJavaScriptMethod(receiver, selector, args);
  240. }
  241. /* Handles not understood messages. Also see the Jtalk counter-part
  242. Object>>doesNotUnderstand: */
  243. return receiver._doesNotUnderstand_(
  244. st.Message._new()
  245. ._selector_(st.convertSelector(selector))
  246. ._arguments_(args)
  247. );
  248. };
  249. function callJavaScriptMethod(receiver, selector, args) {
  250. /* Call a method of a JS object, or answer a property.
  251. Converts keyword-based selectors by using the first
  252. keyword only, but keeping all message arguments.
  253. Example:
  254. "self do: aBlock with: anObject" -> "self.do(aBlock, anObject)" */
  255. var jsSelector = selector
  256. .replace(/^_/, '')
  257. .replace(/_.*/g, '');
  258. var jsProperty = receiver[jsSelector];
  259. if(typeof jsProperty === "function") {
  260. return jsProperty.apply(receiver, args);
  261. } else if(jsProperty !== undefined) {
  262. if(args[0]) {
  263. receiver[jsSelector] = args[0];
  264. return nil;
  265. } else {
  266. return jsProperty
  267. }
  268. }
  269. smalltalk.Error._signal_(receiver + ' is not a Jtalk object and "' + jsSelector + '" is undefined')
  270. };
  271. /* Reuse old contexts stored in oldContexts */
  272. st.oldContexts = [];
  273. /* Handle thisContext pseudo variable */
  274. pushContext = function(receiver, selector, temps) {
  275. if(thisContext) {
  276. return thisContext = thisContext.newContext(receiver, selector, temps);
  277. } else {
  278. return thisContext = new SmalltalkMethodContext(receiver, selector, temps);
  279. }
  280. };
  281. popContext = function(context) {
  282. if(context) {
  283. context.removeYourself();
  284. }
  285. };
  286. /* Convert a string to a valid smalltalk selector.
  287. if you modify the following functions, also change String>>asSelector
  288. accordingly */
  289. st.convertSelector = function(selector) {
  290. if(selector.match(/__/)) {
  291. return convertBinarySelector(selector);
  292. } else {
  293. return convertKeywordSelector(selector);
  294. }
  295. };
  296. function convertKeywordSelector(selector) {
  297. return selector.replace(/^_/, '').replace(/_/g, ':');
  298. };
  299. function convertBinarySelector(selector) {
  300. return selector
  301. .replace(/^_/, '')
  302. .replace(/_plus/, '+')
  303. .replace(/_minus/, '-')
  304. .replace(/_star/, '*')
  305. .replace(/_slash/, '/')
  306. .replace(/_gt/, '>')
  307. .replace(/_lt/, '<')
  308. .replace(/_eq/, '=')
  309. .replace(/_comma/, ',')
  310. .replace(/_at/, '@')
  311. };
  312. /* Converts a JavaScript object to valid Smalltalk Object */
  313. st.readJSObject = function(js) {
  314. var object = js;
  315. var readObject = (js.constructor === Object);
  316. var readArray = (js.constructor === Array);
  317. if(readObject) {
  318. object = smalltalk.Dictionary._new();
  319. }
  320. for(var i in js) {
  321. if(readObject) {
  322. object._at_put_(i, st.readJSObject(js[i]));
  323. }
  324. if(readArray) {
  325. object[i] = st.readJSObject(js[i]);
  326. }
  327. }
  328. return object;
  329. };
  330. /* Toggle deployment mode (no context will be handled during message send */
  331. st.setDeploymentMode = function() {
  332. st.send = sendWithoutContext;
  333. };
  334. st.setDevelopmentMode = function() {
  335. st.send = sendWithContext;
  336. }
  337. /* Set development mode by default */
  338. st.setDevelopmentMode();
  339. }
  340. function SmalltalkMethodContext(receiver, selector, temps, home) {
  341. var that = this;
  342. that.receiver = receiver;
  343. that.selector = selector;
  344. that.temps = temps || {};
  345. that.homeContext = home;
  346. that.newContext = function(receiver, selector, temps) {
  347. var c = smalltalk.oldContexts.pop();
  348. if(c) {
  349. c.homeContext = that;
  350. c.receiver = receiver;
  351. c.selector = selector;
  352. c.temps = temps || {};
  353. } else {
  354. c = new SmalltalkMethodContext(receiver, selector, temps, that);
  355. }
  356. return c;
  357. }
  358. that.removeYourself = function() {
  359. thisContext = that.homeContext;
  360. smalltalk.oldContexts.push(that);
  361. }
  362. }
  363. /* Global Smalltalk objects. nil and thisContext shouldn't be globals. */
  364. var nil = new SmalltalkNil();
  365. var smalltalk = new Smalltalk();
  366. var thisContext = undefined;
  367. /* Utilities */
  368. Array.prototype.remove = function(s){
  369. var index = this.indexOf(s);
  370. if(this.indexOf(s) != -1)this.splice(index, 1);
  371. }
  372. if(this.jQuery) {
  373. this.jQuery.allowJavaScriptCalls = true;
  374. }
  375. /****************************************************************************************/
  376. /* Base classes mapping. If you edit this part, do not forget to set the superclass of the
  377. object metaclass to Class after the definition of Object */
  378. smalltalk.mapClassName("Object", "Kernel", SmalltalkObject);
  379. smalltalk.mapClassName("Smalltalk", "Kernel", Smalltalk, smalltalk.Object);
  380. smalltalk.mapClassName("Behavior", "Kernel", SmalltalkBehavior, smalltalk.Object);
  381. smalltalk.mapClassName("Class", "Kernel", SmalltalkClass, smalltalk.Behavior);
  382. smalltalk.mapClassName("Metaclass", "Kernel", SmalltalkMetaclass, smalltalk.Behavior);
  383. smalltalk.mapClassName("CompiledMethod", "Kernel", SmalltalkMethod, smalltalk.Object);
  384. smalltalk.Object.klass.superclass = smalltalk.Class
  385. smalltalk.mapClassName("Number", "Kernel", Number, smalltalk.Object);
  386. smalltalk.mapClassName("BlockClosure", "Kernel", Function, smalltalk.Object);
  387. smalltalk.mapClassName("Boolean", "Kernel", Boolean, smalltalk.Object);
  388. smalltalk.mapClassName("Date", "Kernel", Date, smalltalk.Object);
  389. smalltalk.mapClassName("UndefinedObject", "Kernel", SmalltalkNil, smalltalk.Object);
  390. smalltalk.mapClassName("Collection", "Kernel", null, smalltalk.Object);
  391. smalltalk.mapClassName("SequenceableCollection", "Kernel", null, smalltalk.Collection);
  392. smalltalk.mapClassName("String", "Kernel", String, smalltalk.SequenceableCollection);
  393. smalltalk.mapClassName("Array", "Kernel", Array, smalltalk.SequenceableCollection);
  394. smalltalk.mapClassName("RegularExpression", "Kernel", RegExp, smalltalk.String);
  395. smalltalk.mapClassName("Error", "Kernel", Error, smalltalk.Object);
  396. smalltalk.mapClassName("MethodContext", "Kernel", SmalltalkMethodContext, smalltalk.Object);
  397. if(this.CanvasRenderingContext2D) {
  398. smalltalk.mapClassName("CanvasRenderingContext", "Canvas", CanvasRenderingContext2D, smalltalk.Object);
  399. }