opal/corelib/runtime.js in opal-0.11.4 vs opal/corelib/runtime.js in opal-1.0.0.beta1

- old
+ new

@@ -49,31 +49,13 @@ var Module; // The actual Class class var Class; - // Constructor for instances of BasicObject - function BasicObject_alloc(){} - - // Constructor for instances of Object - function Object_alloc(){} - - // Constructor for instances of Class - function Class_alloc(){} - - // Constructor for instances of Module - function Module_alloc(){} - - // Constructor for instances of NilClass (nil) - function NilClass_alloc(){} - // The Opal object that is exposed globally var Opal = this.Opal = {}; - // All bridged classes - keep track to donate methods from Object - var BridgedClasses = {}; - // This is a useful reference to global object inside ruby files Opal.global = global_object; global_object.Opal = Opal; // Configure runtime behavior with regards to require and unsupported fearures @@ -82,12 +64,15 @@ unsupported_features_severity: 'warning', // error, warning, ignore enable_stack_trace: true // true, false } // Minify common function calls - var $hasOwn = Object.hasOwnProperty; - var $slice = Opal.slice = Array.prototype.slice; + var $hasOwn = Object.hasOwnProperty; + var $bind = Function.prototype.bind; + var $setPrototype = Object.setPrototypeOf; + var $slice = Array.prototype.slice; + var $splice = Array.prototype.splice; // Nil object id is always 4 var nil_id = 4; // Generates even sequential numbers greater than 4 @@ -101,11 +86,15 @@ }; // 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()); + if (obj.$$id != null) { + return obj.$$id; + }; + $defineProperty(obj, '$$id', Opal.uid()); + return obj.$$id; }; // Globals table Opal.gvars = {}; @@ -136,11 +125,35 @@ else { return obj.$inspect(); } } + function $defineProperty(object, name, initialValue) { + if (typeof(object) === "string") { + // Special case for: + // s = "string" + // def s.m; end + // String class is the only class that: + // + compiles to JS primitive + // + allows method definition directly on instances + // numbers, true, false and nil do not support it. + object[name] = initialValue; + } else { + Object.defineProperty(object, name, { + value: initialValue, + enumerable: false, + configurable: true, + writable: true + }); + } + } + Opal.defineProperty = $defineProperty; + + Opal.slice = $slice; + + // Truth // ----- Opal.truthy = function(val) { return (val !== nil && val != null && (!val.$$is_boolean || val == true)); @@ -218,11 +231,11 @@ if (cref == null) return; if (cref === '::') cref = _Object; - if (!cref.$$is_a_module) { + if (!cref.$$is_module && !cref.$$is_class) { 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; @@ -235,16 +248,17 @@ if (cref == null) return; if (cref === '::') cref = _Object; - if (!cref.$$is_a_module) { + if (!cref.$$is_module && !cref.$$is_class) { throw new Opal.TypeError(cref.toString() + " is not a class/module"); } if ((cache = cref.$$const_cache) == null) { - cache = cref.$$const_cache = Object.create(null); + $defineProperty(cref, '$$const_cache', Object.create(null)); + cache = cref.$$const_cache; } cached = cache[name]; if (cached == null || cached[0] !== current_version) { ((result = const_get_name(cref, name)) != null) || @@ -264,11 +278,12 @@ // 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 ((cache = nesting.$$const_cache) == null) { - cache = nesting.$$const_cache = Object.create(null); + $defineProperty(nesting, '$$const_cache', Object.create(null)); + cache = nesting.$$const_cache; } cached = cache[name]; if (cached == null || cached[0] !== current_version) { ((result = const_get_name(cref, name)) != null) || @@ -295,15 +310,23 @@ } cref.$$const = (cref.$$const || Object.create(null)); cref.$$const[name] = value; + // Add a short helper to navigate constants manually. + // @example + // Opal.$$.Regexp.$$.IGNORECASE + cref.$$ = cref.$$const; + Opal.const_cache_version++; // Expose top level constants onto the Opal object if (cref === _Object) Opal[name] = value; + // Name new class directly onto current scope (Opal.Foo.Baz = klass) + $defineProperty(cref, name, value); + return value; }; // Get all the constants reachable from a given cref, by default will include // inherited constants. @@ -362,315 +385,236 @@ // // We pass a constructor to this method of the form `function ClassName() {}` // simply so that classes show up with nicely formatted names inside debuggers // in the web browser (or node/sprockets). // - // The `base` is the current `self` value where the class is being created + // The `scope` is the current `self` value where the class is being created // from. We use this to get the scope for where the class should be created. - // If `base` is an object (not a class/module), we simple get its class and - // use that as the base instead. + // If `scope` is an object (not a class/module), we simple get its class and + // use that as the scope instead. // - // @param base [Object] where the class is being created + // @param scope [Object] where the class is being created // @param superclass [Class,null] superclass of the new class (may be null) // @param id [String] the name of the class to be created // @param constructor [JS.Function] function to use as constructor // // @return new [Class] or existing ruby class // - Opal.klass = function(base, superclass, name, constructor) { - var klass, bridged, alloc; + Opal.allocate_class = function(name, superclass) { + var klass, constructor; - if (base == null) { - base = _Object; + if (superclass != null && superclass.$$bridge) { + // Inheritance from bridged classes requires + // calling original JS constructors + constructor = function() { + var args = $slice.call(arguments), + self = new ($bind.apply(superclass.$$constructor, [null].concat(args)))(); + + // and replacing a __proto__ manually + $setPrototype(self, klass.$$prototype); + return self; + } + } else { + constructor = function(){}; } - // If base is an object, use its class - if (!base.$$is_class && !base.$$is_module) { - base = base.$$class; + if (name) { + $defineProperty(constructor, 'displayName', '::'+name); } - // If the superclass is a function then we're bridging a native JS class - if (typeof(superclass) === 'function') { - bridged = superclass; - superclass = _Object; + klass = constructor; + + $defineProperty(klass, '$$name', name); + $defineProperty(klass, '$$constructor', constructor); + $defineProperty(klass, '$$prototype', constructor.prototype); + $defineProperty(klass, '$$const', {}); + $defineProperty(klass, '$$is_class', true); + $defineProperty(klass, '$$is_a_module', true); + $defineProperty(klass, '$$super', superclass); + $defineProperty(klass, '$$cvars', {}); + $defineProperty(klass, '$$own_included_modules', []); + $defineProperty(klass, '$$own_prepended_modules', []); + $defineProperty(klass, '$$ancestors', []); + $defineProperty(klass, '$$ancestors_cache_version', null); + + $defineProperty(klass.$$prototype, '$$class', klass); + + // By default if there are no singleton class methods + // __proto__ is Class.prototype + // Later singleton methods generate a singleton_class + // and inject it into ancestors chain + if (Opal.Class) { + $setPrototype(klass, Opal.Class.prototype); } + if (superclass != null) { + $setPrototype(klass.$$prototype, superclass.$$prototype); + + if (superclass.$$meta) { + // If superclass has metaclass then we have explicitely inherit it. + Opal.build_class_singleton_class(klass); + } + }; + + return klass; + } + + + function find_existing_class(scope, name) { // Try to find the class in the current scope - klass = const_get_name(base, name); + var klass = const_get_name(scope, name); // If the class exists in the scope, then we must use that 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"); } - // Make sure existing class has same superclass - if (superclass && klass.$$super !== superclass) { - throw Opal.TypeError.$new("superclass mismatch for class " + name); - } - return klass; } + } - // Class doesnt exist, create a new one with given superclass... - - // Not specifying a superclass means we can assume it to be Object - if (superclass == null) { - superclass = _Object; + function ensureSuperclassMatch(klass, superclass) { + if (klass.$$super !== superclass) { + throw Opal.TypeError.$new("superclass mismatch for class " + klass.$$name); } + } - // If bridged the JS class will also be the alloc function - alloc = bridged || Opal.boot_class_alloc(name, constructor, superclass); + Opal.klass = function(scope, superclass, name) { + var bridged; - // Create the class object (instance of Class) - klass = Opal.setup_class_object(name, alloc, superclass.$$name, superclass.constructor); + if (scope == null) { + // Global scope + scope = _Object; + } else if (!scope.$$is_class && !scope.$$is_module) { + // Scope is an object, use its class + scope = scope.$$class; + } - // @property $$super the superclass, doesn't get changed by module inclusions - klass.$$super = superclass; + // If the superclass is not an Opal-generated class then we're bridging a native JS class + if (superclass != null && !superclass.hasOwnProperty('$$is_class')) { + bridged = superclass; + superclass = _Object; + } - // @property $$parent direct parent class - // starts with the superclass, after klass inclusion is - // the last included klass - klass.$$parent = superclass; + var klass = find_existing_class(scope, name); - Opal.const_set(base, name, klass); + if (klass) { + if (superclass) { + // Make sure existing class has same superclass + ensureSuperclassMatch(klass, superclass); + } + return klass; + } - // Name new class directly onto current scope (Opal.Foo.Baz = klass) - base[name] = klass; + // Class doesn't exist, create a new one with given superclass... - if (bridged) { - Opal.bridge(klass, alloc); + // Not specifying a superclass means we can assume it to be Object + if (superclass == null) { + superclass = _Object; } - else { - // Call .inherited() hook with new class on the superclass - if (superclass.$inherited) { - superclass.$inherited(klass); - } - } - return klass; - }; + // Create the class object (instance of Class) + klass = Opal.allocate_class(name, superclass); + Opal.const_set(scope, name, klass); - // Boot a base class (makes instances). - // - // @param name [String,null] the class name - // @param constructor [JS.Function] the class' instances constructor/alloc function - // @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; - constructor.prototype = new alloc_proxy(); + // Call .inherited() hook with new class on the superclass + if (superclass.$inherited) { + superclass.$inherited(klass); } - if (name) { - constructor.displayName = name+'_alloc'; + if (bridged) { + Opal.bridge(bridged, klass); } - 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); + return klass; } - - - // 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 - // - // @param superclass_name [String,null] - // The name of the super class, this is - // usefule to build the `.displayName` of the singleton class - // - // @param superclass_alloc [JS.Function] - // The constructor of the superclass from which the singleton_class is - // derived. - // - // @return [Class] - Opal.setup_class_object = function(name, alloc, superclass_name, superclass_alloc) { - // Grab the superclass prototype and use it to build an intermediary object - // in the prototype chain. - var superclass_alloc_proxy = function() {}; - superclass_alloc_proxy.prototype = superclass_alloc.prototype; - superclass_alloc_proxy.displayName = superclass_name; - - var singleton_class_alloc = function() {} - 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; - - // 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; - - // @property $$proto.$$class Make available to instances a reference to the - // class they belong to. - klass.$$proto.$$class = klass; - - // @property constructor keeps a ref to the constructor, but apparently the - // constructor is already set on: - // - // `var klass = new constructor` is called. - // - // Maybe there are some browsers not abiding (IE6?) - klass.constructor = singleton_class_alloc; - - // @property $$is_class Clearly mark this as a class - klass.$$is_class = true; - - // @property $$class Classes are instances of the class Class - klass.$$class = Class; - - return klass; - }; - - // Define new module (or return existing module). The given `base` is basically + // Define new module (or return existing module). The given `scope` is basically // the current `self` value the `module` statement was defined in. If this is - // a ruby module or class, then it is used, otherwise if the base is a ruby - // object then that objects real ruby class is used (e.g. if the base is the - // main object, then the top level `Object` class is used as the base). + // a ruby module or class, then it is used, otherwise if the scope is a ruby + // object then that objects real ruby class is used (e.g. if the scope is the + // main object, then the top level `Object` class is used as the scope). // - // If a module of the given name is already defined in the base, then that + // If a module of the given name is already defined in the scope, then that // instance is just returned. // - // If there is a class of the given name in the base, then an error is - // generated instead (cannot have a class and module of same name in same base). + // If there is a class of the given name in the scope, then an error is + // generated instead (cannot have a class and module of same name in same scope). // - // Otherwise, a new module is created in the base with the given name, and that + // Otherwise, a new module is created in the scope with the given name, and that // new instance is returned back (to be referenced at runtime). // - // @param base [Module, Class] class or module this definition is inside + // @param scope [Module, Class] class or module this definition is inside // @param id [String] the name of the new (or existing) module // // @return [Module] - Opal.module = function(base, name) { - var module; - - if (base == null) { - base = _Object; + Opal.allocate_module = function(name) { + var constructor = function(){}; + if (name) { + $defineProperty(constructor, 'displayName', name+'.$$constructor'); } - if (!base.$$is_class && !base.$$is_module) { - base = base.$$class; - } + var module = constructor; - module = const_get_name(base, name); - if (module == null && base === _Object) module = const_lookup_ancestors(_Object, name); + if (name) + $defineProperty(constructor, 'displayName', name+'.constructor'); + $defineProperty(module, '$$name', name); + $defineProperty(module, '$$prototype', constructor.prototype); + $defineProperty(module, '$$const', {}); + $defineProperty(module, '$$is_module', true); + $defineProperty(module, '$$is_a_module', true); + $defineProperty(module, '$$cvars', {}); + $defineProperty(module, '$$iclasses', []); + $defineProperty(module, '$$own_included_modules', []); + $defineProperty(module, '$$own_prepended_modules', []); + $defineProperty(module, '$$ancestors', [module]); + $defineProperty(module, '$$ancestors_cache_version', null); + + $setPrototype(module, Opal.Module.prototype); + + return module; + } + + function find_existing_module(scope, name) { + var module = const_get_name(scope, name); + if (module == null && scope === _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.const_set(base, name, module); - } return module; - }; + } - // The implementation for Module#initialize - // @param module [Module] - // @param block [Proc,nil] - // @return nil - Opal.module_initialize = function(module, block) { - if (block !== nil) { - var block_self = block.$$s; - block.$$s = null; - block.call(module); - block.$$s = block_self; + Opal.module = function(scope, name) { + var module; + + if (scope == null) { + // Global scope + scope = _Object; + } else if (!scope.$$is_class && !scope.$$is_module) { + // Scope is an object, use its class + scope = scope.$$class; } - return nil; - }; - // Internal function to create a new module instance. This simply sets up - // the prototype hierarchy and method tables. - // - Opal.module_allocate = function(superclass) { - var mtor = function() {}; - mtor.prototype = superclass.$$alloc.prototype; + module = find_existing_module(scope, name); - var module_constructor = function() {}; - module_constructor.prototype = new mtor(); + if (module) { + return module; + } - var module = new module_constructor(); - var module_prototype = {}; + // Module doesnt exist, create a new one... + module = Opal.allocate_module(name); + Opal.const_set(scope, name, module); - 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; - - // @property constructor - // keeps a ref to the constructor, but apparently the - // constructor is already set on: - // - // `var module = new constructor` is called. - // - // Maybe there are some browsers not abiding (IE6?) - module.constructor = module_constructor; - - // @property $$is_module Clearly mark this as a module - module.$$is_module = true; - module.$$class = Module; - - // @property $$super - // the superclass, doesn't get changed by module inclusions - module.$$super = superclass; - - // @property $$parent - // direct parent class or module - // starts with the superclass, after module inclusion is - // the last included module - module.$$parent = superclass; - return module; - }; + } // Return the singleton class for the passed object. // // If the given object alredy has a singleton class, then it will be stored on // the object as the `$$meta` property. If this exists, then it is simply @@ -684,15 +628,17 @@ Opal.get_singleton_class = function(object) { if (object.$$meta) { return object.$$meta; } - if (object.$$is_class || object.$$is_module) { + if (object.hasOwnProperty('$$is_class')) { return Opal.build_class_singleton_class(object); + } else if (object.hasOwnProperty('$$is_module')) { + return Opal.build_module_singletin_class(object); + } else { + return Opal.build_object_singleton_class(object); } - - return Opal.build_object_singleton_class(object); }; // Build the singleton class for an existing class. Class object are built // with their singleton class already in the prototype chain and inheriting // from their superclass object (up to `Class` itself). @@ -700,58 +646,150 @@ // NOTE: Actually in MRI a class' singleton class inherits from its // superclass' singleton class which in turn inherits from Class. // // @param klass [Class] // @return [Class] - Opal.build_class_singleton_class = function(object) { - var alloc, superclass, klass; + Opal.build_class_singleton_class = function(klass) { + var superclass, meta; - if (object.$$meta) { - return object.$$meta; + if (klass.$$meta) { + return klass.$$meta; } - // The constructor and prototype of the singleton_class instances is the - // current class constructor and prototype. - alloc = object.constructor; - // The singleton_class superclass is the singleton_class of its superclass; // 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); + superclass = klass === BasicObject ? Class : Opal.get_singleton_class(klass.$$super); - klass = Opal.setup_class_object(null, alloc, superclass.$$name, superclass.constructor); - klass.$$super = superclass; - klass.$$parent = superclass; + meta = Opal.allocate_class(null, superclass, function(){}); - klass.$$is_singleton = true; - klass.$$singleton_of = object; + $defineProperty(meta, '$$is_singleton', true); + $defineProperty(meta, '$$singleton_of', klass); + $defineProperty(klass, '$$meta', meta); + $setPrototype(klass, meta.$$prototype); + // Restoring ClassName.class + $defineProperty(klass, '$$class', Opal.Class); - return object.$$meta = klass; + return meta; }; + Opal.build_module_singletin_class = function(mod) { + if (mod.$$meta) { + return mod.$$meta; + } + + var meta = Opal.allocate_class(null, Opal.Module, function(){}); + + $defineProperty(meta, '$$is_singleton', true); + $defineProperty(meta, '$$singleton_of', mod); + $defineProperty(mod, '$$meta', meta); + $setPrototype(mod, meta.$$prototype); + // Restoring ModuleName.class + $defineProperty(mod, '$$class', Opal.Module); + + return meta; + } + // Build the singleton class for a Ruby (non class) Object. // // @param object [Object] // @return [Class] Opal.build_object_singleton_class = function(object) { var superclass = object.$$class, - name = "#<Class:#<" + superclass.$$name + ":" + superclass.$$id + ">>"; + klass = Opal.allocate_class(nil, superclass, function(){}); - var alloc = Opal.boot_class_alloc(name, function(){}, superclass) - var klass = Opal.setup_class_object(name, alloc, superclass.$$name, superclass.constructor); + $defineProperty(klass, '$$is_singleton', true); + $defineProperty(klass, '$$singleton_of', object); - klass.$$super = superclass; - klass.$$parent = superclass; - klass.$$class = superclass.$$class; - klass.$$proto = object; + delete klass.$$prototype.$$class; - klass.$$is_singleton = true; - klass.$$singleton_of = object; + $defineProperty(object, '$$meta', klass); - return object.$$meta = klass; + $setPrototype(object, object.$$meta.$$prototype); + + return klass; }; + Opal.is_method = function(prop) { + return (prop[0] === '$' && prop[1] !== '$'); + } + + Opal.instance_methods = function(mod) { + var exclude = [], results = [], ancestors = Opal.ancestors(mod); + + for (var i = 0, l = ancestors.length; i < l; i++) { + var ancestor = ancestors[i], + proto = ancestor.$$prototype; + + if (proto.hasOwnProperty('$$dummy')) { + proto = proto.$$define_methods_on; + } + + var props = Object.getOwnPropertyNames(proto); + + for (var j = 0, ll = props.length; j < ll; j++) { + var prop = props[j]; + + if (Opal.is_method(prop)) { + var method_name = prop.slice(1), + method = proto[prop]; + + if (method.$$stub && exclude.indexOf(method_name) === -1) { + exclude.push(method_name); + } + + if (!method.$$stub && results.indexOf(method_name) === -1 && exclude.indexOf(method_name) === -1) { + results.push(method_name); + } + } + } + } + + return results; + } + + Opal.own_instance_methods = function(mod) { + var results = [], + proto = mod.$$prototype; + + if (proto.hasOwnProperty('$$dummy')) { + proto = proto.$$define_methods_on; + } + + var props = Object.getOwnPropertyNames(proto); + + for (var i = 0, length = props.length; i < length; i++) { + var prop = props[i]; + + if (Opal.is_method(prop)) { + var method = proto[prop]; + + if (!method.$$stub) { + var method_name = prop.slice(1); + results.push(method_name); + } + } + } + + return results; + } + + Opal.methods = function(obj) { + return Opal.instance_methods(Opal.get_singleton_class(obj)); + } + + Opal.own_methods = function(obj) { + return Opal.own_instance_methods(Opal.get_singleton_class(obj)); + } + + Opal.receiver_methods = function(obj) { + var mod = Opal.get_singleton_class(obj); + var singleton_methods = Opal.own_instance_methods(mod); + var instance_methods = Opal.own_instance_methods(mod.$$super); + return singleton_methods.concat(instance_methods); + } + // Returns an object containing all pairs of names/values // for all class variables defined in provided +module+ // and its ancestors. // // @param module [Module] @@ -794,113 +832,54 @@ module.$$cvars[name] = value; return value; } - // Bridges a single method. - // - // @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; + function isRoot(proto) { + return proto.hasOwnProperty('$$iclass') && proto.hasOwnProperty('$$root'); + } - ancestors = target_constructor.$$bridge.$ancestors(); + function own_included_modules(module) { + var result = [], mod, proto = Object.getPrototypeOf(module.$$prototype); - // 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]; - - if ($hasOwn.call(ancestor.$$proto, name) && - ancestor.$$proto[name] && - !ancestor.$$proto[name].$$donated && - !ancestor.$$proto[name].$$stub && - ancestor !== from) { + while (proto) { + if (proto.hasOwnProperty('$$class')) { + // superclass break; } - - if (ancestor === from) { - target_constructor.prototype[name] = body - break; + mod = protoToModule(proto); + if (mod) { + result.push(mod); } + proto = Object.getPrototypeOf(proto); } - }; - // Bridges from *donator* to a *target*. - // - // @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 (bridged) { - BridgedClasses[donator_id] = bridged.slice(); - - for (i = bridged.length - 1; i >= 0; i--) { - Opal_bridge_methods_to_constructor(bridged[i], donator) - } - } - }; - - // Actually bridge methods to the bridged (shared) prototype. - function Opal_bridge_methods_to_constructor(target_constructor, donator) { - var i, - method, - methods = donator.$instance_methods(); - - for (i = methods.length - 1; i >= 0; i--) { - method = '$' + methods[i]; - Opal.bridge_method(target_constructor, donator, method, donator.$$proto[method]); - } + return result; } - // Associate the target as a bridged class for the current "donator" - function Opal_add_bridged_constructor(target_constructor, donator) { - var donator_id = donator.$__id__(); + function own_prepended_modules(module) { + var result = [], mod, proto = Object.getPrototypeOf(module.$$prototype); - if (!BridgedClasses[donator_id]) { - BridgedClasses[donator_id] = []; - } - BridgedClasses[donator_id].push(target_constructor); - } + if (module.$$prototype.hasOwnProperty('$$dummy')) { + while (proto) { + if (proto === module.$$prototype.$$define_methods_on) { + break; + } - // 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; + mod = protoToModule(proto); + if (mod) { + result.push(mod); + } - for (i = deps.length - 1; i >= 0; i--) { - dep = deps[i]; - dep_id = dep.$$id; - - if (seen[dep_id]) { - continue; + proto = Object.getPrototypeOf(proto); } - 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; + return result; } + // 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 @@ -916,195 +895,349 @@ // // @param module [Module] the module to include // @param includer [Module] the target class to include module into // @return [null] Opal.append_features = function(module, includer) { - var iclass, donator, prototype, methods, id, i; + var module_ancestors = Opal.ancestors(module); + var iclasses = []; - // check if this module is already included in the class - for (i = includer.$$inc.length - 1; i >= 0; i--) { - if (includer.$$inc[i] === module) { - return; + if (module_ancestors.indexOf(includer) !== -1) { + throw Opal.ArgumentError.$new('cyclic include detected'); + } + + for (var i = 0, length = module_ancestors.length; i < length; i++) { + var ancestor = module_ancestors[i], iclass = create_iclass(ancestor); + $defineProperty(iclass, '$$included', true); + iclasses.push(iclass); + } + var includer_ancestors = Opal.ancestors(includer), + chain = chain_iclasses(iclasses), + start_chain_after, + end_chain_on; + + if (includer_ancestors.indexOf(module) === -1) { + // first time include + + // includer -> chain.first -> ...chain... -> chain.last -> includer.parent + start_chain_after = includer.$$prototype; + end_chain_on = Object.getPrototypeOf(includer.$$prototype); + } else { + // The module has been already included, + // we don't need to put it into the ancestors chain again, + // but this module may have new included modules. + // If it's true we need to copy them. + // + // The simplest way is to replace ancestors chain from + // parent + // | + // `module` iclass (has a $$root flag) + // | + // ...previos chain of module.included_modules ... + // | + // "next ancestor" (has a $$root flag or is a real class) + // + // to + // parent + // | + // `module` iclass (has a $$root flag) + // | + // ...regenerated chain of module.included_modules + // | + // "next ancestor" (has a $$root flag or is a real class) + // + // because there are no intermediate classes between `parent` and `next ancestor`. + // It doesn't break any prototypes of other objects as we don't change class references. + + var proto = includer.$$prototype, parent = proto, module_iclass = Object.getPrototypeOf(parent); + + while (module_iclass != null) { + if (isRoot(module_iclass) && module_iclass.$$module === module) { + break; + } + + parent = module_iclass; + module_iclass = Object.getPrototypeOf(module_iclass); } + + var next_ancestor = Object.getPrototypeOf(module_iclass); + + // skip non-root iclasses (that were recursively included) + while (next_ancestor.hasOwnProperty('$$iclass') && !isRoot(next_ancestor)) { + next_ancestor = Object.getPrototypeOf(next_ancestor); + } + + start_chain_after = parent; + end_chain_on = next_ancestor; } - // 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') + $setPrototype(start_chain_after, chain.first); + $setPrototype(chain.last, end_chain_on); + + // recalculate own_included_modules cache + includer.$$own_included_modules = own_included_modules(includer); + + Opal.const_cache_version++; + } + + Opal.prepend_features = function(module, prepender) { + // Here we change the ancestors chain from + // + // prepender + // | + // parent + // + // to: + // + // dummy(prepender) + // | + // iclass(module) + // | + // iclass(prepender) + // | + // parent + var module_ancestors = Opal.ancestors(module); + var iclasses = []; + + if (module_ancestors.indexOf(prepender) !== -1) { + throw Opal.ArgumentError.$new('cyclic prepend detected'); } + for (var i = 0, length = module_ancestors.length; i < length; i++) { + var ancestor = module_ancestors[i], iclass = create_iclass(ancestor); + $defineProperty(iclass, '$$prepended', true); + iclasses.push(iclass); + } + + var chain = chain_iclasses(iclasses), + dummy_prepender = prepender.$$prototype, + previous_parent = Object.getPrototypeOf(dummy_prepender), + prepender_iclass, + start_chain_after, + end_chain_on; + + if (dummy_prepender.hasOwnProperty('$$dummy')) { + // The module already has some prepended modules + // which means that we don't need to make it "dummy" + prepender_iclass = dummy_prepender.$$define_methods_on; + } else { + // Making the module "dummy" + prepender_iclass = create_dummy_iclass(prepender); + flush_methods_in(prepender); + $defineProperty(dummy_prepender, '$$dummy', true); + $defineProperty(dummy_prepender, '$$define_methods_on', prepender_iclass); + + // Converting + // dummy(prepender) -> previous_parent + // to + // dummy(prepender) -> iclass(prepender) -> previous_parent + $setPrototype(dummy_prepender, prepender_iclass); + $setPrototype(prepender_iclass, previous_parent); + } + + var prepender_ancestors = Opal.ancestors(prepender); + + if (prepender_ancestors.indexOf(module) === -1) { + // first time prepend + + start_chain_after = dummy_prepender; + + // next $$root or prepender_iclass or non-$$iclass + end_chain_on = Object.getPrototypeOf(dummy_prepender); + while (end_chain_on != null) { + if ( + end_chain_on.hasOwnProperty('$$root') || + end_chain_on === prepender_iclass || + !end_chain_on.hasOwnProperty('$$iclass') + ) { + break; + } + + end_chain_on = Object.getPrototypeOf(end_chain_on); + } + } else { + throw Opal.RuntimeError.$new("Prepending a module multiple times is not supported"); + } + + $setPrototype(start_chain_after, chain.first); + $setPrototype(chain.last, end_chain_on); + + // recalculate own_prepended_modules cache + prepender.$$own_prepended_modules = own_prepended_modules(prepender); + 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: includer.$$parent, - $$module: module, - $$iclass: true - }; + function flush_methods_in(module) { + var proto = module.$$prototype, + props = Object.getOwnPropertyNames(proto); - includer.$$parent = iclass; + for (var i = 0; i < props.length; i++) { + var prop = props[i]; + if (Opal.is_method(prop)) { + delete proto[prop]; + } + } + } - methods = module.$instance_methods(); + function create_iclass(module) { + var iclass = create_dummy_iclass(module); - for (i = methods.length - 1; i >= 0; i--) { - Opal.update_includer(module, includer, '$' + methods[i]) + if (module.$$is_module) { + module.$$iclasses.push(iclass); } - }; - // 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 = {}; + return iclass; + } + // Dummy iclass doesn't receive updates when the module gets a new method. + function create_dummy_iclass(module) { + var iclass = {}, + proto = module.$$prototype; + + if (proto.hasOwnProperty('$$dummy')) { + proto = proto.$$define_methods_on; + } + + var props = Object.getOwnPropertyNames(proto), + length = props.length, i; + + for (i = 0; i < length; i++) { + var prop = props[i]; + $defineProperty(iclass, prop, proto[prop]); + } + + $defineProperty(iclass, '$$iclass', true); + $defineProperty(iclass, '$$module', module); + + return iclass; + } + + function chain_iclasses(iclasses) { + var length = iclasses.length, first = iclasses[0]; + + $defineProperty(first, '$$root', true); + + if (length === 1) { + return { first: first, last: first }; + } + + var previous = first; + + for (var i = 1; i < length; i++) { + var current = iclasses[i]; + $setPrototype(previous, current); + previous = current; + } + + + return { first: iclasses[0], last: iclasses[length - 1] }; + } + // For performance, some core Ruby classes are toll-free bridged to their // native JavaScript counterparts (e.g. a Ruby Array is a JavaScript Array). // // This method is used to setup a native constructor (e.g. Array), to have // its prototype act like a normal Ruby class. Firstly, a new Ruby class is // created using the native constructor so that its prototype is set as the - // target for th new class. Note: all bridged classes are set to inherit + // target for the new class. Note: all bridged classes are set to inherit // from Object. // // Example: // // Opal.bridge(self, Function); // // @param klass [Class] the Ruby class to bridge // @param constructor [JS.Function] native JavaScript constructor to use // @return [Class] returns the passed Ruby class // - Opal.bridge = function(klass, constructor) { - if (constructor.$$bridge) { + Opal.bridge = function(native_klass, klass) { + if (native_klass.hasOwnProperty('$$bridge')) { throw Opal.ArgumentError.$new("already bridged"); } - Opal.stub_subscribers.push(constructor.prototype); + var klass_to_inject, klass_reference; - // Populate constructor with previously stored stubs - for (var method_name in Opal.stubs) { - if (!(method_name in constructor.prototype)) { - constructor.prototype[method_name] = Opal.stub_for(method_name); - } - } + klass_to_inject = klass.$$super || Opal.Object; + klass_reference = klass; + var original_prototype = klass.$$prototype; - constructor.prototype.$$class = klass; - constructor.$$bridge = klass; + // constructor is a JS function with a prototype chain like: + // - constructor + // - super + // + // What we need to do is to inject our class (with its prototype chain) + // between constructor and super. For example, after injecting ::Object + // into JS String we get: + // + // - constructor (window.String) + // - Opal.Object + // - Opal.Kernel + // - Opal.BasicObject + // - super (window.Object) + // - null + // + $defineProperty(native_klass, '$$bridge', klass); + $setPrototype(native_klass.prototype, (klass.$$super || Opal.Object).$$prototype); + $defineProperty(klass, '$$prototype', native_klass.prototype); - 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_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]; - - if (method && method.$$stub && !(name in constructor.prototype)) { - constructor.prototype[name] = method; - } - } - - return klass; + $defineProperty(klass.$$prototype, '$$class', klass); + $defineProperty(klass, '$$constructor', native_klass); + $defineProperty(klass, '$$bridge', true); }; - // 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; - - 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 + function protoToModule(proto) { + if (proto.hasOwnProperty('$$dummy')) { + return; + } else if (proto.hasOwnProperty('$$iclass')) { + return proto.$$module; + } else if (proto.hasOwnProperty('$$class')) { + return proto.$$class; } - else if (dest.hasOwnProperty(jsid) && !current.$$stub) { - // target class includes another module that has defined this method - klass_includees = includer.$$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; - } - } + function own_ancestors(module) { + return module.$$own_prepended_modules.concat([module]).concat(module.$$own_included_modules); + } - // 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; - } + // The Array of ancestors for a given module/class + Opal.ancestors = function(module) { + if (!module) { return []; } - // if the includer is a module, recursively update all of its includres. - if (includer.$$included_in) { - Opal.update_includers(includer, jsid); + if (module.$$ancestors_cache_version === Opal.const_cache_version) { + return module.$$ancestors; } - }; - // Update `jsid` method cache of all classes / modules including `module`. - Opal.update_includers = function(module, jsid) { - var i, ii, includee, included_in; + var result = [], i, mods, length; - included_in = module.$$included_in; - - if (!included_in) { - return; + for (i = 0, mods = own_ancestors(module), length = mods.length; i < length; i++) { + result.push(mods[i]); } - for (i = 0, ii = included_in.length; i < ii; i++) { - includee = included_in[i]; - Opal.update_includer(module, includee, jsid); + if (module.$$super) { + for (i = 0, mods = Opal.ancestors(module.$$super), length = mods.length; i < length; i++) { + result.push(mods[i]); + } } - }; - // The Array of ancestors for a given module/class - Opal.ancestors = function(module_or_class) { - var parent = module_or_class, - result = [], - modules, i, ii, j, jj; + module.$$ancestors_cache_version = Opal.const_cache_version; + module.$$ancestors = result; - while (parent) { - result.push(parent); - for (i = parent.$$inc.length-1; i >= 0; i--) { - modules = Opal.ancestors(parent.$$inc[i]); + return result; + } - for(j = 0, jj = modules.length; j < jj; j++) { - result.push(modules[j]); - } - } + Opal.included_modules = function(module) { + var result = [], mod = null, proto = Object.getPrototypeOf(module.$$prototype); - // only the actual singleton class gets included in its ancestry - // after that, traverse the normal class hierarchy - if (parent.$$is_singleton && parent.$$singleton_of.$$is_module) { - parent = parent.$$singleton_of.$$super; + for (; proto && Object.getPrototypeOf(proto); proto = Object.getPrototypeOf(proto)) { + mod = protoToModule(proto); + if (mod && mod.$$is_module && proto.$$iclass && proto.$$included) { + result.push(mod); } - else { - parent = parent.$$is_class ? parent.$$super : null; - } } return result; - }; + } // Method Missing // -------------- @@ -1134,50 +1267,30 @@ // `Kernel#respond_to?` uses this property to detect a methods presence. // // @param stubs [Array] an array of method stubs to add // @return [undefined] Opal.add_stubs = function(stubs) { - var subscriber, subscribers = Opal.stub_subscribers, - i, ilength = stubs.length, - j, jlength = subscribers.length, - method_name, stub, - opal_stubs = Opal.stubs; + var proto = Opal.BasicObject.$$prototype; - for (i = 0; i < ilength; i++) { - method_name = stubs[i]; + for (var i = 0, length = stubs.length; i < length; i++) { + var stub = stubs[i], existing_method = proto[stub]; - 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); - - for (j = 0; j < jlength; j++) { - subscriber = subscribers[j]; - - if (!(method_name in subscriber)) { - subscriber[method_name] = stub; - } - } + if (existing_method == null || existing_method.$$stub) { + Opal.add_stub_for(proto, stub); } } }; - // Keep a list of prototypes that want method_missing stubs to be added. - // - // @default [Prototype List] BasicObject_alloc.prototype - // - Opal.stub_subscribers = [BasicObject_alloc.prototype]; - // Add a method_missing stub function to the given prototype for the // given name. // // @param prototype [Prototype] the target prototype // @param stub [String] stub name to add (e.g. "$foo") // @return [undefined] Opal.add_stub_for = function(prototype, stub) { var method_missing_stub = Opal.stub_for(stub); - prototype[stub] = method_missing_stub; + $defineProperty(prototype, stub, method_missing_stub); }; // Generate the method_missing stub for a given method name. // // @param method_name [String] The js-name of the method to stub (e.g. "$foo") @@ -1213,11 +1326,11 @@ // @param object [Object] owner of the method +meth+ // @param meth [String] method name that got wrong number of arguments // @raise [ArgumentError] Opal.ac = function(actual, expected, object, meth) { var inspect = ''; - if (object.$$is_class || object.$$is_module) { + if (object.$$is_a_module) { inspect += object.$$name + '.'; } else { inspect += object.$$class.$$name + '#'; } @@ -1238,27 +1351,39 @@ throw Opal.ArgumentError.$new(inspect + ': wrong number of arguments (' + actual + ' for ' + expected + ')'); }; // Super dispatcher Opal.find_super_dispatcher = function(obj, mid, current_func, defcheck, defs) { - var dispatcher, super_method; + var jsid = '$' + mid, ancestors, super_method; - if (defs) { - if (obj.$$is_class || obj.$$is_module) { - dispatcher = defs.$$super; + if (obj.hasOwnProperty('$$meta')) { + ancestors = Opal.ancestors(obj.$$meta); + } else { + ancestors = Opal.ancestors(obj.$$class); + } + + var current_index = ancestors.indexOf(current_func.$$owner); + + for (var i = current_index + 1; i < ancestors.length; i++) { + var ancestor = ancestors[i], + proto = ancestor.$$prototype; + + if (proto.hasOwnProperty('$$dummy')) { + proto = proto.$$define_methods_on; } - else { - dispatcher = obj.$$class.$$proto; + + if (proto.hasOwnProperty(jsid)) { + var method = proto[jsid]; + + if (!method.$$stub) { + super_method = method; + } + break; } } - else { - dispatcher = Opal.find_obj_super_dispatcher(obj, mid, current_func); - } - super_method = dispatcher['$' + mid]; - - if (!defcheck && super_method.$$stub && Opal.Kernel.$method_missing === obj.$method_missing) { + if (!defcheck && super_method == null && Opal.Kernel.$method_missing === obj.$method_missing) { // method_missing hasn't been explicitly defined throw Opal.NoMethodError.$new('super: no superclass method `'+mid+"' for "+obj, mid); } return super_method; @@ -1281,71 +1406,10 @@ } return Opal.find_super_dispatcher(obj, call_jsid, current_func, defcheck); }; - 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()"); - } - - return Opal.find_super_func(klass, '$' + mid, current_func); - }; - - Opal.find_owning_class = function(klass, current_func) { - var owner = current_func.$$owner; - - while (klass) { - // repeating for readability - - if (klass.$$iclass && klass.$$module === current_func.$$donated) { - // this klass was the last one the module donated to - // case is also hit with multiple module includes - break; - } - else if (klass.$$iclass && klass.$$module === owner) { - // module has donated to other classes but klass isn't one of those - break; - } - else if (owner.$$is_singleton && klass === owner.$$singleton_of.$$class) { - // cases like stdlib `Singleton::included` that use a singleton of a singleton - break; - } - else if (klass === owner) { - // no modules, pure class inheritance - break; - } - - klass = klass.$$parent; - } - - return klass; - }; - - Opal.find_super_func = function(owning_klass, jsid, current_func) { - var klass = owning_klass.$$parent; - - // now we can find the super - while (klass) { - var working = klass.$$proto[jsid]; - - if (working && working !== current_func) { - // ok - break; - } - - klass = klass.$$parent; - } - - return klass.$$proto; - }; - // Used to return as an expression. Sometimes, we can't simply return from // a javascript function as if we were a method, as the return is used as // an expression, or even inside a block which must "return" to the outer // method. This helper simply throws an error which is then caught by the // method. This approach is expensive, so it is only used when absolutely @@ -1434,11 +1498,11 @@ return null; }; Opal.is_a = function(object, klass) { - if (object.$$meta === klass || object.$$class === klass) { + if (klass != null && object.$$meta === klass || object.$$class === klass) { return true; } if (object.$$is_number && klass.$$is_number_class) { return true; @@ -1536,11 +1600,11 @@ // @return [Hash] // Opal.extract_kwargs = function(parameters) { var kwargs = parameters[parameters.length - 1]; if (kwargs != null && kwargs['$respond_to?']('to_hash', true)) { - Array.prototype.splice.call(parameters, parameters.length - 1, 1); + $splice.call(parameters, parameters.length - 1, 1); return kwargs.$to_hash(); } else { return Opal.hash2([], {}); } @@ -1599,17 +1663,24 @@ // @return [Object] returning value of the method call Opal.send = function(recv, method, args, block) { var body = (typeof(method) === 'string') ? recv['$'+method] : method; if (body != null) { - body.$$p = block; + if (typeof block === 'function') { + body.$$p = block; + } return body.apply(recv, args); } return recv.$method_missing.apply(recv, [method].concat(args)); } + Opal.lambda = function(block) { + block.$$is_lambda = true; + return block; + } + // 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. // @@ -1643,73 +1714,74 @@ // @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) { + // Special case for a method definition in the + // top-level namespace + if (obj === Opal.top) { + Opal.defn(Opal.Object, 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)) { + else if (!obj.$$eval && obj.$$is_a_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; - if (body.displayName == null) body.displayName = jsid.substr(1); + Opal.defn = function(module, jsid, body) { + body.displayName = jsid; + body.$$owner = module; - // is it a module? - if (obj.$$is_module) { - Opal.update_includers(obj, jsid); + var proto = module.$$prototype; + if (proto.hasOwnProperty('$$dummy')) { + proto = proto.$$define_methods_on; + } + $defineProperty(proto, jsid, body); - if (obj.$$module_function) { - Opal.defs(obj, jsid, body); + if (module.$$is_module) { + if (module.$$module_function) { + Opal.defs(module, 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); + for (var i = 0, iclasses = module.$$iclasses, length = iclasses.length; i < length; i++) { + var iclass = iclasses[i]; + $defineProperty(iclass, 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)); + var singleton_of = module.$$singleton_of; + if (module.$method_added && !module.$method_added.$$stub && !singleton_of) { + module.$method_added(jsid.substr(1)); } else if (singleton_of && singleton_of.$singleton_method_added && !singleton_of.$singleton_method_added.$$stub) { singleton_of.$singleton_method_added(jsid.substr(1)); } + } - return nil; - }; - // Define a singleton method on the given object (see Opal.def). Opal.defs = function(obj, jsid, body) { + if (obj.$$is_string || obj.$$is_number) { + throw Opal.TypeError.$new("can't define singleton"); + } Opal.defn(Opal.get_singleton_class(obj), jsid, body) }; // Called from #remove_method. Opal.rdef = function(obj, jsid) { - // TODO: remove from BridgedClasses as well - - if (!$hasOwn.call(obj.$$proto, jsid)) { + if (!$hasOwn.call(obj.$$prototype, jsid)) { throw Opal.NameError.$new("method '" + jsid.substr(1) + "' not defined in " + obj.$name()); } - delete obj.$$proto[jsid]; + delete obj.$$prototype[jsid]; if (obj.$$is_singleton) { - if (obj.$$proto.$singleton_method_removed && !obj.$$proto.$singleton_method_removed.$$stub) { - obj.$$proto.$singleton_method_removed(jsid.substr(1)); + if (obj.$$prototype.$singleton_method_removed && !obj.$$prototype.$singleton_method_removed.$$stub) { + obj.$$prototype.$singleton_method_removed(jsid.substr(1)); } } else { if (obj.$method_removed && !obj.$method_removed.$$stub) { obj.$method_removed(jsid.substr(1)); @@ -1717,57 +1789,66 @@ } }; // Called from #undef_method. Opal.udef = function(obj, jsid) { - if (!obj.$$proto[jsid] || obj.$$proto[jsid].$$stub) { + if (!obj.$$prototype[jsid] || obj.$$prototype[jsid].$$stub) { throw Opal.NameError.$new("method '" + jsid.substr(1) + "' not defined in " + obj.$name()); } - Opal.add_stub_for(obj.$$proto, jsid); + Opal.add_stub_for(obj.$$prototype, jsid); if (obj.$$is_singleton) { - if (obj.$$proto.$singleton_method_undefined && !obj.$$proto.$singleton_method_undefined.$$stub) { - obj.$$proto.$singleton_method_undefined(jsid.substr(1)); + if (obj.$$prototype.$singleton_method_undefined && !obj.$$prototype.$singleton_method_undefined.$$stub) { + obj.$$prototype.$singleton_method_undefined(jsid.substr(1)); } } else { if (obj.$method_undefined && !obj.$method_undefined.$$stub) { obj.$method_undefined(jsid.substr(1)); } } }; + function is_method_body(body) { + return (typeof(body) === "function" && !body.$$stub); + } + Opal.alias = function(obj, name, old) { var id = '$' + name, old_id = '$' + old, - body = obj.$$proto['$' + old], + body = obj.$$prototype['$' + old], alias; // 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) { + if (!is_method_body(body)) { var ancestor = obj.$$super; while (typeof(body) !== "function" && ancestor) { body = ancestor[old_id]; ancestor = ancestor.$$super; } - if (typeof(body) !== "function" || body.$$stub) { + if (!is_method_body(body) && obj.$$is_module) { + // try to look into Object + body = Opal.Object.$$prototype[old_id] + } + + if (!is_method_body(body)) { throw Opal.NameError.$new("undefined method `" + old + "' for class `" + obj.$name() + "'") } } // 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 + // We need a wrapper because otherwise properties // would be ovrewritten on the original body. alias = function() { var block = alias.$$p, args, i, ii; args = new Array(arguments.length); @@ -1794,11 +1875,11 @@ return obj; }; Opal.alias_native = function(obj, name, native_name) { var id = '$' + name, - body = obj.$$proto[native_name]; + body = obj.$$prototype[native_name]; if (typeof(body) !== "function" || body.$$stub) { throw Opal.NameError.$new("undefined native method `" + native_name + "' for class `" + obj.$name() + "'") } @@ -2021,11 +2102,11 @@ if (arguments_length === 1 && arguments[0].$$is_hash) { return arguments[0]; } - hash = new Opal.Hash.$$alloc(); + hash = new Opal.Hash(); Opal.hash_init(hash); if (arguments_length === 1 && arguments[0].$$is_array) { args = arguments[0]; length = args.length; @@ -2075,11 +2156,11 @@ // strings as keys. The map and keys array can be constructed at // compile time, so they are just added here by the constructor // function. // Opal.hash2 = function(keys, smap) { - var hash = new Opal.Hash.$$alloc(); + var hash = new Opal.Hash(); hash.$$smap = smap; hash.$$map = Object.create(null); hash.$$keys = keys; @@ -2088,11 +2169,11 @@ // Create a new range instance with first and last values, and whether the // range excludes the last value. // Opal.range = function(first, last, exc) { - var range = new Opal.Range.$$alloc(); + var range = new Opal.Range(); range.begin = first; range.end = last; range.excl = exc; return range; @@ -2133,19 +2214,57 @@ return str.replace(/([-[\]\/{}()*+?.^$\\| ])/g, '\\$1') .replace(/[\n]/g, '\\n') .replace(/[\r]/g, '\\r') .replace(/[\f]/g, '\\f') .replace(/[\t]/g, '\\t'); - } + }; + // Create a global Regexp from a RegExp object and cache the result + // on the object itself ($$g attribute). + // + Opal.global_regexp = function(pattern) { + if (pattern.global) { + return pattern; // RegExp already has the global flag + } + if (pattern.$$g == null) { + pattern.$$g = new RegExp(pattern.source, (pattern.multiline ? 'gm' : 'g') + (pattern.ignoreCase ? 'i' : '')); + } else { + pattern.$$g.lastIndex = null; // reset lastIndex property + } + return pattern.$$g; + }; + // Create a global multiline Regexp from a RegExp object and cache the result + // on the object itself ($$gm or $$g attribute). + // + Opal.global_multiline_regexp = function(pattern) { + var result; + if (pattern.multiline) { + if (pattern.global) { + return pattern; // RegExp already has the global and multiline flag + } + // we are using the $$g attribute because the Regexp is already multiline + if (pattern.$$g != null) { + result = pattern.$$g; + } else { + result = pattern.$$g = new RegExp(pattern.source, 'gm' + (pattern.ignoreCase ? 'i' : '')); + } + } else if (pattern.$$gm != null) { + result = pattern.$$gm; + } else { + result = pattern.$$gm = new RegExp(pattern.source, 'gm' + (pattern.ignoreCase ? 'i' : '')); + } + result.lastIndex = null; // reset lastIndex property + return result; + }; + // Require system // -------------- Opal.modules = {}; Opal.loaded_features = ['corelib/runtime']; - Opal.current_dir = '.' + Opal.current_dir = '.'; Opal.require_table = {'corelib/runtime': true}; Opal.normalize = function(path) { var parts, part, new_parts = [], SEPARATOR = '/'; @@ -2171,11 +2290,11 @@ for (i = 0, l = paths.length; i < l; i++) { path = Opal.normalize(paths[i]); if (Opal.require_table[path]) { - return; + continue; } Opal.loaded_features.push(path); Opal.require_table[path] = true; } @@ -2194,11 +2313,15 @@ else { var severity = Opal.config.missing_require_severity; var message = 'cannot load such file -- ' + path; if (severity === "error") { - Opal.LoadError ? Opal.LoadError.$new(message) : function(){throw message}(); + if (Opal.LoadError) { + throw Opal.LoadError.$new(message) + } else { + throw message + } } else if (severity === "warning") { console.warn('WARNING: LoadError: ' + message); } } @@ -2217,73 +2340,73 @@ }; // Initialization // -------------- + function $BasicObject() {}; + function $Object() {}; + function $Module() {}; + function $Class() {}; - // Constructors for *instances* of core objects - Opal.boot_class_alloc('BasicObject', BasicObject_alloc); - Opal.boot_class_alloc('Object', Object_alloc, BasicObject_alloc); - Opal.boot_class_alloc('Module', Module_alloc, Object_alloc); - Opal.boot_class_alloc('Class', Class_alloc, Module_alloc); + Opal.BasicObject = BasicObject = Opal.allocate_class('BasicObject', null, $BasicObject); + Opal.Object = _Object = Opal.allocate_class('Object', Opal.BasicObject, $Object); + Opal.Module = Module = Opal.allocate_class('Module', Opal.Object, $Module); + Opal.Class = Class = Opal.allocate_class('Class', Opal.Module, $Class); - // Constructors for *classes* of core objects - 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); + $setPrototype(Opal.BasicObject, Opal.Class.$$prototype); + $setPrototype(Opal.Object, Opal.Class.$$prototype); + $setPrototype(Opal.Module, Opal.Class.$$prototype); + $setPrototype(Opal.Class, Opal.Class.$$prototype); // 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 + // Fix booted classes to have correct .class value BasicObject.$$class = Class; _Object.$$class = Class; Module.$$class = Class; Class.$$class = Class; - // Fix superclasses of booted classes - BasicObject.$$super = null; - _Object.$$super = BasicObject; - Module.$$super = _Object; - Class.$$super = Module; - - BasicObject.$$parent = null; - _Object.$$parent = BasicObject; - Module.$$parent = _Object; - Class.$$parent = Module; - // Forward .toString() to #to_s - _Object.$$proto.toString = function() { + $defineProperty(_Object.$$prototype, 'toString', function() { var to_s = this.$to_s(); if (to_s.$$is_string && typeof(to_s) === 'object') { // a string created using new String('string') return to_s.valueOf(); } else { return to_s; } - }; + }); // Make Kernel#require immediately available as it's needed to require all the // other corelib files. - _Object.$$proto.$require = Opal.require; + $defineProperty(_Object.$$prototype, '$require', Opal.require); - // Instantiate the top object - Opal.top = new _Object.$$alloc(); + // Add a short helper to navigate constants manually. + // @example + // Opal.$$.Regexp.$$.IGNORECASE + Opal.$$ = _Object.$$; + // Instantiate the main object + Opal.top = new _Object(); + Opal.top.$to_s = Opal.top.$inspect = function() { return 'main' }; + + // Nil - Opal.klass(_Object, _Object, 'NilClass', NilClass_alloc); - nil = Opal.nil = new NilClass_alloc(); + function $NilClass() {}; + Opal.NilClass = Opal.allocate_class('NilClass', Opal.Object, $NilClass); + Opal.const_set(_Object, 'NilClass', Opal.NilClass); + nil = Opal.nil = new Opal.NilClass(); nil.$$id = nil_id; nil.call = nil.apply = function() { throw Opal.LocalJumpError.$new('no block given'); }; + + // Errors Opal.breaker = new Error('unexpected break (old)'); Opal.returner = new Error('unexpected return'); - TypeError.$$super = Error; }).call(this);