use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum Node {
Number {
value: String,
#[serde(skip_serializing_if = "std::ops::Not::not")]
group: bool,
#[serde(skip_serializing_if = "Option::is_none")]
thousands: Option<char>,
},
Identifier {
name: String,
#[serde(skip_serializing_if = "Variant::is_default")]
variant: Variant,
},
Function {
name: String,
#[serde(skip_serializing_if = "std::ops::Not::not")]
limits: bool,
},
Operator {
symbol: char,
class: Class,
},
FixedDelimiter {
symbol: char,
class: Class,
size: u8,
},
LargeOperator {
symbol: char,
#[serde(skip_serializing_if = "Option::is_none")]
limits: Option<bool>,
},
Row {
children: Vec<Node>,
},
Fraction {
num: Box<Node>,
den: Box<Node>,
#[serde(skip_serializing_if = "FractionStyle::is_default")]
style: FractionStyle,
#[serde(skip_serializing_if = "std::clone::Clone::clone")]
rule: bool,
},
Root {
radicand: Box<Node>,
#[serde(skip_serializing_if = "Option::is_none")]
index: Option<Box<Node>>,
},
Scripts {
base: Box<Node>,
#[serde(skip_serializing_if = "Option::is_none")]
sup: Option<Box<Node>>,
#[serde(skip_serializing_if = "Option::is_none")]
sub: Option<Box<Node>>,
},
Delimited {
#[serde(skip_serializing_if = "Option::is_none")]
open: Option<char>,
#[serde(skip_serializing_if = "Option::is_none")]
close: Option<char>,
parts: Vec<Node>,
#[serde(skip_serializing_if = "Vec::is_empty")]
middles: Vec<char>,
},
Table {
shape: TableKind,
rows: Vec<Vec<Node>>,
#[serde(skip_serializing_if = "Vec::is_empty")]
columns: Vec<char>,
#[serde(skip_serializing_if = "Vec::is_empty")]
verticals: Vec<usize>,
#[serde(skip_serializing_if = "Vec::is_empty")]
horizontals: Vec<TableRule>,
#[serde(skip_serializing_if = "Vec::is_empty")]
between: Vec<(usize, Node)>,
#[serde(skip_serializing_if = "Vec::is_empty")]
intertexts: Vec<usize>,
},
Accent {
base: Box<Node>,
accent: char,
stretchy: bool,
#[serde(skip_serializing_if = "std::ops::Not::not")]
below: bool,
},
Bar {
base: Box<Node>,
above: bool,
},
Brace {
base: Box<Node>,
above: bool,
#[serde(skip_serializing_if = "Option::is_none")]
label: Option<Box<Node>>,
#[serde(skip_serializing_if = "std::ops::Not::not")]
bracket: bool,
},
Text {
parts: Vec<TextPart>,
},
Space {
em: f32,
},
Canceled {
base: Box<Node>,
shape: Cancel,
},
Framed {
base: Box<Node>,
#[serde(skip_serializing_if = "Frame::is_default")]
shape: Frame,
#[serde(skip_serializing_if = "Option::is_none")]
background: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
border: Option<String>,
},
Arrow {
symbol: char,
#[serde(skip_serializing_if = "Option::is_none")]
above: Option<Box<Node>>,
#[serde(skip_serializing_if = "Option::is_none")]
below: Option<Box<Node>>,
},
Stacked {
base: Box<Node>,
#[serde(skip_serializing_if = "Option::is_none")]
above: Option<Box<Node>>,
#[serde(skip_serializing_if = "Option::is_none")]
below: Option<Box<Node>>,
},
Phantom {
base: Box<Node>,
width: bool,
height: bool,
},
ForcedClass {
class: Class,
base: Box<Node>,
},
Style {
level: u8,
base: Box<Node>,
},
Colored {
color: String,
base: Box<Node>,
},
Quantity {
#[serde(skip_serializing_if = "Option::is_none")]
value: Option<String>,
unit: Box<Node>,
},
Multicolumn {
columns: usize,
alignment: char,
base: Box<Node>,
},
Raised {
em: f32,
base: Box<Node>,
},
Scaled {
factor: f32,
base: Box<Node>,
},
Rule {
width: f32,
height: f32,
raise: f32,
},
Smashed {
base: Box<Node>,
height: bool,
depth: bool,
},
Overlapped {
base: Box<Node>,
side: char,
},
VerticalArrow {
symbol: char,
#[serde(skip_serializing_if = "Option::is_none")]
left: Option<Box<Node>>,
#[serde(skip_serializing_if = "Option::is_none")]
right: Option<Box<Node>>,
},
LongDivision {
dividend: String,
divisor: String,
},
ColumnArithmetic {
operation: char,
terms: Vec<String>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(tag = "type", content = "value", rename_all = "kebab-case")]
pub enum TextPart {
Text(String),
Math(Node),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum Class {
Ord,
Op,
Bin,
Rel,
Open,
Close,
Punct,
Inner,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum Variant {
#[default]
Italic,
Upright,
Bold,
BoldItalic,
DoubleStruck,
Calligraphic,
Fraktur,
SansSerif,
Monospace,
}
impl Variant {
pub fn is_default(&self) -> bool {
*self == Variant::Italic
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum FractionStyle {
#[default]
Auto,
Display,
Row,
}
impl FractionStyle {
pub fn is_default(&self) -> bool {
*self == FractionStyle::Auto
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum Cancel {
Bar,
Backslash,
X,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct TableRule {
pub row: usize,
pub from: usize,
pub to: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum Frame {
#[default]
Rectangle,
Circle,
Fill,
}
impl Frame {
pub fn is_default(&self) -> bool {
*self == Frame::Rectangle
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum TableKind {
Matrix,
PMatrix,
BMatrix,
BraceMatrix,
VMatrix,
VVMatrix,
SmallMatrix,
Cases,
RCases,
Aligned,
Centered,
Array,
Substack,
Diagram,
BorderMatrix,
}
impl Node {
pub fn children(&self) -> &[Node] {
match self {
Node::Row { children } => children,
_ => &[],
}
}
pub fn to_json(&self) -> String {
serde_json::to_string(self).expect("the tree always serializes")
}
pub fn row(children: Vec<Node>) -> Node {
Node::Row { children }
}
pub fn simplify(self) -> Node {
match self {
Node::Row { mut children } if children.len() == 1 => children.pop().unwrap().simplify(),
other => other,
}
}
}