Update Acorn.js to v0.1.01

This commit is contained in:
Jürg Lehni 2013-02-24 13:37:45 -08:00
parent ac8c9cd114
commit 17b81f5f67
2 changed files with 294 additions and 188 deletions

2
lib/acorn-min.js vendored

File diff suppressed because one or more lines are too long

View file

@ -12,13 +12,20 @@
// Please use the [github bug tracker][ghbt] to report issues. // Please use the [github bug tracker][ghbt] to report issues.
// //
// [ghbt]: https://github.com/marijnh/acorn/issues // [ghbt]: https://github.com/marijnh/acorn/issues
//
// This file defines the main parser interface. The library also comes
// with a [error-tolerant parser][dammit] and an
// [abstract syntax tree walker][walk], defined in other files.
//
// [dammit]: acorn_loose.js
// [walk]: util/walk.js
(function(exports) { (function(exports) {
"use strict"; "use strict";
exports.version = "0.0.1"; exports.version = "0.1.01";
// The main exported interface (under `window.acorn` when in the // The main exported interface (under `self.acorn` when in the
// browser) is a `parse` function that takes a code string and // browser) is a `parse` function that takes a code string and
// returns an abstract syntax tree as specified by [Mozilla parser // returns an abstract syntax tree as specified by [Mozilla parser
// API][api], with the caveat that the SpiderMonkey-specific syntax // API][api], with the caveat that the SpiderMonkey-specific syntax
@ -30,10 +37,8 @@
exports.parse = function(inpt, opts) { exports.parse = function(inpt, opts) {
input = String(inpt); inputLen = input.length; input = String(inpt); inputLen = input.length;
options = opts || {}; setOptions(opts);
for (var opt in defaultOptions) if (!options.hasOwnProperty(opt)) initTokenState();
options[opt] = defaultOptions[opt];
sourceFile = options.sourceFile || null;
return parseTopLevel(options.program); return parseTopLevel(options.program);
}; };
@ -55,18 +60,21 @@
// By default, reserved words are not enforced. Enable // By default, reserved words are not enforced. Enable
// `forbidReserved` to enforce them. // `forbidReserved` to enforce them.
forbidReserved: false, forbidReserved: false,
// When `trackComments` is turned on, the parser will attach
// `commentsBefore` and `commentsAfter` properties to AST nodes
// holding arrays of strings. A single comment may appear in both
// a `commentsBefore` and `commentsAfter` array (of the nodes
// after and before it), but never twice in the before (or after)
// array of different nodes.
trackComments: false,
// When `locations` is on, `loc` properties holding objects with // When `locations` is on, `loc` properties holding objects with
// `start` and `end` properties in `{line, column}` form (with // `start` and `end` properties in `{line, column}` form (with
// line being 1-based and column 0-based) will be attached to the // line being 1-based and column 0-based) will be attached to the
// nodes. // nodes.
locations: false, locations: false,
// A function can be passed as `onComment` option, which will
// cause Acorn to call that function with `(block, text, start,
// end)` parameters whenever a comment is skipped. `block` is a
// boolean indicating whether this is a block (`/* */`) comment,
// `text` is the content of the comment, and `start` and `end` are
// character offsets that denote the start and end of the comment.
// When the `locations` option is on, two more parameters are
// passed, the full `{line, column}` locations of the start and
// end of the comments.
onComment: null,
// Nodes have their start and end characters offsets recorded in // Nodes have their start and end characters offsets recorded in
// `start` and `end` properties (directly on the node, rather than // `start` and `end` properties (directly on the node, rather than
// the `loc` object, which holds line/column data. To also add a // the `loc` object, which holds line/column data. To also add a
@ -87,6 +95,13 @@
sourceFile: null sourceFile: null
}; };
function setOptions(opts) {
options = opts || {};
for (var opt in defaultOptions) if (!options.hasOwnProperty(opt))
options[opt] = defaultOptions[opt];
sourceFile = options.sourceFile || null;
}
// The `getLineInfo` function is mostly useful when the // The `getLineInfo` function is mostly useful when the
// `locations` option is off (for performance reasons) and you // `locations` option is off (for performance reasons) and you
// want to find the line/column position for a given character // want to find the line/column position for a given character
@ -106,9 +121,44 @@
}; };
// Acorn is organized as a tokenizer and a recursive-descent parser. // Acorn is organized as a tokenizer and a recursive-descent parser.
// Both use (closure-)global variables to keep their state and // The `tokenize` export provides an interface to the tokenizer.
// communicate. We already saw the `options`, `input`, and // Because the tokenizer is optimized for being efficiently used by
// `inputLen` variables above (set in `parse`). // the Acorn parser itself, this interface is somewhat crude and not
// very modular. Performing another parse or call to `tokenize` will
// reset the internal state, and invalidate existing tokenizers.
exports.tokenize = function(inpt, opts) {
input = String(inpt); inputLen = input.length;
setOptions(opts);
initTokenState();
var t = {};
function getToken(forceRegexp) {
readToken(forceRegexp);
t.start = tokStart; t.end = tokEnd;
t.startLoc = tokStartLoc; t.endLoc = tokEndLoc;
t.type = tokType; t.value = tokVal;
return t;
}
getToken.jumpTo = function(pos, reAllowed) {
tokPos = pos;
if (options.locations) {
tokCurLine = tokLineStart = lineBreak.lastIndex = 0;
var match;
while ((match = lineBreak.exec(input)) && match.index < pos) {
++tokCurLine;
tokLineStart = match.index + match[0].length;
}
}
var ch = input.charAt(pos - 1);
tokRegexpAllowed = reAllowed;
skipSpace();
};
return getToken;
};
// State is kept in (closure-)global variables. We already saw the
// `options`, `input`, and `inputLen` variables above.
// The current position of the tokenizer in the input. // The current position of the tokenizer in the input.
@ -133,11 +183,6 @@
var tokType, tokVal; var tokType, tokVal;
// These are used to hold arrays of comments when
// `options.trackComments` is true.
var tokCommentsBefore, tokCommentsAfter;
// Interal state for the tokenizer. To distinguish between division // Interal state for the tokenizer. To distinguish between division
// operators and regular expressions, it remembers whether the last // operators and regular expressions, it remembers whether the last
// token was one that is allowed to be followed by an expression. // token was one that is allowed to be followed by an expression.
@ -145,13 +190,13 @@
// division operator. See the `parseStatement` function for a // division operator. See the `parseStatement` function for a
// caveat.) // caveat.)
var tokRegexpAllowed, tokComments; var tokRegexpAllowed;
// When `options.locations` is true, these are used to keep // When `options.locations` is true, these are used to keep
// track of the current line, and know when a new line has been // track of the current line, and know when a new line has been
// entered. See the `curLineLoc` function. // entered.
var tokCurLine, tokLineStart, tokLineStartNext; var tokCurLine, tokLineStart;
// These store the position of the previous token, which is useful // These store the position of the previous token, which is useful
// when finishing a node and assigning its `end` position. // when finishing a node and assigning its `end` position.
@ -166,15 +211,17 @@
var inFunction, labels, strict; var inFunction, labels, strict;
// This function is used to raise exceptions on parse errors. It // This function is used to raise exceptions on parse errors. It
// takes either a `{line, column}` object or an offset integer (into // takes an offset integer (into the current `input`) to indicate
// the current `input`) as `pos` argument. It attaches the position // the location of the error, attaches the position to the end
// to the end of the error message, and then raises a `SyntaxError` // of the error message, and then raises a `SyntaxError` with that
// with that message. // message.
function raise(pos, message) { function raise(pos, message) {
if (typeof pos == "number") pos = getLineInfo(input, pos); var loc = getLineInfo(input, pos);
message += " (" + pos.line + ":" + pos.column + ")"; message += " (" + loc.line + ":" + loc.column + ")";
throw new SyntaxError(message); var err = new SyntaxError(message);
err.pos = pos; err.loc = loc; err.raisedAt = tokPos;
throw err;
} }
// ## Token types // ## Token types
@ -233,10 +280,10 @@
"function": _function, "if": _if, "return": _return, "switch": _switch, "function": _function, "if": _if, "return": _return, "switch": _switch,
"throw": _throw, "try": _try, "var": _var, "while": _while, "with": _with, "throw": _throw, "try": _try, "var": _var, "while": _while, "with": _with,
"null": _null, "true": _true, "false": _false, "new": _new, "in": _in, "null": _null, "true": _true, "false": _false, "new": _new, "in": _in,
"instanceof": {keyword: "instanceof", binop: 7}, "this": _this, "instanceof": {keyword: "instanceof", binop: 7, beforeExpr: true}, "this": _this,
"typeof": {keyword: "typeof", prefix: true}, "typeof": {keyword: "typeof", prefix: true, beforeExpr: true},
"void": {keyword: "void", prefix: true}, "void": {keyword: "void", prefix: true, beforeExpr: true},
"delete": {keyword: "delete", prefix: true}}; "delete": {keyword: "delete", prefix: true, beforeExpr: true}};
// Punctuation token types. Again, the `type` property is purely for debugging. // Punctuation token types. Again, the `type` property is purely for debugging.
@ -270,6 +317,15 @@
var _bin7 = {binop: 7, beforeExpr: true}, _bin8 = {binop: 8, beforeExpr: true}; var _bin7 = {binop: 7, beforeExpr: true}, _bin8 = {binop: 8, beforeExpr: true};
var _bin10 = {binop: 10, beforeExpr: true}; var _bin10 = {binop: 10, beforeExpr: true};
// Provide access to the token types for external users of the
// tokenizer.
exports.tokTypes = {bracketL: _bracketL, bracketR: _bracketR, braceL: _braceL, braceR: _braceR,
parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon,
dot: _dot, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof,
num: _num, regexp: _regexp, string: _string};
for (var kw in keywordTypes) exports.tokTypes[kw] = keywordTypes[kw];
// This is a trick taken from Esprima. It turns out that, on // This is a trick taken from Esprima. It turns out that, on
// non-Chrome browsers, to check whether a string is in a set, a // non-Chrome browsers, to check whether a string is in a set, a
// predicate containing a big ugly `switch` statement is faster than // predicate containing a big ugly `switch` statement is faster than
@ -384,23 +440,12 @@
// ## Tokenizer // ## Tokenizer
// These are used when `options.locations` is on, in order to track // These are used when `options.locations` is on, for the
// the current line number and start of line offset, in order to set // `tokStartLoc` and `tokEndLoc` properties.
// `tokStartLoc` and `tokEndLoc`.
function nextLineStart() { function line_loc_t() {
lineBreak.lastIndex = tokLineStart; this.line = tokCurLine;
var match = lineBreak.exec(input); this.column = tokPos - tokLineStart;
return match ? match.index + match[0].length : input.length + 1;
}
function curLineLoc() {
while (tokLineStartNext <= tokPos) {
++tokCurLine;
tokLineStart = tokLineStartNext;
tokLineStartNext = nextLineStart();
}
return {line: tokCurLine, column: tokPos - tokLineStart};
} }
// Reset the token state. Used at the start of a parse. // Reset the token state. Used at the start of a parse.
@ -408,64 +453,86 @@
function initTokenState() { function initTokenState() {
tokCurLine = 1; tokCurLine = 1;
tokPos = tokLineStart = 0; tokPos = tokLineStart = 0;
tokLineStartNext = nextLineStart();
tokRegexpAllowed = true; tokRegexpAllowed = true;
tokComments = null;
skipSpace(); skipSpace();
} }
// Called at the end of every token. Sets `tokEnd`, `tokVal`, // Called at the end of every token. Sets `tokEnd`, `tokVal`, and
// `tokCommentsAfter`, and `tokRegexpAllowed`, and skips the space // `tokRegexpAllowed`, and skips the space after the token, so that
// after the token, so that the next one's `tokStart` will point at // the next one's `tokStart` will point at the right position.
// the right position.
function finishToken(type, val) { function finishToken(type, val) {
tokEnd = tokPos; tokEnd = tokPos;
if (options.locations) tokEndLoc = curLineLoc(); if (options.locations) tokEndLoc = new line_loc_t;
tokType = type; tokType = type;
skipSpace(); skipSpace();
tokVal = val; tokVal = val;
tokCommentsAfter = tokComments;
tokRegexpAllowed = type.beforeExpr; tokRegexpAllowed = type.beforeExpr;
} }
function skipBlockComment() { function skipBlockComment() {
var end = input.indexOf("*/", tokPos += 2); var startLoc = options.onComment && options.locations && new line_loc_t;
var start = tokPos, end = input.indexOf("*/", tokPos += 2);
if (end === -1) raise(tokPos - 2, "Unterminated comment"); if (end === -1) raise(tokPos - 2, "Unterminated comment");
if (options.trackComments)
(tokComments || (tokComments = [])).push(input.slice(tokPos, end));
tokPos = end + 2; tokPos = end + 2;
if (options.locations) {
lineBreak.lastIndex = start;
var match;
while ((match = lineBreak.exec(input)) && match.index < tokPos) {
++tokCurLine;
tokLineStart = match.index + match[0].length;
}
}
if (options.onComment)
options.onComment(true, input.slice(start + 2, end), start, tokPos,
startLoc, options.locations && new line_loc_t);
} }
function skipLineComment() { function skipLineComment() {
var start = tokPos; var start = tokPos;
var startLoc = options.onComment && options.locations && new line_loc_t;
var ch = input.charCodeAt(tokPos+=2); var ch = input.charCodeAt(tokPos+=2);
while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8329) { while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8329) {
++tokPos; ++tokPos;
ch = input.charCodeAt(tokPos); ch = input.charCodeAt(tokPos);
} }
if (options.trackComments) if (options.onComment)
(tokComments || (tokComments = [])).push(input.slice(start, tokPos)); options.onComment(false, input.slice(start + 2, tokPos), start, tokPos,
startLoc, options.locations && new line_loc_t);
} }
// Called at the start of the parse and after every token. Skips // Called at the start of the parse and after every token. Skips
// whitespace and comments, and, if `options.trackComments` is on, // whitespace and comments, and.
// will store all skipped comments in `tokComments`.
function skipSpace() { function skipSpace() {
tokComments = null;
while (tokPos < inputLen) { while (tokPos < inputLen) {
var ch = input.charCodeAt(tokPos); var ch = input.charCodeAt(tokPos);
if (ch === 47) { // '/' if (ch === 32) { // ' '
++tokPos;
} else if(ch === 13) {
++tokPos;
var next = input.charCodeAt(tokPos);
if(next === 10) {
++tokPos;
}
if(options.locations) {
++tokCurLine;
tokLineStart = tokPos;
}
} else if (ch === 10) {
++tokPos;
++tokCurLine;
tokLineStart = tokPos;
} else if(ch < 14 && ch > 8) {
++tokPos;
} else if (ch === 47) { // '/'
var next = input.charCodeAt(tokPos+1); var next = input.charCodeAt(tokPos+1);
if (next === 42) { // '*' if (next === 42) { // '*'
skipBlockComment(); skipBlockComment();
} else if (next === 47) { // '/' } else if (next === 47) { // '/'
skipLineComment(); skipLineComment();
} else break; } else break;
} else if (ch < 14 && ch > 8) { } else if ((ch < 14 && ch > 8) || ch === 32 || ch === 160) { // ' ', '\xa0'
++tokPos;
} else if (ch === 32 || ch === 160) { // ' ', '\xa0'
++tokPos; ++tokPos;
} else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) { } else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {
++tokPos; ++tokPos;
@ -487,26 +554,71 @@
// The `forceRegexp` parameter is used in the one case where the // The `forceRegexp` parameter is used in the one case where the
// `tokRegexpAllowed` trick does not work. See `parseStatement`. // `tokRegexpAllowed` trick does not work. See `parseStatement`.
function readToken(forceRegexp) { function readToken_dot() {
tokStart = tokPos;
if (options.locations) tokStartLoc = curLineLoc();
tokCommentsBefore = tokComments;
if (forceRegexp) return readRegexp();
if (tokPos >= inputLen) return finishToken(_eof);
var code = input.charCodeAt(tokPos);
// Identifier or keyword. '\uXXXX' sequences are allowed in
// identifiers, so '\' also dispatches to that.
if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord();
var next = input.charCodeAt(tokPos+1); var next = input.charCodeAt(tokPos+1);
if (next >= 48 && next <= 57) return readNumber(true);
++tokPos;
return finishToken(_dot);
}
function readToken_slash() { // '/'
var next = input.charCodeAt(tokPos+1);
if (tokRegexpAllowed) {++tokPos; return readRegexp();}
if (next === 61) return finishOp(_assign, 2);
return finishOp(_slash, 1);
}
function readToken_mult_modulo() { // '%*'
var next = input.charCodeAt(tokPos+1);
if (next === 61) return finishOp(_assign, 2);
return finishOp(_bin10, 1);
}
function readToken_pipe_amp(code) { // '|&'
var next = input.charCodeAt(tokPos+1);
if (next === code) return finishOp(code === 124 ? _bin1 : _bin2, 2);
if (next === 61) return finishOp(_assign, 2);
return finishOp(code === 124 ? _bin3 : _bin5, 1);
}
function readToken_caret() { // '^'
var next = input.charCodeAt(tokPos+1);
if (next === 61) return finishOp(_assign, 2);
return finishOp(_bin4, 1);
}
function readToken_plus_min(code) { // '+-'
var next = input.charCodeAt(tokPos+1);
if (next === code) return finishOp(_incdec, 2);
if (next === 61) return finishOp(_assign, 2);
return finishOp(_plusmin, 1);
}
function readToken_lt_gt(code) { // '<>'
var next = input.charCodeAt(tokPos+1);
var size = 1;
if (next === code) {
size = code === 62 && input.charCodeAt(tokPos+2) === 62 ? 3 : 2;
if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1);
return finishOp(_bin8, size);
}
if (next === 61)
size = input.charCodeAt(tokPos+2) === 61 ? 3 : 2;
return finishOp(_bin7, size);
}
function readToken_eq_excl(code) { // '=!'
var next = input.charCodeAt(tokPos+1);
if (next === 61) return finishOp(_bin6, input.charCodeAt(tokPos+2) === 61 ? 3 : 2);
return finishOp(code === 61 ? _eq : _prefix, 1);
}
function getTokenFromCode(code) {
switch(code) { switch(code) {
// The interpretation of a dot depends on whether it is followed // The interpretation of a dot depends on whether it is followed
// by a digit. // by a digit.
case 46: // '.' case 46: // '.'
if (next >= 48 && next <= 57) return readNumber(String.fromCharCode(code)); return readToken_dot();
++tokPos;
return finishToken(_dot);
// Punctuation tokens. // Punctuation tokens.
case 40: ++tokPos; return finishToken(_parenL); case 40: ++tokPos; return finishToken(_parenL);
@ -522,11 +634,12 @@
// '0x' is a hexadecimal number. // '0x' is a hexadecimal number.
case 48: // '0' case 48: // '0'
var next = input.charCodeAt(tokPos+1);
if (next === 120 || next === 88) return readHexNumber(); if (next === 120 || next === 88) return readHexNumber();
// Anything else beginning with a digit is an integer, octal // Anything else beginning with a digit is an integer, octal
// number, or float. // number, or float.
case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 1-9 case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 1-9
return readNumber(String.fromCharCode(code)); return readNumber(false);
// Quotes produce strings. // Quotes produce strings.
case 34: case 39: // '"', "'" case 34: case 39: // '"', "'"
@ -538,52 +651,54 @@
// of the type given by its first argument. // of the type given by its first argument.
case 47: // '/' case 47: // '/'
if (tokRegexpAllowed) {++tokPos; return readRegexp();} return readToken_slash(code);
if (next === 61) return finishOp(_assign, 2);
return finishOp(_slash, 1);
case 37: case 42: // '%*' case 37: case 42: // '%*'
if (next === 61) return finishOp(_assign, 2); return readToken_mult_modulo();
return finishOp(_bin10, 1);
case 124: case 38: // '|&' case 124: case 38: // '|&'
if (next === code) return finishOp(code === 124 ? _bin1 : _bin2, 2); return readToken_pipe_amp(code);
if (next === 61) return finishOp(_assign, 2);
return finishOp(code === 124 ? _bin3 : _bin5, 1);
case 94: // '^' case 94: // '^'
if (next === 61) return finishOp(_assign, 2); return readToken_caret();
return finishOp(_bin4, 1);
case 43: case 45: // '+-' case 43: case 45: // '+-'
if (next === code) return finishOp(_incdec, 2); return readToken_plus_min(code);
if (next === 61) return finishOp(_assign, 2);
return finishOp(_plusmin, 1);
case 60: case 62: // '<>' case 60: case 62: // '<>'
var size = 1; return readToken_lt_gt(code);
if (next === code) {
size = code === 62 && input.charCodeAt(tokPos+2) === 62 ? 3 : 2;
if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1);
return finishOp(_bin8, size);
}
if (next === 61)
size = input.charCodeAt(tokPos+2) === 61 ? 3 : 2;
return finishOp(_bin7, size);
case 61: case 33: // '=!' case 61: case 33: // '=!'
if (next === 61) return finishOp(_bin6, input.charCodeAt(tokPos+2) === 61 ? 3 : 2); return readToken_eq_excl(code);
return finishOp(code === 61 ? _eq : _prefix, 1);
case 126: // '~' case 126: // '~'
return finishOp(_prefix, 1); return finishOp(_prefix, 1);
} }
// If we are here, we either found a non-ASCII identifier return false;
// character, or something that's entirely disallowed. }
var ch = String.fromCharCode(code);
if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord(); function readToken(forceRegexp) {
raise(tokPos, "Unexpected character '" + ch + "'"); tokStart = tokPos;
if (options.locations) tokStartLoc = new line_loc_t;
if (forceRegexp) return readRegexp();
if (tokPos >= inputLen) return finishToken(_eof);
var code = input.charCodeAt(tokPos);
// Identifier or keyword. '\uXXXX' sequences are allowed in
// identifiers, so '\' also dispatches to that.
if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord();
var tok = getTokenFromCode(code);
if (tok === false) {
// If we are here, we either found a non-ASCII identifier
// character, or something that's entirely disallowed.
var ch = String.fromCharCode(code);
if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord();
raise(tokPos, "Unexpected character '" + ch + "'");
}
return tok;
} }
function finishOp(type, size) { function finishOp(type, size) {
@ -624,7 +739,7 @@
function readInt(radix, len) { function readInt(radix, len) {
var start = tokPos, total = 0; var start = tokPos, total = 0;
for (;;) { for (var i = 0, e = len == null ? Infinity : len; i < e; ++i) {
var code = input.charCodeAt(tokPos), val; var code = input.charCodeAt(tokPos), val;
if (code >= 97) val = code - 97 + 10; // a if (code >= 97) val = code - 97 + 10; // a
else if (code >= 65) val = code - 65 + 10; // A else if (code >= 65) val = code - 65 + 10; // A
@ -649,18 +764,18 @@
// Read an integer, octal integer, or floating-point number. // Read an integer, octal integer, or floating-point number.
function readNumber(ch) { function readNumber(startsWithDot) {
var start = tokPos, isFloat = ch === "."; var start = tokPos, isFloat = false, octal = input.charCodeAt(tokPos) === 48;
if (!isFloat && readInt(10) == null) raise(start, "Invalid number"); if (!startsWithDot && readInt(10) === null) raise(start, "Invalid number");
if (isFloat || input.charAt(tokPos) === ".") { if (input.charCodeAt(tokPos) === 46) {
var next = input.charAt(++tokPos); ++tokPos;
if (next === "-" || next === "+") ++tokPos; readInt(10);
if (readInt(10) === null && ch === ".") raise(start, "Invalid number");
isFloat = true; isFloat = true;
} }
if (/e/i.test(input.charAt(tokPos))) { var next = input.charCodeAt(tokPos);
var next = input.charAt(++tokPos); if (next === 69 || next === 101) { // 'eE'
if (next === "-" || next === "+") ++tokPos; next = input.charCodeAt(++tokPos);
if (next === 43 || next === 45) ++tokPos; // '+-'
if (readInt(10) === null) raise(start, "Invalid number") if (readInt(10) === null) raise(start, "Invalid number")
isFloat = true; isFloat = true;
} }
@ -668,7 +783,7 @@
var str = input.slice(start, tokPos), val; var str = input.slice(start, tokPos), val;
if (isFloat) val = parseFloat(str); if (isFloat) val = parseFloat(str);
else if (ch !== "0" || str.length === 1) val = parseInt(str, 10); else if (!octal || str.length === 1) val = parseInt(str, 10);
else if (/[89]/.test(str) || strict) raise(start, "Invalid number"); else if (/[89]/.test(str) || strict) raise(start, "Invalid number");
else val = parseInt(str, 8); else val = parseInt(str, 8);
return finishToken(_num, val); return finishToken(_num, val);
@ -678,13 +793,13 @@
function readString(quote) { function readString(quote) {
tokPos++; tokPos++;
var str = []; var out = "";
for (;;) { for (;;) {
if (tokPos >= inputLen) raise(tokStart, "Unterminated string constant"); if (tokPos >= inputLen) raise(tokStart, "Unterminated string constant");
var ch = input.charCodeAt(tokPos); var ch = input.charCodeAt(tokPos);
if (ch === quote) { if (ch === quote) {
++tokPos; ++tokPos;
return finishToken(_string, String.fromCharCode.apply(null, str)); return finishToken(_string, out);
} }
if (ch === 92) { // '\' if (ch === 92) { // '\'
ch = input.charCodeAt(++tokPos); ch = input.charCodeAt(++tokPos);
@ -695,28 +810,30 @@
++tokPos; ++tokPos;
if (octal) { if (octal) {
if (strict) raise(tokPos - 2, "Octal literal in strict mode"); if (strict) raise(tokPos - 2, "Octal literal in strict mode");
str.push(parseInt(octal, 8)); out += String.fromCharCode(parseInt(octal, 8));
tokPos += octal.length - 1; tokPos += octal.length - 1;
} else { } else {
switch (ch) { switch (ch) {
case 110: str.push(10); break; // 'n' -> '\n' case 110: out += "\n"; break; // 'n' -> '\n'
case 114: str.push(13); break; // 'r' -> '\r' case 114: out += "\r"; break; // 'r' -> '\r'
case 120: str.push(readHexChar(2)); break; // 'x' case 120: out += String.fromCharCode(readHexChar(2)); break; // 'x'
case 117: str.push(readHexChar(4)); break; // 'u' case 117: out += String.fromCharCode(readHexChar(4)); break; // 'u'
case 85: str.push(readHexChar(8)); break; // 'U' case 85: out += String.fromCharCode(readHexChar(8)); break; // 'U'
case 116: str.push(9); break; // 't' -> '\t' case 116: out += "\t"; break; // 't' -> '\t'
case 98: str.push(8); break; // 'b' -> '\b' case 98: out += "\b"; break; // 'b' -> '\b'
case 118: str.push(11); break; // 'v' -> '\u000b' case 118: out += "\v"; break; // 'v' -> '\u000b'
case 102: str.push(12); break; // 'f' -> '\f' case 102: out += "\f"; break; // 'f' -> '\f'
case 48: str.push(0); break; // 0 -> '\0' case 48: out += "\0"; break; // 0 -> '\0'
case 13: if (input.charCodeAt(tokPos) === 10) ++tokPos; // '\r\n' case 13: if (input.charCodeAt(tokPos) === 10) ++tokPos; // '\r\n'
case 10: break; // ' \n' case 10: // ' \n'
default: str.push(ch); break; if (options.locations) { tokLineStart = tokPos; ++tokCurLine; }
break;
default: out += String.fromCharCode(ch); break;
} }
} }
} else { } else {
if (ch === 13 || ch === 10 || ch === 8232 || ch === 8329) raise(tokStart, "Unterminated string constant"); if (ch === 13 || ch === 10 || ch === 8232 || ch === 8329) raise(tokStart, "Unterminated string constant");
if (ch !== 92) str.push(ch); // '\' out += String.fromCharCode(ch); // '\'
++tokPos; ++tokPos;
} }
} }
@ -827,65 +944,51 @@
readToken(); readToken();
} }
// Start an AST node, attaching a start offset and optionally a // Start an AST node, attaching a start offset.
// `commentsBefore` property to it.
function node_t() {
this.type = null;
this.start = tokStart;
this.end = null;
}
function node_loc_t() {
this.start = tokStartLoc;
this.end = null;
if (sourceFile !== null) this.source = sourceFile;
}
function startNode() { function startNode() {
var node = {type: null, start: tokStart, end: null}; var node = new node_t();
if (options.trackComments && tokCommentsBefore) {
node.commentsBefore = tokCommentsBefore;
tokCommentsBefore = null;
}
if (options.locations) if (options.locations)
node.loc = {start: tokStartLoc, end: null, source: sourceFile}; node.loc = new node_loc_t();
if (options.ranges) if (options.ranges)
node.range = [tokStart, 0]; node.range = [tokStart, 0];
return node; return node;
} }
// Start a node whose start offset/comments information should be // Start a node whose start offset information should be based on
// based on the start of another node. For example, a binary // the start of another node. For example, a binary operator node is
// operator node is only started after its left-hand side has // only started after its left-hand side has already been parsed.
// already been parsed.
function startNodeFrom(other) { function startNodeFrom(other) {
var node = {type: null, start: other.start}; var node = new node_t();
if (other.commentsBefore) { node.start = other.start;
node.commentsBefore = other.commentsBefore; if (options.locations) {
other.commentsBefore = null; node.loc = new node_loc_t();
node.loc.start = other.loc.start;
} }
if (options.locations)
node.loc = {start: other.loc.start, end: null, source: other.loc.source};
if (options.ranges) if (options.ranges)
node.range = [other.range[0], 0]; node.range = [other.range[0], 0];
return node; return node;
} }
// Finish an AST node, adding `type`, `end`, and `commentsAfter` // Finish an AST node, adding `type` and `end` properties.
// properties.
//
// We keep track of the last node that we finished, in order
// 'bubble' `commentsAfter` properties up to the biggest node. I.e.
// in '`1 + 1 // foo', the comment should be attached to the binary
// operator node, not the second literal node.
var lastFinishedNode;
function finishNode(node, type) { function finishNode(node, type) {
node.type = type; node.type = type;
node.end = lastEnd; node.end = lastEnd;
if (options.trackComments) {
if (tokCommentsAfter) {
node.commentsAfter = tokCommentsAfter;
tokCommentsAfter = null;
} else if (lastFinishedNode && lastFinishedNode.end === lastEnd &&
lastFinishedNode.commentsAfter) {
node.commentsAfter = lastFinishedNode.commentsAfter;
lastFinishedNode.commentsAfter = null;
}
lastFinishedNode = node;
}
if (options.locations) if (options.locations)
node.loc.end = lastEndLoc; node.loc.end = lastEndLoc;
if (options.ranges) if (options.ranges)
@ -956,9 +1059,8 @@
// to its body instead of creating a new node. // to its body instead of creating a new node.
function parseTopLevel(program) { function parseTopLevel(program) {
initTokenState();
lastStart = lastEnd = tokPos; lastStart = lastEnd = tokPos;
if (options.locations) lastEndLoc = curLineLoc(); if (options.locations) lastEndLoc = new line_loc_t;
inFunction = strict = null; inFunction = strict = null;
labels = []; labels = [];
readToken(); readToken();
@ -972,7 +1074,7 @@
first = false; first = false;
} }
return finishNode(node, "Program"); return finishNode(node, "Program");
}; }
var loopLabel = {kind: "loop"}, switchLabel = {kind: "switch"}; var loopLabel = {kind: "loop"}, switchLabel = {kind: "switch"};
@ -1018,6 +1120,7 @@
case _debugger: case _debugger:
next(); next();
semicolon();
return finishNode(node, "DebuggerStatement"); return finishNode(node, "DebuggerStatement");
case _do: case _do:
@ -1117,6 +1220,7 @@
if (newline.test(input.slice(lastEnd, tokStart))) if (newline.test(input.slice(lastEnd, tokStart)))
raise(lastEnd, "Illegal newline after throw"); raise(lastEnd, "Illegal newline after throw");
node.argument = parseExpression(); node.argument = parseExpression();
semicolon();
return finishNode(node, "ThrowStatement"); return finishNode(node, "ThrowStatement");
case _try: case _try:
@ -1182,6 +1286,7 @@
var kind = tokType.isLoop ? "loop" : tokType === _switch ? "switch" : null; var kind = tokType.isLoop ? "loop" : tokType === _switch ? "switch" : null;
labels.push({name: maybeName, kind: kind}); labels.push({name: maybeName, kind: kind});
node.body = parseStatement(); node.body = parseStatement();
labels.pop();
node.label = expr; node.label = expr;
return finishNode(node, "LabeledStatement"); return finishNode(node, "LabeledStatement");
} else { } else {
@ -1429,6 +1534,7 @@
case _null: case _true: case _false: case _null: case _true: case _false:
var node = startNode(); var node = startNode();
node.value = tokType.atomValue; node.value = tokType.atomValue;
node.raw = tokType.keyword
next(); next();
return finishNode(node, "Literal"); return finishNode(node, "Literal");
@ -1476,7 +1582,7 @@
function parseNew() { function parseNew() {
var node = startNode(); var node = startNode();
next(); next();
node.callee = parseSubscripts(parseExprAtom(false), true); node.callee = parseSubscripts(parseExprAtom(), true);
if (eat(_parenL)) node.arguments = parseExprList(_parenR, false); if (eat(_parenL)) node.arguments = parseExprList(_parenR, false);
else node.arguments = []; else node.arguments = [];
return finishNode(node, "NewExpression"); return finishNode(node, "NewExpression");
@ -1503,7 +1609,7 @@
isGetSet = sawGetSet = true; isGetSet = sawGetSet = true;
kind = prop.kind = prop.key.name; kind = prop.kind = prop.key.name;
prop.key = parsePropertyName(); prop.key = parsePropertyName();
if (!tokType === _parenL) unexpected(); if (tokType !== _parenL) unexpected();
prop.value = parseFunction(startNode(), false); prop.value = parseFunction(startNode(), false);
} else unexpected(); } else unexpected();
@ -1601,4 +1707,4 @@
return finishNode(node, "Identifier"); return finishNode(node, "Identifier");
} }
})(typeof exports === "undefined" ? (window.acorn = {}) : exports); })(typeof exports === "undefined" ? (self.acorn = {}) : exports);