use serde::{Deserialize, Serialize};
use slotmap::{new_key_type, SlotMap};
new_key_type! {
pub(crate) struct NodeId;
pub(crate) struct SeqId;
}
#[derive(Debug, Clone)]
pub(crate) struct Tree {
pub(crate) nodes: SlotMap<NodeId, Node>,
pub(crate) seqs: SlotMap<SeqId, Seq>,
pub(crate) root: SeqId,
pub(crate) edits: u64,
}
impl Tree {
pub(crate) fn new() -> Self {
let mut seqs: SlotMap<SeqId, Seq> = SlotMap::with_key();
let root = seqs.insert(Seq { parent: None, items: Vec::new() });
Self { nodes: SlotMap::with_key(), seqs, root, edits: 0 }
}
pub(crate) fn root(&self) -> SeqId {
self.root
}
pub(crate) fn kind(&self, id: NodeId) -> Option<&Kind> {
self.nodes.get(id).map(|n| &n.kind)
}
pub(crate) fn items(&self, id: SeqId) -> &[NodeId] {
self.seqs.get(id).map_or(&[], |s| s.items.as_slice())
}
pub(crate) fn len(&self, id: SeqId) -> usize {
self.items(id).len()
}
pub(crate) fn is_empty(&self, id: SeqId) -> bool {
self.items(id).is_empty()
}
pub(crate) fn touch(&mut self) {
self.edits += 1;
}
pub(crate) fn seq_parent(&self, id: SeqId) -> Option<NodeId> {
self.seqs.get(id).and_then(|s| s.parent)
}
pub(crate) fn script_base_node(&self, seq: SeqId) -> Option<NodeId> {
let parent = self.seq_parent(seq)?;
match self.kind(parent) {
Some(Kind::Script { base, .. }) if *base == seq => Some(parent),
_ => None,
}
}
pub(crate) fn is_text_slot(&self, seq: SeqId) -> bool {
let Some(parent) = self.seq_parent(seq) else {
return false;
};
matches!(self.kind(parent), Some(Kind::Styled { variant: Variant::Text, .. }))
}
pub(crate) fn index_in_parent(&self, node: NodeId) -> Option<(SeqId, usize)> {
let parent = self.nodes.get(node)?.parent;
let idx = self.seqs.get(parent)?.items.iter().position(|&n| n == node)?;
Some((parent, idx))
}
pub(crate) fn before_parent(&self, seq: SeqId) -> Option<Cursor> {
let node = self.seq_parent(seq)?;
let (seq, index) = self.index_in_parent(node)?;
Some(Cursor { seq, index })
}
pub(crate) fn seq_depth(&self, seq: SeqId) -> usize {
let mut depth = 0;
let mut cur = seq;
while let Some(node) = self.seq_parent(cur) {
depth += 1;
let Some(n) = self.nodes.get(node) else { break };
cur = n.parent;
}
depth
}
pub(crate) fn node_height(&self, node: NodeId) -> usize {
self.child_seqs(node)
.into_iter()
.map(|s| 1 + self.seq_height(s))
.max()
.unwrap_or(0)
}
pub(crate) fn seq_height(&self, seq: SeqId) -> usize {
self.items(seq).iter().map(|&n| self.node_height(n)).max().unwrap_or(0)
}
pub(crate) fn child_seqs(&self, node: NodeId) -> Vec<SeqId> {
let Some(n) = self.nodes.get(node) else {
return Vec::new();
};
match &n.kind {
Kind::Atom(_) | Kind::HostBox { .. } => Vec::new(),
Kind::Frac { num, den, .. } => vec![*num, *den],
Kind::Script { base, sub, sup } => {
let mut v = vec![*base];
v.extend(sub.iter().copied());
v.extend(sup.iter().copied());
v
}
Kind::BigOp { upper, lower, .. } => vec![*upper, *lower],
Kind::Sqrt { index, radicand } => vec![*index, *radicand],
Kind::Delim { body, .. } => vec![*body],
Kind::Accent { base, .. } => vec![*base],
Kind::UnderOver { base, over, under, .. } => {
let mut v = Vec::new();
v.extend(over.iter().copied());
v.push(*base);
v.extend(under.iter().copied());
v
}
Kind::Styled { content, .. } => vec![*content],
Kind::Matrix { rows, .. } => rows.iter().flatten().copied().collect(),
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Seq {
pub(crate) parent: Option<NodeId>,
pub(crate) items: Vec<NodeId>,
}
#[derive(Debug, Clone)]
pub(crate) struct Node {
pub(crate) parent: SeqId,
pub(crate) kind: Kind,
}
#[derive(Debug, Clone)]
pub(crate) enum Kind {
Atom(Symbol),
HostBox { token: u32 },
Frac { num: SeqId, den: SeqId, style: FracStyle },
Script { base: SeqId, sub: Option<SeqId>, sup: Option<SeqId> },
BigOp { op: Symbol, lower: SeqId, upper: SeqId },
Sqrt { index: SeqId, radicand: SeqId },
Delim { open: char, close: char, body: SeqId },
Accent { mark: Mark, base: SeqId },
UnderOver { base: SeqId, over: Option<SeqId>, under: Option<SeqId>, over_deco: Deco, under_deco: Deco },
Styled { variant: Variant, content: SeqId },
Matrix { env: MatrixEnv, rows: Vec<Vec<SeqId>> },
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Symbol {
pub latex: String,
pub class: MathClass,
}
impl Symbol {
pub fn from_char(c: char) -> Option<Self> {
if c.is_control() {
return None;
}
let latex = match c {
'%' | '#' | '&' | '$' | '_' | '{' | '}' => format!("\\{c}"),
'~' => "\\sim".to_string(),
'\\' => "\\backslash".to_string(),
'^' => "\\text{\\textasciicircum}".to_string(),
'\'' => "\\prime".to_string(),
' ' => "\\ ".to_string(),
other if needs_text_mode(other) => format!("\\text{{{other}}}"),
other => other.to_string(),
};
let class = latex_class(&latex);
Some(Symbol { latex, class })
}
pub fn from_latex(latex: &str) -> Self {
Symbol { latex: latex.to_string(), class: latex_class(latex) }
}
}
fn needs_text_mode(c: char) -> bool {
let greek = ('\u{0370}'..='\u{03FF}').contains(&c) || ('\u{1F00}'..='\u{1FFF}').contains(&c);
let letterlike = ('\u{2100}'..='\u{214F}').contains(&c);
let math_alnum = ('\u{1D400}'..='\u{1D7FF}').contains(&c);
c.is_alphabetic() && !c.is_ascii() && !greek && !letterlike && !math_alnum
}
fn char_class(c: char) -> MathClass {
match c {
'+' | '-' | '*' | '\u{2212}' | '±' | '∓' | '×' | '÷' | '·' | '∘' | '∙' => MathClass::Bin,
'=' | '<' | '>' | '≤' | '≥' | '≠' | '≈' | '≡' | '∼' | '≅' | '∝' | '→' | '←' | '⇒' | '⇐' | '⇔'
| '∈' | '∉' | '⊂' | '⊆' | '⊃' | '⊇' => MathClass::Rel,
',' | ';' | '.' | ':' => MathClass::Punct,
'(' | '[' | '{' | '⟨' | '⌈' | '⌊' => MathClass::Open,
')' | ']' | '}' | '⟩' | '⌉' | '⌋' => MathClass::Close,
_ => MathClass::Ord,
}
}
fn latex_class(latex: &str) -> MathClass {
let mut chars = latex.chars();
if let (Some(c), None) = (chars.next(), chars.next()) {
return char_class(c);
}
let Some(name) = latex.strip_prefix('\\') else {
return MathClass::Ord;
};
if OPERATOR_NAMES.contains(&name) {
return MathClass::Op;
}
match name {
"{" | "langle" | "lceil" | "lfloor" => MathClass::Open,
"}" | "rangle" | "rceil" | "rfloor" => MathClass::Close,
"leq" | "le" | "geq" | "ge" | "neq" | "ne" | "equiv" | "approx" | "cong" | "sim" | "simeq" | "propto"
| "to" | "gets" | "mapsto" | "implies" | "iff" | "in" | "notin" | "ni" | "subset" | "subseteq"
| "supset" | "supseteq" | "rightarrow" | "leftarrow" | "leftrightarrow" | "Rightarrow" | "Leftarrow"
| "Leftrightarrow" | "Longrightarrow" | "Longleftarrow" | "perp" | "parallel" | "mid" | "ll" | "gg" => {
MathClass::Rel
}
"pm" | "mp" | "times" | "div" | "cdot" | "ast" | "star" | "cup" | "cap" | "setminus" | "circ" | "oplus"
| "otimes" | "wedge" | "vee" | "land" | "lor" => MathClass::Bin,
"cdots" | "ldots" | "dots" | "vdots" | "ddots" => MathClass::Inner,
"sum" | "prod" | "coprod" | "int" | "iint" | "iiint" | "oint" | "bigcup" | "bigcap" | "bigsqcup" | "biguplus"
| "bigoplus" | "bigotimes" | "bigodot" | "bigvee" | "bigwedge" => MathClass::Op,
_ => MathClass::Ord,
}
}
const OPERATOR_NAMES: &[&str] = &[
"sin", "cos", "tan", "cot", "sec", "csc", "sinh", "cosh", "tanh", "arcsin", "arccos", "arctan", "log", "ln",
"exp", "lim", "max", "min", "sup", "inf", "gcd", "det", "dim", "ker", "arg", "deg", "hom",
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MathClass {
Ord,
Op,
Bin,
Rel,
Open,
Close,
Punct,
Inner,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FracStyle {
Bar,
Display,
Text,
Binom,
Atop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ScriptSlot {
Sub,
Sup,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Mark {
Hat,
Vec,
Bar,
Tilde,
Dot,
Ddot,
Widehat,
Widetilde,
Overline,
Underline,
Check,
Breve,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Deco {
None,
Brace,
Arrow,
Line,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Variant {
Normal,
Bold,
Blackboard,
Calligraphic,
Fraktur,
Roman,
SansSerif,
Typewriter,
Text,
OperatorName,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MatrixEnv {
Matrix,
Pmatrix,
Bmatrix,
Vmatrix,
Cases,
Aligned,
Array,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct UnderOverSpec {
pub over: bool,
pub under: bool,
pub over_deco: Deco,
pub under_deco: Deco,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Cursor {
pub(crate) seq: SeqId,
pub(crate) index: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct SeqRange {
pub(crate) seq: SeqId,
pub(crate) anchor: usize,
pub(crate) focus: usize,
}
impl SeqRange {
pub(crate) fn lo(&self) -> usize {
self.anchor.min(self.focus)
}
pub(crate) fn hi(&self) -> usize {
self.anchor.max(self.focus)
}
}