///|
/// Compares two strings by Unicode scalar values (like Rust's `str::cmp`
/// which compares UTF-8 bytes).
fn compare_str(a : String, b : String) -> Int {
let la = a.length()
let lb = b.length()
let n = if la < lb { la } else { lb }
for i in 0..= 0xD800 && x <= 0xDFFF
let ys = y >= 0xD800 && y <= 0xDFFF
if xs && !ys {
return if y >= 0xE000 { 1 } else { 1 }
}
if ys && !xs {
return -1
}
return if x < y { -1 } else { 1 }
}
}
if la < lb {
-1
} else if la > lb {
1
} else {
0
}
}
///|
fn compare_int64(a : Int64, b : Int64) -> Int {
if a < b {
-1
} else if a > b {
1
} else {
0
}
}
///|
fn compare_big(a : BigInt, b : BigInt) -> Int {
if a < b {
-1
} else if a > b {
1
} else {
0
}
}
///|
fn f64_total_cmp(left : Double, right : Double) -> Int {
let mut l = left.reinterpret_as_int64()
let mut r = right.reinterpret_as_int64()
l = l ^ ((l >> 63).reinterpret_as_uint64() >> 1).reinterpret_as_int64()
r = r ^ ((r >> 63).reinterpret_as_uint64() >> 1).reinterpret_as_int64()
compare_int64(l, r)
}
///|
fn cmp_f64(left : Double, right : Double) -> Int {
if left == right {
0
} else {
f64_total_cmp(left, right)
}
}
///|
fn cmp_f64_big(left : Double, right : BigInt, upper : BigInt?) -> Int {
match cmp_f64(left, bigint_to_f64(right)) {
0 if !left.is_nan() && !left.is_inf() => {
let trunc = f64_trunc_to_bigint(left)
match upper {
Some(max) if trunc > max => 1
_ => {
let c = compare_big(trunc, right)
if c != 0 {
c
} else {
let t = left.trunc()
if left < t {
-1
} else if left > t {
1
} else {
0
}
}
}
}
}
rv => rv
}
}
///|
/// Compares numbers that `coerce` could not bring into a common lossless
/// representation.
fn cmp_uncoercible_numbers(left : Value, right : Value) -> Int {
fn num(v : Value) -> (Double?, BigInt?) {
match v {
F64(x) => (Some(x), None)
U64(x) => (None, Some(BigInt::from_uint64(x)))
I64(x) => (None, Some(BigInt::from_int64(x)))
U128(x) | I128(x) => (None, Some(x))
_ => (None, None)
}
}
match (num(left), num(right)) {
((Some(a), _), (Some(b), _)) => cmp_f64(a, b)
((Some(a), _), (_, Some(b))) => cmp_f64_big(a, b, Some(u128_max))
((_, Some(a)), (Some(b), _)) => -cmp_f64_big(b, a, Some(u128_max))
((_, Some(a)), (_, Some(b))) => compare_big(a, b)
_ => 0
}
}
///|
pub impl Eq for Value with fn equal(self, other) {
match (self, other) {
(I64(a), I64(b)) => a == b
(U64(a), U64(b)) => a == b
(NoneValue, NoneValue) => true
(Undefined(_), Undefined(_)) => true
(Str(a, _), Str(b, _)) => a == b
(Bytes(a), Bytes(b)) => a == b
_ =>
match coerce(self, other, false) {
Some(F64(a, b)) => a == b
Some(Int(a, b)) => a == b
Some(Big(a, b)) => a == b
Some(Str(a, b)) => a == b
None =>
match (self, other) {
(Object(a), Object(b)) => objects_equal(self, a, other, b)
_ => false
}
}
}
}
///|
fn objects_equal(av : Value, a : DynObject, bv : Value, b : DynObject) -> Bool {
if av.is_tuple() != bv.is_tuple() {
return false
}
if a.is_same_object(b) {
return true
}
if custom_object_cmp(a, bv) is Some(c) {
return c == 0
}
match (a.repr(), b.repr()) {
(Map, Map) => {
let mut need_length_fallback = true
match (a.enumerator_len(), b.enumerator_len()) {
(Some(a_len), Some(b_len)) => {
if a_len != b_len {
return false
}
need_length_fallback = false
}
_ => ()
}
let mut a_count = 0
match a.try_iter_pairs() {
Some(iter) =>
for pair in iter {
a_count += 1
match b.get_value(pair.0) {
Some(v) if v == pair.1 => ()
_ => return false
}
}
None => return false
}
if !need_length_fallback {
true
} else {
a_count ==
(match b.try_iter() {
Some(it) => it.count()
None => 0
})
}
}
(Seq | Iterable, Seq | Iterable) =>
match (a.try_iter(), b.try_iter()) {
(Some(ai), Some(bi)) =>
for ;; {
match (ai.next(), bi.next()) {
(None, None) => break true
(Some(x), Some(y)) => if x != y { break false }
_ => break false
}
}
_ => false
}
(Plain, Plain) =>
if has_default_render(a) || has_default_render(b) {
// MiniJinja compares the debug representations here; objects
// without a custom rendering only compare equal to themselves.
false
} else {
av.to_string() == bv.to_string()
}
_ => false
}
}
///|
/// Consults the `custom_cmp` hook of custom objects.
fn custom_object_cmp(a : DynObject, other : Value) -> Int? {
match a.inner {
Custom(o) =>
match o.custom_cmp(other) {
Some(c) => Some(if c < 0 { -1 } else if c > 0 { 1 } else { 0 })
None => None
}
_ => None
}
}
///|
/// Returns `true` for custom objects that do not provide a rendering.
fn has_default_render(a : DynObject) -> Bool {
match a.inner {
Custom(o) => o.render() is None
_ => false
}
}
///|
/// Compares two values like MiniJinja's `Ord` implementation.
pub fn Value::cmp(self : Value, other : Value) -> Int {
if small_int(self) is Some(a) && small_int(other) is Some(b) {
return compare_int64(a, b)
}
let kind_ordering = Compare::compare(self.kind(), other.kind())
if kind_ordering != 0 {
return if kind_ordering < 0 { -1 } else { 1 }
}
match (self, other) {
(NoneValue, NoneValue) => 0
(Undefined(_), Undefined(_)) => 0
(Str(a, _), Str(b, _)) => compare_str(a, b)
(Bytes(a), Bytes(b)) => {
let la = a.length()
let lb = b.length()
let n = if la < lb { la } else { lb }
for i in 0.. compare_big(a, b)
_ =>
match coerce(self, other, false) {
Some(F64(a, b)) => cmp_f64(a, b)
Some(Int(a, b)) => compare_int64(a, b)
Some(Big(a, b)) => compare_big(a, b)
Some(Str(a, b)) => compare_str(a, b)
None => {
if self.is_number() && other.is_number() {
return cmp_uncoercible_numbers(self, other)
}
guard self is Object(a) && other is Object(b) else { return 0 }
let at = self.is_tuple()
let bt = other.is_tuple()
if at != bt {
return if at { 1 } else { -1 }
}
if a.is_same_object(b) {
return 0
}
if custom_object_cmp(a, other) is Some(c) {
return c
}
match (a.repr(), b.repr()) {
(Map, Map) =>
match (a.try_iter_pairs(), b.try_iter_pairs()) {
(Some(ai), Some(bi)) =>
for ;; {
match (ai.next(), bi.next()) {
(None, None) => break 0
(None, Some(_)) => break -1
(Some(_), None) => break 1
(Some(x), Some(y)) => {
let c = x.0.cmp(y.0)
if c != 0 {
break c
}
let c = x.1.cmp(y.1)
if c != 0 {
break c
}
}
}
}
_ => 0
}
(Seq | Iterable, Seq | Iterable) =>
match (a.try_iter(), b.try_iter()) {
(Some(ai), Some(bi)) =>
for ;; {
match (ai.next(), bi.next()) {
(None, None) => break 0
(None, Some(_)) => break -1
(Some(_), None) => break 1
(Some(x), Some(y)) => {
let c = x.cmp(y)
if c != 0 {
break c
}
}
}
}
_ => 0
}
(Plain, Plain) =>
if has_default_render(a) || has_default_render(b) {
compare_int64(a.id.to_int64(), b.id.to_int64())
} else {
compare_str(self.to_string(), other.to_string())
}
_ => 0
}
}
}
}
}
///|
pub impl Compare for Value with fn compare(self, other) {
self.cmp(other)
}
///|
pub impl Hash for Value with fn hash_combine(self, hasher) {
match self {
NoneValue | Undefined(_) => hasher.combine_int(0)
Str(s, _) => hasher.combine_string(s)
Bool(b) => hasher.combine_bool(b)
Invalid(e) => {
hasher.combine_string(e.kind().name())
hasher.combine_string(e.detail().unwrap_or(""))
}
Bytes(b) => hasher.combine(b)
Object(o) => {
hasher.combine_bool(self.is_tuple())
if o.try_iter_pairs() is Some(iter) {
for pair in iter {
Hash::hash_combine(pair.0, hasher)
Hash::hash_combine(pair.1, hasher)
}
}
}
U64(_) | I64(_) | F64(_) | U128(_) | I128(_) =>
match self.as_i64() {
Some(v) => hasher.combine_int64(v)
None =>
match as_f64(self, true) {
Some(f) => hasher.combine_int64(f.reinterpret_as_int64())
None => hasher.combine_int(0)
}
}
}
}