Skip to content

Commit

Permalink
walk: restructure
Browse files Browse the repository at this point in the history
  • Loading branch information
jordens committed Nov 20, 2024
1 parent 39a0240 commit 4367959
Show file tree
Hide file tree
Showing 15 changed files with 93 additions and 111 deletions.
33 changes: 11 additions & 22 deletions miniconf/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ macro_rules! impl_tuple {
#[allow(unreachable_code, unused_mut, unused)]
impl<$($t: TreeKey),+> TreeKey for ($($t,)+) {
fn traverse_all<W: Walk>() -> Result<W, W::Error> {
let k = KeyLookup::numbered($n);
let mut walk = W::internal();
$(walk = walk.merge(&$t::traverse_all()?, $i, &k)?;)+
Ok(walk)
W::internal(&[$(&$t::traverse_all()?, )+], &KeyLookup::numbered($n))
}

fn traverse_by_key<K, F, E>(mut keys: K, mut func: F) -> Result<usize, Error<E>>
Expand Down Expand Up @@ -114,7 +111,7 @@ impl<const L: usize, const R: usize> Assert<L, R> {
impl<T: TreeKey, const N: usize> TreeKey for [T; N] {
fn traverse_all<W: Walk>() -> Result<W, W::Error> {
let () = Assert::<N, 0>::GREATER; // internal nodes must have at least one leaf
W::internal().merge(&T::traverse_all()?, 0, &KeyLookup::homogeneous(N))
W::internal(&[&T::traverse_all()?], &KeyLookup::homogeneous(N))
}

fn traverse_by_key<K, F, E>(mut keys: K, mut func: F) -> Result<usize, Error<E>>
Expand Down Expand Up @@ -247,9 +244,7 @@ const RESULT_LOOKUP: KeyLookup = KeyLookup::Named(&["Ok", "Err"]);
impl<T: TreeKey, E: TreeKey> TreeKey for Result<T, E> {
#[inline]
fn traverse_all<W: Walk>() -> Result<W, W::Error> {
W::internal()
.merge(&T::traverse_all()?, 0, &RESULT_LOOKUP)?
.merge(&E::traverse_all()?, 1, &RESULT_LOOKUP)
W::internal(&[&T::traverse_all()?, &E::traverse_all()?], &RESULT_LOOKUP)
}

#[inline]
Expand Down Expand Up @@ -331,10 +326,8 @@ const BOUND_LOOKUP: KeyLookup = KeyLookup::Named(&["Included", "Excluded"]);
impl<T: TreeKey> TreeKey for Bound<T> {
#[inline]
fn traverse_all<W: Walk>() -> Result<W, W::Error> {
let t = T::traverse_all()?;
W::internal()
.merge(&t, 0, &BOUND_LOOKUP)?
.merge(&t, 1, &BOUND_LOOKUP)
let t: W = T::traverse_all()?;
W::internal(&[&t; 2], &BOUND_LOOKUP)
}

#[inline]
Expand Down Expand Up @@ -415,10 +408,8 @@ const RANGE_LOOKUP: KeyLookup = KeyLookup::Named(&["start", "end"]);
impl<T: TreeKey> TreeKey for Range<T> {
#[inline]
fn traverse_all<W: Walk>() -> Result<W, W::Error> {
let t = T::traverse_all()?;
W::internal()
.merge(&t, 0, &RANGE_LOOKUP)?
.merge(&t, 1, &RANGE_LOOKUP)
let t: W = T::traverse_all()?;
W::internal(&[&t; 2], &RANGE_LOOKUP)
}

#[inline]
Expand Down Expand Up @@ -497,10 +488,8 @@ impl<T: TreeAny> TreeAny for Range<T> {
impl<T: TreeKey> TreeKey for RangeInclusive<T> {
#[inline]
fn traverse_all<W: Walk>() -> Result<W, W::Error> {
let t = T::traverse_all()?;
W::internal()
.merge(&t, 0, &RANGE_LOOKUP)?
.merge(&t, 1, &RANGE_LOOKUP)
let t: W = T::traverse_all()?;
W::internal(&[&t; 2], &RANGE_LOOKUP)
}

#[inline]
Expand Down Expand Up @@ -538,7 +527,7 @@ const RANGE_FROM_LOOKUP: KeyLookup = KeyLookup::Named(&["start"]);
impl<T: TreeKey> TreeKey for RangeFrom<T> {
#[inline]
fn traverse_all<W: Walk>() -> Result<W, W::Error> {
W::internal().merge(&T::traverse_all()?, 0, &RANGE_FROM_LOOKUP)
W::internal(&[&T::traverse_all()?], &RANGE_FROM_LOOKUP)
}

#[inline]
Expand Down Expand Up @@ -615,7 +604,7 @@ const RANGE_TO_LOOKUP: KeyLookup = KeyLookup::Named(&["end"]);
impl<T: TreeKey> TreeKey for RangeTo<T> {
#[inline]
fn traverse_all<W: Walk>() -> Result<W, W::Error> {
W::internal().merge(&T::traverse_all()?, 0, &RANGE_TO_LOOKUP)
W::internal(&[&T::traverse_all()?], &RANGE_TO_LOOKUP)
}

#[inline]
Expand Down
2 changes: 1 addition & 1 deletion miniconf/src/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ impl<M: TreeKey + ?Sized, N, const D: usize> NodeIter<M, N, D> {
);
ExactSize {
iter: self,
count: meta.count,
count: meta.count.get(),
}
}

Expand Down
4 changes: 1 addition & 3 deletions miniconf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ mod walk;
pub use walk::*;

#[cfg(feature = "trees")]
mod trees;
#[cfg(feature = "trees")]
pub use trees::*;
pub mod trees;

#[cfg(feature = "derive")]
pub use miniconf_derive::*;
Expand Down
2 changes: 1 addition & 1 deletion miniconf/src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ pub trait TreeKey {
/// bar: [Leaf<u16>; 2],
/// };
/// let m: Metadata = S::traverse_all().unwrap();
/// assert_eq!((m.max_depth, m.max_length, m.count), (2, 4, 3));
/// assert_eq!((m.max_depth, m.max_length, m.count.get()), (2, 4, 3));
/// ```
fn traverse_all<W: Walk>() -> Result<W, W::Error>;

Expand Down
28 changes: 10 additions & 18 deletions miniconf/src/trees.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
//! Tools to convert from TreeKey nodes to `trees::Tree`
use crate::{IntoKeys, KeyLookup, Node, Transcode, Traversal, TreeKey, Walk};

use trees::Tree;

/// Build a [`trees::Tree`] of keys for a `TreeKey`.
pub fn tree<M: TreeKey, K: Transcode + Default>(
pub fn nodes<M: TreeKey, K: Transcode + Default>(
state: &mut [usize],
depth: usize,
) -> Result<Tree<(K, Node)>, Traversal> {
let mut root = Tree::new(M::transcode(state[..depth].into_keys())?);
if !root.data().1.is_leaf() && depth < state.len() {
debug_assert_eq!(state[depth], 0);
loop {
match tree::<M, _>(state, depth + 1) {
match nodes::<M, _>(state, depth + 1) {
Ok(child) => {
debug_assert_eq!(child.data().1.depth(), depth + 1);
root.push_back(child);
Expand All @@ -36,29 +38,19 @@ struct TreeWalk(Tree<Option<KeyLookup>>);
impl Walk for TreeWalk {
type Error = core::convert::Infallible;

fn internal() -> Self {
Self(Tree::new(None))
}
fn leaf() -> Self {
Self(Tree::new(None))
}
fn merge(mut self, walk: &Self, index: usize, lookup: &KeyLookup) -> Result<Self, Self::Error> {
if let Some(l) = self.0.root().data() {
debug_assert_eq!(l, lookup);
}
self.0
.root_mut()
.data_mut()
.get_or_insert_with(|| lookup.clone());
if matches!(lookup, KeyLookup::Homogeneous(_)) {
debug_assert_eq!(index, 0);
fn internal(children: &[&Self], lookup: &KeyLookup) -> Result<Self, Self::Error> {
let mut root = Tree::new(Some(lookup.clone()));
for child in children.iter() {
root.push_back(child.0.clone());
}
self.0.push_back(walk.0.clone());
Ok(self)
Ok(Self(root))
}
}

/// Build a Tree of KeyLookup
pub fn tree_all<M: TreeKey>() -> Tree<Option<KeyLookup>> {
pub fn all<M: TreeKey>() -> Tree<Option<KeyLookup>> {
M::traverse_all::<TreeWalk>().unwrap().0
}
76 changes: 42 additions & 34 deletions miniconf/src/walk.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use core::num::NonZero;

use crate::{KeyLookup, Packed};

/// Metadata about a `TreeKey` namespace.
///
/// Metadata includes paths that may be [`crate::Traversal::Absent`] at runtime.
#[non_exhaustive]
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Metadata {
/// The maximum length of a path in bytes.
///
Expand All @@ -20,7 +22,7 @@ pub struct Metadata {
pub max_depth: usize,

/// The exact total number of keys.
pub count: usize,
pub count: NonZero<usize>,

/// The maximum number of bits (see [`crate::Packed`])
pub max_bits: u32,
Expand All @@ -42,56 +44,62 @@ pub trait Walk: Sized {
/// Error type for `merge()`
type Error;

/// Return the walk starting point for an an empty internal node
fn internal() -> Self;

/// Return the walk starting point for a single leaf node
fn leaf() -> Self;

/// Merge node metadata into self.
///
/// # Args
/// * `walk`: The walk of the node to merge.
/// * `index`: Either the child index or zero for homogeneous.
/// * `children`: The walk of the children to merge.
/// * `lookup`: The namespace the node(s) are in.
fn merge(self, walk: &Self, index: usize, lookup: &KeyLookup) -> Result<Self, Self::Error>;
fn internal(children: &[&Self], lookup: &KeyLookup) -> Result<Self, Self::Error>;
}

impl Walk for Metadata {
type Error = core::convert::Infallible;

#[inline]
fn internal() -> Self {
Default::default()
}

#[inline]
fn leaf() -> Self {
Self {
count: 1,
..Default::default()
count: NonZero::<usize>::MIN,
max_length: 0,
max_depth: 0,
max_bits: 0,
}
}

#[inline]
fn merge(mut self, meta: &Self, index: usize, lookup: &KeyLookup) -> Result<Self, Self::Error> {
let (len, count) = match lookup {
KeyLookup::Named(names) => (names[index].len(), 1),
KeyLookup::Numbered(_len) => {
(index.checked_ilog10().unwrap_or_default() as usize + 1, 1)
}
KeyLookup::Homogeneous(len) => {
debug_assert_eq!(index, 0);
(len.ilog10() as usize + 1, len.get())
}
};
self.max_depth = self.max_depth.max(1 + meta.max_depth);
self.max_length = self.max_length.max(len + meta.max_length);
debug_assert_ne!(meta.count, 0);
self.count += count * meta.count;
self.max_bits = self
.max_bits
.max(Packed::bits_for(lookup.len().get() - 1) + meta.max_bits);
Ok(self)
fn internal(children: &[&Self], lookup: &KeyLookup) -> Result<Self, Self::Error> {
let mut max_depth = 0;
let mut max_length = 0;
let mut count = 0;
let mut max_bits = 0;
// TODO: swap loop and match
for (index, child) in children.iter().enumerate() {
let (len, n) = match lookup {
KeyLookup::Named(names) => {
debug_assert_eq!(children.len(), names.len());
(names[index].len(), 1)
}
KeyLookup::Numbered(len) => {
debug_assert_eq!(children.len(), len.get());
(index.checked_ilog10().unwrap_or_default() as usize + 1, 1)
}
KeyLookup::Homogeneous(len) => {
debug_assert_eq!(children.len(), 1);
(len.ilog10() as usize + 1, len.get())
}
};
max_depth = max_depth.max(1 + child.max_depth);
max_length = max_length.max(len + child.max_length);
count += n * child.count.get();
max_bits = max_bits.max(Packed::bits_for(lookup.len().get() - 1) + child.max_bits);
}
Ok(Self {
max_bits,
max_depth,
max_length,
count: NonZero::new(count).unwrap(),
})
}
}
2 changes: 1 addition & 1 deletion miniconf/tests/arrays.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,5 @@ fn metadata() {
let m: Metadata = Settings::traverse_all().unwrap();
assert_eq!(m.max_depth, 4);
assert_eq!(m.max_length("/"), "/aam/0/0/c".len());
assert_eq!(m.count, 11);
assert_eq!(m.count.get(), 11);
}
2 changes: 1 addition & 1 deletion miniconf/tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ fn meta() {
let meta = Settings::traverse_all::<Metadata>().unwrap();
assert_eq!(meta.max_depth, 2);
assert_eq!(meta.max_length("/"), "/c/inner".len());
assert_eq!(meta.count, 3);
assert_eq!(meta.count.get(), 3);
}

#[test]
Expand Down
6 changes: 3 additions & 3 deletions miniconf/tests/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn generic_type() {
let metadata = Settings::<f32>::traverse_all::<Metadata>().unwrap();
assert_eq!(metadata.max_depth, 1);
assert_eq!(metadata.max_length, "data".len());
assert_eq!(metadata.count, 1);
assert_eq!(metadata.count.get(), 1);
}

#[test]
Expand All @@ -34,7 +34,7 @@ fn generic_array() {
let metadata = Settings::<f32>::traverse_all::<Metadata>().unwrap();
assert_eq!(metadata.max_depth, 2);
assert_eq!(metadata.max_length("/"), "/data/0".len());
assert_eq!(metadata.count, 2);
assert_eq!(metadata.count.get(), 2);
}

#[test]
Expand All @@ -58,7 +58,7 @@ fn generic_struct() {
let metadata = Settings::<Inner>::traverse_all::<Metadata>().unwrap();
assert_eq!(metadata.max_depth, 1);
assert_eq!(metadata.max_length("/"), "/inner".len());
assert_eq!(metadata.count, 1);
assert_eq!(metadata.count.get(), 1);
}

#[test]
Expand Down
6 changes: 4 additions & 2 deletions miniconf/tests/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,17 @@ fn root() {
);
}

#[cfg(feature = "trees")]
#[test]
fn tree() {
let mut state = [0; 4];
let t = miniconf::tree::<Settings, Path<String, '/'>>(&mut state, 0).unwrap();
let t = miniconf::trees::nodes::<Settings, Path<String, '/'>>(&mut state, 0).unwrap();
println!("{t:?}");
}

#[cfg(feature = "trees")]
#[test]
fn tree_all() {
let t = miniconf::tree_all::<Settings>();
let t = miniconf::trees::all::<Settings>();
println!("{t:?}");
}
4 changes: 2 additions & 2 deletions miniconf/tests/packed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ fn size() {
let meta: Metadata = A31::traverse_all().unwrap();
assert_eq!(meta.max_bits, 31);
assert_eq!(meta.max_depth, 31);
assert_eq!(meta.count, 1usize.pow(31));
assert_eq!(meta.count.get(), 1usize.pow(31));
assert_eq!(meta.max_length, 31);

// Another way to get to 32 bit is to take 15 length-3 (2 bit) levels and one length-1 (1 bit) level to fill it, needing (3**15 ~ 14 M) storage.
Expand All @@ -167,6 +167,6 @@ fn size() {
let meta: Metadata = A16::traverse_all().unwrap();
assert_eq!(meta.max_bits, 31);
assert_eq!(meta.max_depth, 16);
assert_eq!(meta.count, 3usize.pow(15));
assert_eq!(meta.count.get(), 3usize.pow(15));
assert_eq!(meta.max_length, 16);
}
2 changes: 1 addition & 1 deletion miniconf/tests/skipped.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn meta() {
let meta = Settings::traverse_all::<Metadata>().unwrap();
assert_eq!(meta.max_depth, 1);
assert_eq!(meta.max_length("/"), "/value".len());
assert_eq!(meta.count, 1);
assert_eq!(meta.count.get(), 1);
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion miniconf/tests/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ fn structs() {
let metadata = Settings::traverse_all::<Metadata>().unwrap();
assert_eq!(metadata.max_depth, 2);
assert_eq!(metadata.max_length("/"), "/d/a".len());
assert_eq!(metadata.count, 4);
assert_eq!(metadata.count.get(), 4);

assert_eq!(paths::<Settings, 2>(), ["/a", "/b", "/c", "/d/a"]);
}
Expand Down
Loading

0 comments on commit 4367959

Please sign in to comment.