1use std::cmp::Reverse;
2use std::collections::{BTreeSet, HashMap};
3use std::fmt::{self, Debug, Display, Formatter, Write};
4use std::sync::Arc;
5
6use ecow::{eco_format, EcoString};
7use serde::{Serialize, Serializer};
8use typst_syntax::{is_ident, Span, Spanned};
9use typst_utils::hash128;
10
11use crate::diag::{bail, SourceResult, StrResult};
12use crate::foundations::{
13 cast, elem, func, scope, ty, Array, Content, Func, NativeElement, NativeFunc, Packed,
14 PlainText, Repr as _,
15};
16
17#[ty(scope, cast)]
47#[derive(Debug, Clone, Eq, PartialEq, Hash)]
48pub struct Symbol(Repr);
49
50#[derive(Clone, Eq, PartialEq, Hash)]
52enum Repr {
53 Single(char),
55 Complex(&'static [(&'static str, char)]),
57 Modified(Arc<(List, EcoString)>),
61}
62
63#[derive(Clone, Eq, PartialEq, Hash)]
65enum List {
66 Static(&'static [(&'static str, char)]),
67 Runtime(Box<[(EcoString, char)]>),
68}
69
70impl Symbol {
71 pub const fn single(c: char) -> Self {
73 Self(Repr::Single(c))
74 }
75
76 #[track_caller]
78 pub const fn list(list: &'static [(&'static str, char)]) -> Self {
79 debug_assert!(!list.is_empty());
80 Self(Repr::Complex(list))
81 }
82
83 #[track_caller]
85 pub fn runtime(list: Box<[(EcoString, char)]>) -> Self {
86 debug_assert!(!list.is_empty());
87 Self(Repr::Modified(Arc::new((List::Runtime(list), EcoString::new()))))
88 }
89
90 pub fn get(&self) -> char {
92 match &self.0 {
93 Repr::Single(c) => *c,
94 Repr::Complex(_) => find(self.variants(), "").unwrap(),
95 Repr::Modified(arc) => find(self.variants(), &arc.1).unwrap(),
96 }
97 }
98
99 pub fn func(&self) -> StrResult<Func> {
101 match self.get() {
102 '⌈' => Ok(crate::math::ceil::func()),
103 '⌊' => Ok(crate::math::floor::func()),
104 '–' => Ok(crate::math::accent::dash::func()),
105 '⋅' | '\u{0307}' => Ok(crate::math::accent::dot::func()),
106 '¨' => Ok(crate::math::accent::dot_double::func()),
107 '\u{20db}' => Ok(crate::math::accent::dot_triple::func()),
108 '\u{20dc}' => Ok(crate::math::accent::dot_quad::func()),
109 '∼' => Ok(crate::math::accent::tilde::func()),
110 '´' => Ok(crate::math::accent::acute::func()),
111 '˝' => Ok(crate::math::accent::acute_double::func()),
112 '˘' => Ok(crate::math::accent::breve::func()),
113 'ˇ' => Ok(crate::math::accent::caron::func()),
114 '^' => Ok(crate::math::accent::hat::func()),
115 '`' => Ok(crate::math::accent::grave::func()),
116 '¯' => Ok(crate::math::accent::macron::func()),
117 '○' => Ok(crate::math::accent::circle::func()),
118 '→' => Ok(crate::math::accent::arrow::func()),
119 '←' => Ok(crate::math::accent::arrow_l::func()),
120 '↔' => Ok(crate::math::accent::arrow_l_r::func()),
121 '⇀' => Ok(crate::math::accent::harpoon::func()),
122 '↼' => Ok(crate::math::accent::harpoon_lt::func()),
123 _ => bail!("symbol {self} is not callable"),
124 }
125 }
126
127 pub fn modified(mut self, modifier: &str) -> StrResult<Self> {
129 if let Repr::Complex(list) = self.0 {
130 self.0 = Repr::Modified(Arc::new((List::Static(list), EcoString::new())));
131 }
132
133 if let Repr::Modified(arc) = &mut self.0 {
134 let (list, modifiers) = Arc::make_mut(arc);
135 if !modifiers.is_empty() {
136 modifiers.push('.');
137 }
138 modifiers.push_str(modifier);
139 if find(list.variants(), modifiers).is_some() {
140 return Ok(self);
141 }
142 }
143
144 bail!("unknown symbol modifier")
145 }
146
147 pub fn variants(&self) -> impl Iterator<Item = (&str, char)> {
149 match &self.0 {
150 Repr::Single(c) => Variants::Single(Some(*c).into_iter()),
151 Repr::Complex(list) => Variants::Static(list.iter()),
152 Repr::Modified(arc) => arc.0.variants(),
153 }
154 }
155
156 pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ {
158 let mut set = BTreeSet::new();
159 let modifiers = match &self.0 {
160 Repr::Modified(arc) => arc.1.as_str(),
161 _ => "",
162 };
163 for modifier in self.variants().flat_map(|(name, _)| name.split('.')) {
164 if !modifier.is_empty() && !contained(modifiers, modifier) {
165 set.insert(modifier);
166 }
167 }
168 set.into_iter()
169 }
170}
171
172#[scope]
173impl Symbol {
174 #[func(constructor)]
192 pub fn construct(
193 span: Span,
194 #[variadic]
202 variants: Vec<Spanned<SymbolVariant>>,
203 ) -> SourceResult<Symbol> {
204 if variants.is_empty() {
205 bail!(span, "expected at least one variant");
206 }
207
208 let mut seen = HashMap::<u128, usize>::new();
211
212 let mut modifiers = Vec::new();
214
215 for (i, &Spanned { ref v, span }) in variants.iter().enumerate() {
217 modifiers.clear();
218
219 if !v.0.is_empty() {
220 for modifier in v.0.split('.') {
222 if !is_ident(modifier) {
223 bail!(span, "invalid symbol modifier: {}", modifier.repr());
224 }
225 modifiers.push(modifier);
226 }
227 }
228
229 modifiers.sort();
231
232 if let Some(ms) = modifiers.windows(2).find(|ms| ms[0] == ms[1]) {
234 bail!(
235 span, "duplicate modifier within variant: {}", ms[0].repr();
236 hint: "modifiers are not ordered, so each one may appear only once"
237 )
238 }
239
240 let hash = hash128(&modifiers);
242 if let Some(&i) = seen.get(&hash) {
243 if v.0.is_empty() {
244 bail!(span, "duplicate default variant");
245 } else if v.0 == variants[i].v.0 {
246 bail!(span, "duplicate variant: {}", v.0.repr());
247 } else {
248 bail!(
249 span, "duplicate variant: {}", v.0.repr();
250 hint: "variants with the same modifiers are identical, regardless of their order"
251 )
252 }
253 }
254
255 seen.insert(hash, i);
256 }
257
258 let list = variants.into_iter().map(|s| (s.v.0, s.v.1)).collect();
259 Ok(Symbol::runtime(list))
260 }
261}
262
263impl Display for Symbol {
264 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
265 f.write_char(self.get())
266 }
267}
268
269impl Debug for Repr {
270 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
271 match self {
272 Self::Single(c) => Debug::fmt(c, f),
273 Self::Complex(list) => list.fmt(f),
274 Self::Modified(lists) => lists.fmt(f),
275 }
276 }
277}
278
279impl Debug for List {
280 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
281 match self {
282 Self::Static(list) => list.fmt(f),
283 Self::Runtime(list) => list.fmt(f),
284 }
285 }
286}
287
288impl crate::foundations::Repr for Symbol {
289 fn repr(&self) -> EcoString {
290 match &self.0 {
291 Repr::Single(c) => eco_format!("symbol(\"{}\")", *c),
292 Repr::Complex(variants) => {
293 eco_format!("symbol{}", repr_variants(variants.iter().copied(), ""))
294 }
295 Repr::Modified(arc) => {
296 let (list, modifiers) = arc.as_ref();
297 if modifiers.is_empty() {
298 eco_format!("symbol{}", repr_variants(list.variants(), ""))
299 } else {
300 eco_format!("symbol{}", repr_variants(list.variants(), modifiers))
301 }
302 }
303 }
304 }
305}
306
307fn repr_variants<'a>(
308 variants: impl Iterator<Item = (&'a str, char)>,
309 applied_modifiers: &str,
310) -> String {
311 crate::foundations::repr::pretty_array_like(
312 &variants
313 .filter(|(variant, _)| {
314 parts(applied_modifiers).all(|am| variant.split('.').any(|m| m == am))
317 })
318 .map(|(variant, c)| {
319 let trimmed_variant = variant
320 .split('.')
321 .filter(|&m| parts(applied_modifiers).all(|am| m != am));
322 if trimmed_variant.clone().all(|m| m.is_empty()) {
323 eco_format!("\"{c}\"")
324 } else {
325 let trimmed_modifiers = trimmed_variant.collect::<Vec<_>>().join(".");
326 eco_format!("(\"{}\", \"{}\")", trimmed_modifiers, c)
327 }
328 })
329 .collect::<Vec<_>>(),
330 false,
331 )
332}
333
334impl Serialize for Symbol {
335 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
336 where
337 S: Serializer,
338 {
339 serializer.serialize_char(self.get())
340 }
341}
342
343impl List {
344 fn variants(&self) -> Variants<'_> {
346 match self {
347 List::Static(list) => Variants::Static(list.iter()),
348 List::Runtime(list) => Variants::Runtime(list.iter()),
349 }
350 }
351}
352
353pub struct SymbolVariant(EcoString, char);
355
356cast! {
357 SymbolVariant,
358 c: char => Self(EcoString::new(), c),
359 array: Array => {
360 let mut iter = array.into_iter();
361 match (iter.next(), iter.next(), iter.next()) {
362 (Some(a), Some(b), None) => Self(a.cast()?, b.cast()?),
363 _ => Err("variant array must contain exactly two entries")?,
364 }
365 },
366}
367
368enum Variants<'a> {
370 Single(std::option::IntoIter<char>),
371 Static(std::slice::Iter<'static, (&'static str, char)>),
372 Runtime(std::slice::Iter<'a, (EcoString, char)>),
373}
374
375impl<'a> Iterator for Variants<'a> {
376 type Item = (&'a str, char);
377
378 fn next(&mut self) -> Option<Self::Item> {
379 match self {
380 Self::Single(iter) => Some(("", iter.next()?)),
381 Self::Static(list) => list.next().copied(),
382 Self::Runtime(list) => list.next().map(|(s, c)| (s.as_str(), *c)),
383 }
384 }
385}
386
387fn find<'a>(
389 variants: impl Iterator<Item = (&'a str, char)>,
390 modifiers: &str,
391) -> Option<char> {
392 let mut best = None;
393 let mut best_score = None;
394
395 'outer: for candidate in variants {
397 for modifier in parts(modifiers) {
398 if !contained(candidate.0, modifier) {
399 continue 'outer;
400 }
401 }
402
403 let mut matching = 0;
404 let mut total = 0;
405 for modifier in parts(candidate.0) {
406 if contained(modifiers, modifier) {
407 matching += 1;
408 }
409 total += 1;
410 }
411
412 let score = (matching, Reverse(total));
413 if best_score.map_or(true, |b| score > b) {
414 best = Some(candidate.1);
415 best_score = Some(score);
416 }
417 }
418
419 best
420}
421
422fn parts(modifiers: &str) -> impl Iterator<Item = &str> {
424 modifiers.split('.').filter(|s| !s.is_empty())
425}
426
427fn contained(modifiers: &str, m: &str) -> bool {
429 parts(modifiers).any(|part| part == m)
430}
431
432#[elem(Repr, PlainText)]
434pub struct SymbolElem {
435 #[required]
437 pub text: char, }
439
440impl SymbolElem {
441 pub fn packed(text: impl Into<char>) -> Content {
443 Self::new(text.into()).pack()
444 }
445}
446
447impl PlainText for Packed<SymbolElem> {
448 fn plain_text(&self, text: &mut EcoString) {
449 text.push(self.text);
450 }
451}
452
453impl crate::foundations::Repr for SymbolElem {
454 fn repr(&self) -> EcoString {
456 eco_format!("[{}]", self.text)
457 }
458}