/*
* JBoss, Home of Professional Open Source
* Copyright 2008-10 Red Hat and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
* @authors Andrew Dinn
*/
package org.jboss.byteman.rule.expression;

import org.jboss.byteman.rule.binding.Bindings;
import org.jboss.byteman.rule.grammar.ParseNode;
import static org.jboss.byteman.rule.grammar.ParseNode.*;
import org.jboss.byteman.rule.type.Type;
import org.jboss.byteman.rule.exception.TypeException;
import org.jboss.byteman.rule.Rule;

import java.util.List;
import java.util.ArrayList;

/**
 * helper class to transform parsed expression AST into an actual Expression instance
 */
public class ExpressionHelper
{
    public static Expression createExpression(Rule rule, Bindings bindings, ParseNode exprTree)
            throws TypeException
    {
        return createExpression(rule, bindings, exprTree, Type.UNDEFINED);
    }

    public static Expression createExpression(Rule rule, Bindings bindings, ParseNode exprTree, Type type)
            throws TypeException
    {
        // we expect expr =  (RETURN)
        //                   (RETURN expr)
        //                   (THROW)
        //                   (THROW expr)
        //                   (UNARYOP unary_oper expr) |
        //                   (BINOP infix_oper expr exp) |
        //                   (TERNOP simple_expr expr expr)
        //                   (ARRAY typename)
        //                   (ARRAY expr expr_list)
        //                   (FIELD expr simple_name)
        //                   (CLASS path simple_name)
        //                   (METH simple_name expr expr_list)
        //                   (NEW name expr_list)
        //                   (BOOLEAN_LITERAL)
        //                   (INTEGER_LITERAL)
        //                   (FLOAT_LITERAL)
        //                   (STRING_LITERAL)
        //                   (NULL_LITERAL)
        //                   (IDENTIFIER simple_name)
        //                   (IDENTIFIER simple_name path)
        //                   (DOLLAR string)
        //                   (ASSIGN simple_name expr)
        //                   (ASSIGN dollar_expr expr)
        //                   (ARRAY_INIT expr_list)
        //                   (ARRAY_INIT)

        int tag = exprTree.getTag();
        Expression expr;
        switch (tag) {
            case IDENTIFIER:
            {
                // check for path qualifier

                String name = (String)exprTree.getChild(0);
                ParseNode child1 = (ParseNode)exprTree.getChild(1);

                if (child1 == null && bindings.lookup(name) != null) {
                    // a clear cut direct variable reference
                    expr = new Variable(rule, type, exprTree);
                } else {
                    // we should only get these as identifiers for binding types or throw types which are
                    // explicitly caught by the bindings or throw processing case handlers so this is an error

                    throw new TypeException("ExpressionHelper.createExpression : unexpected IDENTIFIER " + exprTree.getText() + " in "  + exprTree.getPos());
                }
            }
            break;
            case ARRAY:
            {
                ParseNode child0 = (ParseNode) exprTree.getChild(0);
                ParseNode child1 = (ParseNode) exprTree.getChild(1);

                Expression arrayRef = createExpression(rule, bindings, child0, Type.UNDEFINED);

                List<Expression> indices = createExpressionList(rule, bindings, child1, Type.I);

                if (indices != null) {
                    expr = new ArrayExpression(rule, type, exprTree, arrayRef, indices);
                } else {
                    throw new TypeException("ExpressionHelper.createExpression : invalid array index expression " + exprTree.getPos());
                }
            }
            break;
            case FIELD:
            {
                ParseNode child0 = (ParseNode) exprTree.getChild(0);
                ParseNode child1 = (ParseNode) exprTree.getChild(1);
                expr = createFieldExpression(rule, bindings, child0, child1, type);
            }
            break;
            case CLASS:
            {
                ParseNode child0 = (ParseNode) exprTree.getChild(0);
                expr = createClassLiteralExpression(rule, bindings, child0, type);
            }
            break;
            case METH:
            {
                ParseNode child0 = (ParseNode) exprTree.getChild(0);
                ParseNode child1 = (ParseNode) exprTree.getChild(1);
                ParseNode child2 = (ParseNode) exprTree.getChild(2);
                int tag0 = child0.getTag();
                if (tag0 != IDENTIFIER) {
                    // uurgh we expected a method name
                    throw new TypeException("ExpressionHelper.createExpression : invalid method selector " + tag + " in expression " + child0.getText() + child0.getPos());
                }

                expr = createCallExpression(rule, bindings, child0, child1, child2, type);
            }
            break;
            case THROW:
            {
                ParseNode child0 = (ParseNode) exprTree.getChild(0);
                ParseNode child1 = (ParseNode) exprTree.getChild(1);
                int tag0 = child0.getTag();

                if (tag0 != IDENTIFIER) {
                    throw new TypeException("ExpressionHelper.createExpression : invalid throw type " + tag + " in expression " + child0.getText() + child0.getPos());
                } else {
                    expr = createThrowExpression(rule, bindings, child0, child1);
                }
            }
            break;
            case NEW:
            {
                ParseNode child0 = (ParseNode) exprTree.getChild(0);
                ParseNode child1 = (ParseNode) exprTree.getChild(1);
                ParseNode child2 = (ParseNode) exprTree.getChild(2);
                int tag0 = child0.getTag();

                if (tag0 != IDENTIFIER) {
                    throw new TypeException("ExpressionHelper.createExpression : invalid new type " + tag + " in expression " + child0.getText() + child0.getPos());
                } else {
                    expr = createNewExpression(rule, bindings, child0, child1, child2);
                }
            }
            break;
            case INTEGER_LITERAL:
            case FLOAT_LITERAL:
            {
                // value will be an Integer, Long, Float or Double
                Object value = exprTree.getChild(0);
                // we want to use it as an int, long, float or double
                Type t = Type.boxType(value.getClass());
                expr = new NumericLiteral(rule, t, exprTree);
            }
            break;
            case STRING_LITERAL:
            {
                expr = new StringLiteral(rule, exprTree);
            }
            break;
            case BOOLEAN_LITERAL:
            {
                expr = new BooleanLiteral(rule, exprTree);
            }
            break;
            case NULL_LITERAL:
            {
                expr = new NullLiteral(rule, exprTree);
            }
            break;
            case RETURN:
            {
                Expression returnValue;
                ParseNode child0 = (ParseNode) exprTree.getChild(0);
                if (child0 != null) {
                    returnValue = createExpression(rule, bindings, child0);
                } else {
                    returnValue = null;
                }
                expr = new ReturnExpression(rule, exprTree, returnValue);
            }
            break;
            case DOLLAR:
            {
                String text = (String)exprTree.getChild(0);
                char leading = text.charAt(1);
                if (Character.isDigit(leading)) {
                    int index = Integer.valueOf(text.substring(1));
                    expr = new DollarExpression(rule, type, exprTree, index);
                } else if (text.equals("$!")) {
                    expr = new DollarExpression(rule, type, exprTree, DollarExpression.RETURN_VALUE_IDX);
                } else if (text.equals("$^")) {
                    expr = new DollarExpression(rule, type, exprTree, DollarExpression.THROWABLE_VALUE_IDX);
                } else if (text.equals("$#")) {
                    expr = new DollarExpression(rule, Type.I, exprTree, DollarExpression.PARAM_COUNT_IDX);
                } else if (text.equals("$*")) {
                    expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.PARAM_ARRAY_IDX);
                } else if (text.equals("$@")) {
                    expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.INVOKE_PARAM_ARRAY_IDX);
                } else if (text.equals("$CLASS")) {
                    expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.TRIGGER_CLASS_IDX);
                } else if (text.equals("$METHOD")) {
                    expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.TRIGGER_METHOD_IDX);
                } else if (text.equals("$NEWCLASS")) {
                    expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.NEW_CLASS_IDX);
                } else {
                    expr = new DollarExpression(rule, type, exprTree, text.substring(1));
                }
            }
            break;
            case ASSIGN:
            {
                ParseNode child0 = (ParseNode) exprTree.getChild(0);
                ParseNode child1 = (ParseNode) exprTree.getChild(1);
                // the left hand side is special but nay expression will do for the right hand side

                AssignableExpression left = createAssignableExpression(rule, bindings, child0, type);;
                Expression right = createExpression(rule, bindings, child1, type);

                expr = new AssignExpression(rule, exprTree, left, right);
            }
            break;
            case UNOP:
            {
                expr = createUnaryExpression(rule, bindings, exprTree, type);
            }
            break;
            case BINOP:
            {
                expr = createBinaryExpression(rule, bindings, exprTree, type);
            }
            break;
            case TERNOP:
            {
                expr = createTernaryExpression(rule, bindings, exprTree, type);
            }
            break;
            case ARRAY_INIT:
            {
                expr = createArrayInitExpression(rule, bindings, exprTree, type);
            }
            break;
            default:
            {
                throw new TypeException("ExpressionHelper.createExpression : unexpected token type " + tag + " for expression " + exprTree.getPos());
            }
        }

        Type exprType = Type.dereference(expr.getType());
        Type targetType = Type.dereference(type);
        if (exprType.isDefined() && targetType.isDefined() && !targetType.isAssignableFrom(exprType)) {
            // we already know this is an invalid type so notify an error and return null
            throw new TypeException("ExpressionHelper.createExpression : invalid expression type " + exprType.getName() + " expecting " + targetType.getName() + exprTree.getPos());
        } else if (targetType.isNumeric() && !exprType.isNumeric()) {
            // we already know this is an invalid type so notify an error and return null
            throw new TypeException("ExpressionHelper.createExpression : invalid expression type " + exprType.getName() + " expecting " + targetType.getName() + exprTree.getPos());
        }
        // don't do this here as it gets called recursively -- need to do it in Binding, Condition and Action
        /*
        if (!expr.bind()) {
            throw new TypeException("ExpressionHelper.createExpression : unknown reference in expression" + exprTree.getPos());
        }
        */

        return expr;
    }

    public static AssignableExpression createFieldExpression(Rule rule, Bindings bindings, ParseNode fieldTree, ParseNode targetTree, Type type)
            throws TypeException
    {
        AssignableExpression fieldExpr;
        Expression target;
        String[] pathList;

        // the recipient tree may be a path or some other form of expression
        // in the former case we don't construct the recipient until type check time

        if (targetTree.getTag() == PATH) {
            target = null;
            pathList = createPathList(targetTree);
        } else {
            target = createExpression(rule, bindings, targetTree, Type.UNDEFINED);
            pathList = null;
        }

        fieldExpr = new FieldExpression(rule, type, fieldTree, fieldTree.getText(), target, pathList);

        return fieldExpr;
    }

    public static ClassLiteralExpression createClassLiteralExpression(Rule rule, Bindings bindings, ParseNode pathTree, Type type)
            throws TypeException
    {
        ClassLiteralExpression classLiteralExpr;
        String[] pathList;

        // this is a class literal -- use the path as the tree for the expression
        // the path tree must have tag PATH

        pathList = createPathList(pathTree);

        classLiteralExpr = new ClassLiteralExpression(rule, type, pathTree, pathList);

        return classLiteralExpr;
    }

    public static Expression createCallExpression(Rule rule, Bindings bindings, ParseNode selectorTree, ParseNode recipientTree, ParseNode argTree, Type type)
            throws TypeException
    {
        Expression expr;
        Expression recipient;
        String[] pathList;
        List<Expression> args;

        // the recipient tree may be a path or some other form of expression
        // in the former case we don't construct the recipient until type check time

        if (recipientTree == null) {
            recipient = null;
            pathList = null;
        } else if (recipientTree.getTag() == PATH) {
            recipient = null;
            pathList = createPathList(recipientTree);
        } else {
            recipient = createExpression(rule, bindings, recipientTree, Type.UNDEFINED);
            pathList = null;
        }
        if (argTree == null) {
            args = new ArrayList<Expression>();
        } else {
            args = createExpressionList(rule, bindings, argTree);
        }

        expr = new MethodExpression(rule, type, selectorTree, recipient, args, pathList);

        return expr;
    }

    public static String[] createPathList(ParseNode pathTree)
    {
        int l = 0;
        ParseNode p = pathTree;

        while (p != null) {
            l++;
            p = (ParseNode)p.getChild(1);
        }

        String[] pathList = new String[l];

        p = pathTree;
        while (p != null) {
            l--;
            pathList[l] = (String)p.getText();
            p = (ParseNode)p.getChild(1);
        }

        return pathList;
    }

    public static Expression createThrowExpression(Rule rule, Bindings bindings, ParseNode typeNameTree, ParseNode argTree)
            throws TypeException
    {
        Expression expr;
        List<Expression> args;

        if (argTree == null) {
            args = new ArrayList<Expression>();
        } else {
            args = createExpressionList(rule, bindings, argTree);
        }

        expr = new ThrowExpression(rule, typeNameTree, args);

        return expr;
    }

    public static Expression createNewExpression(Rule rule, Bindings bindings, ParseNode typeNameTree,
                                                 ParseNode argTree, ParseNode arrayDimsTree)
            throws TypeException
    {
        Expression expr;
        List<Expression> args;
        List<Expression> arrayDims;
        ArrayInitExpression arrayInits;
        if (argTree == null) {
            args = new ArrayList<Expression>();
        } else {
            args = createExpressionList(rule, bindings, argTree);
        }
        if (arrayDimsTree == null) {
            arrayDims = new ArrayList<Expression>();
            arrayInits = null;
        } else {
            // we expect
            // (ARRAY_INIT array_dims array_init)
            // array_dims
            if (arrayDimsTree.getTag() == ParseNode.ARRAY_INIT) {
                ParseNode child0 = (ParseNode)arrayDimsTree.getChild(0);
                ParseNode child1 = (ParseNode)arrayDimsTree.getChild(1);

                arrayDims = createNewArrayDimsList(rule, bindings, child0);
                arrayInits = createArrayInitExpression(rule, bindings, child1, Type.UNDEFINED);
            } else {
                arrayDims = createNewArrayDimsList(rule, bindings, arrayDimsTree);
                arrayInits = null;
            }
        }

        expr = new NewExpression(rule, typeNameTree, args, arrayDims, arrayInits);

        return expr;
    }

    public static Expression createUnaryExpression(Rule rule, Bindings bindings, ParseNode exprTree, Type type)
            throws TypeException
    {
        // we expect ^(UNOP unary_oper expr)

        ParseNode child0 = (ParseNode) exprTree.getChild(0);
        ParseNode child1 = (ParseNode) exprTree.getChild(1);
        Expression expr;
        int tag = child0.getTag();

        switch (tag)
        {
            case TWIDDLE:
            {
                // the argument must be a numeric expression
                if (!type.isUndefined() && !type.isVoid() && !type.isNumeric()) {
                    throw new TypeException("ExpressionHelper.createUnaryExpression : invalid numeric expression" + exprTree.getPos());
                }
                Expression operand = createExpression(rule, bindings, child1, Type.NUMBER);
                expr = new TwiddleExpression(rule, exprTree, operand);
            }
            break;
            case NOT:
            {
                // the argument must be a boolean expression
                if (!type.isUndefined() && !type.isVoid() && !type.isBoolean()) {
                    throw new TypeException("ExpressionHelper.createUnaryExpression : invalid boolean expression" + exprTree.getPos());
                }
                Expression operand = createExpression(rule, bindings, child1, Type.BOOLEAN);
                expr = new NotExpression(rule, exprTree, operand);
            }
            break;
            case UMINUS:
            {
                // the argument must be a numeric expression
                if (!type.isUndefined() && !type.isVoid() && !type.isBoolean()) {
                    throw new TypeException("ExpressionHelper.createUnaryExpression : invalid boolean expression" + exprTree.getPos());
                }
                Expression operand = createExpression(rule, bindings, child1, Type.NUMBER);
                expr = new MinusExpression(rule, exprTree, operand);
            }
            break;
            case DOLLAR:
            {
                if (child1.getTag() == IDENTIFIER) {
                    if (child1.getText().equals("!")) {
                        expr = new DollarExpression(rule, type, exprTree, DollarExpression.RETURN_VALUE_IDX);
                    } else if (child1.getText().equals("^")) {
                        expr = new DollarExpression(rule, type, exprTree, DollarExpression.THROWABLE_VALUE_IDX);
                    } else if (child1.getText().equals("#")) {
                        expr = new DollarExpression(rule, Type.I, exprTree, DollarExpression.PARAM_COUNT_IDX);
                    } else if (child1.getText().equals("*")) {
                        expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.PARAM_ARRAY_IDX);
                    } else if (child1.getText().equals("@")) {
                        expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.INVOKE_PARAM_ARRAY_IDX);
                    } else if (child1.getText().equals("CLASS")) {
                        expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.TRIGGER_CLASS_IDX);
                    } else if (child1.getText().equals("METHOD")) {
                        expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.TRIGGER_METHOD_IDX);
                    } else {
                        expr = new DollarExpression(rule, type, exprTree, child1.getText());
                    }
                } else if (child1.getTag() == INTEGER_LITERAL) {
                    Integer intObject = (Integer) child1.getChild(0);
                    expr = new DollarExpression(rule, type, exprTree, intObject.intValue());
                } else {
                    throw new TypeException("ExpressionHelper.createUnaryExpression : unexpected token type " + child1.getTag() + " for dollar expression tree " + child1.getText() + "" + child1.getPos());
                }
            }
            break;
            default:
            {
                throw new TypeException("ExpressionHelper.createUnaryExpression : unexpected token type " + exprTree.getTag() + " for expression tree " + exprTree.getText() + "" + exprTree.getPos());
            }
        }

        return expr;
    }

    public static Expression createBinaryExpression(Rule rule, Bindings bindings, ParseNode exprTree, Type type)
            throws TypeException
    {
        // we expect ^(BINOP infix_oper simple_expr expr)

        ParseNode child0 = (ParseNode) exprTree.getChild(0);
        ParseNode child1 = (ParseNode) exprTree.getChild(1);
        ParseNode child2 = (ParseNode) exprTree.getChild(2);
        Expression expr;
        int oper = child0.getTag();

        switch (oper)
        {
            case PLUS:
            {
                // this is a special case since we may be doing String concatenation
                Expression operand1;
                Expression operand2;
                /* if (type == Type.STRING) {
                    // must be doing String concatenation
                    // TODO hmm, not sure this is right e.g. ("value " + a + b) might give "value 12" instead of "value 3"
                    operand1 = createExpression(rule, bindings, child1, Type.STRING);
                    operand2 = createExpression(rule, bindings, child2, Type.UNDEFINED);
                    expr = new StringPlusExpression(rule, exprTree, operand1,  operand2);
                } else */ if (type.isNumeric()) {
                    // must be doing arithmetic
                    operand1 = createExpression(rule, bindings, child1, Type.NUMBER);
                    operand2 = createExpression(rule, bindings, child2, Type.NUMBER);
                    int convertedOper = OperExpression.convertOper(oper);
                    expr = new ArithmeticExpression(rule, convertedOper, exprTree, operand1,  operand2);
                } else {
                    // see if the operand gives us any type info
                    operand1 = createExpression(rule, bindings, child1, Type.UNDEFINED);
                    if (operand1.getType().isNumeric()) {
                        operand2 = createExpression(rule, bindings, child2, Type.NUMBER);
                        int convertedOper = OperExpression.convertOper(oper);
                        expr = new ArithmeticExpression(rule, convertedOper, exprTree, operand1, operand2);
                    } else if (operand1.getType() == Type.STRING) {
                        operand2 = createExpression(rule, bindings, child2, Type.UNDEFINED);
                        expr = new StringPlusExpression(rule, exprTree, operand1,  operand2);
                    } else {
                        operand2 = createExpression(rule, bindings, child2, Type.UNDEFINED);
                        // create as generic plus expression which we will replace later during type
                        // checking
                        expr = new PlusExpression(rule, exprTree, operand1,  operand2);
                    }
                }
            }
            break;
            case MINUS:
            case MUL:
            case DIV:
            case MOD:
            {
                Expression operand1 = createExpression(rule, bindings, child1, Type.NUMBER);
                Expression operand2 = createExpression(rule, bindings, child2, Type.NUMBER);

                int convertedOper = OperExpression.convertOper(oper);
                expr = new ArithmeticExpression(rule, convertedOper, exprTree, operand1, operand2);
            }
            break;
            case URSH:
            case RSH:
            case LSH:
            {
                Expression operand1 = createExpression(rule, bindings, child1, Type.NUMBER);
                Expression operand2 = createExpression(rule, bindings, child2, Type.NUMBER);

                int convertedOper = OperExpression.convertOper(oper);
                expr = new ShiftExpression(rule, convertedOper, exprTree, operand1, operand2);
            }
            break;
            case BAND:
            case BOR:
            case BXOR:
            {
                Expression operand1 = createExpression(rule, bindings, child1, Type.NUMBER);
                Expression operand2 = createExpression(rule, bindings, child2, Type.NUMBER);

                int convertedOper = OperExpression.convertOper(oper);
                expr = new BitExpression(rule, convertedOper, exprTree, operand1, operand2);
            }
            break;
            case AND:
            case OR:
            {
                Expression operand1 = createExpression(rule, bindings, child1, Type.BOOLEAN);
                Expression operand2 = createExpression(rule, bindings, child2, Type.BOOLEAN);

                int convertedOper = OperExpression.convertOper(oper);
                expr = new LogicalExpression(rule, convertedOper, exprTree, operand1, operand2);
            }
            break;
            case EQ:
            case NE:
            case GT:
            case LT:
            case GE:
            case LE:
            {
                Expression operand1 = createExpression(rule, bindings, child1, Type.UNDEFINED);
                Expression operand2 = createExpression(rule, bindings, child2, Type.UNDEFINED);

                int convertedOper = OperExpression.convertOper(oper);
                expr = new ComparisonExpression(rule, convertedOper, exprTree, operand1, operand2);
            }
            break;
            case INSTANCEOF:
            {
                Expression operand1 = createExpression(rule, bindings, child1, Type.UNDEFINED);
                ClassLiteralExpression operand2 = createClassLiteralExpression(rule, bindings, child2, Type.UNDEFINED);

                int convertedOper = OperExpression.convertOper(oper);
                expr = new InstanceOfExpression(rule, convertedOper, exprTree, operand1, operand2);
            }
            break;
            default:
            {
                throw new TypeException("ExpressionHelper.createBinaryExpression : unexpected token type " + exprTree.getTag() + " for expression " + exprTree.getText() + "" + exprTree.getPos());
            }
        }

        return expr;
    }

    public static Expression createTernaryExpression(Rule rule, Bindings bindings, ParseNode exprTree, Type type)
            throws TypeException
    {
        // we only expect ^(TERNOP ternary_oper simple_expr expr expr)

        ParseNode child0 = (ParseNode) exprTree.getChild(0);
        ParseNode child1 = (ParseNode) exprTree.getChild(1);
        ParseNode child2 = (ParseNode) exprTree.getChild(2);
        Expression expr;
        int oper = exprTree.getTag();

        switch (oper)
        {
            case TERNOP:
            {
                // the first argument must be a boolean expression
                Expression operand0 = createExpression(rule, bindings, child0, Type.BOOLEAN);
                Expression operand1 = createExpression(rule, bindings, child1, type);
                Expression operand2 = createExpression(rule, bindings, child2, type);
                Type type1 = Type.dereference(operand1.getType());
                Type type2 = Type.dereference(operand2.getType());
                if (type1.isNumeric() || type2.isNumeric()) {
                    if (!type.isUndefined() && !type.isVoid() && !type.isNumeric()) {
                        throw new TypeException("ExpressionHelper.createUnaryExpression : invalid numeric expression" + exprTree.getPos());
                    }
                    expr = new ConditionalEvalExpression(rule, Type.promote(type1, type2),  exprTree, operand0,  operand1, operand2);
                } else if (type1.isDefined() && type2.isDefined()) {
                    // since they are not numeric we have to have the same type
                    if (type1 == type2) {
                        // use this type
                        expr = new ConditionalEvalExpression(rule, type1,  exprTree, operand0,  operand1, operand2);
                    } else {
                        // mismatched types so don't generate a result
                        throw new TypeException("ExpressionHelper.createTernaryExpression : mismatched expression types " + type1.getName() + " and " + type2.getName()  + " in conditional expression " + exprTree.getText() + exprTree.getPos());
                    }
                } else {
                    // have to wait for type check to resolve types
                    expr = new ConditionalEvalExpression(rule, Type.UNDEFINED,  exprTree, operand0,  operand1, operand2);
                }
            }
            break;
            default:
            {
                throw new TypeException("ExpressionHelper.createTernaryExpression : unexpected token type " + exprTree.getTag() + " for expression " + exprTree.getText() + "" + exprTree.getPos());
            }
        }

        return expr;
    }

    public static ArrayInitExpression createArrayInitExpression(Rule rule, Bindings bindings, ParseNode exprTree, Type type)
            throws TypeException
    {
        // we expect (ARRAY_INIT expr_list)
        //           (ARRAY_INIT)

        List<Expression> expr_list;

        if (exprTree.getChildCount() == 1) {
            Type baseType;
            if (type.isArray()) {
                baseType = type.getBaseType();
            } else {
                baseType = Type.UNDEFINED;
            }
            ParseNode child0 = (ParseNode) exprTree.getChild(0);
            expr_list = createExpressionList(rule, bindings, child0, baseType);
        }  else {
            expr_list = new ArrayList<Expression>();
        }

        ArrayInitExpression expr = new ArrayInitExpression(rule, type, exprTree, expr_list);

        return expr;
    }

    public static AssignableExpression createAssignableExpression(Rule rule, Bindings bindings, ParseNode exprTree, Type type)
            throws TypeException
    {
        AssignableExpression expr;
        // we expect expr =  (FIELD expr simple_name)
        //                   (IDENTIFIER simple_name)
        //                   (DOLLAR string)
        // we allow assignment to identifiers or dollar symbols
        int tag = exprTree.getTag();
        switch(tag) {
            case FIELD:
            {
                ParseNode child0 = (ParseNode) exprTree.getChild(0);
                ParseNode child1 = (ParseNode) exprTree.getChild(1);
                expr = createFieldExpression(rule, bindings, child0, child1, type);
            }
            break;
            case IDENTIFIER:
            {
                String name = (String)exprTree.getChild(0);
                ParseNode child1 = (ParseNode)exprTree.getChild(1);

                if (child1 == null && bindings.lookup(name) != null) {
                    // a clear cut direct variable reference
                    expr = new Variable(rule, type, exprTree, name);
                } else {
                    // we should only get these as identifiers for binding types or throw types which are
                    // explicitly caught by the bindings or throw processing case handlers so this is an error

                    throw new TypeException("ExpressionHelper.createAssignableExpression : unexpected IDENTIFIER " + exprTree.getText() + " in "  + exprTree.getPos());
                }
            }
            break;
            case DOLLAR:
            {
                String text = (String)exprTree.getChild(0);
                char leading = text.charAt(1);
                if (Character.isDigit(leading)) {
                    int index = Integer.valueOf(text.substring(1));
                    expr = new DollarExpression(rule, type, exprTree, index);
                } else if (text.equals("$!")) {
                    expr = new DollarExpression(rule, type, exprTree, DollarExpression.RETURN_VALUE_IDX);
                } else if (text.equals("$^")) {
                    expr = new DollarExpression(rule, type, exprTree, DollarExpression.THROWABLE_VALUE_IDX);
                } else if (text.equals("$#")) {
                    expr = new DollarExpression(rule, Type.I, exprTree, DollarExpression.PARAM_COUNT_IDX);
                } else if (text.equals("$*")) {
                    expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.PARAM_ARRAY_IDX);
                } else if (text.equals("$@")) {
                    expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.INVOKE_PARAM_ARRAY_IDX);
                } else if (text.equals("$CLASS")) {
                    expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.TRIGGER_CLASS_IDX);
                } else if (text.equals("$METHOD")) {
                    expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.TRIGGER_METHOD_IDX);
                } else if (text.equals("$NEWCLASS")) {
                    expr = new DollarExpression(rule, rule.getTypeGroup().createArray(Type.OBJECT), exprTree, DollarExpression.NEW_CLASS_IDX);
                } else {
                    expr = new DollarExpression(rule, type, exprTree, text.substring(1));
                }
            }
            break;
            case ARRAY:
            {
                ParseNode child0 = (ParseNode) exprTree.getChild(0);
                ParseNode child1 = (ParseNode) exprTree.getChild(1);

                Expression arrayRef = createExpression(rule, bindings, child0, Type.UNDEFINED);

                List<Expression> indices = createExpressionList(rule, bindings, child1, Type.I);

                if (indices != null) {
                    expr = new ArrayExpression(rule, type, exprTree, arrayRef, indices);
                } else {
                    throw new TypeException("ExpressionHelper.createExpression : invalid array index expression " + exprTree.getPos());
                }
            }
            break;
            default:
                throw new TypeException("ExpressionHelper.createAssignableExpression : unexpected token type " + tag + " for lhs of assignment " + exprTree.getPos());
        }

        return expr;
    }

    public static List<Expression> createExpressionList(Rule rule, Bindings bindings, ParseNode exprTree)
            throws TypeException
    {
        return createExpressionList(rule, bindings, exprTree, Type.UNDEFINED);

    }

    public static List<Expression> createExpressionList(Rule rule, Bindings bindings, ParseNode exprTree, Type type)
            throws TypeException
    {
        // we expect expr_list = ^(EXPR) |
        //                       ^(SEMI expr expr_list)
        //                       ^(COMMA expr expr_list)

        List<Expression> exprList = new ArrayList<Expression>();
        List<TypeException> exceptions = new ArrayList<TypeException>();

        while (exprTree != null)
        {
            try {
                switch (exprTree.getTag())
                {
                    case SEMI:
                    case COMMA:
                    {
                        ParseNode child0 = (ParseNode) exprTree.getChild(0);
                        // assign tree before we risk an exception
                        exprTree = (ParseNode) exprTree.getChild(1);
                        Expression expr = createExpression(rule, bindings, child0, type);
                        exprList.add(expr);
                    }
                    break;
                    default:
                    {
                        // assign tree before we risk an exception
                        ParseNode saveTree = exprTree;
                        exprTree = null;
                        Expression expr = createExpression(rule, bindings, saveTree, type);
                        exprList.add(expr);
                    }
                    break;
                }
            } catch (TypeException te) {
                exceptions.add(te);
            }
        }

        if (!exceptions.isEmpty()) {
            if (exceptions.size() == 1) {
                throw exceptions.get(0);
            } else {
                StringBuffer buffer = new StringBuffer();
                buffer.append("ExpressionHelper.createExpressionList : errors checking expression sequence");
                for (TypeException typeException : exceptions) {
                    buffer.append("\n");
                    buffer.append(typeException.toString());
                }
                throw new TypeException(buffer.toString());
            }
        }

        return exprList;
    }
    public static List<Expression> createNewArrayDimsList(Rule rule, Bindings bindings, ParseNode exprTree)
            throws TypeException
    {
        // we expect array_dims = array_dim
        //                        ^(COMMA array_dim array_dims)
        // where     array_dim = ^(EXPR) |
        //                       ^(NOTHING)
        // and we also have the constraints that any EXPR is expected to have an int type and that
        // once we see the first NOTHING we only expect NOTHING from then onwards

        Type type =  Type.I;

        List<Expression> exprList = new ArrayList<Expression>();
        List<TypeException> exceptions = new ArrayList<TypeException>();
        boolean foundEmptyDim = false;
        int arrayDimCount = 0;
        int emptyDimCount = 0;

        while (exprTree != null)
        {
            try {
                ParseNode saveTree = exprTree;
                ParseNode child;
                if (exprTree.getTag() == COMMA) {
                    child = (ParseNode) exprTree.getChild(0);
                    exprTree = (ParseNode) exprTree.getChild(1);
                } else {
                    child = exprTree;
                    exprTree = null;
                }
                // count an extra array dimension
                arrayDimCount++;

                if (child.getTag() == NOTHING) {
                    //  this is an empty array dimension
                    emptyDimCount++;
                    foundEmptyDim = true;
                    // append null to the list to represent the empty dimension
                    exprList.add(null);
                } else  if (!foundEmptyDim){
                    Expression expr = createExpression(rule, bindings, child, Type.I);
                    exprList.add(expr);
                } else {
                    // once we see an empty dim we expect to see al empty dims
                    throw new TypeException("ExpressionHelper.createNewArrayDimsList : invalid array dimension " + child.getPos());
                }
            } catch (TypeException te) {
                exceptions.add(te);
            }
        }

        if (!exceptions.isEmpty()) {
            if (exceptions.size() == 1) {
                throw exceptions.get(0);
            } else {
                StringBuffer buffer = new StringBuffer();
                buffer.append("ExpressionHelper.createNewArrayDimsList : errors checking new expression array dimensions");
                for (TypeException typeException : exceptions) {
                    buffer.append("\n");
                    buffer.append(typeException.toString());
                }
                throw new TypeException(buffer.toString());
            }
        }

        return exprList;
    }

}
