com.tangosol.dev.compiler.java.Compiler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of coherence Show documentation
Show all versions of coherence Show documentation
Oracle Coherence Community Edition
/*
* Copyright (c) 2000, 2020, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
package com.tangosol.dev.compiler.java;
import com.tangosol.dev.assembler.CodeAttribute;
import com.tangosol.dev.assembler.Return;
import com.tangosol.dev.compiler.CompilerErrorInfo;
import com.tangosol.dev.compiler.CompilerException;
import com.tangosol.dev.compiler.SyntaxException;
import com.tangosol.dev.compiler.Context;
import com.tangosol.dev.compiler.TypeInfo;
import com.tangosol.dev.compiler.MethodInfo;
import com.tangosol.dev.compiler.ParamInfo;
import com.tangosol.dev.component.DataType;
import com.tangosol.util.Base;
import com.tangosol.util.ErrorList;
import com.tangosol.util.NullImplementation;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Set;
import java.util.Map.Entry;
import java.util.ArrayList;
import java.util.HashMap;
/**
* This class implements the Java script compiler.
*
* 1. The Java script is passed as a string. The first step is to create
* a character stream (using the Script interface) which understands
* Unicode escape sequences; this is done within the Tokenizer by using
* the UnicodeScript class.
*
* 2. The second step is to lexically analyze and parse the Java script.
* This is done by the Tokenizer class.
*
* 3. The third step is to parse the tokens into a parse tree. This is
* done by this class (Compiler) using the Statement and Expression
* classes.
*
* 4. The fourth step is to semantically analyze the parse tree. This is
* handled by the various statements and expressions within the parse
* tree. This step is referred to as "precompile".
*
* 5. The last step is to generate the Java byte codes necessary for each
* statement and expression. This step is referred to as "compile".
*
* The following is the hierarchy of language elements. Note that only the
* leaf elements are non-abstract:
*
* Element
* Statement
* EmptyStatement
* DeclarationStatement
* ExpressionStatement
* ConditionalStatement (note: does not include "for")
* IfStatement
* DoStatement
* WhileStatement
* Block
* StatementBlock
* ForStatement
* CatchClause
* SwitchStatement
* TargetStatement
* LabelStatement
* CaseClause
* DefaultClause
* GuardedStatement
* TryStatement
* SynchronizedStatement
* FinallyClause
* BranchStatement
* BreakStatement
* ContinueStatement
* ExitStatement
* ReturnStatement
* ThrowStatement
* Expression
* NameExpression
* TypeExpression
* DimensionedExpression
* LiteralExpression
* NullExpression
* BooleanExpression
* CharExpression
* IntExpression
* LongExpression
* FloatExpression
* DoubleExpression
* StringExpression
* ArrayExpression
* VariableExpression
* ConditionalExpression
* NewExpression
* NewClassExpression
* NewArrayExpression
* UnaryExpression
* IncExpression
* PreIncExpression
* PostIncExpression
* PreDecExpression
* PostDecExpression
* PlusExpression
* MinusExpression
* NotExpression
* BitNotExpression
* CastExpression
* ArrayAccessExpression
* FieldAccessExpression
* InvocationExpression
* BinaryExpression
* AssignExpression
* CastAssignExpression
* LogicalExpression
* AndExpression
* OrExpression
* BitwiseExpression
* BitAndExpression
* BitOrExpression
* BitXorExpression
* EqualityExpression
* EqualExpression
* NotEqualExpression
* RelationalExpression
* LessExpression
* NotLessExpression
* GreaterExpression
* NotGreaterExpression
* InstanceOfExpression
* ShiftExpression
* LeftShiftExpression
* RightShiftExpression
* UnsignedShiftExpression
* AdditiveExpression
* AddExpression
* SubtractExpression
* MultiplicativeExpression
* MultiplyExpression
* DivideExpression
* ModuloExpression
*
* @version 1.00, 09/14/98
* @author Cameron Purdy
*/
public class Compiler
extends Base
implements com.tangosol.dev.compiler.Compiler, Constants, TokenConstants
{
// ----- construction ---------------------------------------------------
/**
* Construct a Java compiler. A public default constructor is required.
*/
public Compiler()
{
}
// ----- compiler interface ---------------------------------------------
/**
* Compile the passed script.
*
* @param ctx the compiler context
* @param sScript the script to compile (as a string)
* @param errlist the error list to log to
*
* @exception CompilerException thrown if the compilation of this script
* fails
*/
public void compile(Context ctx, String sScript, ErrorList errlist)
throws CompilerException
{
// parameters are required
if (ctx == null || sScript == null || errlist == null)
{
throw new IllegalArgumentException(CLASS + ".compile: "
+ "Parameters required!");
}
// register parameter names/types
Block block = new StatementBlock();
// for instance methods, there is an implied final parameter "this"
MethodInfo method = ctx.getMethodInfo();
azzert(method != null, "Failed to retrieve the context method");
if (DEBUG)
{
out();
printMethodInfo(method);
}
if (!method.isStatic())
{
block.addStatement(createParameterDeclaration(block, true,
method.getTypeInfo().getDataType(), "this"));
}
int cParams = method.getParamCount();
for (int i = 0; i < cParams; ++i)
{
ParamInfo param = method.getParamInfo(i);
block.addStatement(createParameterDeclaration(block,
param.isFinal(), param.getDataType(), param.getName()));
}
// store the information used by parsing/code generation
CodeAttribute code = ctx.getCode();
this.errlist = errlist;
this.toker = new Tokenizer(sScript, code.getLine(), errlist);
this.token = next();
// parse the script
parseCompilationUnit(block);
if (DEBUG)
{
block.print();
}
if (errlist.isSevere())
{
throw new CompilerException();
}
// check the imports
checkImports(ctx);
if (errlist.isSevere())
{
throw new CompilerException();
}
// check the parse tree
DualSet setUVars = new DualSet(NullImplementation.getSet());
DualSet setFVars = new DualSet(NullImplementation.getSet());
HashMap mapThrown = new HashMap();
block.precompile(ctx, setUVars, setFVars, mapThrown, errlist);
if (!mapThrown.isEmpty())
{
for (Enumeration enmr = method.exceptionTypes(); enmr.hasMoreElements(); )
{
Expression.catchException(ctx, (DataType) enmr.nextElement(), mapThrown);
if (mapThrown.isEmpty())
{
break;
}
}
}
// uncaught/undeclared exceptions
if (!mapThrown.isEmpty())
{
for (Iterator iterThrown = mapThrown.entrySet().iterator(); iterThrown.hasNext(); )
{
// map key is the data type of the exception; map value is a
// set of expressions that throw the exception
Entry entry = (Entry) iterThrown.next();
DataType dtThrown = (DataType) entry.getKey();
for (Iterator iterExpr = ((Set) entry.getValue()).iterator(); iterExpr.hasNext(); )
{
Expression expr = (Expression) iterExpr.next();
expr.logError(ERROR, EXCEPT_UNCAUGHT,
new String[] {dtThrown.getClassName()}, errlist);
}
}
}
// check for errors from the pre-compile pass
if (errlist.isSevere())
{
throw new CompilerException();
}
// generate code
if (block.compile(ctx, code, true, errlist))
{
// the main block completes; if the method is void, then add
// the implied return, otherwise it is an error
if (method.getDataType() == DataType.VOID)
{
code.add(new Return());
}
else
{
logError(ERROR, RETURN_MISSING, null,
block.getEndLine(), block.getEndOffset(), 0);
}
}
if (DEBUG)
{
code.print();
out();
}
// check for errors from the compile pass
if (errlist.isSevere())
{
throw new CompilerException();
}
}
// ----- script parsing -------------------------------------------------
/**
* Parse the script.
*
* Goal:
* CompilationUnit
* CompilationUnit:
* ImportDeclarations-opt BlockStatements-opt
*/
protected void parseCompilationUnit(Block block)
throws CompilerException
{
parseImportDeclarations();
parseBlockStatements(block);
// after parsing the block, the remaining token should be the pretend
// closing curly brace for the block
if (token != null && token.id == SEP_RCURLYBRACE && token.length == 0)
{
// the block was started with a corresponding pretend open curly
block.setEndToken(token);
}
else
{
// log error - tokens remaining, probably missing {
logError(ERROR, UNBALANCED_BRACE, null, token.getLine(), token.getOffset(), 0);
}
}
/**
* Parse the "import" declarations and register each imported class under
* its short name.
*/
protected void parseImportDeclarations()
throws CompilerException
{
while (peek(KEY_IMPORT) != null)
{
try
{
Token tokName; // name token
String sFull; // fully qualified name
// parse name "n.n.n"
StringBuffer sb = new StringBuffer();
boolean fFirst = true;
do
{
if (fFirst)
{
fFirst = false;
}
else
{
sb.append('.');
}
tokName = match(IDENT);
sb.append(tokName.getText());
}
while (peek(SEP_DOT) != null);
sFull = sb.toString();
// check for optional alias
if (peek(KEY_AS) != null)
{
tokName = match(IDENT);
}
match(SEP_SEMICOLON);
// register the import name
tblImports.put(tokName, sFull);
}
catch (SyntaxException e)
{
Expurgate: while (true)
{
switch (token.id)
{
// end of an import or statement
case SEP_SEMICOLON:
next();
// start of an import
case KEY_IMPORT:
// start of a statement
case KEY_BREAK:
case KEY_CASE:
case KEY_CONTINUE:
case KEY_DEFAULT:
case KEY_DO:
case KEY_FINAL:
case KEY_FOR:
case KEY_IF:
case KEY_RETURN:
case KEY_SWITCH:
case KEY_SYNCHRONIZED:
case KEY_THROW:
case KEY_TRY:
case KEY_WHILE:
// could be end of script - definitely not part of
// the imports!
case SEP_RCURLYBRACE:
break Expurgate;
default:
next();
}
}
}
}
}
// ----- statement parsing ----------------------------------------------
/**
* Parse a statement block.
*
* Block:
* { BlockStatements-opt }
*/
protected Block parseBlock(Statement outer)
throws CompilerException
{
Block block = new StatementBlock(outer, token);
if (token.id == SEP_LCURLYBRACE)
{
// { BlockStatements-opt }
match(SEP_LCURLYBRACE);
parseBlockStatements(block);
block.setEndToken(match(SEP_RCURLYBRACE));
}
else
{
// open curly expected; someone probably just forgot their curlies
logError(ERROR, TOKEN_EXPECTED, new String[] {"{"},
token.getLine(), token.getOffset(), 0);
// question: if the statement parsing fails, would it be
// better to handle a syntax error here or to let the caller
// deal with it? for now, we'll assume the latter
block.addStatement(parseStatement(block));
}
return block;
}
/**
* Parse a sequence of statements.
*
* BlockStatements:
* BlockStatement
* BlockStatements BlockStatement
*/
protected void parseBlockStatements(Block block)
throws CompilerException
{
while (token.id != SEP_RCURLYBRACE)
{
try
{
block.addStatement(parseStatement(block));
}
catch (SyntaxException e)
{
// an error occurred parsing the statement ... skip it
expurgateStatement();
}
}
}
/**
* Parse a statement.
*
* @param outer contains the statement being parsed
*/
protected Statement parseStatement(Statement outer)
throws CompilerException
{
// determine the containing block
Block block = (outer instanceof Block ? (Block) outer : outer.getBlock());
switch (token.id)
{
// EmptyStatement:
// ;
case SEP_SEMICOLON:
return new EmptyStatement(outer, current());
// Block:
// { BlockStatements-opt }
case SEP_LCURLYBRACE:
return parseBlock(outer);
// TryStatement:
// try Block Catches
// try Block Catches-opt Finally
// Catches:
// CatchClause
// Catches CatchClause
// CatchClause:
// catch ( FormalParameter ) Block
// Finally:
// finally Block
case KEY_TRY:
{
TryStatement stmt = new TryStatement(outer, current());
stmt.setInnerStatement(parseBlock(stmt));
CatchClause last = null;
boolean fNoClauses = true;
while (token.id == KEY_CATCH)
{
CatchClause clause = new CatchClause(stmt, current());
match(SEP_LPARENTHESIS);
// note that the CatchClause itself is the Block (and
// that the exception variable is treated as a parameter)
DeclarationStatement stmtDecl = parseDeclaration(clause);
stmtDecl.setParameter(true);
clause.addStatement(stmtDecl);
match(SEP_RPARENTHESIS);
clause.addStatement(parseBlock(clause));
if (fNoClauses)
{
stmt.setCatchClause(clause);
}
else
{
last.setNextStatement(clause);
}
last = clause;
fNoClauses = false;
}
if (token.id == KEY_FINALLY)
{
FinallyClause clause = new FinallyClause(stmt, current());
clause.setInnerStatement(parseBlock(clause));
stmt.setFinallyClause(clause);
fNoClauses = false;
}
if (fNoClauses)
{
// no deadly parsing errors have occurred, but parts of
// the try statement are missing
// TODO log error
}
return stmt;
}
// SynchronizedStatement:
// synchronized ( Expression ) Block
case KEY_SYNCHRONIZED:
{
SynchronizedStatement stmt = new SynchronizedStatement(outer, current());
match(SEP_LPARENTHESIS);
stmt.setExpression(parseExpression(block));
match(SEP_RPARENTHESIS);
Statement inner = parseBlock(stmt);
stmt.setInnerStatement(inner);
stmt.setEndToken(inner.getEndToken());
return stmt;
}
// DoStatement:
// do Statement while ( Expression ) ;
case KEY_DO:
{
DoStatement stmt = new DoStatement(outer, current());
stmt.setInnerStatement(parseStatement(stmt));
match(KEY_WHILE);
match(SEP_LPARENTHESIS);
stmt.setTest(parseExpression(block));
match(SEP_RPARENTHESIS);
stmt.setEndToken(match(SEP_SEMICOLON));
return stmt;
}
// WhileStatement:
// while ( Expression ) Statement
case KEY_WHILE:
{
WhileStatement stmt = new WhileStatement(outer, current());
match(SEP_LPARENTHESIS);
stmt.setTest(parseExpression(block));
match(SEP_RPARENTHESIS);
stmt.setInnerStatement(parseStatement(stmt));
return stmt;
}
// ForStatement:
// for ( ForInit-opt ; Expression-opt ; ForUpdate-opt ) Statement
// ForInit:
// StatementExpressionList
// LocalVariableDeclaration
// ForUpdate:
// StatementExpressionList
// StatementExpressionList:
// StatementExpression
// StatementExpressionList , StatementExpression
case KEY_FOR:
{
ForStatement stmt = new ForStatement(outer, current());
match(SEP_LPARENTHESIS);
if (token.id != SEP_SEMICOLON)
{
stmt.setInit(parseStatementList(stmt, true));
}
match(SEP_SEMICOLON);
if (token.id != SEP_SEMICOLON)
{
// note that the ForStatement itself is the Block
stmt.setTest(parseExpression(stmt));
}
match(SEP_SEMICOLON);
if (token.id != SEP_RPARENTHESIS)
{
stmt.setUpdate(parseStatementList(stmt, false));
}
match(SEP_RPARENTHESIS);
stmt.setInnerStatement(parseStatement(stmt));
return stmt;
}
// IfThenStatement:
// if ( Expression ) Statement
// IfThenElseStatement:
// if ( Expression ) StatementNoShortIf else Statement
case KEY_IF:
{
IfStatement stmt = new IfStatement(outer, current());
match(SEP_LPARENTHESIS);
stmt.setTest(parseExpression(block));
match(SEP_RPARENTHESIS);
stmt.setThenStatement(parseStatement(stmt));
// although the LALR(1) grammar goes into great detail about
// the "NoShortIf" constructs, recursive decent parsing only
// cares whether or not an else exists at this point
if (peek(KEY_ELSE) != null)
{
stmt.setElseStatement(parseStatement(stmt));
}
return stmt;
}
// ReturnStatement:
// return Expression-opt ;
case KEY_RETURN:
{
ReturnStatement stmt = new ReturnStatement(outer, current());
if (token.id != SEP_SEMICOLON)
{
stmt.setExpression(parseExpression(block));
}
stmt.setEndToken(match(SEP_SEMICOLON));
return stmt;
}
// ThrowStatement:
// throw Expression ;
case KEY_THROW:
{
ThrowStatement stmt = new ThrowStatement(outer, current());
stmt.setExpression(parseExpression(block));
stmt.setEndToken(match(SEP_SEMICOLON));
return stmt;
}
// SwitchStatement:
// switch ( Expression ) SwitchBlock
// SwitchBlock:
// { SwitchBlockStatementGroups-opt SwitchLabels-opt }
// SwitchBlockStatementGroups:
// SwitchBlockStatementGroup
// SwitchBlockStatementGroups SwitchBlockStatementGroup
// SwitchBlockStatementGroup:
// SwitchLabels BlockStatements
// SwitchLabels:
// SwitchLabel
// SwitchLabels SwitchLabel
// SwitchLabel:
// case ConstantExpression :
// default :
case KEY_SWITCH:
{
SwitchStatement stmt = new SwitchStatement(outer, current());
match(SEP_LPARENTHESIS);
stmt.setTest(parseExpression(block));
match(SEP_RPARENTHESIS);
match(SEP_LCURLYBRACE);
SwitchBlock: while (true)
{
try
{
switch (token.id)
{
case KEY_CASE:
{
CaseClause clause = new CaseClause(stmt, current());
// note that SwitchStatement itself is the block
clause.setTest(parseExpression(stmt));
clause.setEndToken(match(OP_COLON));
stmt.addStatement(clause);
}
break;
case KEY_DEFAULT:
{
DefaultClause clause = new DefaultClause(stmt, current());
clause.setEndToken(match(OP_COLON));
stmt.addStatement(clause);
}
break;
default:
stmt.addStatement(parseStatement(stmt));
break;
case SEP_RCURLYBRACE:
break SwitchBlock;
}
}
catch (SyntaxException e)
{
expurgateStatement();
}
}
stmt.setEndToken(current()); // right curly
return stmt;
}
// BreakStatement:
// break Identifier-opt ;
case KEY_BREAK:
{
BreakStatement stmt = new BreakStatement(outer, current(), peek(IDENT));
stmt.setEndToken(match(SEP_SEMICOLON));
return stmt;
}
// ContinueStatement:
// continue Identifier-opt ;
case KEY_CONTINUE:
{
ContinueStatement stmt = new ContinueStatement(outer, current(), peek(IDENT));
stmt.setEndToken(match(SEP_SEMICOLON));
return stmt;
}
// VariableDeclaration:
// Modifiers-opt Type VariableDeclarators
case KEY_FINAL:
case KEY_BOOLEAN:
case KEY_BYTE:
case KEY_CHAR:
case KEY_SHORT:
case KEY_INT:
case KEY_LONG:
case KEY_FLOAT:
case KEY_DOUBLE:
{
Statement stmt = parseDeclaration(outer);
stmt.setEndToken(match(SEP_SEMICOLON));
return stmt;
}
// LabeledStatement:
// Identifier : Statement
// VariableDeclaration:
// Modifiers-opt Type VariableDeclarators
// ExpressionStatement:
// StatementExpression ;
case IDENT:
{
Expression expr = parseExpression(block);
switch (token.id)
{
case IDENT:
{
// expr must be the type in a variable declaration
Statement stmt = parseDeclaration(outer, null, toTypeExpression(expr));
stmt.setEndToken(match(SEP_SEMICOLON));
return stmt;
}
case OP_COLON:
{
// expr must be a label, which is a simple identifier
Token tokLabel = expr.getStartToken();
if (tokLabel == expr.getEndToken() && tokLabel.getCategory() == IDENTIFIER)
{
LabelStatement stmt = new LabelStatement(outer, tokLabel, match(OP_COLON));
stmt.setInnerStatement(parseStatement(stmt));
return stmt;
}
// assume it was supposed to be a statement expression
// (so fall through)
}
default:
{
// expr must be a statement expression
// turn it into a ExpressionStatement
Statement stmt = createExpressionStatement(outer, expr);
stmt.setEndToken(match(SEP_SEMICOLON));
return stmt;
}
}
}
// ExpressionStatement:
// StatementExpression ;
case KEY_NEW:
case KEY_THIS:
case KEY_SUPER:
case SEP_LPARENTHESIS:
case OP_INCREMENT:
case OP_DECREMENT:
{
Expression expr = parseExpression(block);
Statement stmt = createExpressionStatement(outer, expr);
stmt.setEndToken(match(SEP_SEMICOLON));
return stmt;
}
// unexpected statement continuations
case KEY_ELSE:
logError(ERROR, ELSE_NO_IF, new String[] {token.getText()}, token);
throw new SyntaxException();
case KEY_CATCH:
logError(ERROR, CATCH_NO_TRY, new String[] {token.getText()}, token);
throw new SyntaxException();
case KEY_FINALLY:
logError(ERROR, FINALLY_NO_TRY, new String[] {token.getText()}, token);
throw new SyntaxException();
case KEY_CASE:
case KEY_DEFAULT:
logError(ERROR, LABEL_NO_SWITCH, new String[] {token.getText()}, token);
throw new SyntaxException();
// unexpected separator; probably recoverable
case SEP_DOT:
case SEP_COMMA:
case SEP_RPARENTHESIS:
case SEP_LBRACKET:
case SEP_RBRACKET:
// unexpected keyword; probably recoverable
case KEY_INSTANCEOF:
// unexpected operator; probably recoverable
case OP_ADD:
case OP_SUB:
case OP_MUL:
case OP_DIV:
case OP_REM:
case OP_SHL:
case OP_SHR:
case OP_USHR:
case OP_BITAND:
case OP_BITOR:
case OP_BITXOR:
case OP_BITNOT:
case OP_ASSIGN:
case OP_ASSIGN_ADD:
case OP_ASSIGN_SUB:
case OP_ASSIGN_MUL:
case OP_ASSIGN_DIV:
case OP_ASSIGN_REM:
case OP_ASSIGN_SHL:
case OP_ASSIGN_SHR:
case OP_ASSIGN_USHR:
case OP_ASSIGN_BITAND:
case OP_ASSIGN_BITOR:
case OP_ASSIGN_BITXOR:
case OP_TEST_EQ:
case OP_TEST_NE:
case OP_TEST_GT:
case OP_TEST_GE:
case OP_TEST_LT:
case OP_TEST_LE:
case OP_LOGICAL_AND:
case OP_LOGICAL_OR:
case OP_LOGICAL_NOT:
case OP_CONDITIONAL: // identifier missing?
case OP_COLON: // label missing?
// unexpected literal value; probably recoverable
case LIT_NULL:
case LIT_TRUE:
case LIT_FALSE:
case LIT_CHAR:
case LIT_INT:
case LIT_LONG:
case LIT_FLOAT:
case LIT_DOUBLE:
case LIT_STRING:
logError(ERROR, TOKEN_UNEXPECTED, new String[] {token.getText()}, token);
throw new SyntaxException();
// totally unexpected tokens ... assume unrecoverable
case SEP_RCURLYBRACE:
case KEY_IMPORT:
logError(ERROR, TOKEN_PANIC, new String[] {token.getText()}, token);
throw new CompilerException();
// illegal keywords - not used in Java script language
case KEY_ABSTRACT:
case KEY_CLASS:
case KEY_EXTENDS:
case KEY_IMPLEMENTS:
case KEY_INTERFACE:
case KEY_NATIVE:
case KEY_PACKAGE:
case KEY_PRIVATE:
case KEY_PROTECTED:
case KEY_PUBLIC:
case KEY_STATIC:
case KEY_THROWS:
case KEY_TRANSIENT:
case KEY_VOID:
case KEY_VOLATILE:
// unsupported keyword in a statement
logError(ERROR, TOKEN_UNSUPP, new String[] {token.getText()}, token);
throw new CompilerException();
// illegal keywords - not used in Java
case KEY_CONST:
case KEY_GOTO:
logError(ERROR, TOKEN_ILLEGAL, new String[] {token.getText()}, token);
throw new CompilerException();
default:
logError(ERROR, TOKEN_UNKNOWN, new String[] {token.getText()}, token);
throw new CompilerException();
}
}
/**
* Parse a statement expression list. Additional statements are linked
* after the first (via the Statement.next field). This is used only
* within the "for" statement.
*
* ForInit:
* StatementExpressionList
* LocalVariableDeclaration
* ForUpdate:
* StatementExpressionList
* StatementExpressionList:
* StatementExpression
* StatementExpressionList , StatementExpression
*
* @param outer contains the statement being parsed
* @param fDeclare true if the statement expression list may be a
* variable declaration
*
* @return the parsed statement(s)
*/
protected Statement parseStatementList(Statement outer, boolean fDeclare)
throws CompilerException
{
Block block = (outer instanceof Block ? (Block) outer : outer.getBlock());
// check for a variable declaration
Token tokFinal = (fDeclare ? peek(KEY_FINAL) : null);
Expression expr = parseExpression(block);
if (fDeclare && (tokFinal != null || token.id == IDENT))
{
return parseDeclaration(outer, tokFinal, toTypeExpression(expr));
}
// build the StatementExpressionList
ExpressionStatement stmt = createExpressionStatement(outer, expr);
ExpressionStatement last = stmt;
while (peek(SEP_COMMA) != null)
{
ExpressionStatement next = createExpressionStatement(outer, parseExpression(block));
last.setNextStatement(next);
last = next;
}
return stmt;
}
/**
* Create an ExpressionStatement from the StatementExpression. If the
* expression is not an expression statement, this method throws a syntax
* exception.
*
* ExpressionStatement:
* StatementExpression ;
* StatementExpression:
* Assignment
* PreIncrementExpression
* PreDecrementExpression
* PostIncrementExpression
* PostDecrementExpression
* MethodInvocation
* ClassInstanceCreationExpression
*
* @param outer contains the expression statement
* @param expr the statement expression
*
* @return the new statement
*
* @exception CompilerException expression is not an ExpressionStatement
*/
protected ExpressionStatement createExpressionStatement(Statement outer, Expression expr)
throws CompilerException
{
if (expr instanceof AssignExpression ||
expr instanceof InvocationExpression ||
expr instanceof NewClassExpression ||
expr instanceof PreIncExpression ||
expr instanceof PostIncExpression ||
expr instanceof PreDecExpression ||
expr instanceof PostDecExpression )
{
ExpressionStatement stmt = new ExpressionStatement(outer, expr.getStartToken());
stmt.setExpression(expr);
return stmt;
}
expr.logError(ERROR, EXPR_NOT_STMT, null, errlist);
throw new SyntaxException();
}
/**
* Helper to parse a variable declaration statement.
*
* @param outer contains the statement being parsed
*
* @return the variable declaration statement
*/
protected DeclarationStatement parseDeclaration(Statement outer)
throws CompilerException
{
Block block = (outer instanceof Block ? (Block) outer : outer.getBlock());
Token tokFinal = peek(KEY_FINAL);
TypeExpression type;
switch (token.id)
{
case KEY_BOOLEAN:
case KEY_BYTE:
case KEY_CHAR:
case KEY_SHORT:
case KEY_INT:
case KEY_LONG:
case KEY_FLOAT:
case KEY_DOUBLE:
type = parseDimensionedExpression(block, new TypeExpression(block, current()));
break;
default:
type = toTypeExpression(parseExpression(block));
break;
}
return parseDeclaration(outer, tokFinal, type);
}
/**
* Parse a variable declaration statement. The modifiers and data type
* are already parsed. This method does not eat the trailing semi-colon
* in order to be usable from the statement list parsing used by the
* "for" statement parsing.
*
* LocalVariableDeclarationStatement:
* LocalVariableDeclaration ;
* LocalVariableDeclaration: (modified for JDK 1.1 to allow "final")
* Modifiers-opt Type VariableDeclarators
* VariableDeclarators:
* VariableDeclarator
* VariableDeclarators , VariableDeclarator
* VariableDeclarator:
* VariableDeclaratorId
* VariableDeclaratorId = VariableInitializer
* VariableDeclaratorId:
* Identifier
* VariableDeclaratorId [ ]
* VariableInitializer:
* Expression
* ArrayInitializer
* ArrayInitializer:
* { VariableInitializers-opt , -opt }
* VariableInitializers:
* VariableInitializer
* VariableInitializers , VariableInitializer
*
* @param outer contains the statement being parsed
* @param tokFinal the "final" modifier token or null
* @param type the type expression
*
* @return the variable declaration statement
*/
protected DeclarationStatement parseDeclaration(Statement outer, Token tokFinal, TypeExpression type)
throws CompilerException
{
Block block = (outer instanceof Block ? (Block) outer : outer.getBlock());
Token tokFirst = (tokFinal == null ? type.getStartToken() : tokFinal);
DeclarationStatement decl = new DeclarationStatement(outer, tokFirst);
decl.setModifier(tokFinal);
decl.setTypeExpression(type);
Statement last = null;
do
{
// the expressions may or may not be assignment expressions,
// but at this point, assume they all are; this simplifies
// parsing of things like: int a, b=3, c=b+1, d, e=b*c;
ExpressionStatement stmt = new ExpressionStatement(decl, token);
stmt.setExpression(parseExpression(block));
// add the variable declaration to the declaration statement
if (last == null)
{
decl.setInnerStatement(stmt);
}
else
{
last.setNextStatement(stmt);
}
last = stmt;
}
while (peek(SEP_COMMA) != null);
return decl;
}
/**
* Used to declare parameters.
*
* @param fFinal
* @param dtParam
* @param sName
*
* @return a declaration statement
*/
protected Statement createParameterDeclaration(Block block, boolean fFinal, DataType dtParam, String sName)
{
// pretend that at (0,0) there are a whole bunch of tokens that
// magically define the variable
Token tokFinal = (fFinal ? new Token(Token.TOK_FINAL, 0, 0, 0) : null);
Token tokType = null;
Token tokLast = null;
Token tokIdent = new Token(IDENTIFIER, NONE, IDENT, null, sName, 0, 0, 0);
DataType dt = dtParam;
while (dt.isArray())
{
dt = dt.getElementType();
if (tokLast != null)
{
tokLast = new Token(Token.TOK_RBRACKET, 0, 0, 0);
}
}
if (dt.isPrimitive())
{
switch (dt.getJVMSignature().charAt(0))
{
case 'Z':
tokType = Token.TOK_BOOLEAN;
break;
case 'B':
tokType = Token.TOK_BYTE;
break;
case 'C':
tokType = Token.TOK_CHAR;
break;
case 'S':
tokType = Token.TOK_SHORT;
break;
case 'I':
tokType = Token.TOK_INT;
break;
case 'J':
tokType = Token.TOK_LONG;
break;
case 'F':
tokType = Token.TOK_FLOAT;
break;
case 'D':
tokType = Token.TOK_DOUBLE;
break;
}
tokType = new Token(tokType, 0, 0, 0);
}
else
{
String sType = (dt.isComponent() ? dt.getComponentName() : dt.getClassName());
int of = sType.indexOf('.');
if (of >= 0)
{
if (tokLast != null)
{
tokLast = new Token(Token.IDENTIFIER, Token.NONE, Token.IDENT, null,
sType.substring(sType.lastIndexOf('.') + 1), 0, 0, 0);
}
sType = sType.substring(0, of);
}
tokType = new Token(Token.IDENTIFIER, Token.NONE, Token.IDENT, null, sType, 0, 0, 0);
}
TypeExpression type = new TypeExpression(block, tokType, dtParam);
type.setEndToken(tokLast == null ? tokType : tokLast);
// create declaration
DeclarationStatement decl = new DeclarationStatement(block, (tokFinal == null ? tokType : tokFinal));
decl.setParameter(true);
decl.setModifier(tokFinal);
decl.setTypeExpression(type);
// declare the parameter
ExpressionStatement stmt = new ExpressionStatement(decl, tokIdent);
stmt.setExpression(new NameExpression(block, tokIdent));
decl.setInnerStatement(stmt);
return decl;
}
/**
* Parsing died in the middle of a statement. Discard tokens until it
* appears that the statement ends.
*/
protected void expurgateStatement()
throws CompilerException
{
while (true)
{
switch (token.id)
{
// 99% of the time, statements will end with semi-colon
case SEP_SEMICOLON:
next();
return;
// keywords which start a new statement
case KEY_BREAK:
case KEY_CONTINUE:
case KEY_DO:
case KEY_FINAL:
case KEY_FOR:
case KEY_IF:
case KEY_RETURN:
case KEY_SWITCH:
case KEY_SYNCHRONIZED:
case KEY_THROW:
case KEY_TRY:
case KEY_WHILE:
return;
// parenthetically speaking ...
case SEP_LCURLYBRACE: // trailing statement or array initializer
case SEP_LPARENTHESIS: // who knows ... method invocation?
case SEP_LBRACKET: // array index?
expurgateParenthetical();
break;
// end of a block (or last token)
case SEP_RCURLYBRACE:
return;
default:
next();
break;
}
}
}
/**
* Discard tokens comprising a parenthetical expression. When called, the
* current token should be one that opens a parenthetical expression. The
* term "expression" does not refer to the language element Expression but
* to some open/close pair of tokens and everything in between.
*/
protected void expurgateParenthetical()
throws CompilerException
{
int idOpen = token.id;
int idClose;
String sClose;
switch (idOpen)
{
default:
case SEP_LCURLYBRACE:
idClose = SEP_RCURLYBRACE;
sClose = "}";
break;
case SEP_LPARENTHESIS:
idClose = SEP_RPARENTHESIS;
sClose = ")";
break;
case SEP_LBRACKET:
idClose = SEP_RBRACKET;
sClose = "]";
break;
}
int cUnmatched = 0; // number of opens w/o closes
do
{
int id = token.id;
if (id == SEP_RCURLYBRACE && token.length == 0)
{
// this is the last token in the script
// note: the call to next() will bomb
logError(ERROR, TOKEN_EXPECTED, new String[] {sClose},
token.getLine(), token.getOffset(), 0);
}
else if (id == idOpen)
{
++cUnmatched;
}
else if (id == idClose)
{
--cUnmatched;
}
next();
}
while (cUnmatched > 0);
}
// ----- expression parsing ---------------------------------------------
/**
* Parse an expression. The expression parsing uses a recursive descent
* algorithm, which requires a different type of grammar than that found
* in the Java Language Specification. For example, the JLS will state:
*
* ConstructList:
* Construct
* ConstructList Construct
*
* A recursive descent parser implicitly re-arranges this as:
*
* ConstructList:
* Construct ConstructList-opt
*
* This method handles the top-most-level of expression parsing, which is
* the assignment expression.
*
* ConstantExpression:
* Expression
* Expression:
* AssignmentExpression
* AssignmentExpression:
* ConditionalExpression
* Assignment
* Assignment:
* LeftHandSide AssignmentOperator AssignmentExpression
* LeftHandSide:
* Name
* FieldAccess
* ArrayAccess
* AssignmentOperator: one of
* = *= /= %= += -= <<= >>= >>>= &= ^= |=
*
* @param block the current block
*
* @return the parsed expression
*/
protected Expression parseExpression(Block block)
throws CompilerException
{
Expression exprLeft = parseConditionalExpression(block);
Token tokOp;
Expression exprRight;
switch (token.id)
{
case OP_ASSIGN:
return new AssignExpression(exprLeft, current(), parseExpression(block));
case OP_ASSIGN_ADD:
tokOp = current();
exprRight = new AddExpression(exprLeft, tokOp, parseExpression(block));
break;
case OP_ASSIGN_SUB:
tokOp = current();
exprRight = new SubtractExpression(exprLeft, tokOp, parseExpression(block));
break;
case OP_ASSIGN_MUL:
tokOp = current();
exprRight = new MultiplyExpression(exprLeft, tokOp, parseExpression(block));
break;
case OP_ASSIGN_DIV:
tokOp = current();
exprRight = new DivideExpression(exprLeft, tokOp, parseExpression(block));
break;
case OP_ASSIGN_REM:
tokOp = current();
exprRight = new ModuloExpression(exprLeft, tokOp, parseExpression(block));
break;
case OP_ASSIGN_SHL:
tokOp = current();
exprRight = new LeftShiftExpression(exprLeft, tokOp, parseExpression(block));
break;
case OP_ASSIGN_SHR:
tokOp = current();
exprRight = new RightShiftExpression(exprLeft, tokOp, parseExpression(block));
break;
case OP_ASSIGN_USHR:
tokOp = current();
exprRight = new UnsignedShiftExpression(exprLeft, tokOp, parseExpression(block));
break;
case OP_ASSIGN_BITAND:
tokOp = current();
exprRight = new BitAndExpression(exprLeft, tokOp, parseExpression(block));
break;
case OP_ASSIGN_BITOR:
tokOp = current();
exprRight = new BitOrExpression(exprLeft, tokOp, parseExpression(block));
break;
case OP_ASSIGN_BITXOR:
tokOp = current();
exprRight = new BitXorExpression(exprLeft, tokOp, parseExpression(block));
break;
default:
return exprLeft;
}
return new CastAssignExpression(exprLeft, tokOp, exprRight);
}
/**
* Parse a conditional expression.
*
* ConditionalExpression:
* ConditionalOrExpression
* ConditionalOrExpression ? Expression : ConditionalExpression
*
* @param block the current block
*
* @return the parsed expression
*/
protected Expression parseConditionalExpression(Block block)
throws CompilerException
{
Expression expr = parseOrExpression(block);
if (token.id == OP_CONDITIONAL)
{
expr = new ConditionalExpression(expr, current(), parseExpression(block),
match(OP_COLON), parseConditionalExpression(block));
}
return expr;
}
/**
* Parse a logical or expression.
*
* ConditionalOrExpression:
* ConditionalAndExpression
* ConditionalOrExpression || ConditionalAndExpression
*
* @param block the current block
*
* @return the parsed expression
*/
protected Expression parseOrExpression(Block block)
throws CompilerException
{
Expression expr = parseAndExpression(block);
while (token.id == OP_LOGICAL_OR)
{
expr = new OrExpression(expr, current(), parseAndExpression(block));
}
return expr;
}
/**
* Parse a logical and expression.
*
* ConditionalAndExpression:
* InclusiveOrExpression
* ConditionalAndExpression && InclusiveOrExpression
*
* @param block the current block
*
* @return the parsed expression
*/
protected Expression parseAndExpression(Block block)
throws CompilerException
{
Expression expr = parseBitOrExpression(block);
while (token.id == OP_LOGICAL_AND)
{
expr = new AndExpression(expr, current(), parseBitOrExpression(block));
}
return expr;
}
/**
* Parse a bitwise or expression.
*
* InclusiveOrExpression:
* ExclusiveOrExpression
* InclusiveOrExpression | ExclusiveOrExpression
*
* @param block the current block
*
* @return the parsed expression
*/
protected Expression parseBitOrExpression(Block block)
throws CompilerException
{
Expression expr = parseBitXorExpression(block);
while (token.id == OP_BITOR)
{
expr = new BitOrExpression(expr, current(), parseBitXorExpression(block));
}
return expr;
}
/**
* Parse a bitwise exclusive or expression.
*
* ExclusiveOrExpression:
* AndExpression
* ExclusiveOrExpression ^ AndExpression
*
* @param block the current block
*
* @return the parsed expression
*/
protected Expression parseBitXorExpression(Block block)
throws CompilerException
{
Expression expr = parseBitAndExpression(block);
while (token.id == OP_BITXOR)
{
expr = new BitXorExpression(expr, current(), parseBitAndExpression(block));
}
return expr;
}
/**
* Parse a bitwise and expression.
*
* AndExpression:
* EqualityExpression
* AndExpression & EqualityExpression
*
* @param block the current block
*
* @return the parsed expression
*/
protected Expression parseBitAndExpression(Block block)
throws CompilerException
{
Expression expr = parseEqualityExpression(block);
while (token.id == OP_BITAND)
{
expr = new BitAndExpression(expr, current(), parseEqualityExpression(block));
}
return expr;
}
/**
* Parse an equality test expression.
*
* EqualityExpression:
* RelationalExpression
* EqualityExpression == RelationalExpression
* EqualityExpression != RelationalExpression
*
* @param block the current block
*
* @return the parsed expression
*/
protected Expression parseEqualityExpression(Block block)
throws CompilerException
{
Expression expr = parseRelationalExpression(block);
while (true)
{
switch (token.id)
{
case OP_TEST_EQ:
expr = new EqualExpression(expr, current(), parseRelationalExpression(block));
break;
case OP_TEST_NE:
expr = new NotEqualExpression(expr, current(), parseRelationalExpression(block));
break;
default:
return expr;
}
}
}
/**
* Parse a relational test expression.
*
* RelationalExpression:
* ShiftExpression
* RelationalExpression < ShiftExpression
* RelationalExpression > ShiftExpression
* RelationalExpression <= ShiftExpression
* RelationalExpression >= ShiftExpression
* RelationalExpression instanceof ReferenceType
*
* @param block the current block
*
* @return the parsed expression
*/
protected Expression parseRelationalExpression(Block block)
throws CompilerException
{
Expression expr = parseShiftExpression(block);
while (true)
{
switch (token.id)
{
case OP_TEST_LT:
expr = new LessExpression(expr, current(), parseShiftExpression(block));
break;
case OP_TEST_GE:
expr = new NotLessExpression(expr, current(), parseShiftExpression(block));
break;
case OP_TEST_GT:
expr = new GreaterExpression(expr, current(), parseShiftExpression(block));
break;
case OP_TEST_LE:
expr = new NotGreaterExpression(expr, current(), parseShiftExpression(block));
break;
case KEY_INSTANCEOF:
expr = new InstanceOfExpression(expr, current(), parseShiftExpression(block));
break;
default:
return expr;
}
}
}
/**
* Parse a bitwise shift expression.
*
* ShiftExpression:
* AdditiveExpression
* ShiftExpression << AdditiveExpression
* ShiftExpression >> AdditiveExpression
* ShiftExpression >>> AdditiveExpression
*
* @param block the current block
*
* @return the parsed expression
*/
protected Expression parseShiftExpression(Block block)
throws CompilerException
{
Expression expr = parseAdditiveExpression(block);
while (true)
{
switch (token.id)
{
case OP_SHL:
expr = new LeftShiftExpression(expr, current(), parseAdditiveExpression(block));
break;
case OP_SHR:
expr = new RightShiftExpression(expr, current(), parseAdditiveExpression(block));
break;
case OP_USHR:
expr = new UnsignedShiftExpression(expr, current(), parseAdditiveExpression(block));
break;
default:
return expr;
}
}
}
/**
* Parse an additive expression.
*
* AdditiveExpression:
* MultiplicativeExpression
* AdditiveExpression + MultiplicativeExpression
* AdditiveExpression - MultiplicativeExpression
*
* @param block the current block
*
* @return the parsed expression
*/
protected Expression parseAdditiveExpression(Block block)
throws CompilerException
{
Expression expr = parseMultiplicativeExpression(block);
while (true)
{
switch (token.id)
{
case OP_ADD:
expr = new AddExpression(expr, current(), parseMultiplicativeExpression(block));
break;
case OP_SUB:
expr = new SubtractExpression(expr, current(), parseMultiplicativeExpression(block));
break;
default:
return expr;
}
}
}
/**
* Parse a multiplicative expression.
*
* MultiplicativeExpression:
* UnaryExpression
* MultiplicativeExpression * UnaryExpression
* MultiplicativeExpression / UnaryExpression
* MultiplicativeExpression % UnaryExpression
*
* @param block the current block
*
* @return the parsed expression
*/
protected Expression parseMultiplicativeExpression(Block block)
throws CompilerException
{
Expression expr = parseUnaryExpression(block);
while (true)
{
switch (token.id)
{
case OP_MUL:
expr = new MultiplyExpression(expr, current(), parseUnaryExpression(block));
break;
case OP_DIV:
expr = new DivideExpression(expr, current(), parseUnaryExpression(block));
break;
case OP_REM:
expr = new ModuloExpression(expr, current(), parseUnaryExpression(block));
break;
default:
return expr;
}
}
}
/**
* Parse a unary expression.
*
* UnaryExpression:
* PreIncrementExpression
* PreDecrementExpression
* + UnaryExpression
* - UnaryExpression
* UnaryExpressionNotPlusMinus
* PreIncrementExpression:
* ++ UnaryExpression
* PreDecrementExpression:
* -- UnaryExpression
* UnaryExpressionNotPlusMinus:
* PostfixExpression
* ~ UnaryExpression
* ! UnaryExpression
* CastExpression
* CastExpression:
* ( PrimitiveType Dims-opt ) UnaryExpression
* ( Expression ) UnaryExpressionNotPlusMinus
* ( Name Dims ) UnaryExpressionNotPlusMinus
*
* This can be simplified to:
*
* UnaryExpression(sign_allowed)
* ++ UnaryExpression(true) // if sign_allowed
* -- UnaryExpression(true) // if sign_allowed
* + UnaryExpression(true) // if sign_allowed
* - UnaryExpression(true) // if sign_allowed
* ~ UnaryExpression(true)
* ! UnaryExpression(true)
* ( PrimitiveType Dims-opt ) UnaryExpression(true)
* ( Expression ) UnaryExpression(false)
* ( Name Dims ) UnaryExpression(false)
* PostfixExpression
*
* The purpose for disallowing the pre-inc/dec and the leading sign in
* some instances is to limit the tokens that could follow a cast. In
* other words, to be able to determine if the partial sequence:
*
* (T) +
*
* ... is a cast to type T of a signed number or an addition to the
* parenthesized expression T. Since, in Java, the only signed types
* and pre/post-inc/dec-remented types are primitives, and since all
* primitive types are known, the use of T as a cast of a signed or
* pre-inc/dec-remented value is determinable since T must be in the
* set of keywords set forth as PrimitiveType; more specifically, for
* semantic correctness, the type must be a NumericType:
*
* PrimitiveType:
* NumericType
* boolean
* NumericType:
* IntegralType
* FloatingPointType
* IntegralType: one of
* byte short int long char
* FloatingPointType: one of
* float double
*
* For a finite automaton parser, this is discussed in 19.1.5.
*
* The PostfixExpression grammar:
*
* PostfixExpression:
* Primary
* Name
* PostIncrementExpression
* PostDecrementExpression
* Primary:
* PrimaryNoNewArray
* ArrayCreationExpression
* Name:
* SimpleName
* QualifiedName
* SimpleName:
* Identifier
* QualifiedName:
* Name . Identifier
* PostIncrementExpression:
* PostfixExpression ++
* PostDecrementExpression:
* PostfixExpression --
*
* PrimaryNoNewArray:
* Literal
* this
* ( Expression )
* ClassInstanceCreationExpression
* FieldAccess
* MethodInvocation
* ArrayAccess
* Literal:
* IntegerLiteral
* FloatingPointLiteral
* BooleanLiteral
* CharacterLiteral
* StringLiteral
* NullLiteral
* ClassInstanceCreationExpression:
* new ClassType ( ArgumentList-opt )
* ArgumentList:
* Expression
* ArgumentList , Expression
* FieldAccess:
* Primary . Identifier
* super . Identifier
* MethodInvocation:
* Name ( ArgumentList-opt )
* Primary . Identifier ( ArgumentList-opt )
* super . Identifier ( ArgumentList-opt )
* ArrayAccess:
* Name [ Expression ]
* PrimaryNoNewArray [ Expression ]
*
* ArrayCreationExpression:
* new PrimitiveType DimExprs Dims-opt
* new ClassOrInterfaceType DimExprs Dims-opt
* DimExprs:
* DimExpr
* DimExprs DimExpr
* DimExpr:
* [ Expression ]
* Dims:
* [ ]
* Dims [ ]
*
* The JDK 1.1 added the ability to initialize an array within an
* expression, modifying
*
* From "Changes [to the Java Language Specification] for Java 1.1":
*
* You can initialize the contents of an array when you new it. For
* example, the following would be a flexible way to create an array
* of strings:
*
* String[] martians = new String[] {"Gidney", "Cloyd"};
*
* As a result, the ArrayCreationExpression grammar is changed to:
*
* ArrayCreationExpression:
* new PrimitiveType DimExprs Dims-opt ArrayInitializer-opt
* new PrimitiveType DimExprs-opt Dims ArrayInitializer
* new ClassOrInterfaceType DimExprs Dims-opt ArrayInitializer-opt
* new ClassOrInterfaceType DimExprs-opt Dims ArrayInitializer
* ArrayInitializer:
* { VariableInitializers-opt , -opt }
* VariableInitializers:
* VariableInitializer
* VariableInitializers , VariableInitializer
*
* The following basic constructs are included here for reference:
*
* Type:
* PrimitiveType
* ReferenceType
* PrimitiveType:
* NumericType
* boolean
* NumericType:
* IntegralType
* FloatingPointType
* IntegralType: one of
* byte short int long char
* FloatingPointType: one of
* float double
* ReferenceType:
* ClassOrInterfaceType
* ArrayType
* ClassOrInterfaceType:
* Name
* ClassType:
* ClassOrInterfaceType
* InterfaceType:
* ClassOrInterfaceType
* ArrayType:
* PrimitiveType [ ]
* Name [ ]
* ArrayType [ ]
*
* From this grammar, we can determine the starting set of tokens for a
* unary expression as follows:
*
* In cases where a sign is allowed:
*
* + Unary plus
* - Unary minus
* ++ Pre-increment
* -- Pre-decrement
*
* In all cases:
*
* ~ Bitwise not
* ! Logical not
* ( Cast or parenthesised expression
* new Class or array creation
* this Either the variable "this" or a field access/method invocation
* super Field access/method invocation
* null
* true
* false
* Literal A literal expression
* Identifier A variable, field access, method invocation, array access
*
* @param block the current block
*
* @return the parsed expression
*/
protected Expression parseUnaryExpression(Block block)
throws CompilerException
{
Expression expr;
switch (token.id)
{
case KEY_BOOLEAN:
case KEY_BYTE:
case KEY_CHAR:
case KEY_SHORT:
case KEY_INT:
case KEY_LONG:
case KEY_FLOAT:
case KEY_DOUBLE:
{
expr = parseNarrowingExpression(block, new TypeExpression(block, current()));
if (expr instanceof TypeExpression)
{
// this is a type, not a unary expression, but it is
// possible that someone called parseExpression() blindly
return expr;
}
}
break;
case SEP_LCURLYBRACE:
// this is not a unary expression, but it is possible that
// a variable declaration with assignment is being parsed
return parseArrayInitializer(block);
case OP_ADD:
expr = new PlusExpression(current(), parseUnaryExpression(block));
break;
case OP_SUB:
{
// there is a very odd problem introduced by the Java
// Language Specification relating to the unary minus:
// it can be applied to a literal such as the out of
// range integer 2147483648; the responsibility for
// handling this is neither the tokenizer's nor the
// semantic evaluators, so it must be in the parser
Token tokMinus = current();
switch (token.id)
{
case LIT_INT:
expr = new IntExpression(block, tokMinus, current());
break;
case LIT_LONG:
expr = new LongExpression(block, tokMinus, current());
break;
default:
expr = new MinusExpression(tokMinus, parseUnaryExpression(block));
break;
}
}
break;
case OP_INCREMENT:
expr = new PreIncExpression(current(), parseUnaryExpression(block));
break;
case OP_DECREMENT:
expr = new PreDecExpression(current(), parseUnaryExpression(block));
break;
case OP_BITNOT:
expr = new BitNotExpression(current(), parseUnaryExpression(block));
break;
case OP_LOGICAL_NOT:
expr = new NotExpression(current(), parseUnaryExpression(block));
break;
case SEP_LPARENTHESIS:
{
// parse the item in the parentheses
Token tokParen = current();
expr = parseExpression(block);
match(SEP_RPARENTHESIS);
// determine if it is a type cast
boolean fCast = false;
if (expr instanceof TypeExpression)
{
// ( PrimitiveType Dims-opt ) UnaryExpression
// ( Name Dims ) UnaryExpressionNotPlusMinus
fCast = true;
}
else
{
switch (token.id)
{
case OP_BITNOT:
case OP_LOGICAL_NOT:
case SEP_LPARENTHESIS:
case KEY_NEW:
case KEY_THIS:
case KEY_SUPER:
case IDENT:
case LIT_NULL:
case LIT_TRUE:
case LIT_FALSE:
case LIT_CHAR:
case LIT_INT:
case LIT_LONG:
case LIT_FLOAT:
case LIT_DOUBLE:
case LIT_STRING:
// ( Expression ) UnaryExpressionNotPlusMinus
fCast = true;
}
}
if (fCast)
{
expr = new CastExpression(tokParen, parseUnaryExpression(block),
toTypeExpression(expr));
}
else
{
// ( Expression )
expr = parseNarrowingExpression(block, expr);
}
}
break;
case KEY_NEW:
// ClassInstanceCreationExpression:
// new ClassType ( ArgumentList-opt )
// ArrayCreationExpression:
// new PrimitiveType DimExprs Dims-opt ArrayInitializer-opt
// new PrimitiveType DimExprs-opt Dims ArrayInitializer
// new ClassOrInterfaceType DimExprs Dims-opt ArrayInitializer-opt
// new ClassOrInterfaceType DimExprs-opt Dims ArrayInitializer
// ArrayInitializer:
// { VariableInitializers-opt , -opt }
// VariableInitializers:
// VariableInitializer
// VariableInitializers , VariableInitializer
{
Token tokFirst = current();
TypeExpression type;
boolean fArray;
switch (token.id)
{
case KEY_BOOLEAN:
case KEY_BYTE:
case KEY_CHAR:
case KEY_SHORT:
case KEY_INT:
case KEY_LONG:
case KEY_FLOAT:
case KEY_DOUBLE:
// type is primitive; must be array allocation
type = new TypeExpression(block, current());
fArray = true;
break;
default:
{
// the type is a name; may be array or class allocation
NameExpression name = new NameExpression(block, current());
while (peek(SEP_DOT) != null)
{
name.addName(match(IDENT));
}
type = new TypeExpression(name);
fArray = (token.id == SEP_LBRACKET);
}
break;
}
if (fArray)
{
// parse dim exprs then dims
ArrayList list = new ArrayList();
boolean fEmpty = false; // parsing dims (not dim exprs)
boolean fInit = true; // array requires initializers
Token tokLast;
match(SEP_LBRACKET);
do
{
if (fEmpty || token.id == SEP_RBRACKET)
{
list.add(null);
fEmpty = true;
}
else
{
list.add(parseExpression(block));
fInit = false;
}
tokLast = match(SEP_RBRACKET);
}
while (peek(SEP_LBRACKET) != null);
Expression[] aexpr = (Expression[]) list.toArray(new Expression[list.size()]);
// array initializers
ArrayExpression value = null;
if (fInit || token.id == SEP_LCURLYBRACE)
{
value = parseArrayInitializer(block);
tokLast = value.getEndToken();
}
expr = new NewArrayExpression(block, tokFirst, tokLast, type, aexpr, value);
}
else
{
Token tokLParen = match(SEP_LPARENTHESIS);
Expression[] aexpr = parseParameters(block);
Token tokRParen = match(SEP_RPARENTHESIS);
expr = parseNarrowingExpression(block, new NewClassExpression(
block, tokFirst, type, tokLParen, aexpr, tokRParen));
}
}
break;
case KEY_THIS:
expr = parseNarrowingExpression(block, new ThisExpression(block, current()));
break;
case KEY_SUPER:
expr = parseNarrowingExpression(block, new SuperExpression(block, current()));
break;
case IDENT:
expr = parseNarrowingExpression(block, new NameExpression(block, current()));
break;
case LIT_NULL:
expr = new NullExpression(block, current());
break;
case LIT_TRUE:
case LIT_FALSE:
expr = new BooleanExpression(block, current());
break;
case LIT_CHAR:
expr = new CharExpression(block, current());
break;
case LIT_INT:
{
LiteralToken literal = (LiteralToken) current();
if (literal.isOutOfRange())
{
logError(ERROR, INTEGRAL_RANGE, null, literal);
}
expr = new IntExpression(block, literal);
}
break;
case LIT_LONG:
{
LiteralToken literal = (LiteralToken) current();
if (literal.isOutOfRange())
{
logError(ERROR, INTEGRAL_RANGE, null, literal);
}
expr = new LongExpression(block, literal);
}
break;
case LIT_FLOAT:
expr = new FloatExpression(block, current());
break;
case LIT_DOUBLE:
expr = new DoubleExpression(block, current());
break;
case LIT_STRING:
expr = parseNarrowingExpression(block, new StringExpression(block, current()));
break;
default:
if (token.getCategory() == KEYWORD)
{
logError(ERROR, KEYWORD_UNEXP, new String[] {token.getText()},
token.getLine(), token.getOffset(), 0);
}
else
{
logError(ERROR, NOT_EXPRESSION, null, token.getLine(), token.getOffset(), 0);
}
throw new SyntaxException();
}
// check for post-increment/decrement(s); see PostfixExpression
while (true)
{
switch (token.id)
{
case OP_INCREMENT:
expr = new PostIncExpression(current(), expr);
break;
case OP_DECREMENT:
expr = new PostDecExpression(current(), expr);
break;
default:
return expr;
}
}
}
/**
* Check for narrowing operators following the passed expression.
* There are two narrowing operators:
*
* 1. Field ('.')
*
* FieldAccess:
* Primary . Identifier
* super . Identifier
*
* (Since parsing does not determine the meaning of (semantic
* evaluation for) a name, this method also parses qualified names
* as if they were field access expressions.)
*
* QualifiedName:
* Name . Identifier
*
* 2. Method calls ('(')
*
* MethodInvocation:
* Name ( ArgumentList-opt )
* Primary . Identifier ( ArgumentList-opt )
* super . Identifier ( ArgumentList-opt )
* ArgumentList:
* Expression
* ArgumentList , Expression
*
* 3. Array subscript ('[')
*
* DimExpr:
* [ Expression ]
*
* For example, after parsing the partial expression "x" from the
* following input:
*
* x[5][3].y.z[3].foo()[0] (etc.)
*
* ... this method completes the parsing.
*
* @param block the current block
* @param expr the expression which may be followed by a narrowing
* operator
*
* @return the parsed narrowing expression or the original expression if
* the expression is not narrowed by a field or array operator
*/
protected Expression parseNarrowingExpression(Block block, Expression expr)
throws CompilerException
{
while (true)
{
switch (token.id)
{
case SEP_DOT:
{
// parsing rules:
// 1) if the token following the dot must is neither
// an identifier nor the keyword "class", a parsing
// error occurs
// 2) otherwise, if the token is the keyword "class",
// then the expression preceding the dot is
// converted to a TypeExpression and the result is
// a ClassExpression
// 3) otherwise, if the expression preceding the dot
// is a NameExpression, then the result is a
// NameExpression
// 4) otherwise, the result is a FieldAccessExpression
Token tokDot = current();
if (token.id == KEY_CLASS)
{
expr = new ClassExpression(tokDot, toTypeExpression(expr), current());
}
else
{
Token tokName = match(IDENT);
if (expr instanceof NameExpression)
{
((NameExpression) expr).addName(tokName);
}
else
{
expr = new FieldAccessExpression(tokDot, expr, tokName);
}
}
}
break;
case SEP_LPARENTHESIS:
{
// parsing rules:
// 1) the method name is determined from the expression
// preceding the opening parenthesis; the expression
// must be either a field access expression or a
// name expression or a parsing error occurs
// 2) if the expression is a field access expression,
// then it is replaced with an invocation expression
// 3) if the expression is an unqualified name
// expression, then it is replaced with an
// invocation expression
// 4) if the expression is a qualified name expression,
// then the rightmost portion of the qualified name
// is removed from the name expression and used to
// create an invocation expression that is qualified
// by the remainder of the name expression
Expression exprQual;
Token tokName;
if (expr instanceof FieldAccessExpression)
{
exprQual = ((FieldAccessExpression) expr).getExpression();
tokName = expr.getEndToken();
}
else if (expr instanceof NameExpression)
{
NameExpression exprName = (NameExpression) expr;
if (exprName.isQualified())
{
exprQual = exprName;
}
else
{
// need an implicit (fake) "this" or this-class
Token tokFirst = exprName.getStartToken();
Token tokThis = new Token(Token.TOK_THIS,
tokFirst.getLine(), tokFirst.getOffset(), 0);
exprQual = new ThisExpression(block, tokThis);
}
tokName = exprName.removeName(); // could be last
}
else
{
expr.logError(ERROR, NOT_METHOD_NAME, null, errlist);
throw new SyntaxException();
}
expr = new InvocationExpression(exprQual, tokName, current(),
parseParameters(block), match(SEP_RPARENTHESIS));
}
break;
case SEP_LBRACKET:
{
Token tokFirst = current();
if (token.id == SEP_RBRACKET)
{
// the expression is a type because dims encountered
TypeExpression type = toTypeExpression(expr);
type = new DimensionedExpression(type, match(SEP_RBRACKET));
type = parseDimensionedExpression(expr.getBlock(), type);
expr = type;
}
else
{
expr = new ArrayAccessExpression(tokFirst, expr, parseExpression(block), match(SEP_RBRACKET));
}
}
break;
default:
return expr;
}
}
}
/**
* Parse an array initialization.
*
* VariableInitializer:
* Expression
* ArrayInitializer
* ArrayInitializer:
* { VariableInitializers-opt , -opt }
* VariableInitializers:
* VariableInitializer
* VariableInitializers , VariableInitializer
*
* @param block the current block
*
* @return the array initialization expression
*/
protected ArrayExpression parseArrayInitializer(Block block)
throws CompilerException
{
Token tokFirst = match(SEP_LCURLYBRACE);
Expression[] aexpr = NO_EXPRESSIONS;
// note: by the grammar it is possible for there to be no elements
// but just one comma
if (token.id != SEP_RCURLYBRACE && peek(SEP_COMMA) == null)
{
ArrayList list = new ArrayList();
do
{
Expression expr;
if (token.id == SEP_LCURLYBRACE)
{
expr = parseArrayInitializer(block);
}
else
{
expr = parseExpression(block);
}
list.add(expr);
}
while (peek(SEP_COMMA) != null && token.id != SEP_RCURLYBRACE);
aexpr = (Expression[]) list.toArray(new Expression[list.size()]);
}
Token tokLast = match(SEP_RCURLYBRACE);
return new ArrayExpression(block, tokFirst, tokLast, aexpr);
}
/**
* Parse parameters. (The left paren must already be eaten. This method
* does not have eat the right paren either.)
*
* ArgumentList:
* Expression
* ArgumentList , Expression
*
* @param block the current block
*
* @return an array of expressions
*/
protected Expression[] parseParameters(Block block)
throws CompilerException
{
Expression[] aexpr = NO_EXPRESSIONS;
if (token.id != SEP_RPARENTHESIS)
{
ArrayList list = new ArrayList();
do
{
list.add(parseExpression(block));
}
while (peek(SEP_COMMA) != null);
aexpr = (Expression[]) list.toArray(new Expression[list.size()]);
}
return aexpr;
}
/**
* Parses the Dims type-modifier used optionally in type casts and array
* creation expressions.
*
* Dims:
* [ ]
* Dims [ ]
*
* @param block not used, but all expression parsing functions take it
* @param expr the type expression
*
* @return the passed type expression, modified to reflect any dims
*/
protected TypeExpression parseDimensionedExpression(Block block, TypeExpression expr)
throws CompilerException
{
while (peek(SEP_LBRACKET) != null)
{
expr = new DimensionedExpression(expr, match(SEP_RBRACKET));
}
return expr;
}
/**
* Convert an expression to a type expression.
*
* @param expr the expression which is assumed to be a type expression
*
* @return the type expression
*/
protected TypeExpression toTypeExpression(Expression expr)
throws CompilerException
{
if (expr instanceof TypeExpression)
{
return (TypeExpression) expr;
}
else if (expr instanceof NameExpression)
{
return new TypeExpression((NameExpression) expr);
}
else
{
// expression is not a type
expr.logError(ERROR, NOT_TYPE_NAME, null, errlist);
throw new SyntaxException();
}
}
// ----- parsing helpers ------------------------------------------------
/**
* Returns the current token and advances to the next token.
*
* @return the current token
*
* @exception CompilerException potentially thrown by the tokenizer
*/
protected Token current()
throws CompilerException
{
Token current = token;
next();
return current;
}
/**
* Advances to and returns the next token.
*
* @return the next token
*
* @exception CompilerException potentially thrown by the tokenizer
*/
protected Token next()
throws CompilerException
{
Tokenizer toker = this.toker;
if (toker.hasMoreTokens())
{
return token = (Token) toker.nextToken();
}
// there is an imaginary closing curly after the script
if (token != null && token.id == SEP_RCURLYBRACE && token.length == 0)
{
// the imaginary right curly was already returned ... now there
// really are no more tokens
logError(ERROR, UNEXPECTED_EOF, null, token);
throw new CompilerException();
}
// this corresponds to the opening curly which is created by the
// default constructor for the Block element)
int iLine = toker.getLine();
int of = 0;
if (token != null)
{
iLine = token.getLine();
of = token.getOffset() + token.getLength();
}
return token = new Token(Token.TOK_RCURLYBRACE, iLine, of, 0);
}
/**
* Verifies that the current token matches the passed token id and, if so,
* advances to the next token. Otherwise, a syntax exception is thrown.
*
* @param id token id to match
*
* @return the current token
*
* @exception SyntaxException thrown if the token does not match
* @exception CompilerException potentially thrown by the tokenizer
*/
protected Token match(int id)
throws CompilerException
{
if (token.id != id)
{
logError(ERROR, TOKEN_EXPECTED, new String[] {Token.DESC[id]},
token.getLine(), token.getOffset(), 0);
throw new SyntaxException();
}
return current();
}
/**
* Tests if the current token matches the passed token id and, if so,
* advances to the next token.
*
* @param id token id to peek for
*
* @return the current token, if matched, or null
*
* @exception CompilerException potentially thrown by the tokenizer
*/
protected Token peek(int id)
throws CompilerException
{
return (token.id == id ? current() : null);
}
/**
* Tests if the current token matches the passed token category and
* sub-category. If so, it returns the current token and advances
* to the next token.
*
* @param cat the category to peek for
* @param subcat the sub-category to peek for
*
* @return the current token, if matched, or null
*
* @exception CompilerException potentially thrown by the tokenizer
*/
protected Token peek(int cat, int subcat)
throws CompilerException
{
Token token = this.token;
return (token.cat == cat && token.subcat == subcat ? current() : null);
}
// ----- imported types -------------------------------------------------
/**
* Scan the imported type names and determine the DataType of each one.
* Any illegal types are logged and the compiler continues, assuming that
* they only have the members present in java.lang.Object.
*
* @param ctx compiler context
*/
protected void checkImports(Context ctx)
throws CompilerException
{
final DataType DEFAULT = DataType.OBJECT;
HashMap tblNames = tblImports; // token name to full name map
HashMap tblTypes = new HashMap(); // short name to type map
for (Iterator iter = tblNames.entrySet().iterator(); iter.hasNext(); )
{
Entry entry = (Entry) iter.next();
Token tokName = (Token) entry.getKey();
String sFull = (String) entry.getValue();
String sName = tokName.getText();
TypeInfo type = ctx.getTypeInfo(sFull);
DataType dt = DEFAULT;
if (type == null)
{
// unknown or invalid type in import
logError(ERROR, IMPORT_NOT_FOUND, new String[] {sFull}, tokName);
}
else
{
dt = type.getDataType();
}
if (tblTypes.containsKey(sName))
{
// duplicate import short name
logError(ERROR, IMPORT_DUPLICATE, new String[] {sName}, tokName);
}
tblTypes.put(sName, dt);
ctx.addImport(sName, dt);
}
// replace the name-to-name lookup with a name-to-type lookup
tblImports = tblTypes;
}
// ----- error logging --------------------------------------------------
/**
* Logs the passed error in the error list.
*
* @param nSeverity severity of the error as defined by ErrorList.Constants
* @param sCode error code, as defined by the class logging the error
* @param asParams replaceable parameters for the error message
* @param token location where the error occurred
*
* @exception CompilerException If the error list overflows.
*/
protected void logError(int nSeverity, String sCode, String[] asParams, Token token)
throws CompilerException
{
logError(nSeverity, sCode, asParams,
token.getLine(), token.getOffset(), token.getLength());
}
/**
* Logs the passed error in the error list.
*
* @param nSeverity severity of the error as defined by ErrorList.Constants
* @param sCode error code, as defined by the class logging the error
* @param asParams replaceable parameters for the error message
* @param iLine line in which the error was detected
* @param ofInLine offset of the error within the line
* @param cchError length of the detected error
*
* @exception CompilerException If the error list overflows.
*/
protected void logError(int nSeverity, String sCode, String[] asParams, int iLine, int ofInLine, int cchError)
throws CompilerException
{
if (errlist != null)
{
try
{
errlist.add(new CompilerErrorInfo(nSeverity, sCode, RESOURCES, asParams,
iLine, ofInLine, cchError));
}
catch (ErrorList.OverflowException e)
{
throw new CompilerException();
}
}
}
// ----- debug output ---------------------------------------------------
/**
* Print information about the passed method.
*
* @param method the MethodInfo object to "dump"
*/
public static void printMethodInfo(MethodInfo method)
{
StringBuffer sb = new StringBuffer();
if (method.isPublic())
{
sb.append("public ");
}
if (method.isProtected())
{
sb.append("protected ");
}
if (method.isPackage())
{
sb.append("package ");
}
if (method.isPrivate())
{
sb.append("private ");
}
if (method.isAbstract())
{
sb.append("abstract ");
}
if (method.isStatic())
{
sb.append("static ");
}
if (method.isFinal())
{
sb.append("final ");
}
if (method.isAccessible())
{
sb.append("[accessible] ");
}
sb.append(method.getDataType())
.append(' ')
.append(method.getName())
.append('(');
int c = method.getParamCount();
for (int i = 0; i < c; ++i)
{
ParamInfo param = method.getParamInfo(i);
if (i > 0)
{
sb.append(", ");
}
if (param.isFinal())
{
sb.append("final ");
}
sb.append(param.getDataType())
.append(' ')
.append(param.getName());
}
sb.append(')');
out(sb.toString());
}
// ----- data members ---------------------------------------------------
/**
* The class name.
*/
private static final String CLASS = "Compiler";
/**
* Empty set of parameters.
*/
private static final Expression[] NO_EXPRESSIONS = new Expression[0];
/**
* Debug mode.
*/
private static final boolean DEBUG = ("JAVA".equals(System.getProperty("DEBUG")));
/**
* The error list to log to.
*/
protected ErrorList errlist;
/**
* The lexical tokenizer.
*/
protected Tokenizer toker;
/**
* The "current" token being evaluated.
*/
protected Token token;
/**
* Imports looked up by "short name".
*/
protected HashMap tblImports = new HashMap();
}