Split up math library

This commit is contained in:
Laurenz 2023-01-03 12:40:14 +01:00
parent e38839c287
commit e460da1ce7
7 changed files with 453 additions and 435 deletions

112
library/src/math/accent.rs Normal file
View File

@ -0,0 +1,112 @@
use super::*;
/// # Accent
/// An accented node.
///
/// ## Example
/// ```
/// $acc(a, ->) != acc(a, ~)$ \
/// $acc(a, `) = acc(a, grave)$
/// ```
///
/// ## Parameters
/// - base: Content (positional, required)
/// The base to which the accent is applied.
/// May consist of multiple letters.
///
/// ### Example
/// ```
/// $acc(A B C, ->)$
/// ```
///
/// - accent: Content (positional, required)
/// The accent to apply to the base.
///
/// Supported accents include:
/// - Grave: `` ` ``
/// - Acute: `´`
/// - Circumflex: `^`
/// - Tilde: `~`
/// - Macron: `¯`
/// - Overline: `‾`
/// - Breve: `˘`
/// - Dot: `.`
/// - Diaeresis: `¨`
/// - Caron: `ˇ`
/// - Arrow: `→`
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct AccNode {
/// The accent base.
pub base: Content,
/// The Unicode accent character.
pub accent: char,
}
#[node]
impl AccNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let base = args.expect("base")?;
let Spanned { v, span } = args.expect::<Spanned<Content>>("accent")?;
let accent = match extract(&v) {
Some(Ok(c)) => c,
Some(Err(msg)) => bail!(span, "{}", msg),
None => bail!(span, "not an accent"),
};
Ok(Self { base, accent }.pack())
}
}
#[rustfmt::skip]
fn extract(content: &Content) -> Option<Result<char, &'static str>> {
let MathNode { children, .. } = content.to::<MathNode>()?;
let [child] = children.as_slice() else { return None };
let c = if let Some(atom) = child.to::<AtomNode>() {
let mut chars = atom.0.chars();
chars.next().filter(|_| chars.next().is_none())?
} else if let Some(symbol) = child.to::<SymbolNode>() {
match symmie::get(&symbol.0) {
Some(c) => c,
None => return Some(Err("unknown symbol")),
}
} else {
return None;
};
Some(Ok(match c {
'`' | '\u{300}' => '\u{300}', // Grave
'´' | '\u{301}' => '\u{301}', // Acute
'^' | '\u{302}' => '\u{302}', // Circumflex
'~' | '\u{223C}' | '\u{303}' => '\u{303}', // Tilde
'¯' | '\u{304}' => '\u{304}', // Macron
'‾' | '\u{305}' => '\u{305}', // Overline
'˘' | '\u{306}' => '\u{306}', // Breve
'.' | '\u{22C5}' | '\u{307}' => '\u{307}', // Dot
'¨' | '\u{308}' => '\u{308}', // Diaeresis
'ˇ' | '\u{30C}' => '\u{30C}', // Caron
'→' | '\u{20D7}' => '\u{20D7}', // Arrow
_ => return None,
}))
}
impl Texify for AccNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
if let Some(sym) = unicode_math::SYMBOLS.iter().find(|sym| {
sym.codepoint == self.accent
&& sym.atom_type == unicode_math::AtomType::Accent
}) {
t.push_str("\\");
t.push_str(sym.name);
t.push_str("{");
self.base.texify(t)?;
t.push_str("}");
} else {
self.base.texify(t)?;
}
Ok(())
}
}

48
library/src/math/atom.rs Normal file
View File

@ -0,0 +1,48 @@
use super::*;
/// # Atom
/// An atom in a math formula: `x`, `+`, `12`.
///
/// ## Parameters
/// - text: EcoString (positional, required)
/// The atom's text.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct AtomNode(pub EcoString);
#[node]
impl AtomNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("text")?).pack())
}
}
impl Texify for AtomNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
let multi = self.0.graphemes(true).count() > 1;
if multi {
t.push_str("\\mathrm{");
}
for c in self.0.chars() {
let supportive = c == '|';
if supportive {
t.support();
}
t.push_escaped(c);
if supportive {
t.support();
}
}
if multi {
t.push_str("}");
}
Ok(())
}
}

102
library/src/math/frac.rs Normal file
View File

@ -0,0 +1,102 @@
use super::*;
/// # Fraction
/// A mathematical fraction.
///
/// ## Syntax
/// This function also has dedicated syntax: Use a slash to turn neighbouring
/// expressions into a fraction. Multiple atoms can be grouped into a single
/// expression using round grouping parenthesis. Such parentheses are removed
/// from the output, but you can nest multiple to force them.
///
/// ## Example
/// ```
/// $ 1/2 < (x+1)/2 $
/// $ ((x+1)) / 2 = frac(a, b) $
/// ```
///
/// ## Parameters
/// - num: Content (positional, required)
/// The fraction's numerator.
///
/// - denom: Content (positional, required)
/// The fraction's denominator.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct FracNode {
/// The numerator.
pub num: Content,
/// The denominator.
pub denom: Content,
}
#[node]
impl FracNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let num = args.expect("numerator")?;
let denom = args.expect("denominator")?;
Ok(Self { num, denom }.pack())
}
}
impl Texify for FracNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\frac{");
self.num.texify_unparen(t)?;
t.push_str("}{");
self.denom.texify_unparen(t)?;
t.push_str("}");
Ok(())
}
}
/// # Binomial
/// A binomial expression.
///
/// ## Example
/// ```
/// $ binom(n, k) $
/// ```
///
/// ## Parameters
/// - upper: Content (positional, required)
/// The binomial's upper index.
///
/// - lower: Content (positional, required)
/// The binomial's lower index.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct BinomNode {
/// The upper index.
pub upper: Content,
/// The lower index.
pub lower: Content,
}
#[node]
impl BinomNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let upper = args.expect("upper index")?;
let lower = args.expect("lower index")?;
Ok(Self { upper, lower }.pack())
}
}
impl Texify for BinomNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\binom{");
self.upper.texify(t)?;
t.push_str("}{");
self.lower.texify(t)?;
t.push_str("}");
Ok(())
}
}

71
library/src/math/group.rs Normal file
View File

@ -0,0 +1,71 @@
use super::*;
/// # Floor
/// A floored expression.
///
/// ## Example
/// ```
/// $ floor(x/2) $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The expression to floor.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct FloorNode(pub Content);
#[node]
impl FloorNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for FloorNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\left\\lfloor ");
self.0.texify(t)?;
t.push_str("\\right\\rfloor ");
Ok(())
}
}
/// # Ceil
/// A ceiled expression.
///
/// ## Example
/// ```
/// $ ceil(x/2) $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The expression to ceil.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct CeilNode(pub Content);
#[node]
impl CeilNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for CeilNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\left\\lceil ");
self.0.texify(t)?;
t.push_str("\\right\\rceil ");
Ok(())
}
}

View File

@ -1,10 +1,22 @@
//! Mathematical formulas.
mod accent;
mod atom;
mod frac;
mod group;
mod matrix;
mod root;
mod script;
mod style;
mod tex;
pub use self::accent::*;
pub use self::atom::*;
pub use self::frac::*;
pub use self::group::*;
pub use self::matrix::*;
pub use self::root::*;
pub use self::script::*;
pub use self::style::*;
use typst::model::{Guard, SequenceNode};
@ -297,334 +309,6 @@ impl Texify for Content {
}
}
/// # Atom
/// An atom in a math formula: `x`, `+`, `12`.
///
/// ## Parameters
/// - text: EcoString (positional, required)
/// The atom's text.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct AtomNode(pub EcoString);
#[node]
impl AtomNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("text")?).pack())
}
}
impl Texify for AtomNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
let multi = self.0.graphemes(true).count() > 1;
if multi {
t.push_str("\\mathrm{");
}
for c in self.0.chars() {
let supportive = c == '|';
if supportive {
t.support();
}
t.push_escaped(c);
if supportive {
t.support();
}
}
if multi {
t.push_str("}");
}
Ok(())
}
}
/// # Accent
/// An accented node.
///
/// ## Example
/// ```
/// $acc(a, ->) != acc(a, ~)$ \
/// $acc(a, `) = acc(a, grave)$
/// ```
///
/// ## Parameters
/// - base: Content (positional, required)
/// The base to which the accent is applied.
/// May consist of multiple letters.
///
/// ### Example
/// ```
/// $acc(A B C, ->)$
/// ```
///
/// - accent: Content (positional, required)
/// The accent to apply to the base.
///
/// Supported accents include:
/// - Grave: `` ` ``
/// - Acute: `´`
/// - Circumflex: `^`
/// - Tilde: `~`
/// - Macron: `¯`
/// - Overline: `‾`
/// - Breve: `˘`
/// - Dot: `.`
/// - Diaeresis: `¨`
/// - Caron: `ˇ`
/// - Arrow: `→`
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct AccNode {
/// The accent base.
pub base: Content,
/// The Unicode accent character.
pub accent: char,
}
#[node]
impl AccNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let base = args.expect("base")?;
let Spanned { v, span } = args.expect::<Spanned<Content>>("accent")?;
let accent = match extract(&v) {
Some(Ok(c)) => c,
Some(Err(msg)) => bail!(span, "{}", msg),
None => bail!(span, "not an accent"),
};
Ok(Self { base, accent }.pack())
}
}
#[rustfmt::skip]
fn extract(content: &Content) -> Option<Result<char, &'static str>> {
let MathNode { children, .. } = content.to::<MathNode>()?;
let [child] = children.as_slice() else { return None };
let c = if let Some(atom) = child.to::<AtomNode>() {
let mut chars = atom.0.chars();
chars.next().filter(|_| chars.next().is_none())?
} else if let Some(symbol) = child.to::<SymbolNode>() {
match symmie::get(&symbol.0) {
Some(c) => c,
None => return Some(Err("unknown symbol")),
}
} else {
return None;
};
Some(Ok(match c {
'`' | '\u{300}' => '\u{300}', // Grave
'´' | '\u{301}' => '\u{301}', // Acute
'^' | '\u{302}' => '\u{302}', // Circumflex
'~' | '\u{223C}' | '\u{303}' => '\u{303}', // Tilde
'¯' | '\u{304}' => '\u{304}', // Macron
'‾' | '\u{305}' => '\u{305}', // Overline
'˘' | '\u{306}' => '\u{306}', // Breve
'.' | '\u{22C5}' | '\u{307}' => '\u{307}', // Dot
'¨' | '\u{308}' => '\u{308}', // Diaeresis
'ˇ' | '\u{30C}' => '\u{30C}', // Caron
'→' | '\u{20D7}' => '\u{20D7}', // Arrow
_ => return None,
}))
}
impl Texify for AccNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
if let Some(sym) = unicode_math::SYMBOLS.iter().find(|sym| {
sym.codepoint == self.accent
&& sym.atom_type == unicode_math::AtomType::Accent
}) {
t.push_str("\\");
t.push_str(sym.name);
t.push_str("{");
self.base.texify(t)?;
t.push_str("}");
} else {
self.base.texify(t)?;
}
Ok(())
}
}
/// # Fraction
/// A mathematical fraction.
///
/// ## Syntax
/// This function also has dedicated syntax: Use a slash to turn neighbouring
/// expressions into a fraction. Multiple atoms can be grouped into a single
/// expression using round grouping parenthesis. Such parentheses are removed
/// from the output, but you can nest multiple to force them.
///
/// ## Example
/// ```
/// $ 1/2 < (x+1)/2 $
/// $ ((x+1)) / 2 = frac(a, b) $
/// ```
///
/// ## Parameters
/// - num: Content (positional, required)
/// The fraction's numerator.
///
/// - denom: Content (positional, required)
/// The fraction's denominator.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct FracNode {
/// The numerator.
pub num: Content,
/// The denominator.
pub denom: Content,
}
#[node]
impl FracNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let num = args.expect("numerator")?;
let denom = args.expect("denominator")?;
Ok(Self { num, denom }.pack())
}
}
impl Texify for FracNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\frac{");
self.num.texify_unparen(t)?;
t.push_str("}{");
self.denom.texify_unparen(t)?;
t.push_str("}");
Ok(())
}
}
/// # Binomial
/// A binomial expression.
///
/// ## Example
/// ```
/// $ binom(n, k) $
/// ```
///
/// ## Parameters
/// - upper: Content (positional, required)
/// The binomial's upper index.
///
/// - lower: Content (positional, required)
/// The binomial's lower index.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct BinomNode {
/// The upper index.
pub upper: Content,
/// The lower index.
pub lower: Content,
}
#[node]
impl BinomNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let upper = args.expect("upper index")?;
let lower = args.expect("lower index")?;
Ok(Self { upper, lower }.pack())
}
}
impl Texify for BinomNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\binom{");
self.upper.texify(t)?;
t.push_str("}{");
self.lower.texify(t)?;
t.push_str("}");
Ok(())
}
}
/// # Script
/// A mathematical sub- and/or superscript.
///
/// _Note:_ In the future, this might be unified with the [sub](@sub) and
/// [super](@super) functions that handle sub- and superscripts in text.
///
/// ## Syntax
/// This function also has dedicated syntax: Use the underscore (`_`) to
/// indicate a subscript and the circumflex (`^`) to indicate a superscript.
///
/// ## Example
/// ```
/// $ a_i = 2^(1+i) $
/// ```
///
/// ## Parameters
/// - base: Content (positional, required)
/// The base to which the applies the sub- and/or superscript.
///
/// - sub: Content (named)
/// The subscript.
///
/// - sup: Content (named)
/// The superscript.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct ScriptNode {
/// The base.
pub base: Content,
/// The subscript.
pub sub: Option<Content>,
/// The superscript.
pub sup: Option<Content>,
}
#[node]
impl ScriptNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let base = args.expect("base")?;
let sub = args.named("sub")?;
let sup = args.named("sup")?;
Ok(Self { base, sub, sup }.pack())
}
}
impl Texify for ScriptNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
self.base.texify(t)?;
if let Some(sub) = &self.sub {
t.push_str("_{");
sub.texify_unparen(t)?;
t.push_str("}");
}
if let Some(sup) = &self.sup {
t.push_str("^{");
sup.texify_unparen(t)?;
t.push_str("}");
}
Ok(())
}
}
/// # Alignment Point
/// A math alignment point: `&`, `&&`.
///
@ -651,110 +335,3 @@ impl Texify for AlignPointNode {
Ok(())
}
}
/// # Square Root
/// A square root.
///
/// _Note:_ Non-square roots are not yet supported.
///
/// ## Example
/// ```
/// $ sqrt(x^2) = x = sqrt(x)^2 $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The expression to take the square root of.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct SqrtNode(pub Content);
#[node]
impl SqrtNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for SqrtNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\sqrt{");
self.0.texify(t)?;
t.push_str("}");
Ok(())
}
}
/// # Floor
/// A floored expression.
///
/// ## Example
/// ```
/// $ floor(x/2) $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The expression to floor.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct FloorNode(pub Content);
#[node]
impl FloorNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for FloorNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\left\\lfloor ");
self.0.texify(t)?;
t.push_str("\\right\\rfloor ");
Ok(())
}
}
/// # Ceil
/// A ceiled expression.
///
/// ## Example
/// ```
/// $ ceil(x/2) $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The expression to ceil.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct CeilNode(pub Content);
#[node]
impl CeilNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for CeilNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\left\\lceil ");
self.0.texify(t)?;
t.push_str("\\right\\rceil ");
Ok(())
}
}

38
library/src/math/root.rs Normal file
View File

@ -0,0 +1,38 @@
use super::*;
/// # Square Root
/// A square root.
///
/// _Note:_ Non-square roots are not yet supported.
///
/// ## Example
/// ```
/// $ sqrt(x^2) = x = sqrt(x)^2 $
/// ```
///
/// ## Parameters
/// - body: Content (positional, required)
/// The expression to take the square root of.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct SqrtNode(pub Content);
#[node]
impl SqrtNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack())
}
}
impl Texify for SqrtNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
t.push_str("\\sqrt{");
self.0.texify(t)?;
t.push_str("}");
Ok(())
}
}

View File

@ -0,0 +1,70 @@
use super::*;
/// # Script
/// A mathematical sub- and/or superscript.
///
/// _Note:_ In the future, this might be unified with the [sub](@sub) and
/// [super](@super) functions that handle sub- and superscripts in text.
///
/// ## Syntax
/// This function also has dedicated syntax: Use the underscore (`_`) to
/// indicate a subscript and the circumflex (`^`) to indicate a superscript.
///
/// ## Example
/// ```
/// $ a_i = 2^(1+i) $
/// ```
///
/// ## Parameters
/// - base: Content (positional, required)
/// The base to which the applies the sub- and/or superscript.
///
/// - sub: Content (named)
/// The subscript.
///
/// - sup: Content (named)
/// The superscript.
///
/// ## Category
/// math
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct ScriptNode {
/// The base.
pub base: Content,
/// The subscript.
pub sub: Option<Content>,
/// The superscript.
pub sup: Option<Content>,
}
#[node]
impl ScriptNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let base = args.expect("base")?;
let sub = args.named("sub")?;
let sup = args.named("sup")?;
Ok(Self { base, sub, sup }.pack())
}
}
impl Texify for ScriptNode {
fn texify(&self, t: &mut Texifier) -> SourceResult<()> {
self.base.texify(t)?;
if let Some(sub) = &self.sub {
t.push_str("_{");
sub.texify_unparen(t)?;
t.push_str("}");
}
if let Some(sup) = &self.sup {
t.push_str("^{");
sup.texify_unparen(t)?;
t.push_str("}");
}
Ok(())
}
}