opal/corelib/runtime.js in opal-0.10.6 vs opal/corelib/runtime.js in opal-0.11.0.rc1

- old
+ new

@@ -11,10 +11,28 @@ // to improve or for alternative solutions // // The way the code is digested before going through Yardoc is a secret kept // in the docs repo (https://github.com/opal/docs/tree/master). + var global_object = this, console; + + // Detect the global object + if (typeof(global) !== 'undefined') { global_object = global; } + if (typeof(window) !== 'undefined') { global_object = window; } + + // Setup a dummy console object if missing + if (typeof(global_object.console) === 'object') { + console = global_object.console; + } else if (global_object.console == null) { + console = global_object.console = {}; + } else { + console = {}; + } + + if (!('log' in console)) { console.log = function () {}; } + if (!('warn' in console)) { console.warn = console.log; } + if (typeof(this.Opal) !== 'undefined') { console.warn('Opal already loaded. Loading twice can cause troubles, please fix your setup.'); return this.Opal; } @@ -50,35 +68,25 @@ // The Opal object that is exposed globally var Opal = this.Opal = {}; // All bridged classes - keep track to donate methods from Object - var bridges = {}; + var BridgedClasses = {}; - // TopScope is used for inheriting constants from the top scope - var TopScope = function(){}; - - // Opal just acts as the top scope - TopScope.prototype = Opal; - - // To inherit scopes - Opal.constructor = TopScope; - - // List top scope constants - Opal.constants = []; - // This is a useful reference to global object inside ruby files - Opal.global = this; + Opal.global = global_object; + global_object.Opal = Opal; // Configure runtime behavior with regards to require and unsupported fearures Opal.config = { - missing_require_severity: 'error', // error, warning, ignore - unsupported_features_severity: 'warning' // error, warning, ignore + missing_require_severity: 'error', // error, warning, ignore + unsupported_features_severity: 'warning', // error, warning, ignore + enable_stack_trace: true // true, false } // Minify common function calls - var $hasOwn = Opal.hasOwnProperty; + var $hasOwn = Object.hasOwnProperty; var $slice = Opal.slice = Array.prototype.slice; // Nil object id is always 4 var nil_id = 4; @@ -90,12 +98,15 @@ Opal.uid = function() { unique_id += 2; return unique_id; }; - // Table holds all class variables - Opal.cvars = {}; + // Retrieve or assign the id of an object + Opal.id = function(obj) { + if (obj.$$is_number) return (obj * 2)+1; + return obj.$$id || (obj.$$id = Opal.uid()); + }; // Globals table Opal.gvars = {}; // Exit function, this should be replaced by platform specific implementation @@ -109,139 +120,225 @@ // Pops an exception from the stack and updates `$!`. Opal.pop_exception = function() { Opal.gvars["!"] = Opal.exceptions.pop() || nil; } + // Inspect any kind of object, including non Ruby ones + Opal.inspect = function(obj) { + if (obj === undefined) { + return "undefined"; + } + else if (obj === null) { + return "null"; + } + else if (!obj.$$class) { + return obj.toString(); + } + else { + return obj.$inspect(); + } + } + // Constants // --------- - - // Get a constant on the given scope. Every class and module in Opal has a - // scope used to store, and inherit, constants. For example, the top level - // `Object` in ruby has a scope accessible as `Opal.Object.$$scope`. // - // To get the `Array` class using this scope, you could use: + // For future reference: + // - The Rails autoloading guide (http://guides.rubyonrails.org/v5.0/autoloading_and_reloading_constants.html) + // - @ConradIrwin's 2012 post on “Everything you ever wanted to know about constant lookup in Ruby” (http://cirw.in/blog/constant-lookup.html) // - // Opal.Object.$$scope.get("Array") - // - // If a constant with the given name cannot be found, then a dispatch to the - // class/module's `#const_method` is called, which by default will raise an - // error. - // - // @param name [String] the name of the constant to lookup - // @return [Object] - // - Opal.get = function(name) { - var constant = this[name]; + // Legend of MRI concepts/names: + // - constant reference (cref): the module/class that acts as a namespace + // - nesting: the namespaces wrapping the current scope, e.g. nesting inside + // `module A; module B::C; end; end` is `[B::C, A]` - if (constant == null) { - return this.base.$const_get(name); + // Get the cosntant in the scope of the current cref + function const_get_name(cref, name) { + if (cref) return cref.$$const[name]; + } + + // Walk up the nesting array looking for the constant + function const_lookup_nesting(nesting, name) { + var i, ii, result, constant; + + if (nesting.length === 0) return; + + // If the nesting is not empty the constant is looked up in its elements + // and in order. The ancestors of those elements are ignored. + for (i = 0, ii = nesting.length; i < ii; i++) { + constant = nesting[i].$$const[name]; + if (constant != null) return constant; } + } - return constant; - }; + // Walk up the ancestors chain looking for the constant + function const_lookup_ancestors(cref, name) { + var i, ii, result, ancestors; - // Create a new constants scope for the given class with the given - // base. Constants are looked up through their parents, so the base - // scope will be the outer scope of the new klass. - // - // @param base_scope [$$scope] the scope in which the new scope should be created - // @param klass [Class] - // @param id [String, null] the name of the newly created scope - // - Opal.create_scope = function(base_scope, klass, id) { - var const_alloc = function() {}; - var const_scope = const_alloc.prototype = new base_scope.constructor(); + if (cref == null) return; - klass.$$scope = const_scope; - klass.$$base_module = base_scope.base; + ancestors = Opal.ancestors(cref); - const_scope.base = klass; - const_scope.constructor = const_alloc; - const_scope.constants = []; + for (i = 0, ii = ancestors.length; i < ii; i++) { + if (ancestors[i].$$const && $hasOwn.call(ancestors[i].$$const, name)) { + return ancestors[i].$$const[name]; + } + } + } - if (id) { - Opal.cdecl(base_scope, id, klass); - const_alloc.displayName = id+"_scope_alloc"; + // Walk up Object's ancestors chain looking for the constant, + // but only if cref is missing or a module. + function const_lookup_Object(cref, name) { + if (cref == null || cref.$$is_module) { + return const_lookup_ancestors(_Object, name); } + } + + // Call const_missing if nothing else worked + function const_missing(cref, name, skip_missing) { + if (!skip_missing) { + return (cref || _Object).$const_missing(name); + } + } + + // Look for the constant just in the current cref or call `#const_missing` + Opal.const_get_local = function(cref, name, skip_missing) { + var result; + + if (cref == null) return; + + if (cref === '::') cref = _Object; + + if (!cref.$$is_a_module) { + throw new Opal.TypeError(cref.toString() + " is not a class/module"); + } + + result = const_get_name(cref, name); if (result != null) return result; + result = const_missing(cref, name, skip_missing); if (result != null) return result; + } + + // Look for the constant relative to a cref or call `#const_missing` (when the + // constant is prefixed by `::`). + Opal.const_get_qualified = function(cref, name, skip_missing) { + var result, cache, cached, current_version = Opal.const_cache_version; + + if (cref == null) return; + + if (cref === '::') cref = _Object; + + if (!cref.$$is_a_module) { + throw new Opal.TypeError(cref.toString() + " is not a class/module"); + } + + if (cref.$$const_cache == null) { + cache = cref.$$const_cache = Object.create(null); + } else { + cache = cref.$$const_cache; + } + cached = cache[name]; + + if (cached == null || cached[0] !== current_version) { + ((result = const_get_name(cref, name)) != null) || + ((result = const_lookup_ancestors(cref, name)) != null); + cache[name] = [current_version, result]; + } else { + result = cached[1]; + } + + return result != null ? result : const_missing(cref, name, skip_missing); }; - // Constant assignment, see also `Opal.cdecl` - // - // @param base_module [Module, Class] the constant namespace - // @param name [String] the name of the constant - // @param value [Object] the value of the constant - // - // @example Assigning a namespaced constant - // self::FOO = 'bar' - // - // @example Assigning with Module#const_set - // Foo.const_set :BAR, 123 - // - Opal.casgn = function(base_module, name, value) { - function update(klass, name) { - klass.$$name = name; + // Initialize the top level constant cache generation counter + Opal.const_cache_version = 1; - for (name in klass.$$scope) { - var value = klass.$$scope[name]; + // Look for the constant in the open using the current nesting and the nearest + // cref ancestors or call `#const_missing` (when the constant has no :: prefix). + Opal.const_get_relative = function(nesting, name, skip_missing) { + var cref = nesting[0], result, current_version = Opal.const_cache_version, cache, cached; - if (value.$$name === nil && (value.$$is_class || value.$$is_module)) { - update(value, name) - } - } + if (nesting.$$const_cache == null) { + cache = nesting.$$const_cache = Object.create(null); + } else { + cache = nesting.$$const_cache; } + cached = cache[name]; - var scope = base_module.$$scope; + if (cached == null || cached[0] !== current_version) { + ((result = const_get_name(cref, name)) != null) || + ((result = const_lookup_nesting(nesting, name)) != null) || + ((result = const_lookup_ancestors(cref, name)) != null) || + ((result = const_lookup_Object(cref, name)) != null); - if (value.$$is_class || value.$$is_module) { - // Only checking _Object prevents setting a const on an anonymous class - // that has a superclass that's not Object - if (value.$$is_class || value.$$base_module === _Object) { - value.$$base_module = base_module; - } + cache[name] = [current_version, result]; + } else { + result = cached[1]; + } - if (value.$$name === nil && value.$$base_module.$$name !== nil) { - update(value, name); - } + return result != null ? result : const_missing(cref, name, skip_missing); + }; + + // Register the constant on a cref and opportunistically set the name of + // unnamed classes/modules. + Opal.const_set = function(cref, name, value) { + if (cref == null || cref === '::') cref = _Object; + + if (value.$$is_a_module) { + if (value.$$name == null || value.$$name === nil) value.$$name = name; + if (value.$$base_module == null) value.$$base_module = cref; } - scope.constants.push(name); - scope[name] = value; + cref.$$const = (cref.$$const || Object.create(null)); + cref.$$const[name] = value; - // If we dynamically declare a constant in a module, - // we should populate all the classes that include this module - // with the same constant - if (base_module.$$is_module && base_module.$$dep) { - for (var i = 0; i < base_module.$$dep.length; i++) { - var dep = base_module.$$dep[i]; - Opal.casgn(dep, name, value); + Opal.const_cache_version++; + + // Expose top level constants onto the Opal object + if (cref === _Object) Opal[name] = value; + + return value; + }; + + // Get all the constants reachable from a given cref, by default will include + // inherited constants. + Opal.constants = function(cref, inherit) { + if (inherit == null) inherit = true; + + var module, modules = [cref], module_constants, i, ii, constants = {}, constant; + + if (inherit) modules = modules.concat(Opal.ancestors(cref)); + if (inherit && cref.$$is_module) modules = modules.concat([Opal.Object]).concat(Opal.ancestors(Opal.Object)); + + for (i = 0, ii = modules.length; i < ii; i++) { + module = modules[i]; + + // Don not show Objects constants unless we're querying Object itself + if (cref !== _Object && module == _Object) break; + + for (constant in module.$$const) { + constants[constant] = true; } } - return value; + return Object.keys(constants); }; - // Constant declaration - // - // @example - // FOO = :bar - // - // @param base_scope [$$scope] the current scope - // @param name [String] the name of the constant - // @param value [Object] the value of the constant - Opal.cdecl = function(base_scope, name, value) { - if ((value.$$is_class || value.$$is_module) && value.$$orig_scope == null) { - value.$$name = name; - value.$$orig_scope = base_scope; - // Here we should explicitly set a base module - // (a module where the constant was initially defined) - value.$$base_module = base_scope.base; - base_scope.constructor[name] = value; + // Remove a constant from a cref. + Opal.const_remove = function(cref, name) { + Opal.const_cache_version++; + + if (cref.$$const[name] != null) { + var old = cref.$$const[name]; + delete cref.$$const[name]; + return old; } - base_scope.constants.push(name); - return base_scope[name] = value; + if (cref.$$autoload != null && cref.$$autoload[name] != null) { + delete cref.$$autoload[name]; + return nil; + } + + throw Opal.NameError.$new("constant "+cref+"::"+cref.$name()+" not defined"); }; // Modules & Classes // ----------------- @@ -272,10 +369,14 @@ // @return new [Class] or existing ruby class // Opal.klass = function(base, superclass, name, constructor) { var klass, bridged, alloc; + if (base == null) { + base = _Object; + } + // If base is an object, use its class if (!base.$$is_class && !base.$$is_module) { base = base.$$class; } @@ -284,14 +385,14 @@ bridged = superclass; superclass = _Object; } // Try to find the class in the current scope - klass = base.$$scope[name]; + klass = const_get_name(base, name); // If the class exists in the scope, then we must use that - if (klass && klass.$$orig_scope === base.$$scope) { + if (klass) { // Make sure the existing constant is a class, or raise error if (!klass.$$is_class) { throw Opal.TypeError.$new(name + " is not a class"); } @@ -322,25 +423,19 @@ // @property $$parent direct parent class // starts with the superclass, after klass inclusion is // the last included klass klass.$$parent = superclass; - // Every class gets its own constant scope, inherited from current scope - Opal.create_scope(base.$$scope, klass, name); + Opal.const_set(base, name, klass); // Name new class directly onto current scope (Opal.Foo.Baz = klass) base[name] = klass; if (bridged) { Opal.bridge(klass, alloc); } else { - // Copy all parent constants to child, unless parent is Object - if (superclass !== _Object && superclass !== BasicObject) { - Opal.donate_constants(superclass, klass); - } - // Call .inherited() hook with new class on the superclass if (superclass.$inherited) { superclass.$inherited(klass); } } @@ -355,11 +450,11 @@ // @param superclass [Class,null] the superclass object // @return [JS.Function] the consturctor holding the prototype for the class' instances Opal.boot_class_alloc = function(name, constructor, superclass) { if (superclass) { var alloc_proxy = function() {}; - alloc_proxy.prototype = superclass.$$proto || superclass.prototype; + alloc_proxy.prototype = superclass.$$proto || superclass.prototype; constructor.prototype = new alloc_proxy(); } if (name) { constructor.displayName = name+'_alloc'; @@ -368,10 +463,34 @@ constructor.prototype.constructor = constructor; return constructor; }; + Opal.setup_module_or_class = function(module) { + // @property $$id Each class/module is assigned a unique `id` that helps + // comparation and implementation of `#object_id` + module.$$id = Opal.uid(); + + // @property $$is_a_module Will be true for Module and its subclasses + // instances (namely: Class). + module.$$is_a_module = true; + + // @property $$inc included modules + module.$$inc = []; + + // initialize the name with nil + module.$$name = nil; + + // Initialize the constants table + module.$$const = Object.create(null); + + // @property $$cvars class variables defined in the current module + module.$$cvars = Object.create(null); + } + + + // Adds common/required properties to class object (as in `Class.new`) // // @param name [String,null] The name of the class // // @param alloc [JS.Function] The constructor of the class' instances @@ -396,20 +515,18 @@ singleton_class_alloc.prototype = new superclass_alloc_proxy(); // The built class is the only instance of its singleton_class var klass = new singleton_class_alloc(); + Opal.setup_module_or_class(klass); + // @property $$alloc This is the constructor of instances of the current // class. Its prototype will be used for method lookup klass.$$alloc = alloc; klass.$$name = name || nil; - // @property $$id Each class is assigned a unique `id` that helps - // comparation and implementation of `#object_id` - klass.$$id = Opal.uid(); - // Set a displayName for the singleton_class singleton_class_alloc.displayName = "#<Class:"+(name || ("#<Class:"+klass.$$id+">"))+">"; // @property $$proto This is the prototype on which methods will be defined klass.$$proto = alloc.prototype; @@ -430,13 +547,10 @@ klass.$$is_class = true; // @property $$class Classes are instances of the class Class klass.$$class = Class; - // @property $$inc included modules - klass.$$inc = []; - return klass; }; // Define new module (or return existing module). The given `base` is basically // the current `self` value the `module` statement was defined in. If this is @@ -458,24 +572,29 @@ // // @return [Module] Opal.module = function(base, name) { var module; + if (base == null) { + base = _Object; + } + if (!base.$$is_class && !base.$$is_module) { base = base.$$class; } - if ($hasOwn.call(base.$$scope, name)) { - module = base.$$scope[name]; + module = const_get_name(base, name); + if (module == null && base === _Object) module = const_lookup_ancestors(_Object, name); + if (module) { if (!module.$$is_module && module !== _Object) { throw Opal.TypeError.$new(name + " is not a module"); } } else { module = Opal.module_allocate(Module); - Opal.create_scope(base.$$scope, module, name); + Opal.const_set(base, name, module); } return module; }; @@ -498,20 +617,21 @@ // Opal.module_allocate = function(superclass) { var mtor = function() {}; mtor.prototype = superclass.$$alloc.prototype; - function module_constructor() {} + var module_constructor = function() {}; module_constructor.prototype = new mtor(); var module = new module_constructor(); var module_prototype = {}; - // @property $$id Each class is assigned a unique `id` that helps - // comparation and implementation of `#object_id` - module.$$id = Opal.uid(); + Opal.setup_module_or_class(module); + // initialize dependency tracking + module.$$included_in = []; + // Set the display name of the singleton prototype holder module_constructor.displayName = "#<Class:#<Module:"+module.$$id+">>" // @property $$proto This is the prototype on which methods will be defined module.$$proto = module_prototype; @@ -537,22 +657,10 @@ // direct parent class or module // starts with the superclass, after module inclusion is // the last included module module.$$parent = superclass; - // @property $$inc included modules - module.$$inc = []; - - // mark the object as a module - module.$$is_module = true; - - // initialize dependency tracking - module.$$dep = []; - - // initialize the name with nil - module.$$name = nil; - return module; }; // Return the singleton class for the passed object. // @@ -601,16 +709,13 @@ // but BasicObject has no superclass (its `$$super` is null), thus we // fallback on `Class`. superclass = object === BasicObject ? Class : Opal.build_class_singleton_class(object.$$super); klass = Opal.setup_class_object(null, alloc, superclass.$$name, superclass.constructor); - klass.$$super = superclass; + klass.$$super = superclass; klass.$$parent = superclass; - // The singleton_class retains the same scope as the original class - Opal.create_scope(object.$$scope, klass); - klass.$$is_singleton = true; klass.$$singleton_of = object; return object.$$meta = klass; }; @@ -627,24 +732,74 @@ var klass = Opal.setup_class_object(name, alloc, superclass.$$name, superclass.constructor); klass.$$super = superclass; klass.$$parent = superclass; klass.$$class = superclass.$$class; - klass.$$scope = superclass.$$scope; klass.$$proto = object; klass.$$is_singleton = true; klass.$$singleton_of = object; return object.$$meta = klass; }; + // Returns an object containing all pairs of names/values + // for all class variables defined in provided +module+ + // and its ancestors. + // + // @param module [Module] + // @return [Object] + Opal.class_variables = function(module) { + var ancestors = Opal.ancestors(module), + i, length = ancestors.length, + result = {}; + + for (i = length - 1; i >= 0; i--) { + var ancestor = ancestors[i]; + + for (var cvar in ancestor.$$cvars) { + result[cvar] = ancestor.$$cvars[cvar]; + } + } + + return result; + } + + // Sets class variable with specified +name+ to +value+ + // in provided +module+ + // + // @param module [Module] + // @param name [String] + // @param value [Object] + Opal.class_variable_set = function(module, name, value) { + var ancestors = Opal.ancestors(module), + i, length = ancestors.length; + + for (i = length - 2; i >= 0; i--) { + var ancestor = ancestors[i]; + + if ($hasOwn.call(ancestor.$$cvars, name)) { + ancestor.$$cvars[name] = value; + return value; + } + } + + module.$$cvars[name] = value; + + return value; + } + // Bridges a single method. - Opal.bridge_method = function(target, from, name, body) { + // + // @param target [JS::Function] the constructor of the bridged class + // @param from [Module] the module/class we are importing the method from + // @param name [String] the method name in JS land (i.e. starting with $) + // @param body [JS::Function] the body of the method + Opal.bridge_method = function(target_constructor, from, name, body) { var ancestors, i, ancestor, length; - ancestors = target.$$bridge.$ancestors(); + ancestors = target_constructor.$$bridge.$ancestors(); // order important here, we have to check for method presence in // ancestors from the bridged class to the last ancestor for (i = 0, length = ancestors.length; i < length; i++) { ancestor = ancestors[i]; @@ -656,50 +811,88 @@ ancestor !== from) { break; } if (ancestor === from) { - target.prototype[name] = body + target_constructor.prototype[name] = body break; } } - }; // Bridges from *donator* to a *target*. - Opal._bridge = function(target, donator) { - var id, methods, method, i, bridged; + // + // @param target [Module] the potentially associated with bridged classes module + // @param donator [Module] the module/class source of the methods that should be bridged + Opal.bridge_methods = function(target, donator) { + var i, + bridged = BridgedClasses[target.$__id__()], + donator_id = donator.$__id__(); - if (typeof(target) === "function") { - id = donator.$__id__(); - methods = donator.$instance_methods(); + if (bridged) { + BridgedClasses[donator_id] = bridged.slice(); - for (i = methods.length - 1; i >= 0; i--) { - method = '$' + methods[i]; - - Opal.bridge_method(target, donator, method, donator.$$proto[method]); + for (i = bridged.length - 1; i >= 0; i--) { + Opal_bridge_methods_to_constructor(bridged[i], donator) } + } + }; - if (!bridges[id]) { - bridges[id] = []; - } + // Actually bridge methods to the bridged (shared) prototype. + function Opal_bridge_methods_to_constructor(target_constructor, donator) { + var i, + method, + methods = donator.$instance_methods(); - bridges[id].push(target); + for (i = methods.length - 1; i >= 0; i--) { + method = '$' + methods[i]; + Opal.bridge_method(target_constructor, donator, method, donator.$$proto[method]); } - else { - bridged = bridges[target.$__id__()]; + } - if (bridged) { - for (i = bridged.length - 1; i >= 0; i--) { - Opal._bridge(bridged[i], donator); - } + // Associate the target as a bridged class for the current "donator" + function Opal_add_bridged_constructor(target_constructor, donator) { + var donator_id = donator.$__id__(); - bridges[donator.$__id__()] = bridged.slice(); + if (!BridgedClasses[donator_id]) { + BridgedClasses[donator_id] = []; + } + BridgedClasses[donator_id].push(target_constructor); + } + + // Walks the dependency tree detecting the presence of the base among its + // own dependencies. + // + // @param [Integer] base_id The id of the base module (eg. the "includer") + // @param [Array<Module>] deps The array of dependencies (eg. the included module, included.$$deps) + // @param [String] prop The property that holds dependencies (eg. "$$deps") + // @param [JS::Object] seen A JS object holding the cache of already visited objects + // @return [Boolean] true if a cyclic dependency is present + Opal.has_cyclic_dep = function has_cyclic_dep(base_id, deps, prop, seen) { + var i, dep_id, dep; + + for (i = deps.length - 1; i >= 0; i--) { + dep = deps[i]; + dep_id = dep.$$id; + + if (seen[dep_id]) { + continue; } + seen[dep_id] = true; + + if (dep_id === base_id) { + return true; + } + + if (has_cyclic_dep(base_id, dep[prop], prop, seen)) { + return true; + } } - }; + return false; + } + // The actual inclusion of a module into a class. // // ## Class `$$parent` and `iclass` // // To handle `super` calls, every class has a `$$parent`. This parent is @@ -712,58 +905,49 @@ // `$$parent` which can then point to the superclass. The `iclass` acts as // a proxy to the actual module, so the `super` chain can then search it for // the required method. // // @param module [Module] the module to include - // @param klass [Class] the target class to include module into + // @param includer [Module] the target class to include module into // @return [null] - Opal.append_features = function(module, klass) { + Opal.append_features = function(module, includer) { var iclass, donator, prototype, methods, id, i; // check if this module is already included in the class - for (i = klass.$$inc.length - 1; i >= 0; i--) { - if (klass.$$inc[i] === module) { + for (i = includer.$$inc.length - 1; i >= 0; i--) { + if (includer.$$inc[i] === module) { return; } } - klass.$$inc.push(module); - module.$$dep.push(klass); - Opal._bridge(klass, module); + // Check that the base module is not also a dependency, classes can't be + // dependencies so we have a special case for them. + if (!includer.$$is_class && Opal.has_cyclic_dep(includer.$$id, [module], '$$inc', {})) { + throw Opal.ArgumentError.$new('cyclic include detected') + } + Opal.const_cache_version++; + includer.$$inc.push(module); + module.$$included_in.push(includer); + Opal.bridge_methods(includer, module); + // iclass iclass = { $$name: module.$$name, $$proto: module.$$proto, - $$parent: klass.$$parent, + $$parent: includer.$$parent, $$module: module, $$iclass: true }; - klass.$$parent = iclass; + includer.$$parent = iclass; - donator = module.$$proto; - prototype = klass.$$proto; - methods = module.$instance_methods(); + methods = module.$instance_methods(); for (i = methods.length - 1; i >= 0; i--) { - id = '$' + methods[i]; - - // if the target class already has a method of the same name defined - // and that method was NOT donated, then it must be a method defined - // by the class so we do not want to override it - if ( prototype.hasOwnProperty(id) && - !prototype[id].$$donated && - !prototype[id].$$stub) { - continue; - } - - prototype[id] = donator[id]; - prototype[id].$$donated = module; + Opal.update_includer(module, includer, '$' + methods[i]) } - - Opal.donate_constants(module, klass); }; // Table that holds all methods that have been defined on all objects // It is used for defining method stubs for new coming native classes Opal.stubs = {}; @@ -805,11 +989,12 @@ var ancestors = klass.$ancestors(); // order important here, we have to bridge from the last ancestor to the // bridged class for (var i = ancestors.length - 1; i >= 0; i--) { - Opal._bridge(constructor, ancestors[i]); + Opal_add_bridged_constructor(constructor, ancestors[i]); + Opal_bridge_methods_to_constructor(constructor, ancestors[i]); } for (var name in BasicObject_alloc.prototype) { var method = BasicObject_alloc.prototype[method]; @@ -819,88 +1004,83 @@ } return klass; }; - // When a source module is included into the target module, we must also copy - // its constants to the target. - // - Opal.donate_constants = function(source_mod, target_mod) { - var source_constants = source_mod.$$scope.constants, - target_scope = target_mod.$$scope, - target_constants = target_scope.constants; - - for (var i = 0, length = source_constants.length; i < length; i++) { - target_constants.push(source_constants[i]); - target_scope[source_constants[i]] = source_mod.$$scope[source_constants[i]]; - } - }; - - // Donate methods for a module. - Opal.donate = function(module, jsid) { - var included_in = module.$$dep, - body = module.$$proto[jsid], - i, length, includee, dest, current, + // Update `jsid` method cache of all classes / modules including `module`. + Opal.update_includer = function(module, includer, jsid) { + var dest, current, body, klass_includees, j, jj, current_owner_index, module_index; - if (!included_in) { - return; + body = module.$$proto[jsid]; + dest = includer.$$proto; + current = dest[jsid]; + + if (dest.hasOwnProperty(jsid) && !current.$$donated && !current.$$stub) { + // target class has already defined the same method name - do nothing } + else if (dest.hasOwnProperty(jsid) && !current.$$stub) { + // target class includes another module that has defined this method + klass_includees = includer.$$inc; - for (i = 0, length = included_in.length; i < length; i++) { - includee = included_in[i]; - dest = includee.$$proto; - current = dest[jsid]; - - if (dest.hasOwnProperty(jsid) && !current.$$donated && !current.$$stub) { - // target class has already defined the same method name - do nothing - } - else if (dest.hasOwnProperty(jsid) && !current.$$stub) { - // target class includes another module that has defined this method - klass_includees = includee.$$inc; - - for (j = 0, jj = klass_includees.length; j < jj; j++) { - if (klass_includees[j] === current.$$donated) { - current_owner_index = j; - } - if (klass_includees[j] === module) { - module_index = j; - } + for (j = 0, jj = klass_includees.length; j < jj; j++) { + if (klass_includees[j] === current.$$donated) { + current_owner_index = j; } - - // only redefine method on class if the module was included AFTER - // the module which defined the current method body. Also make sure - // a module can overwrite a method it defined before - if (current_owner_index <= module_index) { - dest[jsid] = body; - dest[jsid].$$donated = module; + if (klass_includees[j] === module) { + module_index = j; } } - else { - // neither a class, or module included by class, has defined method + + // only redefine method on class if the module was included AFTER + // the module which defined the current method body. Also make sure + // a module can overwrite a method it defined before + if (current_owner_index <= module_index) { dest[jsid] = body; dest[jsid].$$donated = module; } + } + else { + // neither a class, or module included by class, has defined method + dest[jsid] = body; + dest[jsid].$$donated = module; + } - if (includee.$$dep) { - Opal.donate(includee, jsid); - } + // if the includer is a module, recursively update all of its includres. + if (includer.$$included_in) { + Opal.update_includers(includer, jsid); } }; + // Update `jsid` method cache of all classes / modules including `module`. + Opal.update_includers = function(module, jsid) { + var i, ii, includee, included_in; + + included_in = module.$$included_in; + + if (!included_in) { + return; + } + + for (i = 0, ii = included_in.length; i < ii; i++) { + includee = included_in[i]; + Opal.update_includer(module, includee, jsid); + } + }; + // The Array of ancestors for a given module/class Opal.ancestors = function(module_or_class) { var parent = module_or_class, result = [], - modules; + modules, i, ii, j, jj; while (parent) { result.push(parent); - for (var i=0; i < parent.$$inc.length; i++) { + for (i = parent.$$inc.length-1; i >= 0; i--) { modules = Opal.ancestors(parent.$$inc[i]); - for(var j = 0; j < modules.length; j++) { + for(j = 0, jj = modules.length; j < jj; j++) { result.push(modules[j]); } } // only the actual singleton class gets included in its ancestry @@ -949,23 +1129,27 @@ // @return [undefined] Opal.add_stubs = function(stubs) { var subscriber, subscribers = Opal.stub_subscribers, i, ilength = stubs.length, j, jlength = subscribers.length, - method_name, stub; + method_name, stub, + opal_stubs = Opal.stubs; for (i = 0; i < ilength; i++) { method_name = stubs[i]; - // Save method name to populate other subscribers with this stub - Opal.stubs[method_name] = true; - stub = Opal.stub_for(method_name); - for (j = 0; j < jlength; j++) { - subscriber = subscribers[j]; + if(!opal_stubs.hasOwnProperty(method_name)) { + // Save method name to populate other subscribers with this stub + opal_stubs[method_name] = true; + stub = Opal.stub_for(method_name); - if (!(method_name in subscriber)) { - subscriber[method_name] = stub; + for (j = 0; j < jlength; j++) { + subscriber = subscribers[j]; + + if (!(method_name in subscriber)) { + subscriber[method_name] = stub; + } } } } }; @@ -1042,36 +1226,36 @@ // @raise [ArgumentError] Opal.block_ac = function(actual, expected, context) { var inspect = "`block in " + context + "'"; throw Opal.ArgumentError.$new(inspect + ': wrong number of arguments (' + actual + ' for ' + expected + ')'); - } + }; // Super dispatcher - Opal.find_super_dispatcher = function(obj, jsid, current_func, defcheck, defs) { - var dispatcher; + Opal.find_super_dispatcher = function(obj, mid, current_func, defcheck, defs) { + var dispatcher, super_method; if (defs) { if (obj.$$is_class || obj.$$is_module) { dispatcher = defs.$$super; } else { dispatcher = obj.$$class.$$proto; } } else { - dispatcher = Opal.find_obj_super_dispatcher(obj, jsid, current_func); + dispatcher = Opal.find_obj_super_dispatcher(obj, mid, current_func); } - dispatcher = dispatcher['$' + jsid]; + super_method = dispatcher['$' + mid]; - if (!defcheck && dispatcher.$$stub && Opal.Kernel.$method_missing === obj.$method_missing) { + if (!defcheck && super_method.$$stub && Opal.Kernel.$method_missing === obj.$method_missing) { // method_missing hasn't been explicitly defined - throw Opal.NoMethodError.$new('super: no superclass method `'+jsid+"' for "+obj, jsid); + throw Opal.NoMethodError.$new('super: no superclass method `'+mid+"' for "+obj, mid); } - return dispatcher; + return super_method; }; // Iter dispatcher for super in a block Opal.find_iter_super_dispatcher = function(obj, jsid, current_func, defcheck, implicit) { var call_jsid = jsid; @@ -1089,22 +1273,21 @@ } return Opal.find_super_dispatcher(obj, call_jsid, current_func, defcheck); }; - Opal.find_obj_super_dispatcher = function(obj, jsid, current_func) { + Opal.find_obj_super_dispatcher = function(obj, mid, current_func) { var klass = obj.$$meta || obj.$$class; // first we need to find the class/module current_func is located on klass = Opal.find_owning_class(klass, current_func); if (!klass) { throw new Error("could not find current class for super()"); } - jsid = '$' + jsid; - return Opal.find_super_func(klass, jsid, current_func); + return Opal.find_super_func(klass, '$' + mid, current_func); }; Opal.find_owning_class = function(klass, current_func) { var owner = current_func.$$owner; @@ -1231,32 +1414,31 @@ if (result) { return result; } } + else if (candidate === Opal.JS.Error) { + return candidate; + } else if (candidate['$==='](exception)) { return candidate; } } return null; }; Opal.is_a = function(object, klass) { - if (object.$$meta === klass) { + if (object.$$meta === klass || object.$$class === klass) { return true; } - var i, length, ancestors = Opal.ancestors(object.$$class); - - for (i = 0, length = ancestors.length; i < length; i++) { - if (ancestors[i] === klass) { - return true; - } + if (object.$$is_number && klass.$$is_number_class) { + return true; } - ancestors = Opal.ancestors(object.$$meta); + var i, length, ancestors = Opal.ancestors(object.$$is_class ? Opal.get_singleton_class(object) : (object.$$meta || object.$$class)); for (i = 0, length = ancestors.length; i < length; i++) { if (ancestors[i] === klass) { return true; } @@ -1381,52 +1563,45 @@ } return Opal.hash2(keys, map); }; - // Call a ruby method on a ruby object with some arguments: + // Calls passed method on a ruby object with arguments and block: // + // Can take a method or a method name. + // + // 1. When method name gets passed it invokes it by its name + // and calls 'method_missing' when object doesn't have this method. + // Used internally by Opal to invoke method that takes a block or a splat. + // 2. When method (i.e. method body) gets passed, it doesn't trigger 'method_missing' + // because it doesn't know the name of the actual method. + // Used internally by Opal to invoke 'super'. + // // @example // var my_array = [1, 2, 3, 4] - // Opal.send(my_array, 'length') # => 4 - // Opal.send(my_array, 'reverse!') # => [4, 3, 2, 1] + // Opal.send(my_array, 'length') # => 4 + // Opal.send(my_array, my_array.$length) # => 4 // - // A missing method will be forwarded to the object via - // method_missing. + // Opal.send(my_array, 'reverse!') # => [4, 3, 2, 1] + // Opal.send(my_array, my_array['$reverse!']') # => [4, 3, 2, 1] // - // The result of either call with be returned. - // - // @param recv [Object] the ruby object - // @param mid [String] ruby method to call - // @return [Object] forwards the return value of the method (or of method_missing) - Opal.send = function(recv, mid) { - var args_ary = new Array(Math.max(arguments.length - 2, 0)); - for(var i = 0, l = args_ary.length; i < l; i++) { args_ary[i] = arguments[i + 2]; } + // @param recv [Object] ruby object + // @param method [Function, String] method body or name of the method + // @param args [Array] arguments that will be passed to the method call + // @param block [Function] ruby block + // @return [Object] returning value of the method call + Opal.send = function(recv, method, args, block) { + var body = (typeof(method) === 'string') ? recv['$'+method] : method; - var func = recv['$' + mid]; - - if (func) { - return func.apply(recv, args_ary); + if (body != null) { + body.$$p = block; + return body.apply(recv, args); } - return recv.$method_missing.apply(recv, [mid].concat(args_ary)); - }; + return recv.$method_missing.apply(recv, [method].concat(args)); + } - Opal.block_send = function(recv, mid, block) { - var args_ary = new Array(Math.max(arguments.length - 3, 0)); - for(var i = 0, l = args_ary.length; i < l; i++) { args_ary[i] = arguments[i + 3]; } - - var func = recv['$' + mid]; - - if (func) { - func.$$p = block; - return func.apply(recv, args_ary); - } - - return recv.$method_missing.apply(recv, [mid].concat(args_ary)); - }; - // Used to define methods on an object. This is a helper method, used by the // compiled source to define methods on special case objects when the compiler // can not determine the destination object, or the object is a Module // instance. This can get called by `Module#define_method` as well. // @@ -1459,33 +1634,44 @@ // @param obj [Object, Class] the actual obj to define method for // @param jsid [String] the JavaScript friendly method name (e.g. '$foo') // @param body [JS.Function] the literal JavaScript function used as method // @return [null] // + Opal.def = function(obj, jsid, body) { + // if instance_eval is invoked on a module/class, it sets inst_eval_mod + if (!obj.$$eval && (obj.$$is_class || obj.$$is_module)) { + Opal.defn(obj, jsid, body); + } + else { + Opal.defs(obj, jsid, body); + } + }; + + // Define method on a module or class (see Opal.def). Opal.defn = function(obj, jsid, body) { obj.$$proto[jsid] = body; // for super dispatcher, etc. body.$$owner = obj; + // is it a module? if (obj.$$is_module) { - Opal.donate(obj, jsid); + Opal.update_includers(obj, jsid); if (obj.$$module_function) { Opal.defs(obj, jsid, body); } } - if (obj.$__id__ && !obj.$__id__.$$stub) { - var bridged = bridges[obj.$__id__()]; - - if (bridged) { - for (var i = bridged.length - 1; i >= 0; i--) { - Opal.bridge_method(bridged[i], obj, jsid, body); - } + // is it a bridged class? + var bridged = obj.$__id__ && !obj.$__id__.$$stub && BridgedClasses[obj.$__id__()]; + if (bridged) { + for (var i = bridged.length - 1; i >= 0; i--) { + Opal.bridge_method(bridged[i], obj, jsid, body); } } + // method_added/singleton_method_added hooks var singleton_of = obj.$$singleton_of; if (obj.$method_added && !obj.$method_added.$$stub && !singleton_of) { obj.$method_added(jsid.substr(1)); } else if (singleton_of && singleton_of.$singleton_method_added && !singleton_of.$singleton_method_added.$$stub) { @@ -1493,28 +1679,18 @@ } return nil; }; - // Define a singleton method on the given object. + // Define a singleton method on the given object (see Opal.def). Opal.defs = function(obj, jsid, body) { Opal.defn(Opal.get_singleton_class(obj), jsid, body) }; - Opal.def = function(obj, jsid, body) { - // if instance_eval is invoked on a module/class, it sets inst_eval_mod - if (!obj.$$eval && (obj.$$is_class || obj.$$is_module)) { - Opal.defn(obj, jsid, body); - } - else { - Opal.defs(obj, jsid, body); - } - }; - // Called from #remove_method. Opal.rdef = function(obj, jsid) { - // TODO: remove from bridges as well + // TODO: remove from BridgedClasses as well if (!$hasOwn.call(obj.$$proto, jsid)) { throw Opal.NameError.$new("method '" + jsid.substr(1) + "' not defined in " + obj.$name()); } @@ -1553,13 +1729,14 @@ }; Opal.alias = function(obj, name, old) { var id = '$' + name, old_id = '$' + old, - body = obj.$$proto['$' + old]; + body = obj.$$proto['$' + old], + alias; - // instance_eval is being run on a class/module, so that need to alias class methods + // When running inside #instance_eval the alias refers to class methods. if (obj.$$eval) { return Opal.alias(Opal.get_singleton_class(obj), name, old); } if (typeof(body) !== "function" || body.$$stub) { @@ -1573,12 +1750,40 @@ if (typeof(body) !== "function" || body.$$stub) { throw Opal.NameError.$new("undefined method `" + old + "' for class `" + obj.$name() + "'") } } - Opal.defn(obj, id, body); + // If the body is itself an alias use the original body + // to keep the max depth at 1. + if (body.$$alias_of) body = body.$$alias_of; + // We need a wrapper because otherwise method $$owner and other properties + // would be ovrewritten on the original body. + alias = function() { + var block = alias.$$p, args, i, ii; + + args = new Array(arguments.length); + for(i = 0, ii = arguments.length; i < ii; i++) { + args[i] = arguments[i]; + } + + if (block != null) { alias.$$p = null } + + return Opal.send(this, body, args, block); + }; + + // Try to make the browser pick the right name + alias.displayName = name; + alias.length = body.length; + alias.$$arity = body.$$arity; + alias.$$parameters = body.$$parameters; + alias.$$source_location = body.$$source_location; + alias.$$alias_of = body; + alias.$$alias_name = name; + + Opal.defn(obj, id, alias); + return obj; }; Opal.alias_native = function(obj, name, native_name) { var id = '$' + name, @@ -1596,24 +1801,24 @@ // Hashes // ------ Opal.hash_init = function(hash) { - hash.$$smap = {}; - hash.$$map = {}; + hash.$$smap = Object.create(null); + hash.$$map = Object.create(null); hash.$$keys = []; }; Opal.hash_clone = function(from_hash, to_hash) { to_hash.$$none = from_hash.$$none; to_hash.$$proc = from_hash.$$proc; - for (var i = 0, keys = from_hash.$$keys, length = keys.length, key, value; i < length; i++) { - key = from_hash.$$keys[i]; + for (var i = 0, keys = from_hash.$$keys, smap = from_hash.$$smap, len = keys.length, key, value; i < len; i++) { + key = keys[i]; if (key.$$is_string) { - value = from_hash.$$smap[key]; + value = smap[key]; } else { value = key.value; key = key.key; } @@ -1621,20 +1826,21 @@ } }; Opal.hash_put = function(hash, key, value) { if (key.$$is_string) { - if (!hash.$$smap.hasOwnProperty(key)) { + if (!$hasOwn.call(hash.$$smap, key)) { hash.$$keys.push(key); } hash.$$smap[key] = value; return; } - var key_hash = key.$hash(), bucket, last_bucket; + var key_hash, bucket, last_bucket; + key_hash = hash.$$by_identity ? Opal.id(key) : key.$hash(); - if (!hash.$$map.hasOwnProperty(key_hash)) { + if (!$hasOwn.call(hash.$$map, key_hash)) { bucket = {key: key, key_hash: key_hash, value: value}; hash.$$keys.push(bucket); hash.$$map[key_hash] = bucket; return; } @@ -1658,19 +1864,20 @@ } }; Opal.hash_get = function(hash, key) { if (key.$$is_string) { - if (hash.$$smap.hasOwnProperty(key)) { + if ($hasOwn.call(hash.$$smap, key)) { return hash.$$smap[key]; } return; } - var key_hash = key.$hash(), bucket; + var key_hash, bucket; + key_hash = hash.$$by_identity ? Opal.id(key) : key.$hash(); - if (hash.$$map.hasOwnProperty(key_hash)) { + if ($hasOwn.call(hash.$$map, key_hash)) { bucket = hash.$$map[key_hash]; while (bucket) { if (key === bucket.key || key['$eql?'](bucket.key)) { return bucket.value; @@ -1682,11 +1889,11 @@ Opal.hash_delete = function(hash, key) { var i, keys = hash.$$keys, length = keys.length, value; if (key.$$is_string) { - if (!hash.$$smap.hasOwnProperty(key)) { + if (!$hasOwn.call(hash.$$smap, key)) { return; } for (i = 0; i < length; i++) { if (keys[i] === key) { @@ -1700,11 +1907,11 @@ return value; } var key_hash = key.$hash(); - if (!hash.$$map.hasOwnProperty(key_hash)) { + if (!$hasOwn.call(hash.$$map, key_hash)) { return; } var bucket = hash.$$map[key_hash], last_bucket; @@ -1775,11 +1982,11 @@ bucket = bucket.next; } hash.$$keys[i].key_hash = key_hash; - if (!hash.$$map.hasOwnProperty(key_hash)) { + if (!$hasOwn.call(hash.$$map, key_hash)) { hash.$$map[key_hash] = hash.$$keys[i]; continue; } bucket = hash.$$map[key_hash]; @@ -1829,11 +2036,11 @@ } if (arguments_length === 1) { args = arguments[0]; for (key in args) { - if (args.hasOwnProperty(key)) { + if ($hasOwn.call(args, key)) { value = args[key]; Opal.hash_put(hash, key, value); } } @@ -1853,20 +2060,20 @@ } return hash; }; - // hash2 is a faster creator for hashes that just use symbols and + // A faster Hash creator for hashes that just use symbols and // strings as keys. The map and keys array can be constructed at // compile time, so they are just added here by the constructor - // function + // function. // Opal.hash2 = function(keys, smap) { var hash = new Opal.Hash.$$alloc(); hash.$$smap = smap; - hash.$$map = {}; + hash.$$map = Object.create(null); hash.$$keys = keys; return hash; }; @@ -1880,10 +2087,13 @@ range.exclude = exc; return range; }; + // Get the ivar name for a given name. + // Mostly adds a trailing $ to reserved names. + // Opal.ivar = function(name) { if ( // properties name === "constructor" || name === "displayName" || @@ -1902,10 +2112,25 @@ return name; }; + // Regexps + // ------- + + // Escape Regexp special chars letting the resulting string be used to build + // a new Regexp. + // + Opal.escape_regexp = function(str) { + return str.replace(/([-[\]\/{}()*+?.^$\\| ])/g, '\\$1') + .replace(/[\n]/g, '\\n') + .replace(/[\r]/g, '\\r') + .replace(/[\f]/g, '\\f') + .replace(/[\t]/g, '\\t'); + } + + // Require system // -------------- Opal.modules = {}; Opal.loaded_features = ['corelib/runtime']; @@ -1995,15 +2220,20 @@ Opal.BasicObject = BasicObject = Opal.setup_class_object('BasicObject', BasicObject_alloc, 'Class', Class_alloc); Opal.Object = _Object = Opal.setup_class_object('Object', Object_alloc, 'BasicObject', BasicObject.constructor); Opal.Module = Module = Opal.setup_class_object('Module', Module_alloc, 'Object', _Object.constructor); Opal.Class = Class = Opal.setup_class_object('Class', Class_alloc, 'Module', Module.constructor); - Opal.constants.push("BasicObject"); - Opal.constants.push("Object"); - Opal.constants.push("Module"); - Opal.constants.push("Class"); + // BasicObject can reach itself, avoid const_set to skip the $$base_module logic + BasicObject.$$const["BasicObject"] = BasicObject; + // Assign basic constants + Opal.const_set(_Object, "BasicObject", BasicObject); + Opal.const_set(_Object, "Object", _Object); + Opal.const_set(_Object, "Module", Module); + Opal.const_set(_Object, "Class", Class); + + // Fix booted classes to use their metaclass BasicObject.$$class = Class; _Object.$$class = Class; Module.$$class = Class; Class.$$class = Class; @@ -2017,19 +2247,10 @@ BasicObject.$$parent = null; _Object.$$parent = BasicObject; Module.$$parent = _Object; Class.$$parent = Module; - Opal.base = _Object; - BasicObject.$$scope = _Object.$$scope = Opal; - BasicObject.$$orig_scope = _Object.$$orig_scope = Opal; - - Module.$$scope = _Object.$$scope; - Module.$$orig_scope = _Object.$$orig_scope; - Class.$$scope = _Object.$$scope; - Class.$$orig_scope = _Object.$$orig_scope; - // Forward .toString() to #to_s _Object.$$proto.toString = function() { return this.$to_s(); }; @@ -2048,15 +2269,5 @@ Opal.breaker = new Error('unexpected break (old)'); Opal.returner = new Error('unexpected return'); TypeError.$$super = Error; }).call(this); - -if (typeof(global) !== 'undefined') { - global.Opal = this.Opal; - Opal.global = global; -} - -if (typeof(window) !== 'undefined') { - window.Opal = this.Opal; - Opal.global = window; -}