/**
* This file Copyright (c) 2005-2008 Aptana, Inc. This program is
* dual-licensed under both the Aptana Public License and the GNU General
* Public license. You may elect to use one or the other of these licenses.
*
* This program is distributed in the hope that it will be useful, but
* AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
* NONINFRINGEMENT. Redistribution, except as permitted by whichever of
* the GPL or APL you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or modify this
* program under the terms of the GNU General Public License,
* Version 3, as published by the Free Software Foundation. You should
* have received a copy of the GNU General Public License, Version 3 along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Aptana provides a special exception to allow redistribution of this file
* with certain other free and open source software ("FOSS") code and certain additional terms
* pursuant to Section 7 of the GPL. You may view the exception and these
* terms on the web at http://www.aptana.com/legal/gpl/.
*
* 2. For the Aptana Public License (APL), this program and the
* accompanying materials are made available under the terms of the APL
* v1.0 which accompanies this distribution, and is available at
* http://www.aptana.com/legal/apl/.
*
* You may view the GPL, Aptana's exception and additional terms, and the
* APL in the file titled license.html at the root of the corresponding
* plugin containing this source file.
*
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.ide.editor.js.parsing;
import java.text.ParseException;
import java.util.Arrays;
import com.aptana.ide.editor.js.lexing.JSTokenTypes;
import com.aptana.ide.editor.js.parsing.nodes.JSNaryNode;
import com.aptana.ide.editor.js.parsing.nodes.JSParseNode;
import com.aptana.ide.editor.js.parsing.nodes.JSParseNodeTypes;
import com.aptana.ide.lexer.ILexer;
import com.aptana.ide.lexer.Lexeme;
import com.aptana.ide.lexer.LexemeList;
import com.aptana.ide.lexer.LexerException;
import com.aptana.ide.parsing.ParserInitializationException;
import com.aptana.ide.parsing.nodes.IParseNode;
import com.aptana.ide.parsing.nodes.IParseNodeFactory;
/**
* @author Kevin Lindsey
*/
public class JSParser extends JSParserBase
{
private String _expressionErrorKey;
// sets
private static final int[] additiveExpressionSet = new int[] {
JSTokenTypes.PLUS,
JSTokenTypes.MINUS
};
private static final int[] assignmentOperatorSet = new int[] {
JSTokenTypes.AMPERSAND_EQUAL,
JSTokenTypes.CARET_EQUAL,
JSTokenTypes.EQUAL,
JSTokenTypes.FORWARD_SLASH_EQUAL,
JSTokenTypes.GREATER_GREATER_EQUAL,
JSTokenTypes.GREATER_GREATER_GREATER_EQUAL,
JSTokenTypes.LESS_LESS_EQUAL,
JSTokenTypes.MINUS_EQUAL,
JSTokenTypes.PERCENT_EQUAL,
JSTokenTypes.PIPE_EQUAL,
JSTokenTypes.PLUS_EQUAL,
JSTokenTypes.STAR_EQUAL
};
private static final int[] commentSet = new int[] {
JSTokenTypes.COMMENT,
JSTokenTypes.DOCUMENTATION
};
private static final int[] caseBlockSet = new int[] {
JSTokenTypes.RCURLY,
JSTokenTypes.CASE,
JSTokenTypes.DEFAULT
};
private static final int[] equalityExpressionSet = new int[] {
JSTokenTypes.EQUAL_EQUAL,
JSTokenTypes.EXCLAMATION_EQUAL,
JSTokenTypes.EQUAL_EQUAL_EQUAL,
JSTokenTypes.EXCLAMATION_EQUAL_EQUAL
};
private static final int[] expressionStatementSet = new int[] {
JSTokenTypes.COMMA,
JSTokenTypes.FUNCTION
};
private static final int[] multiplicativeExpressionSet = new int[] {
JSTokenTypes.STAR,
JSTokenTypes.FORWARD_SLASH,
JSTokenTypes.PERCENT
};
private static final int[] postfixExpressionSet = new int[] {
JSTokenTypes.PLUS_PLUS,
JSTokenTypes.MINUS_MINUS
};
private static final int[] postfixMemberExpressionSet = new int[] {
JSTokenTypes.DOT,
JSTokenTypes.LBRACKET,
JSTokenTypes.LPAREN
};
private static final int[] propertyNameSet = new int[] {
JSTokenTypes.IDENTIFIER,
JSTokenTypes.NUMBER,
JSTokenTypes.STRING
};
private static final int[] relationalExpressionSet = new int[] {
JSTokenTypes.LESS,
JSTokenTypes.GREATER,
JSTokenTypes.LESS_EQUAL,
JSTokenTypes.GREATER_EQUAL,
JSTokenTypes.INSTANCEOF,
JSTokenTypes.IN
};
private static final int[] returnSet = new int[] {
JSTokenTypes.SEMICOLON,
JSTokenTypes.RCURLY,
JSTokenTypes.ERROR
};
private static final int[] shiftExpressionSet = new int[] {
JSTokenTypes.LESS_LESS,
JSTokenTypes.GREATER_GREATER,
JSTokenTypes.GREATER_GREATER_GREATER
};
private static final int[] stopSet = new int[] {
JSTokenTypes.SEMICOLON,
JSTokenTypes.RCURLY
};
private static final int[] unaryExpressionSet = new int[] {
JSTokenTypes.DELETE,
JSTokenTypes.EXCLAMATION,
JSTokenTypes.MINUS,
JSTokenTypes.MINUS_MINUS,
JSTokenTypes.PLUS,
JSTokenTypes.PLUS_PLUS,
JSTokenTypes.TILDE,
JSTokenTypes.TYPEOF,
JSTokenTypes.VOID
};
private static final int[] unclosedCommentSet = new int[] {
JSTokenTypes.START_MULTILINE_COMMENT,
JSTokenTypes.START_DOCUMENTATION
};
private static final int[] referenceableSet = new int[] {
JSParseNodeTypes.GET_ELEMENT,
JSParseNodeTypes.GET_PROPERTY,
JSParseNodeTypes.IDENTIFIER
};
private Lexeme prevLexeme;
private static final String DEFAULT_GROUP = "default"; //$NON-NLS-1$
private static final String REGEX_GROUP = "regex"; //$NON-NLS-1$
private static final String ADDITION_GROUP = "addition"; //$NON-NLS-1$
// private static final String ERROR_GROUP = "error"; //$NON-NLS-1$
public static final String DOCUMENTATION_DELIMITER_GROUP = "documentation-delimiter"; //$NON-NLS-1$
public static final String LINE_DELIMITER_GROUP = "line-delimiter"; //$NON-NLS-1$
/**
* static constructor
*/
static
{
// make sure all of our sets are sorted so that inSet will work properly
// (that method uses a binary search to test existence of members in the
// set)
Arrays.sort(additiveExpressionSet);
Arrays.sort(assignmentOperatorSet);
Arrays.sort(caseBlockSet);
Arrays.sort(commentSet);
Arrays.sort(equalityExpressionSet);
Arrays.sort(expressionStatementSet);
Arrays.sort(multiplicativeExpressionSet);
Arrays.sort(postfixExpressionSet);
Arrays.sort(postfixMemberExpressionSet);
Arrays.sort(propertyNameSet);
Arrays.sort(relationalExpressionSet);
Arrays.sort(returnSet);
Arrays.sort(shiftExpressionSet);
Arrays.sort(stopSet);
Arrays.sort(unaryExpressionSet);
Arrays.sort(unclosedCommentSet);
Arrays.sort(referenceableSet);
}
/**
* Create a new instance of JSParser
*
* @throws ParserInitializationException
*/
public JSParser() throws ParserInitializationException
{
this(JSMimeType.MimeType);
}
/**
* Create a new instance of JSParser
*
* @param mimeType
* @throws ParserInitializationException
*/
public JSParser(String mimeType) throws ParserInitializationException
{
super(mimeType);
}
/**
* createNode
*
* @param type
* @param startingLexeme
* @return JSParseNode
*/
private JSParseNode createNode(int type, Lexeme startingLexeme)
{
IParseNodeFactory factory = this.getParseNodeFactory();
JSParseNode result = null;
if (factory != null)
{
result = (JSParseNode) factory.createParseNode(type, startingLexeme);
}
else
{
// we need to return something to prevent NPE's
result = new JSParseNode(type, startingLexeme);
}
return result;
}
/**
* Handle cases where semicolons are optional
*
* @param node
* The command node that is expecting either an explicit or implicit trailing semicolon
* @throws LexerException
* @throws ParseException
*/
private void handleOptionalSemicolon(JSParseNode node) throws LexerException, ParseException
{
// NOTE: Cached command nodes will include the trailing semicolon, so we need to check the node's last lexeme to
// see if we've already handled a pre-existing semicolon from a previous parse
// if (node.getEndingLexeme().typeIndex != JSTokenTypes.SEMICOLON)
// {
switch (this.currentLexeme.typeIndex)
{
case JSTokenTypes.SEMICOLON:
// associate this lexeme with the specified command node
// this._currentLexeme.setCommandNode(node);
// tag node
node.setIncludesSemicolon(true);
// advance over ';'
this.advance();
break;
case JSTokenTypes.ERROR:
case JSTokenTypes.RCURLY:
// implied ';'
break;
default:
// handle auto semicolon insertion here
if (this.currentLexeme != EOS && this.currentLexeme.isAfterEOL() == false)
{
this.throwParseError(JSMessages.getString("error.semicolon")); //$NON-NLS-1$
}
break;
}
// }
}
/**
* @see com.aptana.ide.parsing.AbstractParser#parseAll(com.aptana.ide.parsing.nodes.IParseNode)
*/
public synchronized void parseAll(IParseNode parentNode) throws LexerException, ParseException
{
// set node to be used as the root node for the results of this parse
IParseNode rootNode = this.getParseRootNode(parentNode, JSNaryNode.class);
// make sure our lexer is using our lexeme cache and switch over to our language and default group
ILexer lexer = this.getLexer();
lexer.setLanguageAndGroup(this.getLanguage(), DEFAULT_GROUP);
// prime the lexeme pump
this.advance();
while (this.isEOS() == false)
{
IParseNode result = this.parseSourceElement();
if (rootNode != null && result != null)
{
rootNode.appendChild(result);
}
}
}
/**
* Parse AdditiveExpression
* <p>
*
* <pre>
* AdditiveExpression
* : MultiplicativeExpression
* | AdditiveExpression PLUS MultiplicativeExpression
* | AdditiveExpression MINUS MultiplicativeExpression
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseAdditiveExpression() throws ParseException, LexerException
{
// get lhs
JSParseNode result = this.parseMultiplicativeExpression();
ILexer lexer = this.getLexer();
if (this.isType(JSTokenTypes.NUMBER))
{
// a '+' or '-' may have been consumed as part of the number
// We want to rescan as PLUS and MINUS followed by a NUMBER
// here.
char startChar = lexer.getCharacterAt(this.currentLexeme.offset);
if (startChar == '-' || startChar == '+')
{
this.rescan(ADDITION_GROUP);
}
}
while (this.inSet(additiveExpressionSet))
{
JSParseNode lhs = result;
JSParseNode rhs;
// save operator type
int operatorType = this.currentLexeme.typeIndex;
Lexeme operator = this.currentLexeme;
// advance over '-' or '+'
this.advance();
// get right-hand side
rhs = this.parseMultiplicativeExpression();
if (this.isType(JSTokenTypes.NUMBER))
{
// a '+' or '-' may have been consumed as part of the number
// We want to rescan as PLUS and MINUS followed by a NUMBER
// here.
char startChar = lexer.getCharacterAt(this.currentLexeme.offset);
if (startChar == '-' || startChar == '+')
{
this.rescan(ADDITION_GROUP);
}
}
switch (operatorType)
{
case JSTokenTypes.MINUS:
result = this.createNode(JSParseNodeTypes.SUBTRACT, operator);
result.appendChild(lhs);
result.appendChild(rhs);
break;
case JSTokenTypes.PLUS:
result = this.createNode(JSParseNodeTypes.ADD, operator);
result.appendChild(lhs);
result.appendChild(rhs);
break;
default:
String typeName = JSTokenTypes.getName(operatorType);
this.throwParseError(JSMessages.getString("error.internal.additive.expression") + typeName); //$NON-NLS-1$
}
}
return result;
}
/**
* Parse ArgumentList
* <p>
*
* <pre>
* ArgumentList
* : AssignmentExpression
* | ArgumentList COMMA AssignmentExpression
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseArgumentList() throws ParseException, LexerException
{
JSParseNode arguments = this.createNode(JSParseNodeTypes.ARGUMENTS, this.currentLexeme);
// add first argument to our list
arguments.appendChild(this.parseAssignmentExpression(false));
while (this.isType(JSTokenTypes.COMMA))
{
// advance over ','
this.advance();
// add next argument
arguments.appendChild(this.parseAssignmentExpression(false));
}
return arguments;
}
/**
* <pre>
* Arguments
* : LPAREN RPAREN
* | LPAREN ArgumentList RPAREN
* ;
* </pre>
*/
/**
* Parse ArrayLiteral
* <p>
*
* <pre>
* ArrayLiteral
* : LBRACKET RBRACE
* | LBRACKET Elision RBRACE
* | LBRACKET ElementList RBRACE
* | LBRACKET ElementList COMMA RBRACE
* | LBRACKET ElementList COMMA Elision RBRACE
* ;
* Elision
* : COMMA
* | Elision COMMA
* ;
* ElementList
* : AssignmentExpression
* | Elision AssignmentExpression
* | ElementList COMMA AssignmentExpression
* | ElementList COMMA Elision AssignmentExpression
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseArrayLiteral() throws ParseException, LexerException
{
// create new command node
JSParseNode result = this.createNode(JSParseNodeTypes.ARRAY_LITERAL, this.currentLexeme);
// make sure we have '['
this.assertType(JSTokenTypes.LBRACKET, "error.internal.punctuator"); //$NON-NLS-1$
// advance over '['
this.advance();
// pretend like we've just seen a comma
boolean comma = true;
while (this.isType(JSTokenTypes.RBRACKET) == false && this.isEOS() == false)
{
if (this.isType(JSTokenTypes.COMMA))
{
// advance over ','
this.advance();
// tag that we've seen a comma
if (comma == false)
{
comma = true;
}
else
{
// add null element
result.appendChild(this.createNode(JSParseNodeTypes.NULL, null));
}
}
else
{
// can't have two assignment expressions in a row
if (comma == false)
{
throwParseError(JSMessages.getString("error.array.missing.comma")); //$NON-NLS-1$
}
// add assignment expression
result.appendChild(this.parseAssignmentExpression(false));
// reset comma flag
comma = false;
}
}
// add null element after last comma
if (comma && result.getChildCount() > 0)
{
result.appendChild(this.createNode(JSParseNodeTypes.NULL, null));
}
// make sure we have ']'
this.assertType(JSTokenTypes.RBRACKET, "error.array.literal.rbracket"); //$NON-NLS-1$
// advance over ']'
this.advance();
result.includeLexemeInRange(currentLexeme);
return result;
}
/**
* Parse AssignmentExpression
* <p>
*
* <pre>
* AssignmentExpression
* : ConditionalExpression
* | LeftHandSideExpression AssignmentOperator AssignmentExpression
* ;
* AssignmentOperator
* : EQUAL
* | STAR_EQUAL
* | FORWARD_SLASH_EQUAL
* | PERCENT_EQUAL
* | PLUS_EQUAL
* | MINUS_EQUAL
* | LESS_LESS_EQUAL
* | GREATER_GREATER_EQUAL
* | GREATER_GREATER_GREATER_EQUAL
* | AMPERSAND_EQUAL
* | CARET_EQUAL
* | PIPE_EQUAL
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseAssignmentExpression(boolean noIn) throws ParseException, LexerException
{
JSParseNode result = this.parseConditionalExpression(noIn);
if (this.inSet(assignmentOperatorSet))
{
boolean referenceable = true;
if (result.getTypeIndex() == JSParseNodeTypes.GROUP)
{
JSParseNode child = (JSParseNode) result.getChild(0);
while (child != null && child.getTypeIndex() == JSParseNodeTypes.GROUP)
{
child = (JSParseNode) child.getChild(0);
}
if (child == null || Arrays.binarySearch(referenceableSet, child.getTypeIndex()) < 0)
{
referenceable = false;
}
}
if (referenceable == false || Arrays.binarySearch(referenceableSet, result.getTypeIndex()) < 0)
{
this.throwParseError(JSMessages.getString("error.assignment.invalid.lhs")); //$NON-NLS-1$
}
// each side of the operator
JSParseNode lhs = result;
JSParseNode rhs;
// save operator type
int operatorType = this.currentLexeme.typeIndex;
Lexeme operator = this.currentLexeme;
// advance over operator
this.advance();
// get right-hand side
rhs = this.parseAssignmentExpression(noIn);
// create command node
switch (operatorType)
{
case JSTokenTypes.AMPERSAND_EQUAL:
result = this.createNode(JSParseNodeTypes.BITWISE_AND_AND_ASSIGN, operator);
break;
case JSTokenTypes.CARET_EQUAL:
result = this.createNode(JSParseNodeTypes.BITWISE_XOR_AND_ASSIGN, operator);
break;
case JSTokenTypes.EQUAL:
result = this.createNode(JSParseNodeTypes.ASSIGN, operator);
break;
case JSTokenTypes.FORWARD_SLASH_EQUAL:
result = this.createNode(JSParseNodeTypes.DIVIDE_AND_ASSIGN, operator);
break;
case JSTokenTypes.GREATER_GREATER_EQUAL:
result = this.createNode(JSParseNodeTypes.SHIFT_RIGHT_AND_ASSIGN, operator);
break;
case JSTokenTypes.GREATER_GREATER_GREATER_EQUAL:
result = this.createNode(JSParseNodeTypes.ARITHMETIC_SHIFT_RIGHT_AND_ASSIGN, operator);
break;
case JSTokenTypes.LESS_LESS_EQUAL:
result = this.createNode(JSParseNodeTypes.SHIFT_LEFT_AND_ASSIGN, operator);
break;
case JSTokenTypes.MINUS_EQUAL:
result = this.createNode(JSParseNodeTypes.SUBTRACT_AND_ASSIGN, operator);
break;
case JSTokenTypes.PERCENT_EQUAL:
result = this.createNode(JSParseNodeTypes.MOD_AND_ASSIGN, operator);
break;
case JSTokenTypes.PIPE_EQUAL:
result = this.createNode(JSParseNodeTypes.BITWISE_OR_AND_ASSIGN, operator);
break;
case JSTokenTypes.PLUS_EQUAL:
result = this.createNode(JSParseNodeTypes.ADD_AND_ASSIGN, operator);
break;
case JSTokenTypes.STAR_EQUAL:
result = this.createNode(JSParseNodeTypes.MULTIPLY_AND_ASSIGN, operator);
break;
default:
String typeName = JSTokenTypes.getName(operatorType);
this.throwParseError(JSMessages.getString("error.internal.assignment.expression") + typeName); //$NON-NLS-1$
}
result.appendChild(lhs);
result.appendChild(rhs);
}
return result;
}
/**
* Parse BitwiseANDExpression
* <p>
*
* <pre>
* BitwiseANDExpression
* : EqualityExpression
* | BitwiseANDExpression AMPERSAND EqualityExpression
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseBitwiseANDExpression(boolean noIn) throws ParseException, LexerException
{
JSParseNode result = this.parseEqualityExpression(noIn);
while (this.isType(JSTokenTypes.AMPERSAND))
{
JSParseNode lhs = result;
JSParseNode rhs;
// get lexeme index
Lexeme operator = this.currentLexeme;
// advance over '&'
this.advance();
// get right-hand side
rhs = this.parseEqualityExpression(noIn);
// create resulting command node
result = this.createNode(JSParseNodeTypes.BITWISE_AND, operator);
result.appendChild(lhs);
result.appendChild(rhs);
}
return result;
}
/**
* Parse BitwiseORExpression
* <p>
*
* <pre>
* BitwiseORExpression
* : BitwiseXORExpression
* | BitwiseORExpression PIPE BitwiseXORExpression
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseBitwiseORExpression(boolean noIn) throws ParseException, LexerException
{
JSParseNode result = this.parseBitwiseXORExpression(noIn);
while (this.isType(JSTokenTypes.PIPE))
{
JSParseNode lhs = result;
JSParseNode rhs;
// get lexeme index
Lexeme operator = this.currentLexeme;
// advance over '|'
this.advance();
// get right-hand side
rhs = this.parseBitwiseXORExpression(noIn);
result = this.createNode(JSParseNodeTypes.BITWISE_OR, operator);
result.appendChild(lhs);
result.appendChild(rhs);
}
return result;
}
/**
* Parse BitwiseXORExpression
* <p>
*
* <pre>
* BitwiseXORExpression
* : BitwiseANDExpression
* | BitwiseXORExpression CARET BitwiseANDExpression
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseBitwiseXORExpression(boolean noIn) throws ParseException, LexerException
{
JSParseNode result = this.parseBitwiseANDExpression(noIn);
while (this.isType(JSTokenTypes.CARET))
{
JSParseNode lhs = result;
JSParseNode rhs;
// get lexeme index
Lexeme operator = this.currentLexeme;
// advance '^'
this.advance();
// get right-hand side
rhs = this.parseBitwiseANDExpression(noIn);
// create command node
result = this.createNode(JSParseNodeTypes.BITWISE_XOR, operator);
result.appendChild(lhs);
result.appendChild(rhs);
}
return result;
}
/**
* Parse Block
* <p>
*
* <pre>
* Block
* : LCURLY StatementList? RCURLY
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseBlock() throws ParseException, LexerException
{
// turn on inBlock flag to indicate to recover not to consume closing braces
// this._inBlock = true;
JSParseNode result = this.createNode(JSParseNodeTypes.STATEMENTS, this.currentLexeme);
// make sure we have '{'
this.assertType(JSTokenTypes.LCURLY, "error.block.lcurly"); //$NON-NLS-1$
// advance over '{'
this.advance();
while (this.isType(JSTokenTypes.RCURLY) == false && this.isEOS() == false)
{
// parse statement
JSParseNode lastStatement = this.parseStatement();
// add statement
result.appendChild(lastStatement);
}
result.includeLexemeInRange(this.currentLexeme);
// check for '}'
this.assertType(JSTokenTypes.RCURLY, "error.block.rcurly"); //$NON-NLS-1$
// advance over '}'
this.advance();
// clear block flag
// this._inBlock = false;
return result;
}
/**
* Parse BreakStatement
* <p>
*
* <pre>
* BreakStatement
* : BREAK [no LINE_TERMINATOR here] Identifier? SEMICOLON
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseBreakStatement() throws ParseException, LexerException
{
JSParseNode result;
Lexeme keyword = this.currentLexeme;
JSParseNode label = this.createNode(JSParseNodeTypes.EMPTY, null); // JSParseNode.Empty;
// advance over 'break'
this.assertAndAdvance(JSTokenTypes.BREAK, "error.internal.keyword"); //$NON-NLS-1$
// handle possible identifier
if (this.isType(JSTokenTypes.IDENTIFIER))
{
// get label index
label = this.createNode(JSParseNodeTypes.IDENTIFIER, this.currentLexeme);
// advance over identifier
this.advance();
}
// create command node
result = this.createNode(JSParseNodeTypes.BREAK, keyword);
result.appendChild(label);
// handle ';'
this.handleOptionalSemicolon(result);
// return result
return result;
}
/**
* <pre>
* CallExpression
* : MemberExpression Arguments
* | CallExpression Arguments
* | CallExpression LBRACKET Expression RBRACE
* | CallExpression DOT IDENTIFIER
* ;
* </pre>
*/
/**
* Parse CaseBlock
* <p>
*
* <pre>
* CaseClause
* : CASE Expression COLON StatementList?
* ;
* DefaultClause
* : DEFAULT COLON
* | DEFAULT COLON StatementList
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseCaseOrDefaultBlock() throws ParseException, LexerException
{
JSParseNode clause = null;
Lexeme keyword = this.currentLexeme;
switch (this.currentLexeme.typeIndex)
{
case JSTokenTypes.CASE:
// advance over 'case'
this.assertAndAdvance(JSTokenTypes.CASE, "error.internal.keyword"); //$NON-NLS-1$
// parse expression
JSParseNode expression = this.parseExpression(false, "error.switch.case.expression"); //$NON-NLS-1$
// grab expression
clause = this.createNode(JSParseNodeTypes.CASE, keyword);
clause.appendChild(expression);
// make sure we have ':'
this.assertType(JSTokenTypes.COLON, "error.switch.case.colon"); //$NON-NLS-1$
// advance over ':'
this.advance();
break;
case JSTokenTypes.DEFAULT:
// advance over 'default'
this.assertAndAdvance(JSTokenTypes.DEFAULT, "error.internal.keyword"); //$NON-NLS-1$
// make sure we have ':'
this.assertType(JSTokenTypes.COLON, "error.switch.default.colon"); //$NON-NLS-1$
// create default clause
clause = this.createNode(JSParseNodeTypes.DEFAULT, keyword);
clause.includeLexemeInRange(this.currentLexeme);
// advance over ':'
this.advance();
break;
default:
this.throwParseError(JSMessages.getString("error.switch.body")); //$NON-NLS-1$
}
// parse statement list
while (this.inSet(caseBlockSet) == false && this.isEOS() == false)
{
// add statement
clause.appendChild(this.parseStatement());
}
return clause;
}
/**
* Parse Catch
* <p>
*
* <pre>
* Catch
* : CATCH LPAREN IDENTIFIER RPAREN Block
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseCatch() throws ParseException, LexerException
{
JSParseNode name;
Lexeme keyword = this.currentLexeme;
// advance over 'catch'
this.assertAndAdvance(JSTokenTypes.CATCH, "error.internal.keyword"); //$NON-NLS-1$
// advance over '('
this.assertAndAdvance(JSTokenTypes.LPAREN, "error.try.catch.lparen"); //$NON-NLS-1$
// make sure we have an identifier
this.assertType(JSTokenTypes.IDENTIFIER, "error.try.catch.identifier"); //$NON-NLS-1$
// grab identifier name
name = this.createNode(JSParseNodeTypes.IDENTIFIER, this.currentLexeme);
// advance over identifier
this.advance();
// advance over ')'
this.assertAndAdvance(JSTokenTypes.RPAREN, "error.try.catch.rparen"); //$NON-NLS-1$
// parse body
JSParseNode body = this.parseBlock();
// create result
JSParseNode result = this.createNode(JSParseNodeTypes.CATCH, keyword);
result.appendChild(name);
result.appendChild(body);
return result;
}
/**
* Parse ConditionalExpression
* <p>
*
* <pre>
* ConditionalExpression
* : LogicalORExpression
* | LogicalORExpression QUESTION AssignmentExpression COLON AssignmentExpression
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseConditionalExpression(boolean noIn) throws ParseException, LexerException
{
JSParseNode result = this.parseLogicalORExpression(noIn);
if (this.isType(JSTokenTypes.QUESTION))
{
JSParseNode condition = result;
JSParseNode trueCase = this.createNode(JSParseNodeTypes.EMPTY, null); // JSParseNode.Empty;
JSParseNode falseCase = this.createNode(JSParseNodeTypes.EMPTY, null); // JSParseNode.Empty;
// get '?' position
Lexeme question = this.currentLexeme;
// advance over '?'
this.advance();
// parse trueCase
trueCase = this.parseAssignmentExpression(noIn);
// check for ':'
this.assertType(JSTokenTypes.COLON, "error.conditional.colon"); //$NON-NLS-1$
// advance over ':'
this.advance();
// parse falseCase
falseCase = this.parseAssignmentExpression(noIn);
// create command node
result = this.createNode(JSParseNodeTypes.CONDITIONAL, question);
result.appendChild(condition);
result.appendChild(trueCase);
result.appendChild(falseCase);
}
return result;
}
/**
* Parse ContinueStatement
* <p>
*
* <pre>
* ContinueStatement
* : CONTINUE [no LINE_TERMINATOR here] IDENTIFIER? SEMICOLON
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseContinueStatement() throws ParseException, LexerException
{
JSParseNode label = this.createNode(JSParseNodeTypes.EMPTY, null); // JSParseNode.Empty;
Lexeme keyword = this.currentLexeme;
JSParseNode result;
// advance over 'continue'
this.assertAndAdvance(JSTokenTypes.CONTINUE, "error.internal.keyword"); //$NON-NLS-1$
if (this.isType(JSTokenTypes.IDENTIFIER))
{
// get label
label = this.createNode(JSParseNodeTypes.IDENTIFIER, this.currentLexeme);
// advance over identifier
this.advance();
}
// create command node
result = this.createNode(JSParseNodeTypes.CONTINUE, keyword);
result.appendChild(label);
// handle ';'
this.handleOptionalSemicolon(result);
// return result
return result;
}
/**
* Parse DoStatement
* <p>
*
* <pre>
* DoStatement
* : DO Statement WHILE LPAREN Expression RPAREN
* ;
* </pre>
*
* @throws LexerException
* @throws ParseException
*/
private JSParseNode parseDoStatement() throws ParseException, LexerException
{
JSParseNode body;
JSParseNode condition;
Lexeme keyword = this.currentLexeme;
JSParseNode result;
// advance over 'do'
this.assertAndAdvance(JSTokenTypes.DO, "error.internal.keyword"); //$NON-NLS-1$
// get body
body = this.parseStatement();
// advance over 'while'
this.assertAndAdvance(JSTokenTypes.WHILE, "error.do.while"); //$NON-NLS-1$
// advance over '('
this.assertAndAdvance(JSTokenTypes.LPAREN, "error.do.while.lparen"); //$NON-NLS-1$
// get condition
condition = this.parseExpression(false, "error.do.while.expression"); //$NON-NLS-1$
// advance over ')'
result = this.createNode(JSParseNodeTypes.DO, keyword);
result.includeLexemeInRange(this.currentLexeme);
this.assertAndAdvance(JSTokenTypes.RPAREN, "error.do.while.rparen"); //$NON-NLS-1$
if (this.isType(JSTokenTypes.SEMICOLON))
{
result.includeLexemeInRange(this.currentLexeme);
result.setIncludesSemicolon(true);
this.advance();
}
result.appendChild(body);
result.appendChild(condition);
return result;
}
/**
* Parse EqualityExpression
* <p>
*
* <pre>
* EqualityExpression
* : RelationalExpression
* | EqualityExpression EQUAL_EQUAL RelationalExpression
* | EqualityExpression EXCLAMATION_EQUAL RelationalExpression
* | EqualityExpression EQUAL_EQUAL_EQUAL RelationalExpression
* | EqualityExpression EXCLAMATION_EQUAL_EQUAL RelationalExpression
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseEqualityExpression(boolean noIn) throws ParseException, LexerException
{
JSParseNode result = this.parseRelationalExpression(noIn);
while (this.inSet(equalityExpressionSet))
{
JSParseNode lhs = result;
JSParseNode rhs;
// save operator type
int operatorType = this.currentLexeme.typeIndex;
Lexeme operator = this.currentLexeme;
// advance over '==', '!=', '===', or '!=='
this.advance();
// get right-hand side
rhs = this.parseRelationalExpression(noIn);
switch (operatorType)
{
case JSTokenTypes.EQUAL_EQUAL:
result = this.createNode(JSParseNodeTypes.EQUAL, operator);
break;
case JSTokenTypes.EXCLAMATION_EQUAL:
result = this.createNode(JSParseNodeTypes.NOT_EQUAL, operator);
break;
case JSTokenTypes.EQUAL_EQUAL_EQUAL:
result = this.createNode(JSParseNodeTypes.IDENTITY, operator);
break;
case JSTokenTypes.EXCLAMATION_EQUAL_EQUAL:
result = this.createNode(JSParseNodeTypes.NOT_IDENTITY, operator);
break;
default:
String typeName = JSTokenTypes.getName(operatorType);
this.throwParseError(JSMessages.getString("error.internal.equality.expression") + typeName); //$NON-NLS-1$
}
result.appendChild(lhs);
result.appendChild(rhs);
}
return result;
}
/**
* Parse Expression
* <p>
*
* <pre>
* Expression
* : AssignmentExpression
* | Expression COMMA AssignmentExpression
* ;
* </pre>
*
* @param errorKey
* A key used to look up an error message from our error property list
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseExpression(boolean noIn, String errorKey) throws ParseException, LexerException
{
// store error key in case we encounter an error while processing this expression
this._expressionErrorKey = errorKey;
// parse assignment
JSParseNode result = this.parseAssignmentExpression(noIn);
if (this.isType(JSTokenTypes.COMMA))
{
JSParseNode commas = this.createNode(JSParseNodeTypes.COMMA, this.currentLexeme);
// add first expression
commas.appendChild(result);
while (this.isType(JSTokenTypes.COMMA))
{
// advance over ','
this.advance();
// parse expression
JSParseNode node = this.parseAssignmentExpression(noIn);
// add expression to list
commas.appendChild(node);
}
result = commas;
}
// clear error key so other non-expression parses won't pick up our cached error key
this._expressionErrorKey = null;
// return resulting expression
return result;
}
/**
* <pre>
* ExpressionStatement
* : Expression SEMICOLON
* ;
* </pre>
*/
/**
* Parse Finally
* <p>
*
* <pre>
* Finally
* : FINALLY Block
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseFinally() throws ParseException, LexerException
{
// test for 'finally'
this.assertType(JSTokenTypes.FINALLY, "error.internal.keyword"); //$NON-NLS-1$
// get 'finally' lexeme
Lexeme finallyLexeme = this.currentLexeme;
// advance over 'finally'
this.advance();
// parse block
JSParseNode block = this.parseBlock();
// create command node
JSParseNode result = this.createNode(JSParseNodeTypes.FINALLY, finallyLexeme);
result.appendChild(block);
// return result
return result;
}
/**
* Parse ForStatement
* <p>
*
* <pre>
* ForStatement
* : FOR LPAREN ExpressionNoIn* SEMICOLON Expression* SEMICOLON Expression* RPAREN Statement
* | FOR LPAREN VAR VariableDeclarationListNoIn SEMICOLON Expression* SEMICOLON Expression* RPAREN Statement
* | FOR LPAREN LeftHandSideExpression IN Expression RPAREN Statement
* | FOR LPAREN VAR VariableDeclarationNoIn IN Expression RPAREN Statement
* ;
* </pre>
*
* @throws LexerException
* @throws ParseException
*/
private JSParseNode parseForStatement() throws ParseException, LexerException
{
JSParseNode result;
JSParseNode condition = null; // JSParseNode.Empty;
JSParseNode object = this.createNode(JSParseNodeTypes.EMPTY, null); // JSParseNode.Empty;
JSParseNode advance = null; // JSParseNode.Empty;
JSParseNode body = this.createNode(JSParseNodeTypes.EMPTY, null); // JSParseNode.Empty;
boolean isForIn = false;
Lexeme keyword = this.currentLexeme;
// advance over 'for'
this.assertAndAdvance(JSTokenTypes.FOR, "error.internal.keyword"); //$NON-NLS-1$
JSParseNode initialize = this.createNode(JSParseNodeTypes.EMPTY, currentLexeme); // JSParseNode.Empty;
// advance over '('
this.assertAndAdvance(JSTokenTypes.LPAREN, "error.for.lparen"); //$NON-NLS-1$
// initial assignment
if (this.isType(JSTokenTypes.SEMICOLON) == false)
{
if (this.isType(JSTokenTypes.VAR))
{
initialize = this.parseVarExpression(true);
}
else
{
initialize = this.parseExpression(true, "error.for.initialization.expression"); //$NON-NLS-1$
}
}
else
{
initialize.includeLexemeInRange(currentLexeme);
}
// determine if this is a for-loop or for-in-loop
if (this.isType(JSTokenTypes.IN))
{
// advance over 'in'
this.advance();
// set flag telling us which for-node to create
isForIn = true;
// parse expression that returns an object
object = this.parseExpression(false, "error.for.in.expression"); //$NON-NLS-1$
}
else
{
condition = this.createNode(JSParseNodeTypes.EMPTY, currentLexeme); // JSParseNode.Empty;
// advance over ';'
this.assertAndAdvance(JSTokenTypes.SEMICOLON, "error.for.initialization.semicolon"); //$NON-NLS-1$
if (this.isType(JSTokenTypes.SEMICOLON) == false)
{
// parse condition
condition = this.parseExpression(false, "error.for.condition.expression"); //$NON-NLS-1$
}
else
{
condition.includeLexemeInRange(currentLexeme);
}
// advance over ';'
advance = this.createNode(JSParseNodeTypes.EMPTY, currentLexeme); // JSParseNode.Empty;
this.assertAndAdvance(JSTokenTypes.SEMICOLON, "error.for.condition.semicolon"); //$NON-NLS-1$
// iterator
if (this.isType(JSTokenTypes.RPAREN) == false)
{
// parse iterator
advance = this.parseExpression(false, "error.for.iteration.expression"); //$NON-NLS-1$
}
else
{
advance.includeLexemeInRange(currentLexeme);
}
}
// advance over ')'
this.assertAndAdvance(JSTokenTypes.RPAREN, "error.for.rparen"); //$NON-NLS-1$
// parse body
body = this.parseStatement();
if (isForIn)
{
// create command node
result = this.createNode(JSParseNodeTypes.FOR_IN, keyword);
result.appendChild(initialize);
result.appendChild(object);
result.appendChild(body);
}
else
{
// create command node
result = this.createNode(JSParseNodeTypes.FOR, keyword);
result.appendChild(initialize);
result.appendChild(condition);
result.appendChild(advance);
result.appendChild(body);
}
return result;
}
/**
* Parse FormalParameterList
* <p>
*
* <pre>
* FormalParameterList
* : IDENTIFIER
* | FormalParameterList COMMA IDENTIFIER
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseFormalParameterList() throws ParseException, LexerException
{
JSParseNode result = this.createNode(JSParseNodeTypes.PARAMETERS, this.prevLexeme);
// make sure we have at least one identifier
this.assertType(JSTokenTypes.IDENTIFIER, "error.parameters.name"); //$NON-NLS-1$
// create identifier
JSParseNode name = this.createNode(JSParseNodeTypes.IDENTIFIER, this.currentLexeme);
// advance over identifier
this.advance();
// save parameter
result.appendChild(name);
while (this.isType(JSTokenTypes.COMMA))
{
// advance over ','
this.advance();
// make sure we have an identifier
this.assertType(JSTokenTypes.IDENTIFIER, "error.parameters.name"); //$NON-NLS-1$
// save, associate, advance, and add
name = this.createNode(JSParseNodeTypes.IDENTIFIER, this.currentLexeme);
// this._currentLexeme.setCommandNode(name);
this.advance();
result.appendChild(name);
}
return result;
}
/**
* Parse FunctionBody
* <p>
*
* <pre>
* FunctionBody
* : SourceElements
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseFunctionBody() throws ParseException, LexerException
{
JSParseNode statements = this.createNode(JSParseNodeTypes.STATEMENTS, prevLexeme);
// turn on inFunction flag to indicate it's OK to encounter 'return' now
// this._inFunction = true;
while (this.isType(JSTokenTypes.RCURLY) == false && this.isEOS() == false)
{
JSParseNode statement;
if (this.isType(JSTokenTypes.FUNCTION))
{
// add function declaration to body collection
statement = this.parseFunctionDeclaration(false);
}
else
{
// add statement to body collection
statement = this.parseStatement();
}
statements.appendChild(statement);
}
statements.includeLexemeInRange(currentLexeme);
// turn off inFunction flag
// this._inFunction = false;
return statements;
}
/**
* Main entry point for parsing function definitions
*
* @param identifierRequired
* @return
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseFunctionDeclaration(boolean identifierRequired) throws ParseException, LexerException
{
Lexeme keyword = this.currentLexeme;
// advance over 'function'
this.assertAndAdvance(JSTokenTypes.FUNCTION, "error.internal.keyword"); //$NON-NLS-1$
return parseFunctionDeclaration2(identifierRequired, keyword);
}
/**
* Parse a function declaration. This is a shared method used both by actual function declarations and getter/setter
* declarations in object literals
* <p>
*
* <pre>
* FunctionDeclaration
* : FUNCTION IDENTIFIER LPAREN FormalParameterList? RPAREN LCURLY FunctionBody RCURLY
* ;
* FunctionExpression
* : FUNCTION IDENTIFIER? LPAREN FormalParameterList? RPAREN LCURLY FunctionBody RCURLY
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseFunctionDeclaration2(boolean identifierRequired, Lexeme keyword) throws LexerException,
ParseException
{
String name = null;
JSParseNode result;
JSParseNode parameters;
JSParseNode body;
Lexeme rcurly;
// NOTE: function declaration requires an identifier, but a function
// expression does not
if (this.isType(JSTokenTypes.IDENTIFIER))
{
// get function name
name = this.currentLexeme.getText();
// advance over identifier
this.advance();
}
else if (identifierRequired)
{
this.throwParseError(JSMessages.getString("error.function.name")); //$NON-NLS-1$
}
// advance over '('
parameters = this.createNode(JSParseNodeTypes.EMPTY, currentLexeme);
prevLexeme = currentLexeme;
this.assertAndAdvance(JSTokenTypes.LPAREN, "error.function.lparen"); //$NON-NLS-1$
// handle formal parameter list
if (this.isType(JSTokenTypes.IDENTIFIER))
{
parameters = this.parseFormalParameterList();
}
// advance over ')'
parameters.includeLexemeInRange(currentLexeme);
this.assertAndAdvance(JSTokenTypes.RPAREN, "error.function.rparen"); //$NON-NLS-1$
// advance over '{'
this.prevLexeme = this.currentLexeme;
this.assertAndAdvance(JSTokenTypes.LCURLY, "error.function.lcurly"); //$NON-NLS-1$
// parse body and retrieve
body = this.parseFunctionBody();
// get '}' index
rcurly = this.currentLexeme;
// advance over '}'
this.assertAndAdvance(JSTokenTypes.RCURLY, "error.function.rcurly"); //$NON-NLS-1$
// create function node
result = this.createNode(JSParseNodeTypes.FUNCTION, keyword);
if (name != null)
{
result.setAttribute("name", name); //$NON-NLS-1$
}
result.appendChild(parameters);
result.appendChild(body);
result.includeLexemeInRange(rcurly);
return result;
}
/**
* <pre>
* LeftHandSideExpression
* : NewExpression
* | CallExpression
* ;
* </pre>
*/
/**
* Parse IfStatement
* <p>
*
* <pre>
* IfStatement
* : IF LPAREN Expression RPAREN Statement ELSE Statement
* | IF LPAREN Expression RPAREN Statement
* ;
* </pre>
*
* @throws LexerException
* @throws ParseException
*/
private JSParseNode parseIfStatement() throws ParseException, LexerException
{
JSParseNode result;
JSParseNode condition;
JSParseNode trueCase;
JSParseNode falseCase = this.createNode(JSParseNodeTypes.EMPTY, null); // JSParseNode.Empty;
Lexeme keyword = this.currentLexeme;
// advance over 'if'
this.assertAndAdvance(JSTokenTypes.IF, "error.internal.keyword"); //$NON-NLS-1$
// advance over '('
this.assertAndAdvance(JSTokenTypes.LPAREN, "error.if.lparen"); //$NON-NLS-1$
// parse condition
condition = this.parseExpression(false, "error.if.condition"); //$NON-NLS-1$
// advance over ')'
this.assertAndAdvance(JSTokenTypes.RPAREN, "error.if.rparen"); //$NON-NLS-1$
// parse true case
trueCase = this.parseStatement();
if (this.isType(JSTokenTypes.ELSE))
{
// advance over 'else'
this.advance();
// parse false case
falseCase = this.parseStatement();
}
// create command node
result = this.createNode(JSParseNodeTypes.IF, keyword);
result.appendChild(condition);
result.appendChild(trueCase);
result.appendChild(falseCase);
return result;
}
/**
* Parse LabelledStatement
* <p>
*
* <pre>
* LabelledStatement
* : Identifier COLON Statement
* ;
* </pre>
*
* @throws LexerException
* @throws ParseException
*/
private JSParseNode parseLabelledStatement() throws ParseException, LexerException
{
JSParseNode result;
Lexeme identifier = this.currentLexeme;
result = this.parseExpression(false, null); // TODO: add error message for this case
if (this.isType(JSTokenTypes.COLON))
{
// recover label name
JSParseNode label = this.createNode(JSParseNodeTypes.IDENTIFIER, identifier);
// is label, advance over ':'
this.advance();
// parse statement following label
JSParseNode statement = this.parseStatement();
// create command node
result = this.createNode(JSParseNodeTypes.LABELLED, null);
result.appendChild(label);
result.appendChild(statement);
}
// handle ';'
this.handleOptionalSemicolon(result);
// return result
return result;
}
/**
* Parse LogicalANDExpression
* <p>
*
* <pre>
* LogicalANDExpression
* : BitwiseORExpression
* | LogicalANDExpression AMPERSAND_AMPERSAND BitwiseORExpression
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseLogicalANDExpression(boolean noIn) throws ParseException, LexerException
{
JSParseNode result = this.parseBitwiseORExpression(noIn);
while (this.isType(JSTokenTypes.AMPERSAND_AMPERSAND))
{
JSParseNode lhs = result;
JSParseNode rhs;
// get operator index
Lexeme operator = this.currentLexeme;
// advance over '&&'
this.advance();
// get right-hand side
rhs = this.parseBitwiseORExpression(noIn);
// create command node
result = this.createNode(JSParseNodeTypes.LOGICAL_AND, operator);
result.appendChild(lhs);
result.appendChild(rhs);
}
return result;
}
/**
* Parse LogicalORExpression
* <p>
*
* <pre>
* LogicalORExpression
* : LogicalANDExpression
* | LogicalORExpression PIPE_PIPE LogicalANDExpression
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseLogicalORExpression(boolean noIn) throws ParseException, LexerException
{
JSParseNode result = this.parseLogicalANDExpression(noIn);
while (this.isType(JSTokenTypes.PIPE_PIPE))
{
JSParseNode lhs = result;
JSParseNode rhs;
// get operator index
Lexeme operator = this.currentLexeme;
// advance over '||'
this.advance();
// get right-hand size
rhs = this.parseLogicalANDExpression(noIn);
// create command node
result = this.createNode(JSParseNodeTypes.LOGICAL_OR, operator);
result.appendChild(lhs);
result.appendChild(rhs);
}
return result;
}
/**
* Parse MemberExpression
* <p>
*
* <pre>
* MemberExpression
* : PrimaryExpression
* | FunctionExpression
* | MemberExpression LBRACKET Expression RBRACE
* | MemberExpression DOT IDENTIFIER
* | NEW MemberExpression Arguments
* ;
* </pre>
*
* @param isInvocable
* A boolean indicating whether invocation is allowed after this member expression
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseMemberExpression(boolean isInvocable) throws ParseException, LexerException
{
JSParseNode result = null;
switch (this.currentLexeme.typeIndex)
{
case JSTokenTypes.FUNCTION:
// parse function
result = this.parseFunctionDeclaration(false);
break;
case JSTokenTypes.NEW:
Lexeme newKeyword = this.currentLexeme;
// advance over 'new'
this.advance();
// parse member expression following 'new'
JSParseNode memberExpression = this.parseMemberExpression(false);
// parse arguments
JSParseNode arguments = this.createNode(JSParseNodeTypes.EMPTY, currentLexeme); // JSParseNode.Empty;
if (this.isType(JSTokenTypes.LPAREN))
{
// advance over '('
this.advance();
if (this.isType(JSTokenTypes.RPAREN) == false)
{
// parse parameter list
arguments = this.parseArgumentList();
}
else
{
arguments.includeLexemeInRange(currentLexeme);
}
// advance over ');
this.assertAndAdvance(JSTokenTypes.RPAREN, "error.new.rparen"); //$NON-NLS-1$
}
// create command node
result = this.createNode(JSParseNodeTypes.CONSTRUCT, newKeyword);
result.appendChild(memberExpression);
result.appendChild(arguments);
break;
default:
result = this.parsePrimaryExpression();
break;
}
memberTail: while (this.inSet(postfixMemberExpressionSet))
{
switch (this.currentLexeme.typeIndex)
{
case JSTokenTypes.DOT:
Lexeme dot = this.currentLexeme;
JSParseNode identifier;
// advance over '.'
this.advance();
// make sure we have an identifier
this.assertType(JSTokenTypes.IDENTIFIER, "error.get.property.name"); //$NON-NLS-1$
// get id index
identifier = this.createNode(JSParseNodeTypes.IDENTIFIER, this.currentLexeme);
// advance over identifier
this.advance();
// add getName node
JSParseNode temp = this.createNode(JSParseNodeTypes.GET_PROPERTY, dot);
temp.appendChild(result);
temp.appendChild(identifier);
result = temp;
break;
case JSTokenTypes.LBRACKET:
Lexeme lbrace = this.currentLexeme;
// advance over '['
this.advance();
// get index expression
JSParseNode indexExpression = this.parseExpression(false, "error.get.element.expression"); //$NON-NLS-1$
// advance over ']'
this.assertAndAdvance(JSTokenTypes.RBRACKET, "error.get.element.rbracket"); //$NON-NLS-1$
// add index node
temp = this.createNode(JSParseNodeTypes.GET_ELEMENT, lbrace);
temp.appendChild(result);
temp.appendChild(indexExpression);
result = temp;
break;
case JSTokenTypes.LPAREN:
if (isInvocable == false)
{
break memberTail;
}
JSParseNode arguments = this.createNode(JSParseNodeTypes.EMPTY, this.currentLexeme); // JSParseNode.Empty;
Lexeme lparen = this.currentLexeme;
// advance over '('
this.advance();
if (this.currentLexeme.typeIndex != JSTokenTypes.RPAREN)
{
// parse parameter list
arguments = this.parseArgumentList();
arguments.includeLexemeInRange(this.currentLexeme);
}
else
{
arguments.includeLexemeInRange(this.currentLexeme);
}
Lexeme end = this.currentLexeme;
// advance over ');
this.assertAndAdvance(JSTokenTypes.RPAREN, "error.invocation.rparen"); //$NON-NLS-1$
// create invoke node
temp = this.createNode(JSParseNodeTypes.INVOKE, lparen);
temp.appendChild(result);
temp.appendChild(arguments);
temp.includeLexemeInRange(end);
result = temp;
break;
default:
break;
}
}
return result;
}
/**
* Parse MultiplicativeExpression
* <p>
*
* <pre>
* MultiplicativeExpression
* : UnaryExpression
* | MultiplicativeExpression STAR UnaryExpression
* | MultiplicativeExpression FORWARD_SLASH UnaryExpression
* | MultiplicativeExpression PERCENT UnaryExpression
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseMultiplicativeExpression() throws ParseException, LexerException
{
JSParseNode result = this.parseUnaryExpression();
while (this.inSet(multiplicativeExpressionSet))
{
JSParseNode lhs = result;
JSParseNode rhs;
// save operator type
int operatorType = this.currentLexeme.typeIndex;
Lexeme operator = this.currentLexeme;
// advance over '*', '/', or '%'
this.advance();
// get right-hand side
rhs = this.parseUnaryExpression();
switch (operatorType)
{
case JSTokenTypes.STAR:
result = this.createNode(JSParseNodeTypes.MULTIPLY, operator);
break;
case JSTokenTypes.FORWARD_SLASH:
result = this.createNode(JSParseNodeTypes.DIVIDE, operator);
break;
case JSTokenTypes.PERCENT:
result = this.createNode(JSParseNodeTypes.MOD, operator);
break;
default:
String typeName = JSTokenTypes.getName(operatorType);
this.throwParseError(JSMessages.getString("error.internal.multiplicative.expression") + typeName); //$NON-NLS-1$
}
result.appendChild(lhs);
result.appendChild(rhs);
}
return result;
}
/**
* <pre>
* NewExpression
* : MemberExpression
* | NEW NewExpression
* ;
* </pre>
*/
/**
* Parse ObjectLiteral
* <p>
*
* <pre>
* ObjectLiteral
* : LCURLY RCURLY
* | LCURLY PropertyNameAndValueList RCURLY
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseObjectLiteral() throws ParseException, LexerException
{
JSParseNode result = this.createNode(JSParseNodeTypes.OBJECT_LITERAL, this.currentLexeme);
// advance over '{'
this.assertAndAdvance(JSTokenTypes.LCURLY, "error.internal.punctuator"); //$NON-NLS-1$
if (this.inSet(propertyNameSet))
{
// parse and add name/value pair
result.appendChild(this.parseNameValuePair());
while (this.isType(JSTokenTypes.COMMA))
{
// advance over ','
this.advance();
// allow an object literal body to end with a comma
if (this.isType(JSTokenTypes.RCURLY) == false)
{
// parse and add name/value pair
result.appendChild(this.parseNameValuePair());
}
}
}
// advance over '}'
result.includeLexemeInRange(this.currentLexeme);
this.assertAndAdvance(JSTokenTypes.RCURLY, "error.object.literal.rcurly"); //$NON-NLS-1$
// return result
return result;
}
/**
* Parse PrimaryExpression
* <p>
*
* <pre>
* PrimaryExpression
* : THIS
* | IDENTIFIER
* | Literal
* | ArrayLiteral
* | ObjectLiteral
* | LPAREN Expression RPAREN
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parsePrimaryExpression() throws ParseException, LexerException
{
JSParseNode result = null;
switch (this.currentLexeme.typeIndex)
{
case JSTokenTypes.FORWARD_SLASH:
case JSTokenTypes.FORWARD_SLASH_EQUAL:
Lexeme save = this.currentLexeme;
// We're supposed to have a regex here but the lexer is reporting the incorrect type since it doesn't
// have the proper context, so we have to re-scan this section of source
this.rescan(REGEX_GROUP);
// make sure we ended up with a REGEX
if (this.currentLexeme.typeIndex == JSTokenTypes.REGEX)
{
// save regex node
result = this.createNode(JSParseNodeTypes.REGULAR_EXPRESSION, this.currentLexeme);
// advance over regex
this.advance();
}
else
{
ILexer lexer = this.getLexer();
// reset lexeme to get a better error
this.currentLexeme = save;
// put lexeme back into list
this.addLexeme(this.currentLexeme);
// reset the lexer's offset to the beginning of the invalid lexeme's position
lexer.setCurrentOffset(save.getEndingOffset());
// switch lexer over to a group that will scan for the correct token type
lexer.setGroup(DEFAULT_GROUP);
// throw error
this.assertType(JSTokenTypes.REGEX, "error.regex"); //$NON-NLS-1$
}
break;
case JSTokenTypes.REGEX:
// save regex node
result = this.createNode(JSParseNodeTypes.REGULAR_EXPRESSION, this.currentLexeme);
// advance over regex
this.advance();
break;
case JSTokenTypes.FALSE:
// save false node
result = this.createNode(JSParseNodeTypes.FALSE, this.currentLexeme);
// advance over 'false'
this.advance();
break;
case JSTokenTypes.IDENTIFIER:
// save identifier
result = this.createNode(JSParseNodeTypes.IDENTIFIER, this.currentLexeme);
// advance over identifier
this.advance();
break;
case JSTokenTypes.LBRACKET:
result = this.parseArrayLiteral();
break;
case JSTokenTypes.LCURLY:
result = this.parseObjectLiteral();
break;
case JSTokenTypes.LPAREN:
Lexeme lparen = this.currentLexeme;
// advance over '('
this.advance();
// get interior expression
JSParseNode expression = this.parseExpression(false, "error.group.expression"); //$NON-NLS-1$
// make sure we have ')'
this.assertType(JSTokenTypes.RPAREN, "error.group.rparen"); //$NON-NLS-1$
// advance over ')'
this.advance();
// create command node
result = this.createNode(JSParseNodeTypes.GROUP, lparen);
result.appendChild(expression);
break;
case JSTokenTypes.NULL:
// save null node
result = this.createNode(JSParseNodeTypes.NULL, this.currentLexeme);
// advance over 'null'
this.advance();
break;
case JSTokenTypes.NUMBER:
// save number node
result = this.createNode(JSParseNodeTypes.NUMBER, this.currentLexeme);
// advance over number
this.advance();
break;
case JSTokenTypes.STRING:
// save string node
result = this.createNode(JSParseNodeTypes.STRING, this.currentLexeme);
// advance over string
this.advance();
break;
case JSTokenTypes.THIS:
// save 'this' node
result = this.createNode(JSParseNodeTypes.THIS, this.currentLexeme);
// advance over 'this'
this.advance();
break;
case JSTokenTypes.TRUE:
// save true node
result = this.createNode(JSParseNodeTypes.TRUE, this.currentLexeme);
// advance over 'true'
this.advance();
break;
default:
// NOTE: If we want to throw errors on future reserved keywords, that would need to be done here
String message = JSMessages.getString("error.primitive"); //$NON-NLS-1$
if (this.isEOS())
{
message += JSMessages.getString("error.found.eof"); //$NON-NLS-1$
}
this.throwParseError(message);
}
return result;
}
/**
* Parse PropertyNameAndValueList
* <p>
*
* <pre>
* PropertyNameAndValueList
* : PropertyName COLON AssignmentExpression
* | PropertyNameAndValueList COMMA PropertyName COLON AssignmentExpression
* ;
* PropertyName
* : IDENTIFIER
* | STRING
* | NUMBER
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseNameValuePair() throws ParseException, LexerException
{
JSParseNode result;
JSParseNode name;
JSParseNode value;
// make sure we have a valid property name
this.assertInSet(propertyNameSet, "error.object.literal.name"); //$NON-NLS-1$
String identifier = this.currentLexeme.getText();
if ("get".equals(identifier) || "set".equals(identifier)) //$NON-NLS-1$ //$NON-NLS-2$
{
Lexeme keyword = this.currentLexeme;
// advance over keyword
this.advance();
switch (this.currentLexeme.typeIndex)
{
case JSTokenTypes.COLON:
// get name
name = this.createNode(JSParseNodeTypes.IDENTIFIER, keyword);
// advance over ':'
this.assertAndAdvance(JSTokenTypes.COLON, "error.object.literal.colon"); //$NON-NLS-1$
// parse expression
value = this.parseAssignmentExpression(false);
break;
default:
// get name
name = this.createNode(JSParseNodeTypes.IDENTIFIER, this.currentLexeme);
// parse function declaration
value = this.parseFunctionDeclaration2(true, keyword);
break;
}
}
else
{
// get name
name = this.createNode(JSParseNodeTypes.IDENTIFIER, this.currentLexeme);
// advance over property name
this.advance();
// advance over ':'
this.assertAndAdvance(JSTokenTypes.COLON, "error.object.literal.colon"); //$NON-NLS-1$
// parse expression
value = this.parseAssignmentExpression(false);
}
// create new command node
result = this.createNode(JSParseNodeTypes.NAME_VALUE_PAIR, null);
result.appendChild(name);
result.appendChild(value);
return result;
}
/**
* Parse RelationalExpression
* <p>
*
* <pre>
* RelationalExpression
* : ShiftExpression
* | RelationalExpression LESS ShiftExpression
* | RelationalExpression GREATER ShiftExpression
* | RelationalExpression LESS_EQUAL ShiftExpression
* | RelationalExpression GREATER_EQUAL ShiftExpression
* | RelationalExpression INSTANCEOF ShiftExpression
* | RelationalExpression IN ShiftExpression
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseRelationalExpression(boolean noIn) throws ParseException, LexerException
{
JSParseNode result = this.parseShiftExpression();
while (this.inSet(relationalExpressionSet))
{
// check if we're supposed to include 'in'
if (noIn)
{
break;
}
JSParseNode lhs = result;
JSParseNode rhs;
// save operator type
int operatorType = this.currentLexeme.typeIndex;
Lexeme operator = this.currentLexeme;
// advance over '<', '>', '<=', '>=', 'instanceof', or 'in'
this.advance();
// get right-hand side
rhs = this.parseShiftExpression();
switch (operatorType)
{
case JSTokenTypes.LESS:
result = this.createNode(JSParseNodeTypes.LESS_THAN, operator);
result.appendChild(lhs);
result.appendChild(rhs);
break;
case JSTokenTypes.GREATER:
result = this.createNode(JSParseNodeTypes.GREATER_THAN, operator);
result.appendChild(lhs);
result.appendChild(rhs);
break;
case JSTokenTypes.LESS_EQUAL:
result = this.createNode(JSParseNodeTypes.LESS_THAN_OR_EQUAL, operator);
result.appendChild(lhs);
result.appendChild(rhs);
break;
case JSTokenTypes.GREATER_EQUAL:
result = this.createNode(JSParseNodeTypes.GREATER_THAN_OR_EQUAL, operator);
result.appendChild(lhs);
result.appendChild(rhs);
break;
case JSTokenTypes.INSTANCEOF:
result = this.createNode(JSParseNodeTypes.INSTANCE_OF, operator);
result.appendChild(lhs);
result.appendChild(rhs);
break;
case JSTokenTypes.IN:
result = this.createNode(JSParseNodeTypes.IN, operator);
result.appendChild(lhs);
result.appendChild(rhs);
break;
default:
String typeName = JSTokenTypes.getName(operatorType);
this.throwParseError(JSMessages.getString("error.internal.relational.expression") + typeName); //$NON-NLS-1$
}
}
return result;
}
/**
* Parse ReturnStatement
* <p>
*
* <pre>
* ReturnStatement
* : RETURN [no LINE_TERMINATOR here] Expression? SEMICOLON
* ;
* </pre>
*
* @throws LexerException
* @throws ParseException
*/
private JSParseNode parseReturnStatement() throws ParseException, LexerException
{
JSParseNode result;
JSParseNode expression = this.createNode(JSParseNodeTypes.EMPTY, null); // JSParseNode.Empty;
Lexeme keyword = this.currentLexeme;
// advance over 'return'
this.assertAndAdvance(JSTokenTypes.RETURN, "error.internal.keyword"); //$NON-NLS-1$
if (this.inSet(returnSet) == false)
{
// parse expression
expression = this.parseExpression(false, "error.return.expression"); //$NON-NLS-1$
}
// create command node
result = this.createNode(JSParseNodeTypes.RETURN, keyword);
result.appendChild(expression);
// handle ';'
this.handleOptionalSemicolon(result);
// if (this._inFunction == false)
// {
// // TODO: throw error since 'return' is only allowed inside of a function
// }
return result;
}
/**
* Parse ShiftExpression
* <p>
*
* <pre>
* ShiftExpression
* : AdditiveExpression
* | ShiftExpression LESS_LESS AdditiveExpression
* | ShiftExpression GREATER_GREATER AdditiveExpression
* | ShiftExpression GREATER_GREATER_GREATER AdditiveExpression
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseShiftExpression() throws ParseException, LexerException
{
JSParseNode result = this.parseAdditiveExpression();
while (this.inSet(shiftExpressionSet))
{
JSParseNode lhs = result;
JSParseNode rhs;
// save operator type
int operatorType = this.currentLexeme.typeIndex;
Lexeme operator = this.currentLexeme;
// advance over '<<', '>>', or '>>>'
this.advance();
// get right-hand side
rhs = this.parseAdditiveExpression();
// create command node
switch (operatorType)
{
case JSTokenTypes.LESS_LESS:
result = this.createNode(JSParseNodeTypes.SHIFT_LEFT, operator);
result.appendChild(lhs);
result.appendChild(rhs);
break;
case JSTokenTypes.GREATER_GREATER:
result = this.createNode(JSParseNodeTypes.SHIFT_RIGHT, operator);
result.appendChild(lhs);
result.appendChild(rhs);
break;
case JSTokenTypes.GREATER_GREATER_GREATER:
result = this.createNode(JSParseNodeTypes.ARITHMETIC_SHIFT_RIGHT, operator);
result.appendChild(lhs);
result.appendChild(rhs);
break;
default:
String typeName = JSTokenTypes.getName(operatorType);
this.throwParseError(JSMessages.getString("error.internal.shift.expression") + typeName); //$NON-NLS-1$
}
}
return result;
}
/**
* Parse SourceElement
* <p>
*
* <pre>
* SourceElement
* : FunctionDeclaration
* | Statement
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseSourceElement() throws ParseException, LexerException
{
JSParseNode result = null;
Lexeme startingLexeme = this.currentLexeme;
if (startingLexeme != EOS)
{
switch (startingLexeme.typeIndex)
{
case JSTokenTypes.FUNCTION:
try
{
result = this.parseFunctionDeclaration(true);
}
catch (ParseException pe)
{
result = this.recover(startingLexeme, pe);
}
break;
default:
result = this.parseStatement();
break;
}
}
return result;
}
/**
* Parse a statement
* <p>
*
* <pre>
* Statement
* : Block
* | BreakStatement
* | ContinueStatement
* | EmptyStatement
* | ExpressionStatement
* | IfStatement
* | IterationStatement
* | LabelledStatement
* | ReturnStatement
* | SwitchStatement
* | ThrowStatement
* | TryStatement
* | VariableStatement
* | WithStatement
* ;
* EmptyStatement
* : SEMICOLON
* ;
* IterationStatement
* : DoStatement
* | WhileStatement
* | ForStatement
* ;
* </pre>
*
* @throws LexerException
*/
private JSParseNode parseStatement() throws LexerException
{
Lexeme startingLexeme = this.currentLexeme;
JSParseNode result = null;
try
{
switch (this.currentLexeme.typeIndex)
{
// Block
case JSTokenTypes.LCURLY:
result = this.parseBlock();
break;
// BreakStatement
case JSTokenTypes.BREAK:
result = this.parseBreakStatement();
break;
// ContinueStatement
case JSTokenTypes.CONTINUE:
result = this.parseContinueStatement();
break;
// EmptyStatement
case JSTokenTypes.SEMICOLON:
result = this.createNode(JSParseNodeTypes.EMPTY, this.currentLexeme);
result.setIncludesSemicolon(true);
// advance over ';'
this.advance();
break;
// ForStatement
case JSTokenTypes.FOR:
result = this.parseForStatement();
break;
// LabelledStatement
case JSTokenTypes.IDENTIFIER:
result = this.parseLabelledStatement();
break;
// IfStatement
case JSTokenTypes.IF:
result = this.parseIfStatement();
break;
// DoStatement
case JSTokenTypes.DO:
result = this.parseDoStatement();
break;
// ReturnStatement
case JSTokenTypes.RETURN:
result = this.parseReturnStatement();
break;
// SwitchStatement
case JSTokenTypes.SWITCH:
result = this.parseSwitchStatement();
break;
// ThrowStatement
case JSTokenTypes.THROW:
result = this.parseThrowStatement();
break;
// TryStatement
case JSTokenTypes.TRY:
result = this.parseTryStatement();
break;
// VarStatement
// TryStatement
case JSTokenTypes.VAR:
result = this.parseVarExpression(false);
// NOTE: we're putting the EOL handling here so we can
// re-use parseVarExpression in ForStatement
this.handleOptionalSemicolon(result);
break;
// WhileStatement
case JSTokenTypes.WHILE:
result = this.parseWhileStatement();
break;
// WithStatement
case JSTokenTypes.WITH:
result = this.parseWithStatement();
break;
// Expression
default:
result = this.parseExpression(false, null); // TODO: add error message
this.handleOptionalSemicolon(result);
}
}
catch (ParseException pe)
{
result = this.recover(startingLexeme, pe);
}
return result;
}
/**
* <pre>
* StatementList
* : Statement
* | StatementList Statement
* ;
* </pre>
*/
/**
* Parse SwitchStatement
* <p>
*
* <pre>
* SwitchStatement
* : SWITCH LPAREN Expression RPAREN CaseBlock
* ;
* CaseBlock
* : LCURLY RCURLY
* | LCURLY CaseClauses RCURLY
* | LCURLY CaseClauses DefaultClause RCURLY
* | LCURLY CaseClauses DefaultClause CaseClauses RCURLY
* | LCURLY DefaultClause RCURLY
* | LCURLY DefaultClause CaseClauses RCURLY
* ;
* CaseClauses
* : CaseClause
* | CaseClauses CaseClause
* ;
* </pre>
*
* @throws LexerException
* @throws ParseException
*/
private JSParseNode parseSwitchStatement() throws ParseException, LexerException
{
JSParseNode result;
boolean hasDefault = false;
Lexeme keyword = this.currentLexeme;
// advance over 'switch'
this.assertAndAdvance(JSTokenTypes.SWITCH, "error.internal.keyword"); //$NON-NLS-1$
// advance over '('
this.assertAndAdvance(JSTokenTypes.LPAREN, "error.switch.lparen"); //$NON-NLS-1$
// parse expression
JSParseNode expression = this.parseExpression(false, "error.switch.expression"); //$NON-NLS-1$
// create command node
result = this.createNode(JSParseNodeTypes.SWITCH, keyword);
result.appendChild(expression);
// advance over ')'
this.assertAndAdvance(JSTokenTypes.RPAREN, "error.switch.rparen"); //$NON-NLS-1$
// advance over '{'
this.assertAndAdvance(JSTokenTypes.LCURLY, "error.switch.lcurly"); //$NON-NLS-1$
// process all case and default blocks
while (this.isType(JSTokenTypes.RCURLY) == false && this.isEOS() == false)
{
// parse 'case' or 'default'
JSParseNode block = this.parseCaseOrDefaultBlock();
// make sure we have only one default case
if (block.getTypeIndex() == JSParseNodeTypes.DEFAULT)
{
if (hasDefault)
{
this.throwParseError(JSMessages.getString("error.switch.default.duplicate")); //$NON-NLS-1$
}
else
{
hasDefault = true;
}
}
// add to switch
result.appendChild(block);
}
// advance over '}'
result.includeLexemeInRange(currentLexeme);
this.assertAndAdvance(JSTokenTypes.RCURLY, "error.switch.rcurly"); //$NON-NLS-1$
return result;
}
/**
* Parse ThrowStatement
* <p>
*
* <pre>
* ThrowStatement
* : THROW Expression SEMICOLON
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseThrowStatement() throws ParseException, LexerException
{
JSParseNode result;
Lexeme keyword = this.currentLexeme;
// advance over 'throw;
this.assertAndAdvance(JSTokenTypes.THROW, "error.internal.keyword"); //$NON-NLS-1$
// parse expression
JSParseNode expression = this.parseExpression(false, "error.throw.expression"); //$NON-NLS-1$
// create command node
result = this.createNode(JSParseNodeTypes.THROW, keyword);
result.appendChild(expression);
// handle ';'
this.handleOptionalSemicolon(result);
// return result
return result;
}
/**
* Parse TryStatement
* <p>
*
* <pre>
* TryStatement
* : TRY Block Catch
* | TRY Block Finally
* | TRY Block Catch Finally
* ;
* </pre>
*
* @throws LexerException
* @throws ParseException
*/
private JSParseNode parseTryStatement() throws ParseException, LexerException
{
JSParseNode result;
JSParseNode body;
JSParseNode catchNode = this.createNode(JSParseNodeTypes.EMPTY, null); // JSParseNode.Empty;
JSParseNode finallyNode = this.createNode(JSParseNodeTypes.EMPTY, null); // JSParseNode.Empty;
// advance over 'try'
this.assertType(JSTokenTypes.TRY, "error.internal.keyword"); //$NON-NLS-1$
// get 'try' index
Lexeme keyword = this.currentLexeme;
// advance over 'try'
this.advance();
// parse body
body = this.parseBlock();
if (this.isType(JSTokenTypes.CATCH))
{
// parse catch
catchNode = this.parseCatch();
}
if (this.isType(JSTokenTypes.FINALLY))
{
// parse finally
finallyNode = this.parseFinally();
}
// make sure either catch or finally is defined
if (catchNode == null && finallyNode == null)
{
this.throwParseError(JSMessages.getString("error.try.empty")); //$NON-NLS-1$
}
// create command node
result = this.createNode(JSParseNodeTypes.TRY, keyword);
result.appendChild(body);
if (catchNode.isEmpty())
{
result.includeLexemeInRange(catchNode.getEndingLexeme());
}
result.appendChild(catchNode);
if (!finallyNode.isEmpty())
{
result.includeLexemeInRange(finallyNode.getEndingLexeme());
}
result.appendChild(finallyNode);
return result;
}
/**
* Parse UnaryExpression
* <p>
*
* <pre>
* UnaryExpression
* : PostfixExpression
* | DELETE UnaryExpression
* | VOID UnaryExpression
* | TYPEOF UnaryExpression
* | PLUS_PLUS UnaryExpression
* | MINUS_MINUS UnaryExpression
* | PLUS UnaryExpression
* | MINUS UnaryExpression
* | TILDE UnaryExpression
* | EXCLAMATION UnaryExpression
* ;
* PostfixExpression
* : LeftHandSideExpression
* | LeftHandSideExpression PLUS_PLUS
* | LeftHandSideExpression MINUS_MINUS
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseUnaryExpression() throws ParseException, LexerException
{
JSParseNode result = null;
if (this.inSet(unaryExpressionSet))
{
// save type
int type = this.currentLexeme.typeIndex;
// determine which expression type to call
boolean isMemberExpression = this.inSet(postfixExpressionSet);
// get operator index
Lexeme operator = this.currentLexeme;
// advance over operator
this.advance();
if (isMemberExpression)
{
// parse member expression
result = this.parseMemberExpression(true);
}
else
{
// parse unary expression
result = this.parseUnaryExpression();
}
switch (type)
{
case JSTokenTypes.DELETE:
JSParseNode temp = this.createNode(JSParseNodeTypes.DELETE, operator);
temp.appendChild(result);
result = temp;
break;
case JSTokenTypes.EXCLAMATION:
temp = this.createNode(JSParseNodeTypes.LOGICAL_NOT, operator);
temp.appendChild(result);
result = temp;
break;
case JSTokenTypes.MINUS:
temp = this.createNode(JSParseNodeTypes.NEGATE, operator);
temp.appendChild(result);
result = temp;
break;
case JSTokenTypes.MINUS_MINUS:
temp = this.createNode(JSParseNodeTypes.PRE_DECREMENT, operator);
temp.appendChild(result);
result = temp;
break;
case JSTokenTypes.PLUS:
temp = this.createNode(JSParseNodeTypes.POSITIVE, operator);
temp.appendChild(result);
result = temp;
break;
case JSTokenTypes.PLUS_PLUS:
temp = this.createNode(JSParseNodeTypes.PRE_INCREMENT, operator);
temp.appendChild(result);
result = temp;
break;
case JSTokenTypes.TILDE:
temp = this.createNode(JSParseNodeTypes.BITWISE_NOT, operator);
temp.appendChild(result);
result = temp;
break;
case JSTokenTypes.TYPEOF:
temp = this.createNode(JSParseNodeTypes.TYPEOF, operator);
temp.appendChild(result);
result = temp;
break;
case JSTokenTypes.VOID:
temp = this.createNode(JSParseNodeTypes.VOID, operator);
temp.appendChild(result);
result = temp;
break;
default:
break;
}
}
else
{
result = this.parseMemberExpression(true);
if (this.currentLexeme.isAfterEOL() == false && this.inSet(postfixExpressionSet))
{
int type = this.currentLexeme.typeIndex;
// get operator index
Lexeme operator = this.currentLexeme;
// advance over '--' or '++'
this.advance();
switch (type)
{
case JSTokenTypes.PLUS_PLUS:
JSParseNode temp = this.createNode(JSParseNodeTypes.POST_INCREMENT, operator);
temp.appendChild(result);
result = temp;
break;
case JSTokenTypes.MINUS_MINUS:
temp = this.createNode(JSParseNodeTypes.POST_DECREMENT, operator);
temp.appendChild(result);
result = temp;
break;
default:
break;
}
}
}
return result;
}
/**
* Parse VarExpression
* <p>
*
* <pre>
* VarExpression
* : VAR VariableDeclarationList SEMICOLON
* ;
* VariableDeclarationList
* : VariableDeclaration
* | VariableDeclarationList COMMA VariableDeclaration
* ;
* </pre>
*
* @throws LexerException
* @throws ParseException
*/
private JSParseNode parseVarExpression(boolean noIn) throws ParseException, LexerException
{
JSParseNode result = this.createNode(JSParseNodeTypes.VAR, this.currentLexeme);
// advance over 'var'
this.assertAndAdvance(JSTokenTypes.VAR, "error.internal.keyword"); //$NON-NLS-1$
// add variable declaration to our collection
result.appendChild(this.parseVariableDeclaration(noIn));
while (this.isType(JSTokenTypes.COMMA))
{
// advance over ','
this.advance();
// add variable declaration to our collection
result.appendChild(this.parseVariableDeclaration(noIn));
}
return result;
}
/**
* Parse VariableDeclaration
* <p>
*
* <pre>
* VariableDeclaration
* : IDENTIFIER
* | IDENTIFIER EQUAL AssignmentExpression
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseVariableDeclaration(boolean noIn) throws ParseException, LexerException
{
JSParseNode id;
JSParseNode assignment = this.createNode(JSParseNodeTypes.EMPTY, null); // JSParseNode.Empty;
JSParseNode result;
// make sure we have at least one identifier
this.assertType(JSTokenTypes.IDENTIFIER, "error.var.name"); //$NON-NLS-1$
// grab name
id = this.createNode(JSParseNodeTypes.IDENTIFIER, this.currentLexeme);
// advance over identifier
this.advance();
if (this.isType(JSTokenTypes.EQUAL))
{
// advance over '='
this.advance();
// get right-hand side
assignment = this.parseAssignmentExpression(noIn);
}
// create result
result = this.createNode(JSParseNodeTypes.DECLARATION, null);
result.appendChild(id);
result.appendChild(assignment);
return result;
}
/**
* Parse WhileStatement
* <p>
*
* <pre>
* WhileStatement
* : WHILE LPAREN Expression RPAREN Statement
* ;
* </pre>
*
* @throws LexerException
* @throws ParseException
*/
private JSParseNode parseWhileStatement() throws ParseException, LexerException
{
JSParseNode result;
JSParseNode condition;
JSParseNode body;
Lexeme keyword = this.currentLexeme;
// advance over 'while'
this.assertAndAdvance(JSTokenTypes.WHILE, "error.internal.keyword"); //$NON-NLS-1$
// advance over '('
this.assertAndAdvance(JSTokenTypes.LPAREN, "error.while.lparen"); //$NON-NLS-1$
// parse condition
condition = this.parseExpression(false, "error.while.expression"); //$NON-NLS-1$
// advance over ')'
this.assertAndAdvance(JSTokenTypes.RPAREN, "error.while.rparen"); //$NON-NLS-1$
// parse body
body = this.parseStatement();
result = this.createNode(JSParseNodeTypes.WHILE, keyword);
result.appendChild(condition);
result.appendChild(body);
return result;
}
/**
* Parse WithStatement
* <p>
*
* <pre>
* WithStatement
* : WITH LPAREN Expression RPAREN Statement
* ;
* </pre>
*
* @throws ParseException
* @throws LexerException
*/
private JSParseNode parseWithStatement() throws ParseException, LexerException
{
JSParseNode result;
JSParseNode expression;
JSParseNode body;
Lexeme keyword = this.currentLexeme;
// advance over 'with'
this.assertAndAdvance(JSTokenTypes.WITH, "error.internal.keyword"); //$NON-NLS-1$
// advance over '('
this.assertAndAdvance(JSTokenTypes.LPAREN, "error.with.lparen"); //$NON-NLS-1$
// parse expression
expression = this.parseExpression(false, "error.with.expression"); //$NON-NLS-1$
// advance over '('
this.assertAndAdvance(JSTokenTypes.RPAREN, "error.with.rparen"); //$NON-NLS-1$
// parse body
body = this.parseStatement();
result = this.createNode(JSParseNodeTypes.WITH, keyword);
result.appendChild(expression);
result.appendChild(body);
return result;
}
/**
* Try to recover from a parse error
*
* @param exception
* @return Returns an error node including all lexemes that are considered in error
* @throws LexerException
*/
private JSParseNode recover(Lexeme startingLexeme, ParseException exception) throws LexerException
{
// try to recover to a known good starting point
JSParseNode error;
// create a new error node
error = this.createNode(JSParseNodeTypes.ERROR, startingLexeme);
// if (this._currentLexeme != EOS)
// {
// // associate error token with error node
// error.setErrorRange(this._currentLexeme);
// }
// else if (startingLexeme != EOS)
// {
// // associate error token with error node
// error.setErrorRange(startingLexeme);
// }
if (startingLexeme != EOS)
{
LexemeList lexemes = this.getLexemeList();
int lexemeCount = lexemes.size();
boolean inStopSet = false;
// get starting and ending lexeme indexes
int start = lexemes.getLexemeIndex(startingLexeme);
int stop = (this.currentLexeme != EOS) ? lexemes.getLexemeIndex(this.currentLexeme) : lexemeCount;
// boolean inBlock = this._inFunction == true || this._inBlock == true;
// boolean isRCurly = this._currentLexeme.typeIndex == JSTokenTypes.RCURLY;
// boolean consumeCurly = inBlock && isRCurly;
//
// if (start == stop && consumeCurly == false)
if (start == stop)
{
stop = Math.min(stop + 1, lexemeCount);
this.advance();
}
// associate all lexemes from the starting lexeme to the current
// lexeme with this error node
// for (int i = start; i < stop; i++)
// {
// Lexeme currentLexeme = this._lexemes.get(i);
//
// currentLexeme.setCommandNode(error);
// }
if (this.isEOS() == false && this.currentLexeme.isAfterEOL())
{
inStopSet = this.inSet(stopSet);
if (inStopSet == false)
{
// add current lexeme to the error node
// this._currentLexeme.setCommandNode(error);
// advance to the next lexeme
this.advance();
}
}
// stop until we reach the end of a line, a semicolon, or the end of
// the file
while (this.isEOS() == false && this.currentLexeme.isAfterEOL() == false && inStopSet == false)
{
inStopSet = this.inSet(stopSet);
// add current lexeme to the error node
// this._currentLexeme.setCommandNode(error);
// advance to the next lexeme
this.advance();
}
}
// mark error node as root command node
// error.setCommandRoot();
// add to error list.
// NOTE: We can't add error nodes to an error list if it does not refer to a range of characters in the
// source code. EOF errors are empty, so parse methods receiving this error must add lexemes to the error
// and add the error to the error list
// if (error.isAtEOF() == false)
// {
// this.removeInvalidErrors(this._state);
// this._errors.add(error);
// }
return error;
}
/**
* Reposition the lexer to the current lexeme's beginning offset, switch to a new lexer group and rescan
*
* @param groupName
* The name of the lexer group to switch to
* @throws LexerException
*/
private void rescan(String groupName) throws LexerException
{
ILexer lexer = this.getLexer();
LexemeList lexemes = this.getLexemeList();
// remove token since it is (potentially) invalid
lexemes.remove(this.currentLexeme);
// update the affected region
lexemes.getAffectedRegion().includeInRange(this.currentLexeme);
// set the lexer's group and reset lexer's offset to the beginning of
// the invalid lexeme's position
lexer.setLexerState(groupName, this.currentLexeme.offset);
// force a rescan
this.advance();
// include new lexeme in refresh region
if (this.isEOS() == false)
{
this.getParseState().addUpdateRegion(this.currentLexeme);
}
}
/**
* Throw a parse exception
*
* @param message
* The exception message
* @throws ParseException
*/
protected void throwParseError(String message) throws ParseException
{
if (this.currentLexeme != null)
{
if (this._expressionErrorKey != null && this._expressionErrorKey.length() > 0)
{
message = JSMessages.getString(this._expressionErrorKey);
}
if (this.currentLexeme != EOS)
{
message += JSMessages.getString("error.near", this.currentLexeme.getText()); //$NON-NLS-1$
}
}
throw new ParseException(message, -1);
}
/**
* @return language to process text inside of PI instructions
*/
public String getPiLanguage()
{
return this._piLanguage;
}
/**
* @param piLanguage
*/
public void setPiLanguage(String piLanguage)
{
this._piLanguage = piLanguage;
}
}