use core::{convert::Infallible, num::NonZero};
use serde::{Serialize, Serializer, ser::SerializeMap as _};
use crate::{DescendError, ExactSize, IntoKeys, KeyError, Keys, NodeIter, Shape, Transcode};
#[cfg(feature = "sem")]
type StoredSem = Sem;
#[cfg(not(feature = "sem"))]
type StoredSem = ();
#[cfg(feature = "meta-node")]
type StoredNodeMeta = Meta;
#[cfg(not(feature = "meta-node"))]
type StoredNodeMeta = ();
#[cfg(feature = "meta-edge")]
type StoredEdgeMeta = Meta;
#[cfg(not(feature = "meta-edge"))]
type StoredEdgeMeta = ();
pub const ONEOF_SEM: Sem = Sem::new(None, true, false);
pub const MAYBE_ABSENT_SEM: Sem = Sem::new(None, false, true);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Lookup {
pub depth: usize,
pub schema: &'static Schema,
}
#[cfg(feature = "defmt")]
impl defmt::Format for Lookup {
fn format(&self, fmt: defmt::Formatter<'_>) {
defmt::write!(
fmt,
"Lookup {{ depth: {=usize}, leaf: {=bool} }}",
self.depth,
self.schema.is_leaf()
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ResolveError {
pub error: DescendError<()>,
pub lookup: Lookup,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct Sem {
#[serde(skip_serializing_if = "Option::is_none")]
ty: Option<Ty>,
#[serde(skip_serializing_if = "core::ops::Not::not")]
oneof: bool,
#[serde(skip_serializing_if = "core::ops::Not::not")]
maybe_absent: bool,
}
impl Sem {
pub const EMPTY: Self = Self::new(None, false, false);
pub const fn new(ty: Option<Ty>, oneof: bool, maybe_absent: bool) -> Self {
Self {
ty,
oneof,
maybe_absent,
}
}
pub const fn ty(&self) -> Option<Ty> {
self.ty
}
pub const fn oneof(&self) -> bool {
self.oneof
}
pub const fn maybe_absent(&self) -> bool {
self.maybe_absent
}
pub const fn is_empty(&self) -> bool {
self.ty.is_none() && !self.oneof && !self.maybe_absent
}
}
#[cfg(feature = "sem")]
const fn store_sem(sem: Sem) -> StoredSem {
sem
}
#[cfg(not(feature = "sem"))]
const fn store_sem(_sem: Sem) -> StoredSem {}
#[cfg(feature = "sem")]
const fn sem_ref(sem: &StoredSem) -> Option<&Sem> {
Some(sem)
}
#[cfg(not(feature = "sem"))]
const fn sem_ref(_sem: &StoredSem) -> Option<&Sem> {
None
}
#[cfg(feature = "meta-node")]
const fn store_node_meta(meta: Meta) -> StoredNodeMeta {
meta
}
#[cfg(not(feature = "meta-node"))]
const fn store_node_meta(_meta: Meta) -> StoredNodeMeta {}
#[cfg(feature = "meta-node")]
const fn node_meta_ref(meta: &StoredNodeMeta) -> &Meta {
meta
}
#[cfg(not(feature = "meta-node"))]
const fn node_meta_ref(_meta: &StoredNodeMeta) -> &Meta {
&Meta::EMPTY
}
#[cfg(feature = "meta-edge")]
const fn store_edge_meta(meta: Meta) -> StoredEdgeMeta {
meta
}
#[cfg(not(feature = "meta-edge"))]
const fn store_edge_meta(_meta: Meta) -> StoredEdgeMeta {}
#[cfg(feature = "meta-edge")]
const fn edge_meta_ref(meta: &StoredEdgeMeta) -> &Meta {
meta
}
#[cfg(not(feature = "meta-edge"))]
const fn edge_meta_ref(_meta: &StoredEdgeMeta) -> &Meta {
&Meta::EMPTY
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Ty {
#[serde(rename = "bool")]
Bool,
#[serde(rename = "i8")]
I8,
#[serde(rename = "i16")]
I16,
#[serde(rename = "i32")]
I32,
#[serde(rename = "i64")]
I64,
#[serde(rename = "i128")]
I128,
#[serde(rename = "isize")]
Isize,
#[serde(rename = "u8")]
U8,
#[serde(rename = "u16")]
U16,
#[serde(rename = "u32")]
U32,
#[serde(rename = "u64")]
U64,
#[serde(rename = "u128")]
U128,
#[serde(rename = "usize")]
Usize,
#[serde(rename = "f32")]
F32,
#[serde(rename = "f64")]
F64,
#[serde(rename = "str")]
Str,
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize)]
pub struct Numbered {
pub(crate) schema: &'static Schema,
pub(crate) meta: StoredEdgeMeta,
}
impl Numbered {
pub const fn new(schema: &'static Schema, meta: Meta) -> Self {
Self {
schema,
meta: store_edge_meta(meta),
}
}
pub const fn schema(&self) -> &'static Schema {
self.schema
}
pub const fn edge_meta(&self) -> &Meta {
edge_meta_ref(&self.meta)
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize)]
pub struct Named {
pub(crate) name: &'static str,
pub(crate) schema: &'static Schema,
pub(crate) meta: StoredEdgeMeta,
}
impl Named {
pub const fn new(name: &'static str, schema: &'static Schema, meta: Meta) -> Self {
Self {
name,
schema,
meta: store_edge_meta(meta),
}
}
pub const fn name(&self) -> &'static str {
self.name
}
pub const fn schema(&self) -> &'static Schema {
self.schema
}
pub const fn edge_meta(&self) -> &Meta {
edge_meta_ref(&self.meta)
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize)]
pub struct Homogeneous {
pub(crate) len: NonZero<usize>,
pub(crate) schema: &'static Schema,
pub(crate) meta: StoredEdgeMeta,
}
impl Homogeneous {
pub const fn new(len: usize, schema: &'static Schema, meta: Meta) -> Self {
Self {
len: NonZero::new(len).expect("Must have at least one child"),
schema,
meta: store_edge_meta(meta),
}
}
pub const fn len(&self) -> NonZero<usize> {
self.len
}
pub const fn schema(&self) -> &'static Schema {
self.schema
}
pub const fn edge_meta(&self) -> &Meta {
edge_meta_ref(&self.meta)
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize)]
pub enum Internal {
Named(&'static [Named]),
Numbered(&'static [Numbered]),
Homogeneous(Homogeneous),
}
impl Internal {
pub const fn len(&self) -> NonZero<usize> {
match self {
Self::Named(n) => NonZero::new(n.len()).expect("Must have at least one child"),
Self::Numbered(n) => NonZero::new(n.len()).expect("Must have at least one child"),
Self::Homogeneous(h) => h.len,
}
}
pub const fn get_schema(&self, idx: usize) -> &Schema {
match self {
Self::Named(nameds) => nameds[idx].schema,
Self::Numbered(numbereds) => numbereds[idx].schema,
Self::Homogeneous(homogeneous) => homogeneous.schema,
}
}
pub const fn get_edge_meta(&self, idx: usize) -> &Meta {
match self {
Internal::Named(nameds) => nameds[idx].edge_meta(),
Internal::Numbered(numbereds) => numbereds[idx].edge_meta(),
Internal::Homogeneous(homogeneous) => homogeneous.edge_meta(),
}
}
pub const fn get_name(&self, idx: usize) -> Option<&str> {
if let Self::Named(n) = self {
Some(n[idx].name)
} else {
None
}
}
pub fn get_index(&self, name: &str) -> Option<usize> {
match self {
Internal::Named(n) => n.iter().position(|n| n.name == name),
Internal::Numbered(n) => name.parse().ok().filter(|i| *i < n.len()),
Internal::Homogeneous(h, ..) => name.parse().ok().filter(|i| *i < h.len.get()),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub struct Meta {
items: &'static [(&'static str, &'static str)],
}
impl Meta {
pub const EMPTY: Self = Self { items: &[] };
pub const fn new(items: &'static [(&'static str, &'static str)]) -> Self {
Self { items }
}
pub const fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub const fn len(&self) -> usize {
self.items.len()
}
pub const fn as_slice(&self) -> &'static [(&'static str, &'static str)] {
self.items
}
pub fn iter(&self) -> core::slice::Iter<'static, (&'static str, &'static str)> {
self.items.iter()
}
pub fn get(&self, key: &str) -> Option<&'static str> {
self.items
.iter()
.find_map(|(have_key, have_value)| (*have_key == key).then_some(*have_value))
}
}
impl Serialize for Meta {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(self.items.len()))?;
for (key, value) in self.items {
map.serialize_entry(key, value)?;
}
map.end()
}
}
#[cfg(test)]
mod tests {
use super::*;
const META: Meta = Meta::new(&[("doc", "node"), ("doc", "second")]);
const EDGE_META: Meta = Meta::new(&[("unit", "V")]);
const CHILD: Schema = Schema::leaf(META, Sem::new(Some(Ty::U16), false, false));
const ROOT: Schema = Schema::Internal(InternalSchema::new(
NodeSchema::new(META, ONEOF_SEM),
Internal::Named(&[Named::new("value", &CHILD, EDGE_META)]),
));
#[test]
fn meta_iter_preserves_declaration_order_and_duplicates() {
assert_eq!(META.len(), 2);
assert_eq!(META.get("doc"), Some("node"));
assert_eq!(
META.iter().copied().collect::<std::vec::Vec<_>>(),
[("doc", "node"), ("doc", "second")]
);
}
#[test]
fn feature_retention_contract_is_explicit() {
#[cfg(feature = "sem")]
{
assert_eq!(ROOT.sem(), Some(&ONEOF_SEM));
assert_eq!(CHILD.sem(), Some(&Sem::new(Some(Ty::U16), false, false)));
}
#[cfg(not(feature = "sem"))]
{
assert_eq!(ROOT.sem(), None);
assert_eq!(CHILD.sem(), None);
}
#[cfg(feature = "meta-node")]
assert_eq!(ROOT.node_meta().get("doc"), Some("node"));
#[cfg(not(feature = "meta-node"))]
assert!(ROOT.node_meta().is_empty());
let internal = ROOT.internal().unwrap();
#[cfg(feature = "meta-edge")]
assert_eq!(internal.get_edge_meta(0).get("unit"), Some("V"));
#[cfg(not(feature = "meta-edge"))]
assert!(internal.get_edge_meta(0).is_empty());
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize)]
pub struct NodeSchema {
meta: StoredNodeMeta,
sem: StoredSem,
}
impl NodeSchema {
pub const EMPTY: Self = Self {
meta: store_node_meta(Meta::EMPTY),
sem: store_sem(Sem::EMPTY),
};
pub const fn new(meta: Meta, sem: Sem) -> Self {
Self {
meta: store_node_meta(meta),
sem: store_sem(sem),
}
}
const fn sem(&self) -> Option<&Sem> {
sem_ref(&self.sem)
}
const fn node_meta(&self) -> &Meta {
node_meta_ref(&self.meta)
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize)]
pub struct InternalSchema {
node: NodeSchema,
internal: Internal,
}
impl InternalSchema {
pub const fn new(node: NodeSchema, internal: Internal) -> Self {
Self { node, internal }
}
const fn sem(&self) -> Option<&Sem> {
self.node.sem()
}
const fn node_meta(&self) -> &Meta {
self.node.node_meta()
}
pub const fn internal(&self) -> &Internal {
&self.internal
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize)]
pub enum Schema {
Leaf(NodeSchema),
Internal(InternalSchema),
}
impl Default for Schema {
fn default() -> Self {
Self::LEAF
}
}
impl Schema {
pub const LEAF: Self = Self::Leaf(NodeSchema::EMPTY);
pub const fn leaf(meta: Meta, sem: Sem) -> Self {
Self::Leaf(NodeSchema::new(meta, sem))
}
pub const fn rebuild(&self, meta: Meta, sem: Sem) -> Self {
let node = NodeSchema::new(meta, sem);
match self {
Self::Leaf(_) => Self::Leaf(node),
Self::Internal(schema) => Self::Internal(InternalSchema::new(node, schema.internal)),
}
}
pub(crate) const fn leaf_ty(ty: Ty) -> Self {
Self::leaf(Meta::EMPTY, Sem::new(Some(ty), false, false))
}
pub const fn numbered(numbered: &'static [Numbered]) -> Self {
Self::Internal(InternalSchema::new(
NodeSchema::EMPTY,
Internal::Numbered(numbered),
))
}
pub const fn named(named: &'static [Named]) -> Self {
Self::Internal(InternalSchema::new(
NodeSchema::EMPTY,
Internal::Named(named),
))
}
pub const fn homogeneous(homogeneous: Homogeneous) -> Self {
Self::Internal(InternalSchema::new(
NodeSchema::EMPTY,
Internal::Homogeneous(homogeneous),
))
}
pub const fn is_leaf(&self) -> bool {
matches!(self, Self::Leaf(_))
}
#[allow(clippy::len_without_is_empty)]
pub const fn len(&self) -> usize {
match self {
Self::Leaf(_) => 0,
Self::Internal(schema) => schema.internal().len().get(),
}
}
pub const fn sem(&self) -> Option<&Sem> {
match self {
Self::Leaf(schema) => schema.sem(),
Self::Internal(schema) => schema.sem(),
}
}
pub const fn node_meta(&self) -> &Meta {
match self {
Self::Leaf(node) => node.node_meta(),
Self::Internal(schema) => schema.node_meta(),
}
}
pub const fn internal(&self) -> Option<&Internal> {
match self {
Self::Leaf(_) => None,
Self::Internal(schema) => Some(schema.internal()),
}
}
pub fn next(&self, mut keys: impl Keys) -> Result<usize, KeyError> {
keys.next(self.internal().ok_or(KeyError::TooLong)?)
}
pub fn descend<'a, T, E>(
&'a self,
mut keys: impl Keys,
mut func: impl FnMut(&'a Self, Option<(usize, &'a Internal)>) -> Result<T, E>,
) -> Result<T, DescendError<E>> {
let mut schema = self;
while let Some(internal) = schema.internal() {
let idx = keys.next(internal)?;
func(schema, Some((idx, internal))).map_err(DescendError::Inner)?;
schema = internal.get_schema(idx);
}
keys.finalize()?;
func(schema, None).map_err(DescendError::Inner)
}
pub fn get_meta(&self, keys: impl IntoKeys) -> Result<(Option<&Meta>, &Meta), KeyError> {
let mut edge = None;
let mut node = self.node_meta();
self.descend(keys.into_keys(), |schema, idx_internal| {
if let Some((idx, internal)) = idx_internal {
edge = Some(internal.get_edge_meta(idx));
}
node = schema.node_meta();
Ok::<_, Infallible>(())
})
.map_err(|e| e.try_into().unwrap())?;
Ok((edge, node))
}
fn walk(
&'static self,
mut keys: impl Keys,
mut on_index: impl FnMut(usize, usize) -> bool,
) -> Result<Lookup, ResolveError> {
let mut schema = self;
let mut depth = 0;
while let Some(internal) = schema.internal() {
let idx = match keys.next(internal) {
Ok(idx) => idx,
Err(KeyError::TooShort) => {
debug_assert!(!schema.is_leaf());
return Ok(Lookup { depth, schema });
}
Err(err) => {
return Err(ResolveError {
error: err.into(),
lookup: Lookup { depth, schema },
});
}
};
if !on_index(depth, idx) {
return Err(ResolveError {
error: DescendError::Inner(()),
lookup: Lookup { depth, schema },
});
}
depth += 1;
schema = internal.get_schema(idx);
}
match keys.finalize() {
Ok(()) => Ok(Lookup { depth, schema }),
Err(KeyError::TooLong) => Err(ResolveError {
error: KeyError::TooLong.into(),
lookup: Lookup { depth, schema },
}),
Err(err) => unreachable!("unexpected finalize error: {err:?}"),
}
}
pub fn resolve_into(
&'static self,
keys: impl IntoKeys,
state: &mut [usize],
) -> Result<Lookup, ResolveError> {
self.walk(keys.into_keys(), |depth, idx| {
let Some(slot) = state.get_mut(depth) else {
return false;
};
*slot = idx;
true
})
}
pub fn get(&'static self, keys: impl IntoKeys) -> Result<Lookup, KeyError> {
self.walk(keys.into_keys(), |_, _| true)
.map_err(|err| match err.error {
DescendError::Key(err) => err,
DescendError::Inner(()) => unreachable!("infallible exact lookup"),
})
}
pub fn transcode<N: Transcode + Default>(
&self,
keys: impl IntoKeys,
) -> Result<N, DescendError<N::Error>> {
N::transcode(self, keys)
}
pub const fn shape(&self) -> Shape {
Shape::new(self)
}
pub const fn max_depth(&self) -> usize {
self.shape().max_depth
}
pub const fn max_length(&self, separator: &str) -> usize {
self.shape().max_length(separator)
}
pub const fn nodes<N: Transcode + Default, const D: usize>(
&'static self,
) -> ExactSize<NodeIter<N, D>> {
NodeIter::<N, D>::new(self).exact_size()
}
}