All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tangosol.dev.compiler.java.Compiler Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * 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();
    }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy