use pyo3::ffi;
use pyo3::prelude::*;
use pyo3::types::PyString;
#[cfg(target_arch = "x86_64")]
use crate::kernels;
use crate::{compute, Baseline, Isa, Lcs, Lev, UniView};
const _: () = assert!(
ffi::PyUnicode_1BYTE_KIND == crate::KIND_1BYTE
&& ffi::PyUnicode_2BYTE_KIND == crate::KIND_2BYTE
&& ffi::PyUnicode_4BYTE_KIND == crate::KIND_4BYTE
);
#[cfg(not(all(
Py_3_14,
target_endian = "little",
not(all(Py_GIL_DISABLED, target_env = "msvc"))
)))]
#[inline(always)]
unsafe fn view(s: &Bound<'_, PyString>) -> UniView {
let ptr = s.as_ptr();
let kind = ffi::PyUnicode_KIND(ptr);
UniView {
kind,
#[cfg(not(Py_3_14))]
ascii: ffi::PyUnicode_IS_ASCII(ptr) != 0,
#[cfg(Py_3_14)]
ascii: false,
data: ffi::PyUnicode_DATA(ptr) as *const u8,
len: ffi::PyUnicode_GET_LENGTH(ptr) as usize,
}
}
#[cfg(all(
Py_3_14,
target_endian = "little",
not(all(Py_GIL_DISABLED, target_env = "msvc"))
))]
#[inline(always)]
unsafe fn view(s: &Bound<'_, PyString>) -> UniView {
const KIND_SHIFT: u32 = if cfg!(Py_GIL_DISABLED) { 8 } else { 2 };
let ptr = s.as_ptr();
let obj = ptr as *mut ffi::PyASCIIObject;
let state = (*obj).state;
let kind = ((state >> KIND_SHIFT) & 0b111) as std::os::raw::c_uint;
let compact = (state >> (KIND_SHIFT + 3)) & 1 != 0;
let ascii = (state >> (KIND_SHIFT + 4)) & 1 != 0;
let data = if compact {
if ascii {
obj.add(1) as *const u8
} else {
(ptr as *mut ffi::PyCompactUnicodeObject).add(1) as *const u8
}
} else {
(*(ptr as *mut ffi::PyUnicodeObject)).data.any as *const u8
};
debug_assert_eq!(
kind,
ffi::PyUnicode_KIND(ptr),
"state bitfield layout changed"
);
debug_assert_eq!(
data,
ffi::PyUnicode_DATA(ptr) as *const u8,
"compact data offset changed"
);
UniView {
kind,
ascii,
data,
len: (*obj).length as usize,
}
}
#[inline(always)]
pub(crate) unsafe fn distance_in<X: Isa>(
s1: Borrowed<'_, '_, PyString>,
s2: Borrowed<'_, '_, PyString>,
) -> usize {
if s1.is(s2) {
return 0;
}
compute::<Lev<X>>(&view(&s1), &view(&s2))
}
#[inline(always)]
pub(crate) unsafe fn ratio_in<X: Isa>(
s1: Borrowed<'_, '_, PyString>,
s2: Borrowed<'_, '_, PyString>,
) -> f64 {
if s1.is(s2) {
return 1.0;
}
let (v1, v2) = (view(&s1), view(&s2));
let total = v1.len + v2.len;
if total == 0 {
return 1.0;
}
1.0 - compute::<Lcs<X>>(&v1, &v2) as f64 / total as f64
}
macro_rules! entry_points {
($distance:path, $ratio:path) => {
#[pyfunction]
#[pyo3(pass_module, signature = (s1, s2, /))]
pub(crate) fn distance(
_m: &Bound<'_, PyModule>,
s1: &Bound<'_, PyString>,
s2: &Bound<'_, PyString>,
) -> usize {
unsafe { $distance(s1.as_borrowed(), s2.as_borrowed()) }
}
#[pyfunction]
#[pyo3(pass_module, signature = (s1, s2, /))]
pub(crate) fn ratio(
_m: &Bound<'_, PyModule>,
s1: &Bound<'_, PyString>,
s2: &Bound<'_, PyString>,
) -> f64 {
unsafe { $ratio(s1.as_borrowed(), s2.as_borrowed()) }
}
};
}
#[cfg(target_arch = "x86_64")]
pub(crate) use entry_points;
entry_points!(distance_in::<Baseline>, ratio_in::<Baseline>);
#[pymodule]
fn _lev(m: &Bound<'_, PyModule>) -> PyResult<()> {
#[cfg(target_arch = "x86_64")]
if kernels::x86_64::v2::detected() {
use kernels::x86_64::v2::py;
m.add_function(wrap_pyfunction!(py::distance, m)?)?;
m.add_function(wrap_pyfunction!(py::ratio, m)?)?;
return Ok(());
}
m.add_function(wrap_pyfunction!(distance, m)?)?;
m.add_function(wrap_pyfunction!(ratio, m)?)?;
Ok(())
}