///|
/// Comparison semantics shared by the AST evaluator and the bytecode VM.
///|
fn compare_sequence_values(
left : Array[Value],
right : Array[Value],
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Result[Int32, RuntimeError] {
let min_len = if left.length() < right.length() {
left.length()
} else {
right.length()
}
for i = 0; i < min_len; i = i + 1 {
if eq_value(left[i], right[i]) {
continue
}
let lt = match
compare_values_with_env(
CompareOp::Lt,
left[i],
right[i],
globals,
builtins,
io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
return Ok(if lt { -1 } else { 1 })
}
if left.length() == right.length() {
Ok(0)
} else if left.length() < right.length() {
Ok(-1)
} else {
Ok(1)
}
}
///|
fn compare_bytes_like(left : Array[Int32], right : Array[Int32]) -> Int32 {
let min_len = if left.length() < right.length() {
left.length()
} else {
right.length()
}
for i = 0; i < min_len; i = i + 1 {
let a = left[i] & 0xFF
let b = right[i] & 0xFF
if a < b {
return -1
}
if a > b {
return 1
}
}
if left.length() == right.length() {
0
} else if left.length() < right.length() {
-1
} else {
1
}
}
///|
fn is_intrinsic_default_compare(method_value : Value, name : String) -> Bool {
match method_value {
Value::BoundMethod(bound) =>
bound.function.name == name && bound.function.body.length() == 0
Value::Function(func) => func.name == name && func.body.length() == 0
_ => false
}
}
///|
fn call_compare_method(
target : Value,
method_name : String,
other : Value,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Result[Value?, RuntimeError] {
if !(target is Value::Instance(_)) {
return Ok(None)
}
let candidate = match
get_attr_from_value(target, method_name, globals, builtins, io) {
Ok(v) => v
Err(err) =>
match err.kind {
RuntimeErrorKind::Attribute => return Ok(None)
_ => return Err(err)
}
}
if candidate is Value::None {
return Err(
make_runtime_error(
RuntimeErrorKind::Type,
method_name + " is not callable".to_string(),
),
)
}
if is_intrinsic_default_compare(candidate, method_name) {
return Ok(None)
}
match call_callable_with_env(candidate, [other], [], globals, builtins, io) {
Ok(v) => Ok(Some(v))
Err(err) => Err(err)
}
}
///|
fn is_notimplemented(value : Value) -> Bool {
match value {
Value::Instance(inst) => inst.class.name == "NotImplementedType"
_ => false
}
}
///|
fn is_strict_subclass(left : Value, right : Value) -> Bool {
match (left, right) {
(Value::Instance(left_inst), Value::Instance(right_inst)) =>
if left_inst.class.name == right_inst.class.name {
false
} else {
match class_mro(right_inst.class) {
Ok(mro) => {
for cls in mro {
if cls.name == left_inst.class.name {
return true
}
}
false
}
Err(_) => false
}
}
_ => false
}
}
///|
fn eq_bool(
left : Value,
right : Value,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Result[Bool, RuntimeError] {
fn as_list(value : Value) -> Array[Value]? {
match value {
Value::List(values) => Some(values)
Value::Instance(inst) =>
match get_named_value(inst.dict, list_storage_name) {
Some(Value::List(values)) => Some(values)
_ => None
}
_ => None
}
}
fn as_tuple(value : Value) -> Array[Value]? {
match value {
Value::Tuple(values) => Some(values)
Value::Instance(inst) =>
match get_named_value(inst.dict, tuple_storage_name) {
Some(Value::Tuple(values)) => Some(values)
_ => None
}
_ => None
}
}
match (as_list(left), as_list(right)) {
(Some(xs), Some(ys)) =>
if xs.length() != ys.length() {
return Ok(false)
} else {
for i = 0; i < xs.length(); i = i + 1 {
match eq_bool(xs[i], ys[i], globals, builtins, io) {
Ok(true) => ()
Ok(false) => return Ok(false)
Err(err) => return Err(err)
}
}
return Ok(true)
}
_ => ()
}
match (as_tuple(left), as_tuple(right)) {
(Some(xs), Some(ys)) =>
if xs.length() != ys.length() {
return Ok(false)
} else {
for i = 0; i < xs.length(); i = i + 1 {
match eq_bool(xs[i], ys[i], globals, builtins, io) {
Ok(true) => ()
Ok(false) => return Ok(false)
Err(err) => return Err(err)
}
}
return Ok(true)
}
_ => ()
}
let swapped = is_strict_subclass(left, right)
let first = if swapped { right } else { left }
let second = if swapped { left } else { right }
match call_compare_method(first, "__eq__", second, globals, builtins, io) {
Ok(Some(value)) =>
if is_notimplemented(value) {
match
call_compare_method(second, "__eq__", first, globals, builtins, io) {
Ok(Some(value2)) =>
if is_notimplemented(value2) {
Ok(eq_value(left, right))
} else {
truthy_from_value_with_env(value2, globals, builtins, io)
}
Ok(None) => Ok(eq_value(left, right))
Err(err) => Err(err)
}
} else {
truthy_from_value_with_env(value, globals, builtins, io)
}
Ok(None) =>
match
call_compare_method(second, "__eq__", first, globals, builtins, io) {
Ok(Some(value2)) =>
if is_notimplemented(value2) {
Ok(eq_value(left, right))
} else {
truthy_from_value_with_env(value2, globals, builtins, io)
}
Ok(None) => Ok(eq_value(left, right))
Err(err) => Err(err)
}
Err(err) => Err(err)
}
}
///|
fn ne_bool(
left : Value,
right : Value,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Result[Bool, RuntimeError] {
if is_strict_subclass(left, right) {
match call_compare_method(right, "__ne__", left, globals, builtins, io) {
Ok(Some(value)) =>
if is_notimplemented(value) {
match
call_compare_method(left, "__ne__", right, globals, builtins, io) {
Ok(Some(value2)) =>
if is_notimplemented(value2) {
match eq_bool(left, right, globals, builtins, io) {
Ok(v) => Ok(!v)
Err(err) => Err(err)
}
} else {
truthy_from_value_with_env(value2, globals, builtins, io)
}
Ok(None) =>
match eq_bool(left, right, globals, builtins, io) {
Ok(v) => Ok(!v)
Err(err) => Err(err)
}
Err(err) => Err(err)
}
} else {
truthy_from_value_with_env(value, globals, builtins, io)
}
Ok(None) =>
match
call_compare_method(left, "__ne__", right, globals, builtins, io) {
Ok(Some(value2)) =>
if is_notimplemented(value2) {
match eq_bool(left, right, globals, builtins, io) {
Ok(v) => Ok(!v)
Err(err) => Err(err)
}
} else {
truthy_from_value_with_env(value2, globals, builtins, io)
}
Ok(None) =>
match eq_bool(left, right, globals, builtins, io) {
Ok(v) => Ok(!v)
Err(err) => Err(err)
}
Err(err) => Err(err)
}
Err(err) => Err(err)
}
} else {
match call_compare_method(left, "__ne__", right, globals, builtins, io) {
Ok(Some(value)) =>
if is_notimplemented(value) {
match eq_bool(left, right, globals, builtins, io) {
Ok(v) => Ok(!v)
Err(err) => Err(err)
}
} else {
truthy_from_value_with_env(value, globals, builtins, io)
}
Ok(None) =>
match eq_bool(left, right, globals, builtins, io) {
Ok(v) => Ok(!v)
Err(err) => Err(err)
}
Err(err) => Err(err)
}
}
}
///|
fn ordering_bool(
left : Value,
right : Value,
method_name : String,
reverse_method_name : String,
op_symbol : String,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Result[Bool, RuntimeError] {
fn unsupported(
op_symbol : String,
left : Value,
right : Value,
) -> RuntimeError {
make_runtime_error(
RuntimeErrorKind::Type,
"'" +
op_symbol +
"' not supported between instances of '" +
type_name_from_value(left) +
"' and '" +
type_name_from_value(right) +
"'",
)
}
if is_strict_subclass(left, right) {
match
call_compare_method(
right, reverse_method_name, left, globals, builtins, io,
) {
Ok(Some(value)) =>
if is_notimplemented(value) {
match
call_compare_method(left, method_name, right, globals, builtins, io) {
Ok(Some(value2)) =>
if is_notimplemented(value2) {
Err(unsupported(op_symbol, left, right))
} else {
truthy_from_value_with_env(value2, globals, builtins, io)
}
Ok(None) => Err(unsupported(op_symbol, left, right))
Err(err) => Err(err)
}
} else {
truthy_from_value_with_env(value, globals, builtins, io)
}
Ok(None) =>
match
call_compare_method(left, method_name, right, globals, builtins, io) {
Ok(Some(value2)) =>
if is_notimplemented(value2) {
Err(unsupported(op_symbol, left, right))
} else {
truthy_from_value_with_env(value2, globals, builtins, io)
}
Ok(None) => Err(unsupported(op_symbol, left, right))
Err(err) => Err(err)
}
Err(err) => Err(err)
}
} else {
match call_compare_method(left, method_name, right, globals, builtins, io) {
Ok(Some(value)) =>
if is_notimplemented(value) {
match
call_compare_method(
right, reverse_method_name, left, globals, builtins, io,
) {
Ok(Some(value2)) =>
if is_notimplemented(value2) {
Err(unsupported(op_symbol, left, right))
} else {
truthy_from_value_with_env(value2, globals, builtins, io)
}
Ok(None) => Err(unsupported(op_symbol, left, right))
Err(err) => Err(err)
}
} else {
truthy_from_value_with_env(value, globals, builtins, io)
}
Ok(None) =>
match
call_compare_method(
right, reverse_method_name, left, globals, builtins, io,
) {
Ok(Some(value2)) =>
if is_notimplemented(value2) {
Err(unsupported(op_symbol, left, right))
} else {
truthy_from_value_with_env(value2, globals, builtins, io)
}
Ok(None) => Err(unsupported(op_symbol, left, right))
Err(err) => Err(err)
}
Err(err) => Err(err)
}
}
}
///|
fn in_bool(
needle : Value,
haystack : Value,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Result[Bool, RuntimeError] {
match haystack {
Value::List(values) | Value::Tuple(values) => {
let mut found = false
for item in values {
if is_value_identity(item, needle) || eq_value(item, needle) {
found = true
break
}
}
Ok(found)
}
Value::Set(values) =>
match set_find_index(values, needle) {
Ok(index) => Ok(index is Some(_))
Err(err) => Err(err)
}
Value::Bytes(bytes) | Value::ByteArray(bytes) | Value::MemoryView(bytes) =>
Ok(bytes_contains(bytes, needle))
Value::Dict(pairs) =>
match dict_find_index(pairs, needle) {
Ok(index) => Ok(index is Some(_))
Err(err) => Err(err)
}
Value::Str(text) =>
match needle {
Value::Str(needle) => Ok(text.contains(needle))
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::Type,
"'in ' requires string as left operand, not " +
type_name_from_value(needle),
),
)
}
_ =>
match haystack {
Value::Instance(_) =>
match
get_attr_from_value(haystack, "__contains__", globals, builtins, io) {
Ok(contains_method) => {
if contains_method is Value::None {
return Err(
make_runtime_error(
RuntimeErrorKind::Type,
"argument of type '" +
type_name_from_value(haystack) +
"' is not iterable",
),
)
}
let result = match
call_callable_with_env(
contains_method,
[needle],
[],
globals,
builtins,
io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
Ok(bool_from_value(result))
}
Err(err) =>
match err.kind {
RuntimeErrorKind::Attribute => {
// Fall back to iteration.
let iterator = match
iter_value_to_iterator(haystack, globals, builtins, io) {
Ok(v) => v
Err(e) => return Err(e)
}
let mut found = false
while true {
match iterator_next(iterator, None, globals, builtins, io) {
Ok(item) =>
if is_value_identity(item, needle) ||
eq_value(item, needle) {
found = true
break
}
Err(e) =>
if e.exc_type == "StopIteration" {
break
} else {
return Err(e)
}
}
}
Ok(found)
}
_ => Err(err)
}
}
_ => {
let iterator = match
iter_value_to_iterator(haystack, globals, builtins, io) {
Ok(v) => v
Err(e) => return Err(e)
}
let mut found = false
while true {
match iterator_next(iterator, None, globals, builtins, io) {
Ok(item) =>
if is_value_identity(item, needle) || eq_value(item, needle) {
found = true
break
}
Err(e) =>
if e.exc_type == "StopIteration" {
break
} else {
return Err(e)
}
}
}
Ok(found)
}
}
}
}
///|
fn compare_values_with_env(
op : CompareOp,
left : Value,
right : Value,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Result[Bool, RuntimeError] {
fn tuple_items_for_compare(v : Value) -> Array[Value]? {
match v {
Value::Tuple(values) => Some(values)
Value::Instance(inst) =>
match get_named_value(inst.dict, tuple_storage_name) {
Some(Value::Tuple(values)) => Some(values)
_ => None
}
_ => None
}
}
match op {
CompareOp::Eq => eq_bool(left, right, globals, builtins, io)
CompareOp::NotEq => ne_bool(left, right, globals, builtins, io)
CompareOp::Lt =>
match (tuple_items_for_compare(left), tuple_items_for_compare(right)) {
(Some(left), Some(right)) =>
match compare_sequence_values(left, right, globals, builtins, io) {
Ok(cmp) => Ok(cmp < 0)
Err(err) => Err(err)
}
_ =>
match (left, right) {
(Value::Tuple(left), Value::Tuple(right)) =>
match
compare_sequence_values(left, right, globals, builtins, io) {
Ok(cmp) => Ok(cmp < 0)
Err(err) => Err(err)
}
(Value::List(left), Value::List(right)) =>
match
compare_sequence_values(left, right, globals, builtins, io) {
Ok(cmp) => Ok(cmp < 0)
Err(err) => Err(err)
}
(Value::Set(left), Value::Set(right)) => {
let mut ok = true
for item in left {
let hit = match set_find_index(right, item) {
Ok(v) => v
Err(err) => return Err(err)
}
if hit is None {
ok = false
break
}
}
Ok(ok && left.length() < right.length())
}
(Value::Int(left), Value::Int(right)) => Ok(left < right)
(Value::Bool(left), Value::Bool(right)) =>
Ok((if left { 1N } else { 0N }) < (if right { 1N } else { 0N }))
(Value::Bool(left), Value::Int(right)) =>
Ok((if left { 1N } else { 0N }) < right)
(Value::Int(left), Value::Bool(right)) =>
Ok(left < (if right { 1N } else { 0N }))
(Value::Float(left), Value::Float(right)) => Ok(left < right)
(Value::Float(left), Value::Int(right)) =>
match bigint_to_double_checked(right) {
Ok(v) => Ok(left < v)
Err(err) => Err(err)
}
(Value::Int(left), Value::Float(right)) =>
match bigint_to_double_checked(left) {
Ok(v) => Ok(v < right)
Err(err) => Err(err)
}
(Value::Float(left), Value::Bool(right)) =>
Ok(left < (if right { 1.0 } else { 0.0 }))
(Value::Bool(left), Value::Float(right)) =>
Ok((if left { 1.0 } else { 0.0 }) < right)
(Value::Str(left), Value::Str(right)) =>
Ok(cmp_string_for_sorted(left, right) < 0)
(
Value::Bytes(left)
| Value::ByteArray(left)
| Value::MemoryView(left),
Value::Bytes(right)
| Value::ByteArray(right)
| Value::MemoryView(right),
) => Ok(compare_bytes_like(left, right) < 0)
_ =>
ordering_bool(
left, right, "__lt__", "__gt__", "<", globals, builtins, io,
)
}
}
CompareOp::Lte =>
match (tuple_items_for_compare(left), tuple_items_for_compare(right)) {
(Some(left), Some(right)) =>
match compare_sequence_values(left, right, globals, builtins, io) {
Ok(cmp) => Ok(cmp <= 0)
Err(err) => Err(err)
}
_ =>
match (left, right) {
(Value::Tuple(left), Value::Tuple(right)) =>
match
compare_sequence_values(left, right, globals, builtins, io) {
Ok(cmp) => Ok(cmp <= 0)
Err(err) => Err(err)
}
(Value::List(left), Value::List(right)) =>
match
compare_sequence_values(left, right, globals, builtins, io) {
Ok(cmp) => Ok(cmp <= 0)
Err(err) => Err(err)
}
(Value::Set(left), Value::Set(right)) => {
let mut ok = true
for item in left {
let hit = match set_find_index(right, item) {
Ok(v) => v
Err(err) => return Err(err)
}
if hit is None {
ok = false
break
}
}
Ok(ok)
}
(Value::Int(left), Value::Int(right)) => Ok(left <= right)
(Value::Bool(left), Value::Bool(right)) =>
Ok((if left { 1N } else { 0N }) <= (if right { 1N } else { 0N }))
(Value::Bool(left), Value::Int(right)) =>
Ok((if left { 1N } else { 0N }) <= right)
(Value::Int(left), Value::Bool(right)) =>
Ok(left <= (if right { 1N } else { 0N }))
(Value::Float(left), Value::Float(right)) => Ok(left <= right)
(Value::Float(left), Value::Int(right)) =>
match bigint_to_double_checked(right) {
Ok(v) => Ok(left <= v)
Err(err) => Err(err)
}
(Value::Int(left), Value::Float(right)) =>
match bigint_to_double_checked(left) {
Ok(v) => Ok(v <= right)
Err(err) => Err(err)
}
(Value::Float(left), Value::Bool(right)) =>
Ok(left <= (if right { 1.0 } else { 0.0 }))
(Value::Bool(left), Value::Float(right)) =>
Ok((if left { 1.0 } else { 0.0 }) <= right)
(Value::Str(left), Value::Str(right)) =>
Ok(cmp_string_for_sorted(left, right) <= 0)
(
Value::Bytes(left)
| Value::ByteArray(left)
| Value::MemoryView(left),
Value::Bytes(right)
| Value::ByteArray(right)
| Value::MemoryView(right),
) => Ok(compare_bytes_like(left, right) <= 0)
_ =>
ordering_bool(
left, right, "__le__", "__ge__", "<=", globals, builtins, io,
)
}
}
CompareOp::Gt =>
match (tuple_items_for_compare(left), tuple_items_for_compare(right)) {
(Some(left), Some(right)) =>
match compare_sequence_values(left, right, globals, builtins, io) {
Ok(cmp) => Ok(cmp > 0)
Err(err) => Err(err)
}
_ =>
match (left, right) {
(Value::Tuple(left), Value::Tuple(right)) =>
match
compare_sequence_values(left, right, globals, builtins, io) {
Ok(cmp) => Ok(cmp > 0)
Err(err) => Err(err)
}
(Value::List(left), Value::List(right)) =>
match
compare_sequence_values(left, right, globals, builtins, io) {
Ok(cmp) => Ok(cmp > 0)
Err(err) => Err(err)
}
(Value::Set(left), Value::Set(right)) => {
let mut ok = true
for item in right {
let hit = match set_find_index(left, item) {
Ok(v) => v
Err(err) => return Err(err)
}
if hit is None {
ok = false
break
}
}
Ok(ok && left.length() > right.length())
}
(Value::Int(left), Value::Int(right)) => Ok(left > right)
(Value::Bool(left), Value::Bool(right)) =>
Ok((if left { 1N } else { 0N }) > (if right { 1N } else { 0N }))
(Value::Bool(left), Value::Int(right)) =>
Ok((if left { 1N } else { 0N }) > right)
(Value::Int(left), Value::Bool(right)) =>
Ok(left > (if right { 1N } else { 0N }))
(Value::Float(left), Value::Float(right)) => Ok(left > right)
(Value::Float(left), Value::Int(right)) =>
match bigint_to_double_checked(right) {
Ok(v) => Ok(left > v)
Err(err) => Err(err)
}
(Value::Int(left), Value::Float(right)) =>
match bigint_to_double_checked(left) {
Ok(v) => Ok(v > right)
Err(err) => Err(err)
}
(Value::Float(left), Value::Bool(right)) =>
Ok(left > (if right { 1.0 } else { 0.0 }))
(Value::Bool(left), Value::Float(right)) =>
Ok((if left { 1.0 } else { 0.0 }) > right)
(Value::Str(left), Value::Str(right)) =>
Ok(cmp_string_for_sorted(left, right) > 0)
(
Value::Bytes(left)
| Value::ByteArray(left)
| Value::MemoryView(left),
Value::Bytes(right)
| Value::ByteArray(right)
| Value::MemoryView(right),
) => Ok(compare_bytes_like(left, right) > 0)
_ =>
ordering_bool(
left, right, "__gt__", "__lt__", ">", globals, builtins, io,
)
}
}
CompareOp::Gte =>
match (tuple_items_for_compare(left), tuple_items_for_compare(right)) {
(Some(left), Some(right)) =>
match compare_sequence_values(left, right, globals, builtins, io) {
Ok(cmp) => Ok(cmp >= 0)
Err(err) => Err(err)
}
_ =>
match (left, right) {
(Value::Tuple(left), Value::Tuple(right)) =>
match
compare_sequence_values(left, right, globals, builtins, io) {
Ok(cmp) => Ok(cmp >= 0)
Err(err) => Err(err)
}
(Value::List(left), Value::List(right)) =>
match
compare_sequence_values(left, right, globals, builtins, io) {
Ok(cmp) => Ok(cmp >= 0)
Err(err) => Err(err)
}
(Value::Set(left), Value::Set(right)) => {
let mut ok = true
for item in right {
let hit = match set_find_index(left, item) {
Ok(v) => v
Err(err) => return Err(err)
}
if hit is None {
ok = false
break
}
}
Ok(ok)
}
(Value::Int(left), Value::Int(right)) => Ok(left >= right)
(Value::Bool(left), Value::Bool(right)) =>
Ok((if left { 1N } else { 0N }) >= (if right { 1N } else { 0N }))
(Value::Bool(left), Value::Int(right)) =>
Ok((if left { 1N } else { 0N }) >= right)
(Value::Int(left), Value::Bool(right)) =>
Ok(left >= (if right { 1N } else { 0N }))
(Value::Float(left), Value::Float(right)) => Ok(left >= right)
(Value::Float(left), Value::Int(right)) =>
match bigint_to_double_checked(right) {
Ok(v) => Ok(left >= v)
Err(err) => Err(err)
}
(Value::Int(left), Value::Float(right)) =>
match bigint_to_double_checked(left) {
Ok(v) => Ok(v >= right)
Err(err) => Err(err)
}
(Value::Float(left), Value::Bool(right)) =>
Ok(left >= (if right { 1.0 } else { 0.0 }))
(Value::Bool(left), Value::Float(right)) =>
Ok((if left { 1.0 } else { 0.0 }) >= right)
(Value::Str(left), Value::Str(right)) =>
Ok(cmp_string_for_sorted(left, right) >= 0)
(
Value::Bytes(left)
| Value::ByteArray(left)
| Value::MemoryView(left),
Value::Bytes(right)
| Value::ByteArray(right)
| Value::MemoryView(right),
) => Ok(compare_bytes_like(left, right) >= 0)
_ =>
ordering_bool(
left, right, "__ge__", "__le__", ">=", globals, builtins, io,
)
}
}
CompareOp::Is => Ok(is_value_identity(left, right))
CompareOp::IsNot => Ok(!is_value_identity(left, right))
CompareOp::In => in_bool(left, right, globals, builtins, io)
CompareOp::NotIn =>
match in_bool(left, right, globals, builtins, io) {
Ok(v) => Ok(!v)
Err(err) => Err(err)
}
}
}