diff --git a/Cargo.toml b/Cargo.toml index ccdc31c..433ee74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "asciimath-rs" description = "AsciiMath parser" repository = "https://github.com/trivernis/asciimath-rs" -version = "0.5.7" +version = "0.5.8" authors = ["trivernis "] edition = "2018" readme = "README.md" diff --git a/src/elements/mod.rs b/src/elements/mod.rs index cc1d152..4304ff0 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -15,6 +15,7 @@ pub enum Element { Special(Special), Group(Group), Accent(ExpressionAccent), + Null, } impl Boxed for Element {} diff --git a/src/format/mathml.rs b/src/format/mathml.rs index 1a8222b..74b4228 100644 --- a/src/format/mathml.rs +++ b/src/format/mathml.rs @@ -586,8 +586,8 @@ impl ToMathML for Root { fn to_mathml(&self) -> String { format!( "{}{}", + self.inner.to_mathml(), self.base.to_mathml(), - self.inner.to_mathml() ) } } @@ -696,6 +696,7 @@ impl ToMathML for Element { Element::Literal(l) => l.to_mathml(), Element::Group(g) => g.to_mathml(), Element::Accent(a) => a.to_mathml(), + Element::Null => "".to_string(), } } } diff --git a/src/lib.rs b/src/lib.rs index 52ff275..5777e57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,363 +36,4 @@ pub fn parse(content: String) -> Expression { } #[cfg(test)] -mod tests { - use crate::elements::group::{Brackets, Group, Matrix, Parentheses, Vector}; - use crate::elements::literal::{Literal, Number}; - use crate::elements::special::{Expression, Special, Sum}; - use crate::elements::Element; - use crate::format::mathml::ToMathML; - use crate::parse; - use crate::parsing::tokenizer::Tokenizer; - use crate::parsing::tree_parser::TreeParser; - use crate::tokens::{Function, Grouping, Misc, Operation, Relation, Text, Token}; - use crate::utils::Boxed; - use std::fs; - - #[test] - fn it_tokenizes_expressions1() { - let expression = "sum_(i=1)^n*sin(x)"; - let mut tokenizer = Tokenizer::new(expression.to_string()); - let tokens = tokenizer.parse(); - assert_eq!( - tokens, - vec![ - Token::Operation(Operation::Sum), - Token::Misc(Misc::Sub), - Token::Grouping(Grouping::RParen), - Token::Text(Text::Symbol("i".to_string())), - Token::Relation(Relation::Eq), - Token::Text(Text::Number("1".to_string())), - Token::Grouping(Grouping::LParen), - Token::Misc(Misc::Pow), - Token::Text(Text::Symbol("n".to_string())), - Token::Operation(Operation::CDot), - Token::Function(Function::Sin), - Token::Grouping(Grouping::RParen), - Token::Text(Text::Symbol("x".to_string())), - Token::Grouping(Grouping::LParen), - ] - ); - } - - #[test] - fn it_tokenizes_expressions2() { - let expression = "G_(11) = 5.16e6 € * (215)/(170) = 6.53e6"; - let mut tokenizer = Tokenizer::new(expression.to_string()); - let tokens = tokenizer.parse(); - assert_eq!( - tokens, - vec![ - Token::Text(Text::Symbol("G".to_string())), - Token::Misc(Misc::Sub), - Token::Grouping(Grouping::RParen), - Token::Text(Text::Number("11".to_string())), - Token::Grouping(Grouping::LParen), - Token::Text(Text::Whitespace), - Token::Relation(Relation::Eq), - Token::Text(Text::Whitespace), - Token::Text(Text::Number("5.16e6".to_string())), - Token::Text(Text::Whitespace), - Token::Text(Text::Symbol("€".to_string())), - Token::Text(Text::Whitespace), - Token::Operation(Operation::CDot), - Token::Text(Text::Whitespace), - Token::Grouping(Grouping::RParen), - Token::Text(Text::Number("215".to_string())), - Token::Grouping(Grouping::LParen), - Token::Misc(Misc::AsciiFrac), - Token::Grouping(Grouping::RParen), - Token::Text(Text::Number("170".to_string())), - Token::Grouping(Grouping::LParen), - Token::Text(Text::Whitespace), - Token::Relation(Relation::Eq), - Token::Text(Text::Whitespace), - Token::Text(Text::Number("6.53e6".to_string())) - ] - ); - } - - #[test] - fn it_tokenizes_expressions3() { - let expression = "[[1, 2],[3, 4]] // \\\n"; - let mut tokenizer = Tokenizer::new(expression.to_string()); - let tokens = tokenizer.parse(); - assert_eq!( - tokens, - vec![ - Token::Grouping(Grouping::RBracket), - Token::Grouping(Grouping::RBracket), - Token::Text(Text::Number("1".to_string())), - Token::Grouping(Grouping::MSep), - Token::Text(Text::Whitespace), - Token::Text(Text::Number("2".to_string())), - Token::Grouping(Grouping::LBracket), - Token::Grouping(Grouping::MSep), - Token::Grouping(Grouping::RBracket), - Token::Text(Text::Number("3".to_string())), - Token::Grouping(Grouping::MSep), - Token::Text(Text::Whitespace), - Token::Text(Text::Number("4".to_string())), - Token::Grouping(Grouping::LBracket), - Token::Grouping(Grouping::LBracket), - Token::Text(Text::Whitespace), - Token::Operation(Operation::Slash), - Token::Text(Text::Whitespace), - Token::Text(Text::NewLine), - ] - ); - } - - #[test] - fn it_tokenizes_text1() { - let expression = "\"just plain text\""; - let mut tokenizer = Tokenizer::new(expression.to_string()); - let tokens = tokenizer.parse(); - assert_eq!( - tokens, - vec![Token::Text(Text::Plain("just plain text".to_string()))] - ) - } - - #[test] - fn it_tokenizes_text2() { - let expression = "\"plain text\" * \"plain text 2\" + a"; - let mut tokenizer = Tokenizer::new(expression.to_string()); - let tokens = tokenizer.parse(); - assert_eq!( - tokens, - vec![ - Token::Text(Text::Plain("plain text".to_string())), - Token::Text(Text::Whitespace), - Token::Operation(Operation::CDot), - Token::Text(Text::Whitespace), - Token::Text(Text::Plain("plain text 2".to_string())), - Token::Text(Text::Whitespace), - Token::Operation(Operation::Plus), - Token::Text(Text::Whitespace), - Token::Text(Text::Symbol("a".to_string())) - ] - ) - } - - #[test] - fn it_parses_into_a_tree1() { - let expression = "sum_2^3"; - let mut tokenizer = Tokenizer::new(expression.to_string()); - let tokens = tokenizer.parse(); - let mut tree_parser = TreeParser::new(tokens.clone()); - let expression = tree_parser.parse(); - let mut test_expression = Expression::new(); - test_expression.add_child(Element::Special(Special::Sum(Sum { - bottom: Some( - Element::Literal(Literal::Number(Number { - number: "2".to_string(), - })) - .boxed(), - ), - top: Some( - Element::Literal(Literal::Number(Number { - number: "3".to_string(), - })) - .boxed(), - ), - }))); - assert_eq!(expression, test_expression) - } - - #[test] - fn it_parses_matrices() { - assert_eq!( - parse("[[1, 2],[3,4]]".to_string()), - Expression { - children: vec![Element::Group(Group::Matrix(Matrix { - inner: vec![ - vec![ - Expression { - children: vec![Element::Literal(Literal::Number(Number { - number: "1".to_string() - })),] - }, - Expression { - children: vec![Element::Literal(Literal::Number(Number { - number: "2".to_string() - })),] - } - ], - vec![ - Expression { - children: vec![Element::Literal(Literal::Number(Number { - number: "3".to_string() - })),] - }, - Expression { - children: vec![Element::Literal(Literal::Number(Number { - number: "4".to_string() - })),] - } - ] - ] - }))] - } - ); - } - - #[test] - fn it_rejects_invalid_matrices() { - assert_eq!( - parse("[[1, 3, 4],[3,4]]".to_string()), - Expression { - children: vec![Element::Group(Group::Brackets(Brackets { - inner: Expression { - children: vec![ - Element::Group(Group::Brackets(Brackets { - inner: Expression { - children: vec![ - Element::Literal(Literal::Number(Number { - number: "1".to_string() - })), - Element::Group(Group::MSep), - Element::Literal(Literal::Number(Number { - number: "3".to_string() - })), - Element::Group(Group::MSep), - Element::Literal(Literal::Number(Number { - number: "4".to_string() - })) - ] - } - .boxed() - })), - Element::Group(Group::MSep), - Element::Group(Group::Brackets(Brackets { - inner: Expression { - children: vec![ - Element::Literal(Literal::Number(Number { - number: "3".to_string() - })), - Element::Group(Group::MSep), - Element::Literal(Literal::Number(Number { - number: "4".to_string() - })) - ] - } - .boxed() - })) - ] - } - .boxed() - }))] - } - ); - assert_eq!( - parse("[[1]]".to_string()), - Expression { - children: vec![Element::Group(Group::Brackets(Brackets { - inner: Expression { - children: vec![Element::Group(Group::Brackets(Brackets { - inner: Expression { - children: vec![Element::Literal(Literal::Number(Number { - number: "1".to_string() - })),] - } - .boxed() - })),] - } - .boxed() - }))] - } - ); - } - - #[test] - fn it_parses_vectors() { - assert_eq!( - parse("((1), (2))(1,2) - f".to_string()), - Expression { - children: vec![ - Element::Group(Group::Vector(Vector { - inner: vec![ - vec![Expression { - children: vec![Element::Literal(Literal::Number(Number { - number: "1".to_string() - }))] - }], - vec![Expression { - children: vec![Element::Literal(Literal::Number(Number { - number: "2".to_string() - }))] - }] - ] - })), - Element::Group(Group::Parentheses(Parentheses { - inner: Expression { - children: vec![ - Element::Literal(Literal::Number(Number { - number: "1".to_string() - })), - Element::Group(Group::MSep), - Element::Literal(Literal::Number(Number { - number: "2".to_string() - })) - ] - } - .boxed() - })), - Element::Literal(Literal::Operation(Operation::Minus)), - Element::Literal(Literal::Function(Function::F)) - ] - } - ); - assert_eq!( - parse("((1, 3), (2, 5))".to_string()), - Expression { - children: vec![Element::Group(Group::Vector(Vector { - inner: vec![ - vec![ - Expression { - children: vec![Element::Literal(Literal::Number(Number { - number: "1".to_string() - }))] - }, - Expression { - children: vec![Element::Literal(Literal::Number(Number { - number: "3".to_string() - }))] - } - ], - vec![ - Expression { - children: vec![Element::Literal(Literal::Number(Number { - number: "2".to_string() - }))] - }, - Expression { - children: vec![Element::Literal(Literal::Number(Number { - number: "5".to_string() - }))] - } - ] - ] - }))] - } - ) - } - - #[allow(dead_code)] - //#[test] - fn it_writes_mathml() { - let str_expression = - "alpha sqrt 1 in NN implies 2^4 + \\\n<=> sum_(k = 1)^3 - ((1),(2))[[2, 3 + 3],[4, 5]] + alpha"; - let expression = parse(str_expression.to_string()); - fs::write( - "test-files/test.html", - format!( - "
{}
{}
{:#?}
", - str_expression, - expression.to_mathml(), - expression, - ), - ) - .unwrap(); - } -} +mod tests; diff --git a/src/parsing/tree_parser.rs b/src/parsing/tree_parser.rs index 9ca0b25..a2d18b5 100644 --- a/src/parsing/tree_parser.rs +++ b/src/parsing/tree_parser.rs @@ -228,21 +228,21 @@ impl TreeParser { Misc::LatexFrac => { self.step(); Element::Special(Special::Frac(Frac { - top: self.parse_element().unwrap().boxed(), - bottom: self.parse_element().unwrap().boxed(), + top: self.parse_element().unwrap_or(Element::Null).boxed(), + bottom: self.parse_element().unwrap_or(Element::Null).boxed(), })) } Misc::Sqrt => { self.step(); Element::Special(Special::Sqrt(Sqrt { - inner: self.parse_element().unwrap().boxed(), + inner: self.parse_element().unwrap_or(Element::Null).boxed(), })) } Misc::Root => { self.step(); - let base = self.parse_element().unwrap().boxed(); + let base = self.parse_element().unwrap_or(Element::Null).boxed(); self.step(); - let inner = self.parse_element().unwrap().boxed(); + let inner = self.parse_element().unwrap_or(Element::Null).boxed(); Element::Special(Special::Root(Root { inner, base })) } Misc::Int => Element::Special(Special::Integral(Integral { @@ -432,7 +432,11 @@ impl TreeParser { self.step(); Some(Pow { base: previous.clone().boxed(), - exp: self.parse_element().unwrap().to_non_enclosed().boxed(), + exp: self + .parse_element() + .unwrap_or(Element::Null) + .to_non_enclosed() + .boxed(), }) } else { None @@ -446,7 +450,11 @@ impl TreeParser { self.step(); Some(Sub { base: previous.clone().boxed(), - lower: self.parse_element().unwrap().to_non_enclosed().boxed(), + lower: self + .parse_element() + .unwrap_or(Element::Null) + .to_non_enclosed() + .boxed(), }) } else { None diff --git a/src/tests/mathml.rs b/src/tests/mathml.rs new file mode 100644 index 0000000..5147401 --- /dev/null +++ b/src/tests/mathml.rs @@ -0,0 +1,11 @@ +use crate::format::mathml::ToMathML; +use crate::parse; + +#[test] +fn it_renders_roots() { + let expr = parse("root 3 16".to_string()); + assert_eq!( + expr.to_mathml(), + "163" + ) +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 0000000..f78f014 --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1,3 @@ +mod mathml; +mod parsing; +mod tokenization; diff --git a/src/tests/parsing.rs b/src/tests/parsing.rs new file mode 100644 index 0000000..ee8e892 --- /dev/null +++ b/src/tests/parsing.rs @@ -0,0 +1,250 @@ +use crate::elements::group::{Brackets, Group, Matrix, Parentheses, Vector}; +use crate::elements::literal::{Literal, Number}; +use crate::elements::special::{Expression, Root, Special, Sum}; +use crate::elements::Element; +use crate::parse; +use crate::parsing::tokenizer::Tokenizer; +use crate::parsing::tree_parser::TreeParser; +use crate::tokens::{Function, Operation}; +use crate::utils::Boxed; + +#[test] +fn it_parses_into_a_tree1() { + let expression = "sum_2^3"; + let mut tokenizer = Tokenizer::new(expression.to_string()); + let tokens = tokenizer.parse(); + let mut tree_parser = TreeParser::new(tokens.clone()); + let expression = tree_parser.parse(); + let mut test_expression = Expression::new(); + test_expression.add_child(Element::Special(Special::Sum(Sum { + bottom: Some( + Element::Literal(Literal::Number(Number { + number: "2".to_string(), + })) + .boxed(), + ), + top: Some( + Element::Literal(Literal::Number(Number { + number: "3".to_string(), + })) + .boxed(), + ), + }))); + assert_eq!(expression, test_expression) +} + +#[test] +fn it_parses_matrices() { + assert_eq!( + parse("[[1, 2],[3,4]]".to_string()), + Expression { + children: vec![Element::Group(Group::Matrix(Matrix { + inner: vec![ + vec![ + Expression { + children: vec![Element::Literal(Literal::Number(Number { + number: "1".to_string() + })),] + }, + Expression { + children: vec![Element::Literal(Literal::Number(Number { + number: "2".to_string() + })),] + } + ], + vec![ + Expression { + children: vec![Element::Literal(Literal::Number(Number { + number: "3".to_string() + })),] + }, + Expression { + children: vec![Element::Literal(Literal::Number(Number { + number: "4".to_string() + })),] + } + ] + ] + }))] + } + ); +} + +#[test] +fn it_rejects_invalid_matrices() { + assert_eq!( + parse("[[1, 3, 4],[3,4]]".to_string()), + Expression { + children: vec![Element::Group(Group::Brackets(Brackets { + inner: Expression { + children: vec![ + Element::Group(Group::Brackets(Brackets { + inner: Expression { + children: vec![ + Element::Literal(Literal::Number(Number { + number: "1".to_string() + })), + Element::Group(Group::MSep), + Element::Literal(Literal::Number(Number { + number: "3".to_string() + })), + Element::Group(Group::MSep), + Element::Literal(Literal::Number(Number { + number: "4".to_string() + })) + ] + } + .boxed() + })), + Element::Group(Group::MSep), + Element::Group(Group::Brackets(Brackets { + inner: Expression { + children: vec![ + Element::Literal(Literal::Number(Number { + number: "3".to_string() + })), + Element::Group(Group::MSep), + Element::Literal(Literal::Number(Number { + number: "4".to_string() + })) + ] + } + .boxed() + })) + ] + } + .boxed() + }))] + } + ); + assert_eq!( + parse("[[1]]".to_string()), + Expression { + children: vec![Element::Group(Group::Brackets(Brackets { + inner: Expression { + children: vec![Element::Group(Group::Brackets(Brackets { + inner: Expression { + children: vec![Element::Literal(Literal::Number(Number { + number: "1".to_string() + })),] + } + .boxed() + })),] + } + .boxed() + }))] + } + ); +} + +#[test] +fn it_parses_vectors() { + assert_eq!( + parse("((1), (2))(1,2) - f".to_string()), + Expression { + children: vec![ + Element::Group(Group::Vector(Vector { + inner: vec![ + vec![Expression { + children: vec![Element::Literal(Literal::Number(Number { + number: "1".to_string() + }))] + }], + vec![Expression { + children: vec![Element::Literal(Literal::Number(Number { + number: "2".to_string() + }))] + }] + ] + })), + Element::Group(Group::Parentheses(Parentheses { + inner: Expression { + children: vec![ + Element::Literal(Literal::Number(Number { + number: "1".to_string() + })), + Element::Group(Group::MSep), + Element::Literal(Literal::Number(Number { + number: "2".to_string() + })) + ] + } + .boxed() + })), + Element::Literal(Literal::Operation(Operation::Minus)), + Element::Literal(Literal::Function(Function::F)) + ] + } + ); + assert_eq!( + parse("((1, 3), (2, 5))".to_string()), + Expression { + children: vec![Element::Group(Group::Vector(Vector { + inner: vec![ + vec![ + Expression { + children: vec![Element::Literal(Literal::Number(Number { + number: "1".to_string() + }))] + }, + Expression { + children: vec![Element::Literal(Literal::Number(Number { + number: "3".to_string() + }))] + } + ], + vec![ + Expression { + children: vec![Element::Literal(Literal::Number(Number { + number: "2".to_string() + }))] + }, + Expression { + children: vec![Element::Literal(Literal::Number(Number { + number: "5".to_string() + }))] + } + ] + ] + }))] + } + ) +} + +#[test] +fn it_parses_roots() { + let expr = parse("root 3 16".to_string()); + assert_eq!( + expr, + Expression { + children: vec![Element::Special(Special::Root(Root { + base: Element::Literal(Literal::Number(Number { + number: "3".to_string() + })) + .boxed(), + inner: Element::Literal(Literal::Number(Number { + number: "16".to_string() + })) + .boxed(), + }))] + } + ); + // test no fail + parse("root 3".to_string()); +} + +#[test] +fn it_parses_functions() { + let expr = parse("sin 10".to_string()); + assert_eq!( + expr, + Expression { + children: vec![ + Element::Literal(Literal::Function(Function::Sin)), + Element::Literal(Literal::Number(Number { + number: "10".to_string() + })) + ] + } + ) +} diff --git a/src/tests/tokenization.rs b/src/tests/tokenization.rs new file mode 100644 index 0000000..4eeea2e --- /dev/null +++ b/src/tests/tokenization.rs @@ -0,0 +1,128 @@ +use crate::parsing::tokenizer::Tokenizer; +use crate::tokens::{Function, Grouping, Misc, Operation, Relation, Text, Token}; + +#[test] +fn it_tokenizes_expressions1() { + let expression = "sum_(i=1)^n*sin(x)"; + let mut tokenizer = Tokenizer::new(expression.to_string()); + let tokens = tokenizer.parse(); + assert_eq!( + tokens, + vec![ + Token::Operation(Operation::Sum), + Token::Misc(Misc::Sub), + Token::Grouping(Grouping::RParen), + Token::Text(Text::Symbol("i".to_string())), + Token::Relation(Relation::Eq), + Token::Text(Text::Number("1".to_string())), + Token::Grouping(Grouping::LParen), + Token::Misc(Misc::Pow), + Token::Text(Text::Symbol("n".to_string())), + Token::Operation(Operation::CDot), + Token::Function(Function::Sin), + Token::Grouping(Grouping::RParen), + Token::Text(Text::Symbol("x".to_string())), + Token::Grouping(Grouping::LParen), + ] + ); +} + +#[test] +fn it_tokenizes_expressions2() { + let expression = "G_(11) = 5.16e6 € * (215)/(170) = 6.53e6"; + let mut tokenizer = Tokenizer::new(expression.to_string()); + let tokens = tokenizer.parse(); + assert_eq!( + tokens, + vec![ + Token::Text(Text::Symbol("G".to_string())), + Token::Misc(Misc::Sub), + Token::Grouping(Grouping::RParen), + Token::Text(Text::Number("11".to_string())), + Token::Grouping(Grouping::LParen), + Token::Text(Text::Whitespace), + Token::Relation(Relation::Eq), + Token::Text(Text::Whitespace), + Token::Text(Text::Number("5.16e6".to_string())), + Token::Text(Text::Whitespace), + Token::Text(Text::Symbol("€".to_string())), + Token::Text(Text::Whitespace), + Token::Operation(Operation::CDot), + Token::Text(Text::Whitespace), + Token::Grouping(Grouping::RParen), + Token::Text(Text::Number("215".to_string())), + Token::Grouping(Grouping::LParen), + Token::Misc(Misc::AsciiFrac), + Token::Grouping(Grouping::RParen), + Token::Text(Text::Number("170".to_string())), + Token::Grouping(Grouping::LParen), + Token::Text(Text::Whitespace), + Token::Relation(Relation::Eq), + Token::Text(Text::Whitespace), + Token::Text(Text::Number("6.53e6".to_string())) + ] + ); +} + +#[test] +fn it_tokenizes_expressions3() { + let expression = "[[1, 2],[3, 4]] // \\\n"; + let mut tokenizer = Tokenizer::new(expression.to_string()); + let tokens = tokenizer.parse(); + assert_eq!( + tokens, + vec![ + Token::Grouping(Grouping::RBracket), + Token::Grouping(Grouping::RBracket), + Token::Text(Text::Number("1".to_string())), + Token::Grouping(Grouping::MSep), + Token::Text(Text::Whitespace), + Token::Text(Text::Number("2".to_string())), + Token::Grouping(Grouping::LBracket), + Token::Grouping(Grouping::MSep), + Token::Grouping(Grouping::RBracket), + Token::Text(Text::Number("3".to_string())), + Token::Grouping(Grouping::MSep), + Token::Text(Text::Whitespace), + Token::Text(Text::Number("4".to_string())), + Token::Grouping(Grouping::LBracket), + Token::Grouping(Grouping::LBracket), + Token::Text(Text::Whitespace), + Token::Operation(Operation::Slash), + Token::Text(Text::Whitespace), + Token::Text(Text::NewLine), + ] + ); +} + +#[test] +fn it_tokenizes_text1() { + let expression = "\"just plain text\""; + let mut tokenizer = Tokenizer::new(expression.to_string()); + let tokens = tokenizer.parse(); + assert_eq!( + tokens, + vec![Token::Text(Text::Plain("just plain text".to_string()))] + ) +} + +#[test] +fn it_tokenizes_text2() { + let expression = "\"plain text\" * \"plain text 2\" + a"; + let mut tokenizer = Tokenizer::new(expression.to_string()); + let tokens = tokenizer.parse(); + assert_eq!( + tokens, + vec![ + Token::Text(Text::Plain("plain text".to_string())), + Token::Text(Text::Whitespace), + Token::Operation(Operation::CDot), + Token::Text(Text::Whitespace), + Token::Text(Text::Plain("plain text 2".to_string())), + Token::Text(Text::Whitespace), + Token::Operation(Operation::Plus), + Token::Text(Text::Whitespace), + Token::Text(Text::Symbol("a".to_string())) + ] + ) +}