(function(global_object) { "use strict"; // @note // A few conventions for the documentation of this file: // 1. Always use "//" (in contrast with "/**/") // 2. The syntax used is Yardoc (yardoc.org), which is intended for Ruby (se below) // 3. `@param` and `@return` types should be preceded by `JS.` when referring to // JavaScript constructors (e.g. `JS.Function`) otherwise Ruby is assumed. // 4. `nil` and `null` being unambiguous refer to the respective // objects/values in Ruby and JavaScript // 5. This is still WIP :) so please give feedback and suggestions on how // to improve or for alternative solutions // // The way the code is digested before going through Yardoc is a secret kept // in the docs repo (https://github.com/opal/docs/tree/master). var console; // Detect the global object if (typeof(globalThis) !== 'undefined') { global_object = globalThis; } else if (typeof(global) !== 'undefined') { global_object = global; } else if (typeof(window) !== 'undefined') { global_object = window; } // Setup a dummy console object if missing if (global_object.console == null) { global_object.console = {}; } if (typeof(global_object.console) === 'object') { console = global_object.console; } else { console = {}; } if (!('log' in console)) { console.log = function () {}; } if (!('warn' in console)) { console.warn = console.log; } if (typeof(global_object.Opal) !== 'undefined') { console.warn('Opal already loaded. Loading twice can cause troubles, please fix your setup.'); return global_object.Opal; } var nil; // The actual class for BasicObject var BasicObject; // The actual Object class. // The leading underscore is to avoid confusion with window.Object() var _Object; // The actual Module class var Module; // The actual Class class var Class; // The Opal.Opal class (helpers etc.) var _Opal; // The Kernel module var Kernel; // The Opal object that is exposed globally var Opal = global_object.Opal = {}; // This is a useful reference to global object inside ruby files Opal.global = global_object; // Configure runtime behavior with regards to require and unsupported features Opal.config = { missing_require_severity: 'error', // error, warning, ignore unsupported_features_severity: 'warning', // error, warning, ignore experimental_features_severity: 'warning',// warning, ignore enable_stack_trace: true // true, false }; // Minify common function calls var $call = Function.prototype.call; var $bind = Function.prototype.bind; var $has_own = Object.hasOwn || $call.bind(Object.prototype.hasOwnProperty); var $set_proto = Object.setPrototypeOf; var $slice = $call.bind(Array.prototype.slice); var $splice = $call.bind(Array.prototype.splice); // Nil object id is always 4 var nil_id = 4; // Generates even sequential numbers greater than 4 // (nil_id) to serve as unique ids for ruby objects var unique_id = nil_id; // Return next unique id function $uid() { unique_id += 2; return unique_id; }; Opal.uid = $uid; // Retrieve or assign the id of an object Opal.id = function(obj) { if (obj.$$is_number) return (obj * 2)+1; if (obj.$$id == null) { $prop(obj, '$$id', $uid()); } return obj.$$id; }; // Globals table var $gvars = Opal.gvars = {}; // Exit function, this should be replaced by platform specific implementation // (See nodejs and chrome for examples) Opal.exit = function(status) { if ($gvars.DEBUG) console.log('Exited with status '+status); }; // keeps track of exceptions for $! Opal.exceptions = []; // @private // Pops an exception from the stack and updates `$!`. Opal.pop_exception = function(rescued_exception) { var exception = Opal.exceptions.pop(); if (exception === rescued_exception) { // Current $! is raised in the rescue block, so we don't update it } else if (exception) { $gvars["!"] = exception; } else { $gvars["!"] = nil; } }; // A helper function for raising things, that gracefully degrades if necessary // functionality is not yet loaded. function $raise(klass, message) { // Raise Exception, so we can know that something wrong is going on. if (!klass) klass = Opal.Exception || Error; if (Kernel && Kernel.$raise) { if (arguments.length > 2) { Kernel.$raise(klass.$new.apply(klass, $slice(arguments, 1))); } else { Kernel.$raise(klass, message); } } else if (!klass.$new) { throw new klass(message); } else { throw klass.$new(message); } } // Reuse the same object for performance/memory sake var prop_options = { value: undefined, enumerable: false, configurable: true, writable: true }; function $prop(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 null do not support it. object[name] = initialValue; } else { prop_options.value = initialValue; Object.defineProperty(object, name, prop_options); } } Opal.prop = $prop; // @deprecated Opal.defineProperty = Opal.prop; Opal.slice = $slice; // Helpers // ----- var $truthy = Opal.truthy = function(val) { return false !== val && nil !== val && undefined !== val && null !== val && (!(val instanceof Boolean) || true === val.valueOf()); }; Opal.falsy = function(val) { return !$truthy(val); }; Opal.type_error = function(object, type, method, coerced) { object = object.$$class; if (coerced && method) { coerced = coerced.$$class; $raise(Opal.TypeError, "can't convert " + object + " into " + type + " (" + object + "#" + method + " gives " + coerced + ")" ) } else { $raise(Opal.TypeError, "no implicit conversion of " + object + " into " + type ) } }; Opal.coerce_to = function(object, type, method, args) { var body; if (method === 'to_int' && type === Opal.Integer && object.$$is_number) return object < 0 ? Math.ceil(object) : Math.floor(object); if (method === 'to_str' && type === Opal.String && object.$$is_string) return object; if (Opal.is_a(object, type)) return object; // Fast path for the most common situation if (object['$respond_to?'].$$pristine && object.$method_missing.$$pristine) { body = object[$jsid(method)]; if (body == null || body.$$stub) Opal.type_error(object, type); return body.apply(object, args); } if (!object['$respond_to?'](method)) { Opal.type_error(object, type); } if (args == null) args = []; return Opal.send(object, method, args); } Opal.respond_to = function(obj, jsid, include_all) { if (obj == null || !obj.$$class) return false; include_all = !!include_all; var body = obj[jsid]; if (obj['$respond_to?'].$$pristine) { if (typeof(body) === "function" && !body.$$stub) { return true; } if (!obj['$respond_to_missing?'].$$pristine) { return Opal.send(obj, obj['$respond_to_missing?'], [jsid.substr(1), include_all]); } } else { return Opal.send(obj, obj['$respond_to?'], [jsid.substr(1), include_all]); } } // TracePoint support // ------------------ // // Support for `TracePoint.trace(:class) do ... end` Opal.trace_class = false; Opal.tracers_for_class = []; function invoke_tracers_for_class(klass_or_module) { var i, ii, tracer; for(i = 0, ii = Opal.tracers_for_class.length; i < ii; i++) { tracer = Opal.tracers_for_class[i]; tracer.trace_object = klass_or_module; tracer.block.$call(tracer); } } function handle_autoload(cref, name) { if (!cref.$$autoload[name].loaded) { cref.$$autoload[name].loaded = true; try { Opal.Kernel.$require(cref.$$autoload[name].path); } catch (e) { cref.$$autoload[name].exception = e; throw e; } cref.$$autoload[name].required = true; if (cref.$$const[name] != null) { cref.$$autoload[name].success = true; return cref.$$const[name]; } } else if (cref.$$autoload[name].loaded && !cref.$$autoload[name].required) { if (cref.$$autoload[name].exception) { throw cref.$$autoload[name].exception; } } } // Constants // --------- // // For future reference: // - The Rails autoloading guide (http://guides.rubyonrails.org/v5.0/autoloading_and_reloading_constants.html) // - @ConradIrwin's 2012 post on “Everything you ever wanted to know about constant lookup in Ruby” (http://cirw.in/blog/constant-lookup.html) // // Legend of MRI concepts/names: // - constant reference (cref): the module/class that acts as a namespace // - nesting: the namespaces wrapping the current scope, e.g. nesting inside // `module A; module B::C; end; end` is `[B::C, A]` // Get the constant in the scope of the current cref function const_get_name(cref, name) { if (cref) { if (cref.$$const[name] != null) { return cref.$$const[name]; } if (cref.$$autoload && cref.$$autoload[name]) { return handle_autoload(cref, name); } } } // Walk up the nesting array looking for the constant function const_lookup_nesting(nesting, name) { var i, ii, constant; if (nesting.length === 0) return; // If the nesting is not empty the constant is looked up in its elements // and in order. The ancestors of those elements are ignored. for (i = 0, ii = nesting.length; i < ii; i++) { constant = nesting[i].$$const[name]; if (constant != null) { return constant; } else if (nesting[i].$$autoload && nesting[i].$$autoload[name]) { return handle_autoload(nesting[i], name); } } } // Walk up the ancestors chain looking for the constant function const_lookup_ancestors(cref, name) { var i, ii, ancestors; if (cref == null) return; ancestors = $ancestors(cref); for (i = 0, ii = ancestors.length; i < ii; i++) { if (ancestors[i].$$const && $has_own(ancestors[i].$$const, name)) { return ancestors[i].$$const[name]; } else if (ancestors[i].$$autoload && ancestors[i].$$autoload[name]) { return handle_autoload(ancestors[i], name); } } } // Walk up Object's ancestors chain looking for the constant, // but only if cref is missing or a module. function const_lookup_Object(cref, name) { if (cref == null || cref.$$is_module) { return const_lookup_ancestors(_Object, name); } } // Call const_missing if nothing else worked function const_missing(cref, name) { return (cref || _Object).$const_missing(name); } // Look for the constant just in the current cref or call `#const_missing` Opal.const_get_local = function(cref, name, skip_missing) { var result; if (cref == null) return; if (cref === '::') cref = _Object; if (!cref.$$is_module && !cref.$$is_class) { $raise(Opal.TypeError, cref.toString() + " is not a class/module"); } result = const_get_name(cref, name); return result != null || skip_missing ? result : const_missing(cref, name); }; // Look for the constant relative to a cref or call `#const_missing` (when the // constant is prefixed by `::`). Opal.const_get_qualified = function(cref, name, skip_missing) { var result, cache, cached, current_version = Opal.const_cache_version; if (name == null) { // A shortpath for calls like ::String => $$$("String") result = const_get_name(_Object, cref); if (result != null) return result; return Opal.const_get_qualified(_Object, cref, skip_missing); } if (cref == null) return; if (cref === '::') cref = _Object; if (!cref.$$is_module && !cref.$$is_class) { $raise(Opal.TypeError, cref.toString() + " is not a class/module"); } if ((cache = cref.$$const_cache) == null) { $prop(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) || ((result = const_lookup_ancestors(cref, name)) != null); cache[name] = [current_version, result]; } else { result = cached[1]; } return result != null || skip_missing ? result : const_missing(cref, name); }; // Initialize the top level constant cache generation counter Opal.const_cache_version = 1; // Look for the constant in the open using the current nesting and the nearest // cref ancestors or call `#const_missing` (when the constant has no :: prefix). Opal.const_get_relative = function(nesting, name, skip_missing) { var cref = nesting[0], result, current_version = Opal.const_cache_version, cache, cached; if ((cache = nesting.$$const_cache) == null) { $prop(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) || ((result = const_lookup_nesting(nesting, name)) != null) || ((result = const_lookup_ancestors(cref, name)) != null) || ((result = const_lookup_Object(cref, name)) != null); cache[name] = [current_version, result]; } else { result = cached[1]; } return result != null || skip_missing ? result : const_missing(cref, name); }; // Register the constant on a cref and opportunistically set the name of // unnamed classes/modules. function $const_set(cref, name, value) { var new_const = true; if (cref == null || cref === '::') cref = _Object; if (value.$$is_a_module) { if (value.$$name == null || value.$$name === nil) value.$$name = name; if (value.$$base_module == null) value.$$base_module = cref; } cref.$$const = (cref.$$const || Object.create(null)); if (name in cref.$$const || ("$$autoload" in cref && name in cref.$$autoload)) { new_const = false; } 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) $prop(cref, name, value); if (new_const && cref.$const_added && !cref.$const_added.$$pristine) { cref.$const_added(name); } return value; }; Opal.const_set = $const_set; // Get all the constants reachable from a given cref, by default will include // inherited constants. Opal.constants = function(cref, inherit) { if (inherit == null) inherit = true; var module, modules = [cref], i, ii, constants = {}, constant; if (inherit) modules = modules.concat($ancestors(cref)); if (inherit && cref.$$is_module) modules = modules.concat([Opal.Object]).concat($ancestors(Opal.Object)); for (i = 0, ii = modules.length; i < ii; i++) { module = modules[i]; // Do not show Objects constants unless we're querying Object itself if (cref !== _Object && module == _Object) break; for (constant in module.$$const) { constants[constant] = true; } if (module.$$autoload) { for (constant in module.$$autoload) { constants[constant] = true; } } } return Object.keys(constants); }; // Remove a constant from a cref. Opal.const_remove = function(cref, name) { Opal.const_cache_version++; if (cref.$$const[name] != null) { var old = cref.$$const[name]; delete cref.$$const[name]; return old; } if (cref.$$autoload && cref.$$autoload[name]) { delete cref.$$autoload[name]; return nil; } $raise(Opal.NameError, "constant "+cref+"::"+cref.$name()+" not defined"); }; // Generates a function that is a curried const_get_relative. Opal.const_get_relative_factory = function(nesting) { return function(name, skip_missing) { return Opal.$$(nesting, name, skip_missing); } } // Setup some shortcuts to reduce compiled size Opal.$$ = Opal.const_get_relative; Opal.$$$ = Opal.const_get_qualified; Opal.$r = Opal.const_get_relative_factory; function descends_from_bridged_class(klass) { if (klass == null) return false; if (klass.$$bridge) return klass; if (klass.$$super) return descends_from_bridged_class(klass.$$super); return false; } // Modules & Classes // ----------------- // A `class Foo; end` expression in ruby is compiled to call this runtime // method which either returns an existing class of the given name, or creates // a new class in the given `base` scope. // // If a constant with the given name exists, then we check to make sure that // it is a class and also that the superclasses match. If either of these // fail, then we raise a `TypeError`. Note, `superclass` may be null if one // was not specified in the ruby code. // // 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 `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 `scope` is an object (not a class/module), we simple get its class and // use that as the scope instead. // // @param scope [Object] where the class is being created // @param superclass [Class,null] superclass of the new class (may be null) // @param singleton [Boolean,null] a true value denotes we want to allocate // a singleton // // @return new [Class] or existing ruby class // function $allocate_class(name, superclass, singleton) { var klass, bridged_descendant; if (bridged_descendant = descends_from_bridged_class(superclass)) { // Inheritance from bridged classes requires // calling original JS constructors klass = function() { var self = new ($bind.apply(bridged_descendant.$$constructor, $prepend(null, arguments)))(); // and replacing a __proto__ manually $set_proto(self, klass.$$prototype); return self; } } else { klass = function(){}; } if (name && name !== nil) { $prop(klass, 'displayName', '::'+name); } $prop(klass, '$$name', name); $prop(klass, '$$constructor', klass); $prop(klass, '$$prototype', klass.prototype); $prop(klass, '$$const', {}); $prop(klass, '$$is_class', true); $prop(klass, '$$is_a_module', true); $prop(klass, '$$super', superclass); $prop(klass, '$$cvars', {}); $prop(klass, '$$own_included_modules', []); $prop(klass, '$$own_prepended_modules', []); $prop(klass, '$$ancestors', []); $prop(klass, '$$ancestors_cache_version', null); $prop(klass, '$$subclasses', []); $prop(klass, '$$cloned_from', []); $prop(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) { $set_proto(klass, Opal.Class.prototype); } if (superclass != null) { $set_proto(klass.$$prototype, superclass.$$prototype); if (singleton !== true) { // Let's not forbid GC from cleaning up our // subclasses. if (typeof WeakRef !== 'undefined') { // First, let's clean up our array from empty objects. var i, subclass, rebuilt_subclasses = []; for (i = 0; i < superclass.$$subclasses.length; i++) { subclass = superclass.$$subclasses[i]; if (subclass.deref() !== undefined) { rebuilt_subclasses.push(subclass); } } // Now, let's add our class. rebuilt_subclasses.push(new WeakRef(klass)); superclass.$$subclasses = rebuilt_subclasses; } else { superclass.$$subclasses.push(klass); } } if (superclass.$$meta) { // If superclass has metaclass then we have explicitely inherit it. Opal.build_class_singleton_class(klass); } } return klass; }; Opal.allocate_class = $allocate_class; function find_existing_class(scope, name) { // Try to find the class in the current scope 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) { $raise(Opal.TypeError, name + " is not a class"); } return klass; } } function ensureSuperclassMatch(klass, superclass) { if (klass.$$super !== superclass) { $raise(Opal.TypeError, "superclass mismatch for class " + klass.$$name); } } Opal.klass = function(scope, superclass, name) { var bridged; if (scope == null || scope == '::') { // Global scope scope = _Object; } else if (!scope.$$is_class && !scope.$$is_module) { // Scope is an object, use its class scope = scope.$$class; } // If the superclass is not an Opal-generated class then we're bridging a native JS class if ( superclass != null && (!superclass.hasOwnProperty || ( superclass.hasOwnProperty && !superclass.hasOwnProperty('$$is_class') )) ) { if (superclass.constructor && superclass.constructor.name == "Function") { bridged = superclass; superclass = _Object; } else { $raise(Opal.TypeError, "superclass must be a Class (" + ( (superclass.constructor && (superclass.constructor.name || superclass.constructor.$$name)) || typeof(superclass) ) + " given)"); } } var klass = find_existing_class(scope, name); if (klass != null) { if (superclass) { // Make sure existing class has same superclass ensureSuperclassMatch(klass, superclass); } } else { // Class doesn't 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; } // Create the class object (instance of Class) klass = $allocate_class(name, superclass); $const_set(scope, name, klass); // Call .inherited() hook with new class on the superclass if (superclass.$inherited) { superclass.$inherited(klass); } if (bridged) { Opal.bridge(bridged, klass); } } if (Opal.trace_class) { invoke_tracers_for_class(klass); } return klass; }; // 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 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 scope, then that // instance is just returned. // // 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 scope with the given name, and that // new instance is returned back (to be referenced at runtime). // // @param scope [Module, Class] class or module this definition is inside // @param id [String] the name of the new (or existing) module // // @return [Module] function $allocate_module(name) { var constructor = function(){}; var module = constructor; if (name) $prop(constructor, 'displayName', name+'.constructor'); $prop(module, '$$name', name); $prop(module, '$$prototype', constructor.prototype); $prop(module, '$$const', {}); $prop(module, '$$is_module', true); $prop(module, '$$is_a_module', true); $prop(module, '$$cvars', {}); $prop(module, '$$iclasses', []); $prop(module, '$$own_included_modules', []); $prop(module, '$$own_prepended_modules', []); $prop(module, '$$ancestors', [module]); $prop(module, '$$ancestors_cache_version', null); $prop(module, '$$cloned_from', []); $set_proto(module, Opal.Module.prototype); return module; }; Opal.allocate_module = $allocate_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) { $raise(Opal.TypeError, name + " is not a module"); } } return module; } Opal.module = function(scope, name) { var module; if (scope == null || scope == '::') { // Global scope scope = _Object; } else if (!scope.$$is_class && !scope.$$is_module) { // Scope is an object, use its class scope = scope.$$class; } module = find_existing_module(scope, name); if (module == null) { // Module doesnt exist, create a new one... module = $allocate_module(name); $const_set(scope, name, module); } if (Opal.trace_class) { invoke_tracers_for_class(module); } 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 // returned back. // // Otherwise, a new singleton object for the class or object is created, set on // the object at `$$meta` for future use, and then returned. // // @param object [Object] the ruby object // @return [Class] the singleton class for object Opal.get_singleton_class = function(object) { if (object.$$is_number) { $raise(Opal.TypeError, "can't define singleton"); } if (object.$$meta) { return object.$$meta; } if (object.hasOwnProperty('$$is_class')) { return Opal.build_class_singleton_class(object); } else if (object.hasOwnProperty('$$is_module')) { return Opal.build_module_singleton_class(object); } else { return Opal.build_object_singleton_class(object); } }; // helper to set $$meta on klass, module or instance function set_meta(obj, meta) { if (obj.hasOwnProperty('$$meta')) { obj.$$meta = meta; } else { $prop(obj, '$$meta', meta); } if (obj.$$frozen) { // If a object is frozen (sealed), freeze $$meta too. // No need to inject $$meta.$$prototype in the prototype chain, // as $$meta cannot be modified anyway. obj.$$meta.$freeze(); } else { $set_proto(obj, meta.$$prototype); } }; // 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). // // 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(klass) { if (klass.$$meta) { return klass.$$meta; } // 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`. var superclass = klass === BasicObject ? Class : Opal.get_singleton_class(klass.$$super); var meta = $allocate_class(null, superclass, true); $prop(meta, '$$is_singleton', true); $prop(meta, '$$singleton_of', klass); set_meta(klass, meta); // Restoring ClassName.class $prop(klass, '$$class', Opal.Class); return meta; }; Opal.build_module_singleton_class = function(mod) { if (mod.$$meta) { return mod.$$meta; } var meta = $allocate_class(null, Opal.Module, true); $prop(meta, '$$is_singleton', true); $prop(meta, '$$singleton_of', mod); set_meta(mod, meta); // Restoring ModuleName.class $prop(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, klass = $allocate_class(nil, superclass, true); $prop(klass, '$$is_singleton', true); $prop(klass, '$$singleton_of', object); delete klass.$$prototype.$$class; set_meta(object, klass); return klass; }; Opal.is_method = function(prop) { return (prop[0] === '$' && prop[1] !== '$'); }; Opal.instance_methods = function(mod) { var processed = Object.create(null), results = [], ancestors = $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 (processed[prop]) { continue; } if (Opal.is_method(prop)) { var method = proto[prop]; if (!method.$$stub) { var method_name = prop.slice(1); results.push(method_name); } } processed[prop] = true; } } 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(obj.$$meta || obj.$$class); }; Opal.own_methods = function(obj) { return obj.$$meta ? Opal.own_instance_methods(obj.$$meta) : []; }; 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] // @return [Object] Opal.class_variables = function(module) { var ancestors = $ancestors(module), i, length = ancestors.length, result = {}; for (i = length - 1; i >= 0; i--) { var ancestor = ancestors[i]; for (var cvar in ancestor.$$cvars) { result[cvar] = ancestor.$$cvars[cvar]; } } return result; }; // Sets class variable with specified +name+ to +value+ // in provided +module+ // // @param module [Module] // @param name [String] // @param value [Object] Opal.class_variable_set = function(module, name, value) { var ancestors = $ancestors(module), i, length = ancestors.length; for (i = length - 2; i >= 0; i--) { var ancestor = ancestors[i]; if ($has_own(ancestor.$$cvars, name)) { ancestor.$$cvars[name] = value; return value; } } module.$$cvars[name] = value; return value; }; // Gets class variable with specified +name+ from provided +module+ // // @param module [Module] // @param name [String] Opal.class_variable_get = function(module, name, tolerant) { if ($has_own(module.$$cvars, name)) return module.$$cvars[name]; var ancestors = $ancestors(module), i, length = ancestors.length; for (i = 0; i < length; i++) { var ancestor = ancestors[i]; if ($has_own(ancestor.$$cvars, name)) { return ancestor.$$cvars[name]; } } if (!tolerant) $raise(Opal.NameError, 'uninitialized class variable '+name+' in '+module.$name()); return nil; } function isRoot(proto) { return proto.hasOwnProperty('$$iclass') && proto.hasOwnProperty('$$root'); } function own_included_modules(module) { var result = [], mod, proto = Object.getPrototypeOf(module.$$prototype); while (proto) { if (proto.hasOwnProperty('$$class')) { // superclass break; } mod = protoToModule(proto); if (mod) { result.push(mod); } proto = Object.getPrototypeOf(proto); } return result; } function own_prepended_modules(module) { var result = [], mod, proto = Object.getPrototypeOf(module.$$prototype); if (module.$$prototype.hasOwnProperty('$$dummy')) { while (proto) { if (proto === module.$$prototype.$$define_methods_on) { break; } mod = protoToModule(proto); if (mod) { result.push(mod); } proto = Object.getPrototypeOf(proto); } } 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 // used to resolve the next class for a super call. A normal class would // have this point to its superclass. However, if a class includes a module // then this would need to take into account the module. The module would // also have to then point its `$$parent` to the actual superclass. We // cannot modify modules like this, because it might be included in more // then one class. To fix this, we actually insert an `iclass` as the class' // `$$parent` which can then point to the superclass. The `iclass` acts as // a proxy to the actual module, so the `super` chain can then search it for // the required method. // // @param module [Module] the module to include // @param includer [Module] the target class to include module into // @return [null] Opal.append_features = function(module, includer) { var module_ancestors = $ancestors(module); var iclasses = []; if (module_ancestors.indexOf(includer) !== -1) { $raise(Opal.ArgumentError, 'cyclic include detected'); } for (var i = 0, length = module_ancestors.length; i < length; i++) { var ancestor = module_ancestors[i], iclass = create_iclass(ancestor); $prop(iclass, '$$included', true); iclasses.push(iclass); } var includer_ancestors = $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 parent = includer.$$prototype, module_iclass = Object.getPrototypeOf(parent); while (module_iclass != null) { if (module_iclass.$$module === module && isRoot(module_iclass)) { break; } parent = module_iclass; module_iclass = Object.getPrototypeOf(module_iclass); } if (module_iclass) { // module has been directly included 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; } else { // module has not been directly included but was in ancestor chain because it was included by another module // include it directly start_chain_after = includer.$$prototype; end_chain_on = Object.getPrototypeOf(includer.$$prototype); } } $set_proto(start_chain_after, chain.first); $set_proto(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 = $ancestors(module); var iclasses = []; if (module_ancestors.indexOf(prepender) !== -1) { $raise(Opal.ArgumentError, 'cyclic prepend detected'); } for (var i = 0, length = module_ancestors.length; i < length; i++) { var ancestor = module_ancestors[i], iclass = create_iclass(ancestor); $prop(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); $prop(dummy_prepender, '$$dummy', true); $prop(dummy_prepender, '$$define_methods_on', prepender_iclass); // Converting // dummy(prepender) -> previous_parent // to // dummy(prepender) -> iclass(prepender) -> previous_parent $set_proto(dummy_prepender, prepender_iclass); $set_proto(prepender_iclass, previous_parent); } var prepender_ancestors = $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 { $raise(Opal.RuntimeError, "Prepending a module multiple times is not supported"); } $set_proto(start_chain_after, chain.first); $set_proto(chain.last, end_chain_on); // recalculate own_prepended_modules cache prepender.$$own_prepended_modules = own_prepended_modules(prepender); Opal.const_cache_version++; }; function flush_methods_in(module) { var proto = module.$$prototype, props = Object.getOwnPropertyNames(proto); for (var i = 0; i < props.length; i++) { var prop = props[i]; if (Opal.is_method(prop)) { delete proto[prop]; } } } function create_iclass(module) { var iclass = create_dummy_iclass(module); if (module.$$is_module) { module.$$iclasses.push(iclass); } 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]; $prop(iclass, prop, proto[prop]); } $prop(iclass, '$$iclass', true); $prop(iclass, '$$module', module); return iclass; } function chain_iclasses(iclasses) { var length = iclasses.length, first = iclasses[0]; $prop(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]; $set_proto(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 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(native_klass, klass) { if (native_klass.hasOwnProperty('$$bridge')) { $raise(Opal.ArgumentError, "already bridged"); } // 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 // $prop(native_klass, '$$bridge', klass); $set_proto(native_klass.prototype, (klass.$$super || Opal.Object).$$prototype); $prop(klass, '$$prototype', native_klass.prototype); $prop(klass.$$prototype, '$$class', klass); $prop(klass, '$$constructor', native_klass); $prop(klass, '$$bridge', true); }; function protoToModule(proto) { if (proto.hasOwnProperty('$$dummy')) { return; } else if (proto.hasOwnProperty('$$iclass')) { return proto.$$module; } else if (proto.hasOwnProperty('$$class')) { return proto.$$class; } } function own_ancestors(module) { return module.$$own_prepended_modules.concat([module]).concat(module.$$own_included_modules); } // The Array of ancestors for a given module/class function $ancestors(module) { if (!module) { return []; } if (module.$$ancestors_cache_version === Opal.const_cache_version) { return module.$$ancestors; } var result = [], i, mods, length; for (i = 0, mods = own_ancestors(module), length = mods.length; i < length; i++) { result.push(mods[i]); } if (module.$$super) { for (i = 0, mods = $ancestors(module.$$super), length = mods.length; i < length; i++) { result.push(mods[i]); } } module.$$ancestors_cache_version = Opal.const_cache_version; module.$$ancestors = result; return result; }; Opal.ancestors = $ancestors; Opal.included_modules = function(module) { var result = [], mod = null, proto = Object.getPrototypeOf(module.$$prototype); for (; proto && Object.getPrototypeOf(proto); proto = Object.getPrototypeOf(proto)) { mod = protoToModule(proto); if (mod && mod.$$is_module && proto.$$iclass && proto.$$included) { result.push(mod); } } return result; }; // Method Missing // -------------- // Methods stubs are used to facilitate method_missing in opal. A stub is a // placeholder function which just calls `method_missing` on the receiver. // If no method with the given name is actually defined on an object, then it // is obvious to say that the stub will be called instead, and then in turn // method_missing will be called. // // When a file in ruby gets compiled to javascript, it includes a call to // this function which adds stubs for every method name in the compiled file. // It should then be safe to assume that method_missing will work for any // method call detected. // // Method stubs are added to the BasicObject prototype, which every other // ruby object inherits, so all objects should handle method missing. A stub // is only added if the given property name (method name) is not already // defined. // // Note: all ruby methods have a `$` prefix in javascript, so all stubs will // have this prefix as well (to make this method more performant). // // Opal.add_stubs("foo,bar,baz="); // // All stub functions will have a private `$$stub` property set to true so // that other internal methods can detect if a method is just a stub or not. // `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 proto = Opal.BasicObject.$$prototype; var stub, existing_method; stubs = stubs.split(','); for (var i = 0, length = stubs.length; i < length; i++) { stub = $jsid(stubs[i]), existing_method = proto[stub]; if (existing_method == null || existing_method.$$stub) { Opal.add_stub_for(proto, stub); } } }; // 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) { // Opal.stub_for(stub) is the method_missing_stub $prop(prototype, stub, Opal.stub_for(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") // @return [undefined] Opal.stub_for = function(method_name) { function method_missing_stub() { // Copy any given block onto the method_missing dispatcher this.$method_missing.$$p = method_missing_stub.$$p; // Set block property to null ready for the next call (stop false-positives) method_missing_stub.$$p = null; // call method missing with correct args (remove '$' prefix on method name) return this.$method_missing.apply(this, $prepend(method_name.slice(1), arguments)); }; method_missing_stub.$$stub = true; return method_missing_stub; }; // Methods // ------- // Arity count error dispatcher for methods // // @param actual [Fixnum] number of arguments given to method // @param expected [Fixnum] expected number of arguments // @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_a_module) { inspect += object.$$name + '.'; } else { inspect += object.$$class.$$name + '#'; } inspect += meth; $raise(Opal.ArgumentError, '[' + inspect + '] wrong number of arguments (given ' + actual + ', expected ' + expected + ')'); }; // Arity count error dispatcher for blocks // // @param actual [Fixnum] number of arguments given to block // @param expected [Fixnum] expected number of arguments // @param context [Object] context of the block definition // @raise [ArgumentError] Opal.block_ac = function(actual, expected, context) { var inspect = "`block in " + context + "'"; $raise(Opal.ArgumentError, inspect + ': wrong number of arguments (given ' + actual + ', expected ' + expected + ')'); }; function get_ancestors(obj) { if (obj.hasOwnProperty('$$meta') && obj.$$meta !== null) { return $ancestors(obj.$$meta); } else { return $ancestors(obj.$$class); } }; // Super dispatcher Opal.find_super = function(obj, mid, current_func, defcheck, allow_stubs) { var jsid = $jsid(mid), ancestors, ancestor, super_method, method_owner, current_index = -1, i; ancestors = get_ancestors(obj); method_owner = current_func.$$owner; for (i = 0; i < ancestors.length; i++) { ancestor = ancestors[i]; if (ancestor === method_owner || ancestor.$$cloned_from.indexOf(method_owner) !== -1) { current_index = i; break; } } for (i = current_index + 1; i < ancestors.length; i++) { ancestor = ancestors[i]; var proto = ancestor.$$prototype; if (proto.hasOwnProperty('$$dummy')) { proto = proto.$$define_methods_on; } if (proto.hasOwnProperty(jsid)) { super_method = proto[jsid]; break; } } if (!defcheck && super_method && super_method.$$stub && obj.$method_missing.$$pristine) { // method_missing hasn't been explicitly defined $raise(Opal.NoMethodError, 'super: no superclass method `'+mid+"' for "+obj, mid); } return (super_method.$$stub && !allow_stubs) ? null : super_method; }; // Iter dispatcher for super in a block Opal.find_block_super = function(obj, jsid, current_func, defcheck, implicit) { var call_jsid = jsid; if (!current_func) { $raise(Opal.RuntimeError, "super called outside of method"); } if (implicit && current_func.$$define_meth) { $raise(Opal.RuntimeError, "implicit argument passing of super from method defined by define_method() is not supported. " + "Specify all arguments explicitly" ); } if (current_func.$$def) { call_jsid = current_func.$$jsid; } return Opal.find_super(obj, call_jsid, current_func, defcheck); }; // @deprecated Opal.find_super_dispatcher = Opal.find_super; // @deprecated Opal.find_iter_super_dispatcher = Opal.find_block_super; function call_lambda(block, arg, ret) { try { block(arg); } catch (e) { if (e === ret) { return ret.$v; } throw e; } } // handles yield calls for 1 yielded arg Opal.yield1 = function(block, arg) { if (typeof(block) !== "function") { $raise(Opal.LocalJumpError, "no block given"); } var has_mlhs = block.$$has_top_level_mlhs_arg, has_trailing_comma = block.$$has_trailing_comma_in_args, is_returning_lambda = block.$$is_lambda && block.$$ret; if (block.length > 1 || ((has_mlhs || has_trailing_comma) && block.length === 1)) { arg = Opal.to_ary(arg); } if ((block.length > 1 || (has_trailing_comma && block.length === 1)) && arg.$$is_array) { if (is_returning_lambda) { return call_lambda(block.apply.bind(block, null), arg, block.$$ret); } return block.apply(null, arg); } else { if (is_returning_lambda) { return call_lambda(block, arg, block.$$ret); } return block(arg); } }; // handles yield for > 1 yielded arg Opal.yieldX = function(block, args) { if (typeof(block) !== "function") { $raise(Opal.LocalJumpError, "no block given"); } if (block.length > 1 && args.length === 1) { if (args[0].$$is_array) { args = args[0]; } } if (block.$$is_lambda && block.$$ret) { return call_lambda(block.apply.bind(block, null), args, block.$$ret); } return block.apply(null, args); }; // Finds the corresponding exception match in candidates. Each candidate can // be a value, or an array of values. Returns null if not found. Opal.rescue = function(exception, candidates) { for (var i = 0; i < candidates.length; i++) { var candidate = candidates[i]; if (candidate.$$is_array) { var result = Opal.rescue(exception, candidate); if (result) { return result; } } else if (candidate === Opal.JS.Error || candidate['$==='](exception)) { return candidate; } } return null; }; Opal.is_a = function(object, klass) { if (klass != null && object.$$meta === klass || object.$$class === klass) { return true; } if (object.$$is_number && klass.$$is_number_class) { return (klass.$$is_integer_class) ? (object % 1) === 0 : true; } var ancestors = $ancestors(object.$$is_class ? Opal.get_singleton_class(object) : (object.$$meta || object.$$class)); return ancestors.indexOf(klass) !== -1; }; // Helpers for extracting kwsplats // Used for: { **h } Opal.to_hash = function(value) { if (value.$$is_hash) { return value; } else if (value['$respond_to?']('to_hash', true)) { var hash = value.$to_hash(); if (hash.$$is_hash) { return hash; } else { $raise(Opal.TypeError, "Can't convert " + value.$$class + " to Hash (" + value.$$class + "#to_hash gives " + hash.$$class + ")"); } } else { $raise(Opal.TypeError, "no implicit conversion of " + value.$$class + " into Hash"); } }; // Helpers for implementing multiple assignment // Our code for extracting the values and assigning them only works if the // return value is a JS array. // So if we get an Array subclass, extract the wrapped JS array from it // Used for: a, b = something (no splat) Opal.to_ary = function(value) { if (value.$$is_array) { return value; } else if (value['$respond_to?']('to_ary', true)) { var ary = value.$to_ary(); if (ary === nil) { return [value]; } else if (ary.$$is_array) { return ary; } else { $raise(Opal.TypeError, "Can't convert " + value.$$class + " to Array (" + value.$$class + "#to_ary gives " + ary.$$class + ")"); } } else { return [value]; } }; // Used for: a, b = *something (with splat) Opal.to_a = function(value) { if (value.$$is_array) { // A splatted array must be copied return value.slice(); } else if (value['$respond_to?']('to_a', true)) { var ary = value.$to_a(); if (ary === nil) { return [value]; } else if (ary.$$is_array) { return ary; } else { $raise(Opal.TypeError, "Can't convert " + value.$$class + " to Array (" + value.$$class + "#to_a gives " + ary.$$class + ")"); } } else { return [value]; } }; // Used for extracting keyword arguments from arguments passed to // JS function. // // @param parameters [Array] // @return [Hash] or undefined // Opal.extract_kwargs = function(parameters) { var kwargs = parameters[parameters.length - 1]; if (kwargs != null && Opal.respond_to(kwargs, '$to_hash', true)) { $splice(parameters, parameters.length - 1); return kwargs; } }; // Used to get a list of rest keyword arguments. Method takes the given // keyword args, i.e. the hash literal passed to the method containing all // keyword arguments passed to method, as well as the used args which are // the names of required and optional arguments defined. This method then // just returns all key/value pairs which have not been used, in a new // hash literal. // // @param given_args [Hash] all kwargs given to method // @param used_args [Object] all keys used as named kwargs // @return [Hash] // Opal.kwrestargs = function(given_args, used_args) { var map = new Map(); Opal.hash_each(given_args, false, function(key, value) { if (!used_args[key]) { Opal.hash_put(map, key, value); } return [false, false]; }); return map; }; function apply_blockopts(block, blockopts) { if (typeof(blockopts) === 'number') { block.$$arity = blockopts; } else if (typeof(blockopts) === 'object') { Object.assign(block, blockopts); } } // Optimization for a costly operation of prepending '$' to method names var jsid_cache = new Map(); function $jsid(name) { var jsid = jsid_cache.get(name); if (!jsid) { jsid = '$' + name; jsid_cache.set(name, jsid); } return jsid; } Opal.jsid = $jsid; function $prepend(first, second) { if (!second.$$is_array) second = $slice(second); return [first].concat(second); } // Calls passed method on a ruby object with arguments and block: // // Can take a method or a method name. // // 1. When method name gets passed it invokes it by its name // and calls 'method_missing' when object doesn't have this method. // Used internally by Opal to invoke method that takes a block or a splat. // 2. When method (i.e. method body) gets passed, it doesn't trigger 'method_missing' // because it doesn't know the name of the actual method. // Used internally by Opal to invoke 'super'. // // @example // var my_array = [1, 2, 3, 4] // Opal.send(my_array, 'length') # => 4 // Opal.send(my_array, my_array.$length) # => 4 // // Opal.send(my_array, 'reverse!') # => [4, 3, 2, 1] // Opal.send(my_array, my_array['$reverse!']') # => [4, 3, 2, 1] // // @param recv [Object] ruby object // @param method [Function, String] method body or name of the method // @param args [Array] arguments that will be passed to the method call // @param block [Function] ruby block // @param blockopts [Object, Number] optional properties to set on the block // @return [Object] returning value of the method call Opal.send = function(recv, method, args, block, blockopts) { var body; if (typeof(method) === 'function') { body = method; method = null; } else if (typeof(method) === 'string') { body = recv[$jsid(method)]; } else { $raise(Opal.NameError, "Passed method should be a string or a function"); } return Opal.send2(recv, body, method, args, block, blockopts); }; Opal.send2 = function(recv, body, method, args, block, blockopts) { if (body == null && method != null && recv.$method_missing) { body = recv.$method_missing; args = $prepend(method, args); } apply_blockopts(block, blockopts); if (typeof block === 'function') body.$$p = block; return body.apply(recv, args); }; Opal.refined_send = function(refinement_groups, recv, method, args, block, blockopts) { var i, j, k, ancestors, ancestor, refinements, refinement, refine_modules, refine_module, body; ancestors = get_ancestors(recv); // For all ancestors that there are, starting from the closest to the furthest... for (i = 0; i < ancestors.length; i++) { ancestor = Opal.id(ancestors[i]); // For all refinement groups there are, starting from the closest scope to the furthest... for (j = 0; j < refinement_groups.length; j++) { refinements = refinement_groups[j]; // For all refinements there are, starting from the last `using` call to the furthest... for (k = refinements.length - 1; k >= 0; k--) { refinement = refinements[k]; if (typeof refinement.$$refine_modules === 'undefined') continue; // A single module being given as an argument of the `using` call contains multiple // refinement modules refine_modules = refinement.$$refine_modules; // Does this module refine a given call for a given ancestor module? if (typeof refine_modules[ancestor] === 'undefined') continue; refine_module = refine_modules[ancestor]; // Does this module define a method we want to call? if (typeof refine_module.$$prototype[$jsid(method)] !== 'undefined') { body = refine_module.$$prototype[$jsid(method)]; return Opal.send2(recv, body, method, args, block, blockopts); } } } } return Opal.send(recv, method, args, block, blockopts); }; Opal.lambda = function(block, blockopts) { block.$$is_lambda = true; apply_blockopts(block, blockopts); 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. // // ## Modules // // Any method defined on a module will come through this runtime helper. // The method is added to the module body, and the owner of the method is // set to be the module itself. This is used later when choosing which // method should show on a class if more than 1 included modules define // the same method. Finally, if the module is in `module_function` mode, // then the method is also defined onto the module itself. // // ## Classes // // This helper will only be called for classes when a method is being // defined indirectly; either through `Module#define_method`, or by a // literal `def` method inside an `instance_eval` or `class_eval` body. In // either case, the method is simply added to the class' prototype. A special // exception exists for `BasicObject` and `Object`. These two classes are // special because they are used in toll-free bridged classes. In each of // these two cases, extra work is required to define the methods on toll-free // bridged class' prototypes as well. // // ## Objects // // If a simple ruby object is the object, then the method is simply just // defined on the object as a singleton method. This would be the case when // a method is defined inside an `instance_eval` block. // // @param obj [Object, Class] the actual obj to define method for // @param jsid [String] the JavaScript friendly method name (e.g. '$foo') // @param body [JS.Function] the literal JavaScript function used as method // @param blockopts [Object, Number] optional properties to set on the body // @return [null] // Opal.def = function(obj, jsid, body, blockopts) { apply_blockopts(body, blockopts); // Special case for a method definition in the // top-level namespace if (obj === Opal.top) { return Opal.defn(Opal.Object, jsid, body); } // if instance_eval is invoked on a module/class, it sets inst_eval_mod else if (!obj.$$eval && obj.$$is_a_module) { return Opal.defn(obj, jsid, body); } else { return Opal.defs(obj, jsid, body); } }; // Define method on a module or class (see Opal.def). Opal.defn = function(module, jsid, body) { $deny_frozen_access(module); body.displayName = jsid; body.$$owner = module; var name = jsid.substr(1); var proto = module.$$prototype; if (proto.hasOwnProperty('$$dummy')) { proto = proto.$$define_methods_on; } $prop(proto, jsid, body); if (module.$$is_module) { if (module.$$module_function) { Opal.defs(module, jsid, body) } for (var i = 0, iclasses = module.$$iclasses, length = iclasses.length; i < length; i++) { var iclass = iclasses[i]; $prop(iclass, jsid, body); } } var singleton_of = module.$$singleton_of; if (module.$method_added && !module.$method_added.$$stub && !singleton_of) { module.$method_added(name); } else if (singleton_of && singleton_of.$singleton_method_added && !singleton_of.$singleton_method_added.$$stub) { singleton_of.$singleton_method_added(name); } return name; }; // Define a singleton method on the given object (see Opal.def). Opal.defs = function(obj, jsid, body, blockopts) { apply_blockopts(body, blockopts); if (obj.$$is_string || obj.$$is_number) { $raise(Opal.TypeError, "can't define singleton"); } return Opal.defn(Opal.get_singleton_class(obj), jsid, body); }; // Since JavaScript has no concept of modules, we create proxy classes // called `iclasses` that store copies of methods loaded. We need to // update them if we remove a method. function remove_method_from_iclasses(obj, jsid) { if (obj.$$is_module) { for (var i = 0, iclasses = obj.$$iclasses, length = iclasses.length; i < length; i++) { var iclass = iclasses[i]; delete iclass[jsid]; } } } // Called from #remove_method. Opal.rdef = function(obj, jsid) { if (!$has_own(obj.$$prototype, jsid)) { $raise(Opal.NameError, "method '" + jsid.substr(1) + "' not defined in " + obj.$name()); } delete obj.$$prototype[jsid]; remove_method_from_iclasses(obj, jsid); if (obj.$$is_singleton) { 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)); } } }; // Called from #undef_method. Opal.udef = function(obj, jsid) { if (!obj.$$prototype[jsid] || obj.$$prototype[jsid].$$stub) { $raise(Opal.NameError, "method '" + jsid.substr(1) + "' not defined in " + obj.$name()); } Opal.add_stub_for(obj.$$prototype, jsid); remove_method_from_iclasses(obj, jsid); if (obj.$$is_singleton) { 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 = $jsid(name), old_id = $jsid(old), body, alias; // Aliasing on main means aliasing on Object... if (typeof obj.$$prototype === 'undefined') { obj = Opal.Object; } body = obj.$$prototype[old_id]; // 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 (!is_method_body(body)) { var ancestor = obj.$$super; while (typeof(body) !== "function" && ancestor) { body = ancestor[old_id]; ancestor = ancestor.$$super; } if (!is_method_body(body) && obj.$$is_module) { // try to look into Object body = Opal.Object.$$prototype[old_id] } if (!is_method_body(body)) { $raise(Opal.NameError, "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 properties // would be overwritten on the original body. alias = Opal.wrap_method_body(body); // Try to make the browser pick the right name alias.displayName = name; alias.$$alias_of = body; alias.$$alias_name = name; Opal.defn(obj, id, alias); return obj; }; Opal.wrap_method_body = function(body) { var wrapped = function() { var block = wrapped.$$p; wrapped.$$p = null; return Opal.send(this, body, arguments, block); }; // Assign the 'length' value with defineProperty because // in strict mode the property is not writable. // It doesn't work in older browsers (like Chrome 38), where // an exception is thrown breaking Opal altogether. try { Object.defineProperty(wrapped, 'length', { value: body.length }); } catch (e) {} wrapped.$$arity = body.$$arity == null ? body.length : body.$$arity; wrapped.$$parameters = body.$$parameters; wrapped.$$source_location = body.$$source_location; return wrapped; }; Opal.alias_gvar = function(new_name, old_name) { Object.defineProperty($gvars, new_name, { configurable: true, enumerable: true, get: function() { return $gvars[old_name]; }, set: function(new_value) { $gvars[old_name] = new_value; } }); return nil; } Opal.alias_native = function(obj, name, native_name) { var id = $jsid(name), body = obj.$$prototype[native_name]; if (typeof(body) !== "function" || body.$$stub) { $raise(Opal.NameError, "undefined native method `" + native_name + "' for class `" + obj.$name() + "'") } Opal.defn(obj, id, body); return obj; }; // Hashes // ------ Opal.hash_init = function (_hash) { console.warn("DEPRECATION: Opal.hash_init is deprecated and is now a no-op."); } Opal.hash_clone = function(from_hash, to_hash) { to_hash.$$none = from_hash.$$none; to_hash.$$proc = from_hash.$$proc; return Opal.hash_each(from_hash, to_hash, function(key, value) { Opal.hash_put(to_hash, key, value); return [false, to_hash]; }); }; Opal.hash_put = function(hash, key, value) { var type = typeof key; if (type === "string" || type === "symbol" || type === "number" || type === "boolean" || type === "bigint") { hash.set(key, value) } else if (key.$$is_string) { hash.set(key.valueOf(), value); } else { if (!hash.$$keys) hash.$$keys = new Map(); var key_hash = key.$$is_string ? key.valueOf() : (hash.$$by_identity ? Opal.id(key) : key.$hash()), keys = hash.$$keys; if (!keys.has(key_hash)) { keys.set(key_hash, [key]); hash.set(key, value); return; } var objects = keys.get(key_hash), object; for (var i=0; i= 0; for (var i = 0, ii = parts.length; i < ii; i++) { part = parts[i]; if (part instanceof RegExp) { if (part.ignoreCase !== ignoreCase) Opal.Kernel.$warn( "ignore case doesn't match for " + part.source.$inspect(), new Map([['uplevel', 1]]) ) part = part.source; } if (part === '') part = '(?:' + part + ')'; parts[i] = part; } if (flags) { return new RegExp(parts.join(''), flags); } else { return new RegExp(parts.join('')); } }; // Require system // -------------- Opal.modules = {}; Opal.loaded_features = ['corelib/runtime']; Opal.current_dir = '.'; Opal.require_table = {'corelib/runtime': true}; Opal.normalize = function(path) { var parts, part, new_parts = [], SEPARATOR = '/'; if (Opal.current_dir !== '.') { path = Opal.current_dir.replace(/\/*$/, '/') + path; } path = path.replace(/^\.\//, ''); path = path.replace(/\.(rb|opal|js)$/, ''); parts = path.split(SEPARATOR); for (var i = 0, ii = parts.length; i < ii; i++) { part = parts[i]; if (part === '') continue; (part === '..') ? new_parts.pop() : new_parts.push(part) } return new_parts.join(SEPARATOR); }; Opal.loaded = function(paths) { var i, l, path; for (i = 0, l = paths.length; i < l; i++) { path = Opal.normalize(paths[i]); if (Opal.require_table[path]) { continue; } Opal.loaded_features.push(path); Opal.require_table[path] = true; } }; Opal.load_normalized = function(path) { Opal.loaded([path]); var module = Opal.modules[path]; if (module) { var retval = module(Opal); if (typeof Promise !== 'undefined' && retval instanceof Promise) { // A special case of require having an async top: // We will need to await it. return retval.then($return_val(true)); } } else { var severity = Opal.config.missing_require_severity; var message = 'cannot load such file -- ' + path; if (severity === "error") { $raise(Opal.LoadError, message); } else if (severity === "warning") { console.warn('WARNING: LoadError: ' + message); } } return true; }; Opal.load = function(path) { path = Opal.normalize(path); return Opal.load_normalized(path); }; Opal.require = function(path) { path = Opal.normalize(path); if (Opal.require_table[path]) { return false; } return Opal.load_normalized(path); }; // Strings // ------- Opal.encodings = Object.create(null); // Sets the encoding on a string, will treat string literals as frozen strings // raising a FrozenError. // // @param str [String] the string on which the encoding should be set // @param name [String] the canonical name of the encoding // @param type [String] possible values are either `"encoding"`, `"internal_encoding"`, or `undefined Opal.set_encoding = function(str, name, type) { if (typeof type === "undefined") type = "encoding"; if (typeof str === 'string' || str.$$frozen === true) $raise(Opal.FrozenError, "can't modify frozen String"); var encoding = Opal.find_encoding(name); if (encoding === str[type]) { return str; } str[type] = encoding; return str; }; // Fetches the encoding for the given name or raises ArgumentError. Opal.find_encoding = function(name) { var register = Opal.encodings; var encoding = register[name] || register[name.toUpperCase()]; if (!encoding) $raise(Opal.ArgumentError, "unknown encoding name - " + name); return encoding; } // @returns a String object with the encoding set from a string literal Opal.enc = function(str, name) { var dup = new String(str); dup = Opal.set_encoding(dup, name); dup.internal_encoding = dup.encoding; return dup } // @returns a String object with the internal encoding set to Binary Opal.binary = function(str) { var dup = new String(str); return Opal.set_encoding(dup, "binary", "internal_encoding"); } Opal.last_promise = null; Opal.promise_unhandled_exception = false; // Run a block of code, but if it returns a Promise, don't run the next // one, but queue it. Opal.queue = function(proc) { if (Opal.last_promise) { // The async path is taken only if anything before returned a // Promise(V2). Opal.last_promise = Opal.last_promise.then(function() { if (!Opal.promise_unhandled_exception) return proc(Opal); })['catch'](function(error) { if (Opal.respond_to(error, '$full_message')) { error = error.$full_message(); } console.error(error); // Abort further execution Opal.promise_unhandled_exception = true; Opal.exit(1); }); return Opal.last_promise; } else { var ret = proc(Opal); if (typeof Promise === 'function' && typeof ret === 'object' && ret instanceof Promise) { Opal.last_promise = ret; } return ret; } } // Operator helpers // ---------------- function are_both_numbers(l,r) { return typeof(l) === 'number' && typeof(r) === 'number' } Opal.rb_plus = function(l,r) { return are_both_numbers(l,r) ? l + r : l['$+'](r); } Opal.rb_minus = function(l,r) { return are_both_numbers(l,r) ? l - r : l['$-'](r); } Opal.rb_times = function(l,r) { return are_both_numbers(l,r) ? l * r : l['$*'](r); } Opal.rb_divide = function(l,r) { return are_both_numbers(l,r) ? l / r : l['$/'](r); } Opal.rb_lt = function(l,r) { return are_both_numbers(l,r) ? l < r : l['$<'](r); } Opal.rb_gt = function(l,r) { return are_both_numbers(l,r) ? l > r : l['$>'](r); } Opal.rb_le = function(l,r) { return are_both_numbers(l,r) ? l <= r : l['$<='](r); } Opal.rb_ge = function(l,r) { return are_both_numbers(l,r) ? l >= r : l['$>='](r); } // Optimized helpers for calls like $truthy((a)['$==='](b)) -> $eqeqeq(a, b) function are_both_numbers_or_strings(lhs, rhs) { return (typeof lhs === 'number' && typeof rhs === 'number') || (typeof lhs === 'string' && typeof rhs === 'string'); } function $eqeq(lhs, rhs) { return are_both_numbers_or_strings(lhs,rhs) ? lhs === rhs : $truthy((lhs)['$=='](rhs)); }; Opal.eqeq = $eqeq; Opal.eqeqeq = function(lhs, rhs) { return are_both_numbers_or_strings(lhs,rhs) ? lhs === rhs : $truthy((lhs)['$==='](rhs)); }; Opal.neqeq = function(lhs, rhs) { return are_both_numbers_or_strings(lhs,rhs) ? lhs !== rhs : $truthy((lhs)['$!='](rhs)); }; Opal.not = function(arg) { if (undefined === arg || null === arg || false === arg || nil === arg) return true; if (true === arg || arg['$!'].$$pristine) return false; return $truthy(arg['$!']()); } // Shortcuts - optimized function generators for simple kinds of functions function $return_val(arg) { return function() { return arg; } } Opal.return_val = $return_val; Opal.return_self = function() { return this; } Opal.return_ivar = function(ivar) { return function() { if (this[ivar] == null) { return nil; } return this[ivar]; } } Opal.assign_ivar = function(ivar) { return function(val) { $deny_frozen_access(this); return this[ivar] = val; } } Opal.assign_ivar_val = function(ivar, static_val) { return function() { $deny_frozen_access(this); return this[ivar] = static_val; } } // Primitives for handling parameters Opal.ensure_kwargs = function(kwargs) { if (kwargs == null) { return new Map(); } else if (kwargs.$$is_hash) { return kwargs; } else { $raise(Opal.ArgumentError, 'expected kwargs'); } } Opal.get_kwarg = function(kwargs, key) { var kwarg = Opal.hash_get(kwargs, key); if (kwarg === undefined) { $raise(Opal.ArgumentError, 'missing keyword: '+key); } return kwarg; } // Arrays of size > 32 elements that contain only strings, // symbols, integers and nils are compiled as a self-extracting // string. Opal.large_array_unpack = function(str) { var array = str.split(","), length = array.length, i; for (i = 0; i < length; i++) { switch(array[i][0]) { case undefined: array[i] = nil break; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': array[i] = +array[i]; } } return array; } // Opal32-checksum algorithm for #hash // ----------------------------------- Opal.opal32_init = $return_val(0x4f70616c); function $opal32_ror(n, d) { return (n << d)|(n >>> (32 - d)); }; Opal.opal32_add = function(hash, next) { hash ^= next; hash = $opal32_ror(hash, 1); return hash; }; // Initialization // -------------- Opal.BasicObject = BasicObject = $allocate_class('BasicObject', null); Opal.Object = _Object = $allocate_class('Object', Opal.BasicObject); Opal.Module = Module = $allocate_class('Module', Opal.Object); Opal.Class = Class = $allocate_class('Class', Opal.Module); Opal.Opal = _Opal = $allocate_module('Opal'); Opal.Kernel = Kernel = $allocate_module('Kernel'); $set_proto(Opal.BasicObject, Opal.Class.$$prototype); $set_proto(Opal.Object, Opal.Class.$$prototype); $set_proto(Opal.Module, Opal.Class.$$prototype); $set_proto(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 $const_set(_Object, "BasicObject", BasicObject); $const_set(_Object, "Object", _Object); $const_set(_Object, "Module", Module); $const_set(_Object, "Class", Class); $const_set(_Object, "Opal", _Opal); $const_set(_Object, "Kernel", Kernel); // Fix booted classes to have correct .class value BasicObject.$$class = Class; _Object.$$class = Class; Module.$$class = Class; Class.$$class = Class; _Opal.$$class = Module; Kernel.$$class = Module; // Forward .toString() to #to_s $prop(_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. $prop(_Object.$$prototype, '$require', Opal.require); // Instantiate the main object Opal.top = new _Object(); Opal.top.$to_s = Opal.top.$inspect = $return_val('main'); Opal.top.$define_method = top_define_method; // Foward calls to define_method on the top object to Object function top_define_method() { var block = top_define_method.$$p; top_define_method.$$p = null; return Opal.send(_Object, 'define_method', arguments, block) }; // Nil Opal.NilClass = $allocate_class('NilClass', Opal.Object); $const_set(_Object, 'NilClass', Opal.NilClass); nil = Opal.nil = new Opal.NilClass(); nil.$$id = nil_id; nil.call = nil.apply = function() { $raise(Opal.LocalJumpError, 'no block given'); }; nil.$$frozen = true; nil.$$comparable = false; Object.seal(nil); Opal.thrower = function(type) { var thrower = { $thrower_type: type, $throw: function(value, called_from_lambda) { if (value == null) value = nil; if (this.is_orphan && !called_from_lambda) { $raise(Opal.LocalJumpError, 'unexpected ' + type, value, type.$to_sym()); } this.$v = value; throw this; }, is_orphan: false } return thrower; }; // Define a "$@" global variable, which would compute and return a backtrace on demand. Object.defineProperty($gvars, "@", { enumerable: true, configurable: true, get: function() { if ($truthy($gvars["!"])) return $gvars["!"].$backtrace(); return nil; }, set: function(bt) { if ($truthy($gvars["!"])) $gvars["!"].$set_backtrace(bt); else $raise(Opal.ArgumentError, "$! not set"); } }); Opal.t_eval_return = Opal.thrower("return"); TypeError.$$super = Error; // If enable-file-source-embed compiler option is enabled, each module loaded will add its // sources to this object Opal.file_sources = {}; }).call(this);