From 6f9855a8c39bea36a06496a34a9083a12c93308b Mon Sep 17 00:00:00 2001 From: Yip Coekjan <69834864+Coekjan@users.noreply.github.com> Date: Fri, 14 Jun 2024 00:57:34 +0800 Subject: [PATCH] Make symbols callable like functions & migrate callable accents to callable symbols (#4299) Co-authored-by: Laurenz --- crates/typst-macros/src/lib.rs | 34 ++++++-- crates/typst-macros/src/symbols.rs | 56 ++++++++++-- crates/typst/src/eval/call.rs | 30 ++----- crates/typst/src/eval/markup.rs | 4 +- crates/typst/src/foundations/selector.rs | 2 +- crates/typst/src/foundations/value.rs | 3 +- crates/typst/src/math/accent.rs | 76 +++++++++++++++- crates/typst/src/math/mod.rs | 6 +- crates/typst/src/symbols/sym.rs | 41 +++++---- crates/typst/src/symbols/symbol.rs | 105 +++++++++++++---------- docs/src/lib.rs | 6 +- 11 files changed, 252 insertions(+), 111 deletions(-) diff --git a/crates/typst-macros/src/lib.rs b/crates/typst-macros/src/lib.rs index c37af237f..acc5e6034 100644 --- a/crates/typst-macros/src/lib.rs +++ b/crates/typst-macros/src/lib.rs @@ -340,17 +340,37 @@ pub fn derive_cast(item: BoundaryStream) -> BoundaryStream { /// Defines a list of `Symbol`s. /// +/// The `#[call(path)]` attribute can be used to specify a function to call when +/// the symbol is invoked. The function must be `NativeFunc`. +/// /// ```ignore /// const EMOJI: &[(&str, Symbol)] = symbols! { -/// // A plain symbol without modifiers. -/// abacus: '🧮', +/// // A plain symbol without modifiers. +/// abacus: '🧮', /// -/// // A symbol with a modifierless default and one modifier. -/// alien: ['👽', monster: '👾'], +/// // A symbol with a modifierless default and one modifier. +/// alien: ['👽', monster: '👾'], /// -/// // A symbol where each variant has a modifier. The first one will be -/// // the default. -/// clock: [one: '🕐', two: '🕑', ...], +/// // A symbol where each variant has a modifier. The first one will be +/// // the default. +/// clock: [one: '🕐', two: '🕑', ...], +/// +/// // A callable symbol without modifiers. +/// breve: #[call(crate::math::breve)] '˘', +/// +/// // A callable symbol with a modifierless default and one modifier. +/// acute: [ +/// #[call(crate::math::acute)] '´', +/// double: '˝', +/// ], +/// +/// // A callable symbol where each variant has a modifier. +/// arrow: [ +/// #[call(crate::math::arrow)] r: '→', +/// r.long.bar: '⟼', +/// #[call(crate::math::arrow_l)] l: '←', +/// l.long.bar: '⟻', +/// ], /// } /// ``` /// diff --git a/crates/typst-macros/src/symbols.rs b/crates/typst-macros/src/symbols.rs index 2ddb922fa..6b35f87f6 100644 --- a/crates/typst-macros/src/symbols.rs +++ b/crates/typst-macros/src/symbols.rs @@ -3,7 +3,7 @@ use quote::quote; use syn::ext::IdentExt; use syn::parse::{Parse, ParseStream, Parser}; use syn::punctuated::Punctuated; -use syn::{Ident, Result, Token}; +use syn::{Ident, LitChar, Path, Result, Token}; /// Expand the `symbols!` macro. pub fn symbols(stream: TokenStream) -> Result { @@ -12,12 +12,16 @@ pub fn symbols(stream: TokenStream) -> Result { let pairs = list.iter().map(|symbol| { let name = symbol.name.to_string(); let kind = match &symbol.kind { - Kind::Single(c) => quote! { ::typst::symbols::Symbol::single(#c), }, + Kind::Single(c, h) => { + let symbol = construct_sym_char(c, h); + quote! { ::typst::symbols::Symbol::single(#symbol), } + } Kind::Multiple(variants) => { let variants = variants.iter().map(|variant| { let name = &variant.name; let c = &variant.c; - quote! { (#name, #c) } + let symbol = construct_sym_char(c, &variant.handler); + quote! { (#name, #symbol) } }); quote! { ::typst::symbols::Symbol::list(&[#(#variants),*]) @@ -29,21 +33,36 @@ pub fn symbols(stream: TokenStream) -> Result { Ok(quote! { &[#(#pairs),*] }) } +fn construct_sym_char(ch: &LitChar, handler: &Handler) -> TokenStream { + match &handler.0 { + None => quote! { ::typst::symbols::SymChar::pure(#ch), }, + Some(path) => quote! { + ::typst::symbols::SymChar::with_func( + #ch, + <#path as ::typst::foundations::NativeFunc>::func, + ), + }, + } +} + struct Symbol { name: syn::Ident, kind: Kind, } enum Kind { - Single(syn::LitChar), + Single(syn::LitChar, Handler), Multiple(Punctuated), } struct Variant { name: String, c: syn::LitChar, + handler: Handler, } +struct Handler(Option); + impl Parse for Symbol { fn parse(input: ParseStream) -> Result { let name = input.call(Ident::parse_any)?; @@ -55,9 +74,13 @@ impl Parse for Symbol { impl Parse for Kind { fn parse(input: ParseStream) -> Result { + let handler = input.parse::()?; if input.peek(syn::LitChar) { - Ok(Self::Single(input.parse()?)) + Ok(Self::Single(input.parse()?, handler)) } else { + if handler.0.is_some() { + return Err(input.error("unexpected handler")); + } let content; syn::bracketed!(content in input); Ok(Self::Multiple(Punctuated::parse_terminated(&content)?)) @@ -68,6 +91,7 @@ impl Parse for Kind { impl Parse for Variant { fn parse(input: ParseStream) -> Result { let mut name = String::new(); + let handler = input.parse::()?; if input.peek(syn::Ident::peek_any) { name.push_str(&input.call(Ident::parse_any)?.to_string()); while input.peek(Token![.]) { @@ -78,6 +102,26 @@ impl Parse for Variant { input.parse::()?; } let c = input.parse()?; - Ok(Self { name, c }) + Ok(Self { name, c, handler }) + } +} + +impl Parse for Handler { + fn parse(input: ParseStream) -> Result { + let Ok(attrs) = input.call(syn::Attribute::parse_outer) else { + return Ok(Self(None)); + }; + let handler = attrs + .iter() + .find_map(|attr| { + if attr.path().is_ident("call") { + if let Ok(path) = attr.parse_args::() { + return Some(Self(Some(path))); + } + } + None + }) + .unwrap_or(Self(None)); + Ok(handler) } } diff --git a/crates/typst/src/eval/call.rs b/crates/typst/src/eval/call.rs index 278c3afb6..12d024ad6 100644 --- a/crates/typst/src/eval/call.rs +++ b/crates/typst/src/eval/call.rs @@ -9,8 +9,7 @@ use crate::foundations::{ Context, Func, IntoValue, NativeElement, Scope, Scopes, Value, }; use crate::introspection::Introspector; -use crate::math::{Accent, AccentElem, LrElem}; -use crate::symbols::Symbol; +use crate::math::LrElem; use crate::syntax::ast::{self, AstNode}; use crate::syntax::{Span, Spanned, SyntaxNode}; use crate::text::TextElem; @@ -129,23 +128,9 @@ impl Eval for ast::FuncCall<'_> { (callee.eval(vm)?, args.eval(vm)?.spanned(span)) }; - // Handle math special cases for non-functions: - // Combining accent symbols apply themselves while everything else - // simply displays the arguments verbatim. - if in_math && !matches!(callee, Value::Func(_)) { - if let Value::Symbol(sym) = &callee { - let c = sym.get(); - if let Some(accent) = Symbol::combining_accent(c) { - let base = args.expect("base")?; - let size = args.named("size")?; - args.finish()?; - let mut accent = AccentElem::new(base, Accent::new(accent)); - if let Some(size) = size { - accent = accent.with_size(size); - } - return Ok(Value::Content(accent.pack())); - } - } + let func_result = callee.clone().cast::().at(callee_span); + if in_math && func_result.is_err() { + // For non-functions in math, we wrap the arguments in parentheses. let mut body = Content::empty(); for (i, arg) in args.all::()?.into_iter().enumerate() { if i > 0 { @@ -163,11 +148,10 @@ impl Eval for ast::FuncCall<'_> { )); } - let callee = callee.cast::().at(callee_span)?; - let point = || Tracepoint::Call(callee.name().map(Into::into)); + let func = func_result?; + let point = || Tracepoint::Call(func.name().map(Into::into)); let f = || { - callee - .call(&mut vm.engine, vm.context, args) + func.call(&mut vm.engine, vm.context, args) .trace(vm.world(), point, span) }; diff --git a/crates/typst/src/eval/markup.rs b/crates/typst/src/eval/markup.rs index e38f137df..b44c97897 100644 --- a/crates/typst/src/eval/markup.rs +++ b/crates/typst/src/eval/markup.rs @@ -107,7 +107,7 @@ impl Eval for ast::Escape<'_> { type Output = Value; fn eval(self, _: &mut Vm) -> SourceResult { - Ok(Value::Symbol(Symbol::single(self.get()))) + Ok(Value::Symbol(Symbol::single(self.get().into()))) } } @@ -115,7 +115,7 @@ impl Eval for ast::Shorthand<'_> { type Output = Value; fn eval(self, _: &mut Vm) -> SourceResult { - Ok(Value::Symbol(Symbol::single(self.get()))) + Ok(Value::Symbol(Symbol::single(self.get().into()))) } } diff --git a/crates/typst/src/foundations/selector.rs b/crates/typst/src/foundations/selector.rs index 1aee961b3..6802a1502 100644 --- a/crates/typst/src/foundations/selector.rs +++ b/crates/typst/src/foundations/selector.rs @@ -277,12 +277,12 @@ impl Repr for Selector { cast! { type Selector, + text: EcoString => Self::text(&text)?, func: Func => func .element() .ok_or("only element functions can be used as selectors")? .select(), label: Label => Self::Label(label), - text: EcoString => Self::text(&text)?, regex: Regex => Self::regex(regex)?, location: Location => Self::Location(location), } diff --git a/crates/typst/src/foundations/value.rs b/crates/typst/src/foundations/value.rs index 47ce5d5dc..05abbfba1 100644 --- a/crates/typst/src/foundations/value.rs +++ b/crates/typst/src/foundations/value.rs @@ -658,7 +658,8 @@ primitive! { Dict: "dictionary", Dict } primitive! { Func: "function", Func, - Type(ty) => ty.constructor()?.clone() + Type(ty) => ty.constructor()?.clone(), + Symbol(symbol) => symbol.func()? } primitive! { Args: "arguments", Args } primitive! { Type: "type", Type } diff --git a/crates/typst/src/math/accent.rs b/crates/typst/src/math/accent.rs index 9a12f6d53..dbc0fad67 100644 --- a/crates/typst/src/math/accent.rs +++ b/crates/typst/src/math/accent.rs @@ -1,18 +1,64 @@ use crate::diag::{bail, SourceResult}; use crate::foundations::{ - cast, elem, Content, Packed, Resolve, Smart, StyleChain, Value, + cast, elem, func, Content, NativeElement, Packed, Resolve, Smart, StyleChain, Value, }; use crate::layout::{Em, Frame, Length, Point, Rel, Size}; use crate::math::{ style_cramped, FrameFragment, GlyphFragment, LayoutMath, MathContext, MathFragment, Scaled, }; -use crate::symbols::Symbol; use crate::text::TextElem; /// How much the accent can be shorter than the base. const ACCENT_SHORT_FALL: Em = Em::new(0.5); +/// This macro generates accent-related functions. +/// +/// ```ignore +/// accents! { +/// '\u{0300}' | '`' => grave, +/// // ^^^^^^^^^ ^^^ ^^^^^ +/// // | | | +/// // | | +-- The name of the function. +/// // | +--------- The alternative characters that represent the accent. +/// // +---------------------- The primary character that represents the accent. +/// } +/// ``` +/// +/// When combined with the `Accent::combine` function, accent characters can be normalized +/// to the primary character. +macro_rules! accents { + ($($primary:literal $(| $alt:literal)* => $name:ident),* $(,)?) => { + impl Accent { + /// Normalize an accent to a combining one. + pub fn combine(c: char) -> Option { + Some(match c { + $($primary $(| $alt)* => $primary,)* + _ => return None, + }) + } + } + + $( + /// The accent function for callable symbol definitions. + #[func] + pub fn $name( + /// The base to which the accent is applied. + base: Content, + /// The size of the accent, relative to the width of the base. + #[named] + size: Option>>, + ) -> Content { + let mut accent = AccentElem::new(base, Accent::new($primary)); + if let Some(size) = size { + accent = accent.with_size(size); + } + accent.pack() + } + )+ + }; +} + /// Attaches an accent to a base. /// /// # Example @@ -43,6 +89,7 @@ pub struct AccentElem { /// | Circumflex | `hat` | `^` | /// | Tilde | `tilde` | `~` | /// | Macron | `macron` | `¯` | + /// | Dash | `dash` | `‾` | /// | Breve | `breve` | `˘` | /// | Dot | `dot` | `.` | /// | Double dot, Diaeresis | `dot.double`, `diaer` | `¨` | @@ -130,10 +177,33 @@ pub struct Accent(char); impl Accent { /// Normalize a character into an accent. pub fn new(c: char) -> Self { - Self(Symbol::combining_accent(c).unwrap_or(c)) + Self(Self::combine(c).unwrap_or(c)) } } +// Keep it synced with the documenting table above. +accents! { + '\u{0300}' | '`' => grave, + '\u{0301}' | '´' => acute, + '\u{0302}' | '^' | 'ˆ' => hat, + '\u{0303}' | '~' | '∼' | '˜' => tilde, + '\u{0304}' | '¯' => macron, + '\u{0305}' | '-' | '‾' | '−' => dash, + '\u{0306}' | '˘' => breve, + '\u{0307}' | '.' | '˙' | '⋅' => dot, + '\u{0308}' | '¨' => dot_double, + '\u{20db}' => dot_triple, + '\u{20dc}' => dot_quad, + '\u{030a}' | '∘' | '○' => circle, + '\u{030b}' | '˝' => acute_double, + '\u{030c}' | 'ˇ' => caron, + '\u{20d6}' | '←' => arrow_l, + '\u{20d7}' | '→' | '⟶' => arrow, + '\u{20e1}' | '↔' | '⟷' => arrow_l_r, + '\u{20d0}' | '↼' => harpoon_lt, + '\u{20d1}' | '⇀' => harpoon, +} + cast! { Accent, self => self.0.into_value(), diff --git a/crates/typst/src/math/mod.rs b/crates/typst/src/math/mod.rs index 6ef3df9a2..4b5ce56b4 100644 --- a/crates/typst/src/math/mod.rs +++ b/crates/typst/src/math/mod.rs @@ -2,7 +2,9 @@ #[macro_use] mod ctx; -mod accent; + +pub mod accent; + mod align; mod attach; mod cancel; @@ -21,7 +23,7 @@ mod stretch; mod style; mod underover; -pub use self::accent::*; +pub use self::accent::{Accent, AccentElem}; pub use self::align::*; pub use self::attach::*; pub use self::cancel::*; diff --git a/crates/typst/src/symbols/sym.rs b/crates/typst/src/symbols/sym.rs index b3c90d5ba..d09cb4c21 100644 --- a/crates/typst/src/symbols/sym.rs +++ b/crates/typst/src/symbols/sym.rs @@ -84,7 +84,7 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! { comma: ',', dagger: ['†', double: '‡'], dash: [ - en: '–', + #[call(crate::math::accent::dash)] en: '–', em: '—', fig: '‒', wave: '〜', @@ -93,15 +93,15 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! { wave.double: '〰', ], dot: [ - op: '⋅', + #[call(crate::math::accent::dot)] op: '⋅', basic: '.', c: '·', circle: '⊙', circle.big: '⨀', square: '⊡', - double: '¨', - triple: '\u{20db}', - quad: '\u{20dc}', + #[call(crate::math::accent::dot_double)] double: '¨', + #[call(crate::math::accent::dot_triple)] triple: '\u{20db}', + #[call(crate::math::accent::dot_quad)] quad: '\u{20dc}', ], excl: ['!', double: '‼', inv: '¡', quest: '⁉'], quest: ['?', double: '⁇', excl: '⁈', inv: '¿'], @@ -117,7 +117,7 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! { slash: ['/', double: '⫽', triple: '⫻', big: '\u{29f8}'], dots: [h.c: '⋯', h: '…', v: '⋮', down: '⋱', up: '⋰'], tilde: [ - op: '∼', + #[call(crate::math::accent::tilde)] op: '∼', basic: '~', dot: '⩪', eq: '≃', @@ -133,14 +133,17 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! { ], // Accents, quotes, and primes. - acute: ['´', double: '˝'], - breve: '˘', + acute: [ + #[call(crate::math::accent::acute)] '´', + #[call(crate::math::accent::acute_double)] double: '˝', + ], + breve: #[call(crate::math::accent::breve)] '˘', caret: '‸', - caron: 'ˇ', - hat: '^', - diaer: '¨', - grave: '`', - macron: '¯', + caron: #[call(crate::math::accent::caron)] 'ˇ', + hat: #[call(crate::math::accent::hat)] '^', + diaer: #[call(crate::math::accent::dot_double)] '¨', + grave: #[call(crate::math::accent::grave)] '`', + macron: #[call(crate::math::accent::macron)] '¯', quote: [ double: '"', single: '\'', @@ -487,7 +490,7 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! { // Shapes. bullet: '•', circle: [ - stroked: '○', + #[call(crate::math::accent::circle)] stroked: '○', stroked.tiny: '∘', stroked.small: '⚬', stroked.big: '◯', @@ -580,7 +583,7 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! { // Arrows, harpoons, and tacks. arrow: [ - r: '→', + #[call(crate::math::accent::arrow)] r: '→', r.long.bar: '⟼', r.bar: '↦', r.curve: '⤷', @@ -607,7 +610,7 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! { r.twohead.bar: '⤅', r.twohead: '↠', r.wave: '↝', - l: '←', + #[call(crate::math::accent::arrow_l)] l: '←', l.bar: '↤', l.curve: '⤶', l.dashed: '⇠', @@ -656,7 +659,7 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! { b.stroked: '⇩', b.triple: '⤋', b.twohead: '↡', - l.r: '↔', + #[call(crate::math::accent::arrow_l_r)] l.r: '↔', l.r.double: '⇔', l.r.double.long: '⟺', l.r.double.not: '⇎', @@ -715,13 +718,13 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! { b: '⌄', ], harpoon: [ - rt: '⇀', + #[call(crate::math::accent::harpoon)] rt: '⇀', rt.bar: '⥛', rt.stop: '⥓', rb: '⇁', rb.bar: '⥟', rb.stop: '⥗', - lt: '↼', + #[call(crate::math::accent::harpoon_lt)] lt: '↼', lt.bar: '⥚', lt.stop: '⥒', lb: '↽', diff --git a/crates/typst/src/symbols/symbol.rs b/crates/typst/src/symbols/symbol.rs index 73bdd286a..0be4adc48 100644 --- a/crates/typst/src/symbols/symbol.rs +++ b/crates/typst/src/symbols/symbol.rs @@ -7,7 +7,7 @@ use ecow::{eco_format, EcoString}; use serde::{Serialize, Serializer}; use crate::diag::{bail, SourceResult, StrResult}; -use crate::foundations::{cast, func, scope, ty, Array}; +use crate::foundations::{cast, func, scope, ty, Array, Func}; use crate::syntax::{Span, Spanned}; #[doc(inline)] @@ -46,43 +46,52 @@ pub use typst_macros::symbols; #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Symbol(Repr); +/// The character of a symbol, possibly with a function. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct SymChar(char, Option Func>); + /// The internal representation. #[derive(Clone, Eq, PartialEq, Hash)] enum Repr { - Single(char), - Const(&'static [(&'static str, char)]), + Single(SymChar), + Const(&'static [(&'static str, SymChar)]), Multi(Arc<(List, EcoString)>), } /// A collection of symbols. #[derive(Clone, Eq, PartialEq, Hash)] enum List { - Static(&'static [(&'static str, char)]), - Runtime(Box<[(EcoString, char)]>), + Static(&'static [(&'static str, SymChar)]), + Runtime(Box<[(EcoString, SymChar)]>), } impl Symbol { /// Create a new symbol from a single character. - pub const fn single(c: char) -> Self { + pub const fn single(c: SymChar) -> Self { Self(Repr::Single(c)) } /// Create a symbol with a static variant list. #[track_caller] - pub const fn list(list: &'static [(&'static str, char)]) -> Self { + pub const fn list(list: &'static [(&'static str, SymChar)]) -> Self { debug_assert!(!list.is_empty()); Self(Repr::Const(list)) } /// Create a symbol with a runtime variant list. #[track_caller] - pub fn runtime(list: Box<[(EcoString, char)]>) -> Self { + pub fn runtime(list: Box<[(EcoString, SymChar)]>) -> Self { debug_assert!(!list.is_empty()); Self(Repr::Multi(Arc::new((List::Runtime(list), EcoString::new())))) } - /// Get the symbol's text. + /// Get the symbol's char. pub fn get(&self) -> char { + self.sym().char() + } + + /// Resolve the symbol's `SymChar`. + pub fn sym(&self) -> SymChar { match &self.0 { Repr::Single(c) => *c, Repr::Const(_) => find(self.variants(), "").unwrap(), @@ -90,6 +99,13 @@ impl Symbol { } } + /// Try to get the function associated with the symbol, if any. + pub fn func(&self) -> StrResult { + self.sym() + .func() + .ok_or_else(|| eco_format!("symbol {self} is not callable")) + } + /// Apply a modifier to the symbol. pub fn modified(mut self, modifier: &str) -> StrResult { if let Repr::Const(list) = self.0 { @@ -111,7 +127,7 @@ impl Symbol { } /// The characters that are covered by this symbol. - pub fn variants(&self) -> impl Iterator { + pub fn variants(&self) -> impl Iterator { match &self.0 { Repr::Single(c) => Variants::Single(Some(*c).into_iter()), Repr::Const(list) => Variants::Static(list.iter()), @@ -133,33 +149,6 @@ impl Symbol { } set.into_iter() } - - /// Normalize an accent to a combining one. Keep it synced with the - /// documenting table in accent.rs AccentElem. - pub fn combining_accent(c: char) -> Option { - Some(match c { - '\u{0300}' | '`' => '\u{0300}', - '\u{0301}' | '´' => '\u{0301}', - '\u{0302}' | '^' | 'ˆ' => '\u{0302}', - '\u{0303}' | '~' | '∼' | '˜' => '\u{0303}', - '\u{0304}' | '¯' => '\u{0304}', - '\u{0305}' | '-' | '‾' | '−' => '\u{0305}', - '\u{0306}' | '˘' => '\u{0306}', - '\u{0307}' | '.' | '˙' | '⋅' => '\u{0307}', - '\u{0308}' | '¨' => '\u{0308}', - '\u{20db}' => '\u{20db}', - '\u{20dc}' => '\u{20dc}', - '\u{030a}' | '∘' | '○' => '\u{030a}', - '\u{030b}' | '˝' => '\u{030b}', - '\u{030c}' | 'ˇ' => '\u{030c}', - '\u{20d6}' | '←' => '\u{20d6}', - '\u{20d7}' | '→' | '⟶' => '\u{20d7}', - '\u{20e1}' | '↔' | '⟷' => '\u{20e1}', - '\u{20d0}' | '↼' => '\u{20d0}', - '\u{20d1}' | '⇀' => '\u{20d1}', - _ => return None, - }) - } } #[scope] @@ -203,7 +192,7 @@ impl Symbol { if list.iter().any(|(prev, _)| &v.0 == prev) { bail!(span, "duplicate variant"); } - list.push((v.0, v.1)); + list.push((v.0, SymChar::pure(v.1))); } Ok(Symbol::runtime(list.into_boxed_slice())) } @@ -215,6 +204,34 @@ impl Display for Symbol { } } +impl SymChar { + /// Create a symbol character without a function. + pub const fn pure(c: char) -> Self { + Self(c, None) + } + + /// Create a symbol character with a function. + pub const fn with_func(c: char, func: fn() -> Func) -> Self { + Self(c, Some(func)) + } + + /// Get the character of the symbol. + pub const fn char(&self) -> char { + self.0 + } + + /// Get the function associated with the symbol. + pub fn func(&self) -> Option { + self.1.map(|f| f()) + } +} + +impl From for SymChar { + fn from(c: char) -> Self { + SymChar(c, None) + } +} + impl Debug for Repr { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { @@ -276,13 +293,13 @@ cast! { /// Iterator over variants. enum Variants<'a> { - Single(std::option::IntoIter), - Static(std::slice::Iter<'static, (&'static str, char)>), - Runtime(std::slice::Iter<'a, (EcoString, char)>), + Single(std::option::IntoIter), + Static(std::slice::Iter<'static, (&'static str, SymChar)>), + Runtime(std::slice::Iter<'a, (EcoString, SymChar)>), } impl<'a> Iterator for Variants<'a> { - type Item = (&'a str, char); + type Item = (&'a str, SymChar); fn next(&mut self) -> Option { match self { @@ -295,9 +312,9 @@ impl<'a> Iterator for Variants<'a> { /// Find the best symbol from the list. fn find<'a>( - variants: impl Iterator, + variants: impl Iterator, modifiers: &str, -) -> Option { +) -> Option { let mut best = None; let mut best_score = None; diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 8af7dc0d3..231fe97cf 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -661,15 +661,15 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel { for (variant, c) in symbol.variants() { let shorthand = |list: &[(&'static str, char)]| { - list.iter().copied().find(|&(_, x)| x == c).map(|(s, _)| s) + list.iter().copied().find(|&(_, x)| x == c.char()).map(|(s, _)| s) }; list.push(SymbolModel { name: complete(variant), markup_shorthand: shorthand(typst::syntax::ast::Shorthand::MARKUP_LIST), math_shorthand: shorthand(typst::syntax::ast::Shorthand::MATH_LIST), - codepoint: c as u32, - accent: typst::symbols::Symbol::combining_accent(c).is_some(), + codepoint: c.char() as _, + accent: typst::math::Accent::combine(c.char()).is_some(), alternates: symbol .variants() .filter(|(other, _)| other != &variant)