opal/corelib/runtime.js in opal-1.0.5 vs opal/corelib/runtime.js in opal-1.1.0.rc1

- old
+ new

@@ -1,6 +1,8 @@ -(function(undefined) { +(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 @@ -11,15 +13,16 @@ // to improve or for alternative solutions // // The way the code is digested before going through Yardoc is a secret kept // in the docs repo (https://github.com/opal/docs/tree/master). - var global_object = this, console; + var console; // Detect the global object - if (typeof(global) !== 'undefined') { global_object = global; } - if (typeof(window) !== 'undefined') { global_object = window; } + 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 (typeof(global_object.console) === 'object') { console = global_object.console; } else if (global_object.console == null) { @@ -29,13 +32,13 @@ } if (!('log' in console)) { console.log = function () {}; } if (!('warn' in console)) { console.warn = console.log; } - if (typeof(this.Opal) !== 'undefined') { + if (typeof(global_object.Opal) !== 'undefined') { console.warn('Opal already loaded. Loading twice can cause troubles, please fix your setup.'); - return this.Opal; + return global_object.Opal; } var nil; // The actual class for BasicObject @@ -50,29 +53,29 @@ // The actual Class class var Class; // The Opal object that is exposed globally - var Opal = this.Opal = {}; + var Opal = global_object.Opal = {}; // 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 + // 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 enable_stack_trace: true // true, false }; // Minify common function calls - var $hasOwn = Object.hasOwnProperty; - var $bind = Function.prototype.bind; - var $setPrototype = Object.setPrototypeOf; - var $slice = Array.prototype.slice; - var $splice = Array.prototype.splice; + var $has_own = Object.hasOwnProperty; + var $bind = Function.prototype.bind; + var $set_proto = 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 @@ -133,11 +136,11 @@ // 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. + // numbers, true, false and null do not support it. object[name] = initialValue; } else { Object.defineProperty(object, name, { value: initialValue, enumerable: false, @@ -150,22 +153,65 @@ Opal.defineProperty = $defineProperty; Opal.slice = $slice; - // Truth + // Helpers // ----- Opal.truthy = function(val) { return (val !== nil && val != null && (!val.$$is_boolean || val == true)); }; Opal.falsy = function(val) { return (val === nil || val == null || (val.$$is_boolean && val == false)) }; + Opal.type_error = function(object, type, method, coerced) { + object = object.$$class; + if (coerced && method) { + coerced = coerced.$$class; + return Opal.TypeError.$new( + "can't convert " + object + " into " + type + + " (" + object + "#" + method + " gives " + coerced + ")" + ) + } else { + return Opal.TypeError.$new( + "no implicit conversion of " + object + " into " + type + ) + } + }; + + Opal.coerce_to = function(object, type, method, args) { + if (type['$==='](object)) return object; + + if (!object['$respond_to?'](method)) { + throw 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 (obj['$respond_to_missing?'].$$pristine) { + return typeof(body) === "function" && !body.$$stub; + } else { + 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]); + } + } + + // Constants // --------- // // For future reference: // - The Rails autoloading guide (http://guides.rubyonrails.org/v5.0/autoloading_and_reloading_constants.html) @@ -181,11 +227,11 @@ if (cref) return cref.$$const[name]; } // Walk up the nesting array looking for the constant function const_lookup_nesting(nesting, name) { - var i, ii, result, constant; + 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. @@ -195,18 +241,18 @@ } } // Walk up the ancestors chain looking for the constant function const_lookup_ancestors(cref, name) { - var i, ii, result, ancestors; + var i, ii, ancestors; if (cref == null) return; ancestors = Opal.ancestors(cref); for (i = 0, ii = ancestors.length; i < ii; i++) { - if (ancestors[i].$$const && $hasOwn.call(ancestors[i].$$const, name)) { + if (ancestors[i].$$const && $has_own.call(ancestors[i].$$const, name)) { return ancestors[i].$$const[name]; } } } @@ -331,19 +377,19 @@ // Get all the constants reachable from a given cref, by default will include // inherited constants. Opal.constants = function(cref, inherit) { if (inherit == null) inherit = true; - var module, modules = [cref], module_constants, i, ii, constants = {}, constant; + var module, modules = [cref], i, ii, constants = {}, constant; if (inherit) modules = modules.concat(Opal.ancestors(cref)); if (inherit && cref.$$is_module) modules = modules.concat([Opal.Object]).concat(Opal.ancestors(Opal.Object)); for (i = 0, ii = modules.length; i < ii; i++) { module = modules[i]; - // Don not show Objects constants unless we're querying Object itself + // 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; } @@ -368,11 +414,15 @@ } throw Opal.NameError.$new("constant "+cref+"::"+cref.$name()+" not defined"); }; + // Setup some shortcuts to reduce compiled size + Opal.$$ = Opal.const_get_relative; + Opal.$$$ = Opal.const_get_qualified; + // 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 @@ -408,11 +458,11 @@ 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); + $set_proto(self, klass.$$prototype); return self; } } else { constructor = function(){}; } @@ -441,15 +491,15 @@ // 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); + $set_proto(klass, Opal.Class.prototype); } if (superclass != null) { - $setPrototype(klass.$$prototype, superclass.$$prototype); + $set_proto(klass.$$prototype, superclass.$$prototype); if (superclass.$$meta) { // If superclass has metaclass then we have explicitely inherit it. Opal.build_class_singleton_class(klass); } @@ -570,11 +620,11 @@ $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); + $set_proto(module, Opal.Module.prototype); return module; }; function find_existing_module(scope, name) { @@ -631,11 +681,11 @@ } 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); + return Opal.build_module_singleton_class(object); } else { return Opal.build_object_singleton_class(object); } }; @@ -663,28 +713,28 @@ meta = Opal.allocate_class(null, superclass, function(){}); $defineProperty(meta, '$$is_singleton', true); $defineProperty(meta, '$$singleton_of', klass); $defineProperty(klass, '$$meta', meta); - $setPrototype(klass, meta.$$prototype); + $set_proto(klass, meta.$$prototype); // Restoring ClassName.class $defineProperty(klass, '$$class', Opal.Class); return meta; }; - Opal.build_module_singletin_class = function(mod) { + Opal.build_module_singleton_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); + $set_proto(mod, meta.$$prototype); // Restoring ModuleName.class $defineProperty(mod, '$$class', Opal.Module); return meta; }; @@ -702,11 +752,11 @@ delete klass.$$prototype.$$class; $defineProperty(object, '$$meta', klass); - $setPrototype(object, object.$$meta.$$prototype); + $set_proto(object, object.$$meta.$$prototype); return klass; }; Opal.is_method = function(prop) { @@ -821,11 +871,11 @@ i, length = ancestors.length; for (i = length - 2; i >= 0; i--) { var ancestor = ancestors[i]; - if ($hasOwn.call(ancestor.$$cvars, name)) { + if ($has_own.call(ancestor.$$cvars, name)) { ancestor.$$cvars[name] = value; return value; } } @@ -967,12 +1017,12 @@ start_chain_after = parent; end_chain_on = next_ancestor; } - $setPrototype(start_chain_after, chain.first); - $setPrototype(chain.last, end_chain_on); + $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++; @@ -1027,12 +1077,12 @@ // Converting // dummy(prepender) -> previous_parent // to // dummy(prepender) -> iclass(prepender) -> previous_parent - $setPrototype(dummy_prepender, prepender_iclass); - $setPrototype(prepender_iclass, previous_parent); + $set_proto(dummy_prepender, prepender_iclass); + $set_proto(prepender_iclass, previous_parent); } var prepender_ancestors = Opal.ancestors(prepender); if (prepender_ancestors.indexOf(module) === -1) { @@ -1055,12 +1105,12 @@ } } 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); + $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++; @@ -1122,11 +1172,11 @@ var previous = first; for (var i = 1; i < length; i++) { var current = iclasses[i]; - $setPrototype(previous, current); + $set_proto(previous, current); previous = current; } return { first: iclasses[0], last: iclasses[length - 1] }; @@ -1152,16 +1202,10 @@ Opal.bridge = function(native_klass, klass) { if (native_klass.hasOwnProperty('$$bridge')) { throw Opal.ArgumentError.$new("already bridged"); } - var klass_to_inject, klass_reference; - - klass_to_inject = klass.$$super || Opal.Object; - klass_reference = klass; - var original_prototype = klass.$$prototype; - // 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) @@ -1174,11 +1218,11 @@ // - Opal.BasicObject // - super (window.Object) // - null // $defineProperty(native_klass, '$$bridge', klass); - $setPrototype(native_klass.prototype, (klass.$$super || Opal.Object).$$prototype); + $set_proto(native_klass.prototype, (klass.$$super || Opal.Object).$$prototype); $defineProperty(klass, '$$prototype', native_klass.prototype); $defineProperty(klass.$$prototype, '$$class', klass); $defineProperty(klass, '$$constructor', native_klass); $defineProperty(klass, '$$bridge', true); @@ -1294,11 +1338,14 @@ // 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() { + /* jshint validthis: true */ + // 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; @@ -1350,11 +1397,11 @@ 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) { + Opal.find_super_dispatcher = function(obj, mid, current_func, defcheck, allow_stubs) { var jsid = '$' + mid, ancestors, super_method; if (obj.hasOwnProperty('$$meta')) { ancestors = Opal.ancestors(obj.$$meta); } else { @@ -1370,25 +1417,21 @@ if (proto.hasOwnProperty('$$dummy')) { proto = proto.$$define_methods_on; } if (proto.hasOwnProperty(jsid)) { - var method = proto[jsid]; - - if (!method.$$stub) { - super_method = method; - } + super_method = proto[jsid]; break; } } - if (!defcheck && super_method == null && Opal.Kernel.$method_missing === obj.$method_missing) { + if (!defcheck && super_method && super_method.$$stub && obj.$method_missing.$$pristine) { // method_missing hasn't been explicitly defined throw Opal.NoMethodError.$new('super: no superclass method `'+mid+"' for "+obj, mid); } - return super_method; + return (super_method.$$stub && !allow_stubs) ? null : super_method; }; // Iter dispatcher for super in a block Opal.find_iter_super_dispatcher = function(obj, jsid, current_func, defcheck, implicit) { var call_jsid = jsid; @@ -1503,11 +1546,11 @@ if (klass != null && object.$$meta === klass || object.$$class === klass) { return true; } if (object.$$is_number && klass.$$is_number_class) { - return true; + return (klass.$$is_integer_class) ? (object % 1) === 0 : true; } var i, length, ancestors = Opal.ancestors(object.$$is_class ? Opal.get_singleton_class(object) : (object.$$meta || object.$$class)); for (i = 0, length = ancestors.length; i < length; i++) { @@ -1599,11 +1642,11 @@ // @param parameters [Array] // @return [Hash] // Opal.extract_kwargs = function(parameters) { var kwargs = parameters[parameters.length - 1]; - if (kwargs != null && kwargs['$respond_to?']('to_hash', true)) { + if (kwargs != null && Opal.respond_to(kwargs, '$to_hash', true)) { $splice.call(parameters, parameters.length - 1, 1); return kwargs.$to_hash(); } else { return Opal.hash2([], {}); @@ -1660,22 +1703,34 @@ // @param method [Function, String] method body or name of the method // @param args [Array] arguments that will be passed to the method call // @param block [Function] ruby block // @return [Object] returning value of the method call Opal.send = function(recv, method, args, block) { - var body = (typeof(method) === 'string') ? recv['$'+method] : method; + var body; - if (body != null) { - if (typeof block === 'function') { - body.$$p = block; - } - return body.apply(recv, args); + if (typeof(method) === 'function') { + body = method; + method = null; + } else if (typeof(method) === 'string') { + body = recv['$'+method]; + } else { + throw Opal.NameError.$new("Passed method should be a string or a function"); } - return recv.$method_missing.apply(recv, [method].concat(args)); + return Opal.send2(recv, body, method, args, block); }; + Opal.send2 = function(recv, body, method, args, block) { + if (body == null && method != null && recv.$method_missing) { + body = recv.$method_missing; + args = [method].concat(args); + } + + if (typeof block === 'function') body.$$p = block; + return body.apply(recv, args); + }; + Opal.lambda = function(block) { block.$$is_lambda = true; return block; }; @@ -1769,11 +1824,11 @@ Opal.defn(Opal.get_singleton_class(obj), jsid, body) }; // Called from #remove_method. Opal.rdef = function(obj, jsid) { - if (!$hasOwn.call(obj.$$prototype, jsid)) { + if (!$has_own.call(obj.$$prototype, jsid)) { throw Opal.NameError.$new("method '" + jsid.substr(1) + "' not defined in " + obj.$name()); } delete obj.$$prototype[jsid]; @@ -1845,11 +1900,11 @@ // 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 ovrewritten on the original body. + // would be overwritten on the original body. alias = function() { var block = alias.$$p, args, i, ii; args = new Array(arguments.length); for(i = 0, ii = arguments.length; i < ii; i++) { @@ -1859,13 +1914,21 @@ if (block != null) { alias.$$p = null } return Opal.send(this, body, args, 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(alias, 'length', { value: body.length }); + } catch (e) {} + // Try to make the browser pick the right name alias.displayName = name; - alias.length = body.length; + alias.$$arity = body.$$arity; alias.$$parameters = body.$$parameters; alias.$$source_location = body.$$source_location; alias.$$alias_of = body; alias.$$alias_name = name; @@ -1916,21 +1979,21 @@ } }; Opal.hash_put = function(hash, key, value) { if (key.$$is_string) { - if (!$hasOwn.call(hash.$$smap, key)) { + if (!$has_own.call(hash.$$smap, key)) { hash.$$keys.push(key); } hash.$$smap[key] = value; return; } var key_hash, bucket, last_bucket; key_hash = hash.$$by_identity ? Opal.id(key) : key.$hash(); - if (!$hasOwn.call(hash.$$map, key_hash)) { + if (!$has_own.call(hash.$$map, key_hash)) { bucket = {key: key, key_hash: key_hash, value: value}; hash.$$keys.push(bucket); hash.$$map[key_hash] = bucket; return; } @@ -1954,20 +2017,20 @@ } }; Opal.hash_get = function(hash, key) { if (key.$$is_string) { - if ($hasOwn.call(hash.$$smap, key)) { + if ($has_own.call(hash.$$smap, key)) { return hash.$$smap[key]; } return; } var key_hash, bucket; key_hash = hash.$$by_identity ? Opal.id(key) : key.$hash(); - if ($hasOwn.call(hash.$$map, key_hash)) { + if ($has_own.call(hash.$$map, key_hash)) { bucket = hash.$$map[key_hash]; while (bucket) { if (key === bucket.key || key['$eql?'](bucket.key)) { return bucket.value; @@ -1979,11 +2042,13 @@ Opal.hash_delete = function(hash, key) { var i, keys = hash.$$keys, length = keys.length, value; if (key.$$is_string) { - if (!$hasOwn.call(hash.$$smap, key)) { + if (typeof key !== "string") key = key.valueOf(); + + if (!$has_own.call(hash.$$smap, key)) { return; } for (i = 0; i < length; i++) { if (keys[i] === key) { @@ -1997,11 +2062,11 @@ return value; } var key_hash = key.$hash(); - if (!$hasOwn.call(hash.$$map, key_hash)) { + if (!$has_own.call(hash.$$map, key_hash)) { return; } var bucket = hash.$$map[key_hash], last_bucket; @@ -2072,11 +2137,11 @@ bucket = bucket.next; } hash.$$keys[i].key_hash = key_hash; - if (!$hasOwn.call(hash.$$map, key_hash)) { + if (!$has_own.call(hash.$$map, key_hash)) { hash.$$map[key_hash] = hash.$$keys[i]; continue; } bucket = hash.$$map[key_hash]; @@ -2126,11 +2191,11 @@ } if (arguments_length === 1) { args = arguments[0]; for (key in args) { - if ($hasOwn.call(args, key)) { + if ($has_own.call(args, key)) { value = args[key]; Opal.hash_put(hash, key, value); } } @@ -2255,10 +2320,37 @@ } result.lastIndex = null; // reset lastIndex property return result; }; + // Combine multiple regexp parts together + Opal.regexp = function(parts, flags) { + var part; + var ignoreCase = typeof flags !== 'undefined' && flags && flags.indexOf('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(), + Opal.hash({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']; @@ -2338,10 +2430,41 @@ return Opal.load(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 + Opal.set_encoding = function(str, name) { + if (typeof str === 'string') + throw Opal.FrozenError.$new("can't modify frozen String"); + + var encoding = Opal.encodings[name]; + + if (encoding === str.encoding) { return str; } + + str.encoding = encoding; + + return str; + }; + + // @returns a String object with the encoding set from a string literal + Opal.enc = function(str, name) { + var dup = new String(str); + Opal.set_encoding(dup, name); + dup.internal_encoding = dup.encoding; + return dup + } + + // Initialization // -------------- function $BasicObject() {} function $Object() {} function $Module() {} @@ -2350,14 +2473,14 @@ 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); - $setPrototype(Opal.BasicObject, Opal.Class.$$prototype); - $setPrototype(Opal.Object, Opal.Class.$$prototype); - $setPrototype(Opal.Module, Opal.Class.$$prototype); - $setPrototype(Opal.Class, Opal.Class.$$prototype); + $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 @@ -2385,17 +2508,21 @@ // Make Kernel#require immediately available as it's needed to require all the // other corelib files. $defineProperty(_Object.$$prototype, '$require', Opal.require); - // 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' }; + Opal.top.$define_method = top_define_method; + + // Foward calls to define_method on the top object to Object + function top_define_method() { + var args = Opal.slice.call(arguments, 0, arguments.length); + var block = top_define_method.$$p; + top_define_method.$$p = null; + return Opal.send(_Object, 'define_method', args, block) + }; // Nil function $NilClass() {} Opal.NilClass = Opal.allocate_class('NilClass', Opal.Object, $NilClass);