///|
fn Vm::binary(
  self : Vm,
  op : String,
  a : Value,
  b : Value,
  loc : Location,
) -> Value raise {
  match op {
    "+" =>
      match (a, b) {
        (Num(x), Num(y)) => finite(x + y, loc)
        (ArrayValue(x), ArrayValue(y)) => {
          self.tick(loc)
          if x.length() > self.limits.steps - y.length() ||
            x.length() > self.limits.output_bytes - y.length() {
            raise problem(Limit, loc, "array size limit exceeded")
          }
          ArrayValue(x + y)
        }
        (ObjectValue(x, xt), ObjectValue(y, yt)) => {
          if xt > self.limits.steps - yt {
            raise problem(Limit, loc, "object layer limit exceeded")
          }
          let layers = self.objects[x].layers[:xt].to_owned() +
            self.objects[y].layers[:yt].to_owned()
          self.object_value(layers)
        }
        (Str(x), _) => self.join_string(x, self.coerce_string(b, loc), loc)
        (_, Str(y)) => self.join_string(self.coerce_string(a, loc), y, loc)
        _ => {
          let message = match a {
            Num(_) => "Unexpected type " + value_type(b) + ", expected number"
            ArrayValue(_) =>
              "Unexpected type " + value_type(b) + ", expected array"
            ObjectValue(_, _) =>
              "Unexpected type " + value_type(b) + ", expected object"
            _ => "Unexpected type " + value_type(a)
          }
          raise problem(Type, loc, message)
        }
      }
    "-" => finite(number_value(a, loc) - number_value(b, loc), loc)
    "*" => finite(number_value(a, loc) * number_value(b, loc), loc)
    "/" | "%" => {
      if op == "%" && a is Str(format) {
        return Str(self.format_string(format, b, loc))
      }
      let x = number_value(a, loc)
      let y = number_value(b, loc)
      if y == 0 {
        raise problem(Runtime, loc, "Division by zero.")
      }
      finite(if op == "/" { x / y } else { x % y }, loc)
    }
    "==" => Bool(self.equal(a, b, loc))
    "!=" => Bool(!self.equal(a, b, loc))
    "<" | "<=" | ">" | ">=" => {
      let c = self.compare_values(a, b, loc)
      Bool(
        match op {
          "<" => c < 0
          "<=" => c <= 0
          ">" => c > 0
          _ => c >= 0
        },
      )
    }
    "in" => {
      let k = text_value(a, loc)
      guard b is ObjectValue(id, top) else {
        raise problem(Type, loc, "right operand of in must be object")
      }
      Bool(self.has_field(id, top, k))
    }
    "&" | "|" | "^" | "<<" | ">>" => {
      let x = bitwise_integer(a, loc)
      let y = bitwise_integer(b, loc)
      let n = match op {
        "&" => x.land(y)
        "|" => x.lor(y)
        "^" => x.lxor(y)
        _ => {
          if number_value(b, loc) < 0 {
            raise problem(Runtime, loc, "negative shift")
          }
          let shift = (y % 64L).to_int()
          if op == "<<" {
            x << shift
          } else {
            x >> shift
          }
        }
      }
      Num(n.to_double())
    }
    _ => raise problem(Runtime, loc, "invalid operator '\{op}'")
  }
}

///|
fn bitwise_integer(v : Value, loc : Location) -> Int64 raise {
  let n = number_value(v, loc)
  // Go's floating-point MaxInt64 bound rounds to 2^63. Its conversion at that
  // bound depends on the native architecture and is defined by the FFI helper.
  if n < -9223372036854775808.0 || n > 9223372036854775808.0 {
    raise problem(
      Runtime,
      loc,
      "Bitwise operator argument " +
      diagnostic_number(n) +
      " outside of range [-9223372036854775808, 9223372036854775807]",
    )
  }
  reference_int64(n)
}

///|
fn reference_int64(n : Double) -> Int64 {
  @number.reference_int64(n)
}

///|
fn Vm::compare_arrays(
  self : Vm,
  a : Array[Int],
  b : Array[Int],
  loc : Location,
) -> Int raise {
  if self.depth >= self.limits.depth {
    raise problem(Limit, loc, "comparison depth limit exceeded")
  }
  self.depth += 1
  defer {
    self.depth -= 1
  }
  for i in 0.. Int raise {
  match a {
    Num(x) => compare_numbers(x, number_value(b, loc))
    Str(x) => compare_unicode(x, text_value(b, loc))
    ArrayValue(x) => self.compare_arrays(x, array_value(b, loc), loc)
    _ => raise problem(Type, loc, "Unexpected type " + value_type(a))
  }
}

///|
fn compare_numbers(a : Double, b : Double) -> Int {
  if a < b {
    -1
  } else if a > b {
    1
  } else {
    0
  }
}

///|
fn compare_unicode(a : String, b : String) -> Int {
  let x = a.to_array()
  let y = b.to_array()
  for i in 0.. Value raise {
  if a.length() > self.limits.output_bytes - b.length() {
    raise problem(Limit, loc, "string size limit exceeded")
  }
  Str(a + b)
}

///|
fn Vm::coerce_string(self : Vm, v : Value, loc : Location) -> String raise {
  match v {
    Str(s) => s
    _ =>
      render_json(
        self.manifest(v, loc, 0),
        false,
        0,
        self.limits.output_bytes,
        loc,
      )
  }
}

///|
fn Vm::equal(self : Vm, a : Value, b : Value, loc : Location) -> Bool raise {
  if self.depth >= self.limits.depth {
    raise problem(Limit, loc, "comparison depth limit exceeded")
  }
  self.depth += 1
  defer {
    self.depth -= 1
  }
  self.equal_inner(a, b, loc)
}

///|
fn Vm::equal_inner(
  self : Vm,
  a : Value,
  b : Value,
  loc : Location,
) -> Bool raise {
  self.tick(loc)
  if value_type(a) != value_type(b) {
    return false
  }
  match (a, b) {
    (Closure(_, _, _), _)
    | (_, Closure(_, _, _))
    | (Builtin(_), _)
    | (_, Builtin(_))
    | (Native(_), _)
    | (_, Native(_)) =>
      raise problem(Type, loc, "Cannot test equality of functions")
    (Nil, Nil) => true
    (Bool(x), Bool(y)) => x == y
    (Num(x), Num(y)) => x == y
    (Str(x), Str(y)) => x == y
    (ArrayValue(x), ArrayValue(y)) => {
      if x.length() != y.length() {
        return false
      }
      for i in 0.. {
      let keys = self.keys(x, xt, false)
      if keys != self.keys(y, yt, false) {
        return false
      }
      for k in keys {
        if !self.equal(
            self.field(x, xt, k, loc),
            self.field(y, yt, k, loc),
            loc,
          ) {
          return false
        }
      }
      true
    }
    _ => false
  }
}