1use serde::{Deserialize, Serialize};
4use slotmap::{new_key_type, SlotMap};
5
6new_key_type! {
7 pub(crate) struct NodeId;
9 pub(crate) struct SeqId;
11}
12
13#[derive(Debug, Clone)]
15pub(crate) struct Tree {
16 pub(crate) nodes: SlotMap<NodeId, Node>,
17 pub(crate) seqs: SlotMap<SeqId, Seq>,
18 pub(crate) root: SeqId,
19 pub(crate) edits: u64,
21}
22
23impl Tree {
24 pub(crate) fn new() -> Self {
25 let mut seqs: SlotMap<SeqId, Seq> = SlotMap::with_key();
26 let root = seqs.insert(Seq { parent: None, items: Vec::new() });
27 Self { nodes: SlotMap::with_key(), seqs, root, edits: 0 }
28 }
29
30 pub(crate) fn root(&self) -> SeqId {
31 self.root
32 }
33
34 pub(crate) fn kind(&self, id: NodeId) -> Option<&Kind> {
35 self.nodes.get(id).map(|n| &n.kind)
36 }
37
38 pub(crate) fn items(&self, id: SeqId) -> &[NodeId] {
39 self.seqs.get(id).map_or(&[], |s| s.items.as_slice())
40 }
41
42 pub(crate) fn len(&self, id: SeqId) -> usize {
43 self.items(id).len()
44 }
45
46 pub(crate) fn is_empty(&self, id: SeqId) -> bool {
47 self.items(id).is_empty()
48 }
49
50 pub(crate) fn touch(&mut self) {
51 self.edits += 1;
52 }
53
54 pub(crate) fn seq_parent(&self, id: SeqId) -> Option<NodeId> {
56 self.seqs.get(id).and_then(|s| s.parent)
57 }
58
59 pub(crate) fn script_base_node(&self, seq: SeqId) -> Option<NodeId> {
61 let parent = self.seq_parent(seq)?;
62 match self.kind(parent) {
63 Some(Kind::Script { base, .. }) if *base == seq => Some(parent),
64 _ => None,
65 }
66 }
67
68 pub(crate) fn is_text_slot(&self, seq: SeqId) -> bool {
70 let Some(parent) = self.seq_parent(seq) else {
71 return false;
72 };
73 matches!(self.kind(parent), Some(Kind::Styled { variant: Variant::Text, .. }))
74 }
75
76 pub(crate) fn index_in_parent(&self, node: NodeId) -> Option<(SeqId, usize)> {
78 let parent = self.nodes.get(node)?.parent;
79 let idx = self.seqs.get(parent)?.items.iter().position(|&n| n == node)?;
80 Some((parent, idx))
81 }
82
83 pub(crate) fn before_parent(&self, seq: SeqId) -> Option<Cursor> {
85 let node = self.seq_parent(seq)?;
86 let (seq, index) = self.index_in_parent(node)?;
87 Some(Cursor { seq, index })
88 }
89
90 pub(crate) fn seq_depth(&self, seq: SeqId) -> usize {
92 let mut depth = 0;
93 let mut cur = seq;
94 while let Some(node) = self.seq_parent(cur) {
95 depth += 1;
96 let Some(n) = self.nodes.get(node) else { break };
97 cur = n.parent;
98 }
99 depth
100 }
101
102 pub(crate) fn node_height(&self, node: NodeId) -> usize {
104 self.child_seqs(node)
105 .into_iter()
106 .map(|s| 1 + self.seq_height(s))
107 .max()
108 .unwrap_or(0)
109 }
110
111 pub(crate) fn seq_height(&self, seq: SeqId) -> usize {
112 self.items(seq).iter().map(|&n| self.node_height(n)).max().unwrap_or(0)
113 }
114
115 pub(crate) fn child_seqs(&self, node: NodeId) -> Vec<SeqId> {
117 let Some(n) = self.nodes.get(node) else {
118 return Vec::new();
119 };
120 match &n.kind {
121 Kind::Atom(_) | Kind::HostBox { .. } => Vec::new(),
122 Kind::Frac { num, den, .. } => vec![*num, *den],
123 Kind::Script { base, sub, sup } => {
124 let mut v = vec![*base];
125 v.extend(sub.iter().copied());
126 v.extend(sup.iter().copied());
127 v
128 }
129 Kind::BigOp { upper, lower, .. } => vec![*upper, *lower],
131 Kind::Sqrt { index, radicand } => vec![*index, *radicand],
132 Kind::Delim { body, .. } => vec![*body],
133 Kind::Accent { base, .. } => vec![*base],
134 Kind::UnderOver { base, over, under, .. } => {
135 let mut v = Vec::new();
136 v.extend(over.iter().copied());
137 v.push(*base);
138 v.extend(under.iter().copied());
139 v
140 }
141 Kind::Styled { content, .. } => vec![*content],
142 Kind::Matrix { rows, .. } => rows.iter().flatten().copied().collect(),
143 Kind::Ratio { left, right } => vec![*left, *right],
144 Kind::EvalBar { body, lower, upper } => vec![*body, *lower, *upper],
146 Kind::LabeledArrow { over, under, .. } => vec![*over, *under],
147 }
148 }
149}
150
151#[derive(Debug, Clone)]
153pub(crate) struct Seq {
154 pub(crate) parent: Option<NodeId>,
155 pub(crate) items: Vec<NodeId>,
156}
157
158#[derive(Debug, Clone)]
160pub(crate) struct Node {
161 pub(crate) parent: SeqId,
162 pub(crate) kind: Kind,
163}
164
165#[derive(Debug, Clone)]
167pub(crate) enum Kind {
168 Atom(Symbol),
169 HostBox { token: u32 },
170 Frac { num: SeqId, den: SeqId, style: FracStyle },
171 Script { base: SeqId, sub: Option<SeqId>, sup: Option<SeqId> },
172 BigOp { op: Symbol, lower: SeqId, upper: SeqId },
173 Sqrt { index: SeqId, radicand: SeqId },
174 Delim { open: char, close: char, body: SeqId },
175 Accent { mark: Mark, base: SeqId },
176 UnderOver { base: SeqId, over: Option<SeqId>, under: Option<SeqId>, over_deco: Deco, under_deco: Deco },
177 Styled { variant: Variant, content: SeqId },
178 Matrix { env: MatrixEnv, rows: Vec<Vec<SeqId>> },
179 Ratio { left: SeqId, right: SeqId },
180 EvalBar { body: SeqId, lower: SeqId, upper: SeqId },
181 LabeledArrow { arrow: Arrow, over: SeqId, under: SeqId },
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
186pub struct Symbol {
187 pub latex: String,
189 pub class: MathClass,
191}
192
193impl Symbol {
194 pub fn from_char(c: char) -> Option<Self> {
196 if c.is_control() {
197 return None;
198 }
199 let latex = match c {
200 '%' | '#' | '&' | '$' | '_' | '{' | '}' => format!("\\{c}"),
201 '~' => "\\sim".to_string(),
202 '\\' => "\\backslash".to_string(),
203 '^' => "\\text{\\textasciicircum}".to_string(),
204 '\'' => "\\prime".to_string(),
205 ' ' => "\\ ".to_string(),
206 other if needs_text_mode(other) => format!("\\text{{{other}}}"),
208 other => other.to_string(),
209 };
210 let class = latex_class(&latex);
211 Some(Symbol { latex, class })
212 }
213
214 pub fn from_latex(latex: &str) -> Self {
216 Symbol { latex: latex.to_string(), class: latex_class(latex) }
217 }
218}
219
220fn needs_text_mode(c: char) -> bool {
222 let greek = ('\u{0370}'..='\u{03FF}').contains(&c) || ('\u{1F00}'..='\u{1FFF}').contains(&c);
223 let letterlike = ('\u{2100}'..='\u{214F}').contains(&c);
225 let math_alnum = ('\u{1D400}'..='\u{1D7FF}').contains(&c);
226 c.is_alphabetic() && !c.is_ascii() && !greek && !letterlike && !math_alnum
227}
228
229fn char_class(c: char) -> MathClass {
231 match c {
232 '+' | '-' | '*' | '\u{2212}' | 'ยฑ' | 'โ' | 'ร' | 'รท' | 'ยท' | 'โ' | 'โ' => MathClass::Bin,
233 '=' | '<' | '>' | 'โค' | 'โฅ' | 'โ ' | 'โ' | 'โก' | 'โผ' | 'โ
' | 'โ' | 'โ' | 'โ' | 'โ' | 'โ' | 'โ'
234 | 'โ' | 'โ' | 'โ' | 'โ' | 'โ' | 'โ' => MathClass::Rel,
235 ',' | ';' | '.' | ':' => MathClass::Punct,
236 '(' | '[' | '{' | 'โจ' | 'โ' | 'โ' => MathClass::Open,
237 ')' | ']' | '}' | 'โฉ' | 'โ' | 'โ' => MathClass::Close,
238 _ => MathClass::Ord,
239 }
240}
241
242fn latex_class(latex: &str) -> MathClass {
244 let mut chars = latex.chars();
245 if let (Some(c), None) = (chars.next(), chars.next()) {
246 return char_class(c);
247 }
248 let Some(name) = latex.strip_prefix('\\') else {
249 return MathClass::Ord;
250 };
251 if OPERATOR_NAMES.contains(&name) || name.starts_with("operatorname{") {
252 return MathClass::Op;
253 }
254 if name.starts_with("mathbin{") {
255 return MathClass::Bin;
256 }
257 if name.starts_with("mathrel{") {
258 return MathClass::Rel;
259 }
260 match name {
261 "{" | "langle" | "lceil" | "lfloor" => MathClass::Open,
262 "}" | "rangle" | "rceil" | "rfloor" => MathClass::Close,
263 "leq" | "le" | "geq" | "ge" | "neq" | "ne" | "equiv" | "approx" | "cong" | "sim" | "simeq" | "propto"
264 | "to" | "gets" | "mapsto" | "implies" | "iff" | "in" | "notin" | "ni" | "subset" | "subseteq"
265 | "supset" | "supseteq" | "rightarrow" | "leftarrow" | "leftrightarrow" | "Rightarrow" | "Leftarrow"
266 | "Leftrightarrow" | "Longrightarrow" | "Longleftarrow" | "perp" | "parallel" | "mid" | "ll" | "gg" | "uparrow"
267 | "downarrow" | "nearrow" | "searrow" | "lesssim" | "gtrsim" | "asymp" | "nmid" | "subsetneq" | "supsetneq"
268 | "nsubseteq" | "vdash" | "models" | "coloneq" | "hookrightarrow" | "twoheadrightarrow" | "prec" | "preceq"
269 | "succ" | "succeq" | "trianglelefteq" | "lneq" => MathClass::Rel,
270 "pm" | "mp" | "times" | "div" | "cdot" | "ast" | "star" | "cup" | "cap" | "setminus" | "circ" | "oplus"
271 | "otimes" | "wedge" | "vee" | "land" | "lor" | "bmod" | "sqcup" | "triangleleft" | "ltimes" | "rtimes"
272 | "wr" => MathClass::Bin,
273 "colon" => MathClass::Punct,
274 "cdots" | "ldots" | "dots" | "vdots" | "ddots" => MathClass::Inner,
275 "sum" | "prod" | "coprod" | "int" | "iint" | "iiint" | "oint" | "bigcup" | "bigcap" | "bigsqcup" | "biguplus"
276 | "bigoplus" | "bigotimes" | "bigodot" | "bigvee" | "bigwedge" => MathClass::Op,
277 _ => MathClass::Ord,
278 }
279}
280
281const OPERATOR_NAMES: &[&str] = &[
283 "sin", "cos", "tan", "cot", "sec", "csc", "sinh", "cosh", "tanh", "arcsin", "arccos", "arctan", "log", "ln",
284 "exp", "lim", "max", "min", "sup", "inf", "gcd", "det", "dim", "ker", "arg", "deg", "hom", "limsup", "liminf",
285];
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
289#[serde(rename_all = "snake_case")]
290pub enum MathClass {
291 Ord,
293 Op,
295 Bin,
297 Rel,
299 Open,
301 Close,
303 Punct,
305 Inner,
307}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
311#[serde(rename_all = "snake_case")]
312pub enum FracStyle {
313 Bar,
315 Display,
317 Text,
319 Binom,
321 Atop,
323}
324
325#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
327#[serde(rename_all = "snake_case")]
328pub enum ScriptSlot {
329 Sub,
331 Sup,
333}
334
335#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
337#[serde(rename_all = "snake_case")]
338pub enum Mark {
339 Hat,
341 Vec,
343 Bar,
345 Tilde,
347 Dot,
349 Ddot,
351 Widehat,
353 Widetilde,
355 Overline,
357 Underline,
359 Check,
361 Breve,
363 Ring,
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
369#[serde(rename_all = "snake_case")]
370pub enum Deco {
371 None,
373 Brace,
375 Arrow,
377 Line,
379}
380
381#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
383#[serde(rename_all = "snake_case")]
384pub enum Variant {
385 Normal,
387 Bold,
389 Blackboard,
391 Calligraphic,
393 Fraktur,
395 Roman,
397 SansSerif,
399 Typewriter,
401 Text,
403 OperatorName,
405}
406
407#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
409#[serde(rename_all = "snake_case")]
410pub enum Arrow {
411 Right,
413 Left,
415}
416
417#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
419#[serde(rename_all = "snake_case")]
420pub enum MatrixEnv {
421 Matrix,
423 Pmatrix,
425 Bmatrix,
427 Vmatrix,
429 Cases,
431 Aligned,
433 Array,
435}
436
437#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
439pub struct UnderOverSpec {
440 pub over: bool,
442 pub under: bool,
444 pub over_deco: Deco,
446 pub under_deco: Deco,
448}
449
450#[derive(Debug, Clone, Copy, PartialEq, Eq)]
452pub(crate) struct Cursor {
453 pub(crate) seq: SeqId,
454 pub(crate) index: usize,
455}
456
457#[derive(Debug, Clone, Copy, PartialEq, Eq)]
459pub(crate) struct SeqRange {
460 pub(crate) seq: SeqId,
461 pub(crate) anchor: usize,
462 pub(crate) focus: usize,
463}
464
465impl SeqRange {
466 pub(crate) fn lo(&self) -> usize {
467 self.anchor.min(self.focus)
468 }
469
470 pub(crate) fn hi(&self) -> usize {
471 self.anchor.max(self.focus)
472 }
473}