mod types;
use crate::ParseError;
use crate::namespace::KeyMap;
use crate::types::ParseErrorKind;
use phf::phf_map;
pub use types::{Atom, CharInfo, Font, Group, Mode, NonAtom};
pub struct Symbols {
math: KeyMap<String, CharInfo>,
text: KeyMap<String, CharInfo>,
}
include!(concat!(env!("OUT_DIR"), "/generated_symbols_data.rs"));
impl Default for Symbols {
fn default() -> Self {
Self::new()
}
}
impl Symbols {
#[must_use]
pub fn new() -> Self {
Self {
math: KeyMap::default(),
text: KeyMap::default(),
}
}
pub fn define_symbol(
&mut self,
mode: Mode,
font: Font,
group: Group,
replace: Option<char>,
name: &str,
accept_unicode_char: bool,
) {
let char_info = CharInfo {
font,
group,
replace,
};
let table = match mode {
Mode::Math => &mut self.math,
Mode::Text => &mut self.text,
};
table.insert(name.to_owned(), char_info.clone());
if accept_unicode_char && let Some(s) = replace {
table.insert(s.to_string(), char_info);
}
}
#[must_use]
pub fn get_math(&self, name: &str) -> Option<&CharInfo> {
self.math
.get(name)
.or_else(|| POPULATE_MATH_SYMBOLS_MAP.get(name))
}
#[must_use]
pub fn get_text(&self, name: &str) -> Option<&CharInfo> {
self.text
.get(name)
.or_else(|| POPULATE_TEXT_SYMBOLS_MAP.get(name))
}
#[must_use]
pub fn get(&self, mode: Mode, name: &str) -> Option<&CharInfo> {
match mode {
Mode::Math => self.get_math(name),
Mode::Text => self.get_text(name),
}
}
#[must_use]
pub fn contains(&self, mode: Mode, name: &str) -> bool {
match mode {
Mode::Math => {
self.math.contains_key(name) || POPULATE_MATH_SYMBOLS_MAP.contains_key(name)
}
Mode::Text => {
self.text.contains_key(name) || POPULATE_TEXT_SYMBOLS_MAP.contains_key(name)
}
}
}
}
#[must_use]
pub fn is_ligature(s: &str) -> bool {
LIGATURES.contains_key(s)
}
#[must_use]
const fn create_wide_char(high: u16, low: u16) -> char {
let cp = 0x10000 + (((high as u32) - 0xD800) << 10) + ((low as u32) - 0xDC00);
unsafe { char::from_u32_unchecked(cp) }
}
pub const LIGATURES: phf::Map<&str, &str> = phf_map!(
"--" => "\u{2013}",
"---" => "\u{2014}",
"``" => "\u{201c}",
"''" => "\u{201d}",
);
impl TryFrom<&str> for Group {
type Error = ParseError;
fn try_from(s: &str) -> Result<Self, ParseError> {
match s {
"bin" => Ok(Self::Atom(Atom::Bin)),
"close" => Ok(Self::Atom(Atom::Close)),
"inner" => Ok(Self::Atom(Atom::Inner)),
"open" => Ok(Self::Atom(Atom::Open)),
"punct" => Ok(Self::Atom(Atom::Punct)),
"rel" => Ok(Self::Atom(Atom::Rel)),
"accent" | "accent-token" => Ok(Self::NonAtom(NonAtom::AccentToken)),
"mathord" => Ok(Self::NonAtom(NonAtom::MathOrd)),
"op" | "op-token" => Ok(Self::NonAtom(NonAtom::OpToken)),
"spacing" => Ok(Self::NonAtom(NonAtom::Spacing)),
"textord" => Ok(Self::NonAtom(NonAtom::TextOrd)),
_ => Err(ParseError::new(ParseErrorKind::InvalidGroup {
group: s.to_owned(),
})),
}
}
}
#[must_use]
pub fn create_symbols() -> Symbols {
let mut symbols = Symbols::new();
let math_text_symbols = "0123456789/@.\"";
for ch in math_text_symbols.chars() {
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&ch.to_string(),
false,
);
}
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::AccentToken),
Some('\u{00a8}'),
r#"\""#,
false,
);
let text_symbols = "0123456789!@*()-=+\";:?/.,";
for ch in text_symbols.chars() {
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&ch.to_string(),
false,
);
}
let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
for ch in letters.chars() {
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&ch.to_string(),
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&ch.to_string(),
false,
);
}
let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
for (i, ch) in letters.chars().enumerate() {
let wide_char = create_wide_char(0xD835, 0xDC00 + i as u16).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&wide_char,
false,
);
let wide_char = create_wide_char(0xD835, 0xDC34 + i as u16).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&wide_char,
false,
);
let wide_char = create_wide_char(0xD835, 0xDC68 + i as u16).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&wide_char,
false,
);
let wide_char = create_wide_char(0xD835, 0xDD04 + i as u16).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&wide_char,
false,
);
let wide_char = create_wide_char(0xD835, 0xDD6C + i as u16).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&wide_char,
false,
);
let wide_char = create_wide_char(0xD835, 0xDDA0 + i as u16).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&wide_char,
false,
);
let wide_char = create_wide_char(0xD835, 0xDDD4 + i as u16).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&wide_char,
false,
);
let wide_char = create_wide_char(0xD835, 0xDE08 + i as u16).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&wide_char,
false,
);
let wide_char = create_wide_char(0xD835, 0xDE70 + i as u16).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&wide_char,
false,
);
if i < 26 {
let wide_char = create_wide_char(0xD835, 0xDD38 + i as u16).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&wide_char,
false,
);
let wide_char = create_wide_char(0xD835, 0xDC9C + i as u16).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&wide_char,
false,
);
}
}
let wide_char = create_wide_char(0xD835, 0xDD5C).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some('k'),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some('k'),
&wide_char,
false,
);
for i in 0..10 {
let ch = char::from(b'0' + i);
let wide_char = create_wide_char(0xD835, 0xDFCE + u16::from(i)).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&wide_char,
false,
);
let wide_char = create_wide_char(0xD835, 0xDFE2 + u16::from(i)).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&wide_char,
false,
);
let wide_char = create_wide_char(0xD835, 0xDFEC + u16::from(i)).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&wide_char,
false,
);
let wide_char = create_wide_char(0xD835, 0xDFF6 + u16::from(i)).to_string();
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&wide_char,
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&wide_char,
false,
);
}
let extra_latin = "\u{00d0}\u{00de}\u{00fe}";
for ch in extra_latin.chars() {
symbols.define_symbol(
Mode::Math,
Font::Main,
Group::NonAtom(NonAtom::MathOrd),
Some(ch),
&ch.to_string(),
false,
);
symbols.define_symbol(
Mode::Text,
Font::Main,
Group::NonAtom(NonAtom::TextOrd),
Some(ch),
&ch.to_string(),
false,
);
}
symbols
}
#[cfg(test)]
mod tests {
use super::*;
use crate::symbols::{Atom, Font, Group, NonAtom, create_symbols};
#[test]
fn test_symbol_creation() {
let symbols = create_symbols();
assert!(
symbols.get_math("\\equiv").is_some(),
"Failed to find \\equiv"
);
assert!(
symbols.get_math("\\forall").is_some(),
"Failed to find \\forall"
);
assert!(
symbols.get_math("\\exists").is_some(),
"Failed to find \\exists"
);
assert!(
symbols.get_text("\\#").is_some(),
"Failed to find \\# in text mode"
);
assert!(
symbols.get_math("\\#").is_some(),
"Failed to find \\# in math mode"
);
let equiv_info = symbols.get_math("\\equiv").unwrap();
assert_eq!(equiv_info.replace, Some('\u{2261}'));
}
fn get_ligature_replacement(s: &str) -> Option<&'static str> {
LIGATURES.get(s).copied()
}
#[test]
fn test_ligature_detection() {
assert!(is_ligature("--"));
assert!(is_ligature("---"));
assert!(is_ligature("``"));
assert!(is_ligature("''"));
assert!(!is_ligature("notaligature"));
assert_eq!(get_ligature_replacement("--"), Some("\u{2013}"));
assert_eq!(get_ligature_replacement("---"), Some("\u{2014}"));
assert_eq!(get_ligature_replacement("``"), Some("\u{201c}"));
assert_eq!(get_ligature_replacement("''"), Some("\u{201d}"));
assert_eq!(get_ligature_replacement("notaligature"), None);
}
#[test]
fn test_symbol_properties() {
let symbols = create_symbols();
let equiv_info = symbols.get_math("\\equiv").unwrap();
assert_eq!(equiv_info.font, Font::Main);
assert!(matches!(equiv_info.group, Group::Atom(Atom::Rel)));
let forall_info = symbols.get_math("\\forall").unwrap();
assert_eq!(forall_info.font, Font::Main);
assert!(matches!(
forall_info.group,
Group::NonAtom(NonAtom::TextOrd)
));
}
#[test]
fn test_key_symbols_retrieval() {
let symbols = create_symbols();
assert!(symbols.get_math("\\alpha").is_some());
assert!(symbols.get_math("\\beta").is_some());
assert!(symbols.get_math("\\gamma").is_some());
assert!(symbols.get_math("\\delta").is_some());
let alpha_info = symbols.get_math("\\alpha").unwrap();
assert_eq!(alpha_info.font, Font::Main);
assert!(matches!(alpha_info.group, Group::NonAtom(NonAtom::MathOrd)));
assert_eq!(alpha_info.replace, Some('\u{03b1}'));
for i in 0..10 {
let num_str = format!("{i}");
assert!(symbols.get_math(&num_str).is_some());
assert!(symbols.get_text(&num_str).is_some());
}
let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
for ch in letters.chars() {
let ch_str = ch.to_string();
assert!(symbols.get_math(&ch_str).is_some());
assert!(symbols.get_text(&ch_str).is_some());
}
assert!(symbols.get_math("\\equiv").is_some());
assert!(symbols.get_math("\\forall").is_some());
assert!(symbols.get_math("\\exists").is_some());
assert!(symbols.get_math("\\acute").is_some());
assert!(symbols.get_math("\\grave").is_some());
assert!(symbols.get_math("\\tilde").is_some());
}
}