summaryrefslogtreecommitdiff
path: root/src/model/symbol.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-01-27 12:04:23 +0100
committerLaurenz <laurmaedje@gmail.com>2023-01-27 12:04:36 +0100
commit1de53730bce0bd3f9de89db1da7c19b7889b9a75 (patch)
treee2746f4853a5a8e99f32e8c52d6e4b4f411c1933 /src/model/symbol.rs
parent13efa128c855637a7fe3351a4579383359d1be1b (diff)
Symbol values and modules
Diffstat (limited to 'src/model/symbol.rs')
-rw-r--r--src/model/symbol.rs152
1 files changed, 152 insertions, 0 deletions
diff --git a/src/model/symbol.rs b/src/model/symbol.rs
new file mode 100644
index 00000000..ac1d2b10
--- /dev/null
+++ b/src/model/symbol.rs
@@ -0,0 +1,152 @@
+use std::cmp::Reverse;
+use std::collections::BTreeSet;
+use std::fmt::{self, Debug, Formatter, Write};
+
+use crate::diag::StrResult;
+use crate::util::EcoString;
+
+/// Define a list of symbols.
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __symbols {
+ ($func:ident, $($name:ident: $value:tt),* $(,)?) => {
+ pub(super) fn $func(scope: &mut $crate::model::Scope) {
+ $(scope.define(stringify!($name), $crate::model::symbols!(@one $value));)*
+ }
+ };
+ (@one $c:literal) => { $crate::model::Symbol::new($c) };
+ (@one [$($first:literal $(: $second:literal)?),* $(,)?]) => {
+ $crate::model::Symbol::list(&[
+ $($crate::model::symbols!(@pair $first $(: $second)?)),*
+ ])
+ };
+ (@pair $first:literal) => { ("", $first) };
+ (@pair $first:literal: $second:literal) => { ($first, $second) };
+}
+
+#[doc(inline)]
+pub use crate::__symbols as symbols;
+
+/// A symbol.
+#[derive(Clone, Eq, PartialEq, Hash)]
+pub struct Symbol {
+ repr: Repr,
+ modifiers: EcoString,
+}
+
+/// A collection of symbols.
+#[derive(Clone, Eq, PartialEq, Hash)]
+enum Repr {
+ Single(char),
+ List(&'static [(&'static str, char)]),
+}
+
+impl Symbol {
+ /// Create a new symbol from a single character.
+ pub fn new(c: char) -> Self {
+ Self { repr: Repr::Single(c), modifiers: EcoString::new() }
+ }
+
+ /// Create a symbol with variants.
+ #[track_caller]
+ pub fn list(list: &'static [(&'static str, char)]) -> Self {
+ debug_assert!(!list.is_empty());
+ Self {
+ repr: Repr::List(list),
+ modifiers: EcoString::new(),
+ }
+ }
+
+ /// Get the symbol's text.
+ pub fn get(&self) -> char {
+ match self.repr {
+ Repr::Single(c) => c,
+ Repr::List(list) => find(list, &self.modifiers).unwrap(),
+ }
+ }
+
+ /// Apply a modifier to the symbol.
+ pub fn modified(mut self, modifier: &str) -> StrResult<Self> {
+ if !self.modifiers.is_empty() {
+ self.modifiers.push('.');
+ }
+ self.modifiers.push_str(modifier);
+ if match self.repr {
+ Repr::Single(_) => true,
+ Repr::List(list) => find(list, &self.modifiers).is_none(),
+ } {
+ Err("unknown modifier")?
+ }
+ Ok(self)
+ }
+
+ /// The characters that are covered by this symbol.
+ pub fn chars(&self) -> impl Iterator<Item = char> {
+ let (first, slice) = match self.repr {
+ Repr::Single(c) => (Some(c), [].as_slice()),
+ Repr::List(list) => (None, list),
+ };
+ first.into_iter().chain(slice.iter().map(|&(_, c)| c))
+ }
+
+ /// Possible modifiers.
+ pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ {
+ let mut set = BTreeSet::new();
+ if let Repr::List(list) = self.repr {
+ for modifier in list.iter().flat_map(|(name, _)| name.split('.')) {
+ if !modifier.is_empty() && !contained(&self.modifiers, modifier) {
+ set.insert(modifier);
+ }
+ }
+ }
+ set.into_iter()
+ }
+}
+
+impl Debug for Symbol {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.write_char(self.get())
+ }
+}
+
+/// Find the best symbol from the list.
+fn find(list: &[(&str, char)], modifiers: &str) -> Option<char> {
+ let mut best = None;
+ let mut best_score = None;
+
+ // Find the best table entry with this name.
+ 'outer: for candidate in list {
+ for modifier in parts(modifiers) {
+ if !contained(candidate.0, modifier) {
+ continue 'outer;
+ }
+ }
+
+ let mut matching = 0;
+ let mut total = 0;
+ for modifier in parts(candidate.0) {
+ if contained(modifiers, modifier) {
+ matching += 1;
+ }
+ total += 1;
+ }
+
+ let score = (matching, Reverse(total));
+ if best_score.map_or(true, |b| score > b) {
+ best = Some(candidate.1);
+ best_score = Some(score);
+ }
+ }
+
+ best
+}
+
+/// Split a modifier list into its parts.
+fn parts(modifiers: &str) -> impl Iterator<Item = &str> {
+ modifiers.split('.').filter(|s| !s.is_empty())
+}
+
+/// Whether the modifier string contains the modifier `m`.
+fn contained(modifiers: &str, m: &str) -> bool {
+ parts(modifiers).any(|part| part == m)
+}