// the fact that new Object['toString'] fails to parse in FF.
// It introduces no problem since there are no right-associative
// binary operators with precedence 2 or 5.
left = parseOp(opprec + 1, insertionProtected);
} else {
throw new ParseException(
new Message(MessageType.UNEXPECTED_TOKEN, t.pos,
MessagePart.Factory.valueOf(t.text)));
}
if (op == Operator.CONSTRUCTOR && tq.checkToken(Punctuation.LPAREN)) {
List<Expression> operands = Lists.newArrayList();
operands.add(left);
if (!tq.checkToken(Punctuation.RPAREN)) {
do {
operands.add(parseExpressionPart(true));
} while (tq.checkToken(Punctuation.COMMA));
tq.expectToken(Punctuation.RPAREN);
}
left = new SpecialOperation(posFrom(m), op, operands);
} else {
try {
left = Operation.create(posFrom(m), op, left);
} catch (IllegalArgumentException e) {
throw new ParseException(
new Message(MessageType.ASSIGN_TO_NON_LVALUE, t.pos,
MessagePart.Factory.valueOf(t.text)));
}
}
finish(left, m);
// Not pulling multiple operators off the stack means that
// some prefix operator nestings are impossible. This is intended.
// This prevents such things as (new (++i)).
// This only affects the new operator though since it is the only
// prefix operator with a precedence != 4.
}
if (null == left) {
left = parseExpressionAtom();
}
}
// Parse binary operators, except comma.
while (!tq.isEmpty()) {
Token<JsTokenType> t = tq.peek();
// If it is a binary op then we should consider using it
Operator op = Operator.lookupOperation(t.text, OperatorType.INFIX);
if (null == op) {
op = Operator.lookupOperation(t.text, OperatorType.BRACKET);
if (null == op) {
op = Operator.lookupOperation(t.text, OperatorType.TERNARY);
// Check for semicolon insertion since postfix operators are
// "restricted productions" according to ES3 or ES5 S7.9.1.
if (null == op) {
if (!semicolonInserted()) {
op = Operator.lookupOperation(t.text, OperatorType.POSTFIX);
}
if (null == op) { break; }
}
}
} else if (Operator.COMMA == op) {
break;
}
int opprec = op.getPrecedence();
if (!(opprec < precedence
|| (opprec == precedence
&& Associativity.RIGHT == op.getAssociativity()))) {
break;
}
if (op.getType() == OperatorType.BRACKET) {
checkForMissingSemicolon();
}
Mark opStart = tq.mark();
int nMessages = mq.getMessages().size();
tq.advance(); // Consume the operator token
Expression right;
try {
// Recurse to parse operator arguments.
if (OperatorType.BRACKET == op.getType()) {
if (Operator.FUNCTION_CALL == op) {
List<Expression> actuals;
if (tq.checkToken(op.getClosingSymbol())) {
actuals = Collections.<Expression>emptyList();
} else {
actuals = Lists.newArrayList();
do {
actuals.add(parseExpressionPart(true));
} while (tq.checkToken(Punctuation.COMMA));
tq.expectToken(op.getClosingSymbol());
}
right = new ActualList(actuals);
} else {
right = parseExpressionInt(true);
tq.expectToken(op.getClosingSymbol());
}
} else if (OperatorType.POSTFIX == op.getType()) {
right = null;
} else if (OperatorType.TERNARY == op.getType()) {
right = parseExpressionPart(insertionProtected);
} else if (Operator.MEMBER_ACCESS != op) {
right = parseOp(opprec, insertionProtected);
} else {
// The . operator only accepts a reference on the right.
// No a.b.4 or a.b.(c.d)
right = parseReference(true);
}
} catch (ParseException ex) {
// According to
// http://www.mozilla.org/js/language/js20/rationale/syntax.html
// semicolon insertion requires that we reconsider the decision to
// treat op as a binary op if it could be a prefix op.
// Line-Break Semicolon Insertion
// If the first through the nth tokens of a JavaScript program form
// are grammatically valid but the first through the n+1st tokens
// are not and there is a line break between the nth tokens and the
// n+1st tokens, then the parser tries to parse the program again
// after inserting a VirtualSemicolon token between the nth and the
// n+1st tokens.
if ((Operator.FUNCTION_CALL == op
|| null != Operator.lookupOperation(
op.getOpeningSymbol(), OperatorType.PREFIX))
&& !insertionProtected) {
Mark m3 = tq.mark();
tq.rewind(opStart);
if (semicolonInserted()) {
List<Message> messages = mq.getMessages();
if (nMessages < messages.size()) {
messages.subList(nMessages, messages.size()).clear();
}
FilePosition semiPoint = FilePosition.endOf(tq.lastPosition());
messages.add(new Message(
MessageType.SEMICOLON_INSERTED, semiPoint));
return left;
} else {
tq.rewind(m3);
}
}
throw ex;
}
switch (op.getType()) {
case TERNARY:
{
tq.expectToken(op.getClosingSymbol());
Expression farRight = parseExpressionPart(insertionProtected);
left = Operation.create(posFrom(left), op, left, right, farRight);
}
break;
case BRACKET:
assert right != null;
if (Operator.FUNCTION_CALL == op) {
// Function calls can take nothing or multiple on the right, so
// we wrap function calls up in an ActualList.
ActualList actuals = (ActualList) right;
List<? extends Expression> params = actuals.children();
Expression[] operands = new Expression[params.size() + 1];
operands[0] = left;
for (int i = 1; i < operands.length; ++i) {
operands[i] = params.get(i - 1);
}
left = Operation.create(posFrom(left), op, operands);
} else {
left = Operation.create(posFrom(left), op, left, right);
}
break;
case INFIX:
if (op.getCategory() == OperatorCategory.ASSIGNMENT
&& !left.isLeftHandSide()) {
throw new ParseException(
new Message(MessageType.ASSIGN_TO_NON_LVALUE,
t.pos, MessagePart.Factory.valueOf(t.text)));
}
left = Operation.create(posFrom(left), op, left, right);
break;
case POSTFIX:
if (op.getCategory() == OperatorCategory.ASSIGNMENT
&& !left.isLeftHandSide()) {
throw new ParseException(
new Message(MessageType.ASSIGN_TO_NON_LVALUE,
t.pos, MessagePart.Factory.valueOf(t.text)));
}
left = Operation.create(posFrom(left), op, left);
break;