net.sandius.rembulan.Ordering Maven / Gradle / Ivy
/*
* Copyright 2016 Miroslav Janíček
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sandius.rembulan;
import java.util.Comparator;
/**
* A representation of an ordering on values, allowing the comparison of values
* of the same type (in the type parameter {@code T}).
*
* In Lua, only strings and numbers have such an ordering. This class serves the
* purpose of a bridge between the concrete representation of Lua numbers
* (as {@link java.lang.Number}) and the raw comparison operations provided by
* {@link LuaMathOperators}, and the concrete representation of Lua strings
* (as {@link java.lang.String}) and the comparison operations defined on them.
*
* Consequently, there are two concrete implementations of this class:
* {@link #NUMERIC} for the numeric ordering and {@link #STRING} for the string
* ordering. These instances may be used directly for comparing objects of known,
* conforming types; for unknown objects, the method {@link #of(Object, Object)}
* returns an ordering that accepts {@link java.lang.Object}, and uses one of
* the two ordering instances, or {@code null} if the arguments
* are not directly comparable in Lua.
*
* The comparison methods of this class return unboxed booleans.
*
* This class implements the {@link Comparator} interface by imposing a total
* order on the accepted values. For numbers, this total ordering is different
* from the one imposed by this class. See the documentation of {@link NumericOrdering}
* for more details.
*
* Example: Given two objects {@code a}, and {@code b}, attempt to
* evaluate the Lua expression {@code (a <= b)}:
*
*
* // Object a, b
* final boolean result;
* Ordering<Object> cmp = Ordering.of(a, b);
* if (cmp != null) {
* // a and b are comparable in cmp
* result = cmp.le(a, b);
* }
* else {
* throw new RuntimeException("a and b not comparable");
* }
*
*
* @param the type of values comparable in the ordering
*/
public abstract class Ordering implements Comparator {
private Ordering() {
// not to be instantiated by the outside world
}
/**
* A static instance of the numeric ordering.
*/
public static final NumericOrdering NUMERIC = new NumericOrdering();
/**
* A static instance of the string ordering.
*/
public static final StringOrdering STRING = new StringOrdering();
/**
* Returns {@code true} if {@code a} is equal to {@code b} in this ordering.
*
* @param a first argument, must not be {@code null}
* @param b second argument, must not be {@code null}
* @return {@code true} iff {@code a} is equal to {@code b} in this ordering
*
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
*/
public abstract boolean eq(T a, T b);
/**
* Returns {@code true} if {@code a} is lesser than {@code b} in this ordering.
*
* @param a first argument, must not be {@code null}
* @param b second argument, must not be {@code null}
* @return {@code true} iff {@code a} is lesser than {@code b} in this ordering
*
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
*/
public abstract boolean lt(T a, T b);
/**
* Returns {@code true} if {@code a} is lesser than or equal to {@code b} in this ordering.
*
* @param a first argument, must not be {@code null}
* @param b second argument, must not be {@code null}
* @return {@code true} iff {@code a} is lesser than or equal to equal to {@code b}
* in this ordering
*
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
*/
public abstract boolean le(T a, T b);
/**
* Returns {@code true} iff the object {@code a} is raw-equal to {@code b} following
* the Lua equality rules.
*
* Excerpt from the Lua Reference Manual (§3.4.4):
*
*
* Equality (==) first compares the type of its operands. If the types are different,
* then the result is false. Otherwise, the values of the operands are compared.
* Strings are compared in the obvious way. Numbers are equal if they denote the
* same mathematical value.
*
* Tables, userdata, and threads are compared by reference: two objects are considered
* equal only if they are the same object. Every time you create a new object (a table,
* userdata, or thread), this new object is different from any previously existing
* object. Closures with the same reference are always equal. Closures with any
* detectable difference (different behavior, different definition) are always
* different.
*
*
* Note: Rembulan uses {@link Object#equals(Object)} to compare all non-nil,
* non-string, and non-numeric values for equality, effectively shifting the
* responsibility of adhering to the rules of Lua raw-equality for tables, userdata
* and threads to their implementations.
*
* @param a an object, may be {@code null}
* @param b another object, may be {@code null}
* @return {@code true} iff {@code a} is raw-equal to {@code b}
*/
public static boolean isRawEqual(Object a, Object b) {
if (a == null && b == null) {
// two nils
return true;
}
else if (a == null) {
// b is definitely not nil; also ensures that neither a nor b is null in the tests below
return false;
}
else if (a instanceof Number && b instanceof Number) {
return Ordering.NUMERIC.eq((Number) a, (Number) b);
}
else if (LuaType.isString(a) && LuaType.isString(b)) {
return Ordering.STRING.eq(toByteString(a), toByteString(b));
}
else {
return a.equals(b);
}
}
/**
* Numeric ordering.
*
* Numbers are compared using the comparison methods provided by {@link LuaMathOperators},
* defining the ordering as one based on the ordering of the mathematical values
* of the numbers in question.
*
* This class implements the {@link Comparator} interface by imposing a total order
* on numbers that differs from the ordering defined by the methods
* {@link #eq(Number, Number)}, {@link #lt(Number, Number)}
* and {@link #le(Number, Number)}:
*
*
* - NaN is treated as equal to itself and greater than any other
* number, while {@code eq(a, b) == false} and {@code lt(a, b) == false}
* when {@code a} or {@code b} is NaN;
*
- {@code -0.0} is considered to be lesser than {@code 0.0},
* while {@code eq(-0.0, 0.0) == true} and {@code lt(-0.0, 0.0) == false}.
*
*
* Note that the total ordering imposed by the {@link #compare(Number, Number)}
* is inconsistent with equals.
*
* For proper treatment of NaNs and (float) zero values, use the
* {@code Ordering} methods directly.
*
*/
public static final class NumericOrdering extends Ordering {
private NumericOrdering() {
// not to be instantiated by the outside world
}
/**
* Returns {@code true} iff {@code a} denotes the same mathematical value
* as {@code b}.
*
* Note that since NaN does not denote any mathematical value,
* this method returns {@code false} whenever any of its arguments is NaN.
*
* @param a first argument, must not be {@code null}
* @param b second argument, must not be {@code null}
* @return {@code true} iff {@code a} and {@code b} denote the same mathematical
* value
*
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
*/
@Override
public boolean eq(Number a, Number b) {
boolean isflt_a = a instanceof Double || a instanceof Float;
boolean isflt_b = b instanceof Double || b instanceof Float;
if (isflt_a) {
return isflt_b
? LuaMathOperators.eq(a.doubleValue(), b.doubleValue())
: LuaMathOperators.eq(a.doubleValue(), b.longValue());
}
else {
return isflt_b
? LuaMathOperators.eq(a.longValue(), b.doubleValue())
: LuaMathOperators.eq(a.longValue(), b.longValue());
}
}
/**
* Returns {@code true} iff the mathematical value denoted by {@code a}
* is lesser than the mathematical value denoted by {@code b}.
*
* Note that since NaN does not denote any mathematical value,
* this method returns {@code false} whenever any of its arguments is NaN.
*
* @param a first argument, must not be {@code null}
* @param b second argument, must not be {@code null}
* @return {@code true} iff the mathematical value denoted by {@code a}
* is lesser than the mathematical value denoted by {@code b}
*
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
*/
@Override
public boolean lt(Number a, Number b) {
boolean isflt_a = a instanceof Double || a instanceof Float;
boolean isflt_b = b instanceof Double || b instanceof Float;
if (isflt_a) {
return isflt_b
? LuaMathOperators.lt(a.doubleValue(), b.doubleValue())
: LuaMathOperators.lt(a.doubleValue(), b.longValue());
}
else {
return isflt_b
? LuaMathOperators.lt(a.longValue(), b.doubleValue())
: LuaMathOperators.lt(a.longValue(), b.longValue());
}
}
/**
* Returns {@code true} iff the mathematical value denoted by {@code a}
* is lesser than or equal to the mathematical value denoted by {@code b}.
*
* Note that since NaN does not denote any mathematical value,
* this method returns {@code false} whenever any of its arguments is NaN.
*
* @param a first argument, must not be {@code null}
* @param b second argument, must not be {@code null}
* @return {@code true} iff the mathematical value denoted by {@code a}
* is lesser than or equal to the mathematical value denoted
* by {@code b}
*
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
*/
@Override
public boolean le(Number a, Number b) {
boolean isflt_a = a instanceof Double || a instanceof Float;
boolean isflt_b = b instanceof Double || b instanceof Float;
if (isflt_a) {
return isflt_b
? LuaMathOperators.le(a.doubleValue(), b.doubleValue())
: LuaMathOperators.le(a.doubleValue(), b.longValue());
}
else {
return isflt_b
? LuaMathOperators.le(a.longValue(), b.doubleValue())
: LuaMathOperators.le(a.longValue(), b.longValue());
}
}
/**
* Compare the numbers {@code a} and {@code b}, yielding an integer that
* is negative, zero or positive if {@code a} is lesser than, equal to, or greater
* than {@code b}.
*
* The ordering imposed by this method differs from the one defined
* by the methods {@link #eq(Number, Number)}, {@link #lt(Number, Number)}
* and {@link #le(Number, Number)} in the treatment of NaNs
* and float zeros:
*
*
* - NaN is treated as equal to itself and greater than any other
* number, while {@code eq(a, b) == false} and {@code lt(a, b) == false}
* when {@code a} or {@code b} is NaN;
*
- {@code -0.0} is considered to be lesser than {@code 0.0},
* while {@code eq(-0.0, 0.0) == true} and {@code lt(-0.0, 0.0) == false}.
*
*
* The total ordering of {@code Number} objects imposed by this method
* is inconsistent with equals.
*
* @param a first argument, must not be {@code null}
* @param b second argument, must not be {@code null}
* @return a negative, zero or positive integer if the number {@code a} is lesser
* than, equal to, or greater than the number {@code b}
*
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
*/
@Override
public int compare(Number a, Number b) {
if (lt(a, b)) {
return -1;
}
else if (lt(b, a)) {
return 1;
}
else {
// treat NaN as equal to itself and greater than any other number,
// and -0.0 as lesser than 0.0
return Double.compare(a.doubleValue(), b.doubleValue());
}
}
}
/**
* String ordering.
*
* This is the (total) lexicographical ordering imposed by the method
* {@link String#compareTo(String)}.
*/
public static final class StringOrdering extends Ordering {
private StringOrdering() {
// not to be instantiated by the outside world
}
@Override
public boolean eq(ByteString a, ByteString b) {
return a.compareTo(b) == 0;
}
@Override
public boolean lt(ByteString a, ByteString b) {
return a.compareTo(b) < 0;
}
@Override
public boolean le(ByteString a, ByteString b) {
return a.compareTo(b) <= 0;
}
@Override
public int compare(ByteString a, ByteString b) {
return a.compareTo(b);
}
}
private static final NumericObjectOrdering NUMERIC_OBJECT = new NumericObjectOrdering();
private static final StringObjectOrdering STRING_OBJECT = new StringObjectOrdering();
private static class NumericObjectOrdering extends Ordering