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 }
144 }
145}
146
147#[derive(Debug, Clone)]
149pub(crate) struct Seq {
150 pub(crate) parent: Option<NodeId>,
151 pub(crate) items: Vec<NodeId>,
152}
153
154#[derive(Debug, Clone)]
156pub(crate) struct Node {
157 pub(crate) parent: SeqId,
158 pub(crate) kind: Kind,
159}
160
161#[derive(Debug, Clone)]
163pub(crate) enum Kind {
164 Atom(Symbol),
165 HostBox { token: u32 },
166 Frac { num: SeqId, den: SeqId, style: FracStyle },
167 Script { base: SeqId, sub: Option<SeqId>, sup: Option<SeqId> },
168 BigOp { op: Symbol, lower: SeqId, upper: SeqId },
169 Sqrt { index: SeqId, radicand: SeqId },
170 Delim { open: char, close: char, body: SeqId },
171 Accent { mark: Mark, base: SeqId },
172 UnderOver { base: SeqId, over: Option<SeqId>, under: Option<SeqId>, over_deco: Deco, under_deco: Deco },
173 Styled { variant: Variant, content: SeqId },
174 Matrix { env: MatrixEnv, rows: Vec<Vec<SeqId>> },
175}
176
177#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
179pub struct Symbol {
180 pub latex: String,
182 pub class: MathClass,
184}
185
186impl Symbol {
187 pub fn from_char(c: char) -> Option<Self> {
189 if c.is_control() {
190 return None;
191 }
192 let latex = match c {
193 '%' | '#' | '&' | '$' | '_' | '{' | '}' => format!("\\{c}"),
194 '~' => "\\sim".to_string(),
195 '\\' => "\\backslash".to_string(),
196 '^' => "\\text{\\textasciicircum}".to_string(),
197 '\'' => "\\prime".to_string(),
198 ' ' => "\\ ".to_string(),
199 other if needs_text_mode(other) => format!("\\text{{{other}}}"),
201 other => other.to_string(),
202 };
203 let class = latex_class(&latex);
204 Some(Symbol { latex, class })
205 }
206
207 pub fn from_latex(latex: &str) -> Self {
209 Symbol { latex: latex.to_string(), class: latex_class(latex) }
210 }
211}
212
213fn needs_text_mode(c: char) -> bool {
215 let greek = ('\u{0370}'..='\u{03FF}').contains(&c) || ('\u{1F00}'..='\u{1FFF}').contains(&c);
216 let letterlike = ('\u{2100}'..='\u{214F}').contains(&c);
218 let math_alnum = ('\u{1D400}'..='\u{1D7FF}').contains(&c);
219 c.is_alphabetic() && !c.is_ascii() && !greek && !letterlike && !math_alnum
220}
221
222fn char_class(c: char) -> MathClass {
224 match c {
225 '+' | '-' | '*' | '\u{2212}' | 'ยฑ' | 'โ' | 'ร' | 'รท' | 'ยท' | 'โ' | 'โ' => MathClass::Bin,
226 '=' | '<' | '>' | 'โค' | 'โฅ' | 'โ ' | 'โ' | 'โก' | 'โผ' | 'โ
' | 'โ' | 'โ' | 'โ' | 'โ' | 'โ' | 'โ'
227 | 'โ' | 'โ' | 'โ' | 'โ' | 'โ' | 'โ' => MathClass::Rel,
228 ',' | ';' | '.' | ':' => MathClass::Punct,
229 '(' | '[' | '{' | 'โจ' | 'โ' | 'โ' => MathClass::Open,
230 ')' | ']' | '}' | 'โฉ' | 'โ' | 'โ' => MathClass::Close,
231 _ => MathClass::Ord,
232 }
233}
234
235fn latex_class(latex: &str) -> MathClass {
237 let mut chars = latex.chars();
238 if let (Some(c), None) = (chars.next(), chars.next()) {
239 return char_class(c);
240 }
241 let Some(name) = latex.strip_prefix('\\') else {
242 return MathClass::Ord;
243 };
244 if OPERATOR_NAMES.contains(&name) {
245 return MathClass::Op;
246 }
247 match name {
248 "{" | "langle" | "lceil" | "lfloor" => MathClass::Open,
249 "}" | "rangle" | "rceil" | "rfloor" => MathClass::Close,
250 "leq" | "le" | "geq" | "ge" | "neq" | "ne" | "equiv" | "approx" | "cong" | "sim" | "simeq" | "propto"
251 | "to" | "gets" | "mapsto" | "implies" | "iff" | "in" | "notin" | "ni" | "subset" | "subseteq"
252 | "supset" | "supseteq" | "rightarrow" | "leftarrow" | "leftrightarrow" | "Rightarrow" | "Leftarrow"
253 | "Leftrightarrow" | "Longrightarrow" | "Longleftarrow" | "perp" | "parallel" | "mid" | "ll" | "gg" => {
254 MathClass::Rel
255 }
256 "pm" | "mp" | "times" | "div" | "cdot" | "ast" | "star" | "cup" | "cap" | "setminus" | "circ" | "oplus"
257 | "otimes" | "wedge" | "vee" | "land" | "lor" => MathClass::Bin,
258 "cdots" | "ldots" | "dots" | "vdots" | "ddots" => MathClass::Inner,
259 "sum" | "prod" | "coprod" | "int" | "iint" | "iiint" | "oint" | "bigcup" | "bigcap" | "bigsqcup" | "biguplus"
260 | "bigoplus" | "bigotimes" | "bigodot" | "bigvee" | "bigwedge" => MathClass::Op,
261 _ => MathClass::Ord,
262 }
263}
264
265const OPERATOR_NAMES: &[&str] = &[
267 "sin", "cos", "tan", "cot", "sec", "csc", "sinh", "cosh", "tanh", "arcsin", "arccos", "arctan", "log", "ln",
268 "exp", "lim", "max", "min", "sup", "inf", "gcd", "det", "dim", "ker", "arg", "deg", "hom",
269];
270
271#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
273#[serde(rename_all = "snake_case")]
274pub enum MathClass {
275 Ord,
277 Op,
279 Bin,
281 Rel,
283 Open,
285 Close,
287 Punct,
289 Inner,
291}
292
293#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
295#[serde(rename_all = "snake_case")]
296pub enum FracStyle {
297 Bar,
299 Display,
301 Text,
303 Binom,
305 Atop,
307}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
311#[serde(rename_all = "snake_case")]
312pub enum ScriptSlot {
313 Sub,
315 Sup,
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
321#[serde(rename_all = "snake_case")]
322pub enum Mark {
323 Hat,
325 Vec,
327 Bar,
329 Tilde,
331 Dot,
333 Ddot,
335 Widehat,
337 Widetilde,
339 Overline,
341 Underline,
343 Check,
345 Breve,
347}
348
349#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
351#[serde(rename_all = "snake_case")]
352pub enum Deco {
353 None,
355 Brace,
357 Arrow,
359 Line,
361}
362
363#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
365#[serde(rename_all = "snake_case")]
366pub enum Variant {
367 Normal,
369 Bold,
371 Blackboard,
373 Calligraphic,
375 Fraktur,
377 Roman,
379 SansSerif,
381 Typewriter,
383 Text,
385 OperatorName,
387}
388
389#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
391#[serde(rename_all = "snake_case")]
392pub enum MatrixEnv {
393 Matrix,
395 Pmatrix,
397 Bmatrix,
399 Vmatrix,
401 Cases,
403 Aligned,
405 Array,
407}
408
409#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
411pub struct UnderOverSpec {
412 pub over: bool,
414 pub under: bool,
416 pub over_deco: Deco,
418 pub under_deco: Deco,
420}
421
422#[derive(Debug, Clone, Copy, PartialEq, Eq)]
424pub(crate) struct Cursor {
425 pub(crate) seq: SeqId,
426 pub(crate) index: usize,
427}
428
429#[derive(Debug, Clone, Copy, PartialEq, Eq)]
431pub(crate) struct SeqRange {
432 pub(crate) seq: SeqId,
433 pub(crate) anchor: usize,
434 pub(crate) focus: usize,
435}
436
437impl SeqRange {
438 pub(crate) fn lo(&self) -> usize {
439 self.anchor.min(self.focus)
440 }
441
442 pub(crate) fn hi(&self) -> usize {
443 self.anchor.max(self.focus)
444 }
445}