use super::*;
pub(super) fn match_callback_decision(value: &Bound<'_, PyAny>) -> PyResult<ConditionResult> {
if value.is_none() {
Ok(ConditionResult::Inconclusive)
} else if let Ok(value) = value.cast::<pyo3::types::PyBool>() {
Ok(value.extract::<bool>()?.into())
} else if let Ok(value) = value.extract::<PythonCondition>() {
value
.condition
.evaluate(&None)
.map_err(exceptions::PyValueError::new_err)
} else {
Err(exceptions::PyTypeError::new_err(
"Match callbacks must return True, False, None, or a Condition",
))
}
}
pub(super) fn report_match_callback_result(result: PyResult<ConditionResult>) -> ConditionResult {
match result {
Ok(decision) => decision,
Err(error) => {
error!("Python pattern match callback failed: {error}");
ConditionResult::Inconclusive
}
}
}
pub(super) fn relation(lhs: Pattern, rhs: Pattern, op: CompareOp) -> PythonCondition {
let relation = match op {
CompareOp::Eq => Relation::Eq(lhs, rhs),
CompareOp::Ne => Relation::Ne(lhs, rhs),
CompareOp::Lt => Relation::Lt(lhs, rhs),
CompareOp::Le => Relation::Le(lhs, rhs),
CompareOp::Gt => Relation::Gt(lhs, rhs),
CompareOp::Ge => Relation::Ge(lhs, rhs),
};
PythonCondition {
condition: relation.into(),
}
}
pub(super) fn matching_relation(value: Relation) -> Result<PatternRestriction, &'static str> {
let (lhs, rhs) = match &value {
Relation::Eq(a, b)
| Relation::Ne(a, b)
| Relation::Lt(a, b)
| Relation::Le(a, b)
| Relation::Gt(a, b)
| Relation::Ge(a, b) => (a, b),
_ => unreachable!(),
};
let mut wildcards = Vec::new();
let mut valid = true;
for operand in [lhs, rhs] {
operand.visitor(&mut |p| match p {
Pattern::Wildcard(name, _) => wildcards.push(*name),
Pattern::Transformer(_) | Pattern::Alternative(_) => valid = false,
Pattern::Fn(name, _) if name.get_wildcard_level() > 0 => valid = false,
_ => {}
});
}
if !valid {
return Err(
"Matching comparisons require expression operands with fixed function names. Transformers can be used in Transformer.if_then conditions.",
);
}
Ok(PatternRestriction::MatchStack(Box::new(
move |stack: &MatchStack| {
if wildcards.iter().any(|name| stack.get(*name).is_none()) {
return ConditionResult::Inconclusive;
}
let (a, b) = match &value {
Relation::Eq(a, b)
| Relation::Ne(a, b)
| Relation::Lt(a, b)
| Relation::Le(a, b)
| Relation::Gt(a, b)
| Relation::Ge(a, b) => (a, b),
_ => unreachable!(),
};
let a = a.replace_wildcards_with_matches(stack);
let b = b.replace_wildcards_with_matches(stack);
match value {
Relation::Eq(..) => crate::id::equal_atoms(a.as_view(), b.as_view()).into(),
Relation::Ne(..) => (!crate::id::equal_atoms(a.as_view(), b.as_view())).into(),
_ => {
let Some(order) = crate::id::compare_real_atoms(a.as_view(), b.as_view())
else {
return ConditionResult::Inconclusive;
};
match value {
Relation::Lt(..) => order.is_lt(),
Relation::Le(..) => order.is_le(),
Relation::Gt(..) => order.is_gt(),
Relation::Ge(..) => order.is_ge(),
_ => unreachable!(),
}
.into()
}
}
},
)))
}
fn rational_to_python<'py>(value: Rational, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
if value.is_integer() {
return value.numerator().clone().into_bound_py_any(py);
}
py.import("fractions")?
.getattr("Fraction")?
.call1((value.numerator().clone(), value.denominator().clone()))
}
pub(super) fn numeric_parts<'py>(
atom: AtomView<'_>,
py: Python<'py>,
) -> PyResult<Option<(Bound<'py, PyAny>, Bound<'py, PyAny>)>> {
if let Some(value) = crate::id::exact_numeric_value(atom) {
return Ok(Some((
rational_to_python(value.re, py)?,
rational_to_python(value.im, py)?,
)));
}
if let AtomView::Num(n) = atom
&& let Coefficient::Infinity(Some(phase)) = n.get_coeff_view().to_owned()
&& phase.im.is_zero()
{
return Ok(Some((
if phase.re.is_negative() {
f64::NEG_INFINITY
} else {
f64::INFINITY
}
.into_bound_py_any(py)?,
0.into_bound_py_any(py)?,
)));
}
Ok(None)
}
pub(super) fn expression_eq(
lhs: &PythonExpression,
rhs: &Bound<'_, PyAny>,
) -> PyResult<Option<bool>> {
let py = rhs.py();
let left = numeric_parts(lhs.expr.as_view(), py)?;
if let Ok(other) = rhs.extract::<PythonExpression>() {
if let (Some((lr, li)), Some((rr, ri))) = (left, numeric_parts(other.expr.as_view(), py)?) {
return Ok(Some(lr.eq(rr)? && li.eq(ri)?));
}
return Ok(Some(lhs.expr == other.expr));
}
if rhs.is_instance_of::<PythonFloat>() || rhs.is_instance_of::<PythonComplexFloat>() {
let value = rhs.extract::<PythonMultiPrecisionComplex>()?.0;
let Some((lr, li)) = left else {
return Ok(Some(false));
};
let component = |f: Float| -> PyResult<Bound<'_, PyAny>> {
if let Some(r) = f.try_to_rational() {
rational_to_python(r, py)
} else {
f.to_f64().into_bound_py_any(py)
}
};
return Ok(Some(
lr.eq(component(value.re)?)? && li.eq(component(value.im)?)?,
));
}
if !rhs.is_instance(&py.import("numbers")?.getattr("Number")?)?
&& !rhs.is_instance(&py.import("decimal")?.getattr("Decimal")?)?
{
return Ok(None);
}
let Some((lr, li)) = left else {
return Ok(Some(false));
};
Ok(Some(
lr.eq(rhs.getattr("real")?)? && li.eq(rhs.getattr("imag")?)?,
))
}
pub(super) fn expression_compare(
lhs: &PythonExpression,
rhs: &Bound<'_, PyAny>,
op: CompareOp,
) -> PyResult<Py<PyAny>> {
let py = rhs.py();
if matches!(op, CompareOp::Eq | CompareOp::Ne) {
return match expression_eq(lhs, rhs)? {
Some(equal) => (if matches!(op, CompareOp::Eq) {
equal
} else {
!equal
})
.into_py_any(py),
None => Ok(py.NotImplemented()),
};
}
let Ok(other) = rhs.extract::<ConvertibleToOpenPattern>() else {
return Ok(py.NotImplemented());
};
relation(lhs.expr.to_pattern(), other.to_pattern()?.expr, op).into_py_any(py)
}
pub(super) fn expression_hash(atom: AtomView<'_>, py: Python<'_>) -> PyResult<isize> {
if let Some((re, im)) = numeric_parts(atom, py)? {
let multiplier: isize = py
.import("sys")?
.getattr("hash_info")?
.getattr("imag")?
.extract()?;
let hash = re.hash()?.wrapping_add(multiplier.wrapping_mul(im.hash()?));
return Ok(if hash == -1 { -2 } else { hash });
}
let mut hasher = ahash::AHasher::default();
atom.hash(&mut hasher);
let hash = hasher.finish() as isize;
Ok(if hash == -1 { -2 } else { hash })
}