Skip to main content

sim_lib_forge/
normalize.rs

1use sim_kernel::{ContentId, Datum, Error, Expr, Result};
2
3/// Normalizes prose into the byte-level source identity used by FORGE.
4///
5/// The normal form trims leading and trailing ASCII whitespace and collapses
6/// each internal ASCII whitespace run to one space. It preserves case,
7/// spelling, punctuation, and non-ASCII bytes exactly; this is a deterministic
8/// byte-level key, not a semantic lookup.
9pub fn normalize_prose(prose: &str) -> Result<(String, ContentId)> {
10    let normalized = collapse_ascii_whitespace(prose);
11    if normalized.is_empty() {
12        return Err(Error::Eval("forge lift prose must not be empty".to_owned()));
13    }
14    Ok((normalized.clone(), source_content_id(&normalized)?))
15}
16
17fn collapse_ascii_whitespace(prose: &str) -> String {
18    let mut out = String::with_capacity(prose.len());
19    let mut pending_space = false;
20
21    for ch in prose.chars() {
22        if ch.is_ascii_whitespace() {
23            if !out.is_empty() {
24                pending_space = true;
25            }
26            continue;
27        }
28        if pending_space {
29            out.push(' ');
30            pending_space = false;
31        }
32        out.push(ch);
33    }
34
35    out
36}
37
38fn source_content_id(normalized: &str) -> Result<ContentId> {
39    Datum::try_from(Expr::String(normalized.to_owned()))?.content_id()
40}