From a3a16a73ba43e79c2202087d85f19ee1f75a9c57 Mon Sep 17 00:00:00 2001 From: T0mstone Date: Mon, 3 Feb 2025 21:19:23 +0100 Subject: [PATCH 01/18] Reduce code duplication --- build.rs | 27 +++++++-------------------- src/lib.rs | 45 ++++++++------------------------------------- src/shared.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 57 deletions(-) create mode 100644 src/shared.rs diff --git a/build.rs b/build.rs index 80286bd..f4f1e89 100644 --- a/build.rs +++ b/build.rs @@ -4,8 +4,13 @@ use std::path::Path; type StrResult = Result; -/// A module of definitions. -struct Module<'a>(Vec<(&'a str, Binding<'a>)>); +include!("src/shared.rs"); + +declare_types!{ + <'a> + str = &'a str, + List = Vec<_> +} impl<'a> Module<'a> { fn new(mut list: Vec<(&'a str, Binding<'a>)>) -> Self { @@ -14,24 +19,6 @@ impl<'a> Module<'a> { } } -/// A definition bound in a module, with metadata. -struct Binding<'a> { - def: Def<'a>, - deprecation: Option<&'a str>, -} - -/// A definition in a module. -enum Def<'a> { - Symbol(Symbol<'a>), - Module(Module<'a>), -} - -/// A symbol, either a leaf or with modifiers. -enum Symbol<'a> { - Single(char), - Multi(Vec<(&'a str, char)>), -} - /// A single line during parsing. #[derive(Debug, Copy, Clone)] enum Line<'a> { diff --git a/src/lib.rs b/src/lib.rs index ae64ee1..0787444 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,9 +2,14 @@ Human-friendly notation for Unicode symbols. */ -/// A module of definitions. -#[derive(Debug, Copy, Clone)] -pub struct Module(&'static [(&'static str, Binding)]); +include!("shared.rs"); + +type StaticSlice = &'static [T]; +declare_types! { + derive(Debug, Copy, Clone), + str = &'static str, + List = StaticSlice<_> +} impl Module { /// Try to get a bound definition in the module. @@ -21,40 +26,6 @@ impl Module { } } -/// A definition bound in a module, with metadata. -#[derive(Debug, Copy, Clone)] -pub struct Binding { - /// The bound definition. - pub def: Def, - /// A deprecation message for the definition, if it is deprecated. - pub deprecation: Option<&'static str>, -} - -impl Binding { - /// Create a new bound definition. - pub const fn new(definition: Def) -> Self { - Self { def: definition, deprecation: None } - } -} - -/// A definition in a module. -#[derive(Debug, Copy, Clone)] -pub enum Def { - /// A symbol, potentially with modifiers. - Symbol(Symbol), - /// A nested module. - Module(Module), -} - -/// A symbol, either a leaf or with modifiers. -#[derive(Debug, Copy, Clone)] -pub enum Symbol { - /// A symbol without modifiers. - Single(char), - /// A symbol with named modifiers. The symbol defaults to its first variant. - Multi(&'static [(&'static str, char)]), -} - /// A module that contains the other top-level modules. pub const ROOT: Module = Module(&[ ("emoji", Binding::new(Def::Module(EMOJI))), diff --git a/src/shared.rs b/src/shared.rs new file mode 100644 index 0000000..f4327c6 --- /dev/null +++ b/src/shared.rs @@ -0,0 +1,45 @@ +macro_rules! declare_types { + ($(<$lt:lifetime>)? + $(derive($($Der:ident),*),)? + str = $s:ty, + List = $L:ident<_> + ) => { + /// A module of definitions. + $(#[derive($($Der),*)])? + pub struct Module<$($lt)?>($L<($s, Binding<$($lt)?>)>); + + /// A definition bound in a module, with metadata. + $(#[derive($($Der),*)])? + pub struct Binding<$($lt)?> { + /// The bound definition. + pub def: Def<$($lt)?>, + /// A deprecation message for the definition, if it is deprecated. + pub deprecation: Option<$s>, + } + + impl<$($lt)?> Binding<$($lt)?> { + /// Create a new bound definition. + pub const fn new(definition: Def<$($lt)?>) -> Self { + Self { def: definition, deprecation: None } + } + } + + /// A definition in a module. + $(#[derive($($Der),*)])? + pub enum Def<$($lt)?> { + /// A symbol, potentially with modifiers. + Symbol(Symbol<$($lt)?>), + /// A nested module. + Module(Module<$($lt)?>), + } + + /// A symbol, either a leaf or with modifiers. + $(#[derive($($Der),*)])? + pub enum Symbol<$($lt)?> { + /// A symbol without modifiers. + Single(char), + /// A symbol with named modifiers. The symbol defaults to its first variant. + Multi($L<($s, char)>), + } + }; +} From 536dc495f7110c70761f6fbe8cee5e9bfb1de10e Mon Sep 17 00:00:00 2001 From: T0mstone Date: Mon, 3 Feb 2025 23:59:19 +0100 Subject: [PATCH 02/18] Add new API --- build.rs | 6 +-- src/lib.rs | 49 ++++++++++++++++++++++ src/shared.rs | 111 ++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 159 insertions(+), 7 deletions(-) diff --git a/build.rs b/build.rs index f4f1e89..635307c 100644 --- a/build.rs +++ b/build.rs @@ -27,7 +27,7 @@ enum Line<'a> { ModuleStart(&'a str), ModuleEnd, Symbol(&'a str, Option), - Variant(&'a str, char), + Variant(ModifierSet<&'a str>, char), } fn main() { @@ -97,7 +97,7 @@ fn tokenize(line: &str) -> StrResult { validate_ident(part)?; } let c = decode_char(tail.ok_or("missing char")?)?; - Line::Variant(rest, c) + Line::Variant(ModifierSet(rest), c) } else { validate_ident(head)?; let c = tail.map(decode_char).transpose()?; @@ -154,7 +154,7 @@ fn parse<'a>( let symbol = if variants.len() > 0 { if let Some(c) = c { - variants.insert(0, ("", c)); + variants.insert(0, (ModifierSet::empty(), c)); } Symbol::Multi(variants) } else { diff --git a/src/lib.rs b/src/lib.rs index 0787444..4d208f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,13 @@ /*! Human-friendly notation for Unicode symbols. + +## Model +A [`Symbol`] is a collection of one or more _variants_. +Each variant is identified by a set of _modifiers_ (see [`ModifierSet`]) +and has a single character as its value. +The modifiers themselves can in principle be any non-empty strings +that don't contain the character `.`, but codex only defines +ones that are entirely made of ASCII alphabetical characters. */ include!("shared.rs"); @@ -26,6 +34,47 @@ impl Module { } } +impl<'a> ModifierSet<&'a str> { + /// Iterate over the list of modifiers with the original lifetime. + pub fn to_iter(self) -> impl Iterator { + self.0.split('.').filter(|s| !s.is_empty()) + } +} + +impl Symbol { + /// Get the symbol's character for a given set of modifiers. + pub fn get(&self, modifs: ModifierSet<&str>) -> Option { + match self { + Self::Single(c) => modifs.is_empty().then_some(*c), + Self::Multi(list) => modifs.best_match_in(list.iter().copied()), + } + } + + /// The characters that are covered by this symbol. + pub fn variants(&self) -> impl Iterator, char)> { + enum Variants { + Single(std::iter::Once), + Multi(std::slice::Iter<'static, (ModifierSet<&'static str>, char)>), + } + let mut iter = match self { + Self::Single(c) => Variants::Single(std::iter::once(*c)), + Self::Multi(sl) => Variants::Multi(sl.iter()), + }; + std::iter::from_fn(move || match &mut iter { + Variants::Single(iter) => Some((ModifierSet::empty(), iter.next()?)), + Variants::Multi(iter) => iter.next().copied(), + }) + } + + /// Possible modifiers for this symbol. + pub fn modifiers(&self) -> impl Iterator + '_ { + self.variants() + .flat_map(|(m, _)| m.to_iter()) + .collect::>() + .into_iter() + } +} + /// A module that contains the other top-level modules. pub const ROOT: Module = Module(&[ ("emoji", Binding::new(Def::Module(EMOJI))), diff --git a/src/shared.rs b/src/shared.rs index f4327c6..9407aab 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -1,12 +1,14 @@ +use std::ops::{AddAssign, Deref}; + macro_rules! declare_types { ($(<$lt:lifetime>)? $(derive($($Der:ident),*),)? str = $s:ty, - List = $L:ident<_> + List = $List:ident<_> ) => { /// A module of definitions. $(#[derive($($Der),*)])? - pub struct Module<$($lt)?>($L<($s, Binding<$($lt)?>)>); + pub struct Module<$($lt)?>($List<($s, Binding<$($lt)?>)>); /// A definition bound in a module, with metadata. $(#[derive($($Der),*)])? @@ -38,8 +40,109 @@ macro_rules! declare_types { pub enum Symbol<$($lt)?> { /// A symbol without modifiers. Single(char), - /// A symbol with named modifiers. The symbol defaults to its first variant. - Multi($L<($s, char)>), + /// A symbol with named modifiers. + /// The symbol defaults to its first variant. + Multi($List<(ModifierSet<$s>, char)>), } }; } + +/// A set of modifiers. +#[derive(Debug, Copy, Clone)] +pub struct ModifierSet(S); + +impl> ModifierSet { + /// Convert the underlying string to a slice. + pub fn as_deref(&self) -> ModifierSet<&str> { + ModifierSet(&self.0) + } + + /// Construct a modifier set from a string, + /// where modifiers are separated by the character `.`. + /// + /// It is not unsafe to use this function wrongly, but it can produce + /// unexpected results down the line. Correct usage should ensure that + /// `s` does not contain any empty modifiers (i.e. the sequence `..`) + /// and that no modifier occurs twice. + pub fn new_unchecked(s: S) -> Self { + Self(s) + } + + /// Construct an empty modifier set. + pub fn empty() -> Self + where + S: Default, + { + Self(S::default()) + } + + /// Whether `self` is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Add a modifier to the set, without checking that it is a valid modifier. + /// + /// It is not unsafe to use this method wrongly, but that can produce + /// unexpected results down the line. Correct usage should ensure that + /// `modifier` is not empty and doesn't contain the character `.`. + pub fn add_unchecked(&mut self, m: &str) + where + S: for<'a> AddAssign<&'a str>, + { + if !self.0.is_empty() { + self.0 += "."; + } + self.0 += m; + } + + /// Iterate over the list of modifiers in an arbitrary order. + pub fn iter(&self) -> impl Iterator { + self.0.split('.').filter(|s| !s.is_empty()) + } + + /// Whether the set contains the modifier `m`. + pub fn contains(&self, m: &str) -> bool { + self.iter().any(|lhs| lhs == m) + } + + /// Whether all modifiers in `self` are also present in `other`. + pub fn is_subset(&self, other: ModifierSet<&str>) -> bool { + self.iter().all(|m| other.contains(m)) + } + + /// Find the best match from the list. + /// + /// To be considered a match, the modifier set must be a superset of + /// (or equal to) `self`. Among different matches, the best one is selected + /// by the following two criteria (in order): + /// 1. Number of modifiers in common with `self` (more is better). + /// 2. Total number of modifiers (fewer is better). + pub fn best_match_in<'a, T>( + &self, + variants: impl Iterator, T)>, + ) -> Option { + let mut best = None; + let mut best_score = None; + + // Find the best table entry with this name. + for candidate in variants.filter(|(set, _)| self.is_subset(*set)) { + let mut matching = 0; + let mut total = 0; + for modifier in candidate.0.iter() { + if self.contains(modifier) { + matching += 1; + } + total += 1; + } + + let score = (matching, core::cmp::Reverse(total)); + if best_score.map_or(true, |b| score > b) { + best = Some(candidate.1); + best_score = Some(score); + } + } + + best + } +} From 4895855406d64461ffbc584edadce06c46028ed2 Mon Sep 17 00:00:00 2001 From: T0mstone Date: Tue, 25 Feb 2025 20:54:55 +0100 Subject: [PATCH 03/18] Address comments --- build.rs | 2 +- src/lib.rs | 29 ++++++++++------------------- src/shared.rs | 36 ++++++++++++++++++++++-------------- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/build.rs b/build.rs index 635307c..2f474d4 100644 --- a/build.rs +++ b/build.rs @@ -154,7 +154,7 @@ fn parse<'a>( let symbol = if variants.len() > 0 { if let Some(c) = c { - variants.insert(0, (ModifierSet::empty(), c)); + variants.insert(0, (ModifierSet::default(), c)); } Symbol::Multi(variants) } else { diff --git a/src/lib.rs b/src/lib.rs index 4d208f3..64a3ae4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,12 @@ -/*! -Human-friendly notation for Unicode symbols. - -## Model -A [`Symbol`] is a collection of one or more _variants_. -Each variant is identified by a set of _modifiers_ (see [`ModifierSet`]) -and has a single character as its value. -The modifiers themselves can in principle be any non-empty strings -that don't contain the character `.`, but codex only defines -ones that are entirely made of ASCII alphabetical characters. -*/ +//! Human-friendly notation for Unicode symbols. +//! +//! ## Model +//! A [`Symbol`] is a collection of one or more _variants_. +//! Each variant is identified by a set of [_modifiers_](ModifierSet) +//! and has a single character as its value. +//! The modifiers themselves can in principle be any non-empty strings +//! that don't contain the character `.`, but codex only defines +//! ones that are entirely made of ASCII alphabetical characters. include!("shared.rs"); @@ -34,13 +32,6 @@ impl Module { } } -impl<'a> ModifierSet<&'a str> { - /// Iterate over the list of modifiers with the original lifetime. - pub fn to_iter(self) -> impl Iterator { - self.0.split('.').filter(|s| !s.is_empty()) - } -} - impl Symbol { /// Get the symbol's character for a given set of modifiers. pub fn get(&self, modifs: ModifierSet<&str>) -> Option { @@ -61,7 +52,7 @@ impl Symbol { Self::Multi(sl) => Variants::Multi(sl.iter()), }; std::iter::from_fn(move || match &mut iter { - Variants::Single(iter) => Some((ModifierSet::empty(), iter.next()?)), + Variants::Single(iter) => Some((ModifierSet::default(), iter.next()?)), Variants::Multi(iter) => iter.next().copied(), }) } diff --git a/src/shared.rs b/src/shared.rs index 9407aab..f093dce 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -1,5 +1,3 @@ -use std::ops::{AddAssign, Deref}; - macro_rules! declare_types { ($(<$lt:lifetime>)? $(derive($($Der:ident),*),)? @@ -51,7 +49,18 @@ macro_rules! declare_types { #[derive(Debug, Copy, Clone)] pub struct ModifierSet(S); -impl> ModifierSet { +impl Default for ModifierSet { + /// Construct the default modifier set. + /// + /// This is typically the empty set, + /// though the remark from [`Self::new_unchecked`] applies + /// since `S::default()` could technically be anything. + fn default() -> Self { + Self(S::default()) + } +} + +impl> ModifierSet { /// Convert the underlying string to a slice. pub fn as_deref(&self) -> ModifierSet<&str> { ModifierSet(&self.0) @@ -68,14 +77,6 @@ impl> ModifierSet { Self(s) } - /// Construct an empty modifier set. - pub fn empty() -> Self - where - S: Default, - { - Self(S::default()) - } - /// Whether `self` is empty. pub fn is_empty(&self) -> bool { self.0.is_empty() @@ -88,7 +89,7 @@ impl> ModifierSet { /// `modifier` is not empty and doesn't contain the character `.`. pub fn add_unchecked(&mut self, m: &str) where - S: for<'a> AddAssign<&'a str>, + S: for<'a> std::ops::AddAssign<&'a str>, { if !self.0.is_empty() { self.0 += "."; @@ -136,8 +137,8 @@ impl> ModifierSet { total += 1; } - let score = (matching, core::cmp::Reverse(total)); - if best_score.map_or(true, |b| score > b) { + let score = (matching, std::cmp::Reverse(total)); + if best_score.is_none_or(|b| score > b) { best = Some(candidate.1); best_score = Some(score); } @@ -146,3 +147,10 @@ impl> ModifierSet { best } } + +impl<'a> ModifierSet<&'a str> { + /// Iterate over the list of modifiers with the original lifetime. + pub fn to_iter(self) -> impl Iterator { + self.0.split('.').filter(|s| !s.is_empty()) + } +} From 1fdbb1e3e21bfdcbe6742712c7cae86907755d72 Mon Sep 17 00:00:00 2001 From: T0mstone Date: Tue, 25 Feb 2025 21:18:56 +0100 Subject: [PATCH 04/18] Use a more idiomatic module structure --- build.rs | 8 +++++--- src/lib.rs | 6 ++++-- src/shared.rs | 6 +++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/build.rs b/build.rs index 2f474d4..c7fefda 100644 --- a/build.rs +++ b/build.rs @@ -1,12 +1,14 @@ +use self::shared::ModifierSet; use std::fmt::Write; use std::iter::Peekable; use std::path::Path; type StrResult = Result; -include!("src/shared.rs"); +#[path = "src/shared.rs"] +mod shared; -declare_types!{ +self::shared::declare_types! { <'a> str = &'a str, List = Vec<_> @@ -97,7 +99,7 @@ fn tokenize(line: &str) -> StrResult { validate_ident(part)?; } let c = decode_char(tail.ok_or("missing char")?)?; - Line::Variant(ModifierSet(rest), c) + Line::Variant(ModifierSet::new_unchecked(rest), c) } else { validate_ident(head)?; let c = tail.map(decode_char).transpose()?; diff --git a/src/lib.rs b/src/lib.rs index 64a3ae4..dadafe6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,10 +8,12 @@ //! that don't contain the character `.`, but codex only defines //! ones that are entirely made of ASCII alphabetical characters. -include!("shared.rs"); +pub use self::shared::ModifierSet; + +mod shared; type StaticSlice = &'static [T]; -declare_types! { +self::shared::declare_types! { derive(Debug, Copy, Clone), str = &'static str, List = StaticSlice<_> diff --git a/src/shared.rs b/src/shared.rs index f093dce..2e1de9a 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -45,9 +45,13 @@ macro_rules! declare_types { }; } +pub(crate) use declare_types; + /// A set of modifiers. #[derive(Debug, Copy, Clone)] -pub struct ModifierSet(S); +// note: the visibility needs to be `pub(crate)`, +// since build.rs outputs `ModifierSet(...)` +pub struct ModifierSet(pub(crate) S); impl Default for ModifierSet { /// Construct the default modifier set. From a5cbb1934b97d28b08ed07156122d7d58bd183ca Mon Sep 17 00:00:00 2001 From: T0mstone Date: Tue, 25 Feb 2025 21:26:30 +0100 Subject: [PATCH 05/18] Use IntoIterator instead of inherent methods --- src/lib.rs | 2 +- src/shared.rs | 27 +++++++++++++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dadafe6..0c45c08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,7 @@ impl Symbol { /// Possible modifiers for this symbol. pub fn modifiers(&self) -> impl Iterator + '_ { self.variants() - .flat_map(|(m, _)| m.to_iter()) + .flat_map(|(m, _)| m.into_iter()) .collect::>() .into_iter() } diff --git a/src/shared.rs b/src/shared.rs index 2e1de9a..f5135e6 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -45,6 +45,8 @@ macro_rules! declare_types { }; } +use std::ops::Deref; + pub(crate) use declare_types; /// A set of modifiers. @@ -64,7 +66,7 @@ impl Default for ModifierSet { } } -impl> ModifierSet { +impl> ModifierSet { /// Convert the underlying string to a slice. pub fn as_deref(&self) -> ModifierSet<&str> { ModifierSet(&self.0) @@ -103,7 +105,7 @@ impl> ModifierSet { /// Iterate over the list of modifiers in an arbitrary order. pub fn iter(&self) -> impl Iterator { - self.0.split('.').filter(|s| !s.is_empty()) + self.into_iter() } /// Whether the set contains the modifier `m`. @@ -152,9 +154,22 @@ impl> ModifierSet { } } -impl<'a> ModifierSet<&'a str> { - /// Iterate over the list of modifiers with the original lifetime. - pub fn to_iter(self) -> impl Iterator { - self.0.split('.').filter(|s| !s.is_empty()) +impl<'a, S: Deref> IntoIterator for &'a ModifierSet { + type Item = &'a str; + type IntoIter = std::str::Split<'a, char>; + + /// Iterate over the list of modifiers in an arbitrary order. + fn into_iter(self) -> Self::IntoIter { + self.0.split('.') + } +} + +impl<'a> IntoIterator for ModifierSet<&'a str> { + type Item = &'a str; + type IntoIter = std::str::Split<'a, char>; + + /// Iterate over the list of modifiers in an arbitrary order. + fn into_iter(self) -> Self::IntoIter { + self.0.split('.') } } From 34edb0933b20458dbe8b97a60838697be533cc89 Mon Sep 17 00:00:00 2001 From: T0mstone Date: Tue, 8 Apr 2025 22:04:14 +0200 Subject: [PATCH 06/18] Remove the macro --- build.rs | 25 ++++++++++++++++++++----- src/lib.rs | 43 +++++++++++++++++++++++++++++++++++++------ src/shared.rs | 49 ------------------------------------------------- 3 files changed, 57 insertions(+), 60 deletions(-) diff --git a/build.rs b/build.rs index c7fefda..a1db064 100644 --- a/build.rs +++ b/build.rs @@ -8,11 +8,8 @@ type StrResult = Result; #[path = "src/shared.rs"] mod shared; -self::shared::declare_types! { - <'a> - str = &'a str, - List = Vec<_> -} +/// A module of definitions. +struct Module<'a>(Vec<(&'a str, Binding<'a>)>); impl<'a> Module<'a> { fn new(mut list: Vec<(&'a str, Binding<'a>)>) -> Self { @@ -21,6 +18,24 @@ impl<'a> Module<'a> { } } +/// A definition bound in a module, with metadata. +struct Binding<'a> { + def: Def<'a>, + deprecation: Option<&'a str>, +} + +/// A definition in a module. +enum Def<'a> { + Symbol(Symbol<'a>), + Module(Module<'a>), +} + +/// A symbol, either a leaf or with modifiers. +enum Symbol<'a> { + Single(char), + Multi(Vec<(ModifierSet<&'a str>, char)>), +} + /// A single line during parsing. #[derive(Debug, Copy, Clone)] enum Line<'a> { diff --git a/src/lib.rs b/src/lib.rs index 0c45c08..4bd101d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,12 +12,9 @@ pub use self::shared::ModifierSet; mod shared; -type StaticSlice = &'static [T]; -self::shared::declare_types! { - derive(Debug, Copy, Clone), - str = &'static str, - List = StaticSlice<_> -} +/// A module of definitions. +#[derive(Debug, Copy, Clone)] +pub struct Module(&'static [(&'static str, Binding)]); impl Module { /// Try to get a bound definition in the module. @@ -34,6 +31,40 @@ impl Module { } } +/// A definition bound in a module, with metadata. +#[derive(Debug, Copy, Clone)] +pub struct Binding { + /// The bound definition. + pub def: Def, + /// A deprecation message for the definition, if it is deprecated. + pub deprecation: Option<&'static str>, +} + +impl Binding { + /// Create a new bound definition. + pub const fn new(definition: Def) -> Self { + Self { def: definition, deprecation: None } + } +} + +/// A definition in a module. +#[derive(Debug, Copy, Clone)] +pub enum Def { + /// A symbol, potentially with modifiers. + Symbol(Symbol), + /// A nested module. + Module(Module), +} + +/// A symbol, either a leaf or with modifiers. +#[derive(Debug, Copy, Clone)] +pub enum Symbol { + /// A symbol without modifiers. + Single(char), + /// A symbol with named modifiers. The symbol defaults to its first variant. + Multi(&'static [(ModifierSet<&'static str>, char)]), +} + impl Symbol { /// Get the symbol's character for a given set of modifiers. pub fn get(&self, modifs: ModifierSet<&str>) -> Option { diff --git a/src/shared.rs b/src/shared.rs index f5135e6..1727bfa 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -1,54 +1,5 @@ -macro_rules! declare_types { - ($(<$lt:lifetime>)? - $(derive($($Der:ident),*),)? - str = $s:ty, - List = $List:ident<_> - ) => { - /// A module of definitions. - $(#[derive($($Der),*)])? - pub struct Module<$($lt)?>($List<($s, Binding<$($lt)?>)>); - - /// A definition bound in a module, with metadata. - $(#[derive($($Der),*)])? - pub struct Binding<$($lt)?> { - /// The bound definition. - pub def: Def<$($lt)?>, - /// A deprecation message for the definition, if it is deprecated. - pub deprecation: Option<$s>, - } - - impl<$($lt)?> Binding<$($lt)?> { - /// Create a new bound definition. - pub const fn new(definition: Def<$($lt)?>) -> Self { - Self { def: definition, deprecation: None } - } - } - - /// A definition in a module. - $(#[derive($($Der),*)])? - pub enum Def<$($lt)?> { - /// A symbol, potentially with modifiers. - Symbol(Symbol<$($lt)?>), - /// A nested module. - Module(Module<$($lt)?>), - } - - /// A symbol, either a leaf or with modifiers. - $(#[derive($($Der),*)])? - pub enum Symbol<$($lt)?> { - /// A symbol without modifiers. - Single(char), - /// A symbol with named modifiers. - /// The symbol defaults to its first variant. - Multi($List<(ModifierSet<$s>, char)>), - } - }; -} - use std::ops::Deref; -pub(crate) use declare_types; - /// A set of modifiers. #[derive(Debug, Copy, Clone)] // note: the visibility needs to be `pub(crate)`, From ff6b56a5401d72debffbce684f501d099c2459e7 Mon Sep 17 00:00:00 2001 From: T0mstone Date: Wed, 9 Apr 2025 15:45:40 +0200 Subject: [PATCH 07/18] Add as_str --- src/shared.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/shared.rs b/src/shared.rs index 1727bfa..9c0274d 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -23,6 +23,11 @@ impl> ModifierSet { ModifierSet(&self.0) } + /// Get the string of modifiers separated by `.`. + pub fn as_str(&self) -> &str { + &self.0 + } + /// Construct a modifier set from a string, /// where modifiers are separated by the character `.`. /// From 039f401739ea1943b1a7faa91489e9d41575c428 Mon Sep 17 00:00:00 2001 From: T0mstone Date: Wed, 9 Apr 2025 15:53:06 +0200 Subject: [PATCH 08/18] Revert "Add as_str" This reverts commit ff6b56a5401d72debffbce684f501d099c2459e7. --- src/shared.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/shared.rs b/src/shared.rs index 9c0274d..1727bfa 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -23,11 +23,6 @@ impl> ModifierSet { ModifierSet(&self.0) } - /// Get the string of modifiers separated by `.`. - pub fn as_str(&self) -> &str { - &self.0 - } - /// Construct a modifier set from a string, /// where modifiers are separated by the character `.`. /// From b99817a19c6249607a27010dfc86c8a90d43d494 Mon Sep 17 00:00:00 2001 From: T0mstone Date: Wed, 9 Apr 2025 15:57:29 +0200 Subject: [PATCH 09/18] Derive Eq, Hash --- src/shared.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/shared.rs b/src/shared.rs index 1727bfa..8359b50 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -1,7 +1,12 @@ use std::ops::Deref; /// A set of modifiers. -#[derive(Debug, Copy, Clone)] +/// +/// Beware: The [`Eq`] and [`Hash`] implementations are dependent on the ordering +/// of the modifiers, in opposition to what a set would usually constitute. +/// To test for set-wise equality, use [`iter`](Self::iter) and collect into a +/// true set type like [`HashSet`](std::collections::HashSet). +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] // note: the visibility needs to be `pub(crate)`, // since build.rs outputs `ModifierSet(...)` pub struct ModifierSet(pub(crate) S); From 17d2dff8d13ae526c6429810d602f466a2bb4424 Mon Sep 17 00:00:00 2001 From: T0mstone Date: Wed, 9 Apr 2025 16:12:17 +0200 Subject: [PATCH 10/18] Reapply "Add as_str" This reverts commit 039f401739ea1943b1a7faa91489e9d41575c428. --- src/shared.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/shared.rs b/src/shared.rs index 8359b50..343d3f5 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -28,6 +28,11 @@ impl> ModifierSet { ModifierSet(&self.0) } + /// Get the string of modifiers separated by `.`. + pub fn as_str(&self) -> &str { + &self.0 + } + /// Construct a modifier set from a string, /// where modifiers are separated by the character `.`. /// From 65e5b01e9c061b095994eb98fead801d31d4e849 Mon Sep 17 00:00:00 2001 From: T0mstone Date: Wed, 9 Apr 2025 16:44:41 +0200 Subject: [PATCH 11/18] Fix iterator for empty `ModifierSet` --- src/shared.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/shared.rs b/src/shared.rs index 343d3f5..b5ce79d 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -121,7 +121,12 @@ impl<'a, S: Deref> IntoIterator for &'a ModifierSet { /// Iterate over the list of modifiers in an arbitrary order. fn into_iter(self) -> Self::IntoIter { - self.0.split('.') + let mut iter = self.0.split('.'); + if self.0.is_empty() { + // empty the iterator + let _ = iter.next(); + } + iter } } @@ -131,6 +136,21 @@ impl<'a> IntoIterator for ModifierSet<&'a str> { /// Iterate over the list of modifiers in an arbitrary order. fn into_iter(self) -> Self::IntoIter { - self.0.split('.') + let mut iter = self.0.split('.'); + if self.0.is_empty() { + // empty the iterator + let _ = iter.next(); + } + iter } } + +#[cfg(test)] +mod tests { + type ModifierSet = super::ModifierSet<&'static str>; + + #[test] + fn empty_set_empty_iter() { + assert_eq!(ModifierSet::default().iter().count(), 0); + } +} \ No newline at end of file From 7157b82d817b362c7d71b223681deef61f294f1d Mon Sep 17 00:00:00 2001 From: T0mstone Date: Wed, 9 Apr 2025 16:55:17 +0200 Subject: [PATCH 12/18] Add more tests and clarify `best_match_in` docs --- src/shared.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/src/shared.rs b/src/shared.rs index b5ce79d..f46fb09 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -86,6 +86,8 @@ impl> ModifierSet { /// by the following two criteria (in order): /// 1. Number of modifiers in common with `self` (more is better). /// 2. Total number of modifiers (fewer is better). + /// + /// If there are multiple best matches, the first of them is returned. pub fn best_match_in<'a, T>( &self, variants: impl Iterator, T)>, @@ -150,7 +152,63 @@ mod tests { type ModifierSet = super::ModifierSet<&'static str>; #[test] - fn empty_set_empty_iter() { + fn default_is_empty() { + assert!(ModifierSet::default().is_empty()); + } + + #[test] + fn iter_count() { assert_eq!(ModifierSet::default().iter().count(), 0); + assert_eq!(ModifierSet::new_unchecked("a").iter().count(), 1); + assert_eq!(ModifierSet::new_unchecked("a.b").iter().count(), 2); + assert_eq!(ModifierSet::new_unchecked("a.b.c").iter().count(), 3); } -} \ No newline at end of file + + #[test] + fn subset() { + assert!( + ModifierSet::new_unchecked("a").is_subset(ModifierSet::new_unchecked("a.b")) + ); + assert!( + ModifierSet::new_unchecked("a").is_subset(ModifierSet::new_unchecked("b.a")) + ); + assert!(ModifierSet::new_unchecked("a.b") + .is_subset(ModifierSet::new_unchecked("b.c.a"))); + } + + #[test] + fn best_match() { + // 1. more modifiers in common with self + assert_eq!( + ModifierSet::new_unchecked("a.b").best_match_in([ + (ModifierSet::new_unchecked("a.c"), 1), + (ModifierSet::new_unchecked("a.b"), 2), + ].into_iter()), + Some(2) + ); + // 2. fewer modifiers in general + assert_eq!( + ModifierSet::new_unchecked("a").best_match_in([ + (ModifierSet::new_unchecked("a"), 1), + (ModifierSet::new_unchecked("a.b"), 2), + ].into_iter()), + Some(1) + ); + // the first rule takes priority over the second + assert_eq!( + ModifierSet::new_unchecked("a.b").best_match_in([ + (ModifierSet::new_unchecked("a"), 1), + (ModifierSet::new_unchecked("a.b"), 2), + ].into_iter()), + Some(2) + ); + // among multiple best matches, the first one is returned + assert_eq!( + ModifierSet::default().best_match_in([ + (ModifierSet::new_unchecked("a"), 1), + (ModifierSet::new_unchecked("b"), 2) + ].into_iter()), + Some(1) + ); + } +} From 120d4244073cff37eac6df2793b57b9db2ba3fe0 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 4 Jun 2025 12:41:41 +0200 Subject: [PATCH 13/18] Formatting --- build.rs | 3 +- src/lib.rs | 11 ++--- src/shared.rs | 126 ++++++++++++++++++++++++++++---------------------- 3 files changed, 77 insertions(+), 63 deletions(-) diff --git a/build.rs b/build.rs index a1db064..c1b9f7d 100644 --- a/build.rs +++ b/build.rs @@ -1,8 +1,9 @@ -use self::shared::ModifierSet; use std::fmt::Write; use std::iter::Peekable; use std::path::Path; +use self::shared::ModifierSet; + type StrResult = Result; #[path = "src/shared.rs"] diff --git a/src/lib.rs b/src/lib.rs index 4bd101d..abb8596 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,11 @@ //! Human-friendly notation for Unicode symbols. //! //! ## Model -//! A [`Symbol`] is a collection of one or more _variants_. -//! Each variant is identified by a set of [_modifiers_](ModifierSet) -//! and has a single character as its value. -//! The modifiers themselves can in principle be any non-empty strings -//! that don't contain the character `.`, but codex only defines -//! ones that are entirely made of ASCII alphabetical characters. +//! A [`Symbol`] is a collection of one or more _variants_. Each variant is +//! identified by a set of [_modifiers_](ModifierSet) and has a single character +//! as its value. The modifiers themselves can in principle be any non-empty +//! strings that don't contain the character `.`, but codex only defines ones +//! that are entirely made of ASCII alphabetical characters. pub use self::shared::ModifierSet; diff --git a/src/shared.rs b/src/shared.rs index f46fb09..fa0f2f4 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -2,44 +2,25 @@ use std::ops::Deref; /// A set of modifiers. /// -/// Beware: The [`Eq`] and [`Hash`] implementations are dependent on the ordering -/// of the modifiers, in opposition to what a set would usually constitute. -/// To test for set-wise equality, use [`iter`](Self::iter) and collect into a -/// true set type like [`HashSet`](std::collections::HashSet). +/// Beware: The [`Eq`] and [`Hash`] implementations are dependent on the +/// ordering of the modifiers, in opposition to what a set would usually +/// constitute. To test for set-wise equality, use [`iter`](Self::iter) and +/// collect into a true set type like [`HashSet`](std::collections::HashSet). #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -// note: the visibility needs to be `pub(crate)`, -// since build.rs outputs `ModifierSet(...)` -pub struct ModifierSet(pub(crate) S); - -impl Default for ModifierSet { - /// Construct the default modifier set. - /// - /// This is typically the empty set, - /// though the remark from [`Self::new_unchecked`] applies - /// since `S::default()` could technically be anything. - fn default() -> Self { - Self(S::default()) - } -} +pub struct ModifierSet( + // Note: the visibility needs to be `pub(crate)`, since build.rs outputs + // `ModifierSet(...)`. + pub(crate) S, +); impl> ModifierSet { - /// Convert the underlying string to a slice. - pub fn as_deref(&self) -> ModifierSet<&str> { - ModifierSet(&self.0) - } - - /// Get the string of modifiers separated by `.`. - pub fn as_str(&self) -> &str { - &self.0 - } - - /// Construct a modifier set from a string, - /// where modifiers are separated by the character `.`. + /// Constructs a modifier set from a string, where modifiers are separated by + /// the character `.`. /// /// It is not unsafe to use this function wrongly, but it can produce - /// unexpected results down the line. Correct usage should ensure that - /// `s` does not contain any empty modifiers (i.e. the sequence `..`) - /// and that no modifier occurs twice. + /// unexpected results down the line. Correct usage should ensure that `s` + /// does not contain any empty modifiers (i.e. the sequence `..`) and that + /// no modifier occurs twice. pub fn new_unchecked(s: S) -> Self { Self(s) } @@ -49,6 +30,16 @@ impl> ModifierSet { self.0.is_empty() } + /// Gets the string of modifiers separated by `.`. + pub fn as_str(&self) -> &str { + &self.0 + } + + /// Converts the underlying string to a slice. + pub fn as_deref(&self) -> ModifierSet<&str> { + ModifierSet(&self.0) + } + /// Add a modifier to the set, without checking that it is a valid modifier. /// /// It is not unsafe to use this method wrongly, but that can produce @@ -64,7 +55,7 @@ impl> ModifierSet { self.0 += m; } - /// Iterate over the list of modifiers in an arbitrary order. + /// Iterates over the list of modifiers in an arbitrary order. pub fn iter(&self) -> impl Iterator { self.into_iter() } @@ -74,12 +65,7 @@ impl> ModifierSet { self.iter().any(|lhs| lhs == m) } - /// Whether all modifiers in `self` are also present in `other`. - pub fn is_subset(&self, other: ModifierSet<&str>) -> bool { - self.iter().all(|m| other.contains(m)) - } - - /// Find the best match from the list. + /// Finds the best match from the list. /// /// To be considered a match, the modifier set must be a superset of /// (or equal to) `self`. Among different matches, the best one is selected @@ -115,6 +101,22 @@ impl> ModifierSet { best } + + /// Whether all modifiers in `self` are also present in `other`. + pub fn is_subset(&self, other: ModifierSet<&str>) -> bool { + self.iter().all(|m| other.contains(m)) + } +} + +impl Default for ModifierSet { + /// Constructs the default modifier set. + /// + /// This is typically the empty set, though the remark from + /// [`Self::new_unchecked`] applies since `S::default()` could technically + /// be anything. + fn default() -> Self { + Self(S::default()) + } } impl<'a, S: Deref> IntoIterator for &'a ModifierSet { @@ -180,34 +182,46 @@ mod tests { fn best_match() { // 1. more modifiers in common with self assert_eq!( - ModifierSet::new_unchecked("a.b").best_match_in([ - (ModifierSet::new_unchecked("a.c"), 1), - (ModifierSet::new_unchecked("a.b"), 2), - ].into_iter()), + ModifierSet::new_unchecked("a.b").best_match_in( + [ + (ModifierSet::new_unchecked("a.c"), 1), + (ModifierSet::new_unchecked("a.b"), 2), + ] + .into_iter() + ), Some(2) ); // 2. fewer modifiers in general assert_eq!( - ModifierSet::new_unchecked("a").best_match_in([ - (ModifierSet::new_unchecked("a"), 1), - (ModifierSet::new_unchecked("a.b"), 2), - ].into_iter()), + ModifierSet::new_unchecked("a").best_match_in( + [ + (ModifierSet::new_unchecked("a"), 1), + (ModifierSet::new_unchecked("a.b"), 2), + ] + .into_iter() + ), Some(1) ); // the first rule takes priority over the second assert_eq!( - ModifierSet::new_unchecked("a.b").best_match_in([ - (ModifierSet::new_unchecked("a"), 1), - (ModifierSet::new_unchecked("a.b"), 2), - ].into_iter()), + ModifierSet::new_unchecked("a.b").best_match_in( + [ + (ModifierSet::new_unchecked("a"), 1), + (ModifierSet::new_unchecked("a.b"), 2), + ] + .into_iter() + ), Some(2) ); // among multiple best matches, the first one is returned assert_eq!( - ModifierSet::default().best_match_in([ - (ModifierSet::new_unchecked("a"), 1), - (ModifierSet::new_unchecked("b"), 2) - ].into_iter()), + ModifierSet::default().best_match_in( + [ + (ModifierSet::new_unchecked("a"), 1), + (ModifierSet::new_unchecked("b"), 2) + ] + .into_iter() + ), Some(1) ); } From d7d2c968f1b58b19b82a6164532df1a9c538b68c Mon Sep 17 00:00:00 2001 From: T0mstone Date: Mon, 9 Jun 2025 12:35:19 +0200 Subject: [PATCH 14/18] Improve wording slightly --- src/shared.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared.rs b/src/shared.rs index fa0f2f4..379b0f8 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -17,7 +17,7 @@ impl> ModifierSet { /// Constructs a modifier set from a string, where modifiers are separated by /// the character `.`. /// - /// It is not unsafe to use this function wrongly, but it can produce + /// It is not unsafe to use this function incorrectly, but it can produce /// unexpected results down the line. Correct usage should ensure that `s` /// does not contain any empty modifiers (i.e. the sequence `..`) and that /// no modifier occurs twice. @@ -42,7 +42,7 @@ impl> ModifierSet { /// Add a modifier to the set, without checking that it is a valid modifier. /// - /// It is not unsafe to use this method wrongly, but that can produce + /// It is not unsafe to use this method incorrectly, but that can produce /// unexpected results down the line. Correct usage should ensure that /// `modifier` is not empty and doesn't contain the character `.`. pub fn add_unchecked(&mut self, m: &str) From 6ef866ad3d94dd5b5e0ec024444c9c8cd46e0b1a Mon Sep 17 00:00:00 2001 From: T0mstone Date: Mon, 9 Jun 2025 12:51:19 +0200 Subject: [PATCH 15/18] Rename add to insert --- src/shared.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared.rs b/src/shared.rs index 379b0f8..d52d677 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -40,12 +40,12 @@ impl> ModifierSet { ModifierSet(&self.0) } - /// Add a modifier to the set, without checking that it is a valid modifier. + /// Inserts a modifier into the set, without checking that it is a valid modifier. /// /// It is not unsafe to use this method incorrectly, but that can produce /// unexpected results down the line. Correct usage should ensure that /// `modifier` is not empty and doesn't contain the character `.`. - pub fn add_unchecked(&mut self, m: &str) + pub fn insert_unchecked(&mut self, m: &str) where S: for<'a> std::ops::AddAssign<&'a str>, { From 477a3437c2dafe4de22a1b010b393536327f1b65 Mon Sep 17 00:00:00 2001 From: T0mstone Date: Tue, 10 Jun 2025 04:40:56 +0200 Subject: [PATCH 16/18] Rename `_unchecked` to `_raw`/`_raw_dotted` and reword docs accordingly --- build.rs | 2 +- src/shared.rs | 66 ++++++++++++++++++++++++++------------------------- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/build.rs b/build.rs index c1b9f7d..e835377 100644 --- a/build.rs +++ b/build.rs @@ -115,7 +115,7 @@ fn tokenize(line: &str) -> StrResult { validate_ident(part)?; } let c = decode_char(tail.ok_or("missing char")?)?; - Line::Variant(ModifierSet::new_unchecked(rest), c) + Line::Variant(ModifierSet::from_raw_dotted(rest), c) } else { validate_ident(head)?; let c = tail.map(decode_char).transpose()?; diff --git a/src/shared.rs b/src/shared.rs index d52d677..38b55e4 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -17,11 +17,16 @@ impl> ModifierSet { /// Constructs a modifier set from a string, where modifiers are separated by /// the character `.`. /// - /// It is not unsafe to use this function incorrectly, but it can produce - /// unexpected results down the line. Correct usage should ensure that `s` - /// does not contain any empty modifiers (i.e. the sequence `..`) and that - /// no modifier occurs twice. - pub fn new_unchecked(s: S) -> Self { + /// `s` should not contain any empty modifiers (i.e. it shouldn't contain the + /// sequence `..`) and no modifier should occur twice. Otherwise, unexpected + /// errors can occur. + pub fn from_raw_dotted(s: S) -> Self { + // checking the other requirement too feels like it would be a bit too + // expensive, even for debug mode. + debug_assert!( + !s.contains(".."), + "ModifierSet::from_dotted called with string containing empty modifier" + ); Self(s) } @@ -40,12 +45,11 @@ impl> ModifierSet { ModifierSet(&self.0) } - /// Inserts a modifier into the set, without checking that it is a valid modifier. + /// Inserts a new modifier into the set. /// - /// It is not unsafe to use this method incorrectly, but that can produce - /// unexpected results down the line. Correct usage should ensure that - /// `modifier` is not empty and doesn't contain the character `.`. - pub fn insert_unchecked(&mut self, m: &str) + /// `m` should not be empty, contain the character `.`, + /// or already be in the set. Otherwise, unexpected errors can occur. + pub fn insert_raw(&mut self, m: &str) where S: for<'a> std::ops::AddAssign<&'a str>, { @@ -161,31 +165,29 @@ mod tests { #[test] fn iter_count() { assert_eq!(ModifierSet::default().iter().count(), 0); - assert_eq!(ModifierSet::new_unchecked("a").iter().count(), 1); - assert_eq!(ModifierSet::new_unchecked("a.b").iter().count(), 2); - assert_eq!(ModifierSet::new_unchecked("a.b.c").iter().count(), 3); + assert_eq!(ModifierSet::from_raw_dotted("a").iter().count(), 1); + assert_eq!(ModifierSet::from_raw_dotted("a.b").iter().count(), 2); + assert_eq!(ModifierSet::from_raw_dotted("a.b.c").iter().count(), 3); } #[test] fn subset() { - assert!( - ModifierSet::new_unchecked("a").is_subset(ModifierSet::new_unchecked("a.b")) - ); - assert!( - ModifierSet::new_unchecked("a").is_subset(ModifierSet::new_unchecked("b.a")) - ); - assert!(ModifierSet::new_unchecked("a.b") - .is_subset(ModifierSet::new_unchecked("b.c.a"))); + assert!(ModifierSet::from_raw_dotted("a") + .is_subset(ModifierSet::from_raw_dotted("a.b"))); + assert!(ModifierSet::from_raw_dotted("a") + .is_subset(ModifierSet::from_raw_dotted("b.a"))); + assert!(ModifierSet::from_raw_dotted("a.b") + .is_subset(ModifierSet::from_raw_dotted("b.c.a"))); } #[test] fn best_match() { // 1. more modifiers in common with self assert_eq!( - ModifierSet::new_unchecked("a.b").best_match_in( + ModifierSet::from_raw_dotted("a.b").best_match_in( [ - (ModifierSet::new_unchecked("a.c"), 1), - (ModifierSet::new_unchecked("a.b"), 2), + (ModifierSet::from_raw_dotted("a.c"), 1), + (ModifierSet::from_raw_dotted("a.b"), 2), ] .into_iter() ), @@ -193,10 +195,10 @@ mod tests { ); // 2. fewer modifiers in general assert_eq!( - ModifierSet::new_unchecked("a").best_match_in( + ModifierSet::from_raw_dotted("a").best_match_in( [ - (ModifierSet::new_unchecked("a"), 1), - (ModifierSet::new_unchecked("a.b"), 2), + (ModifierSet::from_raw_dotted("a"), 1), + (ModifierSet::from_raw_dotted("a.b"), 2), ] .into_iter() ), @@ -204,10 +206,10 @@ mod tests { ); // the first rule takes priority over the second assert_eq!( - ModifierSet::new_unchecked("a.b").best_match_in( + ModifierSet::from_raw_dotted("a.b").best_match_in( [ - (ModifierSet::new_unchecked("a"), 1), - (ModifierSet::new_unchecked("a.b"), 2), + (ModifierSet::from_raw_dotted("a"), 1), + (ModifierSet::from_raw_dotted("a.b"), 2), ] .into_iter() ), @@ -217,8 +219,8 @@ mod tests { assert_eq!( ModifierSet::default().best_match_in( [ - (ModifierSet::new_unchecked("a"), 1), - (ModifierSet::new_unchecked("b"), 2) + (ModifierSet::from_raw_dotted("a"), 1), + (ModifierSet::from_raw_dotted("b"), 2) ] .into_iter() ), From 1e8e94b12b00a2ef553a22684254bfbf4f4b86f3 Mon Sep 17 00:00:00 2001 From: T0mstone Date: Tue, 10 Jun 2025 04:48:00 +0200 Subject: [PATCH 17/18] Fix clippy lints --- build.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.rs b/build.rs index e835377..09432a4 100644 --- a/build.rs +++ b/build.rs @@ -84,7 +84,7 @@ fn process(buf: &mut String, file: &Path, name: &str, desc: &str) { write!(buf, "#[doc = {desc:?}] pub const {name}: Module = ").unwrap(); encode(buf, &module); - buf.push_str(";"); + buf.push(';'); } /// Tokenizes and classifies a line. @@ -170,7 +170,7 @@ fn parse<'a>( p.next(); } - let symbol = if variants.len() > 0 { + let symbol = if !variants.is_empty() { if let Some(c) = c { variants.insert(0, (ModifierSet::default(), c)); } @@ -209,7 +209,7 @@ fn encode(buf: &mut String, module: &Module) { Def::Module(module) => { buf.push_str("Def::Module("); encode(buf, module); - buf.push_str(")"); + buf.push(')'); } Def::Symbol(symbol) => { buf.push_str("Def::Symbol(Symbol::"); @@ -217,7 +217,7 @@ fn encode(buf: &mut String, module: &Module) { Symbol::Single(c) => write!(buf, "Single({c:?})").unwrap(), Symbol::Multi(list) => write!(buf, "Multi(&{list:?})").unwrap(), } - buf.push_str(")"); + buf.push(')'); } } write!(buf, ", deprecation: {:?} }}),", entry.deprecation).unwrap(); From 02ea0fb3c80d9902510f9686338532209b974ee5 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 10 Jun 2025 10:32:39 +0200 Subject: [PATCH 18/18] Rewrap lines and fix dead link --- src/shared.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/shared.rs b/src/shared.rs index 38b55e4..cc9286e 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -14,14 +14,14 @@ pub struct ModifierSet( ); impl> ModifierSet { - /// Constructs a modifier set from a string, where modifiers are separated by - /// the character `.`. + /// Constructs a modifier set from a string, where modifiers are separated + /// by the character `.`. /// - /// `s` should not contain any empty modifiers (i.e. it shouldn't contain the - /// sequence `..`) and no modifier should occur twice. Otherwise, unexpected - /// errors can occur. + /// `s` should not contain any empty modifiers (i.e. it shouldn't contain + /// the sequence `..`) and no modifier should occur twice. Otherwise, + /// unexpected errors can occur. pub fn from_raw_dotted(s: S) -> Self { - // checking the other requirement too feels like it would be a bit too + // Checking the other requirement too feels like it would be a bit too // expensive, even for debug mode. debug_assert!( !s.contains(".."), @@ -47,8 +47,8 @@ impl> ModifierSet { /// Inserts a new modifier into the set. /// - /// `m` should not be empty, contain the character `.`, - /// or already be in the set. Otherwise, unexpected errors can occur. + /// `m` should not be empty, contain the character `.`, or already be in the + /// set. Otherwise, unexpected errors can occur. pub fn insert_raw(&mut self, m: &str) where S: for<'a> std::ops::AddAssign<&'a str>, @@ -71,9 +71,9 @@ impl> ModifierSet { /// Finds the best match from the list. /// - /// To be considered a match, the modifier set must be a superset of - /// (or equal to) `self`. Among different matches, the best one is selected - /// by the following two criteria (in order): + /// To be considered a match, the modifier set must be a superset of (or + /// equal to) `self`. Among different matches, the best one is selected by + /// the following two criteria (in order): /// 1. Number of modifiers in common with `self` (more is better). /// 2. Total number of modifiers (fewer is better). /// @@ -116,7 +116,7 @@ impl Default for ModifierSet { /// Constructs the default modifier set. /// /// This is typically the empty set, though the remark from - /// [`Self::new_unchecked`] applies since `S::default()` could technically + /// [`Self::from_raw_dotted`] applies since `S::default()` could technically /// be anything. fn default() -> Self { Self(S::default()) @@ -131,7 +131,7 @@ impl<'a, S: Deref> IntoIterator for &'a ModifierSet { fn into_iter(self) -> Self::IntoIter { let mut iter = self.0.split('.'); if self.0.is_empty() { - // empty the iterator + // Empty the iterator let _ = iter.next(); } iter @@ -146,7 +146,7 @@ impl<'a> IntoIterator for ModifierSet<&'a str> { fn into_iter(self) -> Self::IntoIter { let mut iter = self.0.split('.'); if self.0.is_empty() { - // empty the iterator + // Empty the iterator let _ = iter.next(); } iter