1use std::collections::BTreeSet;
2use std::fmt::{self, Debug, Display, Formatter};
3use std::sync::Arc;
4
5use codex::ModifierSet;
6use ecow::{EcoString, eco_format};
7use rustc_hash::FxHashMap;
8use serde::{Serialize, Serializer};
9use typst_syntax::{Span, Spanned, is_ident};
10use typst_utils::hash128;
11use unicode_segmentation::UnicodeSegmentation;
12
13use crate::diag::{DeprecationSink, SourceResult, StrResult, bail, error};
14use crate::foundations::{
15 Array, Content, Func, NativeElement, NativeFunc, Packed, PlainText, Repr as _, cast,
16 elem, func, scope, ty,
17};
18
19#[ty(scope, cast)]
50#[derive(Debug, Clone, Eq, PartialEq, Hash)]
51pub struct Symbol(Repr);
52
53#[derive(Clone, Eq, PartialEq, Hash)]
55enum Repr {
56 Single(&'static str),
58 Complex(&'static [Variant<&'static str>]),
60 Modified(Arc<Modified>),
62}
63
64#[derive(Debug, Clone, Eq, PartialEq, Hash)]
68struct Modified {
69 list: List,
71 modifiers: ModifierSet<EcoString>,
73 deprecated: bool,
76}
77
78type Variant<S> = (ModifierSet<S>, S, Option<S>);
81
82#[derive(Clone, Eq, PartialEq, Hash)]
84enum List {
85 Static(&'static [Variant<&'static str>]),
86 Runtime(Box<[Variant<EcoString>]>),
87}
88
89impl Symbol {
90 pub const fn single(value: &'static str) -> Self {
92 Self(Repr::Single(value))
93 }
94
95 #[track_caller]
97 pub const fn list(list: &'static [Variant<&'static str>]) -> Self {
98 debug_assert!(!list.is_empty());
99 Self(Repr::Complex(list))
100 }
101
102 pub fn runtime_char(c: char) -> Self {
104 Self::runtime(Box::new([(ModifierSet::default(), c.into(), None)]))
105 }
106
107 #[track_caller]
109 pub fn runtime(list: Box<[Variant<EcoString>]>) -> Self {
110 debug_assert!(!list.is_empty());
111 Self(Repr::Modified(Arc::new(Modified {
112 list: List::Runtime(list),
113 modifiers: ModifierSet::default(),
114 deprecated: false,
115 })))
116 }
117
118 pub fn get(&self) -> &str {
120 match &self.0 {
121 Repr::Single(value) => value,
122 Repr::Complex(_) => ModifierSet::<&'static str>::default()
123 .best_match_in(self.variants().map(|(m, v, _)| (m, v)))
124 .unwrap(),
125 Repr::Modified(arc) => arc
126 .modifiers
127 .best_match_in(self.variants().map(|(m, v, _)| (m, v)))
128 .unwrap(),
129 }
130 }
131
132 pub fn func(&self) -> StrResult<Func> {
134 match self.get() {
135 "⌈" => Ok(crate::math::ceil::func()),
136 "⌊" => Ok(crate::math::floor::func()),
137 "–" => Ok(crate::math::accent::dash::func()),
138 "⋅" | "\u{0307}" => Ok(crate::math::accent::dot::func()),
139 "¨" => Ok(crate::math::accent::dot_double::func()),
140 "\u{20db}" => Ok(crate::math::accent::dot_triple::func()),
141 "\u{20dc}" => Ok(crate::math::accent::dot_quad::func()),
142 "∼" => Ok(crate::math::accent::tilde::func()),
143 "´" => Ok(crate::math::accent::acute::func()),
144 "˝" => Ok(crate::math::accent::acute_double::func()),
145 "˘" => Ok(crate::math::accent::breve::func()),
146 "ˇ" => Ok(crate::math::accent::caron::func()),
147 "^" => Ok(crate::math::accent::hat::func()),
148 "`" => Ok(crate::math::accent::grave::func()),
149 "¯" => Ok(crate::math::accent::macron::func()),
150 "○" => Ok(crate::math::accent::circle::func()),
151 "→" => Ok(crate::math::accent::arrow::func()),
152 "←" => Ok(crate::math::accent::arrow_l::func()),
153 "↔" => Ok(crate::math::accent::arrow_l_r::func()),
154 "⇀" => Ok(crate::math::accent::harpoon::func()),
155 "↼" => Ok(crate::math::accent::harpoon_lt::func()),
156 _ => bail!("symbol {self} is not callable"),
157 }
158 }
159
160 pub fn modified(
162 mut self,
163 sink: impl DeprecationSink,
164 modifier: &str,
165 ) -> StrResult<Self> {
166 if let Repr::Complex(list) = self.0 {
167 self.0 = Repr::Modified(Arc::new(Modified {
168 list: List::Static(list),
169 modifiers: ModifierSet::default(),
170 deprecated: false,
171 }));
172 }
173
174 if let Repr::Modified(arc) = &mut self.0 {
175 let modified = Arc::make_mut(arc);
176 modified.modifiers.insert_raw(modifier);
177 if let Some(deprecation) = modified
178 .modifiers
179 .best_match_in(modified.list.variants().map(|(m, _, d)| (m, d)))
180 {
181 if !modified.deprecated
184 && let Some(message) = deprecation
185 {
186 modified.deprecated = true;
187 sink.emit(message, None);
188 }
189 return Ok(self);
190 }
191 }
192
193 bail!("unknown symbol modifier")
194 }
195
196 pub fn variants(&self) -> impl Iterator<Item = Variant<&str>> {
198 match &self.0 {
199 Repr::Single(value) => Variants::Single(std::iter::once(*value)),
200 Repr::Complex(list) => Variants::Static(list.iter()),
201 Repr::Modified(arc) => arc.list.variants(),
202 }
203 }
204
205 pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ {
207 let modifiers = match &self.0 {
208 Repr::Modified(arc) => arc.modifiers.as_deref(),
209 _ => ModifierSet::default(),
210 };
211 self.variants()
212 .flat_map(|(m, _, _)| m)
213 .filter(|modifier| !modifier.is_empty() && !modifiers.contains(modifier))
214 .collect::<BTreeSet<_>>()
215 .into_iter()
216 }
217}
218
219#[scope]
220impl Symbol {
221 #[func(constructor)]
239 pub fn construct(
240 span: Span,
241 #[variadic]
249 variants: Vec<Spanned<SymbolVariant>>,
250 ) -> SourceResult<Symbol> {
251 if variants.is_empty() {
252 bail!(span, "expected at least one variant");
253 }
254
255 let mut seen = FxHashMap::<u128, usize>::default();
258
259 let mut modifiers = Vec::new();
261
262 let mut errors = ecow::eco_vec![];
263
264 'variants: for (i, &Spanned { ref v, span }) in variants.iter().enumerate() {
266 modifiers.clear();
267
268 if v.1.is_empty() || v.1.graphemes(true).nth(1).is_some() {
269 errors.push(error!(
270 span, "invalid variant value: {}", v.1.repr();
271 hint: "variant value must be exactly one grapheme cluster"
272 ));
273 }
274
275 if !v.0.is_empty() {
276 for modifier in v.0.split('.') {
278 if !is_ident(modifier) {
279 errors.push(error!(
280 span,
281 "invalid symbol modifier: {}",
282 modifier.repr()
283 ));
284 continue 'variants;
285 }
286 modifiers.push(modifier);
287 }
288 }
289
290 modifiers.sort();
292
293 if let Some(ms) = modifiers.windows(2).find(|ms| ms[0] == ms[1]) {
295 errors.push(error!(
296 span, "duplicate modifier within variant: {}", ms[0].repr();
297 hint: "modifiers are not ordered, so each one may appear only once"
298 ));
299 continue 'variants;
300 }
301
302 let hash = hash128(&modifiers);
304 if let Some(&i) = seen.get(&hash) {
305 errors.push(if v.0.is_empty() {
306 error!(span, "duplicate default variant")
307 } else if v.0 == variants[i].v.0 {
308 error!(span, "duplicate variant: {}", v.0.repr())
309 } else {
310 error!(
311 span, "duplicate variant: {}", v.0.repr();
312 hint: "variants with the same modifiers are identical, regardless of their order"
313 )
314 });
315 continue 'variants;
316 }
317
318 seen.insert(hash, i);
319 }
320 if !errors.is_empty() {
321 return Err(errors);
322 }
323
324 let list = variants
325 .into_iter()
326 .map(|s| (ModifierSet::from_raw_dotted(s.v.0), s.v.1, None))
327 .collect();
328 Ok(Symbol::runtime(list))
329 }
330}
331
332impl Display for Symbol {
333 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
334 f.write_str(self.get())
335 }
336}
337
338impl Debug for Repr {
339 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
340 match self {
341 Self::Single(value) => Debug::fmt(value, f),
342 Self::Complex(list) => list.fmt(f),
343 Self::Modified(lists) => lists.fmt(f),
344 }
345 }
346}
347
348impl Debug for List {
349 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
350 match self {
351 Self::Static(list) => list.fmt(f),
352 Self::Runtime(list) => list.fmt(f),
353 }
354 }
355}
356
357impl crate::foundations::Repr for Symbol {
358 fn repr(&self) -> EcoString {
359 match &self.0 {
360 Repr::Single(value) => eco_format!("symbol({})", value.repr()),
361 Repr::Complex(variants) => {
362 eco_format!(
363 "symbol{}",
364 repr_variants(variants.iter().copied(), ModifierSet::default())
365 )
366 }
367 Repr::Modified(arc) => {
368 let Modified { list, modifiers, .. } = arc.as_ref();
369 if modifiers.is_empty() {
370 eco_format!(
371 "symbol{}",
372 repr_variants(list.variants(), ModifierSet::default())
373 )
374 } else {
375 eco_format!(
376 "symbol{}",
377 repr_variants(list.variants(), modifiers.as_deref())
378 )
379 }
380 }
381 }
382 }
383}
384
385fn repr_variants<'a>(
386 variants: impl Iterator<Item = Variant<&'a str>>,
387 applied_modifiers: ModifierSet<&str>,
388) -> String {
389 crate::foundations::repr::pretty_array_like(
390 &variants
391 .filter(|(modifiers, _, _)| {
392 applied_modifiers.iter().all(|am| modifiers.contains(am))
395 })
396 .map(|(modifiers, value, _)| {
397 let trimmed_modifiers =
398 modifiers.into_iter().filter(|&m| !applied_modifiers.contains(m));
399 if trimmed_modifiers.clone().all(|m| m.is_empty()) {
400 value.repr()
401 } else {
402 let trimmed_modifiers =
403 trimmed_modifiers.collect::<Vec<_>>().join(".");
404 eco_format!("({}, {})", trimmed_modifiers.repr(), value.repr())
405 }
406 })
407 .collect::<Vec<_>>(),
408 false,
409 )
410}
411
412impl Serialize for Symbol {
413 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
414 where
415 S: Serializer,
416 {
417 serializer.serialize_str(self.get())
418 }
419}
420
421impl List {
422 fn variants(&self) -> Variants<'_> {
424 match self {
425 List::Static(list) => Variants::Static(list.iter()),
426 List::Runtime(list) => Variants::Runtime(list.iter()),
427 }
428 }
429}
430
431pub struct SymbolVariant(EcoString, EcoString);
433
434cast! {
435 SymbolVariant,
436 s: EcoString => Self(EcoString::new(), s),
437 array: Array => {
438 let mut iter = array.into_iter();
439 match (iter.next(), iter.next(), iter.next()) {
440 (Some(a), Some(b), None) => Self(a.cast()?, b.cast()?),
441 _ => Err("variant array must contain exactly two entries")?,
442 }
443 },
444}
445
446enum Variants<'a> {
448 Single(std::iter::Once<&'static str>),
449 Static(std::slice::Iter<'static, Variant<&'static str>>),
450 Runtime(std::slice::Iter<'a, Variant<EcoString>>),
451}
452
453impl<'a> Iterator for Variants<'a> {
454 type Item = Variant<&'a str>;
455
456 fn next(&mut self) -> Option<Self::Item> {
457 match self {
458 Self::Single(iter) => Some((ModifierSet::default(), iter.next()?, None)),
459 Self::Static(list) => list.next().copied(),
460 Self::Runtime(list) => {
461 list.next().map(|(m, s, d)| (m.as_deref(), s.as_str(), d.as_deref()))
462 }
463 }
464 }
465}
466
467#[elem(Repr, PlainText)]
469pub struct SymbolElem {
470 #[required]
472 pub text: EcoString, }
474
475impl SymbolElem {
476 pub fn packed(text: impl Into<EcoString>) -> Content {
478 Self::new(text.into()).pack()
479 }
480}
481
482impl PlainText for Packed<SymbolElem> {
483 fn plain_text(&self, text: &mut EcoString) {
484 text.push_str(&self.text);
485 }
486}
487
488impl crate::foundations::Repr for SymbolElem {
489 fn repr(&self) -> EcoString {
491 eco_format!("[{}]", self.text)
492 }
493}