use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RuleHeader<'a> {
pub name: &'a str,
pub full_text: &'a str,
pub consumed: usize,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WhenThen<'a> {
pub condition: &'a str,
pub action: &'a str,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Operator<'a> {
pub op: &'a str,
pub consumed: usize,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Module<'a> {
pub name: &'a str,
pub body: &'a str,
pub consumed: usize,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Rule<'a> {
pub text: &'a str,
pub start: usize,
pub end: usize,
}
pub fn parse_rule_header_zero_copy(text: &str) -> Option<RuleHeader<'_>> {
let trimmed = text.trim_start();
if !trimmed.starts_with("rule") {
return None;
}
let skip = text.len() - trimmed.len();
let after_rule = trimmed[4..].trim_start();
if after_rule.starts_with('"') {
if let Some(end_quote) = memchr::memchr(b'"', &after_rule.as_bytes()[1..]) {
let name = &after_rule[1..end_quote + 1];
let consumed = skip + 4 + (trimmed[4..].len() - after_rule.len()) + end_quote + 2;
return Some(RuleHeader {
name,
full_text: &text[..consumed],
consumed,
});
}
}
let name_end = after_rule
.find(|c: char| !c.is_alphanumeric() && c != '_')
.unwrap_or(after_rule.len());
if name_end > 0 {
let name = &after_rule[..name_end];
let consumed = skip + 4 + (trimmed[4..].len() - after_rule.len()) + name_end;
return Some(RuleHeader {
name,
full_text: &text[..consumed],
consumed,
});
}
None
}
pub fn parse_when_then_zero_copy(body: &str) -> Option<WhenThen<'_>> {
let trimmed = body.trim();
let when_pos = find_literal(trimmed, "when")?;
let after_when = trimmed[when_pos + 4..].trim_start();
let then_pos = find_then_keyword(after_when)?;
let condition = after_when[..then_pos].trim();
let action = after_when[then_pos + 4..].trim();
Some(WhenThen { condition, action })
}
pub fn parse_operator_zero_copy(text: &str) -> Option<Operator<'_>> {
let trimmed = text.trim_start();
if trimmed.len() >= 2 {
let op = &trimmed[..2];
if matches!(op, ">=" | "<=" | "==" | "!=") {
return Some(Operator { op, consumed: 2 });
}
}
if !trimmed.is_empty() {
let op = &trimmed[..1];
if matches!(op, ">" | "<") {
return Some(Operator { op, consumed: 1 });
}
}
if trimmed.starts_with("contains") {
return Some(Operator {
op: &trimmed[..8],
consumed: 8,
});
}
if trimmed.starts_with("matches") {
return Some(Operator {
op: &trimmed[..7],
consumed: 7,
});
}
None
}
pub fn parse_module_zero_copy(text: &str) -> Option<Module<'_>> {
let trimmed = text.trim_start();
if !trimmed.starts_with("defmodule") {
return None;
}
let after_defmodule = trimmed[9..].trim_start();
let name_end = after_defmodule.find(|c: char| !c.is_alphanumeric() && c != '_')?;
let name = &after_defmodule[..name_end];
if !name.chars().next()?.is_uppercase() {
return None;
}
let rest = after_defmodule[name_end..].trim_start();
if !rest.starts_with('{') {
return None;
}
let brace_pos = trimmed.len() - rest.len();
let close_pos = find_matching_brace(trimmed, brace_pos)?;
let body = &trimmed[brace_pos + 1..close_pos];
let consumed = close_pos + 1;
Some(Module {
name,
body,
consumed,
})
}
pub fn split_into_rules_zero_copy(grl_text: &str) -> Vec<Rule<'_>> {
let bytes = grl_text.as_bytes();
let mut rules = Vec::new();
let mut pos = 0;
while pos < bytes.len() {
if let Some(offset) = memchr::memmem::find(&bytes[pos..], b"rule ") {
let rule_pos = pos + offset;
if let Some(brace_offset) = memchr::memchr(b'{', &bytes[rule_pos..]) {
let brace_pos = rule_pos + brace_offset;
if let Some(close_pos) = find_matching_brace(grl_text, brace_pos) {
rules.push(Rule {
text: &grl_text[rule_pos..=close_pos],
start: rule_pos,
end: close_pos,
});
pos = close_pos + 1;
continue;
}
}
}
pos += 1;
}
rules
}
pub fn extract_salience_zero_copy(attributes: &str) -> Option<i32> {
let salience_pos = find_literal(attributes, "salience")?;
let after_salience = attributes[salience_pos + 8..].trim_start();
let end = after_salience
.find(|c: char| !c.is_ascii_digit())
.unwrap_or(after_salience.len());
after_salience[..end].parse().ok()
}
pub fn has_attribute_zero_copy(text: &str, attr: &str) -> bool {
let bytes = text.as_bytes();
let attr_bytes = attr.as_bytes();
if let Some(pos) = memchr::memmem::find(bytes, attr_bytes) {
let before_ok = pos == 0 || !bytes[pos - 1].is_ascii_alphanumeric();
let after_pos = pos + attr_bytes.len();
let after_ok = after_pos >= bytes.len() || !bytes[after_pos].is_ascii_alphanumeric();
return before_ok && after_ok;
}
false
}
fn find_literal(text: &str, pattern: &str) -> Option<usize> {
text.find(pattern)
}
fn find_then_keyword(text: &str) -> Option<usize> {
let bytes = text.as_bytes();
let mut in_string = false;
let mut escape_next = false;
let mut paren_depth = 0;
let mut brace_depth = 0;
let mut i = 0;
while i < bytes.len() {
if escape_next {
escape_next = false;
i += 1;
continue;
}
match bytes[i] {
b'\\' if in_string => escape_next = true,
b'"' => in_string = !in_string,
b'(' if !in_string => paren_depth += 1,
b')' if !in_string => paren_depth -= 1,
b'{' if !in_string => brace_depth += 1,
b'}' if !in_string => brace_depth -= 1,
b't' if !in_string && paren_depth == 0 && brace_depth == 0 => {
if i + 4 <= bytes.len() && &bytes[i..i + 4] == b"then" {
let before_ok = i == 0 || !bytes[i - 1].is_ascii_alphanumeric();
let after_ok = i + 4 >= bytes.len() || !bytes[i + 4].is_ascii_alphanumeric();
if before_ok && after_ok {
return Some(i);
}
}
}
_ => {}
}
i += 1;
}
None
}
fn find_matching_brace(text: &str, open_pos: usize) -> Option<usize> {
let bytes = text.as_bytes();
if open_pos >= bytes.len() || bytes[open_pos] != b'{' {
return None;
}
let mut depth = 1;
let mut in_string = false;
let mut escape_next = false;
#[allow(clippy::needless_range_loop)]
for i in (open_pos + 1)..bytes.len() {
if escape_next {
escape_next = false;
continue;
}
match bytes[i] {
b'\\' if in_string => escape_next = true,
b'"' => in_string = !in_string,
b'{' if !in_string => depth += 1,
b'}' if !in_string => {
depth -= 1;
if depth == 0 {
return Some(i);
}
}
_ => {}
}
}
None
}
impl<'a> fmt::Display for RuleHeader<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "rule \"{}\"", self.name)
}
}
impl<'a> fmt::Display for WhenThen<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "when {} then {}", self.condition, self.action)
}
}
impl<'a> fmt::Display for Module<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "defmodule {} {{ {} }}", self.name, self.body)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_rule_header_zero_copy() {
let text = r#"rule "MyRule" {"#;
let header = parse_rule_header_zero_copy(text).unwrap();
assert_eq!(header.name, "MyRule");
assert!(header.consumed > 0);
}
#[test]
fn test_parse_when_then_zero_copy() {
let body = "when X > 5 then Y = 10";
let wt = parse_when_then_zero_copy(body).unwrap();
assert_eq!(wt.condition, "X > 5");
assert_eq!(wt.action, "Y = 10");
}
#[test]
fn test_parse_operator_zero_copy() {
let op = parse_operator_zero_copy(">=").unwrap();
assert_eq!(op.op, ">=");
assert_eq!(op.consumed, 2);
let op2 = parse_operator_zero_copy("contains").unwrap();
assert_eq!(op2.op, "contains");
assert_eq!(op2.consumed, 8);
}
#[test]
fn test_parse_module_zero_copy() {
let text = "defmodule MYMODULE { export: all }";
let module = parse_module_zero_copy(text).unwrap();
assert_eq!(module.name, "MYMODULE");
assert!(module.body.contains("export"));
}
#[test]
fn test_split_into_rules_zero_copy() {
let grl = r#"
rule "Rule1" { when X > 5 then Y = 10 }
rule "Rule2" { when A < 3 then B = 7 }
"#;
let rules = split_into_rules_zero_copy(grl);
assert_eq!(rules.len(), 2);
assert!(rules[0].text.contains("Rule1"));
assert!(rules[1].text.contains("Rule2"));
}
#[test]
fn test_extract_salience_zero_copy() {
assert_eq!(extract_salience_zero_copy("salience 10"), Some(10));
assert_eq!(extract_salience_zero_copy("salience 42 "), Some(42));
}
#[test]
fn test_has_attribute_zero_copy() {
assert!(has_attribute_zero_copy("no-loop lock-on-active", "no-loop"));
assert!(!has_attribute_zero_copy("no-loops", "no-loop"));
}
#[test]
fn test_zero_allocations() {
let text = r#"rule "TestRule" { when X > 5 then Y = 10 }"#;
let header = parse_rule_header_zero_copy(text).unwrap();
let text_start = text.as_ptr() as usize;
let text_end = unsafe { text.as_ptr().add(text.len()) as usize };
let name_ptr = header.name.as_ptr() as usize;
assert!(name_ptr >= text_start);
assert!(name_ptr < text_end);
}
}