use prism::operation::TermValue;
use prism::pipeline::{
ConstrainedTypeShape, ConstraintRef, IntoBindingValue, PartitionProductFields, ShapeViolation,
ViolationKind,
};
use prism::uor_foundation::pipeline::ChunkSource;
pub(crate) const INVALID_SEXPR_VIOLATION: ShapeViolation = ShapeViolation {
shape_iri: "https://uor.foundation/addr/SExprValue",
constraint_iri: "https://uor.foundation/addr/SExprValue/validUtf8SExpr",
property_iri: "https://uor.foundation/addr/inputBytes",
expected_range: "https://uor.foundation/addr/ValidUtf8SExpr",
min_count: 0,
max_count: 1,
kind: ViolationKind::ValueCheck,
};
enum Tok<'a> {
Open,
Close,
Atom(&'a [u8]),
}
fn for_each_token<'a>(
raw: &'a [u8],
on_tok: &mut dyn FnMut(Tok<'a>),
) -> Result<(), ShapeViolation> {
let mut pos = 0;
while pos < raw.len() {
let b = raw[pos];
if b.is_ascii_whitespace() {
pos += 1;
continue;
}
if b == b'(' {
on_tok(Tok::Open);
pos += 1;
continue;
}
if b == b')' {
on_tok(Tok::Close);
pos += 1;
continue;
}
if b.is_ascii_digit() {
let mut i = pos;
while i < raw.len() && raw[i].is_ascii_digit() {
i += 1;
}
if i < raw.len() && raw[i] == b':' {
let len = parse_usize(&raw[pos..i]).ok_or(INVALID_SEXPR_VIOLATION)?;
let start = i + 1;
let end = start.checked_add(len).ok_or(INVALID_SEXPR_VIOLATION)?;
if end > raw.len() {
return Err(INVALID_SEXPR_VIOLATION);
}
on_tok(Tok::Atom(&raw[start..end]));
pos = end;
continue;
}
}
let start = pos;
while pos < raw.len() {
let c = raw[pos];
if c.is_ascii_whitespace() || c == b'(' || c == b')' {
break;
}
pos += 1;
}
on_tok(Tok::Atom(&raw[start..pos]));
}
Ok(())
}
fn parse_usize(digits: &[u8]) -> Option<usize> {
if digits.is_empty() {
return None;
}
let mut n: usize = 0;
for &d in digits {
let v = (d.wrapping_sub(b'0')) as usize;
if v > 9 {
return None;
}
n = n.checked_mul(10)?.checked_add(v)?;
}
Some(n)
}
fn format_usize_into(buf: &mut [u8; 20], mut n: usize) -> &[u8] {
if n == 0 {
buf[0] = b'0';
return &buf[..1];
}
let mut idx = buf.len();
while n > 0 {
idx -= 1;
buf[idx] = b'0' + (n % 10) as u8;
n /= 10;
}
&buf[idx..]
}
#[derive(Clone, Copy, Debug)]
pub struct SExprCanon<'a> {
raw: &'a [u8],
}
impl<'a> SExprCanon<'a> {
#[must_use]
pub fn new(raw: &'a [u8]) -> Self {
Self { raw }
}
pub fn validate(raw: &[u8]) -> Result<(), ShapeViolation> {
core::str::from_utf8(raw).map_err(|_| INVALID_SEXPR_VIOLATION)?;
let mut depth: usize = 0;
let mut seen_top = false;
let mut err: Option<ShapeViolation> = None;
for_each_token(raw, &mut |tok| {
if err.is_some() {
return;
}
match tok {
Tok::Atom(_) => {
if depth == 0 {
if seen_top {
err = Some(INVALID_SEXPR_VIOLATION);
} else {
seen_top = true;
}
}
}
Tok::Open => {
if depth == 0 && seen_top {
err = Some(INVALID_SEXPR_VIOLATION);
} else {
depth += 1;
}
}
Tok::Close => {
if depth == 0 {
err = Some(INVALID_SEXPR_VIOLATION);
} else {
depth -= 1;
if depth == 0 {
seen_top = true;
}
}
}
}
})?;
if let Some(e) = err {
return Err(e);
}
if depth != 0 || !seen_top {
return Err(INVALID_SEXPR_VIOLATION);
}
Ok(())
}
}
impl ChunkSource for SExprCanon<'_> {
fn for_each_chunk(&self, f: &mut dyn FnMut(&[u8])) {
let mut need_space = false;
let _ = for_each_token(self.raw, &mut |tok| match tok {
Tok::Open => {
if need_space {
f(b" ");
}
f(b"(");
need_space = false;
}
Tok::Close => {
f(b")");
need_space = true;
}
Tok::Atom(bytes) => {
if need_space {
f(b" ");
}
let mut buf = [0u8; 20];
f(format_usize_into(&mut buf, bytes.len()));
f(b":");
f(bytes);
need_space = true;
}
});
}
}
#[derive(Clone, Copy, Debug)]
pub struct SExprValue<'a>(&'a SExprCanon<'a>);
impl<'a> SExprValue<'a> {
#[must_use]
pub fn new(canon: &'a SExprCanon<'a>) -> Self {
Self(canon)
}
}
impl ConstrainedTypeShape for SExprValue<'_> {
const IRI: &'static str = "https://uor.foundation/addr/SExprValue";
const SITE_COUNT: usize = 1;
const CONSTRAINTS: &'static [ConstraintRef] = &[];
const CYCLE_SIZE: u64 = u64::MAX;
}
impl prism::uor_foundation::pipeline::__sdk_seal::Sealed for SExprValue<'_> {}
impl<'a> IntoBindingValue<'a> for SExprValue<'a> {
fn as_binding_value<const INLINE_BYTES: usize>(&self) -> TermValue<'a, INLINE_BYTES> {
TermValue::stream(self.0)
}
}
impl PartitionProductFields for SExprValue<'_> {
const FIELDS: &'static [(u32, u32)] = &[];
const FIELD_NAMES: &'static [&'static str] = &[];
}
#[cfg(feature = "alloc")]
pub fn canonicalize(raw: &[u8]) -> Result<alloc::vec::Vec<u8>, ShapeViolation> {
extern crate alloc;
SExprCanon::validate(raw)?;
let canon = SExprCanon::new(raw);
let mut out = alloc::vec::Vec::new();
canon.for_each_chunk(&mut |chunk| out.extend_from_slice(chunk));
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validates_nil() {
SExprCanon::validate(b"()").expect("valid nil");
}
#[test]
fn validates_atom_canonical_form() {
SExprCanon::validate(b"5:hello").expect("valid canonical atom");
}
#[test]
fn validates_token_list() {
SExprCanon::validate(b"(a b c)").expect("valid token list");
}
#[test]
fn rejects_unbalanced_parens() {
let err = SExprCanon::validate(b"((").expect_err("unbalanced parens");
assert_eq!(err.constraint_iri, INVALID_SEXPR_VIOLATION.constraint_iri);
}
#[test]
fn rejects_two_top_level_values() {
for raw in [b"abc def".as_slice(), b"(a)(b)".as_slice()] {
let err = SExprCanon::validate(raw).expect_err("two top-level");
assert_eq!(err.constraint_iri, INVALID_SEXPR_VIOLATION.constraint_iri);
}
}
#[test]
fn rejects_truncated_canonical_atom() {
let err = SExprCanon::validate(b"5:abc").expect_err("declared 5, has 3");
assert_eq!(err.constraint_iri, INVALID_SEXPR_VIOLATION.constraint_iri);
}
#[test]
fn accepts_arbitrary_nesting_depth() {
extern crate alloc;
let mut s = alloc::string::String::new();
for _ in 0..4096 {
s.push('(');
}
s.push('x');
for _ in 0..4096 {
s.push(')');
}
SExprCanon::validate(s.as_bytes()).expect("deep nesting is valid");
}
#[cfg(feature = "alloc")]
const CANONICAL_FIXTURES: &[(&[u8], &[u8])] = &[
(b"()", b"()"),
(b"(a b c)", b"(1:a 1:b 1:c)"),
(b"5:hello", b"5:hello"),
(b"(hello world)", b"(5:hello 5:world)"),
(b"((a) (b))", b"((1:a) (1:b))"),
(b"(a (b c) d)", b"(1:a (1:b 1:c) 1:d)"),
(b"( a\t b\n c )", b"(1:a 1:b 1:c)"),
(b"(1:a 1:b 1:c)", b"(1:a 1:b 1:c)"),
(b"(())", b"(())"),
];
#[cfg(feature = "alloc")]
#[test]
fn canonicalizer_matches_rivest_canonical_form() {
for (raw, expected) in CANONICAL_FIXTURES {
let canon = canonicalize(raw).expect("valid");
assert_eq!(canon, *expected, "raw={raw:?}");
}
}
#[cfg(feature = "alloc")]
#[test]
fn canonicalize_is_idempotent_on_its_own_output() {
for (raw, _expected) in CANONICAL_FIXTURES {
let once = canonicalize(raw).expect("valid");
let twice = canonicalize(&once).expect("re-canonicalises");
assert_eq!(once, twice, "idempotence broken for {raw:?}");
}
}
}