use crate::formula::ast::{MathNode, Operator, Fence, LargeOperator};
use smallvec::SmallVec;
pub type TemplateArgs<'a> = SmallVec<[SmallVec<[MathNode<'a>; 8]>; 4]>;
pub struct TemplateParser;
#[derive(Debug)]
pub struct TemplateDef {
pub selector: u8,
pub variation: u16,
#[allow(dead_code)]
pub description: &'static str,
pub template: &'static str,
}
const MTEF_TEMPLATES: &[TemplateDef] = &[
TemplateDef { selector: 0, variation: 1, description: "fence: angle-left only", template: "\\left\\langle #1[M]\\right. " },
TemplateDef { selector: 0, variation: 2, description: "fence: angle-right only", template: "\\left. #1[M]\\right\\rangle " },
TemplateDef { selector: 0, variation: 3, description: "fence: angle-both", template: "\\left\\langle #1[M]\\right\\rangle " },
TemplateDef { selector: 1, variation: 1, description: "fence: paren-left only", template: "\\left( #1[M]\\right. " },
TemplateDef { selector: 1, variation: 2, description: "fence: paren-right only", template: "\\left. #1[M]\\right) " },
TemplateDef { selector: 1, variation: 3, description: "fence: paren-both", template: "\\left( #1[M]\\right) " },
TemplateDef { selector: 2, variation: 1, description: "fence: brace-left only", template: "\\left\\{ #1[M]\\right. " },
TemplateDef { selector: 2, variation: 2, description: "fence: brace-right only", template: "\\left. #1[M]\\right\\} " },
TemplateDef { selector: 2, variation: 3, description: "fence: brace-both", template: "\\left\\{ #1[M]\\right\\} " },
TemplateDef { selector: 3, variation: 1, description: "fence: brack-left only", template: "\\lef]t[ #1[M]\\right. " },
TemplateDef { selector: 3, variation: 2, description: "fence: brack-right only", template: "\\left. #1[M]\\right] " },
TemplateDef { selector: 3, variation: 3, description: "fence: brack-both", template: "\\left[ #1[M]\\right] " },
TemplateDef { selector: 4, variation: 1, description: "fence: bar-left only", template: "\\left| #1[M]\\right. " },
TemplateDef { selector: 4, variation: 2, description: "fence: bar-right only", template: "\\left. #1[M]\\right| " },
TemplateDef { selector: 4, variation: 3, description: "fence: bar-both", template: "\\left| #1[M]\\right| " },
TemplateDef { selector: 5, variation: 1, description: "fence: dbar-left only", template: "\\left\\| #1[M]\\right. " },
TemplateDef { selector: 5, variation: 2, description: "fence: dbar-right only", template: "\\left. #1[M]\\right\\| " },
TemplateDef { selector: 5, variation: 3, description: "fence: dbar-both", template: "\\left\\| #1[M]\\right\\| " },
TemplateDef { selector: 6, variation: 1, description: "fence: floor", template: "\\left\\lfloor #1[M]\\right. " },
TemplateDef { selector: 6, variation: 2, description: "fence: floor", template: "\\left. #1[M]\\right\\rfloor " },
TemplateDef { selector: 6, variation: 3, description: "fence: floor", template: "\\left\\lfloor #1[M]\\right\\rfloor " },
TemplateDef { selector: 7, variation: 1, description: "fence: ceiling", template: "\\left\\lceil #1[M]\\right. " },
TemplateDef { selector: 7, variation: 2, description: "fence: ceiling", template: "\\left. #1[M]\\right\\rceil " },
TemplateDef { selector: 7, variation: 3, description: "fence: ceiling", template: "\\left\\lceil #1[M]\\right\\rceil " },
TemplateDef { selector: 8, variation: 0, description: "fence: LBLB", template: "\\left[ #1[M]\\right[ " },
TemplateDef { selector: 9, variation: 0, description: "fence: LPLP", template: "\\left( #1[M]\\right( " },
TemplateDef { selector: 9, variation: 1, description: "fence: RPLP", template: "\\left) #1[M]\\right( " },
TemplateDef { selector: 9, variation: 2, description: "fence: LBLP", template: "\\left[ #1[M]\\right( " },
TemplateDef { selector: 9, variation: 3, description: "fence: RBLP", template: "\\left] #1[M]\\right( " },
TemplateDef { selector: 9, variation: 16, description: "fence: LPRP", template: "\\left( #1[M]\\right) " },
TemplateDef { selector: 9, variation: 17, description: "fence: RPRP", template: "\\left) #1[M]\\right) " },
TemplateDef { selector: 9, variation: 18, description: "fence: LBRP", template: "\\left[ #1[M]\\right) " },
TemplateDef { selector: 9, variation: 19, description: "fence: RBRP", template: "\\left] #1[M]\\right) " },
TemplateDef { selector: 9, variation: 32, description: "fence: LPLB", template: "\\left( #1[M]\\right[ " },
TemplateDef { selector: 9, variation: 33, description: "fence: RPLB", template: "\\left) #1[M]\\right[ " },
TemplateDef { selector: 9, variation: 34, description: "fence: LBLB", template: "\\left[ #1[M]\\right[ " },
TemplateDef { selector: 9, variation: 35, description: "fence: RBLB", template: "\\left] #1[M]\\right[ " },
TemplateDef { selector: 9, variation: 48, description: "fence: LPRB", template: "\\left( #1[M]\\right] " },
TemplateDef { selector: 9, variation: 49, description: "fence: RPRB", template: "\\left) #1[M]\\right] " },
TemplateDef { selector: 9, variation: 50, description: "fence: LBRB", template: "\\left[ #1[M]\\right] " },
TemplateDef { selector: 9, variation: 51, description: "fence: RBRB", template: "\\left] #1[M]\\right] " },
TemplateDef { selector: 10, variation: 0, description: "root: sqroot", template: "\\sqrt{#1[M]} " },
TemplateDef { selector: 10, variation: 1, description: "root: nthroot", template: "\\sqrt[#2[M]]{#1[M]} " },
TemplateDef { selector: 11, variation: 0, description: "fract: tmfract", template: "\\frac{#1[M]}{#2[M]} " },
TemplateDef { selector: 11, variation: 1, description: "fract: smfract", template: "\\frac{#1[M]}{#2[M]} " },
TemplateDef { selector: 11, variation: 2, description: "fract: slfract", template: "{#1[M]}/{#2[M]} " },
TemplateDef { selector: 11, variation: 3, description: "fract: slfract", template: "{#1[M]}/{#2[M]} " },
TemplateDef { selector: 11, variation: 4, description: "fract: slfract", template: "{#1[M]}/{#2[M]} " },
TemplateDef { selector: 11, variation: 5, description: "fract: smfract", template: "\\frac{#1[M]}{#2[M]} " },
TemplateDef { selector: 11, variation: 6, description: "fract: slfract", template: "{#1[M]}/{#2[M]} " },
TemplateDef { selector: 11, variation: 7, description: "fract: slfract", template: "{#1[M]}/{#2[M]} " },
TemplateDef { selector: 12, variation: 0, description: "ubar: subar", template: "\\underline{#1[M]} " },
TemplateDef { selector: 12, variation: 1, description: "ubar: dubar", template: "\\underline{\\underline{#1[M]}} " },
TemplateDef { selector: 13, variation: 0, description: "obar: sobar", template: "\\overline{#1[M]} " },
TemplateDef { selector: 13, variation: 1, description: "obar: dobar", template: "\\overline{\\overline{#1[M]}} " },
TemplateDef { selector: 14, variation: 0, description: "larrow: box on top", template: "\\stackrel{#1[M]}{\\longleftarrow} " },
TemplateDef { selector: 14, variation: 1, description: "larrow: box below ", template: "\\stackunder{#1[M]}{\\longleftarrow} " },
TemplateDef { selector: 14, variation: 0, description: "rarrow: box on top", template: "\\stackrel{#1[M]}{\\longrightarrow} " },
TemplateDef { selector: 14, variation: 1, description: "rarrow: box below ", template: "\\stackunder{#1[M]}{\\longrightarrow} " },
TemplateDef { selector: 14, variation: 0, description: "barrow: box on top", template: "\\stackrel{#1[M]}{\\longleftrightarrow} " },
TemplateDef { selector: 14, variation: 1, description: "barrow: box below ", template: "\\stackunder{#1[M]}{\\longleftrightarrow} " },
TemplateDef { selector: 15, variation: 0, description: "integrals: single - no limits", template: "\\int #1[M] " },
TemplateDef { selector: 15, variation: 1, description: "integrals: single - both", template: "\\int\\nolimits#2[L][STARTSUB][ENDSUB]#3[L][STARTSUP][ENDSUP]#1[M] " },
TemplateDef { selector: 15, variation: 2, description: "integrals: double - both", template: "\\iint\\nolimits#2[L][STARTSUB][ENDSUB]#3[L][STARTSUP][ENDSUP]#1[M] " },
TemplateDef { selector: 15, variation: 3, description: "integrals: triple - both", template: "\\iiint\\nolimits#2[L][STARTSUB][ENDSUB]#3[L][STARTSUP][ENDSUP]#1[M] " },
TemplateDef { selector: 15, variation: 4, description: "integrals: contour - no limits", template: "\\oint #1[M] " },
TemplateDef { selector: 15, variation: 8, description: "integrals: contour - no limits", template: "\\oint #1[M] " },
TemplateDef { selector: 15, variation: 12, description: "integrals: contour - no limits", template: "\\oint #1[M] " },
TemplateDef { selector: 16, variation: 0, description: "sum: limits top/bottom - both", template: "\\sum\\limits#2[L][STARTSUB][ENDSUB]#3[L][STARTSUP][ENDSUP]#1[M] " },
TemplateDef { selector: 17, variation: 0, description: "product: limits top/bottom - both", template: "\\prod\\limits#2[L][STARTSUB][ENDSUB]#3[L][STARTSUP][ENDSUP]#1[M] " },
TemplateDef { selector: 18, variation: 0, description: "coproduct: limits top/bottom - both", template: "\\dcoprod\\limits#2[L][STARTSUB][ENDSUB]#3[L][STARTSUP][ENDSUP]#1[M] " },
TemplateDef { selector: 19, variation: 0, description: "union: limits top/bottom - both", template: "\\dbigcup\\limits#2[L][STARTSUB][ENDSUB]#3[L][STARTSUP][ENDSUP]#1[M] " },
TemplateDef { selector: 20, variation: 0, description: "intersection: limits top/bottom - both", template: "\\dbigcap\\limits#2[L][STARTSUB][ENDSUB]#3[L][STARTSUP][ENDSUP]#1[M] " },
TemplateDef { selector: 21, variation: 0, description: "integrals: single - both", template: "\\int#2[L][STARTSUB][ENDSUB]#3[L][STARTSUP][ENDSUP]#1[M] " },
TemplateDef { selector: 22, variation: 0, description: "sum: single - both", template: "\\sum#2[L][STARTSUB][ENDSUB]#3[L][STARTSUP][ENDSUP]#1[M] " },
TemplateDef { selector: 23, variation: 0, description: "limit: both", template: "#1 #2[L][STARTSUB][ENDSUB]#3[L][STARTSUP][ENDSUP] " },
TemplateDef { selector: 24, variation: 0, description: "horizontal brace: lower", template: "\\stackunder{#2[M]}{\\underbrace{#1[M]}} " },
TemplateDef { selector: 24, variation: 1, description: "horizontal brace: upper", template: "\\stackrel{#2[M]}{\\overbrace{#1[M]}} " },
TemplateDef { selector: 25, variation: 0, description: "horizontal brace: lower", template: "\\stackunder{#2[M]}{\\underbrace{#1[M]}} " },
TemplateDef { selector: 25, variation: 1, description: "horizontal brace: upper", template: "\\stackrel{#2[M]}{\\overbrace{#1[M]}} " },
TemplateDef { selector: 25, variation: 0, description: "hbracket", template: " " },
TemplateDef { selector: 27, variation: 0, description: "script: sub", template: "#1[L][STARTSUB][ENDSUB] " },
TemplateDef { selector: 27, variation: 1, description: "script: sub", template: "#1[L][STARTSUB][ENDSUB] " },
TemplateDef { selector: 28, variation: 0, description: "script: super", template: "#2[L][STARTSUP][ENDSUP] " },
TemplateDef { selector: 28, variation: 1, description: "script: super", template: "#2[L][STARTSUP][ENDSUP] " },
TemplateDef { selector: 29, variation: 0, description: "script: subsup", template: "#1[L][STARTSUB][ENDSUB]#2[L][STARTSUP][ENDSUP] " },
];
impl TemplateParser {
pub fn find_template(selector: u8, variation: u16) -> Option<&'static TemplateDef> {
MTEF_TEMPLATES.iter().find(|t| t.selector == selector && t.variation == variation)
}
pub fn parse_template_arguments<'a>(template: &str, args: &TemplateArgs<'a>) -> MathNode<'a> {
let mut result = String::new();
let mut chars = template.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '#' {
if let Some(digit) = chars.next().and_then(|c| c.to_digit(10)) {
let arg_index = digit as usize - 1;
if chars.next() == Some('[') {
for c in chars.by_ref() {
if c == ']' {
break;
}
}
}
if arg_index < args.len() {
let mut arg_text = String::new();
for node in &args[arg_index] {
match node {
MathNode::Text(text) => arg_text.push_str(text),
MathNode::Number(num) => arg_text.push_str(num),
MathNode::Symbol(sym) => {
if let Some(unicode) = sym.unicode {
arg_text.push(unicode);
} else {
arg_text.push_str(&sym.name);
}
}
_ => arg_text.push('?'), }
}
result.push_str(&arg_text);
}
} else {
result.push('#');
}
} else if ch == '[' {
let mut marker = String::new();
for c in chars.by_ref() {
if c == ']' {
break;
}
marker.push(c);
}
match marker.as_str() {
"STARTSUB" => result.push_str("_{"),
"ENDSUB" => result.push('}'),
"STARTSUP" => result.push_str("^{"),
"ENDSUP" => result.push('}'),
_ => {
result.push('[');
result.push_str(&marker);
result.push(']');
}
}
} else {
result.push(ch);
}
}
Self::parse_latex_to_ast(&result, args)
}
fn parse_latex_to_ast<'a>(latex: &str, args: &TemplateArgs<'a>) -> MathNode<'a> {
let latex = latex.trim();
if latex.starts_with("\\frac{")
&& latex.contains("}{")
&& latex[latex.find("}{").unwrap() + 2..].find('}').is_some() {
let mut numerator = Vec::new();
let mut denominator = Vec::new();
if args.len() >= 2 {
numerator = args[0].iter().cloned().collect();
denominator = args[1].iter().cloned().collect();
}
return MathNode::Frac {
numerator,
denominator,
line_thickness: None,
frac_type: None,
};
}
if latex.starts_with("\\sqrt") {
if latex.starts_with("\\sqrt[") {
if let Some(rel_pos) = latex.strip_prefix("\\sqrt[").and_then(|s| s.find("]{")) {
let abs_pos = 6 + rel_pos;
if latex[abs_pos + 2..].find('}').is_some() {
let mut base = Vec::new();
let mut index = Vec::new();
if !args.is_empty() {
base = args[0].iter().cloned().collect();
}
if args.len() >= 2 {
index = args[1].iter().cloned().collect();
}
return MathNode::Root {
base,
index: Some(index),
};
}
}
} else if latex.starts_with("\\sqrt{")
&& latex[6..].find('}').is_some() {
let mut base = Vec::new();
if !args.is_empty() {
base = args[0].iter().cloned().collect();
}
return MathNode::Root {
base,
index: None,
};
}
}
if latex.contains("\\sum") || latex.contains("\\prod") || latex.contains("\\int") {
let operator = if latex.contains("\\sum") {
LargeOperator::Sum
} else if latex.contains("\\prod") {
LargeOperator::Product
} else {
LargeOperator::Integral
};
let mut lower_limit = None;
let mut upper_limit = None;
let mut integrand = None;
if let Some(sub_start) = latex.find("_{")
&& latex[sub_start + 2..].find('}').is_some()
&& args.len() >= 2 {
lower_limit = Some(args[1].iter().cloned().collect());
}
if let Some(sup_start) = latex.find("^{")
&& latex[sup_start + 2..].find('}').is_some()
&& args.len() >= 3 {
upper_limit = Some(args[2].iter().cloned().collect());
}
if !args.is_empty() {
integrand = Some(args[0].iter().cloned().collect());
}
return MathNode::LargeOp {
operator,
lower_limit,
upper_limit,
integrand,
hide_lower: false,
hide_upper: false,
};
}
if latex.contains("_{") && latex.contains("^{") {
let mut base = Vec::new();
let mut subscript = Vec::new();
let mut superscript = Vec::new();
if !args.is_empty() {
base = args[0].iter().cloned().collect();
}
if args.len() >= 2 {
subscript = args[1].iter().cloned().collect();
}
if args.len() >= 3 {
superscript = args[2].iter().cloned().collect();
}
return MathNode::SubSup {
base,
subscript,
superscript,
};
} else if latex.contains("_{") {
let mut base = Vec::new();
let mut subscript = Vec::new();
if !args.is_empty() {
base = args[0].iter().cloned().collect();
}
if args.len() >= 2 {
subscript = args[1].iter().cloned().collect();
}
return MathNode::Sub {
base,
subscript,
};
} else if latex.contains("^{") {
let mut base = Vec::new();
let mut exponent = Vec::new();
if !args.is_empty() {
base = args[0].iter().cloned().collect();
}
if args.len() >= 2 {
exponent = args[1].iter().cloned().collect();
}
return MathNode::Power {
base,
exponent,
};
}
if latex.contains("\\left") && latex.contains("\\right") {
if let Some(left_pos) = latex.find("\\left")
&& let Some(right_pos) = latex.find("\\right") {
let content_start = latex[left_pos..].find('{').map(|p| left_pos + p + 1).unwrap_or(left_pos + 6);
let content_end = right_pos;
if content_start < content_end {
let open_fence = if latex.contains("\\left(") {
Fence::Paren
} else if latex.contains("\\left[") {
Fence::Bracket
} else if latex.contains("\\left{") {
Fence::Brace
} else if latex.contains("\\left|") {
Fence::Pipe
} else {
Fence::Paren };
let close_fence = if latex.contains("\\right)") {
Fence::Paren
} else if latex.contains("\\right]") {
Fence::Bracket
} else if latex.contains("\\right}") {
Fence::Brace
} else if latex.contains("\\right|") {
Fence::Pipe
} else {
Fence::Paren };
let mut content = Vec::new();
if !args.is_empty() {
content = args[0].iter().cloned().collect();
}
return MathNode::Fenced {
open: open_fence,
content,
close: close_fence,
separator: None,
};
}
}
}
MathNode::Text(latex.to_string().into())
}
pub fn parse_fraction<'a>(
numerator: Vec<MathNode<'a>>,
denominator: Vec<MathNode<'a>>,
) -> MathNode<'a> {
MathNode::Frac {
numerator,
denominator,
line_thickness: None,
frac_type: None,
}
}
#[allow(dead_code)] pub fn parse_slash<'a>(
numerator: Vec<MathNode<'a>>,
denominator: Vec<MathNode<'a>>,
) -> MathNode<'a> {
MathNode::Row(vec![
MathNode::Row(numerator),
MathNode::Operator(Operator::Divide),
MathNode::Row(denominator),
])
}
pub fn parse_root<'a>(
base: Vec<MathNode<'a>>,
index: Option<Vec<MathNode<'a>>>,
) -> MathNode<'a> {
MathNode::Root {
base,
index,
}
}
pub fn parse_subscript<'a>(
base: Vec<MathNode<'a>>,
subscript: Vec<MathNode<'a>>,
) -> MathNode<'a> {
MathNode::Sub {
base,
subscript,
}
}
pub fn parse_superscript<'a>(
base: Vec<MathNode<'a>>,
superscript: Vec<MathNode<'a>>,
) -> MathNode<'a> {
MathNode::Power {
base,
exponent: superscript,
}
}
pub fn parse_subsup<'a>(
base: Vec<MathNode<'a>>,
subscript: Vec<MathNode<'a>>,
superscript: Vec<MathNode<'a>>,
) -> MathNode<'a> {
MathNode::SubSup {
base,
subscript,
superscript,
}
}
#[allow(dead_code)] pub fn parse_below<'a>(
base: Vec<MathNode<'a>>,
script: Vec<MathNode<'a>>,
) -> MathNode<'a> {
MathNode::Under {
base,
under: script,
position: None,
}
}
#[allow(dead_code)] pub fn parse_above<'a>(
base: Vec<MathNode<'a>>,
script: Vec<MathNode<'a>>,
) -> MathNode<'a> {
MathNode::Over {
base,
over: script,
position: None,
}
}
#[allow(dead_code)] pub fn parse_below_above<'a>(
base: Vec<MathNode<'a>>,
below: Vec<MathNode<'a>>,
above: Vec<MathNode<'a>>,
) -> MathNode<'a> {
MathNode::UnderOver {
base,
under: below,
over: above,
position: None,
}
}
pub fn parse_large_op<'a>(
operator: LargeOperator,
lower_limit: Vec<MathNode<'a>>,
upper_limit: Vec<MathNode<'a>>,
integrand: Vec<MathNode<'a>>,
) -> MathNode<'a> {
MathNode::LargeOp {
operator,
lower_limit: if lower_limit.is_empty() { None } else { Some(lower_limit) },
upper_limit: if upper_limit.is_empty() { None } else { Some(upper_limit) },
integrand: if integrand.is_empty() { None } else { Some(integrand) },
hide_lower: false,
hide_upper: false,
}
}
pub fn parse_fence<'a>(
fence: Fence,
content: Vec<MathNode<'a>>,
) -> MathNode<'a> {
MathNode::Fenced {
open: fence,
content,
close: fence,
separator: None,
}
}
#[allow(dead_code)] pub fn large_op_from_selector(selector: u8) -> Option<LargeOperator> {
match selector {
15 => Some(LargeOperator::Integral), 16 => Some(LargeOperator::Sum), 17 => Some(LargeOperator::Product), 18 => Some(LargeOperator::Coproduct), 19 => Some(LargeOperator::Union), 20 => Some(LargeOperator::Intersection), 21 => Some(LargeOperator::Integral), 22 => Some(LargeOperator::Sum), 23 => Some(LargeOperator::Integral), _ => None,
}
}
#[allow(dead_code)] pub fn fence_from_selector(selector: u8) -> Option<Fence> {
match selector {
1 => Some(Fence::Paren), 3 => Some(Fence::Bracket), 2 => Some(Fence::Brace), 4 => Some(Fence::Pipe), 5 => Some(Fence::DoublePipe), _ => None,
}
}
}