use std::cmp::Ordering;
use std::slice::from_ref;
use super::bytecode::{BinKind, PLit, PPat, UnKind};
use super::value::Value;
use anyhow::{Result, anyhow, bail};
pub(super) fn apply_bin(op: BinKind, l: &Value, r: &Value) -> Result<Value> {
use BinKind::*;
Ok(match op {
Add | Sub | Mul | Div | Rem => return arith(op, l, r),
Eq => Value::Bool(l.eq_value(r)),
Ne => Value::Bool(!l.eq_value(r)),
Lt => Value::Bool(compare_values(l, r)? == Ordering::Less),
Le => Value::Bool(compare_values(l, r)? != Ordering::Greater),
Gt => Value::Bool(compare_values(l, r)? == Ordering::Greater),
Ge => Value::Bool(compare_values(l, r)? != Ordering::Less),
BitAnd => int_bin(l, r, |a, b| a & b)?,
BitOr => int_bin(l, r, |a, b| a | b)?,
BitXor => int_bin(l, r, |a, b| a ^ b)?,
Shl => int_bin(l, r, |a, b| a << b)?,
Shr => int_bin(l, r, |a, b| a >> b)?,
})
}
pub(super) fn apply_bin_imm(op: BinKind, l: &Value, imm: i64) -> Result<Value> {
use BinKind::*;
if let Value::Int(a) = l {
let a = *a;
return Ok(match op {
Add => Value::Int(a.wrapping_add(imm)),
Sub => Value::Int(a.wrapping_sub(imm)),
Mul => Value::Int(a.wrapping_mul(imm)),
Div => {
if imm == 0 {
bail!("attempt to divide by zero");
}
Value::Int(a.wrapping_div(imm))
}
Rem => {
if imm == 0 {
bail!("attempt to calculate the remainder with a divisor of zero");
}
Value::Int(a.wrapping_rem(imm))
}
Eq => Value::Bool(a == imm),
Ne => Value::Bool(a != imm),
Lt => Value::Bool(a < imm),
Le => Value::Bool(a <= imm),
Gt => Value::Bool(a > imm),
Ge => Value::Bool(a >= imm),
BitAnd => Value::Int(a & imm),
BitOr => Value::Int(a | imm),
BitXor => Value::Int(a ^ imm),
Shl => Value::Int(a << imm),
Shr => Value::Int(a >> imm),
});
}
apply_bin(op, l, &Value::Int(imm))
}
pub(super) fn cmp_test(op: BinKind, l: &Value, r: &Value) -> Result<bool> {
use BinKind::*;
Ok(match op {
Eq => l.eq_value(r),
Ne => !l.eq_value(r),
Lt => compare_values(l, r)? == Ordering::Less,
Le => compare_values(l, r)? != Ordering::Greater,
Gt => compare_values(l, r)? == Ordering::Greater,
Ge => compare_values(l, r)? != Ordering::Less,
_ => unreachable!("compare jump carries a non-comparison operator"),
})
}
pub(super) fn cmp_test_imm(op: BinKind, l: &Value, imm: i64) -> Result<bool> {
use BinKind::*;
if let Value::Int(a) = l {
let a = *a;
return Ok(match op {
Eq => a == imm,
Ne => a != imm,
Lt => a < imm,
Le => a <= imm,
Gt => a > imm,
Ge => a >= imm,
_ => unreachable!("compare jump carries a non-comparison operator"),
});
}
cmp_test(op, l, &Value::Int(imm))
}
fn arith(op: BinKind, l: &Value, r: &Value) -> Result<Value> {
use BinKind::*;
if let (Add, Value::Str(a), Value::Str(b)) = (op, l, r) {
let mut out = String::with_capacity(a.len() + b.len());
out.push_str(a);
out.push_str(b);
return Ok(Value::str(out));
}
match (l, r) {
(Value::Int(a), Value::Int(b)) => {
let (a, b) = (*a, *b);
Ok(Value::Int(match op {
Add => a.wrapping_add(b),
Sub => a.wrapping_sub(b),
Mul => a.wrapping_mul(b),
Div => {
if b == 0 {
bail!("attempt to divide by zero");
}
a.wrapping_div(b)
}
Rem => {
if b == 0 {
bail!("attempt to calculate the remainder with a divisor of zero");
}
a.wrapping_rem(b)
}
_ => unreachable!(),
}))
}
(a, b) => {
let (x, y) = (to_float(a)?, to_float(b)?);
Ok(Value::Float(match op {
Add => x + y,
Sub => x - y,
Mul => x * y,
Div => x / y,
Rem => x % y,
_ => unreachable!(),
}))
}
}
}
fn int_bin(l: &Value, r: &Value, f: impl Fn(i64, i64) -> i64) -> Result<Value> {
match (l, r) {
(Value::Int(a), Value::Int(b)) => Ok(Value::Int(f(*a, *b))),
(Value::Bool(a), Value::Bool(b)) => Ok(Value::Bool(f(*a as i64, *b as i64) != 0)),
_ => bail!("bitwise operators need integers"),
}
}
pub(super) fn compare_values(l: &Value, r: &Value) -> Result<Ordering> {
Ok(match (l, r) {
(Value::Int(a), Value::Int(b)) => a.cmp(b),
(Value::Float(a), Value::Float(b)) => a
.partial_cmp(b)
.ok_or_else(|| anyhow!("cannot order NaN"))?,
(Value::Int(a), Value::Float(b)) => (*a as f64)
.partial_cmp(b)
.ok_or_else(|| anyhow!("cannot order NaN"))?,
(Value::Float(a), Value::Int(b)) => a
.partial_cmp(&(*b as f64))
.ok_or_else(|| anyhow!("cannot order NaN"))?,
(Value::Str(a), Value::Str(b)) => a.as_str().cmp(b.as_str()),
(Value::Char(a), Value::Char(b)) => a.cmp(b),
(Value::Bool(a), Value::Bool(b)) => a.cmp(b),
(a, b) => bail!("cannot compare {} and {}", a.type_name(), b.type_name()),
})
}
fn to_float(v: &Value) -> Result<f64> {
match v {
Value::Int(i) => Ok(*i as f64),
Value::Float(f) => Ok(*f),
other => bail!("expected a number, got {}", other.type_name()),
}
}
pub(super) fn apply_un(op: UnKind, v: &Value) -> Result<Value> {
Ok(match (op, v) {
(UnKind::Neg, Value::Int(i)) => Value::Int(-*i),
(UnKind::Neg, Value::Float(f)) => Value::Float(-*f),
(UnKind::Not, Value::Bool(b)) => Value::Bool(!*b),
(UnKind::Not, Value::Int(i)) => Value::Int(!*i),
(op, v) => bail!("cannot apply {:?} to {}", op, v.type_name()),
})
}
pub(super) fn try_bind(pat: &PPat, val: &Value, define: &mut dyn FnMut(&str, Value)) -> bool {
match pat {
PPat::Wild | PPat::Rest => true,
PPat::Ident { name, sub } => {
if let Some(subpattern) = sub
&& !try_bind(subpattern, val, define)
{
return false;
}
define(name, val.clone());
true
}
PPat::Lit(literal) => literal_matches(literal, val),
PPat::Tuple(patterns) => match val {
Value::Tuple(items) => bind_seq(patterns, &items.borrow(), define),
Value::Unit if patterns.is_empty() => true,
_ => false,
},
PPat::TupleStruct { name, elems } => match val {
Value::Enum { variant, data, .. } => {
name.as_deref() == Some(&**variant) && bind_seq(elems, data, define)
}
Value::Struct(structure) => bind_seq(elems, &structure.values.borrow(), define),
Value::Unit => false,
other => name.as_deref() == Some("Some") && bind_seq(elems, from_ref(other), define),
},
PPat::Path { name } => match val {
Value::Enum { variant, .. } => name.as_deref() == Some(&**variant),
_ => false,
},
PPat::Struct { name, fields } => {
let Value::Struct(structure) = val else {
return false;
};
if let Some(pattern_name) = name
&& pattern_name != super::resolver::bare(structure.name())
{
return false;
}
for (field, pattern) in fields {
match structure.get(field) {
Some(value) if try_bind(pattern, &value, define) => {}
_ => return false,
}
}
true
}
PPat::Or(patterns) => patterns
.iter()
.any(|pattern| try_bind(pattern, val, define)),
PPat::Slice(patterns) => match val {
Value::Vec(items) => bind_seq(patterns, &items.borrow(), define),
_ => false,
},
PPat::Range { lo, hi, inclusive } => {
range_matches(lo.as_ref(), hi.as_ref(), *inclusive, |l| {
endpoint_cmp(l, val)
})
}
PPat::Unsupported => false,
}
}
fn endpoint_cmp(literal: &PLit, value: &Value) -> Option<Ordering> {
match (literal, value) {
(PLit::Int(a), Value::Int(b)) => Some(a.cmp(b)),
(PLit::Float(a), Value::Float(b)) => a.partial_cmp(b),
(PLit::Char(a), Value::Char(b)) => Some(a.cmp(b)),
_ => None,
}
}
pub(super) fn range_matches<L>(
lo: Option<&L>,
hi: Option<&L>,
inclusive: bool,
cmp: impl Fn(&L) -> Option<Ordering>,
) -> bool {
if let Some(l) = lo {
match cmp(l) {
Some(Ordering::Less | Ordering::Equal) => {}
_ => return false,
}
}
if let Some(h) = hi {
match cmp(h) {
Some(Ordering::Greater) => {}
Some(Ordering::Equal) if inclusive => {}
_ => return false,
}
}
true
}
fn bind_seq(patterns: &[PPat], vals: &[Value], define: &mut dyn FnMut(&str, Value)) -> bool {
if patterns.iter().any(|pattern| matches!(pattern, PPat::Rest)) {
let head_len = patterns
.iter()
.take_while(|pattern| !matches!(pattern, PPat::Rest))
.count();
for (pattern, value) in patterns.iter().take(head_len).zip(vals.iter()) {
if !try_bind(pattern, value, define) {
return false;
}
}
for (pattern, value) in patterns.iter().skip(head_len + 1).zip(vals.iter().rev()) {
if !try_bind(pattern, value, define) {
return false;
}
}
return true;
}
patterns.len() == vals.len()
&& patterns
.iter()
.zip(vals.iter())
.all(|(pattern, value)| try_bind(pattern, value, define))
}
fn literal_matches(literal: &PLit, value: &Value) -> bool {
match (literal, value) {
(PLit::Int(left), Value::Int(right)) => left == right,
(PLit::Float(left), Value::Float(right)) => left == right,
(PLit::Bool(left), Value::Bool(right)) => left == right,
(PLit::Str(left), Value::Str(right)) => left == right.as_str(),
(PLit::Char(left), Value::Char(right)) => left == right,
_ => false,
}
}