sim-lib-stream-core 0.1.0

Core stream metadata, packets, envelopes, and buffer values.
Documentation
use sim_kernel::{ContentId, Coordinate, Error, Expr, HandleId, Ref, Result, Symbol};

use crate::buffer::{expr_kind, field, string_field, symbol_field};

pub(super) fn ref_expr(reference: &Ref) -> Expr {
    match reference {
        Ref::Symbol(symbol) => Expr::Symbol(symbol.clone()),
        Ref::Content(content) => content_ref_expr(content),
        Ref::Handle(handle) => Expr::Map(vec![
            (
                Expr::Symbol(Symbol::new("ref")),
                Expr::Symbol(Symbol::qualified("stream/ref", "handle")),
            ),
            (
                Expr::Symbol(Symbol::new("id")),
                Expr::String(handle.0.to_string()),
            ),
        ]),
        Ref::Coord(coordinate) => Expr::Map(vec![
            (
                Expr::Symbol(Symbol::new("ref")),
                Expr::Symbol(Symbol::qualified("stream/ref", "coord")),
            ),
            (
                Expr::Symbol(Symbol::new("space")),
                Expr::Symbol(coordinate.space.clone()),
            ),
            (
                Expr::Symbol(Symbol::new("ordinal")),
                content_ref_expr(&coordinate.ordinal),
            ),
        ]),
    }
}

pub(super) fn ref_from_expr(expr: &Expr) -> Result<Ref> {
    match expr {
        Expr::Symbol(symbol) => Ok(Ref::Symbol(symbol.clone())),
        Expr::Map(entries) => {
            let tag = symbol_field(entries, "ref")?;
            match tag.as_qualified_str().as_str() {
                "stream/ref/content" => Ok(Ref::Content(content_from_entries(entries)?)),
                "stream/ref/handle" => {
                    let id = string_field(entries, "id")?
                        .parse::<u128>()
                        .map_err(|err| Error::Eval(format!("invalid stream handle ref: {err}")))?;
                    Ok(Ref::Handle(HandleId(id)))
                }
                "stream/ref/coord" => {
                    ensure_fields(entries, &["ref", "space", "ordinal"])?;
                    let ordinal = match ref_from_expr(field(entries, "ordinal")?)? {
                        Ref::Content(content) => content,
                        _ => {
                            return Err(Error::Eval(
                                "stream coordinate ordinal must be a content ref".to_owned(),
                            ));
                        }
                    };
                    Ok(Ref::Coord(Coordinate {
                        space: symbol_field(entries, "space")?.clone(),
                        ordinal,
                    }))
                }
                other => Err(Error::Eval(format!("unknown stream ref tag {other}"))),
            }
        }
        other => Err(Error::TypeMismatch {
            expected: "stream ref expression",
            found: expr_kind(other),
        }),
    }
}

fn content_ref_expr(content: &ContentId) -> Expr {
    Expr::Map(vec![
        (
            Expr::Symbol(Symbol::new("ref")),
            Expr::Symbol(Symbol::qualified("stream/ref", "content")),
        ),
        (
            Expr::Symbol(Symbol::new("algorithm")),
            Expr::Symbol(content.algorithm.clone()),
        ),
        (
            Expr::Symbol(Symbol::new("bytes")),
            Expr::Bytes(content.bytes.to_vec()),
        ),
    ])
}

fn content_from_entries(entries: &[(Expr, Expr)]) -> Result<ContentId> {
    ensure_fields(entries, &["ref", "algorithm", "bytes"])?;
    let bytes = match field(entries, "bytes")? {
        Expr::Bytes(bytes) => bytes,
        other => {
            return Err(Error::TypeMismatch {
                expected: "content bytes",
                found: expr_kind(other),
            });
        }
    };
    let bytes: [u8; 32] = bytes
        .as_slice()
        .try_into()
        .map_err(|_| Error::Eval("stream content ref bytes must be 32 bytes".to_owned()))?;
    Ok(ContentId::from_bytes(
        symbol_field(entries, "algorithm")?.clone(),
        bytes,
    ))
}

fn ensure_fields(entries: &[(Expr, Expr)], allowed: &[&str]) -> Result<()> {
    for (key, _) in entries {
        let Expr::Symbol(symbol) = key else {
            return Err(Error::TypeMismatch {
                expected: "symbol stream ref field",
                found: expr_kind(key),
            });
        };
        if symbol.namespace.is_none() && allowed.contains(&symbol.name.as_ref()) {
            continue;
        }
        return Err(Error::Eval(format!(
            "unknown stream ref field {}",
            symbol.as_qualified_str()
        )));
    }
    Ok(())
}