/** * Copyright (c) 2005-2017, KoLmafia development team * http://kolmafia.sourceforge.net/ * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * [1] Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * [2] Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * [3] Neither the name "KoLmafia" nor the names of its contributors may * be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package net.sourceforge.kolmafia.textui.parsetree; import java.io.PrintStream; import net.sourceforge.kolmafia.VYKEACompanionData; import net.sourceforge.kolmafia.textui.DataTypes; import net.sourceforge.kolmafia.textui.Interpreter; import net.sourceforge.kolmafia.textui.Parser; public class Operator extends ParseTreeNode { String operator; // For runtime error messages String fileName; int lineNumber; public Operator( final String operator, final Parser parser ) { this.operator = operator; this.fileName = parser.getShortFileName(); this.lineNumber = parser.getLineNumber(); } public boolean equals( final String op ) { return this.operator.equals( op ); } public boolean precedes( final Operator oper ) { return this.operStrength() > oper.operStrength(); } private int operStrength() { if ( this.operator == Parser.POST_INCREMENT || this.operator == Parser.POST_DECREMENT ) { return 14; } if ( this.operator.equals( "!" ) || this.operator.equals( "~" ) || this.operator.equals( "contains" ) || this.operator.equals( "remove" ) || this.operator == Parser.PRE_INCREMENT || this.operator == Parser.PRE_DECREMENT ) { return 13; } if ( this.operator.equals( "**" ) ) { return 12; } if ( this.operator.equals( "*" ) || this.operator.equals( "/" ) || this.operator.equals( "%" ) ) { return 11; } if ( this.operator.equals( "+" ) || this.operator.equals( "-" ) ) { return 10; } if ( this.operator.equals( "<<" ) || this.operator.equals( ">>" ) || this.operator.equals( ">>>" ) ) { return 9; } if ( this.operator.equals( "<" ) || this.operator.equals( ">" ) || this.operator.equals( "<=" ) || this.operator.equals( ">=" ) ) { return 8; } if ( this.operator.equals( "==" ) || this.operator.equals( Parser.APPROX ) || this.operator.equals( "!=" ) ) { return 7; } if ( this.operator.equals( "&" ) ) { return 6; } if ( this.operator.equals( "^" ) ) { return 5; } if ( this.operator.equals( "|" ) ) { return 4; } if ( this.operator.equals( "&&" ) ) { return 3; } if ( this.operator.equals( "||" ) ) { return 2; } if ( this.operator.equals( "?" ) || this.operator.equals( ":" ) ) { return 1; } return -1; } public static boolean isStringLike( Type type ) { return type.equals( DataTypes.TYPE_STRING ) || type.equals( DataTypes.TYPE_BUFFER ) || type.equals( DataTypes.TYPE_LOCATION ) || type.equals( DataTypes.TYPE_STAT ) || type.equals( DataTypes.TYPE_MONSTER ) || type.equals( DataTypes.TYPE_ELEMENT ) || type.equals( DataTypes.TYPE_COINMASTER ) || type.equals( DataTypes.TYPE_PHYLUM ) || type.equals( DataTypes.TYPE_BOUNTY ); } public boolean isArithmetic() { return this.operator.equals( "+" ) || this.operator.equals( "-" ) || this.operator.equals( "*" ) || this.operator.equals( "/" ) || this.operator.equals( "%" ) || this.operator.equals( "**" ) || this.operator == Parser.PRE_INCREMENT || this.operator == Parser.PRE_DECREMENT || this.operator == Parser.POST_INCREMENT || this.operator == Parser.POST_DECREMENT; } public boolean isBoolean() { return this.operator.equals( "&&" ) || this.operator.equals( "||" ); } public boolean isLogical() { return this.operator.equals( "&" ) || this.operator.equals( "|" ) || this.operator.equals( "^" ) || this.operator.equals( "~" ) || this.operator.equals( "&=" ) || this.operator.equals( "^=" ) || this.operator.equals( "|=" ); } public boolean isInteger() { return this.operator.equals( "<<" ) || this.operator.equals( ">>" ) || this.operator.equals( ">>>" ) || this.operator.equals( "<<=" ) || this.operator.equals( ">>=" ) || this.operator.equals( ">>>=" ); } public boolean isComparison() { return this.operator.equals( "==" ) || this.operator.equals( Parser.APPROX ) || this.operator.equals( "!=" ) || this.operator.equals( "<" ) || this.operator.equals( ">" ) || this.operator.equals( "<=" ) || this.operator.equals( ">=" ); } @Override public String toString() { return this.operator; } private Value compareValues( final Interpreter interpreter, Value leftValue, Value rightValue ) { Type ltype = leftValue.getType(); Type rtype = rightValue.getType(); boolean bool; // If either side is non-numeric, perform string comparison if ( Operator.isStringLike( ltype ) || Operator.isStringLike( rtype ) ) { String lstring = leftValue.toString(); String rstring = rightValue.toString(); int c = this.operator.equals( Parser.APPROX ) ? lstring.compareToIgnoreCase( rstring ) : lstring.compareTo( rstring ); bool = ( this.operator.equals( "==" ) || this.operator.equals( Parser.APPROX ) ) ? c == 0 : this.operator.equals( "!=" ) ? c != 0 : this.operator.equals( ">=" ) ? c >= 0 : this.operator.equals( "<=" ) ? c <= 0 : this.operator.equals( ">" ) ? c > 0 : this.operator.equals( "<" ) ? c < 0 : false; } // If either value is a float, coerce to float and compare. else if ( ltype.equals( DataTypes.TYPE_FLOAT ) || rtype.equals( DataTypes.TYPE_FLOAT ) ) { double lfloat = leftValue.toFloatValue().floatValue(); double rfloat = rightValue.toFloatValue().floatValue(); bool = ( this.operator.equals( "==" ) || this.operator.equals( Parser.APPROX ) ) ? lfloat == rfloat : this.operator.equals( "!=" ) ? lfloat != rfloat : this.operator.equals( ">=" ) ? lfloat >= rfloat : this.operator.equals( "<=" ) ? lfloat <= rfloat : this.operator.equals( ">" ) ? lfloat > rfloat : this.operator.equals( "<" ) ? lfloat < rfloat : false; } // VYKEA companions have a "name" component which should not be compared else if ( ltype.equals( DataTypes.TYPE_VYKEA ) || rtype.equals( DataTypes.TYPE_VYKEA ) ) { VYKEACompanionData v1 = (VYKEACompanionData)( leftValue.content ); VYKEACompanionData v2 = (VYKEACompanionData)( rightValue.content ); int c = v1.compareTo( v2 ); bool = ( this.operator.equals( "==" ) || this.operator.equals( Parser.APPROX ) ) ? c == 0 : this.operator.equals( "!=" ) ? c != 0 : this.operator.equals( ">=" ) ? c >= 0 : this.operator.equals( "<=" ) ? c <= 0 : this.operator.equals( ">" ) ? c > 0 : this.operator.equals( "<" ) ? c < 0 : false; } // Otherwise, compare integers else { long lint = leftValue.intValue(); long rint = rightValue.intValue(); bool = ( this.operator.equals( "==" ) || this.operator.equals( Parser.APPROX ) ) ? lint == rint : this.operator.equals( "!=" ) ? lint != rint : this.operator.equals( ">=" ) ? lint >= rint : this.operator.equals( "<=" ) ? lint <= rint : this.operator.equals( ">" ) ? lint > rint : this.operator.equals( "<" ) ? lint < rint : false; } Value result = bool ? DataTypes.TRUE_VALUE : DataTypes.FALSE_VALUE; if ( interpreter.isTracing() ) { interpreter.trace( "<- " + result ); } interpreter.traceUnindent(); return result; } private Value performArithmetic( final Interpreter interpreter, Value leftValue, Value rightValue ) { Type ltype = leftValue.getType(); Type rtype = rightValue.getType(); Value result; // If either side is non-numeric, perform string operations if ( Operator.isStringLike( ltype) || Operator.isStringLike( rtype ) ) { // Since we only do string concatenation, we should // only get here if the operator is "+". if ( !this.operator.equals( "+" ) ) { throw Interpreter.runtimeException( "Operator '" + this.operator + "' applied to string operands", this.fileName, this.lineNumber ); } String string = leftValue.toStringValue().toString() + rightValue.toStringValue().toString(); result = new Value( string ); } // If either value is a float, coerce to float else if ( ltype.equals( DataTypes.TYPE_FLOAT ) || rtype.equals( DataTypes.TYPE_FLOAT ) ) { double rfloat = rightValue.toFloatValue().floatValue(); if ( ( this.operator.equals( "/" ) || this.operator.equals( "%" ) ) && rfloat == 0.0 ) { throw Interpreter.runtimeException( "Division by zero", this.fileName, this.lineNumber ); } double lfloat = leftValue.toFloatValue().floatValue(); double val = 0.0; if ( this.operator.equals( "**" ) ) { val = Math.pow( lfloat, rfloat ); if ( Double.isNaN( val ) || Double.isInfinite( val ) ) { throw Interpreter.runtimeException( "Invalid exponentiation: cannot take " + lfloat + " ** " + rfloat, this.fileName, this.lineNumber ); } } else { val = this.operator.equals( "+" ) ? lfloat + rfloat : this.operator.equals( "-" ) ? lfloat - rfloat : this.operator.equals( "*" ) ? lfloat * rfloat : this.operator.equals( "/" ) ? lfloat / rfloat : this.operator.equals( "%" ) ? lfloat % rfloat : 0.0; } result = DataTypes.makeFloatValue( val ); } // If this is a logical operator, return an int or boolean else if ( this.isLogical() ) { long lint = leftValue.intValue(); long rint = rightValue.intValue(); long val = this.operator.equals( "&" ) ? lint & rint : this.operator.equals( "^" ) ? lint ^ rint : this.operator.equals( "|" ) ? lint | rint : 0; result = ltype.equals( DataTypes.TYPE_BOOLEAN ) ? DataTypes.makeBooleanValue( val != 0 ) : DataTypes.makeIntValue( val ); } // Otherwise, perform arithmetic on integers else { long rint = rightValue.intValue(); if ( ( this.operator.equals( "/" ) || this.operator.equals( "%" ) ) && rint == 0 ) { throw Interpreter.runtimeException( "Division by zero", this.fileName, this.lineNumber ); } long lint = leftValue.intValue(); long val = this.operator.equals( "+" ) ? lint + rint : this.operator.equals( "-" ) ? lint - rint : this.operator.equals( "*" ) ? lint * rint : this.operator.equals( "/" ) ? lint / rint : this.operator.equals( "%" ) ? lint % rint : this.operator.equals( "**" ) ? (long) Math.pow( lint, rint ) : this.operator.equals( "<<" ) ? lint << rint : this.operator.equals( ">>" ) ? lint >> rint : this.operator.equals( ">>>" ) ? lint >>> rint : 0; result = DataTypes.makeIntValue( val ); } if ( interpreter.isTracing() ) { interpreter.trace( "<- " + result ); } interpreter.traceUnindent(); return result; } public Value applyTo( final Interpreter interpreter, final Value lhs ) { interpreter.traceIndent(); if ( interpreter.isTracing() ) { interpreter.trace( "Operator: " + this.operator ); } // Unary operator with special evaluation of argument if ( this.operator.equals( "remove" ) ) { CompositeReference operand = (CompositeReference) lhs; if ( interpreter.isTracing() ) { interpreter.traceIndent(); interpreter.trace( "Operand: " + operand ); interpreter.traceUnindent(); } Value result = operand.removeKey( interpreter ); if ( interpreter.isTracing() ) { interpreter.trace( "<- " + result ); } interpreter.traceUnindent(); return result; } interpreter.traceIndent(); if ( interpreter.isTracing() ) { interpreter.trace( "Operand: " + lhs ); } Value leftValue = lhs.execute( interpreter ); interpreter.captureValue( leftValue ); if ( leftValue == null ) { leftValue = DataTypes.VOID_VALUE; } if ( interpreter.isTracing() ) { interpreter.trace( "[" + interpreter.getState() + "] <- " + leftValue.toQuotedString() ); } interpreter.traceUnindent(); if ( interpreter.getState() == Interpreter.STATE_EXIT ) { interpreter.traceUnindent(); return null; } Value result; // Unary Operators if ( this.operator.equals( "!" ) ) { result = DataTypes.makeBooleanValue( leftValue.intValue() == 0 ); } else if ( this.operator.equals( "~" ) ) { long val = leftValue.intValue(); result = leftValue.getType().equals( DataTypes.TYPE_BOOLEAN ) ? DataTypes.makeBooleanValue( val == 0 ) : DataTypes.makeIntValue( ~val ); } else if ( this.operator.equals( "-" ) ) { if ( lhs.getType().equals( DataTypes.TYPE_INT ) ) { result = DataTypes.makeIntValue( 0 - leftValue.intValue() ); } else if ( lhs.getType().equals( DataTypes.TYPE_FLOAT ) ) { result = DataTypes.makeFloatValue( 0.0 - leftValue.floatValue() ); } else { throw Interpreter.runtimeException( "Internal error: Unary minus can only be applied to numbers", this.fileName, this.lineNumber ); } } else if ( this.operator == Parser.PRE_INCREMENT || this.operator == Parser.POST_INCREMENT ) { if ( lhs.getType().equals( DataTypes.TYPE_INT ) ) { result = DataTypes.makeIntValue( leftValue.intValue() + 1 ); } else if ( lhs.getType().equals( DataTypes.TYPE_FLOAT ) ) { result = DataTypes.makeFloatValue( leftValue.floatValue() + 1.0 ); } else { throw Interpreter.runtimeException( "Internal error: pre/post increment can only be applied to numbers", this.fileName, this.lineNumber ); } } else if ( this.operator == Parser.PRE_DECREMENT || this.operator == Parser.POST_DECREMENT ) { if ( lhs.getType().equals( DataTypes.TYPE_INT ) ) { result = DataTypes.makeIntValue( leftValue.intValue() - 1 ); } else if ( lhs.getType().equals( DataTypes.TYPE_FLOAT ) ) { result = DataTypes.makeFloatValue( leftValue.floatValue() - 1.0 ); } else { throw Interpreter.runtimeException( "Internal error: pre/post increment can only be applied to numbers", this.fileName, this.lineNumber ); } } else { throw Interpreter.runtimeException( "Internal error: unknown unary operator \"" + this.operator + "\"", this.fileName, this.lineNumber ); } if ( interpreter.isTracing() ) { interpreter.trace( "<- " + result ); } interpreter.traceUnindent(); return result; } public Value applyTo( final Interpreter interpreter, final Value lhs, final Value rhs ) { interpreter.traceIndent(); if ( interpreter.isTracing() ) { interpreter.trace( "Operator: " + this.operator ); } interpreter.traceIndent(); if ( interpreter.isTracing() ) { interpreter.trace( "Operand 1: " + lhs ); } Value leftValue = lhs.execute( interpreter ); interpreter.captureValue( leftValue ); if ( leftValue == null ) { leftValue = DataTypes.VOID_VALUE; } if ( interpreter.isTracing() ) { interpreter.trace( "[" + interpreter.getState() + "] <- " + leftValue.toQuotedString() ); } interpreter.traceUnindent(); if ( interpreter.getState() == Interpreter.STATE_EXIT ) { interpreter.traceUnindent(); return null; } // Unknown operator if ( rhs == null ) { throw Interpreter.runtimeException( "Internal error: missing right operand.", this.fileName, this.lineNumber ); } // Binary operators with optional right values if ( this.operator.equals( "||" ) ) { if ( leftValue.intValue() == 1 ) { if ( interpreter.isTracing() ) { interpreter.trace( "<- " + DataTypes.TRUE_VALUE ); } interpreter.traceUnindent(); return DataTypes.TRUE_VALUE; } interpreter.traceIndent(); if ( interpreter.isTracing() ) { interpreter.trace( "Operand 2: " + rhs ); } Value rightValue = rhs.execute( interpreter ); interpreter.captureValue( rightValue ); if ( rightValue == null ) { rightValue = DataTypes.VOID_VALUE; } if ( interpreter.isTracing() ) { interpreter.trace( "[" + interpreter.getState() + "] <- " + rightValue.toQuotedString() ); } interpreter.traceUnindent(); if ( interpreter.getState() == Interpreter.STATE_EXIT ) { interpreter.traceUnindent(); return null; } if ( interpreter.isTracing() ) { interpreter.trace( "<- " + rightValue ); } interpreter.traceUnindent(); return rightValue; } if ( this.operator.equals( "&&" ) ) { if ( leftValue.intValue() == 0 ) { interpreter.traceUnindent(); if ( interpreter.isTracing() ) { interpreter.trace( "<- " + DataTypes.FALSE_VALUE ); } return DataTypes.FALSE_VALUE; } interpreter.traceIndent(); if ( interpreter.isTracing() ) { interpreter.trace( "Operand 2: " + rhs ); } Value rightValue = rhs.execute( interpreter ); interpreter.captureValue( rightValue ); if ( rightValue == null ) { rightValue = DataTypes.VOID_VALUE; } if ( interpreter.isTracing() ) { interpreter.trace( "[" + interpreter.getState() + "] <- " + rightValue.toQuotedString() ); } interpreter.traceUnindent(); if ( interpreter.getState() == Interpreter.STATE_EXIT ) { interpreter.traceUnindent(); return null; } if ( interpreter.isTracing() ) { interpreter.trace( "<- " + rightValue ); } interpreter.traceUnindent(); return rightValue; } // Ensure type compatibility of operands if ( !Parser.validCoercion( lhs.getType(), rhs.getType(), this ) ) { throw Interpreter.runtimeException( "Internal error: left hand side and right hand side do not correspond", this.fileName, this.lineNumber ); } // Special binary operator: <aggref> contains <any> if ( this.operator.equals( "contains" ) ) { interpreter.traceIndent(); if ( interpreter.isTracing() ) { interpreter.trace( "Operand 2: " + rhs ); } Value rightValue = rhs.execute( interpreter ); interpreter.captureValue( rightValue ); if ( rightValue == null ) { rightValue = DataTypes.VOID_VALUE; } if ( interpreter.isTracing() ) { interpreter.trace( "[" + interpreter.getState() + "] <- " + rightValue.toQuotedString() ); } interpreter.traceUnindent(); if ( interpreter.getState() == Interpreter.STATE_EXIT ) { interpreter.traceUnindent(); return null; } Value result = DataTypes.makeBooleanValue( leftValue.contains( rightValue ) ); if ( interpreter.isTracing() ) { interpreter.trace( "<- " + result ); } interpreter.traceUnindent(); return result; } // Binary operators interpreter.traceIndent(); if ( interpreter.isTracing() ) { interpreter.trace( "Operand 2: " + rhs ); } Value rightValue = rhs.execute( interpreter ); interpreter.captureValue( rightValue ); if ( rightValue == null ) { rightValue = DataTypes.VOID_VALUE; } if ( interpreter.isTracing() ) { interpreter.trace( "[" + interpreter.getState() + "] <- " + rightValue.toQuotedString() ); } interpreter.traceUnindent(); if ( interpreter.getState() == Interpreter.STATE_EXIT ) { interpreter.traceUnindent(); return null; } // Comparison operators if ( this.isComparison() ) { return this.compareValues( interpreter, leftValue, rightValue ); } // Arithmetic operators if ( this.isArithmetic() || this.isLogical() || this.isInteger() ) { return this.performArithmetic( interpreter, leftValue, rightValue ); } // Unknown operator throw Interpreter.runtimeException( "Internal error: unknown binary operator \"" + this.operator + "\"", this.fileName, this.lineNumber ); } @Override public Value execute( final Interpreter interpreter ) { return null; } @Override public void print( final PrintStream stream, final int indent ) { Interpreter.indentLine( stream, indent ); stream.println( "<OPER " + this.operator + ">" ); } }