mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 19:26:42 +08:00
1042 lines
28 KiB
JavaScript
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); |