use crate::tree::Class;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Symbol {
pub symbol: char,
pub class: Class,
}
const fn s(symbol: char, class: Class) -> Option<Symbol> {
Some(Symbol { symbol, class })
}
pub fn character(c: char) -> Option<Symbol> {
use Class::*;
match c {
'+' | '±' | '∓' | '×' | '÷' | '·' | '∗' | '∘' | '∪' | '∩' | '⊕' | '⊗' | '∧' | '∨' => s(c, Bin),
'-' | '−' | '–' => s('−', Bin),
'*' => s('∗', Bin),
'/' => s('/', Ord),
'=' | '<' | '>' | ':' | '≤' | '≥' | '≠' | '≈' | '≡' | '∼' | '≅' | '∝' | '→' | '←' | '↔' | '⇒' | '⇐' | '⇔' | '∈' | '∉' | '⊂' | '⊃' | '⊆' | '⊇' | '⇌' | '↑' | '↓' | '⟶'
| '∥' | '⊥' | '≃' | '≪' | '≫' => s(c, Rel),
'(' | '[' | '⟨' => s(c, Open),
')' | ']' | '⟩' | '!' | '?' => s(c, Close),
',' | ';' => s(c, Punct),
'.' | '|' | '\'' | '°' | '%' | '∞' | '∅' | '∂' | '∇' | '∀' | '∃' | '¬' | '△' | '∠' | '…' | '⋯' | '"' | '@' | '`' | '™' | '€' | '£' | '§' | '¶' | '•' | '★' | '✓' | '✗' => {
s(c, Ord)
}
_ => None,
}
}
pub fn command(name: &str) -> Option<Symbol> {
use Class::*;
match name {
"cdot" => s('·', Bin),
"times" => s('×', Bin),
"div" => s('÷', Bin),
"pm" => s('±', Bin),
"mp" => s('∓', Bin),
"ast" => s('∗', Bin),
"star" => s('⋆', Bin),
"circ" => s('∘', Bin),
"bullet" => s('•', Bin),
"cup" => s('∪', Bin),
"cap" => s('∩', Bin),
"setminus" | "smallsetminus" => s('∖', Bin),
"oplus" => s('⊕', Bin),
"ominus" => s('⊖', Bin),
"otimes" => s('⊗', Bin),
"odot" => s('⊙', Bin),
"wedge" | "land" => s('∧', Bin),
"vee" | "lor" => s('∨', Bin),
"diamond" => s('⋄', Bin),
"bigcirc" => s('◯', Bin),
"triangleleft" => s('◁', Bin),
"triangleright" => s('▷', Bin),
"amalg" => s('⨿', Bin),
"uplus" => s('⊎', Bin),
"sqcup" => s('⊔', Bin),
"sqcap" => s('⊓', Bin),
"boxplus" => s('⊞', Bin),
"boxminus" => s('⊟', Bin),
"boxtimes" => s('⊠', Bin),
"boxdot" => s('⊡', Bin),
"circleddash" => s('⊝', Bin),
"circledast" => s('⊛', Bin),
"circledcirc" => s('⊚', Bin),
"bigtriangleup" => s('△', Bin),
"bigtriangledown" => s('▽', Bin),
"wr" => s('≀', Bin),
"intercal" => s('⊺', Bin),
"le" | "leq" => s('≤', Rel),
"ge" | "geq" => s('≥', Rel),
"leqslant" => s('⩽', Rel),
"geqslant" => s('⩾', Rel),
"ne" | "neq" => s('≠', Rel),
"approx" => s('≈', Rel),
"coloneqq" | "coloneq" => s('≔', Rel),
"eqqcolon" | "eqcolon" => s('≕', Rel),
"Coloneqq" => s('⩴', Rel),
"equiv" => s('≡', Rel),
"sim" => s('∼', Rel),
"simeq" => s('≃', Rel),
"cong" => s('≅', Rel),
"propto" => s('∝', Rel),
"ll" => s('≪', Rel),
"gg" => s('≫', Rel),
"doteq" => s('≐', Rel),
"in" => s('∈', Rel),
"notin" => s('∉', Rel),
"ni" | "owns" => s('∋', Rel),
"subset" => s('⊂', Rel),
"supset" => s('⊃', Rel),
"subseteq" => s('⊆', Rel),
"supseteq" => s('⊇', Rel),
"subsetneq" => s('⊊', Rel),
"not\\subset" | "nsubseteq" => s('⊈', Rel),
"perp" => s('⊥', Rel),
"parallel" => s('∥', Rel),
"nparallel" => s('∦', Rel),
"mid" => s('∣', Rel),
"nmid" => s('∤', Rel),
"models" => s('⊨', Rel),
"vdash" => s('⊢', Rel),
"asymp" => s('≍', Rel),
"prec" => s('≺', Rel),
"succ" => s('≻', Rel),
"triangleq" => s('≜', Rel),
"succeq" => s('⪰', Rel),
"preceq" => s('⪯', Rel),
"lesssim" => s('≲', Rel),
"gtrsim" => s('≳', Rel),
"lessgtr" => s('≶', Rel),
"vDash" => s('⊨', Rel),
"Vdash" => s('⊩', Rel),
"sqsubseteq" => s('⊑', Rel),
"sqsupseteq" => s('⊒', Rel),
"sqsubset" => s('⊏', Rel),
"sqsupset" => s('⊐', Rel),
"bowtie" => s('⋈', Rel),
"smile" => s('⌣', Rel),
"frown" => s('⌢', Rel),
"dashrightarrow" => s('⇢', Rel),
"dashleftarrow" => s('⇠', Rel),
"curvearrowright" => s('↷', Rel),
"curvearrowleft" => s('↶', Rel),
"hookleftarrow" => s('↩', Rel),
"twoheadrightarrow" => s('↠', Rel),
"twoheadleftarrow" => s('↞', Rel),
"rightrightarrows" => s('⇉', Rel),
"leftleftarrows" => s('⇇', Rel),
"circlearrowright" => s('↻', Rel),
"circlearrowleft" => s('↺', Rel),
"multimap" => s('⊸', Rel),
"lt" => s('<', Rel),
"gt" => s('>', Rel),
"to" | "rightarrow" => s('→', Rel),
"gets" | "leftarrow" => s('←', Rel),
"leftrightarrow" => s('↔', Rel),
"Rightarrow" | "implies" => s('⇒', Rel),
"Leftarrow" | "impliedby" => s('⇐', Rel),
"Leftrightarrow" | "iff" => s('⇔', Rel),
"longrightarrow" => s('⟶', Rel),
"longleftarrow" => s('⟵', Rel),
"longleftrightarrow" => s('⟷', Rel),
"Longrightarrow" => s('⟹', Rel),
"Longleftarrow" => s('⟸', Rel),
"Longleftrightarrow" => s('⟺', Rel),
"mapsto" => s('↦', Rel),
"longmapsto" => s('⟼', Rel),
"uparrow" => s('↑', Rel),
"downarrow" => s('↓', Rel),
"updownarrow" => s('↕', Rel),
"Uparrow" => s('⇑', Rel),
"Downarrow" => s('⇓', Rel),
"nearrow" => s('↗', Rel),
"searrow" => s('↘', Rel),
"nwarrow" => s('↖', Rel),
"swarrow" => s('↙', Rel),
"rightleftharpoons" => s('⇌', Rel),
"leftrightharpoons" => s('⇋', Rel),
"rightharpoonup" => s('⇀', Rel),
"leftharpoonup" => s('↼', Rel),
"rightleftarrows" => s('⇄', Rel),
"hookrightarrow" => s('↪', Rel),
"leadsto" | "rightsquigarrow" => s('⇝', Rel),
"colon" => s(':', Punct),
"lbrace" | "{" => s('{', Open),
"rbrace" | "}" => s('}', Close),
"langle" => s('⟨', Open),
"rangle" => s('⟩', Close),
"lfloor" => s('⌊', Open),
"rfloor" => s('⌋', Close),
"lceil" => s('⌈', Open),
"rceil" => s('⌉', Close),
"lvert" => s('|', Open),
"rvert" => s('|', Close),
"lVert" => s('‖', Open),
"rVert" => s('‖', Close),
"vert" => s('|', Ord),
"Vert" | "|" => s('‖', Ord),
"lbrack" => s('[', Open),
"llangle" => s('⟪', Open),
"rrangle" => s('⟫', Close),
"llbracket" | "lBrack" => s('⟦', Open),
"rrbracket" | "rBrack" => s('⟧', Close),
"rbrack" => s(']', Close),
"infty" => s('∞', Ord),
"partial" => s('∂', Ord),
"nabla" => s('∇', Ord),
"forall" => s('∀', Ord),
"exists" => s('∃', Ord),
"nexists" => s('∄', Ord),
"neg" | "lnot" => s('¬', Ord),
"emptyset" | "varnothing" => s('∅', Ord),
"angle" => s('∠', Ord),
"measuredangle" => s('∡', Ord),
"triangle" | "vartriangle" => s('△', Ord),
"square" | "Box" => s('□', Ord),
"blacksquare" => s('■', Ord),
"degree" => s('°', Ord),
"prime" => s('′', Ord),
"hbar" => s('ℏ', Ord),
"ell" => s('ℓ', Ord),
"wp" => s('℘', Ord),
"Re" => s('ℜ', Ord),
"Im" => s('ℑ', Ord),
"aleph" => s('ℵ', Ord),
"top" => s('⊤', Ord),
"bot" => s('⊥', Ord),
"dots" | "ldots" | "dotsc" | "dotso" => s('…', Inner),
"cdots" | "dotsb" | "dotsm" | "dotsi" => s('⋯', Inner),
"vdots" => s('⋮', Ord),
"ddots" => s('⋱', Inner),
"iddots" | "adots" => s('⋰', Inner),
"therefore" => s('∴', Rel),
"because" => s('∵', Rel),
"checkmark" => s('✓', Ord),
"S" => s('§', Ord),
"P" => s('¶', Ord),
"dag" | "dagger" => s('†', Bin),
"ddag" | "ddagger" => s('‡', Bin),
"clubsuit" => s('♣', Ord),
"diamondsuit" => s('♢', Ord),
"heartsuit" => s('♡', Ord),
"spadesuit" => s('♠', Ord),
"flat" => s('♭', Ord),
"sharp" => s('♯', Ord),
"natural" => s('♮', Ord),
"%" => s('%', Ord),
"$" => s('$', Ord),
"&" => s('&', Ord),
"#" => s('#', Ord),
"_" => s('_', Ord),
"textbackslash" | "backslash" => s('\\', Ord),
"textdegree" | "textdegreeC" => s('°', Ord),
"celsius" | "degreeCelsius" => s('℃', Ord),
"ohm" | "Omegaup" => s('Ω', Ord),
"micro" => s('µ', Ord),
"permil" | "textperthousand" => s('‰', Ord),
"euro" => s('€', Ord),
"texteuro" => s('€', Ord),
"yen" => s('¥', Ord),
"textcent" => s('¢', Ord),
"pounds" | "textsterling" => s('£', Ord),
"copyright" | "textcopyright" => s('©', Ord),
"textregistered" => s('®', Ord),
"textmu" => s('µ', Ord),
"textless" => s('<', Rel),
"textgreater" => s('>', Rel),
"textepsilon" => s('ε', Ord),
"Game" => s('⅁', Ord),
"complement" => s('∁', Ord),
"gimel" => s('ℷ', Ord),
"beth" => s('ℶ', Ord),
"daleth" => s('ℸ', Ord),
"eth" => s('ð', Ord),
"mho" => s('℧', Ord),
"bigstar" => s('★', Ord),
"blacktriangle" => s('▲', Ord),
"blacktriangledown" => s('▼', Ord),
"blacktriangleright" => s('▶', Ord),
"blacktriangleleft" => s('◀', Ord),
"lozenge" => s('◊', Ord),
"blacklozenge" => s('⧫', Ord),
"female" | "venus" => s('♀', Ord),
"male" | "mars" => s('♂', Ord),
"pentagon" => s('⬠', Ord),
"hexagon" => s('⬡', Ord),
"o" => s('ø', Ord),
"O" => s('Ø', Ord),
"AA" => s('Å', Ord),
"aa" => s('å', Ord),
"ss" => s('ß', Ord),
"l" => s('ł', Ord),
"smallint" => s('∫', Op),
_ => None,
}
}
pub fn greek(name: &str) -> Option<char> {
Some(match name {
"alpha" => 'α',
"beta" => 'β',
"gamma" => 'γ',
"delta" => 'δ',
"epsilon" => 'ϵ',
"varepsilon" => 'ε',
"zeta" => 'ζ',
"eta" => 'η',
"theta" => 'θ',
"vartheta" => 'ϑ',
"iota" => 'ι',
"kappa" => 'κ',
"lambda" => 'λ',
"mu" => 'μ',
"nu" => 'ν',
"xi" => 'ξ',
"omicron" => 'ο',
"pi" => 'π',
"varpi" => 'ϖ',
"rho" => 'ρ',
"varrho" => 'ϱ',
"sigma" => 'σ',
"varsigma" => 'ς',
"tau" => 'τ',
"upsilon" => 'υ',
"phi" => 'ϕ',
"varphi" => 'φ',
"chi" => 'χ',
"psi" => 'ψ',
"omega" => 'ω',
"Gamma" => 'Γ',
"Delta" => 'Δ',
"Theta" => 'Θ',
"Lambda" => 'Λ',
"Xi" => 'Ξ',
"Pi" => 'Π',
"Sigma" => 'Σ',
"Upsilon" => 'Υ',
"Phi" => 'Φ',
"Psi" => 'Ψ',
"Omega" => 'Ω',
"upmu" => 'μ',
"uppi" => 'π',
"upalpha" => 'α',
"upbeta" => 'β',
"updelta" => 'δ',
"upepsilon" => 'ε',
"uptheta" => 'θ',
"uplambda" => 'λ',
"upsigma" => 'σ',
"upomega" => 'ω',
"upphi" => 'φ',
"uprho" => 'ρ',
"uptau" => 'τ',
"upupsilon" => 'υ',
"upkappa" => 'κ',
"upeta" => 'η',
"upnu" => 'ν',
"upxi" => 'ξ',
"upchi" => 'χ',
"uppsi" => 'ψ',
"upzeta" => 'ζ',
"upiota" => 'ι',
"Nu" => 'Ν',
"Alpha" => 'Α',
"Beta" => 'Β',
"Epsilon" => 'Ε',
"Zeta" => 'Ζ',
"Eta" => 'Η',
"Iota" => 'Ι',
"Kappa" => 'Κ',
"Mu" => 'Μ',
"Omicron" => 'Ο',
"Rho" => 'Ρ',
"Tau" => 'Τ',
"Chi" => 'Χ',
"upgamma" => 'γ',
_ => return None,
})
}
pub fn greek_upright(name: &str) -> bool {
name.starts_with("up") || name.chars().next().is_some_and(|c| c.is_ascii_uppercase())
}
pub fn function(name: &str) -> Option<(&'static str, bool)> {
Some(match name {
"sin" | "sen" => ("sin", false),
"cos" => ("cos", false),
"tan" | "tg" => ("tan", false),
"cot" | "cotg" => ("cot", false),
"sec" => ("sec", false),
"csc" | "cossec" | "cosec" => ("csc", false),
"arcsin" | "arcsen" => ("arcsin", false),
"arccos" => ("arccos", false),
"arctan" | "arctg" => ("arctan", false),
"sinh" | "senh" => ("sinh", false),
"cosh" => ("cosh", false),
"tanh" | "tgh" => ("tanh", false),
"coth" => ("coth", false),
"csch" => ("csch", false),
"sech" => ("sech", false),
"arccot" => ("arccot", false),
"arcsec" => ("arcsec", false),
"arccsc" => ("arccsc", false),
"log" => ("log", false),
"lg" => ("lg", false),
"ln" => ("ln", false),
"exp" => ("exp", false),
"arg" => ("arg", false),
"deg" => ("deg", false),
"dim" => ("dim", false),
"hom" => ("hom", false),
"ker" => ("ker", false),
"det" => ("det", true),
"gcd" | "mdc" => ("gcd", true),
"lcm" | "mmc" => ("lcm", true),
"max" => ("max", true),
"min" => ("min", true),
"sup" => ("sup", true),
"inf" => ("inf", true),
"lim" => ("lim", true),
"limsup" => ("lim sup", true),
"liminf" => ("lim inf", true),
"Pr" => ("Pr", true),
_ => return None,
})
}
pub fn large_operator(name: &str) -> Option<(char, bool)> {
Some(match name {
"sum" => ('∑', true),
"prod" => ('∏', true),
"coprod" => ('∐', true),
"bigcup" => ('⋃', true),
"bigcap" => ('⋂', true),
"bigoplus" => ('⨁', true),
"bigotimes" => ('⨂', true),
"bigodot" => ('⨀', true),
"bigvee" => ('⋁', true),
"bigwedge" => ('⋀', true),
"bigsqcup" => ('⨆', true),
"biguplus" => ('⨄', true),
"int" => ('∫', false),
"iint" => ('∬', false),
"iiint" => ('∭', false),
"oint" => ('∮', false),
"oiint" => ('∯', false),
"oiiint" => ('∰', false),
_ => return None,
})
}
pub fn delimiter_command(name: &str) -> Option<Option<char>> {
Some(Some(match name {
"{" | "lbrace" => '{',
"}" | "rbrace" => '}',
"langle" => '⟨',
"rangle" => '⟩',
"lfloor" => '⌊',
"rfloor" => '⌋',
"lceil" => '⌈',
"rceil" => '⌉',
"vert" | "lvert" | "rvert" | "mid" => '|',
"|" | "Vert" | "lVert" | "rVert" => '‖',
"lbrack" => '[',
"rbrack" => ']',
"llbracket" | "lBrack" => '⟦',
"rrbracket" | "rBrack" => '⟧',
"uparrow" => '↑',
"downarrow" => '↓',
"updownarrow" => '↕',
"backslash" => '\\',
_ => return None,
}))
}
pub fn delimiter_char(c: char) -> Option<Option<char>> {
match c {
'.' => Some(None),
'(' | ')' | '[' | ']' | '|' | '/' | '<' | '>' | '⟨' | '⟩' | '‖' => Some(Some(match c {
'<' => '⟨',
'>' => '⟩',
other => other,
})),
_ => None,
}
}
pub fn accent(name: &str) -> Option<(char, bool)> {
Some(match name {
"hat" => ('\u{0302}', false),
"widehat" => ('\u{0302}', true),
"check" => ('\u{030C}', false),
"widecheck" => ('\u{030C}', true),
"tilde" => ('\u{0303}', false),
"widetilde" => ('\u{0303}', true),
"bar" => ('\u{0304}', false),
"acute" => ('\u{0301}', false),
"grave" => ('\u{0300}', false),
"breve" => ('\u{0306}', false),
"dot" => ('\u{0307}', false),
"ddot" => ('\u{0308}', false),
"dddot" => ('\u{20DB}', false),
"ddddot" => ('\u{20DC}', false),
"mathring" => ('\u{030A}', false),
"Tilde" => ('\u{0303}', false),
"Hat" => ('\u{0302}', false),
"Bar" => ('\u{0304}', false),
"Vec" => ('\u{20D7}', false),
"Dot" => ('\u{0307}', false),
"Ddot" => ('\u{0308}', false),
"vec" => ('\u{20D7}', false),
"overrightarrow" => ('→', true),
"overleftarrow" => ('←', true),
"overleftrightarrow" => ('↔', true),
"overarc" | "wideparen" | "overparen" => ('⏜', true),
"underparen" => ('⏝', true),
_ => return None,
})
}
pub fn text_accent(mark: char, letter: char) -> Option<char> {
let combining = text_combining(mark)?;
let base = if letter == 'i' { 'ı' } else { letter };
compose(base, combining).or_else(|| compose(letter, combining))
}
pub fn text_combining(mark: char) -> Option<char> {
Some(match mark {
'\'' => '\u{0301}',
'`' => '\u{0300}',
'^' => '\u{0302}',
'~' => '\u{0303}',
'"' => '\u{0308}',
'c' => '\u{0327}',
'=' => '\u{0304}',
'.' => '\u{0307}',
'u' => '\u{0306}',
'v' => '\u{030C}',
_ => return None,
})
}
fn compose(base: char, combining: char) -> Option<char> {
const TABLE: &[(char, char, char)] = &[
('a', '\u{0301}', 'á'),
('e', '\u{0301}', 'é'),
('ı', '\u{0301}', 'í'),
('i', '\u{0301}', 'í'),
('o', '\u{0301}', 'ó'),
('u', '\u{0301}', 'ú'),
('A', '\u{0301}', 'Á'),
('E', '\u{0301}', 'É'),
('I', '\u{0301}', 'Í'),
('O', '\u{0301}', 'Ó'),
('U', '\u{0301}', 'Ú'),
('a', '\u{0300}', 'à'),
('A', '\u{0300}', 'À'),
('a', '\u{0302}', 'â'),
('e', '\u{0302}', 'ê'),
('o', '\u{0302}', 'ô'),
('A', '\u{0302}', 'Â'),
('E', '\u{0302}', 'Ê'),
('O', '\u{0302}', 'Ô'),
('a', '\u{0303}', 'ã'),
('o', '\u{0303}', 'õ'),
('n', '\u{0303}', 'ñ'),
('A', '\u{0303}', 'Ã'),
('O', '\u{0303}', 'Õ'),
('u', '\u{0308}', 'ü'),
('o', '\u{0308}', 'ö'),
('a', '\u{0308}', 'ä'),
('U', '\u{0308}', 'Ü'),
('c', '\u{0327}', 'ç'),
('C', '\u{0327}', 'Ç'),
('e', '\u{0300}', 'è'),
('ı', '\u{0300}', 'ì'),
('o', '\u{0300}', 'ò'),
('u', '\u{0300}', 'ù'),
('ı', '\u{0302}', 'î'),
('u', '\u{0302}', 'û'),
('e', '\u{0308}', 'ë'),
('ı', '\u{0308}', 'ï'),
('a', '\u{0304}', 'ā'),
('e', '\u{0304}', 'ē'),
('ı', '\u{0304}', 'ī'),
('o', '\u{0304}', 'ō'),
('u', '\u{0304}', 'ū'),
('z', '\u{0307}', 'ż'),
('e', '\u{0307}', 'ė'),
('a', '\u{0306}', 'ă'),
('g', '\u{0306}', 'ğ'),
('s', '\u{030C}', 'š'),
('c', '\u{030C}', 'č'),
('z', '\u{030C}', 'ž'),
('e', '\u{030C}', 'ě'),
('r', '\u{030C}', 'ř'),
('n', '\u{030C}', 'ň'),
('S', '\u{030C}', 'Š'),
('C', '\u{030C}', 'Č'),
];
TABLE.iter().find(|(b, c, _)| *b == base && *c == combining).map(|(_, _, r)| *r)
}
pub fn unit(name: &str) -> Option<&'static str> {
Some(match name {
"meter" | "metre" => "m",
"second" => "s",
"gram" => "g",
"kilogram" => "kg",
"ampere" => "A",
"kelvin" => "K",
"mole" => "mol",
"candela" => "cd",
"newton" => "N",
"joule" => "J",
"watt" => "W",
"pascal" => "Pa",
"hertz" => "Hz",
"coulomb" => "C",
"volt" => "V",
"ohm" => "Ω",
"farad" => "F",
"tesla" => "T",
"weber" => "Wb",
"henry" => "H",
"siemens" => "S",
"liter" | "litre" => "L",
"hour" => "h",
"minute" => "min",
"day" => "d",
"degreeCelsius" | "celsius" => "°C",
"degree" => "°",
"electronvolt" => "eV",
"calorie" => "cal",
"bar" => "bar",
"atm" | "atmosphere" => "atm",
"percent" => "%",
"mmHg" => "mmHg",
"hectare" => "ha",
"tonne" => "t",
_ => return None,
})
}
pub fn prefix(name: &str) -> Option<&'static str> {
Some(match name {
"tera" => "T",
"giga" => "G",
"mega" => "M",
"kilo" => "k",
"hecto" => "h",
"deca" | "deka" => "da",
"deci" => "d",
"centi" => "c",
"milli" => "m",
"micro" => "µ",
"nano" => "n",
"pico" => "p",
"femto" => "f",
_ => return None,
})
}