Files

1042 lines
28 KiB
JavaScript

/*
Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com>
Copyright (C) 2013 Thaddee Tyl <thaddee.tyl@gmail.com>
Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
Copyright (C) 2012 Mathias Bynens <mathias@qiwi.be>
Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl>
Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com>
Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com>
Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com>
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function (global) {
'use strict';
var Token,
TokenName,
Syntax,
Messages,
source,
index,
length,
delegate,
lookahead,
state;
Token = {
BooleanLiteral: 1,
EOF: 2,
Identifier: 3,
Keyword: 4,
NullLiteral: 5,
NumericLiteral: 6,
Punctuator: 7,
StringLiteral: 8
};
TokenName = {};
TokenName[Token.BooleanLiteral] = 'Boolean';
TokenName[Token.EOF] = '<end>';
TokenName[Token.Identifier] = 'Identifier';
TokenName[Token.Keyword] = 'Keyword';
TokenName[Token.NullLiteral] = 'Null';
TokenName[Token.NumericLiteral] = 'Numeric';
TokenName[Token.Punctuator] = 'Punctuator';
TokenName[Token.StringLiteral] = 'String';
Syntax = {
ArrayExpression: 'ArrayExpression',
BinaryExpression: 'BinaryExpression',
CallExpression: 'CallExpression',
ConditionalExpression: 'ConditionalExpression',
EmptyStatement: 'EmptyStatement',
ExpressionStatement: 'ExpressionStatement',
Identifier: 'Identifier',
Literal: 'Literal',
LabeledStatement: 'LabeledStatement',
LogicalExpression: 'LogicalExpression',
MemberExpression: 'MemberExpression',
ObjectExpression: 'ObjectExpression',
Program: 'Program',
Property: 'Property',
ThisExpression: 'ThisExpression',
UnaryExpression: 'UnaryExpression'
};
// Error messages should be identical to V8.
Messages = {
UnexpectedToken: 'Unexpected token %0',
UnknownLabel: 'Undefined label \'%0\'',
Redeclaration: '%0 \'%1\' has already been declared'
};
// Ensure the condition is true, otherwise throw an error.
// This is only to have a better contract semantic, i.e. another safety net
// to catch a logic error. The condition shall be fulfilled in normal case.
// Do NOT use this to enforce a certain condition on any user input.
function assert(condition, message) {
if (!condition) {
throw new Error('ASSERT: ' + message);
}
}
function isDecimalDigit(ch) {
return (ch >= 48 && ch <= 57); // 0..9
}
// 7.2 White Space
function isWhiteSpace(ch) {
return (ch === 32) || // space
(ch === 9) || // tab
(ch === 0xB) ||
(ch === 0xC) ||
(ch === 0xA0) ||
(ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCode(ch)) > 0);
}
// 7.3 Line Terminators
function isLineTerminator(ch) {
return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029);
}
// 7.6 Identifier Names and Identifiers
function isIdentifierStart(ch) {
return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore)
(ch >= 65 && ch <= 90) || // A..Z
(ch >= 97 && ch <= 122); // a..z
}
function isIdentifierPart(ch) {
return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore)
(ch >= 65 && ch <= 90) || // A..Z
(ch >= 97 && ch <= 122) || // a..z
(ch >= 48 && ch <= 57); // 0..9
}
// 7.6.1.1 Keywords
function isKeyword(id) {
return (id === 'this')
}
// 7.4 Comments
function skipWhitespace() {
while (index < length && isWhiteSpace(source.charCodeAt(index))) {
++index;
}
}
function getIdentifier() {
var start, ch;
start = index++;
while (index < length) {
ch = source.charCodeAt(index);
if (isIdentifierPart(ch)) {
++index;
} else {
break;
}
}
return source.slice(start, index);
}
function scanIdentifier() {
var start, id, type;
start = index;
id = getIdentifier();
// There is no keyword or literal with only one character.
// Thus, it must be an identifier.
if (id.length === 1) {
type = Token.Identifier;
} else if (isKeyword(id)) {
type = Token.Keyword;
} else if (id === 'null') {
type = Token.NullLiteral;
} else if (id === 'true' || id === 'false') {
type = Token.BooleanLiteral;
} else {
type = Token.Identifier;
}
return {
type: type,
value: id,
range: [start, index]
};
}
// 7.7 Punctuators
function scanPunctuator() {
var start = index,
code = source.charCodeAt(index),
code2,
ch1 = source[index],
ch2;
switch (code) {
// Check for most common single-character punctuators.
case 46: // . dot
case 40: // ( open bracket
case 41: // ) close bracket
case 59: // ; semicolon
case 44: // , comma
case 123: // { open curly brace
case 125: // } close curly brace
case 91: // [
case 93: // ]
case 58: // :
case 63: // ?
++index;
return {
type: Token.Punctuator,
value: String.fromCharCode(code),
range: [start, index]
};
default:
code2 = source.charCodeAt(index + 1);
// '=' (char #61) marks an assignment or comparison operator.
if (code2 === 61) {
switch (code) {
case 37: // %
case 38: // &
case 42: // *:
case 43: // +
case 45: // -
case 47: // /
case 60: // <
case 62: // >
case 124: // |
index += 2;
return {
type: Token.Punctuator,
value: String.fromCharCode(code) + String.fromCharCode(code2),
range: [start, index]
};
case 33: // !
case 61: // =
index += 2;
// !== and ===
if (source.charCodeAt(index) === 61) {
++index;
}
return {
type: Token.Punctuator,
value: source.slice(start, index),
range: [start, index]
};
default:
break;
}
}
break;
}
// Peek more characters.
ch2 = source[index + 1];
// Other 2-character punctuators: && ||
if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) {
index += 2;
return {
type: Token.Punctuator,
value: ch1 + ch2,
range: [start, index]
};
}
if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) {
++index;
return {
type: Token.Punctuator,
value: ch1,
range: [start, index]
};
}
throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
}
// 7.8.3 Numeric Literals
function scanNumericLiteral() {
var number, start, ch;
ch = source[index];
assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'),
'Numeric literal must start with a decimal digit or a decimal point');
start = index;
number = '';
if (ch !== '.') {
number = source[index++];
ch = source[index];
// Hex number starts with '0x'.
// Octal number starts with '0'.
if (number === '0') {
// decimal number starts with '0' such as '09' is illegal.
if (ch && isDecimalDigit(ch.charCodeAt(0))) {
throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
}
}
while (isDecimalDigit(source.charCodeAt(index))) {
number += source[index++];
}
ch = source[index];
}
if (ch === '.') {
number += source[index++];
while (isDecimalDigit(source.charCodeAt(index))) {
number += source[index++];
}
ch = source[index];
}
if (ch === 'e' || ch === 'E') {
number += source[index++];
ch = source[index];
if (ch === '+' || ch === '-') {
number += source[index++];
}
if (isDecimalDigit(source.charCodeAt(index))) {
while (isDecimalDigit(source.charCodeAt(index))) {
number += source[index++];
}
} else {
throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
}
}
if (isIdentifierStart(source.charCodeAt(index))) {
throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
}
return {
type: Token.NumericLiteral,
value: parseFloat(number),
range: [start, index]
};
}
// 7.8.4 String Literals
function scanStringLiteral() {
var str = '', quote, start, ch, octal = false;
quote = source[index];
assert((quote === '\'' || quote === '"'),
'String literal must starts with a quote');
start = index;
++index;
while (index < length) {
ch = source[index++];
if (ch === quote) {
quote = '';
break;
} else if (ch === '\\') {
ch = source[index++];
if (!ch || !isLineTerminator(ch.charCodeAt(0))) {
switch (ch) {
case 'n':
str += '\n';
break;
case 'r':
str += '\r';
break;
case 't':
str += '\t';
break;
case 'b':
str += '\b';
break;
case 'f':
str += '\f';
break;
case 'v':
str += '\x0B';
break;
default:
str += ch;
break;
}
} else {
if (ch === '\r' && source[index] === '\n') {
++index;
}
}
} else if (isLineTerminator(ch.charCodeAt(0))) {
break;
} else {
str += ch;
}
}
if (quote !== '') {
throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
}
return {
type: Token.StringLiteral,
value: str,
octal: octal,
range: [start, index]
};
}
function isIdentifierName(token) {
return token.type === Token.Identifier ||
token.type === Token.Keyword ||
token.type === Token.BooleanLiteral ||
token.type === Token.NullLiteral;
}
function advance() {
var ch;
skipWhitespace();
if (index >= length) {
return {
type: Token.EOF,
range: [index, index]
};
}
ch = source.charCodeAt(index);
// Very common: ( and ) and ;
if (ch === 40 || ch === 41 || ch === 58) {
return scanPunctuator();
}
// String literal starts with single quote (#39) or double quote (#34).
if (ch === 39 || ch === 34) {
return scanStringLiteral();
}
if (isIdentifierStart(ch)) {
return scanIdentifier();
}
// Dot (.) char #46 can also start a floating-point number, hence the need
// to check the next character.
if (ch === 46) {
if (isDecimalDigit(source.charCodeAt(index + 1))) {
return scanNumericLiteral();
}
return scanPunctuator();
}
if (isDecimalDigit(ch)) {
return scanNumericLiteral();
}
return scanPunctuator();
}
function lex() {
var token;
token = lookahead;
index = token.range[1];
lookahead = advance();
index = token.range[1];
return token;
}
function peek() {
var pos;
pos = index;
lookahead = advance();
index = pos;
}
// Throw an exception
function throwError(token, messageFormat) {
var error,
args = Array.prototype.slice.call(arguments, 2),
msg = messageFormat.replace(
/%(\d)/g,
function (whole, index) {
assert(index < args.length, 'Message reference must be in range');
return args[index];
}
);
error = new Error(msg);
error.index = index;
error.description = msg;
throw error;
}
// Throw an exception because of the token.
function throwUnexpected(token) {
throwError(token, Messages.UnexpectedToken, token.value);
}
// Expect the next token to match the specified punctuator.
// If not, an exception will be thrown.
function expect(value) {
var token = lex();
if (token.type !== Token.Punctuator || token.value !== value) {
throwUnexpected(token);
}
}
// Return true if the next token matches the specified punctuator.
function match(value) {
return lookahead.type === Token.Punctuator && lookahead.value === value;
}
// Return true if the next token matches the specified keyword
function matchKeyword(keyword) {
return lookahead.type === Token.Keyword && lookahead.value === keyword;
}
function consumeSemicolon() {
// Catch the very common case first: immediately a semicolon (char #59).
if (source.charCodeAt(index) === 59) {
lex();
return;
}
skipWhitespace();
if (match(';')) {
lex();
return;
}
if (lookahead.type !== Token.EOF && !match('}')) {
throwUnexpected(lookahead);
}
}
// 11.1.4 Array Initialiser
function parseArrayInitialiser() {
var elements = [];
expect('[');
while (!match(']')) {
if (match(',')) {
lex();
elements.push(null);
} else {
elements.push(parseExpression());
if (!match(']')) {
expect(',');
}
}
}
expect(']');
return delegate.createArrayExpression(elements);
}
// 11.1.5 Object Initialiser
function parseObjectPropertyKey() {
var token;
skipWhitespace();
token = lex();
// Note: This function is called only from parseObjectProperty(), where
// EOF and Punctuator tokens are already filtered out.
if (token.type === Token.StringLiteral || token.type === Token.NumericLiteral) {
return delegate.createLiteral(token);
}
return delegate.createIdentifier(token.value);
}
function parseObjectProperty() {
var token, key;
token = lookahead;
skipWhitespace();
if (token.type === Token.EOF || token.type === Token.Punctuator) {
throwUnexpected(token);
}
key = parseObjectPropertyKey();
expect(':');
return delegate.createProperty('init', key, parseExpression());
}
function parseObjectInitialiser() {
var properties = [];
expect('{');
while (!match('}')) {
properties.push(parseObjectProperty());
if (!match('}')) {
expect(',');
}
}
expect('}');
return delegate.createObjectExpression(properties);
}
// 11.1.6 The Grouping Operator
function parseGroupExpression() {
var expr;
expect('(');
expr = parseExpression();
expect(')');
return expr;
}
// 11.1 Primary Expressions
function parsePrimaryExpression() {
var type, token, expr;
if (match('(')) {
return parseGroupExpression();
}
type = lookahead.type;
if (type === Token.Identifier) {
expr = delegate.createIdentifier(lex().value);
} else if (type === Token.StringLiteral || type === Token.NumericLiteral) {
expr = delegate.createLiteral(lex());
} else if (type === Token.Keyword) {
if (matchKeyword('this')) {
lex();
expr = delegate.createThisExpression();
}
} else if (type === Token.BooleanLiteral) {
token = lex();
token.value = (token.value === 'true');
expr = delegate.createLiteral(token);
} else if (type === Token.NullLiteral) {
token = lex();
token.value = null;
expr = delegate.createLiteral(token);
} else if (match('[')) {
expr = parseArrayInitialiser();
} else if (match('{')) {
expr = parseObjectInitialiser();
}
if (expr) {
return expr;
}
throwUnexpected(lex());
}
// 11.2 Left-Hand-Side Expressions
function parseArguments() {
var args = [];
expect('(');
if (!match(')')) {
while (index < length) {
args.push(parseExpression());
if (match(')')) {
break;
}
expect(',');
}
}
expect(')');
return args;
}
function parseNonComputedProperty() {
var token;
token = lex();
if (!isIdentifierName(token)) {
throwUnexpected(token);
}
return delegate.createIdentifier(token.value);
}
function parseNonComputedMember() {
expect('.');
return parseNonComputedProperty();
}
function parseComputedMember() {
var expr;
expect('[');
expr = parseExpression();
expect(']');
return expr;
}
function parseLeftHandSideExpression() {
var expr, args, property;
expr = parsePrimaryExpression();
while (true) {
if (match('[')) {
property = parseComputedMember();
expr = delegate.createMemberExpression('[', expr, property);
} else if (match('.')) {
property = parseNonComputedMember();
expr = delegate.createMemberExpression('.', expr, property);
} else if (match('(')) {
args = parseArguments();
expr = delegate.createCallExpression(expr, args);
} else {
break;
}
}
return expr;
}
// 11.3 Postfix Expressions
var parsePostfixExpression = parseLeftHandSideExpression;
// 11.4 Unary Operators
function parseUnaryExpression() {
var token, expr;
if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword) {
expr = parsePostfixExpression();
} else if (match('+') || match('-') || match('!')) {
token = lex();
expr = parseUnaryExpression();
expr = delegate.createUnaryExpression(token.value, expr);
} else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) {
throwError({}, Messages.UnexpectedToken);
} else {
expr = parsePostfixExpression();
}
return expr;
}
function binaryPrecedence(token) {
var prec = 0;
if (token.type !== Token.Punctuator && token.type !== Token.Keyword) {
return 0;
}
switch (token.value) {
case '||':
prec = 1;
break;
case '&&':
prec = 2;
break;
case '==':
case '!=':
case '===':
case '!==':
prec = 6;
break;
case '<':
case '>':
case '<=':
case '>=':
case 'instanceof':
prec = 7;
break;
case 'in':
prec = 7;
break;
case '+':
case '-':
prec = 9;
break;
case '*':
case '/':
case '%':
prec = 11;
break;
default:
break;
}
return prec;
}
// 11.5 Multiplicative Operators
// 11.6 Additive Operators
// 11.7 Bitwise Shift Operators
// 11.8 Relational Operators
// 11.9 Equality Operators
// 11.10 Binary Bitwise Operators
// 11.11 Binary Logical Operators
function parseBinaryExpression() {
var expr, token, prec, stack, right, operator, left, i;
left = parseUnaryExpression();
token = lookahead;
prec = binaryPrecedence(token);
if (prec === 0) {
return left;
}
token.prec = prec;
lex();
right = parseUnaryExpression();
stack = [left, token, right];
while ((prec = binaryPrecedence(lookahead)) > 0) {
// Reduce: make a binary expression from the three topmost entries.
while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) {
right = stack.pop();
operator = stack.pop().value;
left = stack.pop();
expr = delegate.createBinaryExpression(operator, left, right);
stack.push(expr);
}
// Shift.
token = lex();
token.prec = prec;
stack.push(token);
expr = parseUnaryExpression();
stack.push(expr);
}
// Final reduce to clean-up the stack.
i = stack.length - 1;
expr = stack[i];
while (i > 1) {
expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i - 2], expr);
i -= 2;
}
return expr;
}
// 11.12 Conditional Operator
function parseConditionalExpression() {
var expr, consequent, alternate;
expr = parseBinaryExpression();
if (match('?')) {
lex();
consequent = parseConditionalExpression();
expect(':');
alternate = parseConditionalExpression();
expr = delegate.createConditionalExpression(expr, consequent, alternate);
}
return expr;
}
// Simplification since we do not support AssignmentExpression.
var parseExpression = parseConditionalExpression;
// Polymer Syntax extensions
// Filter ::
// Identifier
// Identifier "(" ")"
// Identifier "(" FilterArguments ")"
function parseFilter() {
var identifier, args;
identifier = lex();
if (identifier.type !== Token.Identifier) {
throwUnexpected(identifier);
}
args = match('(') ? parseArguments() : [];
return delegate.createFilter(identifier.value, args);
}
// Filters ::
// "|" Filter
// Filters "|" Filter
function parseFilters() {
while (match('|')) {
lex();
parseFilter();
}
}
// TopLevel ::
// LabelledExpressions
// AsExpression
// InExpression
// FilterExpression
// AsExpression ::
// FilterExpression as Identifier
// InExpression ::
// Identifier, Identifier in FilterExpression
// Identifier in FilterExpression
// FilterExpression ::
// Expression
// Expression Filters
function parseTopLevel() {
skipWhitespace();
peek();
var expr = parseExpression();
if (expr) {
if (lookahead.value === ',' || lookahead.value == 'in' &&
expr.type === Syntax.Identifier) {
parseInExpression(expr);
} else {
parseFilters();
if (lookahead.value === 'as') {
parseAsExpression(expr);
} else {
delegate.createTopLevel(expr);
}
}
}
if (lookahead.type !== Token.EOF) {
throwUnexpected(lookahead);
}
}
function parseAsExpression(expr) {
lex(); // as
var identifier = lex().value;
delegate.createAsExpression(expr, identifier);
}
function parseInExpression(identifier) {
var indexName;
if (lookahead.value === ',') {
lex();
if (lookahead.type !== Token.Identifier)
throwUnexpected(lookahead);
indexName = lex().value;
}
lex(); // in
var expr = parseExpression();
parseFilters();
delegate.createInExpression(identifier.name, indexName, expr);
}
function parse(code, inDelegate) {
delegate = inDelegate;
source = code;
index = 0;
length = source.length;
lookahead = null;
state = {
labelSet: {}
};
return parseTopLevel();
}
global.esprima = {
parse: parse
};
})(module.exports);