Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,465 @@
//! 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]");
}
}