use vyre_foundation::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
use crate::region::wrap_anonymous;
pub const SCAN_SUBSTRING_OP_ID: &str = "vyre-libs::scan::substring_search";
#[must_use]
pub fn substring_search(
haystack: &str,
needle: &str,
matches: &str,
haystack_len: u32,
needle_len: u32,
) -> Program {
build_substring_program(haystack, needle, matches, haystack_len, needle_len)
}
fn build_substring_program(
haystack: &str,
needle: &str,
matches: &str,
haystack_len: u32,
needle_len: u32,
) -> Program {
let counted_storage = |name: &str, binding, count| {
let decl = BufferDecl::storage(name, binding, BufferAccess::ReadOnly, DataType::U32);
if count == 0 {
decl
} else {
decl.with_count(count)
}
};
let output_count = haystack_len.max(1);
let visible_output_bytes = (haystack_len as usize).saturating_mul(4);
let output = BufferDecl::output(matches, 2, DataType::U32)
.with_count(output_count)
.with_output_byte_range(0..visible_output_bytes);
let i = Expr::var("i");
let mut check_body: Vec<Node> = vec![Node::let_bind("ok", Expr::u32(1))];
check_body.push(Node::loop_for(
"k",
Expr::u32(0),
Expr::u32(needle_len),
vec![Node::assign(
"ok",
Expr::bitand(
Expr::var("ok"),
Expr::select(
Expr::eq(
Expr::load(haystack, Expr::add(i.clone(), Expr::var("k"))),
Expr::load(needle, Expr::var("k")),
),
Expr::u32(1),
Expr::u32(0),
),
),
)],
));
check_body.push(Node::Store {
buffer: matches.into(),
index: i.clone(),
value: Expr::var("ok"),
});
let body = vec![
Node::let_bind("i", Expr::InvocationId { axis: 0 }),
Node::let_bind("haystack_len", Expr::buf_len(haystack)),
Node::if_then(
Expr::and(
Expr::le(Expr::u32(needle_len), Expr::var("haystack_len")),
Expr::le(
i.clone(),
Expr::sub(Expr::var("haystack_len"), Expr::u32(needle_len)),
),
),
check_body,
),
];
Program::wrapped(
vec![
counted_storage(haystack, 0, haystack_len),
counted_storage(needle, 1, needle_len),
output,
],
[64, 1, 1],
vec![wrap_anonymous(SCAN_SUBSTRING_OP_ID, body)],
)
}
inventory::submit! {
vyre_foundation::operation::OperationRegistration {
semantic_version: 1,
signature: None,
tier: vyre_foundation::operation::OperationTier::Library,
laws: &[],
tolerance: vyre_foundation::operation::TolerancePolicy::EXACT,
id: SCAN_SUBSTRING_OP_ID,
build: Some(|| substring_search("haystack", "needle", "matches", 8, 3)),
test_inputs: Some(|| {
let to_u32_vec = |s: &str| s.bytes().map(u32::from).collect::<Vec<_>>();
vec![
vec![
crate::fixture_bytes::u32_bytes(&to_u32_vec("abcabc++")),
crate::fixture_bytes::u32_bytes(&to_u32_vec("abc")),
],
vec![
crate::fixture_bytes::u32_bytes(&to_u32_vec("xyzxyzxy")),
crate::fixture_bytes::u32_bytes(&to_u32_vec("xyz")),
]
]
}),
expected_output: Some(|| {
let case0 = crate::fixture_bytes::u32_bytes(&[1u32, 0, 0, 1, 0, 0, 0, 0]);
let case1 = crate::fixture_bytes::u32_bytes(&[1u32, 0, 0, 1, 0, 0, 0, 0]);
vec![vec![case0], vec![case1]]
}),
category: Some("scan"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_uses_canonical_scan_op_id() {
let program = substring_search("haystack", "needle", "matches", 8, 3);
let [Node::Region { generator, .. }] = program.entry() else {
panic!("expected substring search to emit one scan region");
};
assert_eq!(generator.as_str(), SCAN_SUBSTRING_OP_ID);
}
}