use core::fmt;
use yo_common::blake3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Prim {
U8,
U16,
U32,
U64,
I8,
I16,
I32,
I64,
F32,
F64,
Bool,
Str,
Bytes,
}
impl Prim {
#[must_use]
pub const fn token(self) -> &'static str {
match self {
Prim::U8 => "u8",
Prim::U16 => "u16",
Prim::U32 => "u32",
Prim::U64 => "u64",
Prim::I8 => "i8",
Prim::I16 => "i16",
Prim::I32 => "i32",
Prim::I64 => "i64",
Prim::F32 => "f32",
Prim::F64 => "f64",
Prim::Bool => "bool",
Prim::Str => "str",
Prim::Bytes => "bytes",
}
}
pub(crate) const ALL: &'static [Prim] = &[
Prim::Bytes,
Prim::Bool,
Prim::Str,
Prim::U8,
Prim::U16,
Prim::U32,
Prim::U64,
Prim::I8,
Prim::I16,
Prim::I32,
Prim::I64,
Prim::F32,
Prim::F64,
];
}
impl fmt::Display for Prim {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.token())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Metric {
L2,
Cosine,
Ip,
Hamming,
}
impl Metric {
#[must_use]
pub const fn token(self) -> &'static str {
match self {
Metric::L2 => "l2",
Metric::Cosine => "cosine",
Metric::Ip => "ip",
Metric::Hamming => "hamming",
}
}
}
impl fmt::Display for Metric {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.token())
}
}
pub type Describe = fn(&mut Desc);
pub trait Shape {
fn describe(d: &mut Desc);
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Desc {
bytes: Vec<u8>,
open: Vec<(usize, usize)>,
}
impl Desc {
#[must_use]
pub fn new() -> Desc {
Desc {
bytes: Vec::new(),
open: Vec::new(),
}
}
#[must_use]
pub fn of<T: Shape + ?Sized>() -> Desc {
let mut d = Desc::new();
T::describe(&mut d);
d
}
#[must_use]
pub fn from_bytes(bytes: impl Into<Vec<u8>>) -> Desc {
Desc {
bytes: bytes.into(),
open: Vec::new(),
}
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
#[must_use]
pub fn as_text(&self) -> String {
String::from_utf8_lossy(&self.bytes).into_owned()
}
#[must_use]
pub fn tag(&self) -> Tag {
Tag::of(&self.bytes)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
pub fn prim(&mut self, p: Prim) {
self.bytes.extend_from_slice(p.token().as_bytes());
}
pub fn optional(&mut self, inner: Describe) {
self.bytes.push(b'O');
inner(self);
}
pub fn list(&mut self, inner: Describe) {
self.bytes.push(b'L');
inner(self);
}
pub fn map(&mut self, key: Describe, value: Describe) {
self.bytes.push(b'M');
key(self);
value(self);
}
pub fn vector(&mut self, dim: u32, metric: Metric) {
self.bytes.push(b'V');
self.varint(dim);
self.name(metric.token());
}
pub fn reference(&mut self, name: &str) {
self.bytes.push(b'R');
self.name(name);
}
pub fn strukt(&mut self, name: &str, fields: &[(&str, Describe)]) {
if self.is_open(name) {
self.reference(name);
return;
}
self.bytes.push(b'S');
let at = self.name(name);
self.open.push(at);
self.varint(len_as_u32(fields.len()));
for (field, describe) in fields {
self.name(field);
describe(self);
}
self.open.pop();
}
pub fn enumeration(&mut self, name: &str, variants: &[&str]) {
self.bytes.push(b'E');
self.name(name);
self.varint(len_as_u32(variants.len()));
for variant in variants {
self.name(variant);
}
}
fn name(&mut self, s: &str) -> (usize, usize) {
self.varint(len_as_u32(s.len()));
let at = self.bytes.len();
self.bytes.extend_from_slice(s.as_bytes());
(at, s.len())
}
fn varint(&mut self, mut n: u32) {
loop {
let byte = (n & 0x7f) as u8;
n >>= 7;
if n == 0 {
self.bytes.push(byte);
return;
}
self.bytes.push(byte | 0x80);
}
}
fn is_open(&self, name: &str) -> bool {
self.open
.iter()
.any(|&(at, len)| &self.bytes[at..at + len] == name.as_bytes())
}
}
fn len_as_u32(n: usize) -> u32 {
u32::try_from(n).unwrap_or(u32::MAX)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Tag([u8; 16]);
impl Tag {
pub const UNTYPED: Tag = Tag([0; 16]);
#[must_use]
pub fn of(description: &[u8]) -> Tag {
let full = blake3::hash(description);
let mut tag = [0u8; 16];
tag.copy_from_slice(&full[..16]);
Tag(tag)
}
#[must_use]
pub fn for_type<T: Shape + ?Sized>() -> Tag {
Desc::of::<T>().tag()
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 16] {
&self.0
}
#[must_use]
pub const fn from_bytes(bytes: [u8; 16]) -> Tag {
Tag(bytes)
}
#[must_use]
pub fn is_untyped(&self) -> bool {
self.0 == [0; 16]
}
}
impl fmt::Display for Tag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&blake3::to_hex(&self.0))
}
}
macro_rules! prim_shape {
($($t:ty => $p:expr),* $(,)?) => {
$(impl Shape for $t {
fn describe(d: &mut Desc) {
d.prim($p);
}
})*
};
}
prim_shape! {
u8 => Prim::U8,
u16 => Prim::U16,
u32 => Prim::U32,
u64 => Prim::U64,
i8 => Prim::I8,
i16 => Prim::I16,
i32 => Prim::I32,
i64 => Prim::I64,
f32 => Prim::F32,
f64 => Prim::F64,
bool => Prim::Bool,
str => Prim::Str,
String => Prim::Str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Bytes;
impl Shape for Bytes {
fn describe(d: &mut Desc) {
d.prim(Prim::Bytes);
}
}
impl<T: Shape> Shape for Option<T> {
fn describe(d: &mut Desc) {
d.optional(T::describe);
}
}
impl<T: Shape> Shape for Vec<T> {
fn describe(d: &mut Desc) {
d.list(T::describe);
}
}
impl<T: Shape> Shape for [T] {
fn describe(d: &mut Desc) {
d.list(T::describe);
}
}
impl<T: Shape, const N: usize> Shape for [T; N] {
fn describe(d: &mut Desc) {
d.list(T::describe);
}
}
impl<T: Shape + ?Sized> Shape for &T {
fn describe(d: &mut Desc) {
T::describe(d);
}
}
impl<T: Shape + ?Sized> Shape for Box<T> {
fn describe(d: &mut Desc) {
T::describe(d);
}
}
impl<K: Shape, V: Shape> Shape for std::collections::BTreeMap<K, V> {
fn describe(d: &mut Desc) {
d.map(K::describe, V::describe);
}
}
impl<K: Shape, V: Shape, S> Shape for std::collections::HashMap<K, V, S> {
fn describe(d: &mut Desc) {
d.map(K::describe, V::describe);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn primitives_write_their_own_token() {
assert_eq!(Desc::of::<u64>().as_text(), "u64");
assert_eq!(Desc::of::<i8>().as_text(), "i8");
assert_eq!(Desc::of::<f32>().as_text(), "f32");
assert_eq!(Desc::of::<bool>().as_text(), "bool");
assert_eq!(Desc::of::<String>().as_text(), "str");
assert_eq!(Desc::of::<Bytes>().as_text(), "bytes");
}
#[test]
fn containers_nest_left_to_right() {
assert_eq!(Desc::of::<Option<u32>>().as_text(), "Ou32");
assert_eq!(Desc::of::<Vec<Option<i64>>>().as_text(), "LOi64");
assert_eq!(
Desc::of::<std::collections::BTreeMap<String, Vec<u8>>>().as_text(),
"MstrLu8"
);
}
#[test]
fn indirection_is_not_part_of_the_shape() {
assert_eq!(Desc::of::<Box<u64>>(), Desc::of::<u64>());
assert_eq!(Desc::of::<&str>(), Desc::of::<String>());
assert_eq!(Desc::of::<[u16; 4]>(), Desc::of::<Vec<u16>>());
}
fn order(d: &mut Desc) {
d.strukt("Order", &[("id", u64::describe), ("total", f64::describe)]);
}
#[test]
fn a_struct_writes_its_name_then_its_fields_in_order() {
let mut d = Desc::new();
order(&mut d);
assert_eq!(d.as_text(), "S\u{5}Order\u{2}\u{2}idu64\u{5}totalf64");
}
#[test]
fn reordering_fields_changes_the_tag() {
let mut a = Desc::new();
a.strukt("P", &[("x", u64::describe), ("y", u64::describe)]);
let mut b = Desc::new();
b.strukt("P", &[("y", u64::describe), ("x", u64::describe)]);
assert_ne!(a.tag(), b.tag());
}
#[test]
fn a_widening_changes_the_tag() {
let mut a = Desc::new();
a.strukt("P", &[("x", u32::describe)]);
let mut b = Desc::new();
b.strukt("P", &[("x", u64::describe)]);
assert_ne!(a.tag(), b.tag());
assert_ne!(a.as_bytes(), b.as_bytes());
}
#[test]
fn the_same_shape_written_twice_gets_the_same_tag() {
let mut a = Desc::new();
order(&mut a);
let mut b = Desc::new();
order(&mut b);
assert_eq!(a.tag(), b.tag());
assert_eq!(a.tag().to_string().len(), 32);
}
#[test]
fn recursion_writes_a_reference() {
fn node(d: &mut Desc) {
d.strukt("Node", &[("value", u64::describe), ("kids", kids)]);
}
fn kids(d: &mut Desc) {
d.list(node);
}
let mut d = Desc::new();
node(&mut d);
assert_eq!(
d.as_text(),
"S\u{4}Node\u{2}\u{5}valueu64\u{4}kidsLR\u{4}Node"
);
}
#[test]
fn siblings_of_the_same_type_both_expand() {
fn point(d: &mut Desc) {
d.strukt("Point", &[("x", f64::describe)]);
}
let mut d = Desc::new();
d.strukt("Line", &[("a", point), ("b", point)]);
let text = d.as_text();
assert_eq!(text.matches("Point").count(), 2);
assert!(!text.contains('R'));
}
#[test]
fn an_enum_writes_its_variants_in_order() {
let mut d = Desc::new();
d.enumeration("Status", &["Open", "Paid"]);
assert_eq!(d.as_text(), "E\u{6}Status\u{2}\u{4}Open\u{4}Paid");
}
#[test]
fn a_vector_carries_its_dimension_and_metric() {
let mut d = Desc::new();
d.vector(768, Metric::Cosine);
assert_eq!(d.as_bytes(), b"V\x80\x06\x06cosine");
}
#[test]
fn a_long_name_gets_a_two_byte_length() {
let long = "a".repeat(200);
let mut d = Desc::new();
d.enumeration(&long, &[]);
assert_eq!(d.as_bytes()[1], 0xc8);
assert_eq!(d.as_bytes()[2], 0x01);
assert_eq!(d.as_bytes().len(), 1 + 2 + 200 + 1);
}
#[test]
fn the_untyped_tag_is_zero_and_says_so() {
assert!(Tag::UNTYPED.is_untyped());
assert_eq!(Tag::UNTYPED.to_string(), "0".repeat(32));
assert!(!Tag::for_type::<u64>().is_untyped());
}
#[test]
fn the_tag_is_the_first_half_of_the_hash() {
let d = Desc::of::<u64>();
let full = blake3::hash(d.as_bytes());
assert_eq!(d.tag().as_bytes(), &full[..16]);
assert_eq!(d.tag().to_string(), blake3::to_hex(&full[..16]));
assert_eq!(Tag::from_bytes(*d.tag().as_bytes()), d.tag());
}
}