2022-06-10 16:28:15 -04:00
|
|
|
// TAMER formatting helpers
|
|
|
|
//
|
|
|
|
// Copyright (C) 2014-2022 Ryan Specialty Group, LLC.
|
|
|
|
//
|
|
|
|
// This file is part of TAME.
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
//! Typed formatting helpers.
|
|
|
|
//!
|
|
|
|
//! These types create composable formatters for use with [`Display`].
|
|
|
|
//! Whereas [`Display`] operates on data owned by the type implementing it,
|
|
|
|
//! these formatters compose into functions that operate on data provided
|
|
|
|
//! _to_ it.
|
|
|
|
//! Consequently,
|
|
|
|
//! formatters are simply types,
|
|
|
|
//! and writes can be streamed just as they are with [`Display`].
|
|
|
|
//!
|
|
|
|
//! There are two types of wrappers:
|
|
|
|
//!
|
|
|
|
//! - [`DisplayWrapper`] formats objects as atoms; and
|
|
|
|
//! - [`ListDisplayWrapper`] maps a [`DisplayWrapper`] to each of its
|
|
|
|
//! items as atoms,
|
|
|
|
//! where the specific wrapper used depends on the position of the
|
|
|
|
//! item within the list and the properties of the list itself.
|
|
|
|
//!
|
|
|
|
//! [`DisplayWrapper::wrap`] and [`ListDisplayWrapper::wrap`] can be used to
|
|
|
|
//! associate wrappers with data,
|
|
|
|
//! effectively creating a custom [`Display`] implementation controlled
|
|
|
|
//! by the caller.
|
|
|
|
//! This is _not_ a substitute for canonical object representations that own
|
|
|
|
//! their own [`Display`] implementation,
|
|
|
|
//! but is suitable where display is context-dependent.
|
|
|
|
//!
|
|
|
|
//! For example:
|
|
|
|
//!
|
|
|
|
//! ```
|
|
|
|
//! # use tamer::{fmt::*, xir::fmt::*};
|
|
|
|
//! let attrs = ["foo", "bar", "baz"];
|
|
|
|
//!
|
|
|
|
//! assert_eq!(
|
|
|
|
//! AndQualConjList::<"attribute", "attributes", Tt<XmlAttr>>::wrap(&attrs)
|
|
|
|
//! .to_string(),
|
|
|
|
//! "attributes `@foo`, `@bar`, and `@baz`",
|
|
|
|
//! );
|
|
|
|
//!
|
|
|
|
//! assert_eq!(
|
|
|
|
//! AndQualConjList::<"attribute", "attributes", Tt<XmlAttr>>::wrap(&attrs[0..1])
|
|
|
|
//! .to_string(),
|
|
|
|
//! "attribute `@foo`",
|
|
|
|
//! );
|
|
|
|
//!
|
|
|
|
//! assert_eq!(
|
|
|
|
//! AndConjList::<Raw>::wrap(&["toil", "trouble"]).to_string(),
|
|
|
|
//! "toil and trouble",
|
|
|
|
//! );
|
|
|
|
//! ```
|
|
|
|
//!
|
|
|
|
//! This is in contrast to the approach taken by (for example)
|
|
|
|
//! the [diagnostic system](crate::diagnose),
|
|
|
|
//! which is to produce a data structure representing the data to be
|
|
|
|
//! formatted.
|
|
|
|
//! This system is not useful as,
|
|
|
|
//! and is not intended to be,
|
|
|
|
//! an IR for more sophisticated manipulation of data prior to output.
|
|
|
|
|
|
|
|
use std::{
|
|
|
|
fmt::{Display, Formatter, Result},
|
|
|
|
marker::PhantomData,
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Wrapper for a [`Display`]-able type.
|
|
|
|
///
|
|
|
|
/// See the [module-level documentation](super) for more information.
|
|
|
|
pub trait DisplayWrapper {
|
|
|
|
/// Transform inner data and output using the provided [`Formatter`].
|
|
|
|
///
|
|
|
|
/// If a [`Formatter`] is not available,
|
|
|
|
/// [`ListDisplayWrapper::wrap`] may be used to produce a
|
|
|
|
/// [`Display`]-able object instead.
|
|
|
|
fn fmt<T: Display>(inner: T, f: &mut Formatter) -> Result;
|
|
|
|
|
|
|
|
/// Associate data with a [`DisplayWrapper`] for rendering using
|
|
|
|
/// [`Display`].
|
|
|
|
///
|
|
|
|
/// This has the effect of creating an arbitrary [`Display`]
|
|
|
|
/// implementation for the wrapped object,
|
|
|
|
/// which will work well with [`format!`] and anything else that
|
|
|
|
/// does not have access to an explicit [`Formatter`].
|
|
|
|
fn wrap<T: Display>(inner: T) -> Wrap<Self, T> {
|
|
|
|
Wrap {
|
|
|
|
inner,
|
|
|
|
_phantom: Default::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Wrapper with associated data.
|
|
|
|
///
|
|
|
|
/// This has the effect of creating an arbitrary [`Display`] implementation
|
|
|
|
/// for the wrapped data,
|
|
|
|
/// which will work well with [`format!`] and anything else that does
|
|
|
|
/// not have access to an explicit [`Formatter`].
|
|
|
|
pub struct Wrap<W: DisplayWrapper + ?Sized, T: Display> {
|
|
|
|
inner: T,
|
|
|
|
_phantom: PhantomData<W>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<W: DisplayWrapper, T: Display> Display for Wrap<W, T> {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
|
|
|
W::fmt(&self.inner, f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Echo data as-is without any wrapping.
|
|
|
|
///
|
|
|
|
/// This is primarily used at the root of a wrapper composition.
|
|
|
|
pub struct Raw;
|
|
|
|
|
|
|
|
impl DisplayWrapper for Raw {
|
|
|
|
fn fmt<T: Display>(inner: T, f: &mut Formatter) -> Result {
|
|
|
|
inner.fmt(f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Prefix data with a static [`str`].
|
|
|
|
///
|
|
|
|
/// See also [`Suffix`] and [`Delim`].
|
|
|
|
pub struct Prefix<const PREFIX: &'static str, W: DisplayWrapper>(
|
|
|
|
PhantomData<W>,
|
|
|
|
);
|
|
|
|
|
|
|
|
impl<const PREFIX: &'static str, W: DisplayWrapper> DisplayWrapper
|
|
|
|
for Prefix<PREFIX, W>
|
|
|
|
{
|
|
|
|
fn fmt<T: Display>(inner: T, f: &mut Formatter) -> Result {
|
|
|
|
f.write_str(PREFIX)?;
|
|
|
|
W::fmt(inner, f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Suffix data with a static [`str`].
|
|
|
|
///
|
|
|
|
/// See also [`Prefix`] and [`Delim`].
|
|
|
|
pub struct Suffix<const SUFFIX: &'static str, W: DisplayWrapper>(
|
|
|
|
PhantomData<W>,
|
|
|
|
);
|
|
|
|
|
|
|
|
impl<const SUFFIX: &'static str, W: DisplayWrapper> DisplayWrapper
|
|
|
|
for Suffix<SUFFIX, W>
|
|
|
|
{
|
|
|
|
fn fmt<T: Display>(inner: T, f: &mut Formatter) -> Result {
|
|
|
|
W::fmt(inner, f)?;
|
|
|
|
f.write_str(SUFFIX)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Surround a value in delimiters.
|
|
|
|
///
|
|
|
|
/// See also [`Prefix`] and [`Suffix`].
|
|
|
|
pub type Delim<const LEFT: &'static str, const RIGHT: &'static str, W> =
|
|
|
|
Prefix<LEFT, Suffix<RIGHT, W>>;
|
|
|
|
|
|
|
|
/// Denote text that would conventionally be delimited in a teletypewriter
|
|
|
|
/// font.
|
|
|
|
///
|
|
|
|
/// This produces a markdown-style quote using backticks.
|
|
|
|
///
|
|
|
|
/// NB: This does not defend against nested quotes,
|
|
|
|
/// so this is _not_ safe against format escapes.
|
|
|
|
pub type Tt<W> = Delim<"`", "`", W>;
|
|
|
|
|
tamer: xir::parse::ele: Initial element parser generator concept
This begins generating parsers that are capable of parsing elements. I need
to move on, so this abstraction isn't going to go as far as it could, but
let's see where it takes me.
This was the work that required the recent lookahead changes, which has been
detailed in previous commits.
This initial support is basic, but robust. It supports parsing elements
with attributes and children, but it does not yet support the equivalent of
the Kleene star (`*`). Such support will likely be added by supporting
parsers that are able to recurse on their own definition in tail position,
which will also require supporting parsers that do not add to the stack.
This generates parsers that, like all the other parsers, use enums to
provide a typed stack. Stitched parsers produce a nested stack that is
always bounded in size. Fortunately, expressions---which can nest
deeply---do not need to maintain ancestor context on the stack, and so this
should work fine; we can get away with this because XIRF ensures proper
nesting for us. Statements that _do_ need to maintain such context are not
nested.
This also does not yet support emitting an object on closing tag, which
will be necessary for NIR, which will be a streaming IR that is "near" to
the source XML in structure. This will then be used to lower into AIR for
the ASG, which gives structure needed for further analysis.
More information to come; I just want to get this committed to serve as a
mental synchronization point and clear my head, since I've been sitting on
these changes for so long and have to keep stashing them as I tumble down
rabbit holes covered in yak hair.
DEV-7145
2022-07-13 13:55:32 -04:00
|
|
|
/// Quote text that would conventionally be delimited in a teletypewriter
|
|
|
|
/// font.
|
|
|
|
///
|
|
|
|
/// This is a more terse alternative to [`Tt`] when formatter composition is
|
|
|
|
/// unneeded.
|
|
|
|
pub type TtQuote = Tt<Raw>;
|
|
|
|
|
2022-06-10 16:28:15 -04:00
|
|
|
/// Prefix with a single space.
|
|
|
|
pub type Sp<W> = Prefix<" ", W>;
|
|
|
|
|
|
|
|
/// Wrapper for a list that maps each element to a context-specific
|
|
|
|
/// [`DisplayWrapper`].
|
|
|
|
///
|
|
|
|
/// This uses the slice API for wrapping since [`Display`] takes objects by
|
|
|
|
/// non-mutable reference,
|
|
|
|
/// and so we cannot consume an iterator.
|
|
|
|
///
|
|
|
|
/// The associated types define the wrappers to use for items in various
|
|
|
|
/// positions depending on the length of the list.
|
|
|
|
/// They were chosen to be somewhat intuitive given the necessary use cases.
|
|
|
|
///
|
|
|
|
/// See the [module-level documentation](super) for examples.
|
|
|
|
pub trait ListDisplayWrapper {
|
|
|
|
/// Wrapper to use when the list contains only a single item.
|
|
|
|
type Single: DisplayWrapper = Raw;
|
|
|
|
/// Wrapper for the first item in a multi-item list.
|
|
|
|
type First: DisplayWrapper = Raw;
|
|
|
|
/// Wrapper for all but the first and last items in a multi-item list.
|
|
|
|
type Middle: DisplayWrapper = Raw;
|
|
|
|
/// Wrapper for the last item of a list containing a pair of items.
|
|
|
|
type LastOfPair: DisplayWrapper = Raw;
|
|
|
|
/// Wrapper for the last item of a list containing more than two items.
|
|
|
|
type LastOfMany: DisplayWrapper = Raw;
|
|
|
|
|
|
|
|
/// Format a slice using the provided wrappers.
|
|
|
|
///
|
|
|
|
/// If a [`Formatter`] is not available,
|
|
|
|
/// [`ListDisplayWrapper::wrap`] may be used to produce a
|
|
|
|
/// [`Display`]-able object instead.
|
|
|
|
fn fmt<T: Display>(list: &[T], f: &mut Formatter) -> Result {
|
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
2022-08-17 10:16:04 -04:00
|
|
|
let lasti = list.len().saturating_sub(1);
|
2022-06-10 16:28:15 -04:00
|
|
|
|
|
|
|
// 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 {
|
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
2022-08-17 10:16:04 -04:00
|
|
|
(i, x) => Self::fmt_nth(lasti, i, x, f)?,
|
|
|
|
};
|
|
|
|
}
|
2022-06-10 16:28:15 -04:00
|
|
|
|
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
2022-08-17 10:16:04 -04:00
|
|
|
Ok(())
|
|
|
|
}
|
2022-06-10 16:28:15 -04:00
|
|
|
|
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
2022-08-17 10:16:04 -04:00
|
|
|
/// 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)?;
|
|
|
|
}
|
2022-06-10 16:28:15 -04:00
|
|
|
|
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
2022-08-17 10:16:04 -04:00
|
|
|
(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)?;
|
2022-06-10 16:28:15 -04:00
|
|
|
}
|
|
|
|
}
|
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
2022-08-17 10:16:04 -04:00
|
|
|
|
|
|
|
(_, x) => {
|
|
|
|
Self::Middle::fmt(x, f)?;
|
|
|
|
}
|
2022-06-10 16:28:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Associate data with a [`ListDisplayWrapper`] for rendering using
|
|
|
|
/// [`Display`].
|
|
|
|
///
|
|
|
|
/// This has the effect of creating an arbitrary [`Display`]
|
|
|
|
/// implementation for the wrapped slice,
|
|
|
|
/// which will work well with [`format!`] and anything else that
|
|
|
|
/// does not have access to an explicit [`Formatter`].
|
|
|
|
fn wrap<T: Display>(list: &[T]) -> ListWrap<Self, T> {
|
|
|
|
ListWrap {
|
|
|
|
list,
|
|
|
|
_phantom: Default::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Format each item of a slice using a [`DisplayWrapper`] formatter,
|
|
|
|
/// outputting an English list with a serial comma and conjunctive term.
|
|
|
|
///
|
|
|
|
/// No formatting is done to a single item,
|
|
|
|
/// and the serial comma is omitted for only two items.
|
|
|
|
/// It is assumed that the items are not complete sentences,
|
|
|
|
/// and nested lists are not expected
|
|
|
|
/// (in such a case you'd want to replace the inner list with
|
|
|
|
/// semicolons and a more robust abstraction is needed).
|
|
|
|
///
|
|
|
|
/// The use of the serial comma (also known as the Oxford comma) is the
|
|
|
|
/// preference of the author.
|
|
|
|
///
|
|
|
|
/// For example:
|
|
|
|
/// If we have a slice `[1, 2, 3]`,
|
|
|
|
/// this will output "1, 2, and 3".
|
|
|
|
/// If we have a slice `[1, 2]`,
|
|
|
|
/// it will omit the serial comma and output "1 and 2",
|
|
|
|
/// since no person would write "1, and 2" unless they wished to
|
|
|
|
/// place particularly dramatic emphasis on the first item.
|
|
|
|
///
|
|
|
|
/// To output a qualifier before the list,
|
|
|
|
/// see [`QualConjList`].
|
|
|
|
pub struct ConjList<const CONJ: &'static str, W: DisplayWrapper>(
|
|
|
|
PhantomData<W>,
|
|
|
|
);
|
|
|
|
|
|
|
|
impl<const CONJ: &'static str, W: DisplayWrapper> ListDisplayWrapper
|
|
|
|
for ConjList<CONJ, W>
|
|
|
|
{
|
|
|
|
type Single = W;
|
|
|
|
type First = W;
|
|
|
|
type Middle = Prefix<", ", W>;
|
|
|
|
type LastOfPair = Sp<Prefix<CONJ, Sp<W>>>;
|
|
|
|
// Comma after the penultimate term (serial/Oxford comma).
|
|
|
|
type LastOfMany = Prefix<", ", Prefix<CONJ, Sp<W>>>;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A list of values with a serial comma and the term "and" as a
|
|
|
|
/// conjunction between the penultimate and final items.
|
|
|
|
pub type AndConjList<W> = ConjList<"and", W>;
|
|
|
|
/// A list of values with a serial comma and the term "or" as a
|
|
|
|
/// conjunction between the penultimate and final items.
|
|
|
|
///
|
|
|
|
/// Terminology note:
|
|
|
|
/// English refers to the term "or" here as a conjunction between words,
|
|
|
|
/// which differs from "or" in logic as a disjunction.
|
|
|
|
pub type OrConjList<W> = ConjList<"or", W>;
|
|
|
|
|
|
|
|
/// Format each item of a slice using a [`DisplayWrapper`] formatter,
|
|
|
|
/// outputting an English list with a serial comma and conjunctive term,
|
|
|
|
/// qualified by a term that is singular or plural depending on the length
|
|
|
|
/// of the list.
|
|
|
|
///
|
|
|
|
/// See [`ConjList`] for more information;
|
|
|
|
/// this operates identically but with the addition of the qualifier.
|
|
|
|
pub struct QualConjList<
|
|
|
|
const QUAL_ONE: &'static str,
|
|
|
|
const QUAL_MANY: &'static str,
|
|
|
|
const CONJ: &'static str,
|
|
|
|
W: DisplayWrapper,
|
|
|
|
>(PhantomData<W>);
|
|
|
|
|
|
|
|
impl<
|
|
|
|
const QUAL_ONE: &'static str,
|
|
|
|
const QUAL_MANY: &'static str,
|
|
|
|
const CONJ: &'static str,
|
|
|
|
W: DisplayWrapper,
|
|
|
|
> ListDisplayWrapper for QualConjList<QUAL_ONE, QUAL_MANY, CONJ, W>
|
|
|
|
{
|
|
|
|
type Single =
|
|
|
|
Prefix<QUAL_ONE, Sp<<ConjList<CONJ, W> as ListDisplayWrapper>::Single>>;
|
|
|
|
type First =
|
|
|
|
Prefix<QUAL_MANY, Sp<<ConjList<CONJ, W> as ListDisplayWrapper>::First>>;
|
|
|
|
type Middle = <ConjList<CONJ, W> as ListDisplayWrapper>::Middle;
|
|
|
|
type LastOfPair = <ConjList<CONJ, W> as ListDisplayWrapper>::LastOfPair;
|
|
|
|
type LastOfMany = <ConjList<CONJ, W> as ListDisplayWrapper>::LastOfMany;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A list of values with a serial comma and the term "and" as a
|
|
|
|
/// conjunction between the penultimate and final items.
|
|
|
|
/// prefixed with a singular or plural qualifier term depending on the
|
|
|
|
/// length of the list.
|
|
|
|
pub type AndQualConjList<
|
|
|
|
const QUAL_ONE: &'static str,
|
|
|
|
const QUAL_MANY: &'static str,
|
|
|
|
W,
|
|
|
|
> = QualConjList<QUAL_ONE, QUAL_MANY, "and", W>;
|
|
|
|
|
|
|
|
/// A list of values with a serial comma and the term "or" as a
|
|
|
|
/// conjunction between the penultimate and final items.
|
|
|
|
/// prefixed with a singular or plural qualifier term depending on the
|
|
|
|
/// length of the list.
|
|
|
|
///
|
|
|
|
/// Terminology note:
|
|
|
|
/// English refers to the term "or" here as a conjunction between words,
|
|
|
|
/// which differs from "or" in logic as a disjunction.
|
|
|
|
pub type OrQualConjList<
|
|
|
|
const QUAL_ONE: &'static str,
|
|
|
|
const QUAL_MANY: &'static str,
|
|
|
|
W,
|
|
|
|
> = QualConjList<QUAL_ONE, QUAL_MANY, "or", W>;
|
|
|
|
|
|
|
|
/// List wrapper with associated data.
|
|
|
|
///
|
|
|
|
/// This has the effect of creating an arbitrary [`Display`] implementation
|
|
|
|
/// for the wrapped list,
|
|
|
|
/// which will work well with [`format!`] and anything else that does
|
|
|
|
/// not have access to an explicit [`Formatter`].
|
|
|
|
pub struct ListWrap<'a, W: ListDisplayWrapper + ?Sized, T: Display> {
|
|
|
|
list: &'a [T],
|
|
|
|
_phantom: PhantomData<W>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, W: ListDisplayWrapper + ?Sized, T: Display> Display
|
|
|
|
for ListWrap<'a, W, T>
|
|
|
|
{
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
|
|
|
W::fmt(self.list, f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-17 10:01:47 -04:00
|
|
|
/// Wrap a `fmt`-like function to be used as [`Display::fmt`] for this
|
|
|
|
/// object.
|
|
|
|
///
|
|
|
|
/// This works around the problem of having a function expecting a
|
|
|
|
/// [`Formatter`],
|
|
|
|
/// but not having a [`Formatter`] to call it with.
|
|
|
|
/// It also allows for arbitrary (compatible) functions to be used as
|
|
|
|
/// [`Display`].
|
|
|
|
pub struct DisplayFn<F: Fn(&mut Formatter) -> Result>(pub F);
|
|
|
|
|
|
|
|
impl<F: Fn(&mut Formatter) -> Result> Display for DisplayFn<F> {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
|
|
|
(self.0)(f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-10 16:28:15 -04:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn prefix() {
|
|
|
|
assert_eq!(Prefix::<"@!", Raw>::wrap("foo").to_string(), "@!foo",);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
Prefix::<"1", Prefix::<"2", Raw>>::wrap("bar").to_string(),
|
|
|
|
"12bar",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn suffix() {
|
|
|
|
assert_eq!(Suffix::<"!@", Raw>::wrap("foo").to_string(), "foo!@",);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
Suffix::<"1", Suffix::<"2", Raw>>::wrap("bar").to_string(),
|
|
|
|
"bar21",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn delimit() {
|
|
|
|
assert_eq!(Delim::<"[", "]", Raw>::wrap("foo").to_string(), "[foo]",);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
Delim::<"(", ")", Delim::<"|", "|", Raw>>::wrap("bar").to_string(),
|
|
|
|
"(|bar|)",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Certain types are trivially verifiable by their definition,
|
|
|
|
// and so have no tests.
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn conj_list_single() {
|
|
|
|
assert_eq!(
|
|
|
|
ConjList::<"unused", Raw>::wrap(&["single"]).to_string(),
|
|
|
|
"single",
|
|
|
|
);
|
|
|
|
|
|
|
|
// Ensure that we're actually mapping.
|
|
|
|
assert_eq!(
|
|
|
|
ConjList::<"unused", Delim<"(", ")", Raw>>::wrap(&["single"])
|
|
|
|
.to_string(),
|
|
|
|
"(single)",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn conj_list_double() {
|
|
|
|
assert_eq!(
|
|
|
|
ConjList::<"and", Raw>::wrap(&["first", "second"]).to_string(),
|
|
|
|
"first and second",
|
|
|
|
);
|
|
|
|
|
|
|
|
// Ensure that we're actually mapping.
|
|
|
|
assert_eq!(
|
|
|
|
ConjList::<"or", Delim<"(", ")", Raw>>::wrap(&["first", "second"])
|
|
|
|
.to_string(),
|
|
|
|
"(first) or (second)",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn conj_list_many() {
|
|
|
|
assert_eq!(
|
|
|
|
ConjList::<"and", Raw>::wrap(&["first", "second", "third"])
|
|
|
|
.to_string(),
|
|
|
|
"first, second, and third",
|
|
|
|
);
|
|
|
|
|
|
|
|
// Ensure that we're actually mapping.
|
|
|
|
assert_eq!(
|
|
|
|
ConjList::<"or", Delim<"[", "]", Raw>>::wrap(&[
|
|
|
|
"first", "second", "third"
|
|
|
|
])
|
|
|
|
.to_string(),
|
|
|
|
"[first], [second], or [third]",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn qualified_conj_list_single() {
|
|
|
|
assert_eq!(
|
|
|
|
QualConjList::<"thing", "things", "unused", Raw>::wrap(&["a"])
|
|
|
|
.to_string(),
|
|
|
|
"thing a",
|
|
|
|
);
|
|
|
|
|
|
|
|
// Ensure that we're actually mapping.
|
|
|
|
assert_eq!(
|
|
|
|
QualConjList::<"thing", "things", "unused", Delim<"(", ")", Raw>>::wrap(&["a"])
|
|
|
|
.to_string(),
|
|
|
|
"thing (a)",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn qualified_conj_list_double() {
|
|
|
|
assert_eq!(
|
|
|
|
QualConjList::<"thing", "things", "and", Raw>::wrap(&["a", "b"])
|
|
|
|
.to_string(),
|
|
|
|
"things a and b",
|
|
|
|
);
|
|
|
|
|
|
|
|
// Ensure that we're actually mapping.
|
|
|
|
assert_eq!(
|
|
|
|
QualConjList::<"thing", "things", "or", Delim<"(", ")", Raw>>::wrap(&["a", "b"])
|
|
|
|
.to_string(),
|
|
|
|
"things (a) or (b)",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn qualified_conj_list_many() {
|
|
|
|
assert_eq!(
|
|
|
|
QualConjList::<"thing", "things", "and", Raw>::wrap(&[
|
|
|
|
"a", "b", "c"
|
|
|
|
])
|
|
|
|
.to_string(),
|
|
|
|
"things a, b, and c",
|
|
|
|
);
|
|
|
|
|
|
|
|
// Ensure that we're actually mapping.
|
|
|
|
assert_eq!(
|
|
|
|
QualConjList::<"thing", "things", "or", Delim<"(", ")", Raw>>::wrap(&["a", "b", "c"])
|
|
|
|
.to_string(),
|
|
|
|
"things (a), (b), or (c)",
|
|
|
|
);
|
|
|
|
}
|
2022-08-17 10:01:47 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn display_fn() {
|
|
|
|
assert_eq!(
|
|
|
|
DisplayFn(|f| write!(f, "test fmt")).to_string(),
|
|
|
|
"test fmt",
|
|
|
|
);
|
|
|
|
}
|
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
2022-08-17 10:16:04 -04:00
|
|
|
|
|
|
|
// `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",
|
|
|
|
);
|
|
|
|
}
|
2022-06-10 16:28:15 -04:00
|
|
|
}
|