/***********************************************************************
Copyright 2014 (c) Saair Quaderi <saair.quaderi@gmail.com>
Copyright 2012-2013 (c) Mihai Bazon <mihai.bazon@gmail.com>
UglyflyJS sourcecode can be found here:
https://github.com/quaderi/uglyflyjs
UglyflyJS (by Saair) is a fork of UglifyJS2 (by Mihai Bazon)
Both libraries are released under the BSD 2-Clause License.
***********************************************************************/
/*globals define, module, require */
((typeof define === "function") ? define :
function () { "use strict"; require('./nd').apply(module, arguments); })(
"parse",
[
"defaults",
"find_if",
"js_error",
"precedence",
"predicates",
"is"
],
function (defaults, find_if, js_error, precedence, predicates, is) {
"use strict";
/*jslint nomen: true, plusplus: true */
function tokenizer(AST, $TEXT, filename, html5_comments) {
var S = {
text: $TEXT.replace(/\uFEFF/g, ''),
filename: filename,
pos: 0,
tokpos: 0,
line: 1,
tokline: 0,
col: 0,
tokcol: 0,
newline_before: false,
regex_allowed: false,
comments_before: []
},
prev_was_dot = false,
next_token,
read_string,
skip_multiline_comment,
read_regexp,
EX_EOF = {};
function peek() {
return S.text.charAt(S.pos);
}
function next(signal_eof, in_string) {
var ch = S.text.charAt(S.pos);
++S.pos;
if (signal_eof && !ch) {
throw EX_EOF;
}
switch (ch) {
case "\r":
case "\n":
case "\u2028":
case "\u2029":
S.newline_before = S.newline_before || !in_string;
++S.line;
S.col = 0;
if (!in_string && (ch === "\r") && (peek() === "\n")) {
// treat a \r\n sequence as a single \n
++S.pos;
ch = "\n";
}
break;
default:
++S.col;
break;
}
return ch;
}
function forward(i) {
while (i > 0) {
next();
--i;
}
}
function looking_at(str) {
return S.text.substr(S.pos, str.length) === str;
}
function find(what, signal_eof) {
var pos = S.text.indexOf(what, S.pos);
if (signal_eof && pos === -1) {
throw EX_EOF;
}
return pos;
}
function start_token() {
S.tokline = S.line;
S.tokcol = S.col;
S.tokpos = S.pos;
}
function token(type, value, is_comment) {
var ret,
i,
len;
S.regex_allowed = ((type === "operator" && !predicates.UNARY_POSTFIX(value)) ||
(type === "keyword" && predicates.KEYWORDS_BEFORE_EXPRESSION(value)) ||
(type === "punc" && predicates.PUNC_BEFORE_EXPRESSION(value)));
prev_was_dot = (type === "punc" && value === ".");
ret = {
type: type,
value: value,
line: S.tokline,
col: S.tokcol,
pos: S.tokpos,
endline: S.line,
endcol: S.col,
endpos: S.pos,
nlb: S.newline_before,
file: filename
};
if (!is_comment) {
ret.comments_before = S.comments_before;
S.comments_before = [];
// make note of any newlines in the comments that came before
for (i = 0, len = ret.comments_before.length; i < len; ++i) {
ret.nlb = ret.nlb || ret.comments_before[i].nlb;
}
}
S.newline_before = false;
return new AST.Token(ret);
}
function skip_whitespace() {
while (predicates.WHITESPACE_CHARS(peek())) {
next();
}
}
function read_while(pred) {
var ret = "",
ch = peek(),
i = 0;
while (ch && pred(ch, i)) {
ret += next();
ch = peek();
++i;
}
return ret;
}
function parse_error(err) {
js_error(err, filename, S.tokline, S.tokcol, S.tokpos);
}
function parse_js_number(num) {
if (/^0x[0-9a-f]+$/i.test(num)) { //hex
return parseInt(num.substr(2), 16);
}
if (/^0[0-7]+$/.test(num)) { //oct
return parseInt(num.substr(1), 8);
}
if (/^\d*\.?\d*(?:e[+\-]?\d*(?:\d\.?|\.?\d)\d*)?$/i.test(num)) { //dec
return parseFloat(num);
}
}
function read_num(prefix) {
var has_e = false,
after_e = false,
has_x = false,
has_dot = (prefix === "."),
valid,
num = read_while(function (ch, i) {
var code = ch.charCodeAt(0);
switch (code) {
case 120:
case 88: // xX
if (has_x) {
return false;
}
has_x = true;
return true;
case 101:
case 69: // eE
if (has_x) {
return true;
}
if (has_e) {
return false;
}
has_e = after_e = true;
return true;
case 45: // -
return after_e || (i === 0 && !prefix);
case 43: // +
return after_e;
case 46: // .
after_e = false;
if (!has_dot && !has_x && !has_e) {
has_dot = true;
return true;
}
return false;
default:
after_e = false;
return is.alphanumeric_char(code);
}
});
if (prefix) {
num = prefix + num;
}
valid = parse_js_number(num);
if (!isNaN(valid)) {
return token("num", valid);
}
parse_error("Invalid syntax: " + num);
}
function hex_bytes(n) {
/*jslint bitwise: true */
var num = 0,
digit;
while (n > 0) {
digit = parseInt(next(true), 16);
if (isNaN(digit)) {
parse_error("Invalid hex-character pattern in string");
}
num = (num << 4) | digit;
--n;
}
return num;
}
function read_escaped_char(in_string) {
var ch = next(true, in_string);
switch (ch.charCodeAt(0)) {
case 110:
return "\n";
case 114:
return "\r";
case 116:
return "\t";
case 98:
return "\b";
case 118:
return "\u000b"; // \v
case 102:
return "\f";
case 48:
return String.fromCharCode(0);
case 120:
return String.fromCharCode(hex_bytes(2)); // \x
case 117:
return String.fromCharCode(hex_bytes(4)); // \u
case 10:
return ""; // newline
default:
return ch;
}
}
function with_eof_error(eof_error, cont) {
return function (x) {
try {
return cont(x);
} catch (ex) {
if (ex === EX_EOF) {
parse_error(eof_error);
} else {
throw ex;
}
}
};
}
function read_escaped_string(signal_eof) {
// read OctalEscapeSequence (XXX: deprecated if "strict mode")
// https://github.com/mishoo/UglifyJS/issues/178
var octal_str = "",
reading_octal,
ch;
do {
ch = peek();
reading_octal = (ch &&
(ch >= "0") &&
(ch <= "7") &&
((octal_str.length < 2) ||
((octal_str.length === 2) &&
(octal_str[0] < "4"))));
if (reading_octal) {
octal_str += ch;
next(signal_eof);
}
} while (reading_octal);
return octal_str.length ? String.fromCharCode(parseInt(octal_str, 8)) : read_escaped_char(true);
}
read_string = with_eof_error("Unterminated string constant", function (quote_char) {
var quote = next(),
str_val = "",
str,
end_of_string = false,
tok;
do {
str = next(true);
if (str === "\\") {
str_val += read_escaped_string(true);
} else if (str !== quote) {
str_val += str;
} else {
end_of_string = true;
}
} while (!end_of_string);
tok = token("string", str_val);
tok.quote = quote_char;
return tok;
});
function skip_line_comment(type) {
var regex_allowed = S.regex_allowed,
i = find("\n"),
ret;
if (i === -1) {
ret = S.text.substr(S.pos);
S.pos = S.text.length;
} else {
ret = S.text.substring(S.pos, i);
S.pos = i;
}
S.col = S.tokcol + (S.pos - S.tokpos);
S.comments_before.push(token(type, ret, true));
S.regex_allowed = regex_allowed;
return next_token();
}
skip_multiline_comment = with_eof_error("Unterminated multiline comment", function () {
var regex_allowed = S.regex_allowed,
i = find("*/", true),
text = S.text.substring(S.pos, i),
a = text.split("\n"),
n = a.length,
nlb;
// update stream position
S.pos = i + 2;
S.line += n - 1;
if (n > 1) {
S.col = a[n - 1].length;
} else {
S.col += a[n - 1].length;
}
S.col += 2;
nlb = S.newline_before = S.newline_before || text.indexOf("\n") >= 0;
S.comments_before.push(token("comment2", text, true));
S.regex_allowed = regex_allowed;
S.newline_before = nlb;
return next_token();
});
function read_name() {
var backslash = false,
name = "",
ch = peek(),
escaped = false,
hex;
while (ch) {
if (!backslash) {
if (ch === "\\") {
escaped = backslash = true;
next();
} else if (is.identifier_char(ch)) {
name += next();
} else {
break;
}
} else {
if (ch !== "u") {
parse_error("Expecting UnicodeEscapeSequence -- uXXXX");
}
ch = read_escaped_char();
if (!is.identifier_char(ch)) {
parse_error("Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier");
}
name += ch;
backslash = false;
}
ch = peek();
}
if (predicates.KEYWORDS(name) && escaped) {
hex = name.charCodeAt(0).toString(16).toUpperCase();
name = "\\u" + "0000".substr(hex.length) + hex + name.slice(1);
}
return name;
}
read_regexp = with_eof_error("Unterminated regular expression", function (regexp) {
var prev_backslash = false,
in_class = false,
mods,
ch = next(true);
while (ch) {
if (prev_backslash) {
regexp += "\\" + ch;
prev_backslash = false;
} else if (ch === "\\") {
prev_backslash = true;
} else if (ch === "[") {
in_class = true;
regexp += ch;
} else if (in_class) {
if (ch === "]") {
in_class = false;
}
regexp += ch;
} else if (ch !== "/") {
regexp += ch;
} else {
break;
}
ch = next(true);
}
mods = read_name();
return token("regexp", new RegExp(regexp, mods));
});
function read_operator(prefix) {
function grow(op) {
var bigger;
if (!peek()) {
return op;
}
bigger = op + peek();
if (predicates.OPERATORS(bigger)) {
next();
return grow(bigger);
}
return op;
}
return token("operator", grow(prefix || next()));
}
function handle_slash() {
next();
switch (peek()) {
case "/":
next();
return skip_line_comment("comment1");
case "*":
next();
return skip_multiline_comment();
}
if (S.regex_allowed) {
return read_regexp("");
}
return read_operator("/");
}
function handle_dot() {
next();
if (is.digit(peek().charCodeAt(0))) {
return read_num(".");
}
return token("punc", ".");
}
function read_word() {
var word = read_name();
if (prev_was_dot) {
return token("name", word);
}
if (predicates.KEYWORDS_ATOM(word)) {
return token("atom", word);
}
if (!predicates.KEYWORDS(word)) {
return token("name", word);
}
if (predicates.OPERATORS(word)) {
return token("operator", word);
}
return token("keyword", word);
}
next_token = function (force_regexp) {
var ch,
code;
if (force_regexp) {
return read_regexp(force_regexp);
}
skip_whitespace();
start_token();
if (html5_comments) {
if (looking_at("<!--")) {
forward(4);
return skip_line_comment("comment3");
}
if (looking_at("-->") && S.newline_before) {
forward(3);
return skip_line_comment("comment4");
}
}
ch = peek();
if (!ch) {
return token("eof");
}
code = ch.charCodeAt(0);
switch (code) {
case 34:
case 39:
return read_string(ch);
case 46:
return handle_dot();
case 47:
return handle_slash();
}
if (is.digit(code)) {
return read_num();
}
if (predicates.PUNC_CHARS(ch)) {
return token("punc", next());
}
if (predicates.OPERATOR_CHARS(ch)) {
return read_operator();
}
if (code === 92 || is.identifier_start(code)) {
return read_word();
}
parse_error("Unexpected character '" + ch + "'");
};
next_token.context = function (nc) {
if (nc) {
S = nc;
}
return S;
};
return next_token;
}
/* -----[ Parser ]----- */
function parse(AST, $TEXT, options) {
var S,
statement,
function_,
var_,
const_,
new_,
expr_atom,
array_,
object_,
subscripts,
maybe_unary,
expr_op,
maybe_conditional,
maybe_assign,
expression;
options = defaults(options, {
strict: false,
filename: null,
toplevel: null,
expression: false,
html5_comments: true,
bare_returns: false
});
S = {
input: (typeof $TEXT === "string" ? tokenizer(AST, $TEXT, options.filename, options.html5_comments) : $TEXT),
token: null,
prev: null,
peeked: null,
in_function: 0,
in_directives: true,
in_loop: 0,
labels: []
};
function token_is(type, value) {
return is.token(S.token, type, value);
}
function peek() {
if (!S.peeked) {
S.peeked = S.input();
}
return S.peeked;
}
function next() {
S.prev = S.token;
if (S.peeked) {
S.token = S.peeked;
S.peeked = null;
} else {
S.token = S.input();
}
S.in_directives = S.in_directives && (
S.token.type === "string" || token_is("punc", ";")
);
return S.token;
}
S.token = next();
function prev() {
return S.prev;
}
function croak(msg, line, col, pos) {
var ctx = S.input.context();
line = (line === 0) ? 0 : line || ctx.tokline;
col = (col === 0) ? 0 : col || ctx.tokcol;
pos = (pos === 0) ? 0 : pos || ctx.tokpos;
js_error(msg, ctx.filename, line, col, pos);
}
function token_error(token, msg) {
croak(msg, token.line, token.col);
}
function unexpected() {
var token = S.token;
token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")");
}
function expect_token(type, val) {
var token = S.token;
if (token_is(type, val)) {
return next();
}
token_error(token, "Unexpected token " + token.type + " «" + token.value + "»" + ", expected " + type + " «" + val + "»");
}
function expect(punc) {
return expect_token("punc", punc);
}
function can_insert_semicolon() {
return !options.strict && (
S.token.nlb || token_is("eof") || token_is("punc", "}")
);
}
function semicolon() {
if (token_is("punc", ";")) {
next();
} else if (!can_insert_semicolon()) {
unexpected();
}
}
function parenthesised() {
var exp;
expect("(");
exp = expression(true);
expect(")");
return exp;
}
function embed_tokens(parser) {
return function () {
var start = S.token,
expr = parser(),
end = prev();
expr.start = start;
expr.end = end;
return expr;
};
}
function handle_regexp() {
if (token_is("operator", "/") || token_is("operator", "/=")) {
S.peeked = null;
S.token = S.input(S.token.value.substr(1)); // force regexp
}
}
function simple_statement(tmp) {
tmp = expression(true);
semicolon();
return new AST.SimpleStatement({
body: tmp
});
}
function _make_symbol(SymbolType) {
var token = S.token,
name = token.value,
obj = {
name: String(name),
start: token,
end: token
};
if (name === "this") {
SymbolType = AST.This;
}
return new SymbolType(obj);
}
function as_symbol(type, noerror) {
var sym;
if (!token_is("name")) {
if (!noerror) {
croak("Name expected");
}
return null;
}
sym = _make_symbol(type);
next();
return sym;
}
function break_cont(SymbolType) {
var label = null,
ldef,
stat;
if (!can_insert_semicolon()) {
label = as_symbol(AST.LabelRef, true);
}
if (label) {
ldef = find_if(function (l) {
return (l.name === label.name);
}, S.labels);
if (!ldef) {
croak("Undefined label " + label.name);
}
label.thedef = ldef;
} else if (!S.in_loop) {
croak(SymbolType.TYPE + " not inside a loop or switch");
}
semicolon();
stat = new SymbolType({
label: label
});
if (ldef) {
ldef.references.push(stat);
}
return stat;
}
function in_loop(cont) {
var ret;
++S.in_loop;
ret = cont();
--S.in_loop;
return ret;
}
function regular_for(init) {
var test,
step;
expect(";");
test = token_is("punc", ";") ? null : expression(true);
expect(";");
step = token_is("punc", ")") ? null : expression(true);
expect(")");
return new AST.For({
init: init,
condition: test,
step: step,
body: in_loop(statement)
});
}
function for_in(init) {
var lhs = (init instanceof AST.Var) ? init.definitions[0].name : null,
obj = expression(true);
expect(")");
return new AST.ForIn({
init: init,
name: lhs,
object: obj,
body: in_loop(statement)
});
}
function for_() {
var init = null;
expect("(");
if (!token_is("punc", ";")) {
if (token_is("keyword", "var")) {
next();
init = var_(true);
} else {
init = expression(true, true);
}
if (token_is("operator", "in")) {
if ((init instanceof AST.Var) && (init.definitions.length > 1)) {
croak("Only one variable declaration allowed in for..in loop");
}
next();
return for_in(init);
}
}
return regular_for(init);
}
function labeled_statement() {
var label = as_symbol(AST.Label),
stat;
if (find_if(function (l) {
return l.name === label.name;
},
S.labels)) {
// ECMA-262, 12.12: An ECMAScript program is considered
// syntactically incorrect if it contains a
// LabelledStatement that is enclosed by a
// LabelledStatement with the same Identifier as label.
croak("Label " + label.name + " defined twice");
}
expect(":");
S.labels.push(label);
stat = statement();
S.labels.pop();
if (!(stat instanceof AST.IterationStatement)) {
// check for `continue` that refers to this label.
// those should be reported as syntax errors.
// https://github.com/mishoo/UglifyJS2/issues/287
label.references.forEach(function (ref) {
if (ref instanceof AST.Continue) {
ref = ref.label.start;
croak("Continue label `" + label.name + "` refers to non-IterationStatement.",
ref.line, ref.col, ref.pos);
}
});
}
return new AST.LabeledStatement({
body: stat,
label: label
});
}
function if_() {
var cond = parenthesised(),
body = statement(),
belse = null;
if (token_is("keyword", "else")) {
next();
belse = statement();
}
return new AST.If({
condition: cond,
body: body,
alternative: belse
});
}
function block_() {
var a = [];
expect("{");
while (!token_is("punc", "}")) {
if (token_is("eof")) {
unexpected();
}
a.push(statement());
}
next();
return a;
}
function try_() {
var body = block_(),
bcatch = null,
bfinally = null,
start,
name;
if (token_is("keyword", "catch")) {
start = S.token;
next();
expect("(");
name = as_symbol(AST.SymbolCatch);
expect(")");
bcatch = new AST.Catch({
start: start,
argname: name,
body: block_(),
end: prev()
});
}
if (token_is("keyword", "finally")) {
start = S.token;
next();
bfinally = new AST.Finally({
start: start,
body: block_(),
end: prev()
});
}
if (!bcatch && !bfinally) {
croak("Missing catch/finally blocks");
}
return new AST.Try({
body: body,
bcatch: bcatch,
bfinally: bfinally
});
}
function switch_body_() {
var a = [],
cur = null,
branch = null,
tmp;
expect("{");
while (!token_is("punc", "}")) {
if (token_is("eof")) {
unexpected();
}
if (token_is("keyword", "case")) {
if (branch) {
branch.end = prev();
}
cur = [];
tmp = S.token;
next();
branch = new AST.Case({
start: tmp,
expression: expression(true),
body: cur
});
a.push(branch);
expect(":");
} else if (token_is("keyword", "default")) {
if (branch) {
branch.end = prev();
}
cur = [];
tmp = S.token;
next();
expect(":");
branch = new AST.Default({
start: tmp,
body: cur
});
a.push(branch);
} else {
if (!cur) {
unexpected();
}
cur.push(statement());
}
}
if (branch) {
branch.end = prev();
}
next();
return a;
}
statement = embed_tokens(function () {
var tmp,
dir,
stat,
body;
handle_regexp();
switch (S.token.type) {
case "string":
dir = S.in_directives;
stat = simple_statement();
// XXXv2: decide how to fix directives
if (dir && (stat.body instanceof AST.String) && !token_is("punc", ",")) {
return new AST.Directive({
start: stat.body.start,
end: stat.body.end,
quote: stat.body.quote,
value: stat.body.value
});
}
return stat;
case "num":
case "regexp":
case "operator":
case "atom":
return simple_statement();
case "name":
return is.token(peek(), "punc", ":") ? labeled_statement() : simple_statement();
case "punc":
switch (S.token.value) {
case "{":
return new AST.BlockStatement({
start: S.token,
body: block_(),
end: prev()
});
case "[":
case "(":
return simple_statement();
case ";":
next();
return new AST.EmptyStatement();
default:
unexpected();
break;
}
break;
case "keyword":
tmp = S.token.value;
next();
switch (tmp) {
case "break":
return break_cont(AST.Break);
case "continue":
return break_cont(AST.Continue);
case "debugger":
semicolon();
return new AST.Debugger();
case "do":
body = in_loop(statement);
expect_token("keyword", "while");
tmp = parenthesised();
semicolon();
return new AST.Do({
body: body,
condition: tmp
});
case "while":
return new AST.While({
condition: parenthesised(),
body: in_loop(statement)
});
case "for":
return for_();
case "function":
return function_(AST.Defun);
case "if":
return if_();
case "return":
tmp = null;
if (!S.in_function && !options.bare_returns) {
croak("'return' outside of function");
}
if (token_is("punc", ";")) {
next();
} else if (!can_insert_semicolon()) {
tmp = expression(true);
semicolon();
}
return new AST.Return({
value: tmp
});
case "switch":
return new AST.Switch({
expression: parenthesised(),
body: in_loop(switch_body_)
});
case "throw":
if (S.token.nlb) {
croak("Illegal newline after 'throw'");
}
tmp = expression(true);
semicolon();
return new AST.Throw({
value: tmp
});
case "try":
return try_();
case "var":
tmp = var_();
semicolon();
return tmp;
case "const":
tmp = const_();
semicolon();
return tmp;
case "with":
return new AST.With({
expression: parenthesised(),
body: statement()
});
default:
unexpected();
break;
}
break;
}
});
function_ = function (FunctionType) {
var in_statement = (FunctionType === AST.Defun),
name = null;
if (token_is("name")) {
name = as_symbol(in_statement ? AST.SymbolDefun : AST.SymbolLambda);
}
if (in_statement && !name) {
unexpected();
}
expect("(");
return new FunctionType({
name: name,
argnames: (function (first, a) {
while (!token_is("punc", ")")) {
if (first) {
first = false;
} else {
expect(",");
}
a.push(as_symbol(AST.SymbolFunarg));
}
next();
return a;
}(true, [])),
body: (function (loop, labels) {
var a;
++S.in_function;
S.in_directives = true;
S.in_loop = 0;
S.labels = [];
a = block_();
--S.in_function;
S.in_loop = loop;
S.labels = labels;
return a;
}(S.in_loop, S.labels))
});
};
function vardefs(no_in, in_const) {
var a = [],
name,
val;
while (true) {
val = null;
name = as_symbol(in_const ? AST.SymbolConst : AST.SymbolVar);
if (token_is("operator", "=")) {
next();
val = expression(false, no_in);
}
a.push(new AST.VarDef({
start: S.token,
name: name,
value: val,
end: prev()
}));
if (!token_is("punc", ",")) {
break;
}
next();
}
return a;
}
var_ = function (no_in) {
return new AST.Var({
start: prev(),
definitions: vardefs(no_in, false),
end: prev()
});
};
const_ = function () {
return new AST.Const({
start: prev(),
definitions: vardefs(false, true),
end: prev()
});
};
function expr_list(closing, allow_trailing_comma, allow_empty) {
var first = true,
a = [];
while (!token_is("punc", closing)) {
if (first) {
first = false;
} else {
expect(",");
}
if (allow_trailing_comma && token_is("punc", closing)) {
break;
}
if (token_is("punc", ",") && allow_empty) {
a.push(new AST.Hole({
start: S.token,
end: S.token
}));
} else {
a.push(expression(false));
}
}
next();
return a;
}
new_ = function () {
var start = S.token,
newexp,
args;
expect_token("operator", "new");
newexp = expr_atom(false);
if (token_is("punc", "(")) {
next();
args = expr_list(")");
} else {
args = [];
}
return subscripts(new AST.New({
start: start,
expression: newexp,
args: args,
end: prev()
}), true);
};
function as_atom_node() {
var token = S.token,
ret;
switch (token.type) {
case "name":
case "keyword":
ret = _make_symbol(AST.SymbolRef);
break;
case "num":
ret = new AST.Number({
start: token,
end: token,
value: token.value
});
break;
case "string":
ret = new AST.String({
start: token,
end: token,
value: token.value,
quote: token.quote
});
break;
case "regexp":
ret = new AST.RegExp({
start: token,
end: token,
value: token.value
});
break;
case "atom":
switch (token.value) {
case "false":
ret = new AST.False({
start: token,
end: token
});
break;
case "true":
ret = new AST.True({
start: token,
end: token
});
break;
case "null":
ret = new AST.Null({
start: token,
end: token
});
break;
}
break;
}
next();
return ret;
}
expr_atom = function (allow_calls) {
var start,
func,
ex;
if (token_is("operator", "new")) {
return new_();
}
start = S.token;
if (token_is("punc")) {
switch (start.value) {
case "(":
next();
ex = expression(true);
ex.start = start;
ex.end = S.token;
expect(")");
return subscripts(ex, allow_calls);
case "[":
return subscripts(array_(), allow_calls);
case "{":
return subscripts(object_(), allow_calls);
}
unexpected();
}
if (token_is("keyword", "function")) {
next();
func = function_(AST.Function);
func.start = start;
func.end = prev();
return subscripts(func, allow_calls);
}
switch (S.token.type) {
case "atom":
case "num":
case "string":
case "regexp":
case "name":
return subscripts(as_atom_node(), allow_calls);
}
unexpected();
};
function as_property_name() {
var tmp = S.token;
next();
switch (tmp.type) {
case "num":
case "string":
case "name":
case "operator":
case "keyword":
case "atom":
return tmp.value;
default:
unexpected();
break;
}
}
array_ = embed_tokens(function () {
expect("[");
return new AST.Array({
elements: expr_list("]", !options.strict, true)
});
});
object_ = embed_tokens(function () {
var first = true,
a = [],
start,
type,
name,
wasPushed;
expect("{");
while (!token_is("punc", "}")) {
if (first) {
first = false;
} else {
expect(",");
}
if (!options.strict && token_is("punc", "}")) {
// allow trailing comma
break;
}
start = S.token;
type = start.type;
name = as_property_name();
wasPushed = false;
if (type === "name" && !token_is("punc", ":")) {
if (name === "get") {
a.push(new AST.ObjectGetter({
start: start,
key: as_atom_node(),
value: function_(AST.Accessor),
end: prev()
}));
wasPushed = true;
} else if (name === "set") {
a.push(new AST.ObjectSetter({
start: start,
key: as_atom_node(),
value: function_(AST.Accessor),
end: prev()
}));
wasPushed = true;
}
}
if (!wasPushed) {
expect(":");
a.push(new AST.ObjectKeyVal({
start: start,
quote : start.quote,
key: name,
value: expression(false),
end: prev()
}));
}
}
next();
return new AST.Object({ properties: a });
});
function as_name() {
var tmp = S.token;
next();
switch (tmp.type) {
case "name":
case "operator":
case "keyword":
case "atom":
return tmp.value;
default:
unexpected();
break;
}
}
subscripts = function (expr, allow_calls) {
var start = expr.start,
prop;
if (token_is("punc", ".")) {
next();
return subscripts(new AST.Dot({
start: start,
expression: expr,
property: as_name(),
end: prev()
}), allow_calls);
}
if (token_is("punc", "[")) {
next();
prop = expression(true);
expect("]");
return subscripts(new AST.Sub({
start: start,
expression: expr,
property: prop,
end: prev()
}), allow_calls);
}
if (allow_calls && token_is("punc", "(")) {
next();
return subscripts(new AST.Call({
start: start,
expression: expr,
args: expr_list(")"),
end: prev()
}), true);
}
return expr;
};
function is_assignable(expr) {
if (!options.strict) {
return true;
}
if (expr instanceof AST.This) {
return false;
}
return (expr instanceof AST.PropAccess || expr instanceof AST.Symbol);
}
function make_unary(UnaryOperatorType, op, expr) {
if ((op === "++" || op === "--") && !is_assignable(expr)) {
croak("Invalid use of " + op + " operator");
}
return new UnaryOperatorType({
operator: op,
expression: expr
});
}
maybe_unary = function (allow_calls) {
var start = S.token,
ex,
val;
if (token_is("operator") && predicates.UNARY_PREFIX(start.value)) {
next();
handle_regexp();
ex = make_unary(AST.UnaryPrefix, start.value, maybe_unary(allow_calls));
ex.start = start;
ex.end = prev();
return ex;
}
val = expr_atom(allow_calls);
while (token_is("operator") && predicates.UNARY_POSTFIX(S.token.value) && !S.token.nlb) {
val = make_unary(AST.UnaryPostfix, S.token.value, val);
val.start = start;
val.end = S.token;
next();
}
return val;
};
expr_op = function (left, min_prec, no_in) {
var op = token_is("operator") ? S.token.value : null,
prec,
right;
if (op === "in" && no_in) {
op = null;
}
prec = op ? precedence(op) : null;
if ((prec !== null) && (prec > min_prec)) {
next();
right = expr_op(maybe_unary(true), prec, no_in);
return expr_op(new AST.Binary({
start: left.start,
left: left,
operator: op,
right: right,
end: right.end
}), min_prec, no_in);
}
return left;
};
function expr_ops(no_in) {
return expr_op(maybe_unary(true), 0, no_in);
}
maybe_conditional = function (no_in) {
var start = S.token,
expr = expr_ops(no_in),
yes;
if (token_is("operator", "?")) {
next();
yes = expression(false);
expect(":");
return new AST.Conditional({
start: start,
condition: expr,
consequent: yes,
alternative: expression(false, no_in),
end: prev()
});
}
return expr;
};
maybe_assign = function (no_in) {
var start = S.token,
left = maybe_conditional(no_in),
val = S.token.value;
if (token_is("operator") && predicates.ASSIGNMENT(val)) {
if (is_assignable(left)) {
next();
return new AST.Assign({
start: start,
left: left,
operator: val,
right: maybe_assign(no_in),
end: prev()
});
}
croak("Invalid assignment");
}
return left;
};
expression = function (commas, no_in) {
var start = S.token,
expr = maybe_assign(no_in);
if (commas && token_is("punc", ",")) {
next();
return new AST.Seq({
start: start,
car: expr,
cdr: expression(true, no_in),
end: peek()
});
}
return expr;
};
if (options.expression) {
return expression(true);
}
return (function () {
var start = S.token,
body = [],
end,
toplevel;
while (!token_is("eof")) {
body.push(statement());
}
end = prev();
toplevel = options.toplevel;
if (toplevel) {
toplevel.body = toplevel.body.concat(body);
toplevel.end = end;
} else {
toplevel = new AST.Toplevel({
start: start,
body: body,
end: end
});
}
return toplevel;
}());
}
return parse;
}
);