Files
wifi-densepose/vendor/ruvector/examples/scipix/src/math/asciimath.rs

466 lines
15 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! AsciiMath generation from mathematical AST
//!
//! This module converts mathematical AST nodes to AsciiMath notation,
//! a simplified plain-text format for mathematical expressions.
use crate::math::ast::{BinaryOp, BracketType, LargeOpType, MathExpr, MathNode, UnaryOp};
/// AsciiMath generator for mathematical expressions
pub struct AsciiMathGenerator {
/// Use Unicode symbols (true) or ASCII approximations (false)
unicode: bool,
}
impl AsciiMathGenerator {
/// Create a new AsciiMath generator with Unicode support
pub fn new() -> Self {
Self { unicode: true }
}
/// Create an ASCII-only generator
pub fn ascii_only() -> Self {
Self { unicode: false }
}
/// Generate AsciiMath string from a mathematical expression
pub fn generate(&self, expr: &MathExpr) -> String {
self.generate_node(&expr.root, None)
}
/// Generate AsciiMath for a single node
fn generate_node(&self, node: &MathNode, parent_precedence: Option<u8>) -> String {
match node {
MathNode::Symbol { value, .. } => value.clone(),
MathNode::Number { value, .. } => value.clone(),
MathNode::Binary { op, left, right } => {
let precedence = op.precedence();
let needs_parens = parent_precedence.map_or(false, |p| precedence < p);
let left_str = self.generate_node(left, Some(precedence));
let right_str = self.generate_node(
right,
Some(if op.is_left_associative() {
precedence
} else {
precedence + 1
}),
);
let op_str = self.binary_op_to_asciimath(op);
let result = format!("{} {} {}", left_str, op_str, right_str);
if needs_parens {
format!("({})", result)
} else {
result
}
}
MathNode::Unary { op, operand } => {
let op_str = self.unary_op_to_asciimath(op);
let operand_str = self.generate_node(operand, Some(70));
format!("{}{}", op_str, operand_str)
}
MathNode::Fraction {
numerator,
denominator,
} => {
let num_str = self.generate_node(numerator, None);
let den_str = self.generate_node(denominator, None);
format!("({})/({})", num_str, den_str)
}
MathNode::Radical { index, radicand } => {
let rad_str = self.generate_node(radicand, None);
if let Some(idx) = index {
let idx_str = self.generate_node(idx, None);
format!("root({})({} )", idx_str, rad_str)
} else {
format!("sqrt({})", rad_str)
}
}
MathNode::Script {
base,
subscript,
superscript,
} => {
let base_str = self.generate_node(base, Some(65));
let mut result = base_str;
if let Some(sub) = subscript {
let sub_str = self.generate_node(sub, None);
result.push_str(&format!("_{{{}}}", sub_str));
}
if let Some(sup) = superscript {
let sup_str = self.generate_node(sup, None);
result.push_str(&format!("^{{{}}}", sup_str));
}
result
}
MathNode::Function { name, argument } => {
let arg_str = self.generate_node(argument, None);
format!("{}({})", name, arg_str)
}
MathNode::Matrix { rows, .. } => {
let mut content = String::new();
content.push('[');
for (i, row) in rows.iter().enumerate() {
if i > 0 {
content.push_str("; ");
}
for (j, elem) in row.iter().enumerate() {
if j > 0 {
content.push_str(", ");
}
content.push_str(&self.generate_node(elem, None));
}
}
content.push(']');
content
}
MathNode::Group {
content,
bracket_type,
} => {
let content_str = self.generate_node(content, None);
let (open, close) = match bracket_type {
BracketType::Parentheses => ("(", ")"),
BracketType::Brackets => ("[", "]"),
BracketType::Braces => ("{", "}"),
BracketType::AngleBrackets => {
if self.unicode {
("", "")
} else {
("<", ">")
}
}
BracketType::Vertical => ("|", "|"),
BracketType::DoubleVertical => {
if self.unicode {
("", "")
} else {
("||", "||")
}
}
BracketType::Floor => {
if self.unicode {
("", "")
} else {
("|_", "_|")
}
}
BracketType::Ceiling => {
if self.unicode {
("", "")
} else {
("|^", "^|")
}
}
BracketType::None => ("", ""),
};
format!("{}{}{}", open, content_str, close)
}
MathNode::LargeOp {
op_type,
lower,
upper,
content,
} => {
let op_str = self.large_op_to_asciimath(op_type);
let content_str = self.generate_node(content, None);
let mut result = op_str.to_string();
if let Some(low) = lower {
let low_str = self.generate_node(low, None);
result.push_str(&format!("_{{{}}}", low_str));
}
if let Some(up) = upper {
let up_str = self.generate_node(up, None);
result.push_str(&format!("^{{{}}}", up_str));
}
format!("{} {}", result, content_str)
}
MathNode::Sequence { elements } => elements
.iter()
.map(|e| self.generate_node(e, None))
.collect::<Vec<_>>()
.join(", "),
MathNode::Text { content } => {
format!("\"{}\"", content)
}
MathNode::Empty => String::new(),
}
}
/// Convert binary operator to AsciiMath
fn binary_op_to_asciimath<'a>(&self, op: &'a BinaryOp) -> &'a str {
if self.unicode {
match op {
BinaryOp::Add => "+",
BinaryOp::Subtract => "-",
BinaryOp::Multiply => "×",
BinaryOp::Divide => "÷",
BinaryOp::Power => "^",
BinaryOp::Equal => "=",
BinaryOp::NotEqual => "",
BinaryOp::Less => "<",
BinaryOp::Greater => ">",
BinaryOp::LessEqual => "",
BinaryOp::GreaterEqual => "",
BinaryOp::ApproxEqual => "",
BinaryOp::Equivalent => "",
BinaryOp::Similar => "",
BinaryOp::Congruent => "",
BinaryOp::Proportional => "",
BinaryOp::Custom(s) => s,
}
} else {
match op {
BinaryOp::Add => "+",
BinaryOp::Subtract => "-",
BinaryOp::Multiply => "*",
BinaryOp::Divide => "/",
BinaryOp::Power => "^",
BinaryOp::Equal => "=",
BinaryOp::NotEqual => "!=",
BinaryOp::Less => "<",
BinaryOp::Greater => ">",
BinaryOp::LessEqual => "<=",
BinaryOp::GreaterEqual => ">=",
BinaryOp::ApproxEqual => "~~",
BinaryOp::Equivalent => "-=",
BinaryOp::Similar => "~",
BinaryOp::Congruent => "~=",
BinaryOp::Proportional => "prop",
BinaryOp::Custom(s) => s.as_str(),
}
}
}
/// Convert unary operator to AsciiMath
fn unary_op_to_asciimath<'a>(&self, op: &'a UnaryOp) -> &'a str {
match op {
UnaryOp::Plus => "+",
UnaryOp::Minus => "-",
UnaryOp::Not => {
if self.unicode {
"¬"
} else {
"not "
}
}
UnaryOp::Custom(s) => s.as_str(),
}
}
/// Convert large operator to AsciiMath
fn large_op_to_asciimath(&self, op: &LargeOpType) -> &str {
if self.unicode {
match op {
LargeOpType::Sum => "",
LargeOpType::Product => "",
LargeOpType::Integral => "",
LargeOpType::DoubleIntegral => "",
LargeOpType::TripleIntegral => "",
LargeOpType::ContourIntegral => "",
LargeOpType::Union => "",
LargeOpType::Intersection => "",
LargeOpType::Coproduct => "",
LargeOpType::DirectSum => "",
LargeOpType::Custom(_) => "sum",
}
} else {
match op {
LargeOpType::Sum => "sum",
LargeOpType::Product => "prod",
LargeOpType::Integral => "int",
LargeOpType::DoubleIntegral => "iint",
LargeOpType::TripleIntegral => "iiint",
LargeOpType::ContourIntegral => "oint",
LargeOpType::Union => "cup",
LargeOpType::Intersection => "cap",
LargeOpType::Coproduct => "coprod",
LargeOpType::DirectSum => "oplus",
LargeOpType::Custom(_) => "sum",
}
}
}
}
impl Default for AsciiMathGenerator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_number() {
let expr = MathExpr::new(
MathNode::Number {
value: "42".to_string(),
is_decimal: false,
},
1.0,
);
let gen = AsciiMathGenerator::new();
assert_eq!(gen.generate(&expr), "42");
}
#[test]
fn test_addition() {
let expr = MathExpr::new(
MathNode::Binary {
op: BinaryOp::Add,
left: Box::new(MathNode::Number {
value: "1".to_string(),
is_decimal: false,
}),
right: Box::new(MathNode::Number {
value: "2".to_string(),
is_decimal: false,
}),
},
1.0,
);
let gen = AsciiMathGenerator::new();
assert_eq!(gen.generate(&expr), "1 + 2");
}
#[test]
fn test_fraction() {
let expr = MathExpr::new(
MathNode::Fraction {
numerator: Box::new(MathNode::Number {
value: "1".to_string(),
is_decimal: false,
}),
denominator: Box::new(MathNode::Number {
value: "2".to_string(),
is_decimal: false,
}),
},
1.0,
);
let gen = AsciiMathGenerator::new();
assert_eq!(gen.generate(&expr), "(1)/(2)");
}
#[test]
fn test_sqrt() {
let expr = MathExpr::new(
MathNode::Radical {
index: None,
radicand: Box::new(MathNode::Number {
value: "2".to_string(),
is_decimal: false,
}),
},
1.0,
);
let gen = AsciiMathGenerator::new();
assert_eq!(gen.generate(&expr), "sqrt(2)");
}
#[test]
fn test_superscript() {
let expr = MathExpr::new(
MathNode::Script {
base: Box::new(MathNode::Symbol {
value: "x".to_string(),
unicode: Some('x'),
}),
subscript: None,
superscript: Some(Box::new(MathNode::Number {
value: "2".to_string(),
is_decimal: false,
})),
},
1.0,
);
let gen = AsciiMathGenerator::new();
assert_eq!(gen.generate(&expr), "x^{2}");
}
#[test]
fn test_unicode_vs_ascii() {
let expr = MathExpr::new(
MathNode::Binary {
op: BinaryOp::Multiply,
left: Box::new(MathNode::Number {
value: "2".to_string(),
is_decimal: false,
}),
right: Box::new(MathNode::Number {
value: "3".to_string(),
is_decimal: false,
}),
},
1.0,
);
let gen_unicode = AsciiMathGenerator::new();
assert_eq!(gen_unicode.generate(&expr), "2 × 3");
let gen_ascii = AsciiMathGenerator::ascii_only();
assert_eq!(gen_ascii.generate(&expr), "2 * 3");
}
#[test]
fn test_matrix() {
let expr = MathExpr::new(
MathNode::Matrix {
rows: vec![
vec![
MathNode::Number {
value: "1".to_string(),
is_decimal: false,
},
MathNode::Number {
value: "2".to_string(),
is_decimal: false,
},
],
vec![
MathNode::Number {
value: "3".to_string(),
is_decimal: false,
},
MathNode::Number {
value: "4".to_string(),
is_decimal: false,
},
],
],
bracket_type: BracketType::Brackets,
},
1.0,
);
let gen = AsciiMathGenerator::new();
assert_eq!(gen.generate(&expr), "[1, 2; 3, 4]");
}
}