/* ==================================================================== | | Amber Smalltalk | http://amber-lang.net | ====================================================================== ====================================================================== | | Copyright (c) 2010-2013 | Nicolas Petton | | Copyright (c) 2012-2013 | The Amber team https://github.com/amber-smalltalk?tab=members | Amber contributors https://github.com/amber-smalltalk/amber/graphs/contributors | | Amber is released under the MIT license | | Permission is hereby granted, free of charge, to any person obtaining | a copy of this software and associated documentation files (the | 'Software'), to deal in the Software without restriction, including | without limitation the rights to use, copy, modify, merge, publish, | distribute, sublicense, and/or sell copies of the Software, and to | permit persons to whom the Software is furnished to do so, subject to | the following conditions: | | The above copyright notice and this permission notice shall be | included in all copies or substantial portions of the Software. | | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ==================================================================== */ define("amber_vm/boot", [ './browser-compatibility' ], function () { /* Reconfigurable micro composition system, https://github.com/amber-smalltalk/brikz */ function Brikz(api, apiKey, initKey) { var brikz = this, backup = {}; apiKey = apiKey || 'exports'; initKey = initKey || '__init__'; function mixin(src, target, what) { for (var keys = Object.keys(what||src), l=keys.length, i=0; i>"+method.selector + " from different class " + klass.className); } delete klass.fn.prototype[st.selector(method.selector)]; delete klass.methods[method.selector]; st.initClass(klass); propagateMethodChange(klass); // Do *not* delete protocols from here. // This is handled by #removeCompiledMethod }; /* Answer all method selectors based on dnu handlers */ st.allSelectors = function() { return dnu.selectors; }; } function AugmentsBrik(brikz, st) { /* Make sure that console is defined */ if(typeof console === "undefined") { this.console = { log: function() {}, warn: function() {}, info: function() {}, debug: function() {}, error: function() {} }; } /* Array extensions */ Array.prototype.addElement = function(el) { if(typeof el === 'undefined') { return; } if(this.indexOf(el) == -1) { this.push(el); } }; Array.prototype.removeElement = function(el) { var i = this.indexOf(el); if (i !== -1) { this.splice(i, 1); } }; } function SmalltalkInitBrik(brikz, st) { brikz.ensure("classInit"); brikz.ensure("classes"); var nil = brikz.ensure("root").nil; var initialized = false; /* Smalltalk initialization. Called on page load */ st.initialize = function() { if(initialized) { return; } st.classes().forEach(function(klass) { st.init(klass); }); runnable(); st.classes().forEach(function(klass) { klass._initialize(); }); initialized = true; }; this.initialized = function () { return initialized; }; this.__init__ = function () { st.addPackage("Kernel-Methods"); st.wrapClassName("Number", "Kernel-Objects", Number, st.Object); st.wrapClassName("BlockClosure", "Kernel-Methods", Function, st.Object); st.wrapClassName("Boolean", "Kernel-Objects", Boolean, st.Object); st.wrapClassName("Date", "Kernel-Objects", Date, st.Object); st.addPackage("Kernel-Collections"); st.addClass("Collection", st.Object, null, "Kernel-Collections"); st.addClass("IndexableCollection", st.Collection, null, "Kernel-Collections"); st.addClass("SequenceableCollection", st.IndexableCollection, null, "Kernel-Collections"); st.addClass("CharacterArray", st.SequenceableCollection, null, "Kernel-Collections"); st.wrapClassName("String", "Kernel-Collections", String, st.CharacterArray); st.wrapClassName("Array", "Kernel-Collections", Array, st.SequenceableCollection); st.wrapClassName("RegularExpression", "Kernel-Collections", RegExp, st.Object); st.addPackage("Kernel-Exceptions"); st.wrapClassName("Error", "Kernel-Exceptions", Error, st.Object); /* Alias definitions */ st.alias(st.Array, "OrderedCollection"); st.alias(st.Date, "Time"); }; } function PrimitivesBrik(brikz, st) { /* Unique ID number generator */ var oid = 0; st.nextId = function() { oid += 1; return oid; }; /* Converts a JavaScript object to valid Smalltalk Object */ st.readJSObject = function(js) { var object = js; var readObject = (js.constructor === Object); var readArray = (js.constructor === Array); if(readObject) { object = st.Dictionary._new(); } for(var i in js) { if(readObject) { object._at_put_(i, st.readJSObject(js[i])); } if(readArray) { object[i] = st.readJSObject(js[i]); } } return object; }; /* Boolean assertion */ st.assert = function(shouldBeBoolean) { if (undefined !== shouldBeBoolean && shouldBeBoolean.klass === st.Boolean) { return shouldBeBoolean == true; } else { st.NonBooleanReceiver._new()._object_(shouldBeBoolean)._signal(); } }; /* List of all reserved words in JavaScript. They may not be used as variables in Smalltalk. */ // list of reserved JavaScript keywords as of // http://es5.github.com/#x7.6.1.1 // and // http://people.mozilla.org/~jorendorff/es6-draft.html#sec-7.6.1 st.reservedWords = ['break', 'case', 'catch', 'continue', 'debugger', 'default', 'delete', 'do', 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof', 'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with', // ES5: future use: http://es5.github.com/#x7.6.1.2 'class', 'const', 'enum', 'export', 'extends', 'import', 'super', // ES5: future use in strict mode 'implements', 'interface', 'let', 'package', 'private', 'protected', 'public', 'static', 'yield']; st.globalJsVariables = ['jQuery', 'window', 'document', 'process', 'global']; } function RuntimeBrik(brikz, st) { brikz.ensure("selectorConversion"); var nil = brikz.ensure("root").nil; function SmalltalkMethodContext(home, setup) { this.sendIdx = {}; this.homeContext = home; this.setup = setup || function() {}; } inherits(SmalltalkMethodContext, SmalltalkObject); this.__init__ = function () { st.addPackage("Kernel-Methods"); st.wrapClassName("MethodContext", "Kernel-Methods", SmalltalkMethodContext, st.Object, false); // Fallbacks SmalltalkMethodContext.prototype.locals = {}; SmalltalkMethodContext.prototype.receiver = null; SmalltalkMethodContext.prototype.selector = null; SmalltalkMethodContext.prototype.lookupClass = null; SmalltalkMethodContext.prototype.fill = function(receiver, selector, locals, lookupClass) { this.receiver = receiver; this.selector = selector; this.locals = locals || {}; this.lookupClass = lookupClass; if(this.homeContext) { this.homeContext.evaluatedSelector = selector; } }; SmalltalkMethodContext.prototype.fillBlock = function(locals, ctx, index) { this.locals = locals || {}; this.outerContext = ctx; this.index = index || 0; }; SmalltalkMethodContext.prototype.init = function() { var home = this.homeContext; if(home) { home.init(); } this.setup(this); }; SmalltalkMethodContext.prototype.method = function() { var method; var lookup = this.lookupClass || this.receiver.klass; while(!method && lookup) { method = lookup.methods[st.convertSelector(this.selector)]; lookup = lookup.superclass; } return method; }; }; /* This is the current call context object. While it is publicly available, Use smalltalk.getThisContext() instead which will answer a safe copy of the current context */ st.thisContext = undefined; st.withContext = function(worker, setup) { if(st.thisContext) { return inContext(worker, setup); } else { try { return inContext(worker, setup); } catch(error) { handleError(error); st.thisContext = null; } } }; function inContext(worker, setup) { var context = pushContext(setup); var result = worker(context); popContext(context); return result; } function wrappedError(error) { var errorWrapper = st.JavaScriptException._on_(error); try { errorWrapper._signal(); } catch (ex) {} errorWrapper._context_(st.getThisContext()); return errorWrapper; } /* Handles Smalltalk errors. Triggers the registered ErrorHandler (See the Smalltalk class ErrorHandler and its subclasses */ function handleError(error) { if (!error.smalltalkError) { error = wrappedError(error); } st.ErrorHandler._current()._handleError_(error); // Throw the exception anyway, as we want to stop // the execution to avoid infinite loops // Update: do not throw the exception. It's really annoying. // throw error; } /* Handle thisContext pseudo variable */ st.getThisContext = function() { if(st.thisContext) { st.thisContext.init(); return st.thisContext; } else { return nil; } }; function pushContext(setup) { return st.thisContext = new SmalltalkMethodContext(st.thisContext, setup); } function popContext(context) { st.thisContext = context.homeContext; } } function MessageSendBrik(brikz, st) { brikz.ensure("selectorConversion"); var nil = brikz.ensure("root").nil; /* Handles unhandled errors during message sends */ // simply send the message and handle #dnu: st.send = function(receiver, selector, args, klass) { var method; if(receiver === null) { receiver = nil; } method = klass ? klass.fn.prototype[selector] : receiver.klass && receiver[selector]; if(method) { return method.apply(receiver, args); } else { return messageNotUnderstood(receiver, selector, args); } }; /* Handles #dnu: *and* JavaScript method calls. if the receiver has no klass, we consider it a JS object (outside of the Amber system). Else assume that the receiver understands #doesNotUnderstand: */ function messageNotUnderstood(receiver, selector, args) { /* Handles JS method calls. */ if(receiver.klass === undefined || receiver.allowJavaScriptCalls) { return callJavaScriptMethod(receiver, selector, args); } /* Handles not understood messages. Also see the Amber counter-part Object>>doesNotUnderstand: */ return receiver._doesNotUnderstand_( st.Message._new() ._selector_(st.convertSelector(selector)) ._arguments_(args) ); } /* Call a method of a JS object, or answer a property if it exists. Else try wrapping a JSObjectProxy around the receiver. If the object property is a function, then call it, except if it starts with an uppercase character (we probably want to answer the function itself in this case and send it #new from Amber). Converts keyword-based selectors by using the first keyword only, but keeping all message arguments. Example: "self do: aBlock with: anObject" -> "self.do(aBlock, anObject)" */ function callJavaScriptMethod(receiver, selector, args) { var jsSelector = selector._asJavaScriptSelector(); var jsProperty = receiver[jsSelector]; if(typeof jsProperty === "function" && !/^[A-Z]/.test(jsSelector)) { return jsProperty.apply(receiver, args); } else if(jsProperty !== undefined) { if(args[0]) { receiver[jsSelector] = args[0]; return nil; } else { return jsProperty; } } return st.send(st.JSObjectProxy._on_(receiver), selector, args); } if(typeof jQuery !== "undefined") { jQuery.allowJavaScriptCalls = true; } this.messageNotUnderstood = messageNotUnderstood; } function SelectorConversionBrik(brikz, st) { /* Convert a Smalltalk selector into a JS selector */ st.selector = function(string) { var selector = '_' + string; selector = selector.replace(/:/g, '_'); selector = selector.replace(/[\&]/g, '_and'); selector = selector.replace(/[\|]/g, '_or'); selector = selector.replace(/[+]/g, '_plus'); selector = selector.replace(/-/g, '_minus'); selector = selector.replace(/[*]/g ,'_star'); selector = selector.replace(/[\/]/g ,'_slash'); selector = selector.replace(/[\\]/g ,'_backslash'); selector = selector.replace(/[\~]/g ,'_tild'); selector = selector.replace(/>/g ,'_gt'); selector = selector.replace(/>asSelector accordingly */ st.convertSelector = function(selector) { if(selector.match(/__/)) { return convertBinarySelector(selector); } else { return convertKeywordSelector(selector); } }; function convertKeywordSelector(selector) { return selector.replace(/^_/, '').replace(/_/g, ':'); } function convertBinarySelector(selector) { return selector .replace(/^_/, '') .replace(/_and/g, '&') .replace(/_or/g, '|') .replace(/_plus/g, '+') .replace(/_minus/g, '-') .replace(/_star/g, '*') .replace(/_slash/g, '/') .replace(/_backslash/g, '\\') .replace(/_tild/g, '~') .replace(/_gt/g, '>') .replace(/_lt/g, '<') .replace(/_eq/g, '=') .replace(/_comma/g, ',') .replace(/_at/g, '@'); } } /* Adds AMD and requirejs related methods to the smalltalk object */ function AMDBrik(brikz, st) { this.__init__ = function () { st.amdRequire = st.amdRequire || null; st.defaultTransportType = st.defaultTransportType || "amd"; st.defaultAmdNamespace = st.defaultAmdNamespace || "amber_core"; }; } /* Making smalltalk that can load */ brikz.root = RootBrik; brikz.dnu = DNUBrik; brikz.organize = OrganizeBrik; brikz.selectorConversion = SelectorConversionBrik; brikz.classInit = ClassInitBrik; brikz.manipulation = ManipulationBrik; brikz.classes = ClassesBrik; brikz.methods = MethodsBrik; brikz.stInit = SmalltalkInitBrik; brikz.augments = AugmentsBrik; brikz.amdBrik = AMDBrik; brikz.rebuild(); /* Making smalltalk that can run */ function runnable () { brikz.messageSend = MessageSendBrik; brikz.runtime = RuntimeBrik; brikz.primitives = PrimitivesBrik; brikz.rebuild(); }; return { smalltalk: api, nil: brikz.root.nil }; });