-
-
Notifications
You must be signed in to change notification settings - Fork 11
Codify and Resolve modifiers #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
a3a16a7
536dc49
4895855
1fdbb1e
a5cbb19
34edb09
ff6b56a
039f401
b99817a
17d2dff
65e5b01
7157b82
120d424
d7d2c96
6ef866a
477a343
1e8e94b
02ea0fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
use std::ops::Deref; | ||
|
||
/// A set of modifiers. | ||
#[derive(Debug, Copy, Clone)] | ||
// note: the visibility needs to be `pub(crate)`, | ||
// since build.rs outputs `ModifierSet(...)` | ||
pub struct ModifierSet<S>(pub(crate) S); | ||
|
||
impl<S: Default> Default for ModifierSet<S> { | ||
/// 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<S: Deref<Target = str>> ModifierSet<S> { | ||
/// 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 { | ||
MDLC01 marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not a fan of the term "unchecked" as it invokes unsafety feelings (even if documented). Also There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Not necessarily, e.g. |
||
Self(s) | ||
} | ||
|
||
/// 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) | ||
T0mstone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
where | ||
S: for<'a> std::ops::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<Item = &str> { | ||
self.into_iter() | ||
} | ||
|
||
/// 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<Item = (ModifierSet<&'a str>, T)>, | ||
) -> Option<T> { | ||
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, std::cmp::Reverse(total)); | ||
if best_score.is_none_or(|b| score > b) { | ||
best = Some(candidate.1); | ||
best_score = Some(score); | ||
} | ||
} | ||
|
||
best | ||
} | ||
} | ||
|
||
impl<'a, S: Deref<Target = str>> IntoIterator for &'a ModifierSet<S> { | ||
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('.') | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is outdated since it uses
new_unchecked
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the point where it gets output is
codex/build.rs
Line 217 in 34edb09
which uses the debug-formatting, which outputs
ModifierSet(<string>)
.Changing that would of course be possible, but it'd require constructing the slice manually instead of just using
{:?}
there.