tamer: fmt (ListDisplayWrapper::fmt_nth): List display without a slice

This exposes the internal rendering of `ListDisplayWrapper::fmt` such that
we can output a list without actually creating a list.  This is used in an
upcoming change for =ele_parse!= so that Sum NTs can render the union of all
the QNames that their constituent NTs match on, recursively, as a single
list, without having to create an ephemeral collection only for display.

If Rust supports const functions for arrays/Vecs in the future, we could
generate this at compile-time, if we were okay with the (small) cost, but
this solution seems just fine.  But output may be even _more_ performant
since they'd all be adjacent in memory.

This is used in these secenarios:

  1. Diagnostic messages;
  2. Error messages (overlaps with #1); and
  3. `Display::fmt` of the `ParseState`s themselves.

The reason that we want this to be reasonably performant is because #3
results in a _lot_ of output---easily GiB of output depending on what is
being traced.  Adding heap allocations to this would make it even slower,
since a description is generated for each individual trace.

Anyway, this is a fairly simple solution, albeit a little bit less clear,
and only came after I had tried a number of other different approaches
related to recursively constructing QName lists at compile time; they
weren't worth the effort when this was so easy to do.

DEV-7145
main
Mike Gerwitz 2022-08-17 10:16:04 -04:00
parent 6b29479fd6
commit fd3184c795
1 changed files with 83 additions and 16 deletions

View File

@ -221,32 +221,61 @@ pub trait ListDisplayWrapper {
/// [`ListDisplayWrapper::wrap`] may be used to produce a
/// [`Display`]-able object instead.
fn fmt<T: Display>(list: &[T], f: &mut Formatter) -> Result {
let maxi = list.len().saturating_sub(1);
let lasti = list.len().saturating_sub(1);
// This can be further abstracted away using the above primitives,
// if ever we have a use.
for next in list.into_iter().enumerate() {
match next {
(0, x) if maxi == 0 => {
Self::Single::fmt(x, f)?;
}
(i, x) => Self::fmt_nth(lasti, i, x, f)?,
};
}
(0, x) => {
Self::First::fmt(x, f)?;
}
Ok(())
}
(i, x) if maxi == i => {
if i == 1 {
Self::LastOfPair::fmt(x, f)?;
} else {
Self::LastOfMany::fmt(x, f)?;
}
}
/// Format an item as if it were the `i`th value of a list of length
/// `lasti+1`.
///
/// This allows for generating list-like output without the expense of
/// actually producing a list.
/// This may be useful when values are stored in different memory
/// location,
/// so that the displaying of those values is a problem of invoking
/// this method on them in the right order,
/// rather than collecting them just for the sake of display.
/// If Rust supports `const` array/Vec functions in the future,
/// this may not be necessary anymore,
/// unless we also don't want the space cost of such a
/// precomputation
/// (but it may come with performance benefits from locality).
#[inline]
fn fmt_nth<T: Display>(
lasti: usize,
i: usize,
item: &T,
f: &mut Formatter,
) -> Result {
match (i, item) {
(0, x) if lasti == 0 => {
Self::Single::fmt(x, f)?;
}
(_, x) => {
Self::Middle::fmt(x, f)?;
(0, x) => {
Self::First::fmt(x, f)?;
}
(i, x) if lasti == i => {
if i == 1 {
Self::LastOfPair::fmt(x, f)?;
} else {
Self::LastOfMany::fmt(x, f)?;
}
}
(_, x) => {
Self::Middle::fmt(x, f)?;
}
}
Ok(())
@ -547,4 +576,42 @@ mod test {
"test fmt",
);
}
// `fmt_nth` is used by the above tests,
// but that's an implementation detail;
// we expose it as a public API so it ought to be tested too.
#[test]
fn fmt_nth() {
type Sut = QualConjList<"thing", "things", "or", Raw>;
assert_eq!(
DisplayFn(|f| Sut::fmt_nth(0, 0, &"foo", f)).to_string(),
"thing foo",
);
assert_eq!(
DisplayFn(|f| Sut::fmt_nth(1, 0, &"foo", f)).to_string(),
"things foo",
);
assert_eq!(
DisplayFn(|f| Sut::fmt_nth(1, 1, &"foo", f)).to_string(),
" or foo",
);
assert_eq!(
DisplayFn(|f| Sut::fmt_nth(2, 0, &"foo", f)).to_string(),
"things foo",
);
assert_eq!(
DisplayFn(|f| Sut::fmt_nth(2, 1, &"foo", f)).to_string(),
", foo",
);
assert_eq!(
DisplayFn(|f| Sut::fmt_nth(2, 2, &"foo", f)).to_string(),
", or foo",
);
}
}