Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

updated Narcissus to newer version to fix newline bug

  • Loading branch information...
commit d89c6837cc7606fdac2bd00f696e5d1682f0e186 1 parent 616d01c
@sherhut sherhut authored
View
49 jslib/jit/narcissus/jsdecomp.js
@@ -20,6 +20,9 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
+ * Shu-Yu Guo <shu@rfrn.org>
+ * Bruno Jouhier
+ * Gregor Richards
*
* 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
@@ -43,6 +46,7 @@
Narcissus.decompiler = (function() {
+ const lexer = Narcissus.lexer;
const parser = Narcissus.parser;
const definitions = Narcissus.definitions;
const tokens = definitions.tokens;
@@ -72,13 +76,33 @@ Narcissus.decompiler = (function() {
return isBlock(n) && n.children.length > 0;
}
+ function nodeStrEscape(str) {
+ return str.replace(/\\/g, "\\\\")
+ .replace(/"/g, "\\\"")
+ .replace(/\n/g, "\\n")
+ .replace(/\r/g, "\\r")
+ .replace(/</g, "\\u003C")
+ .replace(/>/g, "\\u003E");
+ }
+
function nodeStr(n) {
- return '"' +
- n.value.replace(/\\/g, "\\\\")
- .replace(/"/g, "\\\"")
- .replace(/\n/g, "\\n")
- .replace(/\r/g, "\\r") +
- '"';
+ if (/[\u0000-\u001F\u0080-\uFFFF]/.test(n.value)) {
+ // use the convoluted algorithm to avoid broken low/high characters
+ var str = "";
+ for (var i = 0; i < n.value.length; i++) {
+ var c = n.value[i];
+ if (c <= "\x1F" || c >= "\x80") {
+ var cc = c.charCodeAt(0).toString(16);
+ while (cc.length < 4) cc = "0" + cc;
+ str += "\\u" + cc;
+ } else {
+ str += nodeStrEscape(c);
+ }
+ }
+ return '"' + str + '"';
+ }
+
+ return '"' + nodeStrEscape(n.value) + '"';
}
function pp(n, d, inLetHead) {
@@ -266,7 +290,7 @@ Narcissus.decompiler = (function() {
case YIELD:
p += "yield";
- if (n.value.type)
+ if (n.value)
p += " " + pp(n.value, d);
break;
@@ -463,8 +487,15 @@ Narcissus.decompiler = (function() {
if (t.type === PROPERTY_INIT) {
var tc = t.children;
var l;
- // see if the left needs to be a string
- if (/[^A-Za-z0-9_$]/.test(tc[0].value)) {
+ /*
+ * See if the left needs to be quoted.
+ *
+ * N.B. If negative numeral prop names ever get converted
+ * internally to numbers by the parser, we need to quote
+ * those also.
+ */
+ var propName = tc[0].value;
+ if (typeof propName === "string" && !lexer.isIdentifier(propName)) {
l = nodeStr(tc[0]);
} else {
l = pp(tc[0], d);
View
361 jslib/jit/narcissus/jsdefs.js
@@ -55,13 +55,26 @@
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() {
+Narcissus.definitions = (function(hostGlobal) {
var tokens = [
// End of source.
@@ -102,10 +115,10 @@ Narcissus.definitions = (function() {
"break",
"case", "catch", "const", "continue",
"debugger", "default", "delete", "do",
- "else",
+ "else", "export",
"false", "finally", "for", "function",
- "if", "in", "instanceof",
- "let",
+ "if", "import", "in", "instanceof",
+ "let", "module",
"new", "null",
"return",
"switch",
@@ -129,6 +142,23 @@ Narcissus.definitions = (function() {
"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
@@ -182,7 +212,7 @@ Narcissus.definitions = (function() {
var tokenIds = {};
// Building up a string to be eval'd in different contexts.
- var consts = "const ";
+ var consts = Narcissus.hostSupportsEvalConst ? "const " : "var ";
for (var i = 0, j = tokens.length; i < j; i++) {
if (i > 0)
consts += ", ";
@@ -217,6 +247,27 @@ Narcissus.definitions = (function() {
{ 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,
@@ -229,6 +280,39 @@ Narcissus.definitions = (function() {
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))
@@ -237,6 +321,17 @@ Narcissus.definitions = (function() {
}
}
+ 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))
@@ -244,6 +339,125 @@ Narcissus.definitions = (function() {
return map;
}
+ function blacklistHandler(target, blacklist) {
+ var mask = Object.create(null, {});
+ var redirect = Dict.create(blacklist).mapObject(function(name) { return mask; });
+ return mixinHandler(redirect, target);
+ }
+
+ function whitelistHandler(target, whitelist) {
+ var catchall = Object.create(null, {});
+ var redirect = Dict.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
@@ -293,17 +507,30 @@ Narcissus.definitions = (function() {
};
}
- // default function used when looking for a property in the global object
- function noPropFound() { return undefined; }
-
var hasOwnProperty = ({}).hasOwnProperty;
- function StringMap() {
- this.table = Object.create(null, {});
- this.size = 0;
+ function hasOwn(obj, name) {
+ return hasOwnProperty.call(obj, name);
+ }
+
+ function Dict(table, size) {
+ this.table = table || Object.create(null, {});
+ this.size = size || 0;
}
- StringMap.prototype = {
+ Dict.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 Dict(init, size);
+ };
+
+ Dict.prototype = {
has: function(x) { return hasOwnProperty.call(this.table, x); },
set: function(x, v) {
if (!hasOwnProperty.call(this.table, x))
@@ -323,7 +550,83 @@ Narcissus.definitions = (function() {
for (var key in table)
f.call(this, key, table[key]);
},
- toString: function() { return "[object StringMap]" }
+ 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 Dict(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 Dict(table, this.size);
+ },
+ keys: function() {
+ return Object.keys(this.table);
+ },
+ toString: function() { return "[object Dict]" }
+ };
+
+ // shim for ES6 WeakMap with poor asymptotics
+ function WeakMap(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();
+ }
+
+ WeakMap.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 });
+ },
+ "delete": function(x) {
+ var a = this.array;
+ searchMap(this, x,
+ function(pair, i) { a.splice(i, 1) },
+ function() { });
+ },
+ toString: function() { return "[object WeakMap]" }
};
// non-destructive stack
@@ -360,8 +663,26 @@ Narcissus.definitions = (function() {
}
};
+ if (!Array.prototype.copy) {
+ defineProperty(Array.prototype, "copy",
+ function() {
+ var result = [];
+ for (var i = 0, n = this.length; i < n; i++)
+ result[i] = this[i];
+ return result;
+ }, false, false, true);
+ }
+
+ if (!Array.prototype.top) {
+ defineProperty(Array.prototype, "top",
+ function() {
+ return this.length && this[this.length-1];
+ }, false, false, true);
+ }
+
return {
tokens: tokens,
+ whitespace: whitespace,
opTypeNames: opTypeNames,
keywords: keywords,
isStatementStartCode: isStatementStartCode,
@@ -369,11 +690,19 @@ Narcissus.definitions = (function() {
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,
- noPropFound: noPropFound,
- StringMap: StringMap,
+ Dict: Dict,
+ WeakMap: hostGlobal.WeakMap || WeakMap,
Stack: Stack
};
-}());
+}(this));
View
188 jslib/jit/narcissus/jslex.js
@@ -55,6 +55,13 @@ Narcissus.lexer = (function() {
// Set constants in the local scope.
eval(definitions.consts);
+ // 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;
+
// Build up a trie of operator tokens.
var opTokens = {};
for (var op in definitions.opTypeNames) {
@@ -72,6 +79,53 @@ Narcissus.lexer = (function() {
}
/*
+ * 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!)
+ */
+ function isValidIdentifierChar(ch, first) {
+ // check directly for ASCII
+ if (ch <= "\u007F") {
+ if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '$' || ch === '_' ||
+ (!first && (ch >= '0' && ch <= '9'))) {
+ return true;
+ }
+ return false;
+ }
+
+ // 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) {}
+
+ return valid;
+ }
+
+ function isIdentifier(str) {
+ if (typeof str !== "string")
+ return false;
+
+ if (str.length === 0)
+ return false;
+
+ if (!isValidIdentifierChar(str[0], true))
+ return false;
+
+ for (var i = 1; i < str.length; i++) {
+ if (!isValidIdentifierChar(str[i], false))
+ return false;
+ }
+
+ return true;
+ }
+
+ /*
* Tokenizer :: (source, filename, line number) -> Tokenizer
*/
function Tokenizer(s, f, l) {
@@ -84,6 +138,8 @@ Narcissus.lexer = (function() {
this.unexpectedEOF = false;
this.filename = f || "";
this.lineno = l || 1;
+ this.blackList = blackLists[Narcissus.options.version];
+ this.blockComments = null;
}
Tokenizer.prototype = {
@@ -97,12 +153,12 @@ Narcissus.lexer = (function() {
return this.tokens[this.tokenIndex];
},
- match: function (tt, scanOperand) {
- return this.get(scanOperand) === tt || this.unget();
+ match: function (tt, scanOperand, keywordIsName) {
+ return this.get(scanOperand, keywordIsName) === tt || this.unget();
},
- mustMatch: function (tt) {
- if (!this.match(tt)) {
+ mustMatch: function (tt, keywordIsName) {
+ if (!this.match(tt, false, keywordIsName)) {
throw this.newSyntaxError("Missing " +
definitions.tokens[tt].toLowerCase());
}
@@ -130,16 +186,31 @@ Narcissus.lexer = (function() {
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 === '*') {
- this.cursor++;
+ var commentStart = ++this.cursor;
for (;;) {
ch = input[this.cursor++];
if (ch === undefined)
@@ -148,6 +219,7 @@ Narcissus.lexer = (function() {
if (ch === '*') {
next = input[this.cursor];
if (next === '/') {
+ var commentEnd = this.cursor - 1;
this.cursor++;
break;
}
@@ -155,19 +227,33 @@ Narcissus.lexer = (function() {
this.lineno++;
}
}
- } else if (ch === '/' && next === '/') {
+ this.blockComments.push(input.substring(commentStart, commentEnd));
+ } else if ((ch === '/' && next === '/') ||
+ (Narcissus.options.allowHTMLComments && ch === '<' && next === '!' &&
+ input[this.cursor + 1] === '-' && input[this.cursor + 2] === '-' &&
+ (this.cursor += 2))) {
this.cursor++;
for (;;) {
ch = input[this.cursor++];
+ next = input[this.cursor];
if (ch === undefined)
return;
+ if (ch === '\r') {
+ // check for \r\n
+ if (next !== '\n') ch = '\n';
+ }
+
if (ch === '\n') {
- this.lineno++;
+ if (this.scanNewlines) {
+ this.cursor--;
+ } else {
+ this.lineno++;
+ }
break;
}
}
- } else if (ch !== ' ' && ch !== '\t') {
+ } else if (!(ch in definitions.whitespace)) {
this.cursor--;
return;
}
@@ -211,8 +297,8 @@ Narcissus.lexer = (function() {
this.cursor--;
this.lexExponent();
- var str = input.substring(token.start, this.cursor);
- token.value = parseFloat(str);
+ token.value = parseFloat(
+ input.substring(token.start, this.cursor));
} else if (ch === 'x' || ch === 'X') {
do {
ch = input[this.cursor++];
@@ -269,8 +355,8 @@ Narcissus.lexer = (function() {
this.lexExponent();
token.type = NUMBER;
- var str = input.substring(token.start, this.cursor);
- token.value = parseFloat(str);
+ token.value = parseFloat(
+ input.substring(token.start, this.cursor));
} else {
token.type = DOT;
token.assignOp = null;
@@ -284,6 +370,8 @@ Narcissus.lexer = (function() {
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");
@@ -364,29 +452,32 @@ Narcissus.lexer = (function() {
},
// FIXME: Unicode escape sequences
- // FIXME: Unicode identifiers
- lexIdent: function (ch) {
- var token = this.token, input = this.source;
-
- do {
- ch = input[this.cursor++];
- } while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
- (ch >= '0' && ch <= '9') || ch === '$' || ch === '_');
+ lexIdent: function (ch, keywordIsName) {
+ var token = this.token;
+ var id = ch;
- this.cursor--; // Put the non-word character back.
+ while ((ch = this.getValidIdentifierChar(false)) !== null) {
+ id += ch;
+ }
- var id = input.substring(token.start, this.cursor);
- token.type = definitions.keywords[id] || IDENTIFIER;
+ token.type = IDENTIFIER;
token.value = id;
+
+ if (keywordIsName)
+ return;
+
+ var kw = definitions.keywords[id];
+ if (kw && !(kw in this.blackList))
+ token.type = kw;
},
/*
- * Tokenizer.get :: void -> token type
+ * Tokenizer.get :: [boolean[, boolean]] -> token type
*
* Consume input *only* if there is no lookahead.
* Dispatch to the appropriate lexing function depending on the input.
*/
- get: function (scanOperand) {
+ get: function (scanOperand, keywordIsName) {
var token;
while (this.lookahead) {
--this.lookahead;
@@ -404,15 +495,16 @@ Narcissus.lexer = (function() {
this.tokens[this.tokenIndex] = token = {};
var input = this.source;
- if (this.cursor === input.length)
+ if (this.cursor >= input.length)
return token.type = END;
token.start = this.cursor;
token.lineno = this.lineno;
- var ch = input[this.cursor++];
- if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '$' || ch === '_') {
- this.lexIdent(ch);
+ var ich = this.getValidIdentifierChar(true);
+ var ch = (ich === null) ? input[this.cursor++] : null;
+ if (ich !== null) {
+ this.lexIdent(ich, keywordIsName);
} else if (scanOperand && ch === '/') {
this.lexRegExp(ch);
} else if (ch in opTokens) {
@@ -425,7 +517,9 @@ Narcissus.lexer = (function() {
this.lexZeroNumber(ch);
} else if (ch === '"' || ch === "'") {
this.lexString(ch);
- } else if (this.scanNewlines && ch === '\n') {
+ } 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++;
@@ -448,6 +542,7 @@ Narcissus.lexer = (function() {
},
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
@@ -455,8 +550,39 @@ Narcissus.lexer = (function() {
: this.cursor;
return e;
},
+
+
+ /* Gets a single valid identifier char from the input stream, or null
+ * if there is none.
+ */
+ getValidIdentifierChar: function(first) {
+ var input = this.source;
+ if (this.cursor >= input.length) return null;
+ var ch = input[this.cursor];
+
+ // 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;
+ }
+
+ var valid = isValidIdentifierChar(ch, first);
+ if (valid) this.cursor++;
+ return (valid ? ch : null);
+ },
};
- return { Tokenizer: Tokenizer };
+
+ return {
+ isIdentifier: isIdentifier,
+ Tokenizer: Tokenizer
+ };
}());
View
653 jslib/jit/narcissus/jsparse.js
@@ -54,12 +54,24 @@ Narcissus.parser = (function() {
var lexer = Narcissus.lexer;
var definitions = Narcissus.definitions;
- const StringMap = definitions.StringMap;
+ const Dict = definitions.Dict;
const Stack = definitions.Stack;
// Set constants in the local scope.
eval(definitions.consts);
+ // 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;
+
/*
* pushDestructuringVarDecls :: (node, hoisting node) -> void
*
@@ -76,21 +88,19 @@ Narcissus.parser = (function() {
}
}
- // 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) {
+ function StaticContext(parentScript, parentBlock, inModule, inFunction) {
this.parentScript = parentScript;
- this.parentBlock = parentBlock;
- this.inFunction = inFunction;
- this.inForLoopInit = inForLoopInit;
- this.nesting = nesting;
+ 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);
}
@@ -116,50 +126,61 @@ Narcissus.parser = (function() {
allLabels: this.allLabels.push(label) });
},
pushTarget: function(target) {
- var isDefaultTarget = target.isLoop || target.type === SWITCH;
+ var isDefaultLoopTarget = target.isLoop;
+ var isDefaultTarget = isDefaultLoopTarget || target.type === SWITCH;
if (this.currentLabels.isEmpty()) {
- return isDefaultTarget
- ? this.update({ defaultTarget: target })
- : this;
+ if (isDefaultLoopTarget) this.update({ defaultLoopTarget: target });
+ if (isDefaultTarget) this.update({ defaultTarget: target });
+ return this;
}
- target.labels = new StringMap();
+ target.labels = new Dict();
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(atLeast) {
- var nesting = Math.max(this.nesting, atLeast);
- return (nesting !== this.nesting)
- ? this.update({ nesting: nesting })
- : this;
+ 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 IMPORT:
+ return !this.inFunction && this.topLevel;
+
+ case MODULE:
+ return !this.inFunction && this.topLevel;
+
+ default:
+ return true;
+ }
}
};
/*
- * Script :: (tokenizer, boolean) -> node
+ * Script :: (tokenizer, boolean, boolean) -> node
*
- * Parses the toplevel and function bodies.
+ * Parses the toplevel and module/function bodies.
*/
- function Script(t, inFunction) {
+ function Script(t, inModule, inFunction) {
var n = new Node(t, scriptInit());
- var x = new StaticContext(n, n, inFunction, false, NESTING_TOP);
- Statements(t, x, n);
+ Statements(t, new StaticContext(n, n, inModule, inFunction), 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
*/
@@ -186,9 +207,71 @@ Narcissus.parser = (function() {
this[prop] = init[prop];
}
- var Np = Node.prototype = {};
+ /*
+ * 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;
- Np.toSource = Object.prototype.toSource;
+
+ 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);
+ // avoid infinite recursion in case of back-links
+ if (this.generatingSource)
+ return mock.toSource();
+ this.generatingSource = true;
+ 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];
+ }
+ try {
+ return mock.toSource();
+ } finally {
+ delete this.generatingSource;
+ }
+ };
// Always use push to add operands to an expression, to update start and end.
Np.push = function (kid) {
@@ -230,6 +313,15 @@ Narcissus.parser = (function() {
return this.tokenizer.source.slice(this.start, this.end);
};
+ Np.synth = function(init) {
+ var node = new SyntheticNode(this.tokenizer, init);
+ node.filename = this.filename;
+ node.lineno = this.lineno;
+ node.start = this.start;
+ node.end = this.end;
+ return node;
+ };
+
/*
* Helper init objects for common nodes.
*/
@@ -244,13 +336,16 @@ Narcissus.parser = (function() {
return { type: SCRIPT,
funDecls: [],
varDecls: [],
- modDecls: [],
+ modDefns: new Dict(),
+ modAssns: new Dict(),
+ modDecls: new Dict(),
+ modLoads: new Dict(),
impDecls: [],
expDecls: [],
- loadDeps: [],
+ exports: new Dict(),
hasEmptyReturn: false,
hasReturnWithValue: false,
- isGenerator: false };
+ hasYield: false };
}
definitions.defineGetter(Np, "filename",
@@ -310,6 +405,102 @@ Narcissus.parser = (function() {
const DECLARED_FORM = 0, EXPRESSED_FORM = 1, STATEMENT_FORM = 2;
/*
+ * Export :: (binding node, boolean) -> Export
+ *
+ * Static semantic representation of a module export.
+ */
+ function Export(node, isDefinition) {
+ this.node = node; // the AST node declaring this individual export
+ this.isDefinition = isDefinition; // is the node an 'export'-annotated definition?
+ this.resolved = null; // resolved pointer to the target of this export
+ }
+
+ /*
+ * registerExport :: (Dict, EXPORT node) -> void
+ */
+ function registerExport(exports, decl) {
+ function register(name, exp) {
+ if (exports.has(name))
+ throw new SyntaxError("multiple exports of " + name);
+ exports.set(name, exp);
+ }
+
+ switch (decl.type) {
+ case MODULE:
+ case FUNCTION:
+ register(decl.name, new Export(decl, true));
+ break;
+
+ case VAR:
+ for (var i = 0; i < decl.children.length; i++)
+ register(decl.children[i].name, new Export(decl.children[i], true));
+ break;
+
+ case LET:
+ case CONST:
+ throw new Error("NYI: " + definitions.tokens[decl.type]);
+
+ case EXPORT:
+ for (var i = 0; i < decl.pathList.length; i++) {
+ var path = decl.pathList[i];
+ switch (path.type) {
+ case OBJECT_INIT:
+ for (var j = 0; j < path.children.length; j++) {
+ // init :: IDENTIFIER | PROPERTY_INIT
+ var init = path.children[j];
+ if (init.type === IDENTIFIER)
+ register(init.value, new Export(init, false));
+ else
+ register(init.children[0].value, new Export(init.children[1], false));
+ }
+ break;
+
+ case DOT:
+ register(path.children[1].value, new Export(path, false));
+ break;
+
+ case IDENTIFIER:
+ register(path.value, new Export(path, false));
+ break;
+
+ default:
+ throw new Error("unexpected export path: " + definitions.tokens[path.type]);
+ }
+ }
+ break;
+
+ default:
+ throw new Error("unexpected export decl: " + definitions.tokens[exp.type]);
+ }
+ }
+
+ /*
+ * Module :: (node) -> Module
+ *
+ * Static semantic representation of a module.
+ */
+ function Module(node) {
+ var exports = node.body.exports;
+ var modDefns = node.body.modDefns;
+
+ var exportedModules = new Dict();
+
+ exports.forEach(function(name, exp) {
+ var node = exp.node;
+ if (node.type === MODULE) {
+ exportedModules.set(name, node);
+ } else if (!exp.isDefinition && node.type === IDENTIFIER && modDefns.has(node.value)) {
+ var mod = modDefns.get(node.value);
+ exportedModules.set(name, mod);
+ }
+ });
+
+ this.node = node;
+ this.exports = exports;
+ this.exportedModules = exportedModules;
+ }
+
+ /*
* Statement :: (tokenizer, compiler context) -> node
*
* Parses a Statement.
@@ -317,35 +508,87 @@ Narcissus.parser = (function() {
function Statement(t, x) {
var i, label, n, n2, p, c, ss, tt = t.get(true), tt2, x2, x3;
+ var comments = t.blockComments;
+
+ if (x.blackList[tt])
+ throw t.newSyntaxError(definitions.tokens[tt] + " statements only allowed in Harmony");
+ if (!x.allow(tt))
+ throw t.newSyntaxError(definitions.tokens[tt] + " statement in illegal context");
+
// Cases for statements ending in a right curly return early, avoiding the
// common semicolon insertion magic after this switch.
switch (tt) {
+ case IMPORT:
+ n = new Node(t);
+ n.pathList = ImportPathList(t, x);
+ x.parentScript.impDecls.push(n);
+ break;
+
+ case EXPORT:
+ switch (t.peek()) {
+ case MODULE:
+ case FUNCTION:
+ case LET:
+ case VAR:
+ case CONST:
+ n = Statement(t, x);
+ n.blockComments = comments;
+ n.exported = true;
+ x.parentScript.expDecls.push(n);
+ registerExport(x.parentScript.exports, n);
+ return n;
+
+ default:
+ n = new Node(t);
+ n.pathList = ExportPathList(t, x);
+ break;
+ }
+ x.parentScript.expDecls.push(n);
+ registerExport(x.parentScript.exports, n);
+ break;
+
+ case MODULE:
+ n = new Node(t);
+ n.blockComments = comments;
+ t.mustMatch(IDENTIFIER);
+ label = t.token.value;
+
+ if (t.match(LEFT_CURLY)) {
+ n.name = label;
+ n.body = Script(t, true, false);
+ n.module = new Module(n);
+ t.mustMatch(RIGHT_CURLY);
+ x.parentScript.modDefns.set(n.name, n);
+ return n;
+ }
+
+ t.unget();
+ ModuleVariables(t, x, n);
+ return n;
+
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);
+ return FunctionDefinition(t, x, true, x.topLevel ? DECLARED_FORM : STATEMENT_FORM, comments);
case LEFT_CURLY:
n = new Node(t, blockInit());
- Statements(t, x.update({ parentBlock: n }).pushTarget(n).nest(NESTING_SHALLOW), n);
+ Statements(t, x.update({ parentBlock: n }).pushTarget(n).nest(), 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);
+ x2 = x.pushTarget(n).nest();
n.thenPart = Statement(t, x2);
- n.elsePart = t.match(ELSE) ? Statement(t, x2) : null;
+ n.elsePart = t.match(ELSE, true) ? 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);
+ x2 = x.pushTarget(n).nest();
t.mustMatch(LEFT_CURLY);
while ((tt = t.get()) !== RIGHT_CURLY) {
switch (tt) {
@@ -375,6 +618,7 @@ Narcissus.parser = (function() {
case FOR:
n = new Node(t, LOOP_INIT);
+ n.blockComments = comments;
if (t.match(IDENTIFIER)) {
if (t.token.value === "each")
n.isEach = true;
@@ -383,9 +627,10 @@ Narcissus.parser = (function() {
}
if (!x.parenFreeMode)
t.mustMatch(LEFT_PAREN);
- x2 = x.pushTarget(n).nest(NESTING_DEEP);
+ x2 = x.pushTarget(n).nest();
x3 = x.update({ inForLoopInit: true });
- if ((tt = t.peek()) !== SEMICOLON) {
+ n2 = null;
+ if ((tt = t.peek(true)) !== SEMICOLON) {
if (tt === VAR || tt === CONST) {
t.get();
n2 = Variables(t, x3);
@@ -430,15 +675,16 @@ Narcissus.parser = (function() {
n.iterator = n2;
}
} else {
+ x3.inForLoopInit = false;
n.setup = n2;
t.mustMatch(SEMICOLON);
if (n.isEach)
throw t.newSyntaxError("Invalid for each..in loop");
- n.condition = (t.peek() === SEMICOLON)
+ n.condition = (t.peek(true) === SEMICOLON)
? null
: Expression(t, x3);
t.mustMatch(SEMICOLON);
- tt2 = t.peek();
+ tt2 = t.peek(true);
n.update = (x.parenFreeMode
? tt2 === LEFT_CURLY || definitions.isStatementStartCode[tt2]
: tt2 === RIGHT_PAREN)
@@ -452,13 +698,15 @@ Narcissus.parser = (function() {
case WHILE:
n = new Node(t, { isLoop: true });
+ n.blockComments = comments;
n.condition = HeadExpression(t, x);
- n.body = Statement(t, x.pushTarget(n).nest(NESTING_DEEP));
+ n.body = Statement(t, x.pushTarget(n).nest());
return n;
case DO:
n = new Node(t, { isLoop: true });
- n.body = Statement(t, x.pushTarget(n).nest(NESTING_DEEP));
+ n.blockComments = comments;
+ n.body = Statement(t, x.pushTarget(n).nest());
t.mustMatch(WHILE);
n.condition = HeadExpression(t, x);
if (!x.ecmaStrictMode) {
@@ -473,6 +721,7 @@ Narcissus.parser = (function() {
case BREAK:
case CONTINUE:
n = new Node(t);
+ n.blockComments = comments;
// handle the |foo: break foo;| corner case
x2 = x.pushTarget(n);
@@ -482,9 +731,15 @@ Narcissus.parser = (function() {
n.label = t.token.value;
}
- n.target = n.label
- ? x2.labeledTargets.find(function(target) { return target.labels.has(n.label) })
- : x2.defaultTarget;
+ if (n.label) {
+ n.target = x2.labeledTargets.find(function(target) {
+ return target.labels.has(n.label)
+ });
+ } else if (tt === CONTINUE) {
+ n.target = x2.defaultLoopTarget;
+ } else {
+ n.target = x2.defaultTarget;
+ }
if (!n.target)
throw t.newSyntaxError("Invalid " + ((tt === BREAK) ? "break" : "continue"));
@@ -495,6 +750,7 @@ Narcissus.parser = (function() {
case TRY:
n = new Node(t, { catchClauses: [] });
+ n.blockComments = comments;
n.tryBlock = Block(t, x);
while (t.match(CATCH)) {
n2 = new Node(t);
@@ -545,8 +801,9 @@ Narcissus.parser = (function() {
case WITH:
n = new Node(t);
+ n.blockComments = comments;
n.object = HeadExpression(t, x);
- n.body = Statement(t, x.pushTarget(n).nest(NESTING_DEEP));
+ n.body = Statement(t, x.pushTarget(n).nest());
return n;
case VAR:
@@ -555,10 +812,11 @@ Narcissus.parser = (function() {
break;
case LET:
- if (t.peek() === LEFT_PAREN)
+ if (t.peek() === LEFT_PAREN) {
n = LetBlock(t, x, true);
- else
- n = Variables(t, x);
+ return n;
+ }
+ n = Variables(t, x);
break;
case DEBUGGER:
@@ -568,6 +826,7 @@ Narcissus.parser = (function() {
case NEWLINE:
case SEMICOLON:
n = new Node(t, { type: SEMICOLON });
+ n.blockComments = comments;
n.expression = null;
return n;
@@ -581,7 +840,8 @@ Narcissus.parser = (function() {
throw t.newSyntaxError("Duplicate label");
t.get();
n = new Node(t, { type: LABEL, label: label });
- n.statement = Statement(t, x.pushLabel(label).nest(NESTING_SHALLOW));
+ n.blockComments = comments;
+ n.statement = Statement(t, x.pushLabel(label).nest());
n.target = (n.statement.type === LABEL) ? n.statement.target : n.statement;
return n;
}
@@ -591,15 +851,20 @@ Narcissus.parser = (function() {
// We unget the current token to parse the expression as a whole.
n = new Node(t, { type: SEMICOLON });
t.unget();
+ n.blockComments = comments;
n.expression = Expression(t, x);
n.end = n.expression.end;
break;
}
+ n.blockComments = comments;
MagicalSemicolon(t);
return n;
}
+ /*
+ * MagicalSemicolon :: (tokenizer) -> void
+ */
function MagicalSemicolon(t) {
var tt;
if (t.lineno === t.token.lineno) {
@@ -610,6 +875,9 @@ Narcissus.parser = (function() {
t.match(SEMICOLON);
}
+ /*
+ * ReturnOrYield :: (tokenizer, compiler context) -> (RETURN | YIELD) node
+ */
function ReturnOrYield(t, x) {
var n, b, tt = t.token.type, tt2;
@@ -621,11 +889,11 @@ Narcissus.parser = (function() {
} else /* if (tt === YIELD) */ {
if (!x.inFunction)
throw t.newSyntaxError("Yield not in function");
- parentScript.isGenerator = true;
+ parentScript.hasYield = true;
}
n = new Node(t, { value: undefined });
- tt2 = t.peek(true);
+ tt2 = (tt === RETURN) ? t.peekOnSameLine(true) : t.peek(true);
if (tt2 !== END && tt2 !== NEWLINE &&
tt2 !== SEMICOLON && tt2 !== RIGHT_CURLY
&& (tt !== YIELD ||
@@ -641,34 +909,170 @@ Narcissus.parser = (function() {
parentScript.hasEmptyReturn = true;
}
- // Disallow return v; in generator.
- if (parentScript.hasReturnWithValue && parentScript.isGenerator)
- throw t.newSyntaxError("Generator returns a value");
+ return n;
+ }
+
+ /*
+ * ModuleExpression :: (tokenizer, compiler context) -> (STRING | IDENTIFIER | DOT) node
+ */
+ function ModuleExpression(t, x) {
+ return t.match(STRING) ? new Node(t) : QualifiedPath(t, x);
+ }
+
+ /*
+ * ImportPathList :: (tokenizer, compiler context) -> Array[DOT node]
+ */
+ function ImportPathList(t, x) {
+ var a = [];
+ do {
+ a.push(ImportPath(t, x));
+ } while (t.match(COMMA));
+ return a;
+ }
+
+ /*
+ * ImportPath :: (tokenizer, compiler context) -> DOT node
+ */
+ function ImportPath(t, x) {
+ var n = QualifiedPath(t, x);
+ if (!t.match(DOT)) {
+ if (n.type === IDENTIFIER)
+ throw t.newSyntaxError("cannot import local variable");
+ return n;
+ }
+
+ var n2 = new Node(t);
+ n2.push(n);
+ n2.push(ImportSpecifierSet(t, x));
+ return n2;
+ }
+
+ /*
+ * ExplicitSpecifierSet :: (tokenizer, compiler context, (tokenizer, compiler context) -> node)
+ * -> OBJECT_INIT node
+ */
+ function ExplicitSpecifierSet(t, x, SpecifierRHS) {
+ var n, n2, id, tt;
+
+ n = new Node(t, { type: OBJECT_INIT });
+ t.mustMatch(LEFT_CURLY);
+
+ if (!t.match(RIGHT_CURLY)) {
+ do {
+ id = Identifier(t, x);
+ if (t.match(COLON)) {
+ n2 = new Node(t, { type: PROPERTY_INIT });
+ n2.push(id);
+ n2.push(SpecifierRHS(t, x));
+ n.push(n2);
+ } else {
+ n.push(id);
+ }
+ } while (!t.match(RIGHT_CURLY) && t.mustMatch(COMMA));
+ }
return n;
}
/*
+ * ImportSpecifierSet :: (tokenizer, compiler context) -> (IDENTIFIER | OBJECT_INIT) node
+ */
+ function ImportSpecifierSet(t, x) {
+ return t.match(MUL)
+ ? new Node(t, { type: IDENTIFIER, name: "*" })
+ : ExplicitSpecifierSet(t, x, Identifier);
+ }
+
+ /*
+ * Identifier :: (tokenizer, compiler context) -> IDENTIFIER node
+ */
+ function Identifier(t, x) {
+ t.mustMatch(IDENTIFIER);
+ return new Node(t, { type: IDENTIFIER });
+ }
+
+ /*
+ * IdentifierName :: (tokenizer) -> IDENTIFIER node
+ */
+ function IdentifierName(t) {
+ t.mustMatch(IDENTIFIER, true);
+ return new Node(t, { type: IDENTIFIER });
+ }
+
+ /*
+ * QualifiedPath :: (tokenizer, compiler context) -> (IDENTIFIER | DOT) node
+ */
+ function QualifiedPath(t, x) {
+ var n, n2;
+
+ n = Identifier(t, x);
+
+ while (t.match(DOT)) {
+ if (t.peek() !== IDENTIFIER) {
+ // Unget the '.' token, which isn't part of the QualifiedPath.
+ t.unget();
+ break;
+ }
+ n2 = new Node(t);
+ n2.push(n);
+ n2.push(Identifier(t, x));
+ n = n2;
+ }
+
+ return n;
+ }
+
+ /*
+ * ExportPath :: (tokenizer, compiler context) -> (IDENTIFIER | DOT | OBJECT_INIT) node
+ */
+ function ExportPath(t, x) {
+ if (t.peek() === LEFT_CURLY)
+ return ExplicitSpecifierSet(t, x, QualifiedPath);
+ return QualifiedPath(t, x);
+ }
+
+ /*
+ * ExportPathList :: (tokenizer, compiler context)
+ * -> Array[(IDENTIFIER | DOT | OBJECT_INIT) node]
+ */
+ function ExportPathList(t, x) {
+ var a = [];
+ do {
+ a.push(ExportPath(t, x));
+ } while (t.match(COMMA));
+ return a;
+ }
+
+ /*
* FunctionDefinition :: (tokenizer, compiler context, boolean,
- * DECLARED_FORM or EXPRESSED_FORM or STATEMENT_FORM)
+ * DECLARED_FORM or EXPRESSED_FORM or STATEMENT_FORM,
+ * [string] or null or undefined)
* -> node
*/
- function FunctionDefinition(t, x, requireName, functionForm) {
+ function FunctionDefinition(t, x, requireName, functionForm, comments) {
var tt;
- var f = new Node(t, { params: [] });
+ var f = new Node(t, { params: [], paramComments: [] });
+ if (typeof comment === "undefined")
+ comment = null;
+ f.blockComments = comments;
if (f.type !== FUNCTION)
f.type = (f.value === "get") ? GETTER : SETTER;
- if (t.match(IDENTIFIER))
+ if (t.match(MUL))
+ f.isExplicitGenerator = true;
+ if (t.match(IDENTIFIER, false, true))
f.name = t.token.value;
else if (requireName)
throw t.newSyntaxError("missing function identifier");
- var x2 = new StaticContext(null, null, true, false, NESTING_TOP);
+ var inModule = x ? x.inModule : false;
+ var x2 = new StaticContext(null, null, inModule, true);
t.mustMatch(LEFT_PAREN);
if (!t.match(RIGHT_PAREN)) {
do {
- switch (t.get()) {
+ tt = t.get();
+ f.paramComments.push(t.lastBlockComment());
+ switch (tt) {
case LEFT_BRACKET:
case LEFT_CURLY:
// Destructured formal parameters.
@@ -687,16 +1091,14 @@ Narcissus.parser = (function() {
}
// Do we have an expression closure or a normal body?
- tt = t.get();
+ tt = t.get(true);
if (tt !== LEFT_CURLY)
t.unget();
if (tt !== LEFT_CURLY) {
f.body = AssignExpression(t, x2);
- if (f.body.isGenerator)
- throw t.newSyntaxError("Generator returns a value");
} else {
- f.body = Script(t, true);
+ f.body = Script(t, inModule, true);
}
if (tt === LEFT_CURLY)
@@ -706,10 +1108,39 @@ Narcissus.parser = (function() {
f.functionForm = functionForm;
if (functionForm === DECLARED_FORM)
x.parentScript.funDecls.push(f);
+
+ if (Narcissus.options.version === "harmony" && !f.isExplicitGenerator && f.body.hasYield)
+ throw t.newSyntaxError("yield in non-generator function");
+
+ if (f.isExplicitGenerator || f.body.hasYield)
+ f.body = new Node(t, { type: GENERATOR, body: f.body });
+
return f;
}
/*
+ * ModuleVariables :: (tokenizer, compiler context, MODULE node) -> void
+ *
+ * Parses a comma-separated list of module declarations (and maybe
+ * initializations).
+ */
+ function ModuleVariables(t, x, n) {
+ var n1, n2;
+ do {
+ n1 = Identifier(t, x);
+ if (t.match(ASSIGN)) {
+ n2 = ModuleExpression(t, x);
+ n1.initializer = n2;
+ if (n2.type === STRING)
+ x.parentScript.modLoads.set(n1.value, n2.value);
+ else
+ x.parentScript.modAssns.set(n1.value, n1);
+ }
+ n.push(n1);
+ } while (t.match(COMMA));
+ }
+
+ /*
* Variables :: (tokenizer, compiler context) -> node
*
* Parses a comma-separated list of var declarations (and maybe
@@ -758,6 +1189,7 @@ Narcissus.parser = (function() {
if (t.token.assignOp)
throw t.newSyntaxError("Invalid variable initialization");
+ n2.blockComment = t.lastBlockComment();
n2.initializer = AssignExpression(t, x);
continue;
@@ -773,11 +1205,15 @@ Narcissus.parser = (function() {
s.varDecls.push(n2);
if (t.match(ASSIGN)) {
+ var comment = t.lastBlockComment();
if (t.token.assignOp)
throw t.newSyntaxError("Invalid variable initialization");
n2.initializer = AssignExpression(t, x);
+ } else {
+ var comment = t.lastBlockComment();
}
+ n2.blockComment = comment;
} while (t.match(COMMA));
return n;
@@ -989,6 +1425,8 @@ Narcissus.parser = (function() {
return lhs;
}
+ n.blockComment = t.lastBlockComment();
+
switch (lhs.type) {
case OBJECT_INIT:
case ARRAY_INIT:
@@ -1001,7 +1439,7 @@ Narcissus.parser = (function() {
break;
}
- n.assignOp = t.token.assignOp;
+ n.assignOp = lhs.assignOp = t.token.assignOp;
n.push(lhs);
n.push(AssignExpression(t, x));
@@ -1238,8 +1676,7 @@ Narcissus.parser = (function() {
case DOT:
n2 = new Node(t);
n2.push(n);
- t.mustMatch(IDENTIFIER);
- n2.push(new Node(t));
+ n2.push(IdentifierName(t));
break;
case LEFT_BRACKET:
@@ -1337,6 +1774,7 @@ Narcissus.parser = (function() {
throw t.newSyntaxError("Illegal property accessor");
n.push(FunctionDefinition(t, x, true, EXPRESSED_FORM));
} else {
+ var comments = t.blockComments;
switch (tt) {
case IDENTIFIER: case NUMBER: case STRING:
id = new Node(t, { type: IDENTIFIER });
@@ -1356,6 +1794,7 @@ Narcissus.parser = (function() {
n2 = new Node(t, { type: PROPERTY_INIT });
n2.push(id);
n2.push(AssignExpression(t, x));
+ n2.blockComments = comments;
n.push(n2);
} else {
// Support, e.g., |var {x, y} = o| as destructuring shorthand
@@ -1386,7 +1825,7 @@ Narcissus.parser = (function() {
break;
default:
- throw t.newSyntaxError("missing operand");
+ throw t.newSyntaxError("missing operand; found " + definitions.tokens[tt]);
break;
}
@@ -1398,7 +1837,7 @@ Narcissus.parser = (function() {
*/
function parse(s, f, l) {
var t = new lexer.Tokenizer(s, f, l);
- var n = Script(t, false);
+ var n = Script(t, false, false);
if (!t.done)
throw t.newSyntaxError("Syntax error");
@@ -1406,26 +1845,66 @@ Narcissus.parser = (function() {
}
/*
- * parseStdin :: (source, {line number}) -> node
+ * parseStdin :: (source, {line number}, string, (string) -> boolean) -> program node
*/
- function parseStdin(s, ln) {
+ function parseStdin(s, ln, prefix, isCommand) {
+ // the special .begin command is only recognized at the beginning
+ if (s.match(/^[\s]*\.begin[\s]*$/)) {
+ ++ln.value;
+ return parseMultiline(ln, prefix);
+ }
+
+ // commands at the beginning are treated as the entire input
+ if (isCommand(s.trim()))
+ s = "";
+
for (;;) {
try {
var t = new lexer.Tokenizer(s, "stdin", ln.value);
- var n = Script(t, false);
+ var n = Script(t, false, false);
ln.value = t.lineno;
return n;
} catch (e) {
if (!t.unexpectedEOF)
throw e;
- var more = readline();
- if (!more)
- throw e;
+
+ // commands in the middle are not treated as part of the input
+ var more;
+ do {
+ if (prefix)
+ putstr(prefix);
+ more = readline();
+ if (!more)
+ throw e;
+ } while (isCommand(more.trim()));
+
s += "\n" + more;
}
}
}
+ /*
+ * parseMultiline :: ({line number}, string | null) -> program node
+ */
+ function parseMultiline(ln, prefix) {
+ var s = "";
+ for (;;) {
+ if (prefix)
+ putstr(prefix);
+ var more = readline();
+ if (more === null)
+ return null;
+ // the only command recognized in multiline mode is .end
+ if (more.match(/^[\s]*\.end[\s]*$/))
+ break;
+ s += "\n" + more;
+ }
+ var t = new lexer.Tokenizer(s, "stdin", ln.value);
+ var n = Script(t, false, false);
+ ln.value = t.lineno;
+ return n;
+ }
+
return {
parse: parse,
parseStdin: parseStdin,
@@ -1434,7 +1913,9 @@ Narcissus.parser = (function() {
EXPRESSED_FORM: EXPRESSED_FORM,
STATEMENT_FORM: STATEMENT_FORM,
Tokenizer: lexer.Tokenizer,
- FunctionDefinition: FunctionDefinition
+ FunctionDefinition: FunctionDefinition,
+ Module: Module,
+ Export: Export
};
}());
Please sign in to comment.
Something went wrong with that request. Please try again.