Skip to content

Commit

Permalink
Merge pull request #256 from quartiq/no-recursion-depth-const-generic
Browse files Browse the repository at this point in the history
no recursion depth const generic
  • Loading branch information
jordens authored Oct 24, 2024
2 parents 6f0d12a + 023bd2a commit 11e0ed2
Show file tree
Hide file tree
Showing 41 changed files with 1,320 additions and 1,391 deletions.
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,39 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased](https://github.com/quartiq/miniconf/compare/v0.16.3...HEAD) - DATE

### Changed

* The "recursion depth" const generic of the `Tree*` traits has been removed in favor of
explicit newtypes implementing the traits for leaf values `Leaf<T>` and `StrLeaf<T>`.
This reduces code duplication, macro usage and complexity.
It enables any recursion depth, does away with most manual tracking of recursion depth
in proc macro attributes, and simplifies code and derive macros, at the expense of having
to wrap leaves in newtypes and having to pass an indices length to `TreeKey::nodes()`.
* `TreeKey::nodes` requires the indices length as a const generic.
* `get`, `get_mut`, `validate` proc macro attributes are now `Expr`
* `Key::find` and `Keys::finalize` return a `Result`, not an `Option` to reduce code duplication
* Derive macro lifetime and type param trait bound heuristics have been improved.
They should now yield the correct result in mpst cases.
* Internal nodes must always have at least one leaf. Trait impls for `[T; 0]` and `()`
have been removed. The `len` argument to the `traverse_by_key` closure is now a
`NonZero<usize>`.

### Added

* `Leaf` to explicitly manage `Serialize/Deserialize` leaf values.
* `StrLeaf` to manage values via `AsRef<str>`/`TryFrom<&str>` (e.g. Enums via `strum`)
* `Tree*` impls for heterogeneous inline tuples `(T0, T1, ...)` up to length 8 (also useful
for enum variants)
* `impl Tree* for &{mut,} T where T: Tree*` blanket impls to simplify usage downstream
* `defer` derive attribute to quickly defer to a downstream field without having to write accessors
* `Metadata` now also computes maximum `Packed` bits usage

### Removed

* `TreeSerialize`/`TreeDeserialize`/`TreeAny` don't need `TreeKey`

## [0.16.3](https://github.com/quartiq/miniconf/compare/v0.16.2...v0.16.3) - 2024-10-20

### Added
Expand Down
49 changes: 20 additions & 29 deletions miniconf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,58 +19,51 @@ providers are supported.

```rust
use serde::{Deserialize, Serialize};
use miniconf::{Error, json, JsonPath, Traversal, Tree, TreeKey, Path, Packed, Node};
use miniconf::{Error, json, JsonPath, Traversal, Tree, TreeKey, Path, Packed, Node, Leaf};

#[derive(Deserialize, Serialize, Default, Tree)]
enum Either {
pub enum Either {
#[default]
Bad,
Good,
A(i32),
B(#[tree(depth=1)] Inner),
C(#[tree(depth=2)] [Inner; 2]),
A(Leaf<i32>),
B(Inner),
C([Inner; 2]),
}

#[derive(Deserialize, Serialize, Default, Tree)]
struct Inner {
a: i32,
b: i32,
pub struct Inner {
a: Leaf<i32>,
b: Leaf<i32>,
}

#[derive(Tree, Default)]
struct Settings {
foo: bool,
enum_: Either,
struct_: Inner,
array: [i32; 2],
option: Option<i32>,
pub struct Settings {
foo: Leaf<bool>,
enum_: Leaf<Either>,
struct_: Leaf<Inner>,
array: Leaf<[i32; 2]>,
option: Leaf<Option<i32>>,

#[tree(skip)]
#[allow(unused)]
skipped: (),

#[tree(depth=1)]
struct_tree: Inner,
#[tree(depth=3)]
enum_tree: Either,
#[tree(depth=1)]
array_tree: [i32; 2],
#[tree(depth=2)]
array_tree: [Leaf<i32>; 2],
array_tree2: [Inner; 2],

#[tree(depth=1)]
option_tree: Option<i32>,
#[tree(depth=2)]
option_tree: Option<Leaf<i32>>,
option_tree2: Option<Inner>,
#[tree(depth=3)]
array_option_tree: [Option<Inner>; 2],
}

let mut settings = Settings::default();

// Atomic updates by field name
json::set(&mut settings,"/foo", b"true")?;
assert_eq!(settings.foo, true);
assert_eq!(*settings.foo, true);
json::set(&mut settings, "/enum_", br#""Good""#)?;
json::set(&mut settings, "/struct_", br#"{"a": 3, "b": 3}"#)?;
json::set(&mut settings, "/array", b"[6, 6]")?;
Expand All @@ -96,7 +89,7 @@ json::set_by_key(&mut settings, &JsonPath(".array_tree2[1].b"), b"10")?;

// Hiding paths by setting an Option to `None` at runtime
assert_eq!(json::set(&mut settings, "/option_tree", b"13"), Err(Traversal::Absent(1).into()));
settings.option_tree = Some(0);
settings.option_tree = Some(0.into());
json::set(&mut settings, "/option_tree", b"13")?;
// Hiding a path and descending into the inner `Tree`
settings.option_tree2 = Some(Inner::default());
Expand All @@ -112,7 +105,7 @@ let len = json::get(&settings, "/struct_", &mut buf).unwrap();
assert_eq!(&buf[..len], br#"{"a":3,"b":3}"#);

// Iterating over all paths
for path in Settings::nodes::<Path<heapless::String<32>, '/'>>() {
for path in Settings::nodes::<Path<heapless::String<32>, '/'>, 4>() {
let (path, node) = path.unwrap();
assert!(node.is_leaf());
// Serialize each
Expand Down Expand Up @@ -173,8 +166,6 @@ Leaf fields/items need to support the respective [`serde`] (and the desired `ser
backend) or [`core::any`] trait.

Structs, enums, arrays, and Options can then be cascaded to construct more complex trees.
When using the derive macro, the behavior and tree recursion depth can be configured for each
struct field using the `#[tree(depth(Y))]` attribute.

See also the [`TreeKey`] trait documentation for details.

Expand All @@ -191,7 +182,7 @@ It implements [`Keys`].

## Limitations

* `enum`: The derive macros don't support enums with record (named fields) variants or tuple variants with more than one field. Only unit, newtype and skipped variants are supported. Without the derive macros, these `enums` are still however usable in their atomic `serde` form as leaf nodes.
* `enum`: The derive macros don't support enums with record (named fields) variants or tuple variants with more than one field. Only unit, newtype and skipped variants are supported. Without the derive macros, these `enums` are still however usable in their atomic `serde` form as leaf nodes. Inline tuple variants are supported.
* The derive macros don't handle `std`/`alloc` smart pointers ( `Box`, `Rc`, `Arc`) in any special way. They however still be handled with accessors (`get`, `get_mut`, `validate`).
* The derive macros only support flattening in non-ambiguous situations (single field structs and single variant enums, both modulo skipped fields/variants and unit variants).

Expand Down
2 changes: 1 addition & 1 deletion miniconf/examples/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn main() -> anyhow::Result<()> {

// Dump settings
let mut buf = vec![0; 1024];
for (key, _node) in Settings::nodes::<Path<String, '-'>>().map(Result::unwrap) {
for (key, _node) in Settings::nodes::<Path<String, '-'>, 4>().map(Result::unwrap) {
match json::get_by_key(&settings, &key, &mut buf[..]) {
Ok(len) => {
println!(
Expand Down
35 changes: 14 additions & 21 deletions miniconf/examples/common.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use miniconf::Tree;
use miniconf::{Leaf, Tree};
use serde::{Deserialize, Serialize};

// Either/Inner/Settings are straight from README.md
Expand All @@ -8,50 +8,43 @@ pub enum Either {
#[default]
Bad,
Good,
A(i32),
B(#[tree(depth = 1)] Inner),
C(#[tree(depth = 2)] [Inner; 2]),
A(Leaf<i32>),
B(Inner),
C([Inner; 2]),
}

#[derive(Deserialize, Serialize, Default, Tree)]
pub struct Inner {
a: i32,
b: i32,
a: Leaf<i32>,
b: Leaf<i32>,
}

#[derive(Tree, Default)]
pub struct Settings {
foo: bool,
enum_: Either,
struct_: Inner,
array: [i32; 2],
option: Option<i32>,
foo: Leaf<bool>,
enum_: Leaf<Either>,
struct_: Leaf<Inner>,
array: Leaf<[i32; 2]>,
option: Leaf<Option<i32>>,

#[tree(skip)]
#[allow(unused)]
skipped: (),

#[tree(depth = 1)]
struct_tree: Inner,
#[tree(depth = 3)]
enum_tree: Either,
#[tree(depth = 1)]
array_tree: [i32; 2],
#[tree(depth = 2)]
array_tree: [Leaf<i32>; 2],
array_tree2: [Inner; 2],

#[tree(depth = 1)]
option_tree: Option<i32>,
#[tree(depth = 2)]
option_tree: Option<Leaf<i32>>,
option_tree2: Option<Inner>,
#[tree(depth = 3)]
array_option_tree: [Option<Inner>; 2],
}

impl Settings {
/// Fill some of the Options
pub fn enable(&mut self) {
self.option_tree = Some(8);
self.option_tree = Some(8.into());
self.enum_tree = Either::B(Default::default());
self.option_tree2 = Some(Default::default());
self.array_option_tree[1] = Some(Default::default());
Expand Down
24 changes: 12 additions & 12 deletions miniconf/examples/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,23 @@ impl<I> From<usize> for Error<I> {
pub const SEPARATOR: char = '/';

#[derive(Debug, PartialEq, PartialOrd)]
pub struct Menu<M, const Y: usize, const D: usize = Y> {
pub struct Menu<M, const D: usize> {
key: Packed,
_m: PhantomData<M>,
}

impl<M, const Y: usize> Default for Menu<M, Y>
impl<M, const D: usize> Default for Menu<M, D>
where
M: TreeKey<Y> + TreeSerialize<Y> + TreeDeserializeOwned<Y> + Default,
M: TreeKey + TreeSerialize + TreeDeserializeOwned + Default,
{
fn default() -> Self {
Self::new(Packed::default())
}
}

impl<M, const Y: usize> Menu<M, Y>
impl<M, const D: usize> Menu<M, D>
where
M: TreeKey<Y> + TreeSerialize<Y> + TreeDeserializeOwned<Y> + Default,
M: TreeKey + TreeSerialize + TreeDeserializeOwned + Default,
{
pub fn new(key: Packed) -> Self {
Self {
Expand All @@ -92,7 +92,7 @@ where
}

fn pop(&self, levels: usize) -> Result<(Self, Node), Traversal> {
let (idx, node): (Indices<[_; Y]>, _) = M::transcode(self.key)?;
let (idx, node): (Indices<[_; D]>, _) = M::transcode(self.key)?;
if let Some(idx) = idx.get(
..node
.depth()
Expand Down Expand Up @@ -121,7 +121,7 @@ where
pub fn list<S: core::fmt::Write + Default>(
&self,
) -> Result<impl Iterator<Item = Result<S, usize>>, Traversal> {
Ok(M::nodes::<Path<S, SEPARATOR>>()
Ok(M::nodes::<Path<S, SEPARATOR>, D>()
.root(self.key)?
.map(|pn| pn.map(|(p, _n)| p.into_inner())))
}
Expand All @@ -148,7 +148,7 @@ where
buf: &mut [u8],
) -> Result<(), miniconf::Error<::postcard::Error>> {
let def = M::default();
for keys in M::nodes::<Packed>().root(self.key)? {
for keys in M::nodes::<Packed, D>().root(self.key)? {
// Slight abuse of TooLong for "keys to long for packed"
let (keys, node) = keys.map_err(|depth| Traversal::TooLong(depth))?;
debug_assert!(node.is_leaf());
Expand Down Expand Up @@ -180,11 +180,11 @@ where
let def = M::default();
let bl = buf.len();
let mut sl = &mut buf[..];
Path::<_, SEPARATOR>::from(WriteWrap(&mut sl)).transcode::<M, Y, _>(self.key)?;
Path::<_, SEPARATOR>::from(WriteWrap(&mut sl)).transcode::<M, _>(self.key)?;
let root_len = bl - sl.len();
awrite(&mut write, &buf[..root_len]).await?;
awrite(&mut write, ">\n".as_bytes()).await?;
for keys in M::nodes::<Packed>().root(self.key)? {
for keys in M::nodes::<Packed, D>().root(self.key)? {
let (keys, node) = keys?;
let (val, rest) = match json::get_by_key(instance, keys, &mut buf[..]) {
Err(miniconf::Error::Traversal(Traversal::TooShort(_))) => {
Expand All @@ -204,7 +204,7 @@ where
awrite(&mut write, " ".as_bytes()).await?;
let rl = rest.len();
let mut sl = &mut rest[..];
Path::<_, SEPARATOR>::from(WriteWrap(&mut sl)).transcode::<M, Y, _>(keys)?;
Path::<_, SEPARATOR>::from(WriteWrap(&mut sl)).transcode::<M, _>(keys)?;
let path_len = rl - sl.len();
awrite(&mut write, &rest[root_len..path_len]).await?;
awrite(&mut write, ": ".as_bytes()).await?;
Expand Down Expand Up @@ -279,7 +279,7 @@ async fn main() -> Result<()> {

let mut stdout = embedded_io_adapters::tokio_1::FromTokio::new(tokio::io::stdout());
let mut stdin = tokio::io::BufReader::new(tokio::io::stdin()).lines();
let mut menu = Menu::default();
let mut menu = Menu::<_, 4>::default();

while let Some(line) = stdin.next_line().await? {
let ret = menu
Expand Down
11 changes: 6 additions & 5 deletions miniconf/examples/scpi.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use core::str;

use miniconf::{
json, IntoKeys, Key, KeyLookup, Keys, PathIter, TreeDeserializeOwned, TreeSerialize,
json, IntoKeys, Key, KeyLookup, Keys, PathIter, Traversal, TreeDeserializeOwned, TreeSerialize,
};

mod common;
Expand All @@ -19,7 +19,7 @@ use common::Settings;
struct ScpiKey<T: ?Sized>(T);

impl<T: AsRef<str> + ?Sized> Key for ScpiKey<T> {
fn find(&self, lookup: &KeyLookup) -> Option<usize> {
fn find(&self, lookup: &KeyLookup) -> Result<usize, Traversal> {
let s = self.0.as_ref();
if let Some(names) = lookup.names {
let mut truncated = None;
Expand All @@ -35,7 +35,7 @@ impl<T: AsRef<str> + ?Sized> Key for ScpiKey<T> {
}
if name.len() == s.len() {
// Exact match: return immediately
return Some(i);
return Ok(i);
}
if truncated.is_some() {
// Multiple truncated matches: ambiguous unless there is an additional exact match
Expand All @@ -53,6 +53,7 @@ impl<T: AsRef<str> + ?Sized> Key for ScpiKey<T> {
} else {
s.parse().ok()
}
.ok_or(Traversal::NotFound(1))
}
}

Expand Down Expand Up @@ -89,9 +90,9 @@ enum Error {
Utf8(#[from] core::str::Utf8Error),
}

struct ScpiCtrl<M, const Y: usize>(M);
struct ScpiCtrl<M>(M);

impl<M: TreeSerialize<Y> + TreeDeserializeOwned<Y>, const Y: usize> ScpiCtrl<M, Y> {
impl<M: TreeSerialize + TreeDeserializeOwned> ScpiCtrl<M> {
fn new(settings: M) -> Self {
Self(settings)
}
Expand Down
Loading

0 comments on commit 11e0ed2

Please sign in to comment.