diff --git a/lib/ace/document.js b/lib/ace/document.js index e89ba8ed..7e31f8bd 100644 --- a/lib/ace/document.js +++ b/lib/ace/document.js @@ -87,7 +87,7 @@ var Document = function(text) { this.$detectNewLine = function(text) { - var match = text.match(/^.*?(\r?\n)/m); + var match = text.match(/^.*?(\r\n|\r|\n)/m); if (match) { this.$autoNewLine = match[1]; } else { @@ -111,7 +111,8 @@ var Document = function(text) { this.$autoNewLine = "\n"; this.$newLineMode = "auto"; this.setNewLineMode = function(newLineMode) { - if (this.$newLineMode === newLineMode) return; + if (this.$newLineMode === newLineMode) + return; this.$newLineMode = newLineMode; }; diff --git a/lib/ace/narcissus/jsdefs.js b/lib/ace/narcissus/jsdefs.js index 09215364..bce60cc8 100644 --- a/lib/ace/narcissus/jsdefs.js +++ b/lib/ace/narcissus/jsdefs.js @@ -52,324 +52,628 @@ define(function(require, exports, module) { -exports.options = { - version: 185, -}; - -(function() { - exports.hostGlobal = this -})(); - -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", - "false", "finally", "for", "function", - "if", "in", "instanceof", - "let", - "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", -]; - -// 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 = "const "; -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 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\]/); -} - -function getPropertyDescriptor(obj, name) { - while (obj) { - if (({}).hasOwnProperty.call(obj, name)) - return Object.getOwnPropertyDescriptor(obj, name); - obj = Object.getPrototypeOf(obj); - } -} - -function getOwnProperties(obj) { - var map = {}; - for (var name in Object.getOwnPropertyNames(obj)) - map[name] = Object.getOwnPropertyDescriptor(obj, name); - return map; -} - -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; + var narcissus = { + options: { + version: 185, + // Global variables to hide from the interpreter + hiddenHostGlobals: { Narcissus: true }, + // Desugar SpiderMonkey language extensions? + desugarExtensions: false }, - 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); + hostSupportsEvalConst: (function() { + try { + return eval("(function(s) { eval(s); return x })('const x = true;')"); + } catch (e) { + return false; } - - // 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); } + })(), + hostGlobal: this }; -} + Narcissus = narcissus; -// default function used when looking for a property in the global object -function noPropFound() { return undefined; } + var tokens = [ + // End of source. + "END", -var hasOwnProperty = ({}).hasOwnProperty; + // Operators and punctuators. Some pair-wise order matters, e.g. (+, -) + // and (UNARY_PLUS, UNARY_MINUS). + "\n", ";", + ",", + "=", + "?", ":", "CONDITIONAL", + "||", + "&&", + "|", + "^", + "&", + "==", "!=", "===", "!==", + "<", "<=", ">=", ">", + "<<", ">>", ">>>", + "+", "-", + "*", "/", "%", + "!", "~", "UNARY_PLUS", "UNARY_MINUS", + "++", "--", + ".", + "[", "]", + "{", "}", + "(", ")", -function StringMap() { - this.table = Object.create(null, {}); - this.size = 0; -} + // 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", -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(); + // 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); } - return this.table[x]; - }, - forEach: function(f) { - var table = this.table; - for (var key in table) - f.call(this, key, table[key]); - }, - toString: function() { return "[object StringMap]" } -}; + consts += name + " = " + i; + tokenIds[name] = i; + tokens[t] = i; + } + consts += ";"; -// non-destructive stack -function Stack(elts) { - this.elts = elts || null; -} + var isStatementStartCode = {__proto__: null}; + for (i = 0, j = statementStartTokens.length; i < j; i++) + isStatementStartCode[keywords[statementStartTokens[i]]] = true; -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); + // 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\]/); + } + + function getPropertyDescriptor(obj, name) { + while (obj) { + if (({}).hasOwnProperty.call(obj, name)) + return Object.getOwnPropertyDescriptor(obj, name); + obj = Object.getPrototypeOf(obj); } } -}; -exports.tokens = tokens; -exports.opTypeNames = opTypeNames; -exports.keywords = keywords; -exports.isStatementStartCode = isStatementStartCode; -exports.tokenIds = tokenIds; -exports.consts = consts; -exports.assignOps = assignOps; -exports.defineGetter = defineGetter; -exports.defineProperty = defineProperty; -exports.isNativeCode = isNativeCode; -exports.makePassthruHandler = makePassthruHandler; -exports.noPropFound = noPropFound; -exports.StringMap = StringMap; -exports.Stack = Stack; + 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); + }, + 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); + } + } + }; + + module.exports = { + 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, + mirrorHandler: mirrorHandler, + mixinHandler: mixinHandler, + whitelistHandler: whitelistHandler, + blacklistHandler: blacklistHandler, + makePassthruHandler: makePassthruHandler, + StringMap: StringMap, + ObjectMap: ObjectMap, + Stack: Stack + }; }); \ No newline at end of file diff --git a/lib/ace/narcissus/jslex.js b/lib/ace/narcissus/jslex.js index e2c5a646..b65cf334 100644 --- a/lib/ace/narcissus/jslex.js +++ b/lib/ace/narcissus/jslex.js @@ -47,416 +47,503 @@ * Lexical scanner. */ -define(function(require, exports, module) { + define(function(require, exports, module) { -var definitions = require("ace/narcissus/jsdefs"); + var definitions = require("ace/narcissus/jsdefs"); -// Set constants in the local scope. -eval(definitions.consts); + // Set constants in the local scope. + eval(definitions.consts); -// Build up a trie of operator tokens. -var opTokens = {}; -for (var op in definitions.opTypeNames) { - if (op === '\n' || op === '.') - continue; + // Banned keywords by language version + const blackLists = { 160: {}, 185: {}, harmony: {} }; + blackLists[160][LET] = true; + blackLists[160][MODULE] = true; + blackLists[160][YIELD] = true; + blackLists[185][MODULE] = true; - var node = opTokens; - for (var i = 0; i < op.length; i++) { - var ch = op[i]; - if (!(ch in node)) - node[ch] = {}; - node = node[ch]; - node.op = op; + // Build up a trie of operator tokens. + var opTokens = {}; + for (var op in definitions.opTypeNames) { + if (op === '\n' || op === '.') + continue; + + var node = opTokens; + for (var i = 0; i < op.length; i++) { + var ch = op[i]; + if (!(ch in node)) + node[ch] = {}; + node = node[ch]; + node.op = op; + } } -} -/* - * Tokenizer :: (source, filename, line number) -> Tokenizer - */ -function Tokenizer(s, f, l) { - this.cursor = 0; - this.source = String(s); - this.tokens = []; - this.tokenIndex = 0; - this.lookahead = 0; - this.scanNewlines = false; - this.unexpectedEOF = false; - this.filename = f || ""; - this.lineno = l || 1; -} - -Tokenizer.prototype = { - get done() { - // We need to set scanOperand to true here because the first thing - // might be a regexp. - return this.peek(true) === END; - }, - - get token() { - return this.tokens[this.tokenIndex]; - }, - - match: function (tt, scanOperand) { - return this.get(scanOperand) === tt || this.unget(); - }, - - mustMatch: function (tt) { - if (!this.match(tt)) { - throw this.newSyntaxError("Missing " + - definitions.tokens[tt].toLowerCase()); - } - return this.token; - }, - - peek: function (scanOperand) { - var tt, next; - if (this.lookahead) { - next = this.tokens[(this.tokenIndex + this.lookahead) & 3]; - tt = (this.scanNewlines && next.lineno !== this.lineno) - ? NEWLINE - : next.type; - } else { - tt = this.get(scanOperand); - this.unget(); - } - return tt; - }, - - peekOnSameLine: function (scanOperand) { - this.scanNewlines = true; - var tt = this.peek(scanOperand); + /* + * Tokenizer :: (source, filename, line number) -> Tokenizer + */ + function Tokenizer(s, f, l) { + this.cursor = 0; + this.source = String(s); + this.tokens = []; + this.tokenIndex = 0; + this.lookahead = 0; this.scanNewlines = false; - return tt; - }, + this.unexpectedEOF = false; + this.filename = f || ""; + this.lineno = l || 1; + this.blackList = blackLists[Narcissus.options.version]; + this.blockComments = null; + } - // Eat comments and whitespace. - skip: function () { - var input = this.source; - for (;;) { - var ch = input[this.cursor++]; - var next = input[this.cursor]; - if (ch === '\n' && !this.scanNewlines) { - this.lineno++; - } else if (ch === '/' && next === '*') { - this.cursor++; - for (;;) { - ch = input[this.cursor++]; - if (ch === undefined) - throw this.newSyntaxError("Unterminated comment"); + Tokenizer.prototype = { + get done() { + // We need to set scanOperand to true here because the first thing + // might be a regexp. + return this.peek(true) === END; + }, - if (ch === '*') { + get token() { + return this.tokens[this.tokenIndex]; + }, + + match: function (tt, scanOperand) { + return this.get(scanOperand) === tt || this.unget(); + }, + + mustMatch: function (tt) { + if (!this.match(tt)) { + throw this.newSyntaxError("Missing " + + definitions.tokens[tt].toLowerCase()); + } + return this.token; + }, + + peek: function (scanOperand) { + var tt, next; + if (this.lookahead) { + next = this.tokens[(this.tokenIndex + this.lookahead) & 3]; + tt = (this.scanNewlines && next.lineno !== this.lineno) + ? NEWLINE + : next.type; + } else { + tt = this.get(scanOperand); + this.unget(); + } + return tt; + }, + + peekOnSameLine: function (scanOperand) { + this.scanNewlines = true; + var tt = this.peek(scanOperand); + this.scanNewlines = false; + return tt; + }, + + lastBlockComment: function() { + var length = this.blockComments.length; + return length ? this.blockComments[length - 1] : null; + }, + + // Eat comments and whitespace. + skip: function () { + var input = this.source; + this.blockComments = []; + for (;;) { + var ch = input[this.cursor++]; + var next = input[this.cursor]; + // handle \r, \r\n and (always preferable) \n + if (ch === '\r') { + // if the next character is \n, we don't care about this at all + if (next === '\n') continue; + + // otherwise, we want to consider this as a newline + ch = '\n'; + } + + if (ch === '\n' && !this.scanNewlines) { + this.lineno++; + } else if (ch === '/' && next === '*') { + var commentStart = ++this.cursor; + for (;;) { + ch = input[this.cursor++]; + if (ch === undefined) + throw this.newSyntaxError("Unterminated comment"); + + if (ch === '*') { + next = input[this.cursor]; + if (next === '/') { + var commentEnd = this.cursor - 1; + this.cursor++; + break; + } + } else if (ch === '\n') { + this.lineno++; + } + } + this.blockComments.push(input.substring(commentStart, commentEnd)); + } else if (ch === '/' && next === '/') { + this.cursor++; + for (;;) { + ch = input[this.cursor++]; next = input[this.cursor]; - if (next === '/') { - this.cursor++; + if (ch === undefined) + return; + + if (ch === '\r') { + // check for \r\n + if (next !== '\n') ch = '\n'; + } + + if (ch === '\n') { + if (this.scanNewlines) { + this.cursor--; + } else { + this.lineno++; + } break; } - } else if (ch === '\n') { - this.lineno++; } + } else if (!(ch in definitions.whitespace)) { + this.cursor--; + return; } - } else if (ch === '/' && next === '/') { + } + }, + + // Lex the exponential part of a number, if present. Return true iff an + // exponential part was found. + lexExponent: function() { + var input = this.source; + var next = input[this.cursor]; + if (next === 'e' || next === 'E') { this.cursor++; - for (;;) { + ch = input[this.cursor++]; + if (ch === '+' || ch === '-') ch = input[this.cursor++]; - if (ch === undefined) - return; - if (ch === '\n') { - this.lineno++; - break; - } - } - } else if (ch !== ' ' && ch !== '\t') { - this.cursor--; - return; - } - } - }, + if (ch < '0' || ch > '9') + throw this.newSyntaxError("Missing exponent"); - // Lex the exponential part of a number, if present. Return true iff an - // exponential part was found. - lexExponent: function() { - var input = this.source; - var next = input[this.cursor]; - if (next === 'e' || next === 'E') { - this.cursor++; - ch = input[this.cursor++]; - if (ch === '+' || ch === '-') - ch = input[this.cursor++]; - - if (ch < '0' || ch > '9') - throw this.newSyntaxError("Missing exponent"); - - do { - ch = input[this.cursor++]; - } while (ch >= '0' && ch <= '9'); - this.cursor--; - - return true; - } - - return false; - }, - - lexZeroNumber: function (ch) { - var token = this.token, input = this.source; - token.type = NUMBER; - - ch = input[this.cursor++]; - if (ch === '.') { - do { - ch = input[this.cursor++]; - } while (ch >= '0' && ch <= '9'); - this.cursor--; - - this.lexExponent(); - token.value = parseFloat(token.start, this.cursor); - } else if (ch === 'x' || ch === 'X') { - do { - ch = input[this.cursor++]; - } while ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || - (ch >= 'A' && ch <= 'F')); - this.cursor--; - - token.value = parseInt(input.substring(token.start, this.cursor)); - } else if (ch >= '0' && ch <= '7') { - do { - ch = input[this.cursor++]; - } while (ch >= '0' && ch <= '7'); - this.cursor--; - - token.value = parseInt(input.substring(token.start, this.cursor)); - } else { - this.cursor--; - this.lexExponent(); // 0E1, &c. - token.value = 0; - } - }, - - lexNumber: function (ch) { - var token = this.token, input = this.source; - token.type = NUMBER; - - var floating = false; - do { - ch = input[this.cursor++]; - if (ch === '.' && !floating) { - floating = true; - ch = input[this.cursor++]; - } - } while (ch >= '0' && ch <= '9'); - - this.cursor--; - - var exponent = this.lexExponent(); - floating = floating || exponent; - - var str = input.substring(token.start, this.cursor); - token.value = floating ? parseFloat(str) : parseInt(str); - }, - - lexDot: function (ch) { - var token = this.token, input = this.source; - var next = input[this.cursor]; - if (next >= '0' && next <= '9') { - do { - ch = input[this.cursor++]; - } while (ch >= '0' && ch <= '9'); - this.cursor--; - - this.lexExponent(); - - token.type = NUMBER; - token.value = parseFloat(token.start, this.cursor); - } else { - token.type = DOT; - token.assignOp = null; - token.value = '.'; - } - }, - - lexString: function (ch) { - var token = this.token, input = this.source; - token.type = STRING; - - var hasEscapes = false; - var delim = ch; - if (input.length <= this.cursor) - throw this.newSyntaxError("Unterminated string literal"); - while ((ch = input[this.cursor++]) !== delim) { - if (this.cursor == input.length) - throw this.newSyntaxError("Unterminated string literal"); - if (ch === '\\') { - hasEscapes = true; - if (++this.cursor == input.length) - throw this.newSyntaxError("Unterminated string literal"); - } - } - - token.value = hasEscapes - ? eval(input.substring(token.start, this.cursor)) - : input.substring(token.start + 1, this.cursor - 1); - }, - - lexRegExp: function (ch) { - var token = this.token, input = this.source; - token.type = REGEXP; - - do { - ch = input[this.cursor++]; - if (ch === '\\') { - this.cursor++; - } else if (ch === '[') { do { - if (ch === undefined) - throw this.newSyntaxError("Unterminated character class"); - - if (ch === '\\') - this.cursor++; - ch = input[this.cursor++]; - } while (ch !== ']'); - } else if (ch === undefined) { - throw this.newSyntaxError("Unterminated regex"); + } while (ch >= '0' && ch <= '9'); + this.cursor--; + + return true; } - } while (ch !== '/'); - do { + return false; + }, + + lexZeroNumber: function (ch) { + var token = this.token, input = this.source; + token.type = NUMBER; + ch = input[this.cursor++]; - } while (ch >= 'a' && ch <= 'z'); + if (ch === '.') { + do { + ch = input[this.cursor++]; + } while (ch >= '0' && ch <= '9'); + this.cursor--; - this.cursor--; + this.lexExponent(); + token.value = parseFloat(token.start, this.cursor); + } else if (ch === 'x' || ch === 'X') { + do { + ch = input[this.cursor++]; + } while ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || + (ch >= 'A' && ch <= 'F')); + this.cursor--; - token.value = eval(input.substring(token.start, this.cursor)); - }, + token.value = parseInt(input.substring(token.start, this.cursor)); + } else if (ch >= '0' && ch <= '7') { + do { + ch = input[this.cursor++]; + } while (ch >= '0' && ch <= '7'); + this.cursor--; - lexOp: function (ch) { - var token = this.token, input = this.source; + token.value = parseInt(input.substring(token.start, this.cursor)); + } else { + this.cursor--; + this.lexExponent(); // 0E1, &c. + token.value = 0; + } + }, - // A bit ugly, but it seems wasteful to write a trie lookup routine - // for only 3 characters... - var node = opTokens[ch]; - var next = input[this.cursor]; - if (next in node) { - node = node[next]; - this.cursor++; - next = input[this.cursor]; + lexNumber: function (ch) { + var token = this.token, input = this.source; + token.type = NUMBER; + + var floating = false; + do { + ch = input[this.cursor++]; + if (ch === '.' && !floating) { + floating = true; + ch = input[this.cursor++]; + } + } while (ch >= '0' && ch <= '9'); + + this.cursor--; + + var exponent = this.lexExponent(); + floating = floating || exponent; + + var str = input.substring(token.start, this.cursor); + token.value = floating ? parseFloat(str) : parseInt(str); + }, + + lexDot: function (ch) { + var token = this.token, input = this.source; + var next = input[this.cursor]; + if (next >= '0' && next <= '9') { + do { + ch = input[this.cursor++]; + } while (ch >= '0' && ch <= '9'); + this.cursor--; + + this.lexExponent(); + + token.type = NUMBER; + token.value = parseFloat(token.start, this.cursor); + } else { + token.type = DOT; + token.assignOp = null; + token.value = '.'; + } + }, + + lexString: function (ch) { + var token = this.token, input = this.source; + token.type = STRING; + + var hasEscapes = false; + var delim = ch; + if (input.length <= this.cursor) + throw this.newSyntaxError("Unterminated string literal"); + while ((ch = input[this.cursor++]) !== delim) { + if (this.cursor == input.length) + throw this.newSyntaxError("Unterminated string literal"); + if (ch === '\\') { + hasEscapes = true; + if (++this.cursor == input.length) + throw this.newSyntaxError("Unterminated string literal"); + } + } + + token.value = hasEscapes + ? eval(input.substring(token.start, this.cursor)) + : input.substring(token.start + 1, this.cursor - 1); + }, + + lexRegExp: function (ch) { + var token = this.token, input = this.source; + token.type = REGEXP; + + do { + ch = input[this.cursor++]; + if (ch === '\\') { + this.cursor++; + } else if (ch === '[') { + do { + if (ch === undefined) + throw this.newSyntaxError("Unterminated character class"); + + if (ch === '\\') + this.cursor++; + + ch = input[this.cursor++]; + } while (ch !== ']'); + } else if (ch === undefined) { + throw this.newSyntaxError("Unterminated regex"); + } + } while (ch !== '/'); + + do { + ch = input[this.cursor++]; + } while (ch >= 'a' && ch <= 'z'); + + this.cursor--; + + token.value = eval(input.substring(token.start, this.cursor)); + }, + + lexOp: function (ch) { + var token = this.token, input = this.source; + + // A bit ugly, but it seems wasteful to write a trie lookup routine + // for only 3 characters... + var node = opTokens[ch]; + var next = input[this.cursor]; if (next in node) { node = node[next]; this.cursor++; next = input[this.cursor]; + if (next in node) { + node = node[next]; + this.cursor++; + next = input[this.cursor]; + } } - } - var op = node.op; - if (definitions.assignOps[op] && input[this.cursor] === '=') { - this.cursor++; - token.type = ASSIGN; - token.assignOp = definitions.tokenIds[definitions.opTypeNames[op]]; - op += '='; - } else { - token.type = definitions.tokenIds[definitions.opTypeNames[op]]; - token.assignOp = null; - } + var op = node.op; + if (definitions.assignOps[op] && input[this.cursor] === '=') { + this.cursor++; + token.type = ASSIGN; + token.assignOp = definitions.tokenIds[definitions.opTypeNames[op]]; + op += '='; + } else { + token.type = definitions.tokenIds[definitions.opTypeNames[op]]; + token.assignOp = null; + } - token.value = op; - }, + token.value = op; + }, - // FIXME: Unicode escape sequences - // FIXME: Unicode identifiers - lexIdent: function (ch) { - var token = this.token, input = this.source; + // FIXME: Unicode escape sequences + lexIdent: function (ch) { + var token = this.token; + var id = ch; - do { - ch = input[this.cursor++]; - } while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || - (ch >= '0' && ch <= '9') || ch === '$' || ch === '_'); + while ((ch = this.getValidIdentifierChar(false)) !== null) { + id += ch; + } - this.cursor--; // Put the non-word character back. + token.type = definitions.keywords[id] || IDENTIFIER; + if (token.type in this.blackList) { + // banned keyword, this is an identifier + token.type = IDENTIFIER; + } + token.value = id; + }, - var id = input.substring(token.start, this.cursor); - token.type = definitions.keywords[id] || IDENTIFIER; - token.value = id; - }, + /* + * Tokenizer.get :: void -> token type + * + * Consume input *only* if there is no lookahead. + * Dispatch to the appropriate lexing function depending on the input. + */ + get: function (scanOperand) { + var token; + while (this.lookahead) { + --this.lookahead; + this.tokenIndex = (this.tokenIndex + 1) & 3; + token = this.tokens[this.tokenIndex]; + if (token.type !== NEWLINE || this.scanNewlines) + return token.type; + } + + this.skip(); - /* - * Tokenizer.get :: void -> token type - * - * Consume input *only* if there is no lookahead. - * Dispatch to the appropriate lexing function depending on the input. - */ - get: function (scanOperand) { - var token; - while (this.lookahead) { - --this.lookahead; this.tokenIndex = (this.tokenIndex + 1) & 3; token = this.tokens[this.tokenIndex]; - if (token.type !== NEWLINE || this.scanNewlines) - return token.type; - } + if (!token) + this.tokens[this.tokenIndex] = token = {}; - this.skip(); + var input = this.source; + if (this.cursor >= input.length) + return token.type = END; - this.tokenIndex = (this.tokenIndex + 1) & 3; - token = this.tokens[this.tokenIndex]; - if (!token) - this.tokens[this.tokenIndex] = token = {}; + token.start = this.cursor; + token.lineno = this.lineno; - var input = this.source; - if (this.cursor === input.length) - return token.type = END; + var ich = this.getValidIdentifierChar(true); + var ch = (ich === null) ? input[this.cursor++] : null; + if (ich !== null) { + this.lexIdent(ich); + } else if (scanOperand && ch === '/') { + this.lexRegExp(ch); + } else if (ch in opTokens) { + this.lexOp(ch); + } else if (ch === '.') { + this.lexDot(ch); + } else if (ch >= '1' && ch <= '9') { + this.lexNumber(ch); + } else if (ch === '0') { + this.lexZeroNumber(ch); + } else if (ch === '"' || ch === "'") { + this.lexString(ch); + } else if (this.scanNewlines && (ch === '\n' || ch === '\r')) { + // if this was a \r, look for \r\n + if (ch === '\r' && input[this.cursor] === '\n') this.cursor++; + token.type = NEWLINE; + token.value = '\n'; + this.lineno++; + } else { + throw this.newSyntaxError("Illegal token"); + } - token.start = this.cursor; - token.lineno = this.lineno; + token.end = this.cursor; + return token.type; + }, - var ch = input[this.cursor++]; - if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '$' || ch === '_') { - this.lexIdent(ch); - } else if (scanOperand && ch === '/') { - this.lexRegExp(ch); - } else if (ch in opTokens) { - this.lexOp(ch); - } else if (ch === '.') { - this.lexDot(ch); - } else if (ch >= '1' && ch <= '9') { - this.lexNumber(ch); - } else if (ch === '0') { - this.lexZeroNumber(ch); - } else if (ch === '"' || ch === "'") { - this.lexString(ch); - } else if (this.scanNewlines && ch === '\n') { - token.type = NEWLINE; - token.value = '\n'; - this.lineno++; - } else { - throw this.newSyntaxError("Illegal token"); - } + /* + * Tokenizer.unget :: void -> undefined + * + * Match depends on unget returning undefined. + */ + unget: function () { + if (++this.lookahead === 4) throw "PANIC: too much lookahead!"; + this.tokenIndex = (this.tokenIndex - 1) & 3; + }, - token.end = this.cursor; - return token.type; - }, + newSyntaxError: function (m) { + m = (this.filename ? this.filename + ":" : "") + this.lineno + ": " + m; + var e = new SyntaxError(m, this.filename, this.lineno); + e.source = this.source; + e.cursor = this.lookahead + ? this.tokens[(this.tokenIndex + this.lookahead) & 3].start + : this.cursor; + return e; + }, - /* - * Tokenizer.unget :: void -> undefined - * - * Match depends on unget returning undefined. - */ - unget: function () { - if (++this.lookahead === 4) throw "PANIC: too much lookahead!"; - this.tokenIndex = (this.tokenIndex - 1) & 3; - }, + /* Gets a single valid identifier char from the input stream, or null + * if there is none. + * Since JavaScript provides no convenient way to determine if a + * character is in a particular Unicode category, we use + * metacircularity to accomplish this (oh yeaaaah!) */ + getValidIdentifierChar: function(first) { + var input = this.source; + if (this.cursor >= input.length) return null; + var ch = input[this.cursor]; - newSyntaxError: function (m) { - var e = new SyntaxError(m, this.filename, this.lineno); - e.source = this.source; - e.lineno = this.lineno; - e.cursor = this.lookahead - ? this.tokens[(this.tokenIndex + this.lookahead) & 3].start - : this.cursor; - return e; - }, -}; + // first check for \u escapes + if (ch === '\\' && input[this.cursor+1] === 'u') { + // get the character value + try { + ch = String.fromCharCode(parseInt( + input.substring(this.cursor + 2, this.cursor + 6), + 16)); + } catch (ex) { + return null; + } + this.cursor += 5; + } -exports.Tokenizer = Tokenizer; + // check directly for ASCII + if (ch <= "\u007F") { + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '$' || ch === '_' || + (!first && (ch >= '0' && ch <= '9'))) { + this.cursor++; + return ch; + } + return null; + } + + // create an object to test this in + var x = {}; + x["x"+ch] = true; + x[ch] = true; + + // then use eval to determine if it's a valid character + var valid = false; + try { + valid = (Function("x", "return (x." + (first?"":"x") + ch + ");")(x) === true); + } catch (ex) {} + if (valid) this.cursor++; + return (valid ? ch : null); + }, + }; -}); \ No newline at end of file + + return { Tokenizer: Tokenizer }; + +}); diff --git a/lib/ace/narcissus/jsparse.js b/lib/ace/narcissus/jsparse.js index 46b85ff3..3855b182 100644 --- a/lib/ace/narcissus/jsparse.js +++ b/lib/ace/narcissus/jsparse.js @@ -51,1382 +51,1859 @@ define(function(require, exports, module) { -var lexer = require("ace/narcissus/jslex"); -var definitions = require("ace/narcissus/jsdefs"); + var lexer = require("ace/narcissus/jslex"); + var definitions = require("ace/narcissus/jsdefs"); -const StringMap = definitions.StringMap; -const Stack = definitions.Stack; + const StringMap = definitions.StringMap; + const Stack = definitions.Stack; -// Set constants in the local scope. -eval(definitions.consts); + // Set constants in the local scope. + eval(definitions.consts); -/* - * pushDestructuringVarDecls :: (node, hoisting node) -> void - * - * Recursively add all destructured declarations to varDecls. - */ -function pushDestructuringVarDecls(n, s) { - for (var i in n) { - var sub = n[i]; - if (sub.type === IDENTIFIER) { - s.varDecls.push(sub); - } else { - pushDestructuringVarDecls(sub, s); - } - } -} + // Banned statement types by language version. + const blackLists = { 160: {}, 185: {}, harmony: {} }; + blackLists[160][IMPORT] = true; + blackLists[160][EXPORT] = true; + blackLists[160][LET] = true; + blackLists[160][MODULE] = true; + blackLists[160][YIELD] = true; + blackLists[185][IMPORT] = true; + blackLists[185][EXPORT] = true; + blackLists[185][MODULE] = true; + blackLists.harmony[WITH] = true; -// NESTING_TOP: top-level -// NESTING_SHALLOW: nested within static forms such as { ... } or labeled statement -// NESTING_DEEP: nested within dynamic forms such as if, loops, etc. -const NESTING_TOP = 0, NESTING_SHALLOW = 1, NESTING_DEEP = 2; - -function StaticContext(parentScript, parentBlock, inFunction, inForLoopInit, nesting) { - this.parentScript = parentScript; - this.parentBlock = parentBlock; - this.inFunction = inFunction; - this.inForLoopInit = inForLoopInit; - this.nesting = nesting; - this.allLabels = new Stack(); - this.currentLabels = new Stack(); - this.labeledTargets = new Stack(); - this.defaultTarget = null; - definitions.options.ecma3OnlyMode && (this.ecma3OnlyMode = true); - definitions.options.parenFreeMode && (this.parenFreeMode = true); -} - -StaticContext.prototype = { - ecma3OnlyMode: false, - parenFreeMode: false, - // non-destructive update via prototype extension - update: function(ext) { - var desc = {}; - for (var key in ext) { - desc[key] = { - value: ext[key], - writable: true, - enumerable: true, - configurable: true + /* + * pushDestructuringVarDecls :: (node, hoisting node) -> void + * + * Recursively add all destructured declarations to varDecls. + */ + function pushDestructuringVarDecls(n, s) { + for (var i in n) { + var sub = n[i]; + if (sub.type === IDENTIFIER) { + s.varDecls.push(sub); + } else { + pushDestructuringVarDecls(sub, s); } } - return Object.create(this, desc); - }, - pushLabel: function(label) { - return this.update({ currentLabels: this.currentLabels.push(label), - allLabels: this.allLabels.push(label) }); - }, - pushTarget: function(target) { - var isDefaultTarget = target.isLoop || target.type === SWITCH; - - if (this.currentLabels.isEmpty()) { - return isDefaultTarget - ? this.update({ defaultTarget: target }) - : this; - } - - target.labels = new StringMap(); - this.currentLabels.forEach(function(label) { - target.labels.set(label, true); - }); - return this.update({ currentLabels: new Stack(), - labeledTargets: this.labeledTargets.push(target), - defaultTarget: isDefaultTarget - ? target - : this.defaultTarget }); - }, - nest: function(atLeast) { - var nesting = Math.max(this.nesting, atLeast); - return (nesting !== this.nesting) - ? this.update({ nesting: nesting }) - : this; - } -}; - -/* - * Script :: (tokenizer, boolean) -> node - * - * Parses the toplevel and function bodies. - */ -function Script(t, inFunction) { - var n = new Node(t, scriptInit()); - var x = new StaticContext(n, n, inFunction, false, NESTING_TOP); - Statements(t, x, n); - return n; -} - -// We extend Array slightly with a top-of-stack method. -definitions.defineProperty(Array.prototype, "top", - function() { - return this.length && this[this.length-1]; - }, false, false, true); - -/* - * Node :: (tokenizer, optional init object) -> node - */ -function Node(t, init) { - var token = t.token; - if (token) { - // If init.type exists it will override token.type. - this.type = token.type; - this.value = token.value; - this.lineno = token.lineno; - - // Start and end are file positions for error handling. - this.start = token.start; - this.end = token.end; - } else { - this.lineno = t.lineno; } - // Node uses a tokenizer for debugging (getSource, filename getter). - this.tokenizer = t; - this.children = []; - - for (var prop in init) - this[prop] = init[prop]; -} - -var Np = Node.prototype = {}; -Np.constructor = Node; -Np.toSource = Object.prototype.toSource; - -// Always use push to add operands to an expression, to update start and end. -Np.push = function (kid) { - // kid can be null e.g. [1, , 2]. - if (kid !== null) { - if (kid.start < this.start) - this.start = kid.start; - if (this.end < kid.end) - this.end = kid.end; + function StaticContext(parentScript, parentBlock, inModule, inFunction) { + this.parentScript = parentScript; + this.parentBlock = parentBlock || parentScript; + this.inModule = inModule || false; + this.inFunction = inFunction || false; + this.inForLoopInit = false; + this.topLevel = true; + this.allLabels = new Stack(); + this.currentLabels = new Stack(); + this.labeledTargets = new Stack(); + this.defaultLoopTarget = null; + this.defaultTarget = null; + this.blackList = blackLists[Narcissus.options.version]; + Narcissus.options.ecma3OnlyMode && (this.ecma3OnlyMode = true); + Narcissus.options.parenFreeMode && (this.parenFreeMode = true); } - return this.children.push(kid); -} -Node.indentLevel = 0; + StaticContext.prototype = { + ecma3OnlyMode: false, + parenFreeMode: false, + // non-destructive update via prototype extension + update: function(ext) { + var desc = {}; + for (var key in ext) { + desc[key] = { + value: ext[key], + writable: true, + enumerable: true, + configurable: true + } + } + return Object.create(this, desc); + }, + pushLabel: function(label) { + return this.update({ currentLabels: this.currentLabels.push(label), + allLabels: this.allLabels.push(label) }); + }, + pushTarget: function(target) { + var isDefaultLoopTarget = target.isLoop; + var isDefaultTarget = isDefaultLoopTarget || target.type === SWITCH; -function tokenString(tt) { - var t = definitions.tokens[tt]; - return /^\W/.test(t) ? definitions.opTypeNames[t] : t.toUpperCase(); -} + if (this.currentLabels.isEmpty()) { + if (isDefaultLoopTarget) this.update({ defaultLoopTarget: target }); + if (isDefaultTarget) this.update({ defaultTarget: target }); + return this; + } -Np.toString = function () { - var a = []; - for (var i in this) { - if (this.hasOwnProperty(i) && i !== 'type' && i !== 'target') - a.push({id: i, value: this[i]}); - } - a.sort(function (a,b) { return (a.id < b.id) ? -1 : 1; }); - const INDENTATION = " "; - var n = ++Node.indentLevel; - var s = "{\n" + INDENTATION.repeat(n) + "type: " + tokenString(this.type); - for (i = 0; i < a.length; i++) - s += ",\n" + INDENTATION.repeat(n) + a[i].id + ": " + a[i].value; - n = --Node.indentLevel; - s += "\n" + INDENTATION.repeat(n) + "}"; - return s; -} - -Np.getSource = function () { - return this.tokenizer.source.slice(this.start, this.end); -}; - -/* - * Helper init objects for common nodes. - */ - -const LOOP_INIT = { isLoop: true }; - -function blockInit() { - return { type: BLOCK, varDecls: [] }; -} - -function scriptInit() { - return { type: SCRIPT, - funDecls: [], - varDecls: [], - modDecls: [], - impDecls: [], - expDecls: [], - loadDeps: [], - hasEmptyReturn: false, - hasReturnWithValue: false, - isGenerator: false }; -} - -definitions.defineGetter(Np, "filename", - function() { - return this.tokenizer.filename; - }); - -definitions.defineProperty(String.prototype, "repeat", - function(n) { - var s = "", t = this + s; - while (--n >= 0) - s += t; - return s; - }, false, false, true); - -function MaybeLeftParen(t, x) { - if (x.parenFreeMode) - return t.match(LEFT_PAREN) ? LEFT_PAREN : END; - return t.mustMatch(LEFT_PAREN).type; -} - -function MaybeRightParen(t, p) { - if (p === LEFT_PAREN) - t.mustMatch(RIGHT_PAREN); -} - -/* - * Statements :: (tokenizer, compiler context, node) -> void - * - * Parses a sequence of Statements. - */ -function Statements(t, x, n) { - try { - while (!t.done && t.peek(true) !== RIGHT_CURLY) - n.push(Statement(t, x)); - } catch (e) { - if (t.done) - t.unexpectedEOF = true; - throw e; - } -} - -function Block(t, x) { - t.mustMatch(LEFT_CURLY); - var n = new Node(t, blockInit()); - Statements(t, x.update({ parentBlock: n }).pushTarget(n), n); - t.mustMatch(RIGHT_CURLY); - return n; -} - -const DECLARED_FORM = 0, EXPRESSED_FORM = 1, STATEMENT_FORM = 2; - -/* - * Statement :: (tokenizer, compiler context) -> node - * - * Parses a Statement. - */ -function Statement(t, x) { - var i, label, n, n2, p, c, ss, tt = t.get(true), tt2, x2, x3; - - // Cases for statements ending in a right curly return early, avoiding the - // common semicolon insertion magic after this switch. - switch (tt) { - case FUNCTION: - // DECLARED_FORM extends funDecls of x, STATEMENT_FORM doesn't. - return FunctionDefinition(t, x, true, - (x.nesting !== NESTING_TOP) - ? STATEMENT_FORM - : DECLARED_FORM); - - case LEFT_CURLY: - n = new Node(t, blockInit()); - Statements(t, x.update({ parentBlock: n }).pushTarget(n).nest(NESTING_SHALLOW), n); - t.mustMatch(RIGHT_CURLY); - return n; - - case IF: - n = new Node(t); - n.condition = HeadExpression(t, x); - x2 = x.pushTarget(n).nest(NESTING_DEEP); - n.thenPart = Statement(t, x2); - n.elsePart = t.match(ELSE) ? Statement(t, x2) : null; - return n; - - case SWITCH: - // This allows CASEs after a DEFAULT, which is in the standard. - n = new Node(t, { cases: [], defaultIndex: -1 }); - n.discriminant = HeadExpression(t, x); - x2 = x.pushTarget(n).nest(NESTING_DEEP); - t.mustMatch(LEFT_CURLY); - while ((tt = t.get()) !== RIGHT_CURLY) { - switch (tt) { - case DEFAULT: - if (n.defaultIndex >= 0) - throw t.newSyntaxError("More than one switch default"); + target.labels = new StringMap(); + this.currentLabels.forEach(function(label) { + target.labels.set(label, true); + }); + return this.update({ currentLabels: new Stack(), + labeledTargets: this.labeledTargets.push(target), + defaultLoopTarget: isDefaultLoopTarget + ? target + : this.defaultLoopTarget, + defaultTarget: isDefaultTarget + ? target + : this.defaultTarget }); + }, + nest: function() { + return this.topLevel ? this.update({ topLevel: false }) : this; + }, + allow: function(type) { + switch (type) { + case EXPORT: + if (!this.inModule || this.inFunction || !this.topLevel) + return false; // FALL THROUGH - case CASE: - n2 = new Node(t); - if (tt === DEFAULT) - n.defaultIndex = n.cases.length; - else - n2.caseLabel = Expression(t, x2, COLON); - break; + + case IMPORT: + return !this.inFunction && this.topLevel; + + case MODULE: + return !this.inFunction && this.topLevel; default: - throw t.newSyntaxError("Invalid switch case"); + return true; } - t.mustMatch(COLON); - n2.statements = new Node(t, blockInit()); - while ((tt=t.peek(true)) !== CASE && tt !== DEFAULT && - tt !== RIGHT_CURLY) - n2.statements.push(Statement(t, x2)); - n.cases.push(n2); } + }; + + /* + * Script :: (tokenizer, boolean, boolean) -> node + * + * Parses the toplevel and module/function bodies. + */ + function Script(t, inModule, inFunction) { + var n = new Node(t, scriptInit()); + Statements(t, new StaticContext(n, n, inModule, inFunction), n); return n; + } - case FOR: - n = new Node(t, LOOP_INIT); - if (t.match(IDENTIFIER)) { - if (t.token.value === "each") - n.isEach = true; - else - t.unget(); - } - if (!x.parenFreeMode) - t.mustMatch(LEFT_PAREN); - x2 = x.pushTarget(n).nest(NESTING_DEEP); - x3 = x.update({ inForLoopInit: true }); - if ((tt = t.peek()) !== SEMICOLON) { - if (tt === VAR || tt === CONST) { - t.get(); - n2 = Variables(t, x3); - } else if (tt === LET) { - t.get(); - if (t.peek() === LEFT_PAREN) { - n2 = LetBlock(t, x3, false); - } else { - // Let in for head, we need to add an implicit block - // around the rest of the for. - x3.parentBlock = n; - n.varDecls = []; - n2 = Variables(t, x3); - } - } else { - n2 = Expression(t, x3); - } - } - if (n2 && t.match(IN)) { - n.type = FOR_IN; - n.object = Expression(t, x3); - if (n2.type === VAR || n2.type === LET) { - c = n2.children; + // We extend Array slightly with a top-of-stack method. + definitions.defineProperty(Array.prototype, "top", + function() { + return this.length && this[this.length-1]; + }, false, false, true); - // Destructuring turns one decl into multiples, so either - // there must be only one destructuring or only one - // decl. - if (c.length !== 1 && n2.destructurings.length !== 1) { - throw new SyntaxError("Invalid for..in left-hand side", - t.filename, n2.lineno); - } - if (n2.destructurings.length > 0) { - n.iterator = n2.destructurings[0]; - } else { - n.iterator = c[0]; - } - n.varDecl = n2; - } else { - if (n2.type === ARRAY_INIT || n2.type === OBJECT_INIT) { - n2.destructuredNames = checkDestructuring(t, x3, n2); - } - n.iterator = n2; - } + /* + * Node :: (tokenizer, optional init object) -> node + */ + function Node(t, init) { + var token = t.token; + if (token) { + // If init.type exists it will override token.type. + this.type = token.type; + this.value = token.value; + this.lineno = token.lineno; + + // Start and end are file positions for error handling. + this.start = token.start; + this.end = token.end; } else { - n.setup = n2; - t.mustMatch(SEMICOLON); - if (n.isEach) - throw t.newSyntaxError("Invalid for each..in loop"); - n.condition = (t.peek() === SEMICOLON) - ? null - : Expression(t, x3); - t.mustMatch(SEMICOLON); - tt2 = t.peek(); - n.update = (x.parenFreeMode - ? tt2 === LEFT_CURLY || definitions.isStatementStartCode[tt2] - : tt2 === RIGHT_PAREN) - ? null - : Expression(t, x3); + this.lineno = t.lineno; } - if (!x.parenFreeMode) + + // Node uses a tokenizer for debugging (getSource, filename getter). + this.tokenizer = t; + this.children = []; + + for (var prop in init) + this[prop] = init[prop]; + } + + /* + * SyntheticNode :: (tokenizer, optional init object) -> node + */ + function SyntheticNode(t, init) { + // print("SYNTHETIC NODE"); + // if (init.type === COMMA) { + // print("SYNTHETIC COMMA"); + // print(init); + // } + this.tokenizer = t; + this.children = []; + for (var prop in init) + this[prop] = init[prop]; + this.synthetic = true; + } + + var Np = Node.prototype = SyntheticNode.prototype = {}; + Np.constructor = Node; + + const TO_SOURCE_SKIP = { + type: true, + value: true, + lineno: true, + start: true, + end: true, + tokenizer: true, + assignOp: true + }; + function unevalableConst(code) { + var token = definitions.tokens[code]; + var constName = definitions.opTypeNames.hasOwnProperty(token) + ? definitions.opTypeNames[token] + : token in definitions.keywords + ? token.toUpperCase() + : token; + return { toSource: function() { return constName } }; + } + Np.toSource = function toSource() { + var mock = {}; + var self = this; + mock.type = unevalableConst(this.type); + if ("value" in this) + mock.value = this.value; + if ("lineno" in this) + mock.lineno = this.lineno; + if ("start" in this) + mock.start = this.start; + if ("end" in this) + mock.end = this.end; + if (this.assignOp) + mock.assignOp = unevalableConst(this.assignOp); + for (var key in this) { + if (this.hasOwnProperty(key) && !(key in TO_SOURCE_SKIP)) + mock[key] = this[key]; + } + return mock.toSource(); + }; + + // Always use push to add operands to an expression, to update start and end. + Np.push = function (kid) { + // kid can be null e.g. [1, , 2]. + if (kid !== null) { + if (kid.start < this.start) + this.start = kid.start; + if (this.end < kid.end) + this.end = kid.end; + } + return this.children.push(kid); + } + + Node.indentLevel = 0; + + function tokenString(tt) { + var t = definitions.tokens[tt]; + return /^\W/.test(t) ? definitions.opTypeNames[t] : t.toUpperCase(); + } + + Np.toString = function () { + var a = []; + for (var i in this) { + if (this.hasOwnProperty(i) && i !== 'type' && i !== 'target') + a.push({id: i, value: this[i]}); + } + a.sort(function (a,b) { return (a.id < b.id) ? -1 : 1; }); + const INDENTATION = " "; + var n = ++Node.indentLevel; + var s = "{\n" + INDENTATION.repeat(n) + "type: " + tokenString(this.type); + for (i = 0; i < a.length; i++) + s += ",\n" + INDENTATION.repeat(n) + a[i].id + ": " + a[i].value; + n = --Node.indentLevel; + s += "\n" + INDENTATION.repeat(n) + "}"; + return s; + } + + Np.getSource = function () { + return this.tokenizer.source.slice(this.start, this.end); + }; + + /* + * Helper init objects for common nodes. + */ + + const LOOP_INIT = { isLoop: true }; + + function blockInit() { + return { type: BLOCK, varDecls: [] }; + } + + function scriptInit() { + return { type: SCRIPT, + funDecls: [], + varDecls: [], + modDefns: new StringMap(), + modAssns: new StringMap(), + modDecls: new StringMap(), + modLoads: new StringMap(), + impDecls: [], + expDecls: [], + exports: new StringMap(), + hasEmptyReturn: false, + hasReturnWithValue: false, + isGenerator: false }; + } + + definitions.defineGetter(Np, "filename", + function() { + return this.tokenizer.filename; + }); + + definitions.defineGetter(Np, "length", + function() { + throw new Error("Node.prototype.length is gone; " + + "use n.children.length instead"); + }); + + definitions.defineProperty(String.prototype, "repeat", + function(n) { + var s = "", t = this + s; + while (--n >= 0) + s += t; + return s; + }, false, false, true); + + function MaybeLeftParen(t, x) { + if (x.parenFreeMode) + return t.match(LEFT_PAREN) ? LEFT_PAREN : END; + return t.mustMatch(LEFT_PAREN).type; + } + + function MaybeRightParen(t, p) { + if (p === LEFT_PAREN) t.mustMatch(RIGHT_PAREN); - n.body = Statement(t, x2); - return n; + } - case WHILE: - n = new Node(t, { isLoop: true }); - n.condition = HeadExpression(t, x); - n.body = Statement(t, x.pushTarget(n).nest(NESTING_DEEP)); - return n; - - case DO: - n = new Node(t, { isLoop: true }); - n.body = Statement(t, x.pushTarget(n).nest(NESTING_DEEP)); - t.mustMatch(WHILE); - n.condition = HeadExpression(t, x); - if (!x.ecmaStrictMode) { - //