use std::io::Cursor;
use neotoma::{
cache::ParsingCache,
literal::Literal,
optional::Optional,
parser::{Parser, Source, parse},
recursive::Recursive,
result::{Error, ParseResult},
utf8class::Utf8Class,
};
#[derive(Debug, Clone, PartialEq)]
enum ArithmeticExpr {
Number(String),
Variable(String),
Real(String, String), BinaryOp {
left: Box<ArithmeticExpr>,
op: String,
right: Box<ArithmeticExpr>,
},
Parenthesized(Box<ArithmeticExpr>),
}
#[derive(Clone)]
struct NumberParser;
impl<Ctx> Parser<Ctx> for NumberParser {
type Output = ArithmeticExpr;
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
_context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: neotoma::parser::Parsable,
{
let integer = Utf8Class::with_min("0123456789", 1);
if let Ok(digits) = integer.parse(source, cache, _context) {
return Ok(ArithmeticExpr::Number(digits));
}
Err(Error::NoMatch)
}
}
#[derive(Clone)]
struct VariableParser;
impl<Ctx> Parser<Ctx> for VariableParser {
type Output = ArithmeticExpr;
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
_context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: neotoma::parser::Parsable,
{
let alpha = Utf8Class::from_predicate_min(|c| c.is_ascii_alphabetic(), 1);
if let Ok(var) = alpha.parse(source, cache, _context) {
return Ok(ArithmeticExpr::Variable(var));
}
Err(Error::NoMatch)
}
}
#[derive(Clone)]
struct RealParser;
impl<Ctx> Parser<Ctx> for RealParser {
type Output = ArithmeticExpr;
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
_context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: neotoma::parser::Parsable,
{
let integer = Utf8Class::with_min("0123456789", 1);
let point = Literal::from_bytes_const(b".");
if let Ok(int_part) = integer.parse(source, cache, _context) {
if point.parse(source, cache, _context).is_ok() {
if let Ok(frac_part) = integer.parse(source, cache, _context) {
return Ok(ArithmeticExpr::Real(int_part, frac_part));
}
}
}
Err(Error::NoMatch)
}
}
struct FactorParser {
expr_parser: Recursive<ArithmeticExpression>,
}
impl FactorParser {
fn new() -> Self {
Self {
expr_parser: Recursive::new(),
}
}
}
impl<Ctx> Parser<Ctx> for FactorParser {
type Output = ArithmeticExpr;
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
_context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: neotoma::parser::Parsable,
{
let whitespace = Optional::new(Utf8Class::whitespace());
let oparen = Literal::from_bytes_const(b"(");
if oparen.parse(source, cache, _context).is_ok() {
let _ = whitespace.parse(source, cache, _context);
if let Ok(inner_expr) = self.expr_parser.parse(source, cache, _context) {
let _ = whitespace.parse(source, cache, _context);
let cparen = Literal::from_bytes_const(b")");
if cparen.parse(source, cache, _context).is_ok() {
return Ok(ArithmeticExpr::Parenthesized(Box::new(inner_expr)));
}
}
}
let real_parser = RealParser;
if let Ok(real_expr) = real_parser.parse(source, cache, _context) {
return Ok(real_expr);
}
let var_parser = VariableParser;
if let Ok(var_expr) = var_parser.parse(source, cache, _context) {
return Ok(var_expr);
}
let num_parser = NumberParser;
if let Ok(num_expr) = num_parser.parse(source, cache, _context) {
return Ok(num_expr);
}
Err(Error::NoMatch)
}
}
struct TermParser {
factor_parser: FactorParser,
}
impl TermParser {
fn new() -> Self {
Self {
factor_parser: FactorParser::new(),
}
}
}
impl<Ctx> Parser<Ctx> for TermParser {
type Output = ArithmeticExpr;
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
_context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: neotoma::parser::Parsable,
{
let whitespace = Optional::new(Utf8Class::whitespace());
let multiply = Literal::from_bytes_const(b"*");
let divide = Literal::from_bytes_const(b"/");
if let Ok(mut left) = self.factor_parser.parse(source, cache, _context) {
let _ = whitespace.parse(source, cache, _context);
if multiply.parse(source, cache, _context).is_ok() {
let _ = whitespace.parse(source, cache, _context);
if let Ok(right) = self.factor_parser.parse(source, cache, _context) {
left = ArithmeticExpr::BinaryOp {
left: Box::new(left),
op: "*".to_string(),
right: Box::new(right),
};
}
}
else if divide.parse(source, cache, _context).is_ok() {
let _ = whitespace.parse(source, cache, _context);
if let Ok(right) = self.factor_parser.parse(source, cache, _context) {
left = ArithmeticExpr::BinaryOp {
left: Box::new(left),
op: "/".to_string(),
right: Box::new(right),
};
}
}
return Ok(left);
}
Err(Error::NoMatch)
}
}
struct ArithmeticExpression {
term_parser: TermParser,
}
impl ArithmeticExpression {
fn new() -> Self {
ArithmeticExpression {
term_parser: TermParser::new(),
}
}
}
impl Default for ArithmeticExpression {
fn default() -> Self {
Self::new()
}
}
impl<Ctx> Parser<Ctx> for ArithmeticExpression {
type Output = ArithmeticExpr;
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
_context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: neotoma::parser::Parsable,
{
let whitespace = Optional::new(Utf8Class::whitespace());
let plus = Literal::from_bytes_const(b"+");
let minus = Literal::from_bytes_const(b"-");
if let Ok(mut left) = self.term_parser.parse(source, cache, _context) {
let _ = whitespace.parse(source, cache, _context);
if plus.parse(source, cache, _context).is_ok() {
let _ = whitespace.parse(source, cache, _context);
if let Ok(right) = self.term_parser.parse(source, cache, _context) {
left = ArithmeticExpr::BinaryOp {
left: Box::new(left),
op: "+".to_string(),
right: Box::new(right),
};
}
}
else if minus.parse(source, cache, _context).is_ok() {
let _ = whitespace.parse(source, cache, _context);
if let Ok(right) = self.term_parser.parse(source, cache, _context) {
left = ArithmeticExpr::BinaryOp {
left: Box::new(left),
op: "-".to_string(),
right: Box::new(right),
};
}
}
return Ok(left);
}
Err(Error::NoMatch)
}
}
#[test]
fn hardcoded_arithmetic_parser() {
let expression = ArithmeticExpression::new();
let cursor = Cursor::new(br#"1 + 2 * 3"#);
let mut source = Source::new(cursor);
let result = parse(expression, &mut source);
assert!(result.is_ok());
if let Ok(expr) = result {
match expr {
ArithmeticExpr::BinaryOp { left, op, right } => {
assert_eq!(op, "+");
if let ArithmeticExpr::Number(n) = *left {
assert_eq!(n, "1");
}
if let ArithmeticExpr::BinaryOp {
left: l2,
op: op2,
right: r2,
} = *right
{
assert_eq!(op2, "*");
if let (ArithmeticExpr::Number(n2), ArithmeticExpr::Number(n3)) = (*l2, *r2) {
assert_eq!(n2, "2");
assert_eq!(n3, "3");
}
}
}
_ => panic!("Expected binary operation"),
}
}
}
#[test]
fn arithmetic_with_parentheses() {
let expression = ArithmeticExpression::new();
let cursor = Cursor::new(br#"(1 + 2) * 3"#);
let mut source = Source::new(cursor);
let result = parse(expression, &mut source);
assert!(result.is_ok());
}
#[test]
fn arithmetic_with_variables() {
let expression = ArithmeticExpression::new();
let cursor = Cursor::new(br#"x + y * z"#);
let mut source = Source::new(cursor);
let result = parse(expression, &mut source);
assert!(result.is_ok());
}
#[test]
fn complex_expression() {
let expression = ArithmeticExpression::new();
let cursor = Cursor::new(br#"1 + 2 + 3 * 3"#);
let mut source = Source::new(cursor);
let result = parse(expression, &mut source);
assert!(
result.is_ok(),
"Complex expression should parse successfully"
);
}
#[test]
fn two_levels_of_nesting() {
let expression = ArithmeticExpression::new();
let cursor = Cursor::new(br#"((1 + 2))"#);
let mut source = Source::new(cursor);
let result = parse(expression, &mut source);
println!("Two levels result: {result:?}");
assert!(
result.is_ok(),
"Two levels of nesting should parse successfully"
);
}
#[test]
fn three_levels_of_nesting() {
let expression = ArithmeticExpression::new();
let cursor = Cursor::new(br#"(((1 + 2)))"#);
let mut source = Source::new(cursor);
let result = parse(expression, &mut source);
assert!(
result.is_ok(),
"Three levels of nesting should parse successfully"
);
}
#[test]
fn five_levels_of_nesting() {
let expression = ArithmeticExpression::new();
let cursor = Cursor::new(br#"((((1 + 2))))"#);
let mut source = Source::new(cursor);
let result = parse(expression, &mut source);
assert!(
result.is_ok(),
"Five levels of nesting should parse successfully"
);
if let Ok(expr) = result {
let mut current = &expr;
let mut depth = 0;
while let ArithmeticExpr::Parenthesized(inner) = current {
depth += 1;
current = inner;
}
assert_eq!(depth, 4, "Should have 4 levels of parentheses nesting");
}
}
#[test]
fn ten_levels_of_nesting() {
let expression = ArithmeticExpression::new();
let cursor = Cursor::new(br#"(((((((((1 + 2)))))))))"#);
let mut source = Source::new(cursor);
let result = parse(expression, &mut source);
assert!(
result.is_ok(),
"Ten levels of nesting should parse successfully"
);
if let Ok(expr) = result {
let mut current = &expr;
let mut depth = 0;
while let ArithmeticExpr::Parenthesized(inner) = current {
depth += 1;
current = inner;
}
assert_eq!(depth, 9, "Should have 9 levels of parentheses nesting");
}
}