mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Make symbols callable like functions & migrate callable accents to callable symbols (#4299)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
parent
ad4ef68a11
commit
6f9855a8c3
@ -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: '⟻',
|
||||
/// ],
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
|
@ -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<TokenStream> {
|
||||
@ -12,12 +12,16 @@ pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
|
||||
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<TokenStream> {
|
||||
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<Variant, Token![,]>),
|
||||
}
|
||||
|
||||
struct Variant {
|
||||
name: String,
|
||||
c: syn::LitChar,
|
||||
handler: Handler,
|
||||
}
|
||||
|
||||
struct Handler(Option<Path>);
|
||||
|
||||
impl Parse for Symbol {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let name = input.call(Ident::parse_any)?;
|
||||
@ -55,9 +74,13 @@ impl Parse for Symbol {
|
||||
|
||||
impl Parse for Kind {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let handler = input.parse::<Handler>()?;
|
||||
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<Self> {
|
||||
let mut name = String::new();
|
||||
let handler = input.parse::<Handler>()?;
|
||||
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::<Token![:]>()?;
|
||||
}
|
||||
let c = input.parse()?;
|
||||
Ok(Self { name, c })
|
||||
Ok(Self { name, c, handler })
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Handler {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
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::<Path>() {
|
||||
return Some(Self(Some(path)));
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.unwrap_or(Self(None));
|
||||
Ok(handler)
|
||||
}
|
||||
}
|
||||
|
@ -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::<Func>().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::<Content>()?.into_iter().enumerate() {
|
||||
if i > 0 {
|
||||
@ -163,11 +148,10 @@ impl Eval for ast::FuncCall<'_> {
|
||||
));
|
||||
}
|
||||
|
||||
let callee = callee.cast::<Func>().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)
|
||||
};
|
||||
|
||||
|
@ -107,7 +107,7 @@ impl Eval for ast::Escape<'_> {
|
||||
type Output = Value;
|
||||
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
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<Self::Output> {
|
||||
Ok(Value::Symbol(Symbol::single(self.get())))
|
||||
Ok(Value::Symbol(Symbol::single(self.get().into())))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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 }
|
||||
|
@ -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<char> {
|
||||
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<Smart<Rel<Length>>>,
|
||||
) -> 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(),
|
||||
|
@ -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::*;
|
||||
|
@ -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: '↽',
|
||||
|
@ -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<fn() -> 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<Func> {
|
||||
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<Self> {
|
||||
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<Item = (&str, char)> {
|
||||
pub fn variants(&self) -> impl Iterator<Item = (&str, SymChar)> {
|
||||
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<char> {
|
||||
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<Func> {
|
||||
self.1.map(|f| f())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<char> 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<char>),
|
||||
Static(std::slice::Iter<'static, (&'static str, char)>),
|
||||
Runtime(std::slice::Iter<'a, (EcoString, char)>),
|
||||
Single(std::option::IntoIter<SymChar>),
|
||||
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<Self::Item> {
|
||||
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<Item = (&'a str, char)>,
|
||||
variants: impl Iterator<Item = (&'a str, SymChar)>,
|
||||
modifiers: &str,
|
||||
) -> Option<char> {
|
||||
) -> Option<SymChar> {
|
||||
let mut best = None;
|
||||
let mut best_score = None;
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user