public interface SourceIndexed {
    int getStart();

    int getEnd();
} public class Identifier extends Token {
    private Identifier(String source, int index) {
        super(TokenID.IDENTIFIER, source, index);
    }

    public static Identifier from(String source, int index) {
        return new Identifier(source, index);
    }
} public class Keyword extends Token {
    private static final Map<String, Keyword> KEYWORDS = new HashMap<>();

    static {
        // control flow
        add(TokenID.KEYWORD_ASSERT, "assert");
        add(TokenID.KEYWORD_IF, "if");
        add(TokenID.KEYWORD_ELSE, "else");
        add(TokenID.KEYWORD_WHILE, "while");
        add(TokenID.KEYWORD_DO, "do");
        add(TokenID.KEYWORD_FOR, "for");
        add(TokenID.KEYWORD_BREAK, "break");
        add(TokenID.KEYWORD_CONTINUE, "continue");
        add(TokenID.KEYWORD_SWITCH, "switch");
        add(TokenID.KEYWORD_CASE, "case");
        add(TokenID.KEYWORD_DEFAULT, "default");
        add(TokenID.KEYWORD_RETURN, "return");
        add(TokenID.KEYWORD_THROW, "throw");
        add(TokenID.KEYWORD_TRY, "try");
        add(TokenID.KEYWORD_CATCH, "catch");
        add(TokenID.KEYWORD_FINALLY, "finally");
        // void type
        add(TokenID.KEYWORD_VOID, "void");
        // primitive type
        add(TokenID.KEYWORD_BOOLEAN, "boolean");
        add(TokenID.KEYWORD_BYTE, "byte");
        add(TokenID.KEYWORD_SHORT, "short");
        add(TokenID.KEYWORD_CHAR, "char");
        add(TokenID.KEYWORD_INT, "int");
        add(TokenID.KEYWORD_LONG, "long");
        add(TokenID.KEYWORD_FLOAT, "float");
        add(TokenID.KEYWORD_DOUBLE, "double");
        // class type
        add(TokenID.KEYWORD_CLASS, "class");
        add(TokenID.KEYWORD_INTERFACE, "interface");
        add(TokenID.KEYWORD_ENUM, "enum");
        // inheritence
        add(TokenID.KEYWORD_EXTENDS, "extends");
        add(TokenID.KEYWORD_IMPLEMENTS, "implements");
        // identifier
        add(TokenID.KEYWORD_SUPER, "super");
        add(TokenID.KEYWORD_THIS, "this");
        // package
        add(TokenID.KEYWORD_PACKAGE, "package");
        // import
        add(TokenID.KEYWORD_IMPORT, "import");
        // access modifier
        add(TokenID.KEYWORD_PUBLIC, "public");
        add(TokenID.KEYWORD_PROTECTED, "protected");
        add(TokenID.KEYWORD_PRIVATE, "private");
        // exception modifier
        add(TokenID.KEYWORD_THROWS, "throws");
        // other modifier
        add(TokenID.KEYWORD_ABSTRACT, "abstract");
        add(TokenID.KEYWORD_STRICTFP, "strictfp");
        add(TokenID.KEYWORD_TRANSIENT, "transient");
        add(TokenID.KEYWORD_VOLATILE, "volatile");
        add(TokenID.KEYWORD_FINAL, "final");
        add(TokenID.KEYWORD_STATIC, "static");
        add(TokenID.KEYWORD_SYNCHRONIZED, "synchronized");
        add(TokenID.KEYWORD_NATIVE, "native");
        // operator
        add(TokenID.KEYWORD_NEW, "new");
        add(TokenID.KEYWORD_INSTANCEOF, "instanceof");
        // unused
        add(TokenID.KEYWORD_GOTO, "goto");
        add(TokenID.KEYWORD_CONST, "const");
    }

    private Keyword(Keyword token, int index) {
        this(token.getID(), token.getSource(), index);
    }

    private Keyword(TokenID id, String source, int index) {
        super(id, source, index);
    }

    public static boolean is(String source) {
        return KEYWORDS.containsKey(source);
    }

    public static Keyword from(String source, int index) {
        final Keyword keyword = KEYWORDS.get(source);
        return keyword == null ? null : new Keyword(keyword, index);
    }

    public static Collection<Keyword> all() {
        return Collections.unmodifiableCollection(KEYWORDS.values());
    }

    private static void add(TokenID id, String source) {
        KEYWORDS.put(source, new Keyword(id, source, 0));
    }
} public class Symbol extends Token {
    private static final Map<String, Symbol> SYMBOLS = new HashMap<>();
    private static final Symbol[] CHAR_SYMBOLS = new Symbol[256];
    private final Symbol compoundAssignOperator;

    static {
        // punctuation
        add(TokenID.SYMBOL_PERIOD, ".");
        add(TokenID.SYMBOL_COMMA, ",");
        add(TokenID.SYMBOL_COLON, ":");
        add(TokenID.SYMBOL_SEMICOLON, ";");
        // math
        add(TokenID.SYMBOL_PLUS, "+");
        add(TokenID.SYMBOL_MINUS, "-");
        add(TokenID.SYMBOL_MULTIPLY, "*");
        add(TokenID.SYMBOL_DIVIDE, "/");
        add(TokenID.SYMBOL_MODULO, "%");
        add(TokenID.SYMBOL_INCREMENT, "++", "+");
        add(TokenID.SYMBOL_DECREMENT, "--", "-");
        // bitwise
        add(TokenID.SYMBOL_BITWISE_AND, "&");
        add(TokenID.SYMBOL_BITWISE_OR, "|");
        add(TokenID.SYMBOL_BITWISE_NOT, "~");
        add(TokenID.SYMBOL_BITWISE_XOR, "^");
        // shift
        add(TokenID.SYMBOL_LOGICAL_LEFT_SHIFT, "<<");
        add(TokenID.SYMBOL_ARITHMETIC_RIGHT_SHIFT, ">>");
        add(TokenID.SYMBOL_LOGICAL_RIGHT_SHIFT, ">>>");
        // boolean
        add(TokenID.SYMBOL_BOOLEAN_NOT, "!");
        add(TokenID.SYMBOL_BOOLEAN_AND, "&&");
        add(TokenID.SYMBOL_BOOLEAN_OR, "||");
        // comparison
        add(TokenID.SYMBOL_LESSER, "<");
        add(TokenID.SYMBOL_GREATER, ">");
        add(TokenID.SYMBOL_GREATER_OR_EQUAL, ">=");
        add(TokenID.SYMBOL_LESSER_OR_EQUAL, "<=");
        add(TokenID.SYMBOL_EQUAL, "==");
        add(TokenID.SYMBOL_NOT_EQUAL, "!=");
        // assignment
        add(TokenID.SYMBOL_ASSIGN, "=");
        add(TokenID.SYMBOL_ADD_ASSIGN, "+=", "+");
        add(TokenID.SYMBOL_SUBTRACT_ASSIGN, "-=", "-");
        add(TokenID.SYMBOL_MULTIPLY_ASSIGN, "*=", "*");
        add(TokenID.SYMBOL_DIVIDE_ASSIGN, "/=", "/");
        add(TokenID.SYMBOL_REMAINDER_ASSIGN, "%=", "%");
        add(TokenID.SYMBOL_BITWISE_AND_ASSIGN, "&=", "&");
        add(TokenID.SYMBOL_BITWISE_OR_ASSIGN, "|=", "|");
        add(TokenID.SYMBOL_BITWISE_XOR_ASSIGN, "^=", "^");
        add(TokenID.SYMBOL_LOGICAL_LEFT_SHIFT_ASSIGN, "<<=", "<<");
        add(TokenID.SYMBOL_ARITHMETIC_RIGHT_SHIFT_ASSIGN, ">>=", ">>");
        add(TokenID.SYMBOL_LOGICAL_RIGHT_SHIFT_ASSIGN, ">>>=", ">>>");
        // enclosing
        add(TokenID.SYMBOL_OPEN_PARENTHESIS, "(");
        add(TokenID.SYMBOL_CLOSE_PARENTHESIS, ")");
        add(TokenID.SYMBOL_OPEN_BRACKET, "[");
        add(TokenID.SYMBOL_CLOSE_BRACKET, "]");
        add(TokenID.SYMBOL_OPEN_BRACE, "{");
        add(TokenID.SYMBOL_CLOSE_BRACE, "}");
        // comment
        add(TokenID.SYMBOL_DOUBLE_SLASH, "//");
        add(TokenID.SYMBOL_SLASH_STAR, "/*");
        add(TokenID.SYMBOL_STAR_SLASH, "*/");
        // other
        add(TokenID.SYMBOL_QUESTION_MARK, "?");
        add(TokenID.SYMBOL_DOUBLE_PERIOD, "..");
        add(TokenID.SYMBOL_TRIPLE_PERIOD, "...");
        add(TokenID.SYMBOL_DOUBLE_COLON, "::");
        add(TokenID.SYMBOL_ARROW, "->");
        add(TokenID.SYMBOL_AT, "@");
    }

    private Symbol(Symbol token, int index) {
        this(token.getID(), token.getSource(), index, token.getCompoundAssignOperator());
    }

    private Symbol(TokenID id, String source, int index, Symbol compoundAssignOperator) {
        super(id, source, index);
        this.compoundAssignOperator = compoundAssignOperator;
    }

    public Symbol getCompoundAssignOperator() {
        return compoundAssignOperator;
    }

    public static boolean is(char source) {
        return CHAR_SYMBOLS[source] != null;
    }

    public static boolean is(String source) {
        if (source.length() == 1) {
            return is(source.charAt(0));
        }
        return SYMBOLS.containsKey(source);
    }

    public static Symbol from(char source, int index) {
        final Symbol symbol = CHAR_SYMBOLS[source];
        return symbol == null ? null : new Symbol(symbol, index);
    }

    public static Symbol from(String source, int index) {
        if (source.length() == 1) {
            return from(source.charAt(0), index);
        }
        final Symbol symbol = SYMBOLS.get(source);
        return symbol == null ? null : new Symbol(symbol, index);
    }

    public static Collection<Symbol> all() {
        return Collections.unmodifiableCollection(SYMBOLS.values());
    }

    private static void add(TokenID id, String source) {
        add(id, source, null);
    }

    private static void add(TokenID id, String source, String compoundAssignOperator) {
        final Symbol symbol = new Symbol(id, source, 0, SYMBOLS.get(compoundAssignOperator));
        SYMBOLS.put(source, symbol);
        if (source.length() == 1) {
            CHAR_SYMBOLS[source.charAt(0)] = symbol;
        }
    }
} public abstract class Token implements SourceIndexed {
    private final TokenID id;
    private final String source;
    private int start;
    private int end;

    protected Token(TokenID id, String source, int index) {
        this.id = id;
        this.source = source;
        this.start = index;
        this.end = index + source.length() - 1;
    }

    protected Token(TokenID id, String source, int start, int end) {
        this.id = id;
        this.source = source;
        this.start = start;
        this.end = end;
    }

    public TokenID getID() {
        return id;
    }

    public TokenGroup getGroup() {
        return id.getGroup();
    }

    public String getSource() {
        return source;
    }

    public int getIndex() {
        return start;
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    public void setStart(int start) {
        this.start = start;
    }

    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return getSource();
    }
} public enum TokenGroup {
    IDENTIFIER,
    LITERAL,
    ACCESS_MODIFIER,
    OTHER_MODIFIER,
    PRIMITIVE_TYPE,
    CLASS_TYPE,
    SELF_REFERENCE,
    UNARY_OPERATOR,
    BINARY_OPERATOR,
    EQUAL_OPERATOR,
    COMPARISON_OPERATOR,
    SHIFT_OPERATOR,
    ADD_OPERATOR,
    MULTIPLY_OPERATOR,
    CALL_OPERATOR,
    ASSIGNMENT,
    COMMENT_DELIMITER,
    UNUSED,
    UNSPECIFIED
} public enum TokenID {
    IDENTIFIER(TokenGroup.IDENTIFIER),
    KEYWORD_ASSERT(TokenGroup.UNSPECIFIED),
    KEYWORD_IF(TokenGroup.UNSPECIFIED),
    KEYWORD_ELSE(TokenGroup.UNSPECIFIED),
    KEYWORD_WHILE(TokenGroup.UNSPECIFIED),
    KEYWORD_DO(TokenGroup.UNSPECIFIED),
    KEYWORD_FOR(TokenGroup.UNSPECIFIED),
    KEYWORD_BREAK(TokenGroup.UNSPECIFIED),
    KEYWORD_CONTINUE(TokenGroup.UNSPECIFIED),
    KEYWORD_SWITCH(TokenGroup.UNSPECIFIED),
    KEYWORD_CASE(TokenGroup.UNSPECIFIED),
    KEYWORD_DEFAULT(TokenGroup.UNSPECIFIED),
    KEYWORD_RETURN(TokenGroup.UNSPECIFIED),
    KEYWORD_THROW(TokenGroup.UNSPECIFIED),
    KEYWORD_TRY(TokenGroup.UNSPECIFIED),
    KEYWORD_CATCH(TokenGroup.UNSPECIFIED),
    KEYWORD_FINALLY(TokenGroup.UNSPECIFIED),
    KEYWORD_VOID(TokenGroup.UNSPECIFIED),
    KEYWORD_BOOLEAN(TokenGroup.PRIMITIVE_TYPE),
    KEYWORD_BYTE(TokenGroup.PRIMITIVE_TYPE),
    KEYWORD_SHORT(TokenGroup.PRIMITIVE_TYPE),
    KEYWORD_CHAR(TokenGroup.PRIMITIVE_TYPE),
    KEYWORD_INT(TokenGroup.PRIMITIVE_TYPE),
    KEYWORD_LONG(TokenGroup.PRIMITIVE_TYPE),
    KEYWORD_FLOAT(TokenGroup.PRIMITIVE_TYPE),
    KEYWORD_DOUBLE(TokenGroup.PRIMITIVE_TYPE),
    KEYWORD_CLASS(TokenGroup.CLASS_TYPE),
    KEYWORD_INTERFACE(TokenGroup.CLASS_TYPE),
    KEYWORD_ENUM(TokenGroup.CLASS_TYPE),
    KEYWORD_EXTENDS(TokenGroup.UNSPECIFIED),
    KEYWORD_IMPLEMENTS(TokenGroup.UNSPECIFIED),
    KEYWORD_SUPER(TokenGroup.SELF_REFERENCE),
    KEYWORD_THIS(TokenGroup.SELF_REFERENCE),
    KEYWORD_PACKAGE(TokenGroup.UNSPECIFIED),
    KEYWORD_IMPORT(TokenGroup.UNSPECIFIED),
    KEYWORD_PUBLIC(TokenGroup.ACCESS_MODIFIER),
    KEYWORD_PROTECTED(TokenGroup.ACCESS_MODIFIER),
    KEYWORD_PRIVATE(TokenGroup.ACCESS_MODIFIER),
    KEYWORD_THROWS(TokenGroup.UNSPECIFIED),
    KEYWORD_ABSTRACT(TokenGroup.OTHER_MODIFIER),
    KEYWORD_STRICTFP(TokenGroup.OTHER_MODIFIER),
    KEYWORD_TRANSIENT(TokenGroup.OTHER_MODIFIER),
    KEYWORD_VOLATILE(TokenGroup.OTHER_MODIFIER),
    KEYWORD_FINAL(TokenGroup.OTHER_MODIFIER),
    KEYWORD_STATIC(TokenGroup.OTHER_MODIFIER),
    KEYWORD_SYNCHRONIZED(TokenGroup.OTHER_MODIFIER),
    KEYWORD_NATIVE(TokenGroup.OTHER_MODIFIER),
    KEYWORD_NEW(TokenGroup.UNSPECIFIED),
    KEYWORD_INSTANCEOF(TokenGroup.BINARY_OPERATOR),
    KEYWORD_GOTO(TokenGroup.UNUSED),
    KEYWORD_CONST(TokenGroup.UNUSED),
    SYMBOL_PERIOD(TokenGroup.CALL_OPERATOR),
    SYMBOL_COMMA(TokenGroup.UNSPECIFIED),
    SYMBOL_COLON(TokenGroup.UNSPECIFIED),
    SYMBOL_SEMICOLON(TokenGroup.UNSPECIFIED),
    SYMBOL_PLUS(TokenGroup.ADD_OPERATOR),
    SYMBOL_MINUS(TokenGroup.ADD_OPERATOR),
    SYMBOL_MULTIPLY(TokenGroup.MULTIPLY_OPERATOR),
    SYMBOL_DIVIDE(TokenGroup.MULTIPLY_OPERATOR),
    SYMBOL_MODULO(TokenGroup.MULTIPLY_OPERATOR),
    SYMBOL_INCREMENT(TokenGroup.UNARY_OPERATOR),
    SYMBOL_DECREMENT(TokenGroup.UNARY_OPERATOR),
    SYMBOL_BITWISE_AND(TokenGroup.BINARY_OPERATOR),
    SYMBOL_BITWISE_OR(TokenGroup.BINARY_OPERATOR),
    SYMBOL_BITWISE_NOT(TokenGroup.UNARY_OPERATOR),
    SYMBOL_BITWISE_XOR(TokenGroup.BINARY_OPERATOR),
    SYMBOL_LOGICAL_LEFT_SHIFT(TokenGroup.SHIFT_OPERATOR),
    SYMBOL_ARITHMETIC_RIGHT_SHIFT(TokenGroup.SHIFT_OPERATOR),
    SYMBOL_LOGICAL_RIGHT_SHIFT(TokenGroup.SHIFT_OPERATOR),
    SYMBOL_BOOLEAN_NOT(TokenGroup.UNARY_OPERATOR),
    SYMBOL_BOOLEAN_AND(TokenGroup.BINARY_OPERATOR),
    SYMBOL_BOOLEAN_OR(TokenGroup.BINARY_OPERATOR),
    SYMBOL_LESSER(TokenGroup.COMPARISON_OPERATOR),
    SYMBOL_GREATER(TokenGroup.COMPARISON_OPERATOR),
    SYMBOL_GREATER_OR_EQUAL(TokenGroup.COMPARISON_OPERATOR),
    SYMBOL_LESSER_OR_EQUAL(TokenGroup.COMPARISON_OPERATOR),
    SYMBOL_EQUAL(TokenGroup.EQUAL_OPERATOR),
    SYMBOL_NOT_EQUAL(TokenGroup.EQUAL_OPERATOR),
    SYMBOL_ASSIGN(TokenGroup.ASSIGNMENT),
    SYMBOL_ADD_ASSIGN(TokenGroup.ASSIGNMENT),
    SYMBOL_SUBTRACT_ASSIGN(TokenGroup.ASSIGNMENT),
    SYMBOL_MULTIPLY_ASSIGN(TokenGroup.ASSIGNMENT),
    SYMBOL_DIVIDE_ASSIGN(TokenGroup.ASSIGNMENT),
    SYMBOL_REMAINDER_ASSIGN(TokenGroup.ASSIGNMENT),
    SYMBOL_BITWISE_AND_ASSIGN(TokenGroup.ASSIGNMENT),
    SYMBOL_BITWISE_OR_ASSIGN(TokenGroup.ASSIGNMENT),
    SYMBOL_BITWISE_XOR_ASSIGN(TokenGroup.ASSIGNMENT),
    SYMBOL_LOGICAL_LEFT_SHIFT_ASSIGN(TokenGroup.ASSIGNMENT),
    SYMBOL_ARITHMETIC_RIGHT_SHIFT_ASSIGN(TokenGroup.ASSIGNMENT),
    SYMBOL_LOGICAL_RIGHT_SHIFT_ASSIGN(TokenGroup.ASSIGNMENT),
    SYMBOL_OPEN_PARENTHESIS(TokenGroup.UNSPECIFIED),
    SYMBOL_CLOSE_PARENTHESIS(TokenGroup.UNSPECIFIED),
    SYMBOL_OPEN_BRACKET(TokenGroup.UNSPECIFIED),
    SYMBOL_CLOSE_BRACKET(TokenGroup.UNSPECIFIED),
    SYMBOL_OPEN_BRACE(TokenGroup.UNSPECIFIED),
    SYMBOL_CLOSE_BRACE(TokenGroup.UNSPECIFIED),
    SYMBOL_DOUBLE_SLASH(TokenGroup.COMMENT_DELIMITER),
    SYMBOL_SLASH_STAR(TokenGroup.COMMENT_DELIMITER),
    SYMBOL_STAR_SLASH(TokenGroup.COMMENT_DELIMITER),
    SYMBOL_QUESTION_MARK(TokenGroup.UNSPECIFIED),
    SYMBOL_DOUBLE_PERIOD(TokenGroup.UNUSED),
    SYMBOL_TRIPLE_PERIOD(TokenGroup.UNSPECIFIED),
    SYMBOL_DOUBLE_COLON(TokenGroup.UNSPECIFIED),
    SYMBOL_ARROW(TokenGroup.UNSPECIFIED),
    SYMBOL_AT(TokenGroup.UNSPECIFIED),
    LITERAL_TRUE(TokenGroup.LITERAL),
    LITERAL_FALSE(TokenGroup.LITERAL),
    LITERAL_CHARACTER(TokenGroup.LITERAL),
    LITERAL_NULL(TokenGroup.LITERAL),
    LITERAL_STRING(TokenGroup.LITERAL),
    LITERAL_DOUBLE(TokenGroup.LITERAL),
    LITERAL_FLOAT(TokenGroup.LITERAL),
    LITERAL_INT(TokenGroup.LITERAL),
    LITERAL_LONG(TokenGroup.LITERAL);
    private final TokenGroup group;

    TokenID(TokenGroup group) {
        this.group = group;
    }

    public TokenGroup getGroup() {
        return group;
    }
} public abstract class Literal extends Token implements Value, Expression {
    protected Literal(TokenID id, String source, int index) {
        super(id, source, index);
    }

    protected Literal(TokenID id, String source, int start, int end) {
        super(id, source, start, end);
    }
} public class IntLiteral extends NumberLiteral {
    private int value = 0;
    private boolean evaluated = false;

    public IntLiteral(String source, int index) {
        super(TokenID.LITERAL_INT, source, index);
    }

    private IntLiteral(String source, int start, int end) {
        super(TokenID.LITERAL_INT, source, start, end);
    }

    private void evaluate() {
        if (evaluated) {
            return;
        }
        String source = StringUtil.reduceSign(getSource());
        final int radix = StringUtil.findRadix(source);
        try {
            source = StringUtil.removeRadixIdentifier(source, radix);
            value = Integer.parseInt(source, radix);
        } catch (Exception exception) {
            throw new EvaluatorException(exception.getMessage(), this);
        }
        evaluated = true;
    }

    @Override
    public boolean asBoolean() {
        throw new EvaluatorException("Cannot convert an int to a boolean", this);
    }

    @Override
    public byte asByte() {
        return (byte) asInt();
    }

    @Override
    public short asShort() {
        return (short) asInt();
    }

    @Override
    public char asChar() {
        return (char) asInt();
    }

    @Override
    public int asInt() {
        evaluate();
        return value;
    }

    @Override
    public long asLong() {
        return asInt();
    }

    @Override
    public float asFloat() {
        return asInt();
    }

    @Override
    public double asDouble() {
        return asInt();
    }

    @Override
    public Integer asObject() {
        return asInt();
    }

    @Override
    public String asString() {
        return Integer.toString(asInt());
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T as() {
        return (T) asObject();
    }

    @Override
    public ValueKind getKind() {
        return ValueKind.INT;
    }

    @Override
    public Class<?> getTypeClass() {
        return int.class;
    }

    @Override
    public Type getType(Environment environment) {
        evaluate();
        return PrimitiveType.THE_INT;
    }

    @Override
    public boolean isPrimitive() {
        return true;
    }

    @Override
    public Value getValue(Environment environment) {
        return this;
    }

    public IntLiteral withSign(Symbol sign) {
        final TokenID id = sign.getID();
        if (id != TokenID.SYMBOL_MINUS && id != TokenID.SYMBOL_PLUS) {
            throw new UnsupportedOperationException("Expected a sign symbol");
        }
        return new IntLiteral(sign.getSource() + (sign.getSource().charAt(0) == getSource().charAt(0) ? ' ' : "") + getSource(), sign.getIndex(), getEnd());
    }
} public class LongLiteral extends NumberLiteral {
    private long value = 0;
    private boolean evaluated = false;

    public LongLiteral(String source, int index) {
        super(TokenID.LITERAL_LONG, source, index);
    }

    private LongLiteral(String source, int start, int end) {
        super(TokenID.LITERAL_LONG, source, start, end);
    }

    private void evaluate() {
        if (!evaluated) {
            String source = StringUtil.reduceSign(getSource());
            final int lastIndex = source.length() - 1;
            if (StringUtil.equalsNoCaseASCII(source.charAt(lastIndex), 'l')) {
                source = source.substring(0, lastIndex);
            }
            final int radix = StringUtil.findRadix(source);
            try {
                source = StringUtil.removeRadixIdentifier(source, radix);
                value = Long.parseLong(source, radix);
            } catch (Exception exception) {
                throw new EvaluatorException(exception.getMessage(), this);
            }
            evaluated = true;
        }
    }

    @Override
    public boolean asBoolean() {
        throw new EvaluatorException("Cannot convert a long to a boolean", this);
    }

    @Override
    public byte asByte() {
        return (byte) asLong();
    }

    @Override
    public short asShort() {
        return (short) asLong();
    }

    @Override
    public char asChar() {
        return (char) asLong();
    }

    @Override
    public int asInt() {
        return (int) asLong();
    }

    @Override
    public long asLong() {
        evaluate();
        return value;
    }

    @Override
    public float asFloat() {
        return asLong();
    }

    @Override
    public double asDouble() {
        return asLong();
    }

    @Override
    public Long asObject() {
        return asLong();
    }

    @Override
    public String asString() {
        return Long.toString(asLong());
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T as() {
        return (T) asObject();
    }

    @Override
    public ValueKind getKind() {
        return ValueKind.LONG;
    }

    @Override
    public boolean isPrimitive() {
        return true;
    }

    @Override
    public Class<?> getTypeClass() {
        return long.class;
    }

    @Override
    public Type getType(Environment environment) {
        evaluate();
        return PrimitiveType.THE_LONG;
    }

    @Override
    public Value getValue(Environment environment) {
        return this;
    }

    public LongLiteral withSign(Symbol sign) {
        final TokenID id = sign.getID();
        if (id != TokenID.SYMBOL_MINUS && id != TokenID.SYMBOL_PLUS) {
            throw new UnsupportedOperationException("Expected a sign symbol");
        }
        return new LongLiteral(sign.getSource() + (sign.getSource().charAt(0) == getSource().charAt(0) ? ' ' : "") + getSource(), sign.getIndex(), getEnd());
    }
} public class ArrayConstructor implements Expression {
    private final TypeName typeName;
    private final ArrayInitializer initializer;
    private final List<Expression> sizes;
    private int start;
    private int end;
    private ComponentType componentType = null;
    private int sizedDimensions = 0;
    private Type type = null;

    public ArrayConstructor(ArrayTypeName typeName, ArrayInitializer initializer, int start, int end) {
        this(typeName, null, initializer, start, end);
    }

    public ArrayConstructor(TypeName componentTypeName, List<Expression> sizes, int start, int end) {
        this(componentTypeName, sizes, null, start, end);
    }

    private ArrayConstructor(TypeName typeName, List<Expression> sizes, ArrayInitializer initializer, int start, int end) {
        this.typeName = typeName;
        this.initializer = initializer;
        this.sizes = sizes;
        this.start = start;
        this.end = end;
    }

    @Override
    public Type getType(Environment environment) {
        if (type != null) {
            return type;
        }
        if (initializer == null) {
            int sizedDimensions = 0;
            for (Expression size : sizes) {
                if (size == null) {
                    break;
                }
                final Type sizeType = size.getType(environment);
                if (!sizeType.convertibleTo(PrimitiveType.THE_INT)) {
                    throw new EvaluatorException("Cannot convert " + sizeType.getName() + " to int", size);
                }
                sizedDimensions++;
            }
            final int dimensions = sizes.size();
            final int unsizedDimensions = dimensions - sizedDimensions;
            final LiteralType baseType = typeName.getType(environment);
            if (!baseType.isReifiable()) {
                throw new EvaluatorException("Array component type must be reifiable", typeName);
            }
            final ComponentType componentType;
            if (unsizedDimensions != 0) {
                componentType = baseType.asArray(unsizedDimensions);
            } else {
                componentType = baseType;
            }
            this.componentType = componentType;
            this.sizedDimensions = sizedDimensions;
            type = baseType.asArray(dimensions);
        } else {
            final Type arrayType = typeName.getType(environment);
            initializer.setType(environment, arrayType);
            type = arrayType;
        }
        return type;
    }

    @Override
    public Value getValue(Environment environment) {
        if (initializer == null) {
            final int[] dimensionSizes = new int[sizedDimensions];
            for (int i = 0; i < sizedDimensions; i++) {
                dimensionSizes[i] = sizes.get(i).getValue(environment).asInt();
            }
            return ObjectValue.of(componentType.newArray(dimensionSizes));
        }
        return initializer.getValue(environment);
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "ArrayConstructor(new " + typeName + (initializer == null ? sizesToString() : initializer) + ")";
    }

    private String sizesToString() {
        final StringBuilder builder = new StringBuilder();
        for (Expression size : sizes) {
            if (size == null) {
                builder.append("[]");
            } else {
                builder.append('[').append(size).append(']');
            }
        }
        return builder.toString();
    }

    public static class ArrayInitializer implements Expression {
        private final List<Expression> elements;
        private int start;
        private int end;
        private Type type = null;
        private ComponentType componentType = null;

        public ArrayInitializer(List<Expression> elements, int start, int end) {
            this.elements = elements;
            this.start = start;
            this.end = end;
        }

        public void setType(Environment environment, Type type) {
            if (this.type == null) {
                if (!type.isArray()) {
                    throw new EvaluatorException("Cannot convert array type to " + type.getName(), this);
                }
                final ComponentType componentType = ((ReferenceType) type).getComponentType();
                for (final Expression element : elements) {
                    if (element instanceof ArrayInitializer) {
                        ((ArrayInitializer) element).setType(environment, componentType);
                    } else {
                        final Type elementType = element.getType(environment);
                        if (!elementType.convertibleTo(componentType)) {
                            throw new EvaluatorException("Cannot convert " + elementType.getName() + " to " + componentType.getName(), this);
                        }
                    }
                }
                this.componentType = componentType;
                this.type = type;
                return;
            }
            throw new UnsupportedOperationException("Cannot reset array initializer type");
        }

        @Override
        public Type getType(Environment environment) {
            if (type == null) {
                throw new UnsupportedOperationException("Array initializer type has not been set");
            }
            return type;
        }

        @Override
        public Value getValue(Environment environment) {
            final int size = elements.size();
            final Object array = componentType.newArray(size);
            for (int i = 0; i < size; i++) {
                Array.set(array, i, elements.get(i).getValue(environment).asObject());
            }
            return ObjectValue.of(array);
        }

        @Override
        public int getStart() {
            return start;
        }

        @Override
        public int getEnd() {
            return end;
        }

        @Override
        public void setStart(int start) {
            this.start = start;
        }

        @Override
        public void setEnd(int end) {
            this.end = end;
        }

        @Override
        public String toString() {
            return '{' + StringUtil.toString(elements, ", ") + '}';
        }
    }
} public static class ArrayInitializer implements Expression {
    private final List<Expression> elements;
    private int start;
    private int end;
    private Type type = null;
    private ComponentType componentType = null;

    public ArrayInitializer(List<Expression> elements, int start, int end) {
        this.elements = elements;
        this.start = start;
        this.end = end;
    }

    public void setType(Environment environment, Type type) {
        if (this.type == null) {
            if (!type.isArray()) {
                throw new EvaluatorException("Cannot convert array type to " + type.getName(), this);
            }
            final ComponentType componentType = ((ReferenceType) type).getComponentType();
            for (final Expression element : elements) {
                if (element instanceof ArrayInitializer) {
                    ((ArrayInitializer) element).setType(environment, componentType);
                } else {
                    final Type elementType = element.getType(environment);
                    if (!elementType.convertibleTo(componentType)) {
                        throw new EvaluatorException("Cannot convert " + elementType.getName() + " to " + componentType.getName(), this);
                    }
                }
            }
            this.componentType = componentType;
            this.type = type;
            return;
        }
        throw new UnsupportedOperationException("Cannot reset array initializer type");
    }

    @Override
    public Type getType(Environment environment) {
        if (type == null) {
            throw new UnsupportedOperationException("Array initializer type has not been set");
        }
        return type;
    }

    @Override
    public Value getValue(Environment environment) {
        final int size = elements.size();
        final Object array = componentType.newArray(size);
        for (int i = 0; i < size; i++) {
            Array.set(array, i, elements.get(i).getValue(environment).asObject());
        }
        return ObjectValue.of(array);
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return '{' + StringUtil.toString(elements, ", ") + '}';
    }
} public class Cast implements Expression {
    private final TypeName typeName;
    private final Expression object;
    private int start;
    private int end;
    private LiteralType type = null;

    public Cast(TypeName typeName, Expression object, int start) {
        this.typeName = typeName;
        this.object = object;
        this.start = start;
        end = object.getEnd();
    }

    @Override
    public Type getType(Environment environment) {
        if (type != null) {
            return type;
        }
        final LiteralType castType = typeName.getType(environment);
        Type objectType = object.getType(environment);
        // Must capture variables subject to casting conversion
        if (object instanceof VariableAccess) {
            objectType = objectType.capture();
        }
        // Check the validity of the cast
        if (objectType.isVoid() || castType.isVoid()) {
            failCast(castType, objectType);
        }
        // apply boxing or unboxing if possible to have
        // a pair primitive-primitive or object-object
        if (castType.isPrimitive()) {
            if (objectType instanceof LiteralReferenceType) {
                objectType = ((LiteralReferenceType) objectType).tryUnbox();
            }
        } else if (objectType instanceof PrimitiveType) {
            objectType = ((PrimitiveType) objectType).box();
        }
        // cast primitive to primitive
        // cast boolean to boolean
        // more complicated rules for reference types
        if (castType.isPrimitive()) {
            if (!objectType.isPrimitive()) {
                failCast(castType, objectType);
            }
            if (castType.isBoolean() ^ objectType.isBoolean()) {
                failCast(castType, objectType);
            }
        } else if (objectType.isPrimitive()) {
            failCast(castType, objectType);
        } else if (!TypeUtil.isValidReferenceCast((ReferenceType) objectType, (ReferenceType) castType)) {
            failCast(castType, objectType);
        }
        return type = castType.capture();
    }

    private void failCast(Type cast, Type object) {
        failCast(cast.getName(), object.getName());
    }

    private void failCast(Type cast, Class<?> object) {
        failCast(cast.getName(), object.getCanonicalName());
    }

    private void failCast(String cast, String object) {
        throw new EvaluatorException("Cannot cast " + object + " to " + cast, this);
    }

    @Override
    public Value getValue(Environment environment) {
        final Value value = object.getValue(environment);
        switch (type.getKind()) {
            case OBJECT:
                final Object object = value.asObject();
                if (object != null && !type.getTypeClass().isInstance(object)) {
                    failCast(type, object.getClass());
                }
                return value;
            default:
                return type.getKind().convert(value);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "Cast((" + typeName + ") " + object + ")";
    }
} public class ClassAccess implements Expression {
    private static final LiteralReferenceType TYPE = LiteralReferenceType.of(Class.class);
    private final TypeName typeName;
    private final SourceIndexed source;
    private int start;
    private int end;
    private Type type = null;
    private Class<?> typeClass = null;

    public ClassAccess(Token voidToken, int end) {
        if (voidToken.getID() != TokenID.KEYWORD_VOID) {
            throw new IllegalArgumentException("Not a void token " + voidToken);
        }
        typeName = null;
        source = voidToken;
        start = source.getStart();
        this.end = end;
    }

    public ClassAccess(TypeName typeName, int end) {
        this.typeName = typeName;
        source = typeName;
        this.end = end;
    }

    @Override
    public Type getType(Environment environment) {
        if (type == null) {
            if (typeName == null) {
                typeClass = void.class;
            } else {
                typeClass = typeName.getType(environment).getTypeClass();
            }
            type = TYPE;
        }
        return type;
    }

    @Override
    public Value getValue(Environment environment) {
        return ObjectValue.of(typeClass);
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "ClassAccess(" + source + ".class" + ")";
    }
} public class Conditional implements Expression {
    private final Expression test;
    private final Expression left;
    private final Expression right;
    private int start;
    private int end;
    private Type type = null;

    public Conditional(Expression test, Expression left, Expression right) {
        this.test = test;
        this.left = left;
        this.right = right;
        start = test.getStart();
        end = right.getEnd();
    }

    @Override
    public Type getType(Environment environment) {
        if (type != null) {
            return type;
        }
        final Type testType = TypeUtil.coerceToPrimitive(environment, test);
        Type leftType = left.getType(environment);
        Type rightType = right.getType(environment);
        if (!testType.isBoolean()) {
            throw new EvaluatorException("Not a boolean: " + testType.getName(), test);
        }
        if (leftType.isVoid()) {
            throw new EvaluatorException("Illegal type: void", left);
        }
        if (rightType.isVoid()) {
            throw new EvaluatorException("Illegal type: void", right);
        }
        if (leftType.equals(rightType)) {
            // both same type to that type
            return type = leftType;
        }
        if (leftType.isNull()) {
            // left null to right type
            if (rightType instanceof PrimitiveType) {
                rightType = ((PrimitiveType) rightType).box();
            }
            return type = rightType;
        }
        if (rightType.isNull()) {
            // right null to left type
            if (leftType instanceof PrimitiveType) {
                leftType = ((PrimitiveType) leftType).box();
            }
            return type = leftType;
        }
        if (leftType instanceof LiteralReferenceType) {
            leftType = ((LiteralReferenceType) leftType).tryUnbox();
        }
        if (rightType instanceof LiteralReferenceType) {
            rightType = ((LiteralReferenceType) rightType).tryUnbox();
        }
        if (leftType.isReference() || rightType.isReference()) {
            // for objects use the lowest upper bound capture
            return type = TypeUtil.lowestUpperBound(
                    leftType instanceof PrimitiveType ? ((PrimitiveType) leftType).box() : (ReferenceType) leftType,
                    rightType instanceof PrimitiveType ? ((PrimitiveType) rightType).box() : (ReferenceType) rightType
            ).capture();
        }
        final PrimitiveType leftPrimitiveType = (PrimitiveType) leftType;
        final PrimitiveType rightPrimitiveType = (PrimitiveType) rightType;
        if (left instanceof IntLiteral && rightPrimitiveType.canNarrowFrom(((IntLiteral) left).asInt())) {
            // left constant numeric that narrows to right, use right
            return type = rightPrimitiveType;
        }
        if (right instanceof IntLiteral && leftPrimitiveType.canNarrowFrom(((IntLiteral) right).asInt())) {
            // right constant numeric that narrows to left, use left
            return type = leftPrimitiveType;
        }
        if (leftPrimitiveType.equals(PrimitiveType.THE_BYTE) && rightPrimitiveType.equals(PrimitiveType.THE_SHORT)
                || leftPrimitiveType.equals(PrimitiveType.THE_SHORT) && rightPrimitiveType.equals(PrimitiveType.THE_BYTE)) {
            // one byte and other short to short
            return type = PrimitiveType.THE_SHORT;
        }
        // else use binary widening
        return type = leftPrimitiveType.binaryWiden(rightPrimitiveType);
    }

    @Override
    public Value getValue(Environment environment) {
        final Value testValue = test.getValue(environment);
        final ValueKind widenKind = type.getKind();
        final Value resultValue = testValue.asBoolean() ? left.getValue(environment) : right.getValue(environment);
        if (resultValue.getKind() == widenKind) {
            // prevent useless conversion
            return resultValue;
        }
        if (resultValue instanceof ObjectValue) {
            // prevent useless reboxing
            return resultValue;
        }
        return widenKind.convert(resultValue);
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "Conditional(" + test + " ? " + left + " : " + right + ")";
    }
} public interface Expression extends SourceIndexed {
    Type getType(Environment environment);

    Value getValue(Environment environment);

    void setStart(int start);

    void setEnd(int end);

    String toString();
} public class Shift implements Expression {
    private final Expression left;
    private final Expression right;
    private final Symbol operator;
    private int start;
    private int end;
    private Type type = null;
    private Type shiftType = null;

    public Shift(Expression left, Expression right, Symbol operator) {
        this.left = left;
        this.right = right;
        this.operator = operator;
        this.start = left.getStart();
        this.end = right.getEnd();
    }

    @Override
    public Type getType(Environment environment) {
        if (type == null) {
            final PrimitiveType leftType = TypeUtil.coerceToPrimitive(environment, left);
            final PrimitiveType rightType = TypeUtil.coerceToPrimitive(environment, right);
            if (!leftType.isIntegral()) {
                throw new EvaluatorException("Not an integral type: " + leftType.getName(), left);
            }
            if (!rightType.isIntegral()) {
                throw new EvaluatorException("Not an integral type: " + rightType.getName(), right);
            }
            type = leftType.unaryWiden();
            shiftType = rightType.unaryWiden();
        }
        return type;
    }

    @Override
    public Value getValue(Environment environment) {
        final Value leftValue = left.getValue(environment);
        final Value rightValue = right.getValue(environment);
        final ValueKind leftWidenKind = type.getKind();
        final ValueKind rightWidenKind = shiftType.getKind();
        switch (operator.getID()) {
            case SYMBOL_LOGICAL_LEFT_SHIFT:
                return doLogicalLeftShift(leftValue, leftWidenKind, rightValue, rightWidenKind);
            case SYMBOL_LOGICAL_RIGHT_SHIFT:
                return doLogicalRightShift(leftValue, leftWidenKind, rightValue, rightWidenKind);
            case SYMBOL_ARITHMETIC_RIGHT_SHIFT:
                return doArithmeticRightShift(leftValue, leftWidenKind, rightValue, rightWidenKind);
            default:
                throw new EvaluatorException("Invalid operator for shift: " + operator, operator);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    private Value doLogicalLeftShift(Value leftValue, ValueKind leftWidenKind, Value rightValue, ValueKind rightWidenKind) {
        switch (leftWidenKind) {
            case INT:
                switch (rightWidenKind) {
                    case INT:
                        return IntValue.of(leftValue.asInt() << rightValue.asInt());
                    case LONG:
                        return IntValue.of(leftValue.asInt() << rightValue.asLong());
                    default:
                        throw new EvaluatorException("Invalid type for logical-left-shift right operand: " + rightWidenKind, this);
                }
            case LONG:
                switch (rightWidenKind) {
                    case INT:
                        return LongValue.of(leftValue.asLong() << rightValue.asInt());
                    case LONG:
                        return LongValue.of(leftValue.asLong() << rightValue.asLong());
                    default:
                        throw new EvaluatorException("Invalid type for logical-left-shift right operand: " + rightWidenKind, this);
                }
            default:
                throw new EvaluatorException("Invalid type for logical-left-shift left operand: " + leftWidenKind, this);
        }
    }

    private Value doLogicalRightShift(Value leftValue, ValueKind leftWidenKind, Value rightValue, ValueKind rightWidenKind) {
        switch (leftWidenKind) {
            case INT:
                switch (rightWidenKind) {
                    case INT:
                        return IntValue.of(leftValue.asInt() >>> rightValue.asInt());
                    case LONG:
                        return IntValue.of(leftValue.asInt() >>> rightValue.asLong());
                    default:
                        throw new EvaluatorException("Invalid type for logical-right-shift right operand: " + rightWidenKind, this);
                }
            case LONG:
                switch (rightWidenKind) {
                    case INT:
                        return LongValue.of(leftValue.asLong() >>> rightValue.asInt());
                    case LONG:
                        return LongValue.of(leftValue.asLong() >>> rightValue.asLong());
                    default:
                        throw new EvaluatorException("Invalid type for logical-right-shift right operand: " + rightWidenKind, this);
                }
            default:
                throw new EvaluatorException("Invalid type for logical-right-shift left operand: " + leftWidenKind, this);
        }
    }

    private Value doArithmeticRightShift(Value leftValue, ValueKind leftWidenKind, Value rightValue, ValueKind rightWidenKind) {
        switch (leftWidenKind) {
            case INT:
                switch (rightWidenKind) {
                    case INT:
                        return IntValue.of(leftValue.asInt() >> rightValue.asInt());
                    case LONG:
                        return IntValue.of(leftValue.asInt() >> rightValue.asLong());
                    default:
                        throw new EvaluatorException("Invalid type for arithmetic-right-shift right operand: " + rightWidenKind, this);
                }
            case LONG:
                switch (rightWidenKind) {
                    case INT:
                        return LongValue.of(leftValue.asLong() >> rightValue.asInt());
                    case LONG:
                        return LongValue.of(leftValue.asLong() >> rightValue.asLong());
                    default:
                        throw new EvaluatorException("Invalid type for arithmetic-right-shift right operand: " + rightWidenKind, this);
                }
            default:
                throw new EvaluatorException("Invalid type for arithmetic-right-shift left operand: " + leftWidenKind, this);
        }
    }

    @Override
    public String toString() {
        return "Shift(" + left + " " + operator + " " + right + ")";
    }
} public class Arithmetic implements Expression {
    private final Expression left;
    private final Expression right;
    private final Symbol operator;
    private int start;
    private int end;
    private boolean stringConcatenation = false;
    private Type leftType = null;
    private Type type = null;

    public Arithmetic(Expression left, Expression right, Symbol operator) {
        this.left = left;
        this.right = right;
        this.operator = operator;
        start = left.getStart();
        end = right.getEnd();
    }

    @Override
    public Type getType(Environment environment) {
        if (type == null) {
            final Type leftType = left.getType(environment);
            final Type rightType = right.getType(environment);
            if (operator.getID() == TokenID.SYMBOL_PLUS && (leftType.equals(LiteralReferenceType.THE_STRING) || rightType.equals(LiteralReferenceType.THE_STRING))) {
                if (leftType.isVoid()) {
                    throw new EvaluatorException("Cannot convert void to java.lang.String", left);
                }
                if (rightType.isVoid()) {
                    throw new EvaluatorException("Cannot convert void to java.lang.String", right);
                }
                type = LiteralReferenceType.THE_STRING;
                stringConcatenation = true;
                this.leftType = leftType;
            } else {
                final PrimitiveType leftPrimitiveType = TypeUtil.coerceToPrimitive(left, leftType);
                final PrimitiveType rightPrimitiveType = TypeUtil.coerceToPrimitive(right, rightType);
                if (!leftPrimitiveType.isNumeric()) {
                    throw new EvaluatorException("Not a numeric type: " + leftPrimitiveType.getName(), left);
                }
                if (!rightPrimitiveType.isNumeric()) {
                    throw new EvaluatorException("Not a numeric type: " + rightPrimitiveType.getName(), right);
                }
                type = leftPrimitiveType.binaryWiden(rightPrimitiveType);
                this.leftType = leftPrimitiveType;
            }
        }
        return type;
    }

    public Type getLeftType() {
        return leftType;
    }

    @Override
    public Value getValue(Environment environment) {
        final Value leftValue = left.getValue(environment);
        final Value rightValue = right.getValue(environment);
        if (stringConcatenation) {
            return doStringConcatenation(leftValue, rightValue);
        }
        final ValueKind widenKind = type.getKind();
        switch (operator.getID()) {
            case SYMBOL_PLUS:
                return doAdd(leftValue, rightValue, widenKind);
            case SYMBOL_MINUS:
                return doSubtract(leftValue, rightValue, widenKind);
            case SYMBOL_MULTIPLY:
                return doMultiply(leftValue, rightValue, widenKind);
            case SYMBOL_DIVIDE:
                return doDivide(leftValue, rightValue, widenKind);
            case SYMBOL_MODULO:
                return doModulo(leftValue, rightValue, widenKind);
            default:
                throw new EvaluatorException("Invalid operator for arithmetic: " + operator, operator);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    private Value doStringConcatenation(Value leftValue, Value rightValue) {
        return ObjectValue.of(leftValue.asString() + rightValue.asString());
    }

    private Value doAdd(Value leftValue, Value rightValue, ValueKind widenKind) {
        switch (widenKind) {
            case INT:
                return IntValue.of(leftValue.asInt() + rightValue.asInt());
            case LONG:
                return LongValue.of(leftValue.asLong() + rightValue.asLong());
            case FLOAT:
                return FloatValue.of(leftValue.asFloat() + rightValue.asFloat());
            case DOUBLE:
                return DoubleValue.of(leftValue.asDouble() + rightValue.asDouble());
            default:
                throw new EvaluatorException("Invalid type for add: " + widenKind, this);
        }
    }

    private Value doSubtract(Value leftValue, Value rightValue, ValueKind widenKind) {
        switch (widenKind) {
            case INT:
                return IntValue.of(leftValue.asInt() - rightValue.asInt());
            case LONG:
                return LongValue.of(leftValue.asLong() - rightValue.asLong());
            case FLOAT:
                return FloatValue.of(leftValue.asFloat() - rightValue.asFloat());
            case DOUBLE:
                return DoubleValue.of(leftValue.asDouble() - rightValue.asDouble());
            default:
                throw new EvaluatorException("Invalid type for subtract: " + widenKind, this);
        }
    }

    private Value doMultiply(Value leftValue, Value rightValue, ValueKind widenKind) {
        switch (widenKind) {
            case INT:
                return IntValue.of(leftValue.asInt() * rightValue.asInt());
            case LONG:
                return LongValue.of(leftValue.asLong() * rightValue.asLong());
            case FLOAT:
                return FloatValue.of(leftValue.asFloat() * rightValue.asFloat());
            case DOUBLE:
                return DoubleValue.of(leftValue.asDouble() * rightValue.asDouble());
            default:
                throw new EvaluatorException("Invalid type for multiply: " + widenKind, this);
        }
    }

    private Value doDivide(Value leftValue, Value rightValue, ValueKind widenKind) {
        switch (widenKind) {
            case INT:
                return IntValue.of(leftValue.asInt() / rightValue.asInt());
            case LONG:
                return LongValue.of(leftValue.asLong() / rightValue.asLong());
            case FLOAT:
                return FloatValue.of(leftValue.asFloat() / rightValue.asFloat());
            case DOUBLE:
                return DoubleValue.of(leftValue.asDouble() / rightValue.asDouble());
            default:
                throw new EvaluatorException("Invalid type for divide: " + widenKind, this);
        }
    }

    private Value doModulo(Value leftValue, Value rightValue, ValueKind widenKind) {
        switch (widenKind) {
            case INT:
                return IntValue.of(leftValue.asInt() % rightValue.asInt());
            case LONG:
                return LongValue.of(leftValue.asLong() % rightValue.asLong());
            case FLOAT:
                return FloatValue.of(leftValue.asFloat() % rightValue.asFloat());
            case DOUBLE:
                return DoubleValue.of(leftValue.asDouble() % rightValue.asDouble());
            default:
                throw new EvaluatorException("Invalid type for modulo: " + widenKind, this);
        }
    }

    @Override
    public String toString() {
        return "Arithmetic(" + left + " " + operator + " " + right + ")";
    }
} public class Sign implements Expression {
    private final Expression inner;
    private final Symbol operator;
    private int start;
    private int end;
    private Type type = null;

    public Sign(Expression inner, Symbol operator) {
        this.inner = inner;
        this.operator = operator;
        start = operator.getStart();
        end = inner.getEnd();
    }

    @Override
    public Type getType(Environment environment) {
        if (type == null) {
            final PrimitiveType innerType = TypeUtil.coerceToPrimitive(environment, inner);
            if (!innerType.isNumeric()) {
                throw new EvaluatorException("Not a numeric type: " + innerType.getName(), inner);
            }
            type = innerType.unaryWiden();
        }
        return type;
    }

    @Override
    public Value getValue(Environment environment) {
        final Value innerValue = inner.getValue(environment);
        final ValueKind widenKind = type.getKind();
        switch (operator.getID()) {
            case SYMBOL_PLUS:
                return doReaffirm(innerValue, widenKind);
            case SYMBOL_MINUS:
                return doNegate(innerValue, widenKind);
            default:
                throw new EvaluatorException("Invalid operator for sign: " + operator, operator);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    private Value doNegate(Value innerValue, ValueKind widenKind) {
        switch (widenKind) {
            case INT:
                return IntValue.of(-innerValue.asInt());
            case LONG:
                return LongValue.of(-innerValue.asLong());
            case FLOAT:
                return FloatValue.of(-innerValue.asFloat());
            case DOUBLE:
                return DoubleValue.of(-innerValue.asDouble());
            default:
                throw new EvaluatorException("Invalid type for negate, got " + widenKind, this);
        }
    }

    private Value doReaffirm(Value innerValue, ValueKind widenKind) {
        if (innerValue.getKind() == widenKind) {
            return innerValue;
        }
        switch (widenKind) {
            case INT:
            case LONG:
            case FLOAT:
            case DOUBLE:
                return widenKind.convert(innerValue);
            default:
                throw new EvaluatorException("Invalid type for reaffirm, got " + widenKind, this);
        }
    }

    @Override
    public String toString() {
        return "Sign(" + operator.toString() + inner.toString() + ")";
    }
} public class Assignment implements Expression, Statement {
    private final Reference assignee;
    private final Expression value;
    private int start;
    private int end;
    private boolean simpleAssign = false;
    private Type type = null;

    public Assignment(Reference assignee, Expression value, Symbol operator) {
        this.assignee = assignee;
        switch (operator.getID()) {
            case SYMBOL_ASSIGN:
                simpleAssign = true;
                this.value = value;
                break;
            case SYMBOL_ADD_ASSIGN:
            case SYMBOL_SUBTRACT_ASSIGN:
            case SYMBOL_MULTIPLY_ASSIGN:
            case SYMBOL_DIVIDE_ASSIGN:
            case SYMBOL_REMAINDER_ASSIGN:
                this.value = new Arithmetic(assignee, value, operator.getCompoundAssignOperator());
                break;
            case SYMBOL_BITWISE_AND_ASSIGN:
            case SYMBOL_BITWISE_OR_ASSIGN:
            case SYMBOL_BITWISE_XOR_ASSIGN:
                this.value = new BitwiseLogic(assignee, value, operator.getCompoundAssignOperator());
                break;
            case SYMBOL_LOGICAL_LEFT_SHIFT_ASSIGN:
            case SYMBOL_ARITHMETIC_RIGHT_SHIFT_ASSIGN:
            case SYMBOL_LOGICAL_RIGHT_SHIFT_ASSIGN:
                this.value = new Shift(assignee, value, operator.getCompoundAssignOperator());
                break;
            default:
                throw new EvaluatorException("Not a valid operator for assign: " + operator, operator);
        }
        start = assignee.getStart();
        end = value.getEnd();
    }

    @Override
    public void execute(Environment environment) {
        try {
            getType(environment);
            getValue(environment);
        } catch (EvaluatorException exception) {
            throw exception;
        } catch (Exception exception) {
            throw new EvaluatorException(exception, this);
        }
    }

    @Override
    public Type getType(Environment environment) {
        if (type != null) {
            return type;
        }
        Type valueType = value.getType(environment);
        final Type assigneeType = assignee.getTargetType(environment);
        // Must capture variables subject to assignment conversion
        if (value instanceof VariableAccess) {
            valueType = valueType.capture();
        }
        // Check validity of the assignment
        if ((simpleAssign || !valueType.isNumeric() || !assigneeType.isNumeric()) && !valueType.convertibleTo(assigneeType)) {
            if (value instanceof IntLiteral) {
                final IntLiteral intLiteral = (IntLiteral) value;
                if (!TypeUtil.coerceToPrimitive(assignee, assigneeType).canNarrowFrom(intLiteral.asInt())) {
                    throw new EvaluatorException("Cannot narrow " + intLiteral + " to " + assigneeType.getName(), intLiteral);
                }
            } else {
                throw new EvaluatorException("Cannot convert " + valueType.getName() + " to " + assigneeType.getName(), value);
            }
        }
        return type = assigneeType.capture();
    }

    @Override
    public Value getValue(Environment environment) {
        final Value result = value.getValue(environment);
        assignee.setValue(environment, simpleAssign ? result : type.getKind().convert(result));
        return result;
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "Assignment(" + assignee + " = " + value + ")";
    }
} public class PostIncrement implements Expression, Statement {
    private static final IntLiteral ONE = new IntLiteral("1", 0) {
        @Override
        public int asInt() {
            return 1;
        }
    };
    protected final Reference inner;
    protected final Arithmetic increment;
    protected final Symbol operator;
    private int start;
    private int end;
    protected Type type = null;

    public PostIncrement(Reference inner, Symbol operator) {
        this(inner, operator, inner.getStart(), operator.getEnd());
    }

    public PostIncrement(Reference inner, Symbol operator, int start, int end) {
        this.inner = inner;
        this.operator = operator;
        increment = new Arithmetic(inner, ONE, operator.getCompoundAssignOperator());
        this.start = start;
        this.end = end;
    }

    @Override
    public void execute(Environment environment) {
        try {
            getType(environment);
            getValue(environment);
        } catch (EvaluatorException exception) {
            throw exception;
        } catch (Exception exception) {
            throw new EvaluatorException(exception, this);
        }
    }

    @Override
    public Type getType(Environment environment) {
        if (type != null) {
            return type;
        }
        increment.getType(environment);
        final Type innerType = increment.getLeftType();
        if (!innerType.isNumeric()) {
            throw new EvaluatorException("Not a numeric type: " + innerType.getName(), inner);
        }
        return type = innerType;
    }

    @Override
    public Value getValue(Environment environment) {
        final Value value = inner.getValue(environment);
        final Value result = increment.getValue(environment);
        inner.setValue(environment, type.getKind().convert(result));
        return value;
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "PostIncrement(" + inner.toString() + operator.toString() + ")";
    }
} public class PreIncrement extends PostIncrement {
    public PreIncrement(Reference inner, Symbol operator) {
        super(inner, operator, operator.getStart(), inner.getEnd());
    }

    @Override
    public Value getValue(Environment environment) {
        final Value result = increment.getValue(environment);
        final Value value = type.getKind().convert(result);
        inner.setValue(environment, value);
        return value;
    }

    @Override
    public String toString() {
        return "PreIncrement(" + operator.toString() + inner.toString() + ")";
    }
} public class AmbiguousCall implements Expression, Statement {
    private final List<Identifier> name;
    private final List<TypeArgumentName> typeArguments;
    private final List<Expression> arguments;
    private int start;
    private int end;
    private Type type = null;
    private MethodCall call = null;

    public AmbiguousCall(List<Identifier> name, List<Expression> arguments, int end) {
        this(name, Collections.<TypeArgumentName>emptyList(), arguments, name.get(name.size() - 1).getStart(), end);
    }

    public AmbiguousCall(List<Identifier> name, List<TypeArgumentName> typeArguments, List<Expression> arguments, int start, int end) {
        this.name = name;
        this.typeArguments = typeArguments;
        this.arguments = arguments;
        this.start = start;
        this.end = end;
    }

    @Override
    public void execute(Environment environment) {
        try {
            getType(environment);
            getValue(environment);
        } catch (EvaluatorException exception) {
            throw exception;
        } catch (Exception exception) {
            throw new EvaluatorException(exception, this);
        }
    }

    @Override
    public Type getType(Environment environment) {
        if (type == null) {
            final int lastIndex = name.size() - 1;
            final Expression resolved = AmbiguousReference.disambiguate(environment, name.subList(0, lastIndex));
            call = new MethodCall(resolved, name.get(lastIndex), typeArguments, arguments, start, end);
            type = call.getType(environment);
        }
        return type;
    }

    @Override
    public Value getValue(Environment environment) {
        return call.getValue(environment);
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        final int lastNameIndex = name.size() - 1;
        return "MethodCall(" + (lastNameIndex == 0 ? "" : StringUtil.toString(name.subList(0, lastNameIndex), ".") + '.')
                + (!typeArguments.isEmpty() ? '<' + StringUtil.toString(typeArguments, ", ") + '>' : "")
                + name.get(lastNameIndex) + "(" + StringUtil.toString(arguments, ", ") + "))";
    }
} public class ConstructorCall implements Statement, Expression {
    private final ClassTypeName typeName;
    private final List<TypeArgumentName> typeArguments;
    private final boolean diamondOperator;
    private final List<Expression> arguments;
    private int start;
    private int end;
    private Callable callable = null;

    public ConstructorCall(ClassTypeName typeName, boolean diamondOperator, List<Expression> arguments, int start, int end) {
        this(typeName, Collections.<TypeArgumentName>emptyList(), diamondOperator, arguments, start, end);
    }

    public ConstructorCall(ClassTypeName typeName, List<TypeArgumentName> typeArguments, boolean diamondOperator, List<Expression> arguments, int start, int end) {
        if (diamondOperator && typeName.hasArguments()) {
            throw new IllegalArgumentException("Cannot use the diamond operator and have class type arguments at the same time");
        }
        this.typeName = typeName;
        this.typeArguments = typeArguments;
        this.diamondOperator = diamondOperator;
        this.arguments = arguments;
        this.start = start;
        this.end = end;
    }

    @Override
    public void execute(Environment environment) {
        try {
            getType(environment);
            getValue(environment);
        } catch (EvaluatorException exception) {
            throw exception;
        } catch (Exception exception) {
            throw new EvaluatorException(exception, this);
        }
    }

    @Override
    public Type getType(Environment environment) {
        if (callable != null) {
            return callable.getReturnType();
        }
        // Check the type to be instantiated
        final LiteralReferenceType type = checkInstantiationType(environment);
        // Check type arguments
        final TypeArgument[] typeArguments = checkTypeArguments(environment, this.typeArguments, diamondOperator);
        // Check argument types
        final Type[] argumentTypes = MethodCall.checkArguments(environment, arguments);
        // Get the constructor to call
        try {
            callable = type.getConstructor(typeArguments, argumentTypes);
        } catch (UnsupportedOperationException exception) {
            throw new EvaluatorException(exception.getMessage(), this);
        }
        return callable.getReturnType();
    }

    private LiteralReferenceType checkInstantiationType(Environment environment) {
        final LiteralReferenceType type = typeName.getType(environment);
        // The class should be top level or static, which means it is not a inner class
        if (!type.isInnerClassOf(null)) {
            throw new EvaluatorException("Cannot instantiate the inner class " + type + " outside its enclosing class", typeName);
        }
        // Now check that we can actually instantiate the type
        if (type.isEnum()) {
            throw new EvaluatorException("Cannot instantiate the enum class " + type, typeName);
        }
        if (type.isAbstract()) {
            throw new EvaluatorException("Cannot instantiate the abstract class " + type, typeName);
        }
        if (!type.isPublic()) {
            throw new EvaluatorException("Cannot access class " + type, typeName);
        }
        if (type instanceof ParametrizedType) {
            // Check that the diamond operator is not being used when type arguments are already given
            if (diamondOperator) {
                throw new EvaluatorException("Cannot use the diamond operator when type arguments are provided", typeName);
            }
            // Check that parametrized types don't have wildcards
            final List<TypeArgument> arguments = ((ParametrizedType) type).getArguments();
            for (int i = 0; i < arguments.size(); i++) {
                if (arguments.get(i) instanceof WildcardType) {
                    throw new EvaluatorException("Cannot use wildcards as type arguments in constructor calls", typeName.getArgument(i));
                }
            }
        } else if (diamondOperator && !type.isRaw()) {
            // Check that the diamond operator is being used on a generic type
            throw new EvaluatorException("Cannot use the diamond operator on the non-generic type " + type, typeName);
        }
        return type;
    }

    public static TypeArgument[] checkTypeArguments(Environment environment, List<TypeArgumentName> typeArgumentNames, boolean diamondOperator) {
        final int typeArgumentCount = typeArgumentNames.size();
        if (typeArgumentCount > 0 && diamondOperator) {
            throw new EvaluatorException("Cannot use constructor type arguments with the diamond operator",
                    typeArgumentNames.get(0).getStart(), typeArgumentNames.get(typeArgumentCount - 1).getEnd());
        }
        final TypeArgument[] typeArguments = new TypeArgument[typeArgumentCount];
        for (int i = 0; i < typeArgumentCount; i++) {
            final TypeArgument typeArgument = typeArgumentNames.get(i).getType(environment);
            if (typeArgument instanceof WildcardType) {
                throw new EvaluatorException("Cannot use wildcards as type arguments in constructor calls", typeArgumentNames.get(i));
            }
            typeArguments[i] = typeArgument;
        }
        return typeArguments;
    }

    @Override
    public Value getValue(Environment environment) {
        final int size = arguments.size();
        final Value[] values = new Value[size];
        for (int i = 0; i < size; i++) {
            values[i] = arguments.get(i).getValue(environment);
        }
        try {
            return callable.call(null, values);
        } catch (Exception exception) {
            throw new EvaluatorException("Could not call constructor", exception, this);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "ConstructorCall(new " + (!typeArguments.isEmpty() ? '<' + StringUtil.toString(typeArguments, ", ") + '>' : "") +
                typeName + (diamondOperator ? "<>" : "") + "(" + StringUtil.toString(arguments, ", ") + ")" + ")";
    }
} public class MethodCall implements Expression, Statement {
    private final Expression object;
    private final Identifier method;
    private final List<TypeArgumentName> typeArguments;
    private final List<Expression> arguments;
    private int start;
    private int end;
    private Callable callable = null;

    public MethodCall(Expression object, Identifier method, List<Expression> arguments, int end) {
        this(object, method, Collections.<TypeArgumentName>emptyList(), arguments, method.getStart(), end);
    }

    public MethodCall(Expression object, Identifier method, List<TypeArgumentName> typeArguments, List<Expression> arguments, int start, int end) {
        this.object = object;
        this.method = method;
        this.typeArguments = typeArguments;
        this.arguments = arguments;
        this.start = start;
        this.end = end;
    }

    @Override
    public void execute(Environment environment) {
        try {
            getType(environment);
            getValue(environment);
        } catch (EvaluatorException exception) {
            throw exception;
        } catch (Exception exception) {
            throw new EvaluatorException(exception, this);
        }
    }

    @Override
    public Type getType(Environment environment) {
        if (callable == null) {
            final Type objectType = object.getType(environment);
            if (!(objectType instanceof ReferenceType)) {
                throw new EvaluatorException("Not a class type " + objectType.getName(), object);
            }
            // Check type arguments
            final int typeArgumentCount = typeArguments.size();
            final TypeArgument[] typeArguments = new TypeArgument[typeArgumentCount];
            for (int i = 0; i < typeArgumentCount; i++) {
                final TypeArgument typeArgument = this.typeArguments.get(i).getType(environment);
                if (typeArgument instanceof WildcardType) {
                    throw new EvaluatorException("Cannot use wildcards as type arguments in method calls", this.typeArguments.get(i));
                }
                typeArguments[i] = typeArgument;
            }
            // Check argument types
            final Type[] argumentTypes = checkArguments(environment, arguments);
            // Get method to call
            try {
                callable = ((ReferenceType) objectType).getMethod(method.getSource(), typeArguments, argumentTypes);
            } catch (UnsupportedOperationException exception) {
                throw new EvaluatorException(exception.getMessage(), this);
            }
            if (!callable.isStatic() && object instanceof AmbiguousReference.StaticAccess) {
                throw new EvaluatorException("Cannot access a non-static member directly from the type name", this);
            }
        }
        return callable.getReturnType();
    }

    public static Type[] checkArguments(Environment environment, List<Expression> arguments) {
        final int size = arguments.size();
        final Type[] argumentTypes = new Type[size];
        for (int i = 0; i < size; i++) {
            Type argumentType = arguments.get(i).getType(environment);
            // Must capture variables subject to invocation conversion
            if (arguments.get(i) instanceof VariableAccess) {
                argumentType = argumentType.capture();
            }
            argumentTypes[i] = argumentType;
        }
        return argumentTypes;
    }

    @Override
    public Value getValue(Environment environment) {
        final Value value = object.getValue(environment);
        final int size = arguments.size();
        final Value[] values = new Value[size];
        for (int i = 0; i < size; i++) {
            values[i] = arguments.get(i).getValue(environment);
        }
        try {
            return callable.call(value, values);
        } catch (Exception exception) {
            throw new EvaluatorException("Could not call method", exception, method);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "MethodCall(" + object + "." + (!typeArguments.isEmpty() ? '<' + StringUtil.toString(typeArguments, ", ") + '>' : "")
                + method + "(" + StringUtil.toString(arguments, ", ") + "))";
    }
} public class QualifiedConstructorCall implements Statement, Expression {
    private final Expression target;
    private final Identifier inner;
    private final List<TypeArgumentName> typeArguments;
    private final List<TypeArgumentName> classTypeArguments;
    private final boolean diamondOperator;
    private final List<Expression> arguments;
    private final int classTypeEnd;
    private int start;
    private int end;
    private Callable callable = null;

    public QualifiedConstructorCall(Expression target, Identifier inner, List<TypeArgumentName> typeArguments, List<TypeArgumentName> classTypeArguments, boolean diamondOperator,
                                    List<Expression> arguments, int classTypeEnd, int start, int end) {
        if (diamondOperator && !classTypeArguments.isEmpty()) {
            throw new IllegalArgumentException("Cannot use the diamond operator and have class type arguments at the same time");
        }
        this.target = target;
        this.inner = inner;
        this.typeArguments = typeArguments;
        this.classTypeArguments = classTypeArguments;
        this.diamondOperator = diamondOperator;
        this.arguments = arguments;
        this.classTypeEnd = classTypeEnd;
        this.start = start;
        this.end = end;
    }

    @Override
    public void execute(Environment environment) {
        try {
            getType(environment);
            getValue(environment);
        } catch (EvaluatorException exception) {
            throw exception;
        } catch (Exception exception) {
            throw new EvaluatorException(exception, this);
        }
    }

    @Override
    public Type getType(Environment environment) {
        if (callable != null) {
            return callable.getReturnType();
        }
        // Check the target type first
        final Type targetType = target.getType(environment);
        if (!(targetType instanceof ReferenceType)) {
            throw new EvaluatorException("Expected a reference type", target);
        }
        final ReferenceType enclosingType = (ReferenceType) targetType;
        // Now check the class type arguments
        final TypeArgument[] classTypeArguments = checkClassTypeArguments(environment);
        // Check the type to be instantiated
        final LiteralReferenceType type = checkInstantiationType(enclosingType, classTypeArguments);
        // Check type arguments
        final TypeArgument[] typeArguments = ConstructorCall.checkTypeArguments(environment, this.typeArguments, diamondOperator);
        // Check argument types
        final Type[] argumentTypes = MethodCall.checkArguments(environment, arguments);
        // The enclosing class is the first argument of the constructor
        final Type[] innerArgumentTypes = new Type[argumentTypes.length + 1];
        System.arraycopy(argumentTypes, 0, innerArgumentTypes, 1, argumentTypes.length);
        innerArgumentTypes[0] = enclosingType;
        // Get the constructor to call
        try {
            callable = type.getConstructor(typeArguments, innerArgumentTypes);
        } catch (UnsupportedOperationException exception) {
            throw new EvaluatorException(exception.getMessage(), this);
        }
        return callable.getReturnType();
    }

    private TypeArgument[] checkClassTypeArguments(Environment environment) {
        final TypeArgument[] typeArguments = new TypeArgument[classTypeArguments.size()];
        for (int i = 0; i < classTypeArguments.size(); i++) {
            final TypeArgumentName typeArgumentName = classTypeArguments.get(i);
            final TypeArgument typeArgument = typeArgumentName.getType(environment);
            if (typeArgument instanceof WildcardType) {
                throw new EvaluatorException("Cannot use wildcards as type arguments in constructor calls", typeArgumentName);
            }
            typeArguments[i] = typeArgument;
        }
        return typeArguments;
    }

    private LiteralReferenceType checkInstantiationType(ReferenceType enclosingType, TypeArgument[] classTypeArguments) {
        // Check that the diamond operator is not being used when type arguments are already given
        if (!typeArguments.isEmpty() && diamondOperator) {
            throw new EvaluatorException("Cannot use the diamond operator when type arguments are provided",
                    typeArguments.get(0).getStart(), typeArguments.get(typeArguments.size() - 1).getEnd());
        }
        // Go looking for an inner class of the enclosing type
        final LiteralReferenceType type;
        try {
            type = enclosingType.getInnerClass(inner.getSource(), classTypeArguments);
        } catch (UnsupportedOperationException exception) {
            throw new EvaluatorException(exception.getMessage(), this);
        }
        // Now check that we can actually instantiate the type
        if (type.isEnum()) {
            throw new EvaluatorException("Cannot instantiate the enum class " + type, inner.getStart(), classTypeEnd);
        }
        if (type.isAbstract()) {
            throw new EvaluatorException("Cannot instantiate the abstract class " + type, inner.getStart(), classTypeEnd);
        }
        if (!type.isPublic()) {
            throw new EvaluatorException("Cannot access class " + type, inner.getStart(), classTypeEnd);
        }
        if (diamondOperator && !type.isRaw()) {
            // Check that the diamond operator is being used on a generic type
            throw new EvaluatorException("Cannot use the diamond operator on the non-generic type " + type, inner.getStart(), classTypeEnd);
        }
        return type;
    }

    @Override
    public Value getValue(Environment environment) {
        final Value enclosing = target.getValue(environment);
        final int size = arguments.size();
        final Value[] values = new Value[size + 1];
        values[0] = enclosing;
        for (int i = 0; i < size; i++) {
            values[i + 1] = arguments.get(i).getValue(environment);
        }
        try {
            return callable.call(null, values);
        } catch (Exception exception) {
            throw new EvaluatorException("Could not call constructor", exception, this);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "QualifiedConstructorCall(" + target + ".new " + toString(typeArguments) +
                inner + (diamondOperator ? "<>" : toString(classTypeArguments)) + "(" + StringUtil.toString(arguments, ", ") + ")" + ")";
    }

    private static String toString(List<TypeArgumentName> typeArguments) {
        return !typeArguments.isEmpty() ? '<' + StringUtil.toString(typeArguments, ", ") + '>' : "";
    }
} public class Comparison implements Expression {
    private final Expression left;
    private final Expression right;
    private final Symbol operator;
    private int start;
    private int end;
    private Type type = null;
    private Type widenType = null;

    public Comparison(Expression left, Expression right, Symbol operator) {
        this.left = left;
        this.right = right;
        this.operator = operator;
        this.start = left.getStart();
        this.end = right.getEnd();
    }

    @Override
    public Type getType(Environment environment) {
        if (type == null) {
            final PrimitiveType leftType = TypeUtil.coerceToPrimitive(environment, left);
            final PrimitiveType rightType = TypeUtil.coerceToPrimitive(environment, right);
            if (!leftType.isNumeric()) {
                throw new EvaluatorException("Not a numeric type: " + leftType.getName(), left);
            }
            if (!rightType.isNumeric()) {
                throw new EvaluatorException("Not a numeric type: " + rightType.getName(), right);
            }
            widenType = leftType.binaryWiden(rightType);
            type = PrimitiveType.THE_BOOLEAN;
        }
        return type;
    }

    @Override
    public Value getValue(Environment environment) {
        final Value leftValue = left.getValue(environment);
        final Value rightValue = right.getValue(environment);
        final ValueKind widenKind = widenType.getKind();
        switch (operator.getID()) {
            case SYMBOL_LESSER:
                return doLesserThan(leftValue, rightValue, widenKind);
            case SYMBOL_LESSER_OR_EQUAL:
                return doLesserOrEqualTo(leftValue, rightValue, widenKind);
            case SYMBOL_GREATER:
                return doGreaterThan(leftValue, rightValue, widenKind);
            case SYMBOL_GREATER_OR_EQUAL:
                return doGreaterOrEqualTo(leftValue, rightValue, widenKind);
            default:
                throw new EvaluatorException("Invalid operator for comparison: " + operator, operator);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    private Value doLesserThan(Value leftValue, Value rightValue, ValueKind widenKind) {
        switch (widenKind) {
            case INT:
                return BooleanValue.of(leftValue.asInt() < rightValue.asInt());
            case LONG:
                return BooleanValue.of(leftValue.asLong() < rightValue.asLong());
            case FLOAT:
                return BooleanValue.of(leftValue.asFloat() < rightValue.asFloat());
            case DOUBLE:
                return BooleanValue.of(leftValue.asDouble() < rightValue.asDouble());
            default:
                throw new EvaluatorException("Invalid type for less than: " + widenKind, this);
        }
    }

    private Value doLesserOrEqualTo(Value leftValue, Value rightValue, ValueKind widenKind) {
        switch (widenKind) {
            case INT:
                return BooleanValue.of(leftValue.asInt() <= rightValue.asInt());
            case LONG:
                return BooleanValue.of(leftValue.asLong() <= rightValue.asLong());
            case FLOAT:
                return BooleanValue.of(leftValue.asFloat() <= rightValue.asFloat());
            case DOUBLE:
                return BooleanValue.of(leftValue.asDouble() <= rightValue.asDouble());
            default:
                throw new EvaluatorException("Invalid type for lesser or equal to: " + widenKind, this);
        }
    }

    private Value doGreaterThan(Value leftValue, Value rightValue, ValueKind widenKind) {
        switch (widenKind) {
            case INT:
                return BooleanValue.of(leftValue.asInt() > rightValue.asInt());
            case LONG:
                return BooleanValue.of(leftValue.asLong() > rightValue.asLong());
            case FLOAT:
                return BooleanValue.of(leftValue.asFloat() > rightValue.asFloat());
            case DOUBLE:
                return BooleanValue.of(leftValue.asDouble() > rightValue.asDouble());
            default:
                throw new EvaluatorException("Invalid type for greater than: " + widenKind, this);
        }
    }

    private Value doGreaterOrEqualTo(Value leftValue, Value rightValue, ValueKind widenKind) {
        switch (widenKind) {
            case INT:
                return BooleanValue.of(leftValue.asInt() >= rightValue.asInt());
            case LONG:
                return BooleanValue.of(leftValue.asLong() >= rightValue.asLong());
            case FLOAT:
                return BooleanValue.of(leftValue.asFloat() >= rightValue.asFloat());
            case DOUBLE:
                return BooleanValue.of(leftValue.asDouble() >= rightValue.asDouble());
            default:
                throw new EvaluatorException("Invalid type for greater or equal to: " + widenKind, this);
        }
    }

    @Override
    public String toString() {
        return "Comparison(" + left + " " + operator + " " + right + ")";
    }
} public class Equal implements Expression {
    private final Expression left;
    private final Expression right;
    private final Symbol operator;
    private int start;
    private int end;
    private ValueKind widenKind = null;

    public Equal(Expression left, Expression right, Symbol operator) {
        this.left = left;
        this.right = right;
        this.operator = operator;
        this.start = left.getStart();
        this.end = right.getEnd();
    }

    @Override
    public Type getType(Environment environment) {
        if (widenKind == null) {
            final Type leftType = left.getType(environment);
            final Type rightType = right.getType(environment);
            if (leftType.isReference() && rightType.isReference()) {
                widenKind = ValueKind.OBJECT;
                return PrimitiveType.THE_BOOLEAN;
            }
            final PrimitiveType primitiveLeftType = TypeUtil.coerceToPrimitive(left, leftType);
            final PrimitiveType primitiveRightType = TypeUtil.coerceToPrimitive(right, rightType);
            if (primitiveLeftType.isBoolean() && primitiveRightType.isBoolean()) {
                widenKind = ValueKind.BOOLEAN;
                return PrimitiveType.THE_BOOLEAN;
            }
            if (primitiveLeftType.isNumeric() && primitiveRightType.isNumeric()) {
                widenKind = primitiveLeftType.binaryWiden(primitiveRightType).getKind();
                return PrimitiveType.THE_BOOLEAN;
            }
            throw new EvaluatorException("Invalid types for equality operation: " + leftType.getName() + " and " + rightType.getName(), this);
        }
        return PrimitiveType.THE_BOOLEAN;
    }

    @Override
    public Value getValue(Environment environment) {
        final Value leftValue = left.getValue(environment);
        final Value rightValue = right.getValue(environment);
        switch (operator.getID()) {
            case SYMBOL_EQUAL:
                return doEqual(leftValue, rightValue, widenKind);
            case SYMBOL_NOT_EQUAL:
                return doNotEqual(leftValue, rightValue, widenKind);
            default:
                throw new EvaluatorException("Invalid operator for equal: " + operator, operator);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    private Value doEqual(Value leftValue, Value rightValue, ValueKind widenKind) {
        switch (widenKind) {
            case BOOLEAN:
                return BooleanValue.of(leftValue.asBoolean() == rightValue.asBoolean());
            case INT:
                return BooleanValue.of(leftValue.asInt() == rightValue.asInt());
            case LONG:
                return BooleanValue.of(leftValue.asLong() == rightValue.asLong());
            case FLOAT:
                return BooleanValue.of(leftValue.asFloat() == rightValue.asFloat());
            case DOUBLE:
                return BooleanValue.of(leftValue.asDouble() == rightValue.asDouble());
            case OBJECT:
                return BooleanValue.of(leftValue.asObject() == rightValue.asObject());
            default:
                throw new EvaluatorException("Invalid type for equal: " + widenKind, this);
        }
    }

    private Value doNotEqual(Value leftValue, Value rightValue, ValueKind widenKind) {
        switch (widenKind) {
            case BOOLEAN:
                return BooleanValue.of(leftValue.asBoolean() != rightValue.asBoolean());
            case INT:
                return BooleanValue.of(leftValue.asInt() != rightValue.asInt());
            case LONG:
                return BooleanValue.of(leftValue.asLong() != rightValue.asLong());
            case FLOAT:
                return BooleanValue.of(leftValue.asFloat() != rightValue.asFloat());
            case DOUBLE:
                return BooleanValue.of(leftValue.asDouble() != rightValue.asDouble());
            case OBJECT:
                return BooleanValue.of(leftValue.asObject() != rightValue.asObject());
            default:
                throw new EvaluatorException("Invalid type for not equal: " + widenKind, this);
        }
    }

    @Override
    public String toString() {
        return "Equal(" + left + " " + operator + " " + right + ")";
    }
} public class TypeCheck implements Expression {
    private final Expression object;
    private final TypeName typeName;
    private int start;
    private int end;
    private Type type = null;
    private LiteralReferenceType checkType = null;

    public TypeCheck(Expression object, TypeName typeName) {
        this.object = object;
        this.typeName = typeName;
        this.start = object.getStart();
        this.end = typeName.getEnd();
    }

    @Override
    public Type getType(Environment environment) {
        if (type == null) {
            final Type objectType = object.getType(environment);
            final Type checkType = typeName.getType(environment);
            // Left operand must be a reference type
            if (!(objectType instanceof ReferenceType)) {
                throw new EvaluatorException("Cannot type check " + objectType.getName(), object);
            }
            // Right operand must also be a reference, and also reifiable
            if (!(checkType instanceof ReferenceType) || !checkType.isReifiable()) {
                throw new EvaluatorException("Cannot check type " + checkType.getName(), typeName);
            }
            // Cast from left operand to right operand must be valid
            if (!TypeUtil.isValidReferenceCast((ReferenceType) objectType, (ReferenceType) checkType)) {
                throw new EvaluatorException("Cannot check type " + checkType.getName(), typeName);
            }
            type = PrimitiveType.THE_BOOLEAN;
            this.checkType = (LiteralReferenceType) checkType;
        }
        return type;
    }

    @Override
    public Value getValue(Environment environment) {
        final Value value = object.getValue(environment);
        return BooleanValue.of(checkType.getTypeClass().isInstance(value.asObject()));
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "TypeCheck(" + object + " instanceof " + typeName + ")";
    }
} public class BitwiseLogic implements Expression {
    private final Expression left;
    private final Expression right;
    private final Symbol operator;
    private int start;
    private int end;
    private Type type = null;

    public BitwiseLogic(Expression left, Expression right, Symbol operator) {
        this.left = left;
        this.right = right;
        this.operator = operator;
        this.start = left.getStart();
        this.end = right.getEnd();
    }

    @Override
    public Type getType(Environment environment) {
        if (type == null) {
            final PrimitiveType leftType = TypeUtil.coerceToPrimitive(environment, left);
            final PrimitiveType rightType = TypeUtil.coerceToPrimitive(environment, right);
            if (leftType.isBoolean()) {
                if (rightType.isBoolean()) {
                    type = PrimitiveType.THE_BOOLEAN;
                } else {
                    throw new EvaluatorException("Not a boolean type: " + rightType.getName(), right);
                }
            } else if (leftType.isIntegral()) {
                if (rightType.isIntegral()) {
                    type = leftType.binaryWiden(rightType);
                } else {
                    throw new EvaluatorException("Not an integral type: " + rightType.getName(), right);
                }
            } else {
                throw new EvaluatorException("Not a boolean or integral type: " + leftType.getName(), left);
            }
        }
        return type;
    }

    @Override
    public Value getValue(Environment environment) {
        final Value leftValue = left.getValue(environment);
        final Value rightValue = right.getValue(environment);
        final ValueKind widenKind = type.getKind();
        switch (operator.getID()) {
            case SYMBOL_BITWISE_AND:
                return doBitwiseAND(leftValue, rightValue, widenKind);
            case SYMBOL_BITWISE_XOR:
                return doBitwiseXOR(leftValue, rightValue, widenKind);
            case SYMBOL_BITWISE_OR:
                return doBitwiseOR(leftValue, rightValue, widenKind);
            default:
                throw new EvaluatorException("Invalid operator for bitwise logic: " + operator, operator);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    private Value doBitwiseAND(Value leftValue, Value rightValue, ValueKind widenKind) {
        switch (widenKind) {
            case BOOLEAN:
                return BooleanValue.of(leftValue.asBoolean() & rightValue.asBoolean());
            case INT:
                return IntValue.of(leftValue.asInt() & rightValue.asInt());
            case LONG:
                return LongValue.of(leftValue.asLong() & rightValue.asLong());
            default:
                throw new EvaluatorException("Invalid type for bitwise AND: " + widenKind, this);
        }
    }

    private Value doBitwiseXOR(Value leftValue, Value rightValue, ValueKind widenKind) {
        switch (widenKind) {
            case BOOLEAN:
                return BooleanValue.of(leftValue.asBoolean() ^ rightValue.asBoolean());
            case INT:
                return IntValue.of(leftValue.asInt() ^ rightValue.asInt());
            case LONG:
                return LongValue.of(leftValue.asLong() ^ rightValue.asLong());
            default:
                throw new EvaluatorException("Invalid type for bitwise XOR: " + widenKind, this);
        }
    }

    private Value doBitwiseOR(Value leftValue, Value rightValue, ValueKind widenKind) {
        switch (widenKind) {
            case BOOLEAN:
                return BooleanValue.of(leftValue.asBoolean() | rightValue.asBoolean());
            case INT:
                return IntValue.of(leftValue.asInt() | rightValue.asInt());
            case LONG:
                return LongValue.of(leftValue.asLong() | rightValue.asLong());
            default:
                throw new EvaluatorException("Invalid type for bitwise OR: " + widenKind, this);
        }
    }

    @Override
    public String toString() {
        return "BitwiseLogic(" + left + " " + operator + " " + right + ")";
    }
} public class BitwiseNot implements Expression {
    private final Expression inner;
    private int start;
    private int end;
    private Type type = null;

    public BitwiseNot(Expression inner, int start) {
        this.inner = inner;
        this.start = start;
        end = inner.getEnd();
    }

    @Override
    public Type getType(Environment environment) {
        if (type == null) {
            final PrimitiveType innerType = TypeUtil.coerceToPrimitive(environment, inner);
            if (!innerType.isIntegral()) {
                throw new EvaluatorException("Not an integral type: " + innerType.getName(), inner);
            }
            type = innerType.unaryWiden();
        }
        return type;
    }

    @Override
    public Value getValue(Environment environment) {
        final Value innerValue = inner.getValue(environment);
        final ValueKind widenKind = type.getKind();
        switch (widenKind) {
            case INT:
                return IntValue.of(~innerValue.asInt());
            case LONG:
                return LongValue.of(~innerValue.asLong());
            default:
                throw new EvaluatorException("Invalid type for bitwise not: " + widenKind, this);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "BitwiseNot(~" + inner + ")";
    }
} public class BooleanLogic implements Expression {
    private final Expression left;
    private final Expression right;
    private final Symbol operator;
    private int start;
    private int end;
    private Type type = null;

    public BooleanLogic(Expression left, Expression right, Symbol operator) {
        this.left = left;
        this.right = right;
        this.operator = operator;
        this.start = left.getStart();
        this.end = right.getEnd();
    }

    @Override
    public Type getType(Environment environment) {
        if (type == null) {
            final PrimitiveType leftType = TypeUtil.coerceToPrimitive(environment, left);
            final PrimitiveType rightType = TypeUtil.coerceToPrimitive(environment, right);
            if (!leftType.isBoolean()) {
                throw new EvaluatorException("Not a boolean: " + leftType.getName(), left);
            }
            if (!rightType.isBoolean()) {
                throw new EvaluatorException("Not a boolean: " + rightType.getName(), right);
            }
            type = PrimitiveType.THE_BOOLEAN;
        }
        return type;
    }

    @Override
    public Value getValue(Environment environment) {
        switch (operator.getID()) {
            case SYMBOL_BOOLEAN_AND:
                return doBooleanAND(environment);
            case SYMBOL_BOOLEAN_OR:
                return doBooleanOR(environment);
            default:
                throw new EvaluatorException("Invalid operator for boolean logic: " + operator, operator);
        }
    }

    private Value doBooleanAND(Environment environment) {
        return BooleanValue.of(left.getValue(environment).asBoolean() && right.getValue(environment).asBoolean());
    }

    private Value doBooleanOR(Environment environment) {
        return BooleanValue.of(left.getValue(environment).asBoolean() || right.getValue(environment).asBoolean());
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "BooleanLogic(" + left + " " + operator + " " + right + ")";
    }
} public class BooleanNot implements Expression {
    private final Expression inner;
    private int start;
    private int end;
    private Type type = null;

    public BooleanNot(Expression inner, int start) {
        this.inner = inner;
        this.start = start;
        end = inner.getEnd();
    }

    @Override
    public Type getType(Environment environment) {
        if (type == null) {
            final PrimitiveType innerType = TypeUtil.coerceToPrimitive(environment, inner);
            if (!innerType.isBoolean()) {
                throw new EvaluatorException("Not a boolean: " + innerType.getName(), inner);
            }
            type = PrimitiveType.THE_BOOLEAN;
        }
        return type;
    }

    @Override
    public Value getValue(Environment environment) {
        final Value innerValue = inner.getValue(environment);
        return BooleanValue.of(!innerValue.asBoolean());
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "BooleanNot(!" + inner + ")";
    }
} public class AmbiguousReference implements Reference {
    private final List<Identifier> name;
    private int start;
    private int end;
    private Reference reference = null;

    public AmbiguousReference(List<Identifier> name) {
        this.name = name;
        start = name.get(0).getStart();
        end = name.get(name.size() - 1).getEnd();
    }

    private Reference getReference(Environment environment) {
        if (reference != null) {
            return reference;
        }
        final Expression resolved = disambiguate(environment, name);
        if (!(resolved instanceof Reference)) {
            throw new EvaluatorException("Not a reference", resolved);
        }
        return reference = (Reference) resolved;
    }

    @Override
    public Type getType(Environment environment) {
        return getReference(environment).getType(environment);
    }

    @Override
    public Type getTargetType(Environment environment) {
        return getReference(environment).getTargetType(environment);
    }

    @Override
    public Value getValue(Environment environment) {
        return getReference(environment).getValue(environment);
    }

    @Override
    public void setValue(Environment environment, Value value) {
        getReference(environment).setValue(environment, value);
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return StringUtil.toString(name, ".");
    }

    public static Expression disambiguate(Environment environment, List<Identifier> name) {
        Expression resolved;
        final List<Identifier> accesses;
        final Identifier identifier = name.get(0);
        // variable followed by one or more field accesses
        // class followed by one or more field accesses
        if (environment.hasVariable(identifier)) {
            resolved = new VariableAccess(identifier);
            accesses = name.subList(1, name.size());
        } else {
            Class<?> _class = environment.findClass(identifier);
            if (_class != null) {
                resolved = new StaticAccess(_class, identifier.getStart(), identifier.getEnd());
                accesses = name.subList(1, name.size());
            } else {
                accesses = new ArrayList<>(name);
                _class = ReflectionUtil.disambiguateClass(accesses);
                final int start = identifier.getStart();
                if (_class == null) {
                    throw new EvaluatorException("Unknown identifier " + StringUtil.toString(name, "."), start, name.get(name.size() - 1).getEnd());
                }
                resolved = new StaticAccess(_class, start, accesses.isEmpty() ? name.get(name.size() - 1).getEnd() : accesses.get(0).getStart());
            }
        }
        for (Identifier access : accesses) {
            resolved = new FieldAccess(resolved, access);
        }
        return resolved;
    }

    public static class StaticAccess implements Expression {
        private final Class<?> _class;
        private int start;
        private int end;
        private Type type = null;

        private StaticAccess(Class<?> _class, int start, int end) {
            this._class = _class;
            this.start = start;
            this.end = end;
        }

        @Override
        public Type getType(Environment environment) {
            if (type == null) {
                type = TypeCache.wrapClass(_class);
            }
            return type;
        }

        @Override
        public Value getValue(Environment environment) {
            return ObjectValue.of(null);
        }

        @Override
        public int getStart() {
            return start;
        }

        @Override
        public int getEnd() {
            return end;
        }

        @Override
        public void setStart(int start) {
            this.start = start;
        }

        @Override
        public void setEnd(int end) {
            this.end = end;
        }

        @Override
        public String toString() {
            return _class.getCanonicalName();
        }
    }
} public class FieldAccess implements Reference {
    private final Expression object;
    private final Identifier field;
    private int start;
    private int end;
    private ClassVariable classVariable = null;

    public FieldAccess(Expression object, Identifier field) {
        this.object = object;
        this.field = field;
        start = field.getStart();
        end = field.getEnd();
    }

    private ClassVariable getClassVariable(Environment environment) {
        if (classVariable != null) {
            return classVariable;
        }
        final Type objectType = object.getType(environment);
        final String name = field.getSource();
        if (!(objectType instanceof ReferenceType)) {
            throw new EvaluatorException("Not a class type " + objectType.getName(), object);
        }
        try {
            classVariable = ((ReferenceType) objectType).getField(name);
        } catch (UnsupportedOperationException exception) {
            throw new EvaluatorException(exception.getMessage(), this);
        }
        if (!classVariable.isStatic() && object instanceof AmbiguousReference.StaticAccess) {
            throw new EvaluatorException("Cannot access a non-static member directly from the type name", field);
        }
        return classVariable;
    }

    @Override
    public Type getType(Environment environment) {
        return getClassVariable(environment).getType();
    }

    @Override
    public Type getTargetType(Environment environment) {
        return getClassVariable(environment).getTargetType();
    }

    @Override
    public Value getValue(Environment environment) {
        final Value target = object.getValue(environment);
        try {
            return getClassVariable(environment).getValue(target);
        } catch (Exception exception) {
            throw new EvaluatorException("Could not access field", exception, field);
        }
    }

    @Override
    public void setValue(Environment environment, Value value) {
        final Value target = object.getValue(environment);
        try {
            getClassVariable(environment).setValue(target, value);
        } catch (Exception exception) {
            throw new EvaluatorException("Could not access field", exception, field);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "FieldAccess(" + object + "." + field + ")";
    }
} public class IndexAccess implements Reference {
    private final Expression object;
    private final Expression index;
    private int start;
    private int end;
    private Type type = null;

    public IndexAccess(Expression object, Expression index, int end) {
        this.object = object;
        this.index = index;
        start = object.getStart();
        this.end = end;
    }

    @Override
    public Type getType(Environment environment) {
        return getTargetType(environment).capture();
    }

    @Override
    public Type getTargetType(Environment environment) {
        if (type != null) {
            return type;
        }
        final Type objectType = object.getType(environment);
        final Type indexType = index.getType(environment);
        if (!objectType.isArray()) {
            throw new EvaluatorException("Not an array: " + objectType.getName(), object);
        }
        if (!indexType.convertibleTo(PrimitiveType.THE_INT)) {
            throw new EvaluatorException("Cannot convert " + indexType.getName() + " to int", index);
        }
        return type = ((ReferenceType) objectType).getComponentType();
    }

    @Override
    public Value getValue(Environment environment) {
        final Value objectValue = object.getValue(environment);
        final Value indexValue = index.getValue(environment);
        try {
            return type.getKind().wrap(Array.get(objectValue.asObject(), indexValue.asInt()));
        } catch (Exception exception) {
            throw new EvaluatorException("Could not get array item", exception, this);
        }
    }

    @Override
    public void setValue(Environment environment, Value value) {
        final Value objectValue = object.getValue(environment);
        final Value indexValue = index.getValue(environment);
        try {
            Array.set(objectValue.asObject(), indexValue.asInt(), value.asObject());
        } catch (Exception exception) {
            throw new EvaluatorException("Could not set array item", exception, this);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "IndexAccess(" + object + "[" + index + "])";
    }
} public interface Reference extends Expression {
    Type getTargetType(Environment environment);

    void setValue(Environment environment, Value value);

    String toString();
} public class VariableAccess implements Reference {
    private final Identifier name;
    private int start;
    private int end;
    private Type type = null;

    public VariableAccess(Identifier name) {
        this.name = name;
        start = name.getStart();
        end = name.getEnd();
    }

    @Override
    public Type getType(Environment environment) {
        return getTargetType(environment);
    }

    @Override
    public Type getTargetType(Environment environment) {
        if (type != null) {
            return type;
        }
        try {
            type = environment.getVariableType(name);
        } catch (UnsupportedOperationException exception) {
            throw new EvaluatorException(exception.getMessage(), name);
        }
        return type;
    }

    @Override
    public Value getValue(Environment environment) {
        try {
            return environment.getVariable(name);
        } catch (UnsupportedOperationException exception) {
            throw new EvaluatorException(exception.getMessage(), name);
        }
    }

    @Override
    public void setValue(Environment environment, Value value) {
        try {
            environment.setVariable(name, value);
        } catch (UnsupportedOperationException exception) {
            throw new EvaluatorException(exception.getMessage(), name);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setStart(int start) {
        this.start = start;
    }

    @Override
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return name.toString();
    }
} public class ArrayTypeName implements TypeName {
    private final TypeName componentTypeName;
    private final int dimensions;
    private final int end;
    private LiteralType componentType = null;
    private LiteralType type = null;

    public ArrayTypeName(TypeName componentTypeName, int dimensions, int end) {
        this.componentTypeName = componentTypeName;
        this.dimensions = dimensions;
        this.end = end;
    }

    @Override
    public LiteralType getType(Environment environment) {
        if (type == null) {
            final LiteralType componentType = componentTypeName.getType(environment);
            final LiteralReferenceType arrayType = componentType.asArray(dimensions);
            if (arrayType == null) {
                throw new EvaluatorException("Class not found: array of " + componentType.getName() + " with dimensions " + dimensions, this);
            }
            this.componentType = componentType;
            type = arrayType;
        }
        return type;
    }

    public LiteralType getComponentType() {
        return componentType;
    }

    public int getDimensions() {
        return dimensions;
    }

    @Override
    public int getStart() {
        return componentTypeName.getStart();
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public String toString() {
        return componentTypeName.toString() + StringUtil.repeat("[]", dimensions);
    }
} public class ClassTypeName implements TypeName {
    private final List<Identifier> name;
    private final List<TypeArgumentName> arguments;
    private final int end;
    private LiteralReferenceType type = null;

    public ClassTypeName(List<Identifier> name) {
        this(name, Collections.<TypeArgumentName>emptyList(), name.get(name.size() - 1).getEnd());
    }

    public ClassTypeName(List<Identifier> name, List<TypeArgumentName> arguments, int end) {
        this.name = name;
        this.arguments = arguments;
        this.end = end;
    }

    @Override
    public LiteralReferenceType getType(Environment environment) {
        if (type == null) {
            final Class<?> _class = findTypeClass(environment);
            final List<TypeArgument> typeArguments = checkArguments(environment);
            final ParametrizedType owner = getOwner(environment);
            if (!typeArguments.isEmpty() || owner != null) {
                type = ParametrizedType.of(owner, _class, typeArguments);
            } else {
                type = LiteralReferenceType.of(_class);
            }
        }
        return type;
    }

    private Class<?> findTypeClass(Environment environment) {
        // look for an imported class with possible inner classes
        Class<?> _class = environment.findClass(name.get(0));
        if (_class != null) {
            final int size = name.size();
            for (int i = 1; i < size; i++) {
                _class = ReflectionUtil.lookupNestedClass(_class, name.get(i).getSource());
                if (_class == null) {
                    break;
                }
            }
        }
        // else try for a full class name
        if (_class == null) {
            _class = ReflectionUtil.findClass(name);
        }
        // we tried everything, fail
        if (_class == null) {
            throw new EvaluatorException("Class not found: " + toString(), getStart(), getEnd());
        }
        return _class;
    }

    private List<TypeArgument> checkArguments(Environment environment) {
        if (arguments.isEmpty()) {
            return Collections.emptyList();
        }
        final List<TypeArgument> typeArguments = new ArrayList<>(arguments.size());
        for (TypeArgumentName parameter : arguments) {
            typeArguments.add(parameter.getType(environment));
        }
        return typeArguments;
    }

    protected ParametrizedType getOwner(Environment environment) {
        return null;
    }

    public boolean hasSimpleName() {
        return name.size() == 1;
    }

    public boolean hasArguments() {
        return arguments.size() > 0;
    }

    public TypeArgumentName getArgument(int i) {
        return arguments.get(i);
    }

    @Override
    public int getStart() {
        return name.get(0).getIndex();
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public String toString() {
        return StringUtil.toString(name, ".") + (!arguments.isEmpty() ? "<" + StringUtil.toString(arguments, ", ") + ">" : "");
    }
} public class InnerClassTypeName extends ClassTypeName {
    private final ClassTypeName outerName;
    private ParametrizedType outerType;
    private boolean typeCached = false;

    public InnerClassTypeName(ClassTypeName outerName, List<Identifier> name) {
        this(outerName, name, Collections.<TypeArgumentName>emptyList(), name.get(name.size() - 1).getEnd());
    }

    public InnerClassTypeName(ClassTypeName outerName, List<Identifier> name, List<TypeArgumentName> arguments, int end) {
        super(name, arguments, end);
        this.outerName = outerName;
    }

    @Override
    protected ParametrizedType getOwner(Environment environment) {
        if (!typeCached) {
            final LiteralReferenceType outerType = outerName.getType(environment);
            this.outerType = outerType instanceof ParametrizedType ? (ParametrizedType) outerType : null;
            typeCached = true;
        }
        return outerType;
    }

    @Override
    public int getStart() {
        return outerName.getStart();
    }

    @Override
    public String toString() {
        return outerName.toString() + '.' + super.toString();
    }
} public class PrimitiveTypeName implements TypeName {
    private final Keyword name;
    private LiteralType type = null;

    public PrimitiveTypeName(Keyword name) {
        this.name = name;
    }

    @Override
    public LiteralType getType(Environment environment) {
        if (type == null) {
            final Class<?> _class;
            switch (name.getID()) {
                case KEYWORD_BOOLEAN:
                    _class = boolean.class;
                    break;
                case KEYWORD_BYTE:
                    _class = byte.class;
                    break;
                case KEYWORD_SHORT:
                    _class = short.class;
                    break;
                case KEYWORD_CHAR:
                    _class = char.class;
                    break;
                case KEYWORD_INT:
                    _class = int.class;
                    break;
                case KEYWORD_LONG:
                    _class = long.class;
                    break;
                case KEYWORD_FLOAT:
                    _class = float.class;
                    break;
                case KEYWORD_DOUBLE:
                    _class = double.class;
                    break;
                default:
                    throw new EvaluatorException("Not a primitive type: " + name, getStart(), getEnd());
            }
            type = PrimitiveType.of(_class);
        }
        return type;
    }

    @Override
    public int getStart() {
        return name.getStart();
    }

    @Override
    public int getEnd() {
        return name.getEnd();
    }

    @Override
    public String toString() {
        return name.toString();
    }
} public class TypeArgumentName implements SourceIndexed {
    private final TypeName bound;
    private final BoundKind kind;
    private final int start;
    private TypeArgument type = null;

    public TypeArgumentName(int index) {
        this(null, BoundKind.NONE, index);
    }

    public TypeArgumentName(TypeName bound, BoundKind kind, int start) {
        this.bound = bound;
        this.kind = kind;
        this.start = start;
    }

    public TypeArgument getType(Environment environment) {
        if (type == null) {
            if (kind == BoundKind.NONE) {
                type = WildcardType.THE_UNBOUNDED;
            } else {
                final LiteralType bound = this.bound.getType(environment);
                if (!(bound instanceof TypeArgument)) {
                    throw new EvaluatorException("Name must be a type argument", this);
                }
                switch (kind) {
                    case EXACT:
                        type = (TypeArgument) bound;
                        break;
                    case LOWER:
                        type = WildcardType.of(Collections.singleton((SingleReferenceType) bound), Collections.<SingleReferenceType>emptySet());
                        break;
                    case UPPER:
                        type = WildcardType.of(Collections.<SingleReferenceType>emptySet(), Collections.singleton((SingleReferenceType) bound));
                        break;
                }
            }
        }
        return type;
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return bound == null ? start : bound.getEnd();
    }

    @Override
    public String toString() {
        switch (kind) {
            case EXACT:
                return bound.toString();
            case NONE:
                return "?";
            case LOWER:
                return "? super " + bound.toString();
            case UPPER:
                return "? extends " + bound.toString();
            default:
                throw new UnsupportedOperationException(kind.name());
        }
    }

    public enum BoundKind {
        EXACT,
        NONE,
        LOWER,
        UPPER
    }
} public enum BoundKind {
    EXACT,
    NONE,
    LOWER,
    UPPER
} public interface TypeName extends SourceIndexed {
    LiteralType getType(Environment environment);

    @Override
    String toString();
} public class Declaration implements Statement {
    private final TypeName typeName;
    private final List<Variable> variables;
    private Map<Variable, LiteralType> declaredTypes = null;

    public Declaration(TypeName typeName, List<Variable> variables) {
        this.typeName = typeName;
        this.variables = variables;
    }

    @Override
    public void execute(Environment environment) {
        try {
            if (declaredTypes == null) {
                final LiteralType declarationType;
                try {
                    declarationType = typeName.getType(environment);
                } catch (UnsupportedOperationException exception) {
                    throw new EvaluatorException(exception.getMessage(), typeName);

                }
                // find the declared type of each variable, which can change by array dimensions
                final Map<Variable, LiteralType> declaredTypes = new HashMap<>();
                for (Variable variable : variables) {
                    int dimensions = variable.getDimensions();
                    if (dimensions == 0) {
                        // no extra dimensions, use the declaration type
                        declaredTypes.put(variable, declarationType);
                    } else {
                        // else add the dimensions and compute the new array type
                        final LiteralType componentType;
                        if (typeName instanceof ArrayTypeName) {
                            // ReflectionUtil.asArrayType doesn't support array types for component type, so get the base one
                            final ArrayTypeName arrayTypeName = (ArrayTypeName) typeName;
                            componentType = arrayTypeName.getComponentType();
                            dimensions += arrayTypeName.getDimensions();
                        } else {
                            componentType = declarationType;
                        }
                        final LiteralReferenceType declaredType = componentType.asArray(dimensions);
                        declaredTypes.put(variable, declaredType);
                    }
                }
                // validate that the variable value type can be converted to the declared type
                for (Entry<Variable, LiteralType> entry : declaredTypes.entrySet()) {
                    final Variable variable = entry.getKey();
                    final Type declaredType = entry.getValue();
                    Type valueType = variable.getType(environment, declaredType);
                    // Must capture variables subject to assignment conversion
                    if (variable.getValueExpression() instanceof VariableAccess) {
                        valueType = valueType.capture();
                    }
                    // Check validity of the assignment
                    if (!valueType.convertibleTo(declaredType)) {
                        if (variable.getValueExpression() instanceof IntLiteral) {
                            final IntLiteral intLiteral = (IntLiteral) variable.getValueExpression();
                            if (!TypeUtil.coerceToPrimitive(variable.getValueExpression(), declaredType).canNarrowFrom(intLiteral.asInt())) {
                                throw new EvaluatorException("Cannot narrow " + intLiteral + " to " + declaredType.getName(), intLiteral);
                            }
                        } else {
                            throw new EvaluatorException("Cannot convert " + valueType.getName() + " to " + declaredType.getName(), variable.getValueExpression());
                        }
                    }
                }
                this.declaredTypes = declaredTypes;
            }
            for (Entry<Variable, LiteralType> entry : declaredTypes.entrySet()) {
                final Variable variable = entry.getKey();
                final LiteralType declaredType = entry.getValue();
                final Identifier name = variable.getName();
                try {
                    environment.declareVariable(name, declaredType, variable.getValue(environment));
                } catch (UnsupportedOperationException exception) {
                    throw new EvaluatorException(exception.getMessage(), name);
                }
            }
        } catch (EvaluatorException exception) {
            throw exception;
        } catch (UnsupportedOperationException exception) {
            throw new EvaluatorException(exception.getMessage(), this);
        } catch (Exception exception) {
            throw new EvaluatorException(exception, this);
        }
    }

    @Override
    public int getStart() {
        return typeName.getStart();
    }

    @Override
    public int getEnd() {
        return variables.get(variables.size() - 1).getEnd();
    }

    @Override
    public String toString() {
        return "Declaration(" + typeName + " " + StringUtil.toString(variables, ", ") + ")";
    }

    public static class Variable {
        private final Identifier name;
        private final int dimensions;
        private final Expression value;
        private Type type = null;

        public Variable(Identifier name, int dimensions) {
            this(name, dimensions, null);
        }

        public Variable(Identifier name, int dimensions, Expression value) {
            this.name = name;
            this.dimensions = dimensions;
            this.value = value;
        }

        private Identifier getName() {
            return name;
        }

        private int getDimensions() {
            return dimensions;
        }

        private Expression getValueExpression() {
            return value;
        }

        private Value getValue(Environment environment) {
            return value == null ? null : value.getValue(environment);
        }

        private Type getType(Environment environment, Type declaredType) {
            if (type == null) {
                if (value == null) {
                    type = declaredType;
                } else if (value instanceof ArrayInitializer) {
                    ((ArrayInitializer) value).setType(environment, declaredType);
                    type = declaredType;
                } else {
                    type = value.getType(environment);
                }
            }
            return type;
        }

        private int getEnd() {
            return value == null ? name.getEnd() : value.getEnd();
        }

        @Override
        public String toString() {
            return name + StringUtil.repeat("[]", dimensions) + (value != null ? " = " + value : "");
        }
    }
} public static class Variable {
    private final Identifier name;
    private final int dimensions;
    private final Expression value;
    private Type type = null;

    public Variable(Identifier name, int dimensions) {
        this(name, dimensions, null);
    }

    public Variable(Identifier name, int dimensions, Expression value) {
        this.name = name;
        this.dimensions = dimensions;
        this.value = value;
    }

    private Identifier getName() {
        return name;
    }

    private int getDimensions() {
        return dimensions;
    }

    private Expression getValueExpression() {
        return value;
    }

    private Value getValue(Environment environment) {
        return value == null ? null : value.getValue(environment);
    }

    private Type getType(Environment environment, Type declaredType) {
        if (type == null) {
            if (value == null) {
                type = declaredType;
            } else if (value instanceof ArrayInitializer) {
                ((ArrayInitializer) value).setType(environment, declaredType);
                type = declaredType;
            } else {
                type = value.getType(environment);
            }
        }
        return type;
    }

    private int getEnd() {
        return value == null ? name.getEnd() : value.getEnd();
    }

    @Override
    public String toString() {
        return name + StringUtil.repeat("[]", dimensions) + (value != null ? " = " + value : "");
    }
} public class Empty implements Statement {
    private final int index;

    public Empty(int index) {
        this.index = index;
    }

    @Override
    public void execute(Environment environment) {
    }

    @Override
    public int getStart() {
        return index;
    }

    @Override
    public int getEnd() {
        return index;
    }

    @Override
    public String toString() {
        return "Empty()";
    }
} public class Import implements Statement {
    private final List<Identifier> name;
    private final boolean _package;
    private final int start;
    private final int end;

    public Import(List<Identifier> name, boolean _package, int start, int end) {
        this.name = name;
        this._package = _package;
        this.start = start;
        this.end = end;
    }

    @Override
    public void execute(Environment environment) {
        try {
            if (_package) {
                final Set<Class<?>> classes = ReflectionUtil.findClasses(name);
                if (classes.isEmpty()) {
                    throw new EvaluatorException("No classes in package, perhaps they were loaded by the bootstrap class loader?", this);
                }
                for (Class<?> _class : classes) {
                    environment.importClass(_class);
                }
            } else {
                final Class<?> _class = ReflectionUtil.findClass(name);
                if (_class == null) {
                    throw new EvaluatorException("Class not found: " + StringUtil.toString(name, "."), this);
                }
                environment.importClass(_class);
            }
        } catch (EvaluatorException exception) {
            throw exception;
        } catch (UnsupportedOperationException exception) {
            throw new EvaluatorException(exception.getMessage(), this);
        } catch (Exception exception) {
            throw new EvaluatorException(exception, this);
        }
    }

    @Override
    public int getStart() {
        return start;
    }

    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public String toString() {
        return "Import(" + StringUtil.toString(name, ".") + (_package ? ".*" : "") + ")";
    }
} public interface Statement extends SourceIndexed {
    void execute(Environment environment);

    String toString();
} public class ListNavigator<E> {
    private final List<E> list;
    private final Deque<Integer> positions = new ArrayDeque<>();
    private int topPosition = 0;
    private int fractionalPosition = 0;

    public ListNavigator(List<E> list) {
        this.list = list;
    }

    public int position() {
        return topPosition;
    }

    public int popPosition() {
        return topPosition = positions.pop();
    }

    public void pushPosition() {
        positions.push(topPosition);
    }

    public int peekPosition() {
        return topPosition = positions.peek();
    }

    public void discardPosition() {
        positions.pop();
    }

    public int advance() {
        return advance(1);
    }

    public int advance(int increment) {
        return topPosition += increment;
    }

    public int retreat() {
        return retreat(1);
    }

    public int retreat(int increment) {
        return advance(-increment);
    }

    public int remaining() {
        return list.size() - topPosition;
    }

    public boolean has() {
        return has(1);
    }

    public boolean has(int remaining) {
        return remaining() >= remaining;
    }

    public E get() {
        return get(0);
    }

    public E get(int forward) {
        return list.get(topPosition + forward);
    }

    public int fractional() {
        return fractionalPosition;
    }

    public int advanceFractional() {
        return advanceFractional(1);
    }

    public int advanceFractional(int increment) {
        return fractionalPosition += increment;
    }

    public void closeFractional() {
        fractionalPosition = 0;
        advance();
    }
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import ca.sapon.jici.SourceIndexed;
import ca.sapon.jici.lexer.Identifier;
import ca.sapon.jici.lexer.Keyword;
import ca.sapon.jici.lexer.Symbol;
import ca.sapon.jici.lexer.Token;
import ca.sapon.jici.lexer.TokenGroup;
import ca.sapon.jici.lexer.TokenID;
import ca.sapon.jici.lexer.literal.Literal;
import ca.sapon.jici.lexer.literal.number.IntLiteral;
import ca.sapon.jici.lexer.literal.number.LongLiteral;
import ca.sapon.jici.parser.expression.ArrayConstructor;
import ca.sapon.jici.parser.expression.ArrayConstructor.ArrayInitializer;
import ca.sapon.jici.parser.expression.Cast;
import ca.sapon.jici.parser.expression.ClassAccess;
import ca.sapon.jici.parser.expression.Conditional;
import ca.sapon.jici.parser.expression.Expression;
import ca.sapon.jici.parser.expression.Shift;
import ca.sapon.jici.parser.expression.arithmetic.Arithmetic;
import ca.sapon.jici.parser.expression.arithmetic.Sign;
import ca.sapon.jici.parser.expression.assignment.Assignment;
import ca.sapon.jici.parser.expression.assignment.PostIncrement;
import ca.sapon.jici.parser.expression.assignment.PreIncrement;
import ca.sapon.jici.parser.expression.call.AmbiguousCall;
import ca.sapon.jici.parser.expression.call.ConstructorCall;
import ca.sapon.jici.parser.expression.call.MethodCall;
import ca.sapon.jici.parser.expression.call.QualifiedConstructorCall;
import ca.sapon.jici.parser.expression.comparison.Comparison;
import ca.sapon.jici.parser.expression.comparison.Equal;
import ca.sapon.jici.parser.expression.comparison.TypeCheck;
import ca.sapon.jici.parser.expression.logic.BitwiseLogic;
import ca.sapon.jici.parser.expression.logic.BitwiseNot;
import ca.sapon.jici.parser.expression.logic.BooleanLogic;
import ca.sapon.jici.parser.expression.logic.BooleanNot;
import ca.sapon.jici.parser.expression.reference.AmbiguousReference;
import ca.sapon.jici.parser.expression.reference.FieldAccess;
import ca.sapon.jici.parser.expression.reference.IndexAccess;
import ca.sapon.jici.parser.expression.reference.Reference;
import ca.sapon.jici.parser.expression.reference.VariableAccess;
import ca.sapon.jici.parser.name.ArrayTypeName;
import ca.sapon.jici.parser.name.ClassTypeName;
import ca.sapon.jici.parser.name.InnerClassTypeName;
import ca.sapon.jici.parser.name.PrimitiveTypeName;
import ca.sapon.jici.parser.name.TypeArgumentName;
import ca.sapon.jici.parser.name.TypeArgumentName.BoundKind;
import ca.sapon.jici.parser.name.TypeName;
import ca.sapon.jici.parser.statement.Declaration;
import ca.sapon.jici.parser.statement.Declaration.Variable;
import ca.sapon.jici.parser.statement.Empty;
import ca.sapon.jici.parser.statement.Import;
import ca.sapon.jici.parser.statement.Statement;
import ca.sapon.jici.util.ListNavigator;
    }

    private static Expression parseShift(ListNavigator<Token> tokens, Expression left) {
        if (tokens.has()) {
            final Token token = tokens.get();
            if (token.getGroup() == TokenGroup.SHIFT_OPERATOR) {
                tokens.advance();
                final Expression right = parseAdd(tokens);
                final Shift add = new Shift(left, right, (Symbol) token);
                return parseShift(tokens, add);
            }
        }
        return left;
    }

    private static Expression parseAdd(ListNavigator<Token> tokens) {
        return parseAdd(tokens, parseMultiply(tokens));
    }

    private static Expression parseAdd(ListNavigator<Token> tokens, Expression left) {
        if (tokens.has()) {
            final Token token = tokens.get();
            if (token.getGroup() == TokenGroup.ADD_OPERATOR) {
                tokens.advance();
                final Expression right = parseMultiply(tokens);
                final Arithmetic arithmetic = new Arithmetic(left, right, (Symbol) token);
                return parseAdd(tokens, arithmetic);
            }
        }
        return left;
    }

    private static Expression parseMultiply(ListNavigator<Token> tokens) {
        return parseMultiply(tokens, parseUnary(tokens));
    }

    private static Expression parseMultiply(ListNavigator<Token> tokens, Expression left) {
        if (tokens.has()) {
            final Token token = tokens.get();
            if (token.getGroup() == TokenGroup.MULTIPLY_OPERATOR) {
                tokens.advance();
                final Expression right = parseUnary(tokens);
                final Arithmetic arithmetic = new Arithmetic(left, right, (Symbol) token);
                return parseMultiply(tokens, arithmetic);
            }
        }
        return left;
    }

    private static Expression parseUnary(ListNavigator<Token> tokens) {
        if (tokens.has()) {
            final Token token = tokens.get();
            switch (token.getID()) {
                case SYMBOL_INCREMENT:
                case SYMBOL_DECREMENT: {
                    tokens.advance();
                    final Expression inner = parseUnary(tokens);
                    if (inner instanceof Reference) {
                        return new PreIncrement((Reference) inner, (Symbol) token);
                    }
                    throw new ParseError("Expected a reference", inner);
                }
                case SYMBOL_PLUS:
                case SYMBOL_MINUS: {
                    tokens.advance();
                    final Expression inner = parseUnary(tokens);
                    if (inner instanceof IntLiteral) {
                        return ((IntLiteral) inner).withSign((Symbol) token);
                    }
                    if (inner instanceof LongLiteral) {
                        return ((LongLiteral) inner).withSign((Symbol) token);
                    }
                    return new Sign(inner, (Symbol) token);
                }
                case SYMBOL_BOOLEAN_NOT: {
                    final int start = tokens.get().getStart();
                    tokens.advance();
                    final Expression inner = parseUnary(tokens);
                    return new BooleanNot(inner, start);
                }
                case SYMBOL_BITWISE_NOT: {
                    final int start = tokens.get().getStart();
                    tokens.advance();
                    final Expression inner = parseUnary(tokens);
                    return new BitwiseNot(inner, start);
                }
                case SYMBOL_OPEN_PARENTHESIS: {
                    if (tokens.has(2)) {
                        tokens.pushPosition();
                        final int start = tokens.get().getStart();
                        tokens.advance();
                        try {
                            final TypeName type = parseTypeName(tokens);
                            if (tokens.has() && tokens.get().getID() == TokenID.SYMBOL_CLOSE_PARENTHESIS) {
                                tokens.advance();
                                final Expression inner = parseUnary(tokens);
                                tokens.discardPosition();
                                return new Cast(type, inner, start);
                            }
                        } catch (ParserException exception) {
                            // this is not a cast, but an access
                        }
                        tokens.popPosition();
                    }
                }
            }
            final Expression inner = parseAccess(tokens);
            return parseUnary(tokens, inner);
        }
        throw new ParseError("Expected a token", tokens);
    }

    private static Expression parseUnary(ListNavigator<Token> tokens, Expression inner) {
        if (tokens.has()) {
            final Token token = tokens.get();
            switch (token.getID()) {
                case SYMBOL_INCREMENT:
                case SYMBOL_DECREMENT: {
                    tokens.advance();
                    if (inner instanceof Reference) {
                        final PostIncrement outer = new PostIncrement((Reference) inner, (Symbol) token);