partway towards commands
I got very confused and started mucking about with "spawn" when in fact all I needed was the "inline" extension type in orcx that allows the interpreter to expose custom constants.
This commit is contained in:
@@ -14,21 +14,27 @@ use regex::Regex;
|
||||
|
||||
use crate::{api, match_mapping};
|
||||
|
||||
/// A unit of formattable text where the formatter must make a single choice
|
||||
/// Converting from various types via [Into::into] keeps strings intact, but
|
||||
/// [str::parse] resolves escape sequences
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
#[must_use]
|
||||
pub struct FmtUnit {
|
||||
/// Sub-units
|
||||
pub subs: Vec<FmtUnit>,
|
||||
/// Parsed text templates for how to render this text
|
||||
pub variants: Rc<Variants>,
|
||||
}
|
||||
impl FmtUnit {
|
||||
pub fn new(variants: Rc<Variants>, subs: impl IntoIterator<Item = FmtUnit>) -> Self {
|
||||
Self { subs: subs.into_iter().collect(), variants }
|
||||
}
|
||||
/// Deserialize from message
|
||||
pub fn from_api(api: &api::FormattingUnit) -> Self {
|
||||
Self {
|
||||
subs: api.subs.iter().map(Self::from_api).collect(),
|
||||
variants: Rc::new(Variants(
|
||||
(api.variants.iter().map(|var| Variant {
|
||||
(api.variants.iter().map(|var| FmtVariant {
|
||||
bounded: var.bounded,
|
||||
elements: var.elements.iter().map(FmtElement::from_api).collect(),
|
||||
}))
|
||||
@@ -36,6 +42,8 @@ impl FmtUnit {
|
||||
)),
|
||||
}
|
||||
}
|
||||
/// Serialize into message. String interner IDs used in the structure must
|
||||
/// remain valid.
|
||||
pub fn to_api(&self) -> api::FormattingUnit {
|
||||
api::FormattingUnit {
|
||||
subs: self.subs.iter().map(Self::to_api).collect(),
|
||||
@@ -46,11 +54,13 @@ impl FmtUnit {
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
/// Shorthand for a variable-length list that can be formatted in exactly one
|
||||
/// way
|
||||
pub fn sequence(
|
||||
head: &str,
|
||||
delim: &str,
|
||||
tail: &str,
|
||||
seq_bnd: Option<bool>,
|
||||
seq_bnd: bool,
|
||||
seq: impl IntoIterator<Item = FmtUnit>,
|
||||
) -> Self {
|
||||
let items = seq.into_iter().collect_vec();
|
||||
@@ -69,18 +79,37 @@ impl FromStr for FmtUnit {
|
||||
}
|
||||
}
|
||||
|
||||
/// A single element of a format string. Composes into [FmtVariant]
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum FmtElement {
|
||||
Sub { slot: u32, bounded: Option<bool> },
|
||||
/// a reference to an interpolable subunit in the enclosing [FmtUnit]
|
||||
Sub {
|
||||
/// Index into [FmtUnit::subs]
|
||||
slot: u32,
|
||||
/// Whether the subunit can use an unbounded (`Some(false)`) [FmtVariant],
|
||||
/// it is restricted to bounded (`Some(true)`) [FmtVariant], or it should
|
||||
/// inherit this information from the enclosing unit, meaning that the slot
|
||||
/// is at the very end of the format string
|
||||
bounded: Option<bool>,
|
||||
},
|
||||
/// a string snippet
|
||||
String(Rc<String>),
|
||||
/// an indented block
|
||||
Indent(Vec<FmtElement>),
|
||||
}
|
||||
impl FmtElement {
|
||||
/// Create a plain string snippet
|
||||
pub fn str(s: &'_ str) -> Self { Self::String(Rc::new(s.to_string())) }
|
||||
/// Create a slot for a subunit
|
||||
pub fn sub(slot: u32, bounded: Option<bool>) -> Self { Self::Sub { slot, bounded } }
|
||||
/// Create a slot for a subunit's bounded representation
|
||||
pub fn bounded(i: u32) -> Self { Self::sub(i, Some(true)) }
|
||||
/// Create a slot for any representation of a subunit
|
||||
pub fn unbounded(i: u32) -> Self { Self::sub(i, Some(false)) }
|
||||
/// Create an end slot bounded by the enclosing unit if that is bounded
|
||||
pub fn last(i: u32) -> Self { Self::sub(i, None) }
|
||||
/// Create a sequence of `len` unbounded slots capped by a slot of the
|
||||
/// specified boundedness
|
||||
pub fn sequence(len: usize, bounded: Option<bool>) -> Vec<Self> {
|
||||
match len.try_into().unwrap() {
|
||||
0u32 => vec![],
|
||||
@@ -88,6 +117,7 @@ impl FmtElement {
|
||||
n => (0..n - 1).map(FmtElement::unbounded).chain([FmtElement::sub(n - 1, bounded)]).collect(),
|
||||
}
|
||||
}
|
||||
/// Decode from a message
|
||||
pub fn from_api(api: &api::FormattingElement) -> Self {
|
||||
match_mapping!(api, api::FormattingElement => FmtElement {
|
||||
Indent(v => v.iter().map(FmtElement::from_api).collect()),
|
||||
@@ -95,6 +125,7 @@ impl FmtElement {
|
||||
Sub{ *slot, *bounded },
|
||||
})
|
||||
}
|
||||
/// Encode to message
|
||||
pub fn to_api(&self) -> api::FormattingElement {
|
||||
match_mapping!(self, FmtElement => api::FormattingElement {
|
||||
Indent(v => v.iter().map(FmtElement::to_api).collect()),
|
||||
@@ -104,39 +135,16 @@ impl FmtElement {
|
||||
}
|
||||
}
|
||||
|
||||
/// A particular way in which a value may be formatted in text.
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Variant {
|
||||
pub struct FmtVariant {
|
||||
/// Whether this representation has an intrinsic end marker or it needs the
|
||||
/// parent to provide one
|
||||
pub bounded: bool,
|
||||
/// Template string syntax elements
|
||||
pub elements: Vec<FmtElement>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variants_parse_test() {
|
||||
let vars = Rc::new(Variants::default().bounded("({{{0}}})"));
|
||||
let expected_vars = Rc::new(Variants(vec![Variant {
|
||||
bounded: true,
|
||||
elements: vec![
|
||||
FmtElement::String(Rc::new("({".to_string())),
|
||||
FmtElement::Sub { bounded: Some(false), slot: 0 },
|
||||
FmtElement::String(Rc::new("})".to_string())),
|
||||
],
|
||||
}]));
|
||||
assert_eq!(vars.as_ref(), expected_vars.as_ref());
|
||||
let unit = vars.units(["1".into()]);
|
||||
assert_eq!(unit, FmtUnit {
|
||||
subs: vec![FmtUnit {
|
||||
subs: vec![],
|
||||
variants: Rc::new(Variants(vec![Variant {
|
||||
bounded: true,
|
||||
elements: vec![FmtElement::String(Rc::new("1".to_string()))]
|
||||
}]))
|
||||
}],
|
||||
variants: expected_vars
|
||||
});
|
||||
let str = take_first(&unit, true);
|
||||
assert_eq!(str, "({1})");
|
||||
}
|
||||
|
||||
/// Represents a collection of formatting strings for the same set of parameters
|
||||
/// from which the formatter can choose within their associated constraints.
|
||||
///
|
||||
@@ -145,7 +153,7 @@ fn variants_parse_test() {
|
||||
/// - {0l} causes the current end restriction to be applied to the parameter.
|
||||
/// This is to be used if the parameter is at the very end of the variant.
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, Default)]
|
||||
pub struct Variants(pub Vec<Variant>);
|
||||
pub struct Variants(pub Vec<FmtVariant>);
|
||||
impl Variants {
|
||||
fn parse_phs(s: &'_ str) -> Vec<FmtElement> {
|
||||
let re = Regex::new(r"(?<tpl>\{\d+?[bl]?\})|(\{\{)|(\}\})").unwrap();
|
||||
@@ -216,7 +224,7 @@ impl Variants {
|
||||
}
|
||||
}
|
||||
fn add(&mut self, bounded: bool, s: &'_ str) {
|
||||
self.0.push(Variant { bounded, elements: Self::parse(s) })
|
||||
self.0.push(FmtVariant { bounded, elements: Self::parse(s) })
|
||||
}
|
||||
/// This option is available in all positions.
|
||||
/// See [Variants] for a description of the format strings
|
||||
@@ -231,35 +239,42 @@ impl Variants {
|
||||
self.add(false, s);
|
||||
self
|
||||
}
|
||||
/// Produces formatting options for `len` parameters separated by `delim`.
|
||||
/// `seq_bnd` indicates whether `delim` and `tail` can unambiguously indicate
|
||||
/// the end of a subsequence. For consistency, the stricter of the two is
|
||||
/// expected to be used
|
||||
pub fn sequence(
|
||||
mut self,
|
||||
len: usize,
|
||||
head: &str,
|
||||
delim: &str,
|
||||
tail: &str,
|
||||
seq_bnd: Option<bool>,
|
||||
seq_bnd: bool,
|
||||
) -> Self {
|
||||
let seq = chain!(
|
||||
[FmtElement::str(head)],
|
||||
Itertools::intersperse(
|
||||
FmtElement::sequence(len, seq_bnd).into_iter(),
|
||||
FmtElement::sequence(len, Some(seq_bnd)).into_iter(),
|
||||
FmtElement::str(delim),
|
||||
),
|
||||
[FmtElement::str(tail)],
|
||||
);
|
||||
self.0.push(Variant { bounded: true, elements: seq.collect_vec() });
|
||||
self.0.push(FmtVariant { bounded: true, elements: seq.collect_vec() });
|
||||
self
|
||||
}
|
||||
/// Pair the slots with subunits to produce a [FmtUnit]
|
||||
pub fn units_own(self, subs: impl IntoIterator<Item = FmtUnit>) -> FmtUnit {
|
||||
FmtUnit::new(Rc::new(self), subs)
|
||||
}
|
||||
/// Pair the slots with subunits to produce a [FmtUnit] by reference. These
|
||||
/// objects should preferably be thread-locally cached whenever possible.
|
||||
pub fn units(self: &Rc<Self>, subs: impl IntoIterator<Item = FmtUnit>) -> FmtUnit {
|
||||
FmtUnit::new(self.clone(), subs)
|
||||
}
|
||||
}
|
||||
impl From<Rc<String>> for Variants {
|
||||
fn from(value: Rc<String>) -> Self {
|
||||
Self(vec![Variant { elements: vec![FmtElement::String(value)], bounded: true }])
|
||||
Self(vec![FmtVariant { elements: vec![FmtElement::String(value)], bounded: true }])
|
||||
}
|
||||
}
|
||||
impl From<String> for Variants {
|
||||
@@ -304,23 +319,18 @@ pub async fn take_first_fmt(v: &(impl Format + ?Sized)) -> String {
|
||||
take_first(&v.print(&FmtCtxImpl { _foo: PhantomData }).await, false)
|
||||
}
|
||||
|
||||
/// [Default] this if you need one
|
||||
#[derive(Default)]
|
||||
pub struct FmtCtxImpl<'a> {
|
||||
_foo: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
pub trait FmtCtx {
|
||||
// fn print_as(&self, p: &(impl Format + ?Sized)) -> impl Future<Output =
|
||||
// String> where Self: Sized {
|
||||
// async {
|
||||
// // for now, always take the first option which is probably the one-line
|
||||
// form let variants = p.print(self).await;
|
||||
// take_first(&variants, true)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
/// Additional settings to the formatter. Implemented by [FmtCtxImpl]. Currently
|
||||
/// not in use
|
||||
pub trait FmtCtx {}
|
||||
impl FmtCtx for FmtCtxImpl<'_> {}
|
||||
|
||||
/// A value that can be formatted into a string with multiple possible forms
|
||||
pub trait Format {
|
||||
#[must_use]
|
||||
fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> impl Future<Output = FmtUnit> + 'a;
|
||||
@@ -337,3 +347,37 @@ pub async fn fmt_v<F: Format + ?Sized>(
|
||||
) -> impl Iterator<Item = String> {
|
||||
join_all(v.into_iter().map(|f| async move { take_first_fmt(f.borrow()).await })).await.into_iter()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::format::{FmtElement, FmtUnit, FmtVariant, Variants, take_first};
|
||||
|
||||
#[test]
|
||||
fn variants_parse_test() {
|
||||
let vars = Rc::new(Variants::default().bounded("({{{0}}})"));
|
||||
let expected_vars = Rc::new(Variants(vec![FmtVariant {
|
||||
bounded: true,
|
||||
elements: vec![
|
||||
FmtElement::String(Rc::new("({".to_string())),
|
||||
FmtElement::Sub { bounded: Some(false), slot: 0 },
|
||||
FmtElement::String(Rc::new("})".to_string())),
|
||||
],
|
||||
}]));
|
||||
assert_eq!(vars.as_ref(), expected_vars.as_ref());
|
||||
let unit = vars.units(["1".into()]);
|
||||
assert_eq!(unit, FmtUnit {
|
||||
subs: vec![FmtUnit {
|
||||
subs: vec![],
|
||||
variants: Rc::new(Variants(vec![FmtVariant {
|
||||
bounded: true,
|
||||
elements: vec![FmtElement::String(Rc::new("1".to_string()))]
|
||||
}]))
|
||||
}],
|
||||
variants: expected_vars
|
||||
});
|
||||
let str = take_first(&unit, true);
|
||||
assert_eq!(str, "({1})");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user