Skip to content

Commit

Permalink
feat: reduce redundancy of grouping symbols (#11)
Browse files Browse the repository at this point in the history
In some cases groupings are used to indicate that a certain part of
expression should be interpreted as a single term. For example in
fractions: `(a + b) / (c + d)`, or when using scripts `x^(n-1)` and so
on.

In such cases, the grouping symbols don't have to be rendered. For
example $\frac{a + b}{c + d} \equiv \frac{(a + b)}{(c + d)}$. The same
holds for the scripts: $x^{n-1} \equiv x^{(n-1)}$.

This PR solves that problem. Also, some structures are simplified by
using `SimpleExpr::Interm` instead of `SimpleExpr::Grouping` with
ignored grouping symbols. This makes snapshots somewhat simpler.

Additionally, block rendering in binary is now supported with `-b,
--block` flag.
  • Loading branch information
nfejzic committed Dec 22, 2023
1 parent 74824f4 commit 176696b
Show file tree
Hide file tree
Showing 14 changed files with 133 additions and 124 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ license = "Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
alemat = "0.6.1"
alemat = "0.7.0"

[dev-dependencies]
insta = "1.34.0"
Expand Down
2 changes: 2 additions & 0 deletions src/lexer/keywords/operators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ generate_impl!(
Operators,
"+" => Plus,
"-" => Minus,
"'" => Prime,
"*" | "cdot" => Dot,
"**" | "ast" => Asterisk,
"***" | "star" => Star,
Expand Down Expand Up @@ -50,6 +51,7 @@ impl From<Operator> for alemat::elements::Operator {
match value {
self::Operator::Plus => Operator::plus(),
self::Operator::Minus => Operator::minus(),
self::Operator::Prime => Operator::from("'"),
self::Operator::Dot => Operator::dot(),
self::Operator::Asterisk => Operator::asterisk(),
self::Operator::Star => Operator::star(),
Expand Down
2 changes: 0 additions & 2 deletions src/lexer/keywords/others.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ generate_impl!(
Other,
Others,
"," => Comma,
"'" => Prime,
"frac" => Fraction,
"/" => ForwardSlash,
"^" => Power,
Expand Down Expand Up @@ -71,7 +70,6 @@ impl From<Other> for Element {
fn from(value: Other) -> Self {
match value {
Other::Comma => Operator::from(",").into(),
Other::Prime => Operator::from("'").into(),
Other::ForwardSlash => Operator::from("/").into(),
Other::Integral => Operator::integral().into(),
Other::OIntegral => Operator::circle_integral().into(),
Expand Down
7 changes: 6 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ where

/// Write an abstract syntax tree into the [`Writer`]. The resulting output is controlled by the
/// implementation of passed in [`Writer`].
///
/// # Errors
///
/// The [`Writer`] may fail to write the mathml. In such case the error defined by the [`Writer`]
/// implementation is returned.
pub fn write_mathml<'w, W>(
ascii_math: AsciiMath<'_>,
writer: &'w mut W,
Expand All @@ -36,7 +41,7 @@ where
Ok(writer)
}

/// Render the abstract syntax tree into a string of MathMl.
/// Render the abstract syntax tree into a string of mathml.
pub fn render_mathml(ascii_math: AsciiMath<'_>) -> String {
let mathml = MathMl::from(ascii_math);
mathml.render().expect("BufMathMlWriter does not fail.")
Expand Down
31 changes: 26 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
use alemat::{BufMathMlWriter, Writer};
use alemat::{BufMathMlWriter, DisplayAttr, MathMl, MathMlAttr, Writer};

fn main() {
let mut args = std::env::args();
let mut args = std::env::args().peekable();
args.next(); // skip program name

let input = args.nth(1).unwrap();
let is_block = match args.peek() {
Some(arg) => {
dbg!(arg);
matches!(arg.as_str(), "--block" | "-b")
}
None => false,
};

if is_block {
// skip blocks argument
args.next();
}

let input = args.next().unwrap();

let ascii_math = mathemascii::parse(&input);

let math_ml = mathemascii::write_mathml(ascii_math, &mut BufMathMlWriter::default())
.map(|w| w.finish())
let mut math = MathMl::from(ascii_math);

if is_block {
math.add_attr(MathMlAttr::Display(DisplayAttr::Block));
}

let math_ml = math
.write(&mut BufMathMlWriter::default())
.map(Writer::finish)
.unwrap();

println!("{math_ml}");
Expand Down
60 changes: 40 additions & 20 deletions src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,17 @@ pub enum SimpleExpr {

/// Unary operator applied to an expression, i.e. `sqrt(a)`.
Unary(Unary),

/// Binary operator applied to two expressions, i.e. `root(3)(a + b)`.
Binary(Binary),

/// Intermediate expression is simply a wrapped [`Expression`].
/// AsciiMath differs Expression and Intermediate expression, but in this implementation they
/// are the same. The top-level expression defined in ascii math grammar is the [`AsciiMath`]
/// iterator that produces multiple [`Expression`]s.
///
/// [`AsciiMath`]: crate::AsciiMath
Interm(Box<Expression>),
}

impl SimpleExpr {
Expand All @@ -40,6 +49,7 @@ impl SimpleExpr {
SimpleExpr::Grouping(GroupingExpr { ref span, .. }) => *span,
SimpleExpr::Unary(unary) => unary.span(),
SimpleExpr::Binary(binary) => binary.span(),
SimpleExpr::Interm(inner) => inner.span(),
}
}

Expand Down Expand Up @@ -81,15 +91,15 @@ impl SimpleExpr {
/// The main AsciiMath expression.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Expression {
pub(crate) val: SimpleExpr,
pub(crate) interm: SimpleExpr,
pub(crate) subscript: Option<SimpleExpr>,
pub(crate) supscript: Option<SimpleExpr>,
}

impl Expression {
/// Returns the [`Span`] of the expression.
pub fn span(&self) -> Span {
let span = self.val.span();
let span = self.interm.span();
let start = span.start;
let mut end = span.end;

Expand All @@ -100,27 +110,36 @@ impl Expression {
Span { start, end }
}

pub(crate) fn into_interm_with(self, f: impl FnOnce(SimpleExpr) -> SimpleExpr) -> SimpleExpr {
f(self.interm)
}

/// Checks whether the expression has subscript or superscript.
pub fn is_scripted(&self) -> bool {
self.subscript.is_some() || self.supscript.is_some()
}

/// Returns `true` if the expression contains no inner expressions.
pub fn is_empty(&self) -> bool {
self.len() == 0
}

/// Returns the number of expressions inside grouping, or 1 otherwise.
pub fn len(&self) -> usize {
self.val.len()
self.interm.len()
}

/// Returns `true` if the expression is a comma.
pub(crate) fn is_comma(&self) -> bool {
match self.val {
match self.interm {
SimpleExpr::Var(ref var) => var.is_comma(),
_ => false,
}
}

/// Returns `true` if the expression is a matrix.
fn is_matrix(&self) -> bool {
let SimpleExpr::Grouping(ref grp) = self.val else {
let SimpleExpr::Grouping(ref grp) = self.interm else {
return false;
};

Expand All @@ -131,7 +150,7 @@ impl Expression {
continue;
}

let SimpleExpr::Grouping(ref grp) = e.val else {
let SimpleExpr::Grouping(ref grp) = e.interm else {
return false;
};

Expand All @@ -150,7 +169,7 @@ impl Expression {

/// Returns `true` if the expression is a vertical bar.
fn is_vertical_bar(&self) -> bool {
let SimpleExpr::Var(ref var) = self.val else {
let SimpleExpr::Var(ref var) = self.interm else {
return false;
};

Expand All @@ -166,7 +185,7 @@ impl Expression {
///
/// If the expressions does not have the form of a matrix (or a vector).
fn into_matrix(self) -> Elements {
let SimpleExpr::Grouping(grp) = self.val else {
let SimpleExpr::Grouping(grp) = self.interm else {
panic!("Expected a matrix.");
};

Expand All @@ -180,7 +199,7 @@ impl Expression {
let first_row = expr.get(0).expect("Matrix row expected.");

// preallocate maximal number of columns
let num_of_columns = match &first_row.val {
let num_of_columns = match &first_row.interm {
SimpleExpr::Grouping(grp) => grp.len(),
_ => unreachable!(),
};
Expand All @@ -196,7 +215,7 @@ impl Expression {
continue;
}

let SimpleExpr::Grouping(grp) = row.val else {
let SimpleExpr::Grouping(grp) = row.interm else {
unreachable!("Expected a matrix row.");
};

Expand Down Expand Up @@ -275,22 +294,22 @@ impl IntoElements for Expression {
return self.into_matrix();
}

let is_underover = self.val.is_underover();

let inner = self.val.into_elements();
let is_underover = self.interm.is_underover();

if matches!((&self.subscript, &self.supscript), (None, None)) {
return inner.into_elements();
}
let inner = if self.is_scripted() {
self.interm.into_elements()
} else {
return self.interm.into_elements();
};

let sub = self.subscript.map(|s| match s {
SimpleExpr::Grouping(grp) => SimpleExpr::Grouping(grp.ignored_parentheses()),
_ => s,
SimpleExpr::Grouping(grp) => grp.ungroup_map(IntoElements::into_elements).collect(),
_ => s.into_elements(),
});

let sup = self.supscript.map(|s| match s {
SimpleExpr::Grouping(grp) => SimpleExpr::Grouping(grp.ignored_parentheses()),
_ => s,
SimpleExpr::Grouping(grp) => grp.ungroup_map(IntoElements::into_elements).collect(),
_ => s.into_elements(),
});

if is_underover {
Expand Down Expand Up @@ -336,6 +355,7 @@ impl IntoElements for SimpleExpr {
}
SimpleExpr::Unary(unary) => unary.into_elements(),
SimpleExpr::Binary(binary) => binary.into_elements(),
SimpleExpr::Interm(inner) => inner.into_elements(),
}
}
}
11 changes: 11 additions & 0 deletions src/parser/grouping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ impl GroupingExpr {
}
}

/// Returns the group of expressions inside the grouping without the grouping symbols.
pub fn ungroup(self) -> Vec<Expression> {
self.expr
}

/// Returns an iterator over the group of expression inside the grouping without the grouping
/// symbols and mapped by the given function.
pub(crate) fn ungroup_map<T>(self, f: impl FnMut(Expression) -> T) -> impl Iterator<Item = T> {
self.ungroup().into_iter().map(f)
}

/// Checks whether the grouping contains any expressions.
pub fn is_empty(&self) -> bool {
self.expr.is_empty()
Expand Down
42 changes: 23 additions & 19 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ pub use unary::*;
pub use var::*;

use crate::{
lexer::{
keywords::{groupings::Grouping, others::Other},
Span, TokenIterator, TokenKind,
},
lexer::{keywords::others::Other, Span, TokenIterator, TokenKind},
scanner::Symbols,
};

Expand Down Expand Up @@ -140,7 +137,7 @@ impl<'s> AsciiMath<'s> {
};

let interm = Expression {
val: s_expr,
interm: s_expr,
subscript,
supscript,
};
Expand All @@ -155,7 +152,6 @@ impl<'s> AsciiMath<'s> {
if matches!(next_token.kind(), TokenKind::Other(Other::ForwardSlash)) {
// I/I case -> fraction
let numerator = interm;
let numer_span = numerator.span();

self.iter.next(); // skip '/' token
let denominator = self.parse_expr()?;
Expand All @@ -165,19 +161,27 @@ impl<'s> AsciiMath<'s> {

// treat intermediate expressions as parenthesised expressions passed to frac:
// a_b/c_d == (a_b)/(c_d) == frac{a_b}{c_d}
let numerator = SimpleExpr::Grouping(GroupingExpr {
left_grouping: Grouping::OpenParen,
right_grouping: Grouping::CloseParen,
expr: vec![numerator],
span: numer_span,
});
let numerator = if numerator.is_scripted() {
SimpleExpr::Interm(Box::new(numerator))
} else {
numerator.into_interm_with(|inner| match inner {
SimpleExpr::Grouping(grp) => {
SimpleExpr::Grouping(grp.ignored_parentheses())
}
_ => inner,
})
};

let denominator = SimpleExpr::Grouping(GroupingExpr {
left_grouping: Grouping::OpenParen,
right_grouping: Grouping::CloseParen,
expr: vec![denominator],
span: numerator.span(),
});
let denominator = if denominator.is_scripted() {
SimpleExpr::Interm(Box::new(denominator))
} else {
denominator.into_interm_with(|inner| match inner {
SimpleExpr::Grouping(grp) => {
SimpleExpr::Grouping(grp.ignored_parentheses())
}
_ => inner,
})
};

let binary = Binary {
kind: BinaryKind::Fraction,
Expand All @@ -187,7 +191,7 @@ impl<'s> AsciiMath<'s> {
};

return Some(Expression {
val: SimpleExpr::Binary(binary),
interm: SimpleExpr::Binary(binary),
subscript: None,
supscript: None,
});
Expand Down
14 changes: 13 additions & 1 deletion src/parser/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl std::fmt::Display for Snapshot<&Expression> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Expression {\n")?;

let val = format!("{}", Snapshot(&self.0.val))
let val = format!("{}", Snapshot(&self.0.interm))
.lines()
.map(|l| format!("{}{l}", indent(1)))
.collect::<Vec<_>>()
Expand Down Expand Up @@ -121,6 +121,18 @@ impl std::fmt::Display for Snapshot<&SimpleExpr> {
}
SimpleExpr::Unary(unary) => f.write_fmt(format_args!("{}", Snapshot(unary))),
SimpleExpr::Binary(binary) => f.write_fmt(format_args!("{}", Snapshot(binary))),
SimpleExpr::Interm(interm) => {
f.write_str("Interm {\n")?;

let val = format!("{}", Snapshot(&**interm))
.lines()
.map(|l| format!("{}{l}", indent(1)))
.collect::<Vec<_>>()
.join("\n");

f.write_str(&val)?;
f.write_str("}\n")
}
}
}
}
Expand Down

0 comments on commit 176696b

Please sign in to comment.