use serde::{Deserialize, Serialize};
use slotmap::{new_key_type, SlotMap};
new_key_type! {
pub struct NodeId;
pub struct SeqId;
}
#[derive(Debug, Clone)]
pub struct Tree {
pub(crate) nodes: SlotMap<NodeId, Node>,
pub(crate) seqs: SlotMap<SeqId, Seq>,
pub(crate) root: SeqId,
}
impl Default for Tree {
fn default() -> Self {
Self::new()
}
}
impl Tree {
pub 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,
}
}
pub fn root(&self) -> SeqId {
self.root
}
pub fn node(&self, id: NodeId) -> Option<&Node> {
self.nodes.get(id)
}
pub fn seq(&self, id: SeqId) -> Option<&Seq> {
self.seqs.get(id)
}
pub fn kind(&self, id: NodeId) -> Option<&Kind> {
self.nodes.get(id).map(|n| &n.kind)
}
pub fn items(&self, id: SeqId) -> &[NodeId] {
self.seqs.get(id).map_or(&[], |s| s.items.as_slice())
}
pub fn len(&self, id: SeqId) -> usize {
self.items(id).len()
}
pub fn is_empty(&self, id: SeqId) -> bool {
self.items(id).is_empty()
}
pub 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 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 fn child_seqs(&self, node: NodeId) -> Vec<SeqId> {
let Some(n) = self.nodes.get(node) else {
return Vec::new();
};
match &n.kind {
Kind::Atom(_) => 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 struct Seq {
pub parent: Option<NodeId>,
pub items: Vec<NodeId>,
}
#[derive(Debug, Clone)]
pub struct Node {
pub parent: SeqId,
pub kind: Kind,
}
#[derive(Debug, Clone)]
pub enum Kind {
Atom(Symbol),
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, Serialize, Deserialize)]
pub struct Symbol {
pub latex: String,
pub class: MathClass,
}
impl Symbol {
pub fn from_char(c: char) -> Self {
let latex = match c {
'%' => "\\%".to_string(),
'#' => "\\#".to_string(),
'&' => "\\&".to_string(),
'$' => "\\$".to_string(),
'_' => "\\_".to_string(),
'{' => "\\{".to_string(),
'}' => "\\}".to_string(),
'~' => "\\sim".to_string(),
'\\' => "\\backslash".to_string(),
other if needs_text_mode(other) => format!("\\text{{{other}}}"),
other => other.to_string(),
};
Symbol {
latex,
class: char_class(c),
}
}
}
fn needs_text_mode(c: char) -> bool {
let greek = ('\u{0370}'..='\u{03FF}').contains(&c) || ('\u{1F00}'..='\u{1FFF}').contains(&c);
c.is_alphabetic() && !c.is_ascii() && !greek
}
pub(crate) fn char_class(c: char) -> MathClass {
match c {
'+' | '-' | '*' => MathClass::Bin,
'=' | '<' | '>' => MathClass::Rel,
',' | ';' | '.' | ':' => MathClass::Punct,
'(' | '[' => MathClass::Open,
')' | ']' => MathClass::Close,
_ => MathClass::Ord,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MathClass {
Ord,
Op,
Bin,
Rel,
Open,
Close,
Punct,
Inner,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FracStyle {
Bar,
Display,
Text,
Binom,
Atop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ScriptSlot {
Sub,
Sup,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, 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, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Deco {
None,
Brace,
Arrow,
Line,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, 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, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MatrixEnv {
Matrix,
Pmatrix,
Bmatrix,
Vmatrix,
Cases,
Aligned,
Array,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, 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 struct Cursor {
pub seq: SeqId,
pub index: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Selection {
pub seq: SeqId,
pub anchor: usize,
pub focus: usize,
}