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);