/* vim: set sw=4 ts=4 et tw=78: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Narcissus JavaScript engine. * * The Initial Developer of the Original Code is * Brendan Eich . * Portions created by the Initial Developer are Copyright (C) 2004 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Tom Austin * Brendan Eich * Shu-Yu Guo * Dave Herman * Dimitris Vardoulakis * Patrick Walton * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * Narcissus - JS implemented in JS. * * Well-known constants and lookup tables. Many consts are generated from the * tokens table via eval to minimize redundancy, so consumers must be compiled * separately to take advantage of the simple switch-case constant propagation * done by SpiderMonkey. */ (function() { var narcissus = { options: { version: 185, // Global variables to hide from the interpreter hiddenHostGlobals: { Narcissus: true }, // Desugar SpiderMonkey language extensions? desugarExtensions: false, // Allow HTML comments? allowHTMLComments: false }, hostSupportsEvalConst: (function() { try { return eval("(function(s) { eval(s); return x })('const x = true;')"); } catch (e) { return false; } })(), hostGlobal: this }; Narcissus = narcissus; })(); Narcissus.definitions = (function() { var tokens = [ // End of source. "END", // Operators and punctuators. Some pair-wise order matters, e.g. (+, -) // and (UNARY_PLUS, UNARY_MINUS). "\n", ";", ",", "=", "?", ":", "CONDITIONAL", "||", "&&", "|", "^", "&", "==", "!=", "===", "!==", "<", "<=", ">=", ">", "<<", ">>", ">>>", "+", "-", "*", "/", "%", "!", "~", "UNARY_PLUS", "UNARY_MINUS", "++", "--", ".", "[", "]", "{", "}", "(", ")", // Nonterminal tree node type codes. "SCRIPT", "BLOCK", "LABEL", "FOR_IN", "CALL", "NEW_WITH_ARGS", "INDEX", "ARRAY_INIT", "OBJECT_INIT", "PROPERTY_INIT", "GETTER", "SETTER", "GROUP", "LIST", "LET_BLOCK", "ARRAY_COMP", "GENERATOR", "COMP_TAIL", // Terminals. "IDENTIFIER", "NUMBER", "STRING", "REGEXP", // Keywords. "break", "case", "catch", "const", "continue", "debugger", "default", "delete", "do", "else", "export", "false", "finally", "for", "function", "if", "import", "in", "instanceof", "let", "module", "new", "null", "return", "switch", "this", "throw", "true", "try", "typeof", "var", "void", "yield", "while", "with", ]; var statementStartTokens = [ "break", "const", "continue", "debugger", "do", "for", "if", "return", "switch", "throw", "try", "var", "yield", "while", "with", ]; // Whitespace characters (see ECMA-262 7.2) var whitespaceChars = [ // normal whitespace: "\u0009", "\u000B", "\u000C", "\u0020", "\u00A0", "\uFEFF", // high-Unicode whitespace: "\u1680", "\u180E", "\u2000", "\u2001", "\u2002", "\u2003", "\u2004", "\u2005", "\u2006", "\u2007", "\u2008", "\u2009", "\u200A", "\u202F", "\u205F", "\u3000" ]; var whitespace = {}; for (var i = 0; i < whitespaceChars.length; i++) { whitespace[whitespaceChars[i]] = true; } // Operator and punctuator mapping from token to tree node type name. // NB: because the lexer doesn't backtrack, all token prefixes must themselves // be valid tokens (e.g. !== is acceptable because its prefixes are the valid // tokens != and !). var opTypeNames = { '\n': "NEWLINE", ';': "SEMICOLON", ',': "COMMA", '?': "HOOK", ':': "COLON", '||': "OR", '&&': "AND", '|': "BITWISE_OR", '^': "BITWISE_XOR", '&': "BITWISE_AND", '===': "STRICT_EQ", '==': "EQ", '=': "ASSIGN", '!==': "STRICT_NE", '!=': "NE", '<<': "LSH", '<=': "LE", '<': "LT", '>>>': "URSH", '>>': "RSH", '>=': "GE", '>': "GT", '++': "INCREMENT", '--': "DECREMENT", '+': "PLUS", '-': "MINUS", '*': "MUL", '/': "DIV", '%': "MOD", '!': "NOT", '~': "BITWISE_NOT", '.': "DOT", '[': "LEFT_BRACKET", ']': "RIGHT_BRACKET", '{': "LEFT_CURLY", '}': "RIGHT_CURLY", '(': "LEFT_PAREN", ')': "RIGHT_PAREN" }; // Hash of keyword identifier to tokens index. NB: we must null __proto__ to // avoid toString, etc. namespace pollution. var keywords = {__proto__: null}; // Define const END, etc., based on the token names. Also map name to index. var tokenIds = {}; // Building up a string to be eval'd in different contexts. var consts = Narcissus.hostSupportsEvalConst ? "const " : "var "; for (var i = 0, j = tokens.length; i < j; i++) { if (i > 0) consts += ", "; var t = tokens[i]; var name; if (/^[a-z]/.test(t)) { name = t.toUpperCase(); keywords[t] = i; } else { name = (/^\W/.test(t) ? opTypeNames[t] : t); } consts += name + " = " + i; tokenIds[name] = i; tokens[t] = i; } consts += ";"; var isStatementStartCode = {__proto__: null}; for (i = 0, j = statementStartTokens.length; i < j; i++) isStatementStartCode[keywords[statementStartTokens[i]]] = true; // Map assignment operators to their indexes in the tokens array. var assignOps = ['|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%']; for (i = 0, j = assignOps.length; i < j; i++) { t = assignOps[i]; assignOps[t] = tokens[t]; } function defineGetter(obj, prop, fn, dontDelete, dontEnum) { Object.defineProperty(obj, prop, { get: fn, configurable: !dontDelete, enumerable: !dontEnum }); } function defineGetterSetter(obj, prop, getter, setter, dontDelete, dontEnum) { Object.defineProperty(obj, prop, { get: getter, set: setter, configurable: !dontDelete, enumerable: !dontEnum }); } function defineMemoGetter(obj, prop, fn, dontDelete, dontEnum) { Object.defineProperty(obj, prop, { get: function() { var val = fn(); defineProperty(obj, prop, val, dontDelete, true, dontEnum); return val; }, configurable: true, enumerable: !dontEnum }); } function defineProperty(obj, prop, val, dontDelete, readOnly, dontEnum) { Object.defineProperty(obj, prop, { value: val, writable: !readOnly, configurable: !dontDelete, enumerable: !dontEnum }); } // Returns true if fn is a native function. (Note: SpiderMonkey specific.) function isNativeCode(fn) { // Relies on the toString method to identify native code. return ((typeof fn) === "function") && fn.toString().match(/\[native code\]/); } var Fpapply = Function.prototype.apply; function apply(f, o, a) { return Fpapply.call(f, [o].concat(a)); } var applyNew; // ES5's bind is a simpler way to implement applyNew if (Function.prototype.bind) { applyNew = function applyNew(f, a) { return new (f.bind.apply(f, [,].concat(a)))(); }; } else { applyNew = function applyNew(f, a) { switch (a.length) { case 0: return new f(); case 1: return new f(a[0]); case 2: return new f(a[0], a[1]); case 3: return new f(a[0], a[1], a[2]); default: var argStr = "a[0]"; for (var i = 1, n = a.length; i < n; i++) argStr += ",a[" + i + "]"; return eval("new f(" + argStr + ")"); } }; } function getPropertyDescriptor(obj, name) { while (obj) { if (({}).hasOwnProperty.call(obj, name)) return Object.getOwnPropertyDescriptor(obj, name); obj = Object.getPrototypeOf(obj); } } function getPropertyNames(obj) { var table = Object.create(null, {}); while (obj) { var names = Object.getOwnPropertyNames(obj); for (var i = 0, n = names.length; i < n; i++) table[names[i]] = true; obj = Object.getPrototypeOf(obj); } return Object.keys(table); } function getOwnProperties(obj) { var map = {}; for (var name in Object.getOwnPropertyNames(obj)) map[name] = Object.getOwnPropertyDescriptor(obj, name); return map; } function blacklistHandler(target, blacklist) { var mask = Object.create(null, {}); var redirect = StringMap.create(blacklist).mapObject(function(name) { return mask; }); return mixinHandler(redirect, target); } function whitelistHandler(target, whitelist) { var catchall = Object.create(null, {}); var redirect = StringMap.create(whitelist).mapObject(function(name) { return target; }); return mixinHandler(redirect, catchall); } function mirrorHandler(target, writable) { var handler = makePassthruHandler(target); var defineProperty = handler.defineProperty; handler.defineProperty = function(name, desc) { if (!desc.enumerable) throw new Error("mirror property must be enumerable"); if (!desc.configurable) throw new Error("mirror property must be configurable"); if (desc.writable !== writable) throw new Error("mirror property must " + (writable ? "" : "not ") + "be writable"); defineProperty(name, desc); }; handler.fix = function() { }; handler.getOwnPropertyDescriptor = handler.getPropertyDescriptor; handler.getOwnPropertyNames = getPropertyNames.bind(handler, target); handler.keys = handler.enumerate; handler["delete"] = function() { return false; }; handler.hasOwn = handler.has; return handler; } /* * Mixin proxies break the single-inheritance model of prototypes, so * the handler treats all properties as own-properties: * * X * | * +------------+------------+ * | O | * | | | * | O O O | * | | | | | * | O O O O | * | | | | | | * | O O O O O | * | | | | | | | * +-(*)--(w)--(x)--(y)--(z)-+ */ function mixinHandler(redirect, catchall) { function targetFor(name) { return hasOwn(redirect, name) ? redirect[name] : catchall; } function getMuxPropertyDescriptor(name) { var desc = getPropertyDescriptor(targetFor(name), name); if (desc) desc.configurable = true; return desc; } function getMuxPropertyNames() { var names1 = Object.getOwnPropertyNames(redirect).filter(function(name) { return name in redirect[name]; }); var names2 = getPropertyNames(catchall).filter(function(name) { return !hasOwn(redirect, name); }); return names1.concat(names2); } function enumerateMux() { var result = Object.getOwnPropertyNames(redirect).filter(function(name) { return name in redirect[name]; }); for (name in catchall) { if (!hasOwn(redirect, name)) result.push(name); }; return result; } function hasMux(name) { return name in targetFor(name); } return { getOwnPropertyDescriptor: getMuxPropertyDescriptor, getPropertyDescriptor: getMuxPropertyDescriptor, getOwnPropertyNames: getMuxPropertyNames, defineProperty: function(name, desc) { Object.defineProperty(targetFor(name), name, desc); }, "delete": function(name) { var target = targetFor(name); return delete target[name]; }, // FIXME: ha ha ha fix: function() { }, has: hasMux, hasOwn: hasMux, get: function(receiver, name) { var target = targetFor(name); return target[name]; }, set: function(receiver, name, val) { var target = targetFor(name); target[name] = val; return true; }, enumerate: enumerateMux, keys: enumerateMux }; } function makePassthruHandler(obj) { // Handler copied from // http://wiki.ecmascript.org/doku.php?id=harmony:proxies&s=proxy%20object#examplea_no-op_forwarding_proxy return { getOwnPropertyDescriptor: function(name) { var desc = Object.getOwnPropertyDescriptor(obj, name); // a trapping proxy's properties must always be configurable desc.configurable = true; return desc; }, getPropertyDescriptor: function(name) { var desc = getPropertyDescriptor(obj, name); // a trapping proxy's properties must always be configurable desc.configurable = true; return desc; }, getOwnPropertyNames: function() { return Object.getOwnPropertyNames(obj); }, defineProperty: function(name, desc) { Object.defineProperty(obj, name, desc); }, "delete": function(name) { return delete obj[name]; }, fix: function() { if (Object.isFrozen(obj)) { return getOwnProperties(obj); } // As long as obj is not frozen, the proxy won't allow itself to be fixed. return undefined; // will cause a TypeError to be thrown }, has: function(name) { return name in obj; }, hasOwn: function(name) { return ({}).hasOwnProperty.call(obj, name); }, get: function(receiver, name) { return obj[name]; }, // bad behavior when set fails in non-strict mode set: function(receiver, name, val) { obj[name] = val; return true; }, enumerate: function() { var result = []; for (name in obj) { result.push(name); }; return result; }, keys: function() { return Object.keys(obj); } }; } var hasOwnProperty = ({}).hasOwnProperty; function hasOwn(obj, name) { return hasOwnProperty.call(obj, name); } function StringMap(table, size) { this.table = table || Object.create(null, {}); this.size = size || 0; } StringMap.create = function(table) { var init = Object.create(null, {}); var size = 0; var names = Object.getOwnPropertyNames(table); for (var i = 0, n = names.length; i < n; i++) { var name = names[i]; init[name] = table[name]; size++; } return new StringMap(init, size); }; StringMap.prototype = { has: function(x) { return hasOwnProperty.call(this.table, x); }, set: function(x, v) { if (!hasOwnProperty.call(this.table, x)) this.size++; this.table[x] = v; }, get: function(x) { return this.table[x]; }, getDef: function(x, thunk) { if (!hasOwnProperty.call(this.table, x)) { this.size++; this.table[x] = thunk(); } return this.table[x]; }, forEach: function(f) { var table = this.table; for (var key in table) f.call(this, key, table[key]); }, map: function(f) { var table1 = this.table; var table2 = Object.create(null, {}); this.forEach(function(key, val) { table2[key] = f.call(this, val, key); }); return new StringMap(table2, this.size); }, mapObject: function(f) { var table1 = this.table; var table2 = Object.create(null, {}); this.forEach(function(key, val) { table2[key] = f.call(this, val, key); }); return table2; }, toObject: function() { return this.mapObject(function(val) { return val; }); }, choose: function() { return Object.getOwnPropertyNames(this.table)[0]; }, remove: function(x) { if (hasOwnProperty.call(this.table, x)) { this.size--; delete this.table[x]; } }, copy: function() { var table = Object.create(null, {}); for (var key in this.table) table[key] = this.table[key]; return new StringMap(table, this.size); }, keys: function() { return Object.keys(this.table); }, toString: function() { return "[object StringMap]" } }; // an object-key table with poor asymptotics (replace with WeakMap when possible) function ObjectMap(array) { this.array = array || []; } function searchMap(map, key, found, notFound) { var a = map.array; for (var i = 0, n = a.length; i < n; i++) { var pair = a[i]; if (pair.key === key) return found(pair, i); } return notFound(); } ObjectMap.prototype = { has: function(x) { return searchMap(this, x, function() { return true }, function() { return false }); }, set: function(x, v) { var a = this.array; searchMap(this, x, function(pair) { pair.value = v }, function() { a.push({ key: x, value: v }) }); }, get: function(x) { return searchMap(this, x, function(pair) { return pair.value }, function() { return null }); }, getDef: function(x, thunk) { var a = this.array; return searchMap(this, x, function(pair) { return pair.value }, function() { var v = thunk(); a.push({ key: x, value: v }); return v; }); }, forEach: function(f) { var a = this.array; for (var i = 0, n = a.length; i < n; i++) { var pair = a[i]; f.call(this, pair.key, pair.value); } }, choose: function() { return this.array[0].key; }, get size() { return this.array.length; }, remove: function(x) { var a = this.array; searchMap(this, x, function(pair, i) { a.splice(i, 1) }, function() { }); }, copy: function() { return new ObjectMap(this.array.map(function(pair) { return { key: pair.key, value: pair.value } })); }, clear: function() { this.array = []; }, toString: function() { return "[object ObjectMap]" } }; // non-destructive stack function Stack(elts) { this.elts = elts || null; } Stack.prototype = { push: function(x) { return new Stack({ top: x, rest: this.elts }); }, top: function() { if (!this.elts) throw new Error("empty stack"); return this.elts.top; }, isEmpty: function() { return this.top === null; }, find: function(test) { for (var elts = this.elts; elts; elts = elts.rest) { if (test(elts.top)) return elts.top; } return null; }, has: function(x) { return Boolean(this.find(function(elt) { return elt === x })); }, forEach: function(f) { for (var elts = this.elts; elts; elts = elts.rest) { f(elts.top); } } }; if (!Array.prototype.copy) { Array.prototype.copy = function() { var result = []; for (var i = 0, n = this.length; i < n; i++) result[i] = this[i]; return result; }; } return { tokens: tokens, whitespace: whitespace, opTypeNames: opTypeNames, keywords: keywords, isStatementStartCode: isStatementStartCode, tokenIds: tokenIds, consts: consts, assignOps: assignOps, defineGetter: defineGetter, defineGetterSetter: defineGetterSetter, defineMemoGetter: defineMemoGetter, defineProperty: defineProperty, isNativeCode: isNativeCode, apply: apply, applyNew: applyNew, mirrorHandler: mirrorHandler, mixinHandler: mixinHandler, whitelistHandler: whitelistHandler, blacklistHandler: blacklistHandler, makePassthruHandler: makePassthruHandler, StringMap: StringMap, ObjectMap: ObjectMap, Stack: Stack }; }());