package org.dynjs.parser.js; import static org.dynjs.parser.js.TokenType.*; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.dynjs.compiler.CompilationContext; import org.dynjs.exception.ThrowException; import org.dynjs.parser.Statement; import org.dynjs.parser.ast.AbstractBinaryExpression; import org.dynjs.parser.ast.ArrayLiteralExpression; import org.dynjs.parser.ast.BlockStatement; import org.dynjs.parser.ast.BreakStatement; import org.dynjs.parser.ast.CaseClause; import org.dynjs.parser.ast.CatchClause; import org.dynjs.parser.ast.ContinueStatement; import org.dynjs.parser.ast.DebuggerStatement; import org.dynjs.parser.ast.DefaultCaseClause; import org.dynjs.parser.ast.DoWhileStatement; import org.dynjs.parser.ast.EmptyStatement; import org.dynjs.parser.ast.Expression; import org.dynjs.parser.ast.ExpressionStatement; import org.dynjs.parser.ast.FunctionDeclaration; import org.dynjs.parser.ast.FunctionDescriptor; import org.dynjs.parser.ast.FunctionExpression; import org.dynjs.parser.ast.IdentifierReferenceExpression; import org.dynjs.parser.ast.IfStatement; import org.dynjs.parser.ast.NamedValue; import org.dynjs.parser.ast.ObjectLiteralExpression; import org.dynjs.parser.ast.Parameter; import org.dynjs.parser.ast.ProgramTree; import org.dynjs.parser.ast.PropertyAssignment; import org.dynjs.parser.ast.PropertyGet; import org.dynjs.parser.ast.PropertySet; import org.dynjs.parser.ast.ReturnStatement; import org.dynjs.parser.ast.StringLiteralExpression; import org.dynjs.parser.ast.SwitchStatement; import org.dynjs.parser.ast.ThrowStatement; import org.dynjs.parser.ast.TryStatement; import org.dynjs.parser.ast.VariableDeclaration; import org.dynjs.parser.ast.VariableStatement; import org.dynjs.parser.ast.WhileStatement; import org.dynjs.parser.ast.WithStatement; import org.dynjs.parser.js.ParserContext.ContextType; import org.dynjs.runtime.ExecutionContext; public class Parser { private CompilationContext compilationContext; private ASTFactory factory; private TokenStream stream; private List<ParserContext> context = new ArrayList<ParserContext>(); private int parens = 0; private boolean forceStrict; public Parser(CompilationContext compilationContext, ASTFactory factory, TokenStream stream) { this.compilationContext = compilationContext; this.factory = factory; this.stream = stream; } public void forceStrict(boolean forceStrict) { this.forceStrict = forceStrict; } public boolean isForceStrict() { return this.forceStrict; } protected TokenType la() { return this.stream.peek(); } protected TokenType la(int pos) { return this.stream.peek(pos); } protected Token laToken() { return this.stream.peekToken(); } protected Token laToken(int pos) { return this.stream.peekToken(pos); } protected Token laToken(boolean skipSkippable) { return this.stream.peekToken(skipSkippable); } protected Token consume() { return this.stream.consume(); } protected Token consume(TokenType type) { Token t = consume(); if (t.getType() != type) { throw new SyntaxError(t, "expected token " + type + " but was '" + t.getText() + "'"); } return t; } protected TokenType la(boolean skipSkippable) { return this.stream.peek(skipSkippable); } protected TokenType la(boolean skipSkippable, int pos) { return this.stream.peek(skipSkippable, pos); } protected Token consume(boolean skipSkippable) { return this.stream.consume(skipSkippable); } protected Token consume(boolean skipSkippable, TokenType type) { Token t = this.stream.consume(skipSkippable); if (t.getType() != type) { throw new SyntaxError(t, "expected token " + type + " but was '" + t.getText() + "'"); } return t; } private void pushContext(ContextType type) { ParserContext parent = currentContext(); ParserContext ctx = new ParserContext(parent, type); if (this.forceStrict || (this.context.size() > 0 && currentContext().isStrict())) { ctx.setStrict(true); } this.context.add(ctx); } private void popContext() { this.context.remove(this.context.size() - 1); } private ParserContext currentContext() { if (this.context.isEmpty()) { return null; } return this.context.get(this.context.size() - 1); } private boolean isValidReturn() { return currentContext().isValidReturn(); } private boolean isValidBreak(String target) { return currentContext().isValidBreak(target); } private boolean isValidContinue(String target) { return currentContext().isValidContinue(target); } private boolean isValidIdentifier(Token token) { if (isReservedWord(token)) { return false; } if (currentContext().isStrict() && isStrictFutureReservedWord(token.getText())) { return false; } return true; } public ProgramTree program() { try { pushContext(ContextType.PROGRAM); List<Statement> statements = sourceElements(); return factory.program(statements, currentContext().isStrict()); } finally { popContext(); } } // ---------------------------------------------------------------------- // Expressions // ---------------------------------------------------------------------- public Expression primaryExpression() { TokenType t = la(); Token token = null; switch (t) { case THIS: return factory.thisExpression(consume()); case IDENTIFIER: token = consume(IDENTIFIER); if (!isValidIdentifier(token)) { throw new SyntaxError(token, "invalid identifier: " + token.getText()); } return factory.identifier(token, token.getText()); case STRING_LITERAL: token = consume(STRING_LITERAL); if (currentContext().isStrict() && token.isEscapedOctalString()) { throw new SyntaxError(token, "octal escapes not allowed in strict mode"); } return factory.stringLiteral(token, token.getText(), token.isEscapedString(), token.isContinuedLine()); case DECIMAL_LITERAL: token = consume(DECIMAL_LITERAL); return factory.decimalLiteral(token, token.getText()); case HEX_LITERAL: token = consume(HEX_LITERAL); return factory.hexLiteral(token, token.getText()); case OCTAL_LITERAL: token = consume(OCTAL_LITERAL); if (currentContext().isStrict()) { throw new SyntaxError(token, "octal literals not allowed in strict mode"); } return factory.octalLiteral(token, token.getText()); case REGEXP_LITERAL: token = consume(REGEXP_LITERAL); return factory.regexpLiteral(token, token.getText()); case TRUE: return factory.trueLiteral(consume()); case FALSE: return factory.falseLiteral(consume()); case NULL: return factory.nullLiteral(consume()); case LEFT_BRACE: return objectLiteral(); case LEFT_BRACKET: return arrayLiteral(); case LEFT_PAREN: consume(LEFT_PAREN); try { ++this.parens; Expression expr = expression(); consume(RIGHT_PAREN); return expr; } finally { --this.parens; } } throw new SyntaxError(laToken(), "unexpected token: " + laToken().getText()); } public Expression expression() { return expression(false); } public Expression expressionNoIn() { return expression(true); } public Expression expression(boolean noIn) { Expression expr = assignmentExpression(noIn); if (la() == COMMA) { consume(); expr = factory.commaOperator(expr, expression(noIn)); } return expr; } public ObjectLiteralExpression objectLiteral() { Token position = consume(LEFT_BRACE); List<PropertyAssignment> propAssignments = new ArrayList<>(); Set<String> values = new HashSet<>(); Set<String> getters = new HashSet<>(); Set<String> setters = new HashSet<>(); while (true) { if (la() == RIGHT_BRACE) { break; } PropertyAssignment assignment = null; Token token = laToken(); switch (token.getText()) { case "get": if (la(2) != COLON) { assignment = propertyGet(); propAssignments.add(assignment); if (values.contains(assignment.getName()) || getters.contains(assignment.getName())) { throw new SyntaxError(token, "duplicate property not allowed: " + assignment.getName()); } getters.add(assignment.getName()); break; } case "set": if (la(2) != COLON) { assignment = propertySet(); propAssignments.add(assignment); if (values.contains(assignment.getName()) || setters.contains(assignment.getName())) { throw new SyntaxError(token, "duplicate property not allowed: " + assignment.getName()); } setters.add(assignment.getName()); break; } default: assignment = namedValue(); propAssignments.add(assignment); if (getters.contains(assignment.getName()) || setters.contains(assignment.getName())) { throw new SyntaxError(token, "duplicate property not allowed: " + assignment.getName()); } if (currentContext().isStrict()) { if (values.contains(assignment.getName())) { throw new SyntaxError(token, "duplicate property not allowed: " + assignment.getName()); } } values.add(assignment.getName()); } if (la() != COMMA) { break; } consume(COMMA); } consume(RIGHT_BRACE); return factory.objectLiteral(position, propAssignments); } protected PropertySet propertySet() { try { pushContext(ContextType.FUNCTION); Token set = consume(IDENTIFIER); if (!set.getText().equals("set")) { throw new SyntaxError("expected 'set', was: " + set); } Token token = laToken(); if (!isPropertyName(token)) { throw new SyntaxError(token, "expected property identifier"); } String name = consume().getText(); consume(LEFT_PAREN); Token param = consume(IDENTIFIER); if (!isAssignableName(param.getText())) { throw new SyntaxError("invalid parameter name: " + param.getText()); } consume(RIGHT_PAREN); Token position = consume(LEFT_BRACE); BlockStatement body = functionBody(); consume(RIGHT_BRACE); return new PropertySet(position, name, param.getText(), body); } finally { popContext(); } } protected PropertyGet propertyGet() { try { pushContext(ContextType.FUNCTION); Token get = consume(IDENTIFIER); if (!get.getText().equals("get")) { throw new SyntaxError("expected 'get', was: " + get); } Token token = laToken(); if (!isPropertyName(token)) { throw new SyntaxError(token, "expected property identifier"); } String name = consume().getText(); consume(LEFT_PAREN); consume(RIGHT_PAREN); Token position = consume(LEFT_BRACE); BlockStatement body = functionBody(); consume(RIGHT_BRACE); return new PropertyGet(position, name, body); } finally { popContext(); } } protected NamedValue namedValue() { Token token = laToken(); if (!isPropertyName(token)) { throw new SyntaxError(token, "expected property identifier"); } String name = consume().getText(); consume(COLON); Expression expr = assignmentExpression(); return new NamedValue(token, name, expr); } protected boolean isPropertyName(Token token) { switch (token.getType()) { case IDENTIFIER: case DECIMAL_LITERAL: case HEX_LITERAL: case STRING_LITERAL: return true; default: return isReservedWord(token); } } protected boolean isAssignableName(String name) { if (isFutureReservedWord(name)) { return false; } if (currentContext().isStrict()) { if (isStrictFutureReservedWord(name) || name.equals("arguments") || name.equals("eval")) { return false; } } return true; } protected boolean isReservedWord(Token token) { return isKeyword(token.getType()) || isFutureReservedWord(token.getText()); } protected boolean isKeyword(TokenType type) { switch (type) { case BREAK: case DO: case INSTANCEOF: case TYPEOF: case CASE: case ELSE: case NEW: case VAR: case CATCH: case FINALLY: case RETURN: case VOID: case CONTINUE: case FOR: case SWITCH: case WHILE: case DEBUGGER: case FUNCTION: case THIS: case WITH: case DEFAULT: case IF: case THROW: case DELETE: case IN: case OF: case TRY: case NULL: case TRUE: case FALSE: return true; } return false; } protected boolean isFutureReservedWord(String name) { switch (name) { case "class": case "enum": case "extends": case "super": case "const": case "export": case "import": return true; } return false; } protected boolean isStrictFutureReservedWord(String name) { switch (name) { case "implements": case "let": case "private": case "public": case "yield": case "interface": case "package": case "protected": case "static": return true; } return false; } public ArrayLiteralExpression arrayLiteral() { Token start = consume(LEFT_BRACKET); List<Expression> exprs = new ArrayList<>(); while (la() != RIGHT_BRACKET) { if (la() == COMMA) { // System.err.println("comma"); consume(); exprs.add(null); continue; } else { // System.err.println("not comma"); exprs.add(assignmentExpression()); if (la() == COMMA) { // System.err.println("followed by comma"); consume(); } else { // System.err.println("followed by not comma"); break; } } } consume(RIGHT_BRACKET); // System.err.println("ARRAY: " + exprs); return factory.arrayLiteral(start, exprs); } public Expression assignmentExpression() { return assignmentExpression(false); } public Expression assignmentExpressionNoIn() { return assignmentExpression(true); } public Expression assignmentExpression(boolean noIn) { Expression expr = conditionalExpression(noIn); switch (la()) { case EQUALS: consume(EQUALS); checkAssignmentLHS(expr); expr = factory.assignmentOperator(expr, assignmentExpression(noIn)); break; case MULTIPLY_EQUALS: consume(MULTIPLY_EQUALS); checkAssignmentLHS(expr); expr = factory.multiplicationAssignmentOperator(expr, assignmentExpression(noIn)); break; case DIVIDE_EQUALS: consume(DIVIDE_EQUALS); checkAssignmentLHS(expr); expr = factory.divisionAssignmentOperator(expr, assignmentExpression(noIn)); break; case MODULO_EQUALS: consume(MODULO_EQUALS); checkAssignmentLHS(expr); expr = factory.moduloAssignmentOperator(expr, assignmentExpression(noIn)); break; case PLUS_EQUALS: consume(PLUS_EQUALS); checkAssignmentLHS(expr); expr = factory.additionAssignmentOperator(expr, assignmentExpression(noIn)); break; case MINUS_EQUALS: consume(MINUS_EQUALS); checkAssignmentLHS(expr); expr = factory.subtractionAssignmentOperator(expr, assignmentExpression(noIn)); break; case LEFT_SHIFT_EQUALS: consume(LEFT_SHIFT_EQUALS); checkAssignmentLHS(expr); expr = factory.leftShiftAssignmentOperator(expr, assignmentExpression(noIn)); break; case RIGHT_SHIFT_EQUALS: consume(RIGHT_SHIFT_EQUALS); checkAssignmentLHS(expr); expr = factory.rightShiftAssignmentOperator(expr, assignmentExpression(noIn)); break; case UNSIGNED_RIGHT_SHIFT_EQUALS: consume(UNSIGNED_RIGHT_SHIFT_EQUALS); checkAssignmentLHS(expr); expr = factory.unsignedRightShiftAssignmentOperator(expr, assignmentExpression(noIn)); break; case BITWISE_AND_EQUALS: consume(BITWISE_AND_EQUALS); checkAssignmentLHS(expr); expr = factory.bitwiseAndAssignmentOperator(expr, assignmentExpression(noIn)); break; case BITWISE_OR_EQUALS: consume(BITWISE_OR_EQUALS); checkAssignmentLHS(expr); expr = factory.bitwiseOrAssignmentOperator(expr, assignmentExpression(noIn)); break; case BITWISE_XOR_EQUALS: consume(BITWISE_XOR_EQUALS); checkAssignmentLHS(expr); expr = factory.bitwiseXorAssignmentOperator(expr, assignmentExpression(noIn)); break; default: return expr; } if (currentContext().isStrict()) { if (expr instanceof AbstractBinaryExpression) { Expression lhs = ((AbstractBinaryExpression) expr).getLhs(); if (lhs instanceof IdentifierReferenceExpression) { String ident = ((IdentifierReferenceExpression) lhs).getIdentifier(); if (!isAssignableName(ident)) { throw new SyntaxError(expr.getPosition(), "invalid identifier for strict-mode: " + ident); } } } } return expr; } protected void checkAssignmentLHS(Expression expr) { if (currentContext().isStrict()) { if (expr instanceof IdentifierReferenceExpression) { String id = ((IdentifierReferenceExpression) expr).getIdentifier(); if (id.equals("eval") || id.equals("arguments")) { throw new ThrowException(null, this.compilationContext.createSyntaxError(id + " may not appear on the left-hand-side of a compound assignment expression in strict mode")); } } } } public Expression conditionalExpression(boolean noIn) { Expression expr = logicalOrExpression(noIn); if (la() == QUESTION) { consume(QUESTION); Expression thenExpr = assignmentExpression(noIn); consume(COLON); Expression elseExpr = assignmentExpression(noIn); return factory.ternaryOperator(expr, thenExpr, elseExpr); } return expr; } public Expression logicalOrExpression(boolean noIn) { Expression expr = logicalAndExpression(noIn); while (la() == LOGICAL_OR) { consume(LOGICAL_OR); expr = factory.logicalOrOperator(expr, logicalAndExpression(noIn)); } return expr; } public Expression logicalAndExpression(boolean noIn) { Expression expr = bitwiseOrExpression(noIn); while (la() == LOGICAL_AND) { consume(LOGICAL_AND); expr = factory.logicalAndOperator(expr, bitwiseOrExpression(noIn)); } return expr; } public Expression bitwiseOrExpression(boolean noIn) { Expression expr = bitwiseXorExpression(noIn); while (la() == BITWISE_OR) { consume(BITWISE_OR); expr = factory.bitwiseOrOperator(expr, bitwiseXorExpression(noIn)); } return expr; } public Expression bitwiseXorExpression(boolean noIn) { Expression expr = bitwiseAndExpression(noIn); while (la() == BITWISE_XOR) { consume(BITWISE_XOR); expr = factory.bitwiseXorOperator(expr, bitwiseAndExpression(noIn)); } return expr; } public Expression bitwiseAndExpression(boolean noIn) { Expression expr = equalityExpression(noIn); while (la() == BITWISE_AND) { consume(BITWISE_AND); expr = factory.bitwiseAndOperator(expr, equalityExpression(noIn)); } return expr; } public Expression equalityExpression(boolean noIn) { Expression expr = relationalExpression(noIn); while (la() == EQUALITY || la() == NOT_EQUALITY || la() == STRICT_EQUALITY || la() == STRICT_NOT_EQUALITY) { switch (la()) { case EQUALITY: consume(); expr = factory.equalityOperator(expr, relationalExpression(noIn)); break; case NOT_EQUALITY: consume(); expr = factory.notEqualityOperator(expr, relationalExpression(noIn)); break; case STRICT_EQUALITY: consume(); expr = factory.strictEqualityOperator(expr, relationalExpression(noIn)); break; case STRICT_NOT_EQUALITY: consume(); expr = factory.strictNotEqualityOperator(expr, relationalExpression(noIn)); break; } } return expr; } public Expression relationalExpression(boolean noIn) { Expression expr = shiftExpression(); while (la() == LESS_THAN || la() == LESS_THAN_EQUAL || la() == GREATER_THAN || la() == GREATER_THAN_EQUAL || la() == INSTANCEOF || (!noIn && (la() == IN || la() == OF))) { switch (la()) { case LESS_THAN: consume(); expr = factory.lessThanOperator(expr, shiftExpression()); break; case LESS_THAN_EQUAL: consume(); expr = factory.lessThanEqualOperator(expr, shiftExpression()); break; case GREATER_THAN: consume(); expr = factory.greaterThanOperator(expr, shiftExpression()); break; case GREATER_THAN_EQUAL: consume(); expr = factory.greaterThanEqualOperator(expr, shiftExpression()); break; case INSTANCEOF: consume(); expr = factory.instanceofOperator(expr, shiftExpression()); break; case IN: if (!noIn) { consume(); expr = factory.inOperator(expr, shiftExpression()); break; } } } return expr; } public Expression shiftExpression() { Expression expr = additiveExpression(); while (la() == LEFT_SHIFT || la() == RIGHT_SHIFT || la() == UNSIGNED_RIGHT_SHIFT) { switch (la()) { case LEFT_SHIFT: consume(); expr = factory.leftShiftOperator(expr, additiveExpression()); break; case RIGHT_SHIFT: consume(); expr = factory.rightShiftOperator(expr, additiveExpression()); break; case UNSIGNED_RIGHT_SHIFT: consume(); expr = factory.unsignedRightShiftOperator(expr, additiveExpression()); break; } } return expr; } public Expression additiveExpression() { Expression expr = multiplicativeExpression(); while (la() == PLUS || la() == MINUS) { switch (la()) { case PLUS: consume(); expr = factory.additionOperator(expr, multiplicativeExpression()); break; case MINUS: consume(); expr = factory.subtractionOperator(expr, multiplicativeExpression()); break; } } return expr; } public Expression multiplicativeExpression() { Expression expr = unaryExpression(); while (la() == MULTIPLY || la() == DIVIDE || la() == MODULO) { switch (la()) { case MULTIPLY: consume(); expr = factory.multiplicationOperator(expr, unaryExpression()); break; case DIVIDE: consume(); expr = factory.divisionOperator(expr, unaryExpression()); break; case MODULO: consume(); expr = factory.moduloOperator(expr, unaryExpression()); break; } } return expr; } public Expression unaryExpression() { Expression expr = null; switch (la()) { case DELETE: consume(); expr = unaryExpression(); if (currentContext().isStrict()) { if (expr instanceof IdentifierReferenceExpression) { throw new ThrowException(null, this.compilationContext.createSyntaxError("cannot delete a direct reference in strict-mode")); } } return factory.deleteOperator(expr); case VOID: consume(); return factory.voidOperator(unaryExpression()); case TYPEOF: consume(); return factory.typeofOperator(unaryExpression()); case PLUS_PLUS: consume(); expr = unaryExpression(); checkAssignmentLHS(expr); return factory.preIncrementOperator(expr); case MINUS_MINUS: consume(); expr = unaryExpression(); checkAssignmentLHS(expr); return factory.preDecrementOperator(expr); case PLUS: consume(); return factory.unaryPlusOperator(unaryExpression()); case MINUS: consume(); return factory.unaryMinusOperator(unaryExpression()); case INVERSION: consume(); return factory.bitwiseInversionOperator(unaryExpression()); case NOT: consume(); return factory.unaryNotOperator(unaryExpression()); default: return postfixExpression(); } } public Expression postfixExpression() { Expression expr = leftHandSideExpression(); switch (la(false)) { case PLUS_PLUS: checkAssignmentLHS(expr); consume(false); return factory.postIncrementOperator(expr); case MINUS_MINUS: checkAssignmentLHS(expr); consume(false); return factory.postDecrementOperator(expr); } return expr; } public Expression leftHandSideExpression() { if (la() == NEW) { return newExpression(); } else { return callExpression(); } } public Expression memberExpression() { Expression expr = null; if (la() == FUNCTION) { expr = functionExpression(); } else { expr = primaryExpression(); } loop: while (true) { switch (la()) { case DOT: consume(DOT); Token token = laToken(); if (!isPropertyName(token)) { throw new SyntaxError(token, "expected property name"); } String identifier = consume().getText(); expr = factory.dotOperator(expr, identifier); break; case LEFT_BRACKET: consume(LEFT_BRACKET); expr = factory.bracketOperator(expr, expression()); consume(RIGHT_BRACKET); break; default: break loop; } } return expr; } public Expression callExpression() { Expression expr = memberExpression(); loop: while (true) { switch (la()) { case DOT: consume(DOT); String identifier = consume().getText(); expr = factory.dotOperator(expr, identifier); break; case LEFT_BRACKET: consume(LEFT_BRACKET); expr = factory.bracketOperator(expr, expression()); consume(RIGHT_BRACKET); break; case LEFT_PAREN: List<Expression> args = arguments(); expr = factory.functionCall(expr, args); break; default: break loop; } } return expr; } public Expression newExpression() { consume(NEW); Expression expr = null; if (la() == NEW) { expr = factory.newOperator(newExpression()); } else { expr = memberExpression(); if (la() == LEFT_PAREN) { List<Expression> args = arguments(); expr = factory.newOperator(expr, args); } else { expr = factory.newOperator(expr); } } loop: while (true) { switch (la()) { case DOT: consume(DOT); String identifier = consume(IDENTIFIER).getText(); expr = factory.dotOperator(expr, identifier); break; case LEFT_BRACKET: consume(LEFT_BRACKET); expr = factory.bracketOperator(expr, expression()); consume(RIGHT_BRACKET); break; case LEFT_PAREN: List<Expression> args = arguments(); expr = factory.functionCall(expr, args); break; default: break loop; } } return expr; } public FunctionExpression functionExpression() { try { pushContext(ContextType.FUNCTION); Token position = consume(FUNCTION); Token identifier = null; String identifierName = null; if (la() == IDENTIFIER) { identifier = consume(IDENTIFIER); if (!isAssignableName(identifier.getText())) { throw new SyntaxError(identifier, "invalid identifier: " + identifier.getText()); } identifierName = identifier.getText(); } List<Parameter> params = formalParameters(); consume(LEFT_BRACE); BlockStatement body = functionBody(); consume(RIGHT_BRACE); return factory.functionExpression(position, identifierName, params, body, currentContext().isStrict()); } finally { popContext(); } } public FunctionDeclaration functionDeclaration() { try { pushContext(ContextType.FUNCTION); Token position = consume(FUNCTION); Token identifier = consume(IDENTIFIER); if (!isAssignableName(identifier.getText())) { throw new SyntaxError(identifier, "invalid identifier: " + identifier.getText()); } List<Parameter> params = formalParameters(); consume(LEFT_BRACE); BlockStatement body = functionBody(); consume(RIGHT_BRACE); return factory.functionDeclaration(position, identifier.getText(), params, body, currentContext().isStrict()); } finally { popContext(); } } public FunctionDescriptor functionDescriptor() { try { pushContext(ContextType.FUNCTION); Token position = consume(FUNCTION); String identifier = null; if (la() == IDENTIFIER) { identifier = consume(IDENTIFIER).getText(); } List<Parameter> params = formalParameters(); consume(LEFT_BRACE); BlockStatement body = functionBody(); consume(RIGHT_BRACE); return new FunctionDescriptor(position, identifier, params, body, currentContext().isStrict()); } finally { popContext(); } } public BlockStatement functionBody() { List<Statement> statements = sourceElements(); return factory.block(statements); } public List<Parameter> formalParameters() { consume(LEFT_PAREN); List<Parameter> params = formalParameterList(); consume(RIGHT_PAREN); return params; } public List<Parameter> formalParameterList() { List<Parameter> params = new ArrayList<>(); while (la() != RIGHT_PAREN) { params.add(parameter()); if (la() == COMMA) { consume(); } else { break; } } if (currentContext().isStrict()) { Set<String> seen = new HashSet<>(); for (Parameter each : params) { if (seen.contains(each.getIdentifier())) { throw new SyntaxError(each.getPosition(), "duplicate formal parameters not allowed in strict-mode"); } seen.add(each.getIdentifier()); } } return params; } public Parameter parameter() { Token identifier = consume(IDENTIFIER); if (!isAssignableName(identifier.getText())) { throw new SyntaxError(identifier, "invalid formal parameter:" + identifier.getText()); } return factory.parameter(identifier, identifier.getText()); } public List<Expression> arguments() { consume(LEFT_PAREN); List<Expression> args = argumentList(); consume(RIGHT_PAREN); return args; } public List<Expression> argumentList() { List<Expression> arguments = new ArrayList<>(); while (la() != RIGHT_PAREN) { arguments.add(assignmentExpression()); if (la() == COMMA) { consume(); } else { break; } } return arguments; } // ---------------------------------------------------------------------- // Statements // ---------------------------------------------------------------------- public List<Statement> sourceElements() { List<Statement> statements = new ArrayList<>(); while (la() != EOF && la() != RIGHT_BRACE) { Statement element = sourceElement(); if (currentContext().isInProlog()) { if (element instanceof ExpressionStatement) { Expression expr = ((ExpressionStatement) element).getExpr(); if (expr instanceof StringLiteralExpression) { if (((StringLiteralExpression) expr).getLiteral().equals("use strict")) { if (!((StringLiteralExpression) expr).isContinuedLine() && !((StringLiteralExpression) expr).isEscaped()) { currentContext().setStrict(true); } } } else { currentContext().setInProlog(false); } } else { currentContext().setInProlog(false); } } statements.add(element); } return statements; } public Statement sourceElement() { if (la() == FUNCTION) { return functionDeclaration(); } return statement(); } public Statement statement() { switch (la()) { case LEFT_BRACE: return block(); case VAR: return variableStatement(); case SEMICOLON: return emptyStatement(); case IF: return ifStatement(); case DO: case FOR: case WHILE: return iterationStatement(); case CONTINUE: return continueStatement(); case BREAK: return breakStatement(); case RETURN: return returnStatement(); case WITH: return withStatement(); case SWITCH: return switchStatement(); case THROW: return throwStatement(); case TRY: return tryStatement(); case DEBUGGER: return debuggerStatement(); case FUNCTION: if (currentContext().isStrict()) { throw new SyntaxError("function declaration in statement not allowed in strict-mode code"); } else { return functionDeclaration(); } } if (la(2) == COLON) { return labelledStatement(); } return expressionStatement(); } protected void semic() { if (la() == EOF) { return; } else if (la() == RIGHT_BRACE) { return; } else if (la() == SEMICOLON) { consume(SEMICOLON); } else { Token next = consume(false); switch (next.getType()) { case CRNL: case CR: case NL: case PARAGRAPH_SEPARATOR: case LINE_SEPARATOR: break; default: throw new SyntaxError(next, "semicolon expected but saw: " + next); } } } public BlockStatement block() { try { pushContext(ContextType.OTHER); consume(LEFT_BRACE); List<Statement> statements = new ArrayList<Statement>(); while (la() != RIGHT_BRACE) { statements.add(statement()); } consume(RIGHT_BRACE); return factory.block(statements); } finally { popContext(); } } public VariableStatement variableStatement() { return variableStatement(false); } public VariableStatement variableStatementNoIn() { return variableStatement(true); } public VariableStatement variableStatement(boolean noIn) { Token position = consume(VAR); List<VariableDeclaration> decls = variableDeclarationList(noIn); semic(); return factory.variableStatement(position, decls); } protected List<VariableDeclaration> variableDeclarationList(boolean noIn) { List<VariableDeclaration> decls = new ArrayList<>(); decls.add(variableDeclaration(noIn)); while (la() == COMMA) { consume(COMMA); decls.add(variableDeclaration(noIn)); } return decls; } public VariableDeclaration variableDeclaration(boolean noIn) { Token identifier = consume(IDENTIFIER); if (!isValidIdentifier(identifier) || !isAssignableName(identifier.getText())) { throw new SyntaxError(identifier, "invalid identifier: " + identifier.getText()); } Expression initializer = null; if (la() == EQUALS) { consume(EQUALS); initializer = assignmentExpression(noIn); } return factory.variableDeclaration(identifier, identifier.getText(), initializer); } public EmptyStatement emptyStatement() { Token position = consume(SEMICOLON); return factory.emptyStatement(position); } public ExpressionStatement expressionStatement() { Expression expr = expression(); semic(); return factory.expressionStatement(expr); } public IfStatement ifStatement() { Token position = consume(IF); consume(LEFT_PAREN); Expression testExpr = expression(); consume(RIGHT_PAREN); Statement body = statement(); if (la() == ELSE) { consume(ELSE); Statement elseBody = statement(); return factory.ifStatement(position, testExpr, body, elseBody); } return factory.ifStatement(position, testExpr, body); } public ContinueStatement continueStatement() { Token position = consume(CONTINUE); String target = null; switch (la(false)) { case CR: case NL: case CRNL: case PARAGRAPH_SEPARATOR: case LINE_SEPARATOR: case SEMICOLON: case RIGHT_BRACE: break; case IDENTIFIER: target = consume(IDENTIFIER).getText(); break; default: throw new SyntaxError(laToken(false), "unexpected token: " + laToken(false).getText()); } if (!isValidContinue(target)) { if (target == null) { throw new SyntaxError(position, "continue not allowed"); } throw new SyntaxError(position, "continue " + target + " not allowed"); } semic(); return factory.continueStatement(position, target); } public BreakStatement breakStatement() { Token position = consume(BREAK); String target = null; switch (la(false)) { case CR: case NL: case CRNL: case PARAGRAPH_SEPARATOR: case LINE_SEPARATOR: case SEMICOLON: case RIGHT_BRACE: break; case IDENTIFIER: target = consume(IDENTIFIER).getText(); break; default: throw new SyntaxError(laToken(false), "unexpected token: " + laToken(false).getText()); } if (!isValidBreak(target)) { if (target == null) { throw new SyntaxError(position, "break not allowed"); } throw new SyntaxError(position, "break " + target + " not allowed"); } semic(); return factory.breakStatement(position, target); } public ReturnStatement returnStatement() { Token position = consume(RETURN); if (!isValidReturn()) { throw new SyntaxError(position, "return not allowed"); } Expression expr = null; switch (la(false)) { case SEMICOLON: consume( SEMICOLON ); case CR: case NL: case CRNL: case PARAGRAPH_SEPARATOR: case LINE_SEPARATOR: case RIGHT_BRACE: return factory.returnStatement(position); default: expr = expression(); } ReturnStatement statement = factory.returnStatement(position, expr); semic(); return statement; } public ThrowStatement throwStatement() { Token position = consume(THROW); switch (la(false)) { case NL: case CR: case CRNL: case LINE_SEPARATOR: case PARAGRAPH_SEPARATOR: case SEMICOLON: throw new SyntaxError("unexpected line terminator after 'throw'"); } ThrowStatement statement = factory.throwStatement(position, expression()); semic(); return statement; } public WithStatement withStatement() { if (currentContext().isStrict()) { throw new SyntaxError("with() statement not allowed in strict-mode code"); } Token position = consume(WITH); consume(LEFT_PAREN); Expression expr = expression(); consume(RIGHT_PAREN); Statement body = statement(); return factory.withStatement(position, expr, body); } public SwitchStatement switchStatement() { try { pushContext(ContextType.SWITCH); Token position = consume(SWITCH); consume(LEFT_PAREN); Expression expr = expression(); consume(RIGHT_PAREN); consume(LEFT_BRACE); List<CaseClause> clauses = new ArrayList<>(); while (la() == CASE) { clauses.add(caseClause()); } if (la() == DEFAULT) { clauses.add(defaultClause()); } while (la() == CASE) { clauses.add(caseClause()); } consume(RIGHT_BRACE); return factory.switchStatement(position, expr, clauses); } finally { popContext(); } } protected CaseClause caseClause() { Token position = consume(CASE); Expression expr = expression(); consume(COLON); List<Statement> statements = new ArrayList<>(); loop: while (true) { TokenType t = la(); switch (t) { case RIGHT_BRACE: case CASE: case DEFAULT: break loop; } statements.add(statement()); } return factory.caseClause(position, expr, statements); } protected DefaultCaseClause defaultClause() { Token position = consume(DEFAULT); consume(COLON); List<Statement> statements = new ArrayList<>(); loop: while (true) { TokenType t = la(); switch (t) { case RIGHT_BRACE: case CASE: case DEFAULT: break loop; } statements.add(statement()); } return factory.defaultClause(position, statements); } public TryStatement tryStatement() { Token position = consume(TRY); BlockStatement block = block(); CatchClause catchClause = null; BlockStatement finallyClause = null; if (la() == CATCH) { catchClause = catchClause(); } if (la() == FINALLY) { finallyClause = finallyClause(); } return factory.tryStatement(position, block, catchClause, finallyClause); } protected CatchClause catchClause() { Token position = consume(CATCH); consume(LEFT_PAREN); String ident = consume(IDENTIFIER).getText(); if (!isAssignableName(ident)) { throw new SyntaxError(position, "invalid identifier: " + ident); } consume(RIGHT_PAREN); BlockStatement block = block(); return factory.catchClause(position, ident, block); } protected BlockStatement finallyClause() { consume(FINALLY); return block(); } public Statement labelledStatement() { String label = consume(IDENTIFIER).getText(); try { currentContext().addLabel(label); consume(COLON); Statement statement = statement(); statement.addLabel(label); return statement; } finally { currentContext().removeLabel(label); } } public DebuggerStatement debuggerStatement() { Token position = consume(DEBUGGER); semic(); return factory.debuggerStatement(position); } public Statement iterationStatement() { try { pushContext(ContextType.ITERATION); switch (la()) { case DO: return doWhileStatement(); case WHILE: return whileStatement(); case FOR: return forStatement(); } throw new SyntaxError("unexpected token: " + laToken().getText()); } finally { popContext(); } } public DoWhileStatement doWhileStatement() { try { pushContext(ContextType.ITERATION); Token position = consume(DO); Statement body = statement(); consume(WHILE); consume(LEFT_PAREN); Expression expr = expression(); consume(RIGHT_PAREN); // don't call semic() here because the semicolon is optional if (la() == SEMICOLON) { consume(SEMICOLON); } return factory.doWhileStatement(position, body, expr); } finally { popContext(); } } public WhileStatement whileStatement() { Token position = consume(WHILE); consume(LEFT_PAREN); Expression expr = expression(); consume(RIGHT_PAREN); Statement body = statement(); return factory.whileStatement(position, expr, body); } public Statement forStatement() { Token position = consume(FOR); consume(LEFT_PAREN); if (la() == VAR) { consume(VAR); List<VariableDeclaration> varDeclList = variableDeclarationList(true); if (la() == IN) { if (varDeclList.size() != 1) { throw new SyntaxError(varDeclList.get(1).getPosition(), "only one variable declaration is allowed"); } // for ( var-decl in ... ) consume(IN); Expression rhs = expression(); consume(RIGHT_PAREN); Statement body = statement(); return factory.forInStatement(position, varDeclList.get(0), rhs, body); } else if (la() == OF) { if (varDeclList.size() != 1) { throw new SyntaxError(varDeclList.get(1).getPosition(), "only one variable declaration is allowed"); } // for ( var-decl of ... ) consume(OF); Expression rhs = expression(); consume(RIGHT_PAREN); Statement body = statement(); return factory.forOfStatement(position, varDeclList.get(0), rhs, body); } else { // for ( var-decl-list ; ... ; ... ) consume(SEMICOLON); Expression middle = null; Expression rhs = null; if (la() != SEMICOLON) { middle = expression(); } consume(SEMICOLON); if (la() != RIGHT_PAREN) { rhs = expression(); } consume(RIGHT_PAREN); Statement body = statement(); return factory.forStatement(position, varDeclList, middle, rhs, body); } } else { Expression initializer = null; if (la() == SEMICOLON) { // for ( ;... ; ... ) // no initializer } else { initializer = expressionNoIn(); } if (la() == IN) { consume(IN); // for ( expr in expr ) Expression rhs = expression(); consume(RIGHT_PAREN); Statement body = statement(); return factory.forInStatement(position, initializer, rhs, body); } else if (la() == OF) { consume(OF); // for ( expr of expr ) Expression rhs = expression(); consume(RIGHT_PAREN); Statement body = statement(); return factory.forOfStatement(position, initializer, rhs, body); } else { // for ( ... ; ... ; ... ) consume(SEMICOLON); Expression middle = null; Expression rhs = null; if (la() != SEMICOLON) { middle = expression(); } consume(SEMICOLON); if (la() != RIGHT_PAREN) { rhs = expression(); } consume(RIGHT_PAREN); Statement body = statement(); return factory.forStatement(position, initializer, middle, rhs, body); } } } }