Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help



Custom Parsers and Extensions

Topic: parser.custom

Extend MathHook's parser for domain-specific mathematical notation. Add custom functions, operators, preprocessors, and grammar modifications.

Custom Parsers and Extensions

Extend MathHook's parser for domain-specific mathematical notation.

Understanding Parser Extension

What Can You Extend?

MathHook's parser is modular and extensible. You can add:

  • Custom Functions: Domain-specific functions (chemistry, physics, engineering)
  • Custom Operators: New infix/prefix/postfix operators
  • Custom Notation: LaTeX macros, specialized symbols
  • Parser Preprocessors: Transform input before parsing
  • Lexer Tokens: New token types for specialized syntax

When to Extend the Parser

Use Built-In Features When:

  • Standard mathematical notation suffices
  • Functions can be named conventionally
  • LaTeX or Wolfram notation covers your needs

Extend the Parser When:

  • Domain-specific notation is essential (chemistry: , physics: )
  • Custom operators with special precedence
  • Proprietary mathematical notation
  • Legacy system compatibility

Architecture Overview

Input String
    ↓
Preprocessor (optional) - Transform syntax before parsing
    ↓
Lexer - Tokenize input (recognizes custom tokens)
    ↓
Parser (LALRPOP) - Build expression tree
    ↓
Post-Processor (optional) - Transform parsed expression
    ↓
Expression

Domain-Specific Examples

Chemistry Notation

Custom parser for chemical equations:

  • for yields
  • for equilibrium
  • Subscripts for molecular formulas

Quantum Mechanics

Specialized notation:

  • for tensor product
  • ⟨|⟩ for inner product
  • [,] for commutator
  • {,} for anticommutator

Financial Mathematics

Finance-specific functions:

  • NPV (Net Present Value)
  • IRR (Internal Rate of Return)
  • FV/PV (Future/Present Value)
  • % operator for percentages

Control Theory

System notation:

  • * as convolution
  • for Laplace transform
  • // for feedback connection

Examples

Adding Custom Functions

Register domain-specific functions

Rust
#![allow(unused)]
fn main() {
use mathhook::parser::ParserBuilder;

let parser = ParserBuilder::new()
    .add_function("erf", "error_function")
    .add_function("Si", "sine_integral")
    .add_function("Ci", "cosine_integral")
    .build();

let expr = parser.parse("erf(x) + Si(x)")?;
// Parsed as: error_function(x) + sine_integral(x)

}
Python
from mathhook.parser import ParserBuilder

parser = (ParserBuilder()
    .add_function("erf", "error_function")
    .add_function("Si", "sine_integral")
    .add_function("Ci", "cosine_integral")
    .build())

expr = parser.parse("erf(x) + Si(x)")
# Parsed as: error_function(x) + sine_integral(x)

JavaScript
const { ParserBuilder } = require('mathhook');

const parser = new ParserBuilder()
    .addFunction("erf", "error_function")
    .addFunction("Si", "sine_integral")
    .addFunction("Ci", "cosine_integral")
    .build();

const expr = parser.parse("erf(x) + Si(x)");
// Parsed as: error_function(x) + sine_integral(x)

Adding Custom Operators

Define new infix operators with precedence

Rust
#![allow(unused)]
fn main() {
use mathhook::parser::ParserBuilder;
use mathhook::parser::Precedence;

let parser = ParserBuilder::new()
    .add_operator("×", "*")      // Cross product symbol
    .add_operator("⊗", "tensor") // Tensor product
    .add_operator_with_precedence(
        "⊕",
        "direct_sum",
        Precedence::Addition
    )
    .build();

let expr = parser.parse("A ⊗ B")?;
// Parsed as: tensor(A, B)

}
Python
from mathhook.parser import ParserBuilder, Precedence

parser = (ParserBuilder()
    .add_operator("×", "*")
    .add_operator("⊗", "tensor")
    .add_operator_with_precedence(
        "⊕",
        "direct_sum",
        Precedence.ADDITION
    )
    .build())

expr = parser.parse("A ⊗ B")
# Parsed as: tensor(A, B)

JavaScript
const { ParserBuilder, Precedence } = require('mathhook');

const parser = new ParserBuilder()
    .addOperator("×", "*")
    .addOperator("⊗", "tensor")
    .addOperatorWithPrecedence(
        "⊕",
        "direct_sum",
        Precedence.ADDITION
    )
    .build();

const expr = parser.parse("A ⊗ B");
// Parsed as: tensor(A, B)

Preprocessor Transformations

Transform input before parsing

Rust
#![allow(unused)]
fn main() {
use mathhook::parser::ParserBuilder;

let parser = ParserBuilder::new()
    .add_preprocessor(|input| {
        input.replace("→", "->")   // Arrow notation
             .replace("×", "*")     // Cross product
             .replace("÷", "/")     // Division symbol
    })
    .build();

let expr = parser.parse("x → ∞")?;
// Preprocessed to: x -> ∞
// Then parsed normally

}
Python
from mathhook.parser import ParserBuilder

def preprocess(input_str):
    return (input_str
        .replace("→", "->")
        .replace("×", "*")
        .replace("÷", "/"))

parser = (ParserBuilder()
    .add_preprocessor(preprocess)
    .build())

expr = parser.parse("x → ∞")
# Preprocessed to: x -> ∞
# Then parsed normally

JavaScript
const { ParserBuilder } = require('mathhook');

const parser = new ParserBuilder()
    .addPreprocessor((input) => {
        return input
            .replace(/→/g, "->")
            .replace(/×/g, "*")
            .replace(/÷/g, "/");
    })
    .build();

const expr = parser.parse("x → ∞");
// Preprocessed to: x -> ∞
// Then parsed normally

Domain-Specific Parser (Chemistry)

Complete chemistry equation parser

Rust
#![allow(unused)]
fn main() {
use mathhook::parser::ParserBuilder;

fn create_chemistry_parser() -> Parser {
    ParserBuilder::new()
        .add_operator("→", "yields")
        .add_operator("⇌", "equilibrium")
        .add_operator("+", "plus")
        .add_preprocessor(|input| {
            // H2O → H_2*O
            expand_chemical_formulas(input)
        })
        .add_postprocessor(|expr| {
            balance_equation(expr)
        })
        .build()
}

let parser = create_chemistry_parser();
let reaction = parser.parse("H₂ + O₂ → H₂O")?;
let balanced = reaction.balance();  // 2H₂ + O₂ → 2H₂O

}
Python
from mathhook.parser import ParserBuilder

def create_chemistry_parser():
    return (ParserBuilder()
        .add_operator("→", "yields")
        .add_operator("⇌", "equilibrium")
        .add_operator("+", "plus")
        .add_preprocessor(expand_chemical_formulas)
        .add_postprocessor(balance_equation)
        .build())

parser = create_chemistry_parser()
reaction = parser.parse("H₂ + O₂ → H₂O")
balanced = reaction.balance()  # 2H₂ + O₂ → 2H₂O

JavaScript
const { ParserBuilder } = require('mathhook');

function createChemistryParser() {
    return new ParserBuilder()
        .addOperator("→", "yields")
        .addOperator("⇌", "equilibrium")
        .addOperator("+", "plus")
        .addPreprocessor(expandChemicalFormulas)
        .addPostprocessor(balanceEquation)
        .build();
}

const parser = createChemistryParser();
const reaction = parser.parse("H₂ + O₂ → H₂O");
const balanced = reaction.balance();  // 2H₂ + O₂ → 2H₂O

Custom LaTeX Macros

Expand LaTeX macros before parsing

Rust
#![allow(unused)]
fn main() {
use mathhook::parser::LatexParserBuilder;

let parser = LatexParserBuilder::new()
    .add_macro(r"\RR", r"\mathbb{R}")    // Real numbers
    .add_macro(r"\CC", r"\mathbb{C}")    // Complex numbers
    .add_macro(r"\NN", r"\mathbb{N}")    // Natural numbers
    .add_macro(r"\dd", r"\mathrm{d}")    // Differential d
    .build();

let expr = parser.parse(r"f: \RR \to \CC")?;
// Expands to: f: \mathbb{R} \to \mathbb{C}

}
Python
from mathhook.parser import LatexParserBuilder

parser = (LatexParserBuilder()
    .add_macro(r"\RR", r"\mathbb{R}")
    .add_macro(r"\CC", r"\mathbb{C}")
    .add_macro(r"\NN", r"\mathbb{N}")
    .add_macro(r"\dd", r"\mathrm{d}")
    .build())

expr = parser.parse(r"f: \RR \to \CC")
# Expands to: f: \mathbb{R} \to \mathbb{C}

JavaScript
const { LatexParserBuilder } = require('mathhook');

const parser = new LatexParserBuilder()
    .addMacro(String.raw`\RR`, String.raw`\mathbb{R}`)
    .addMacro(String.raw`\CC`, String.raw`\mathbb{C}`)
    .addMacro(String.raw`\NN`, String.raw`\mathbb{N}`)
    .addMacro(String.raw`\dd`, String.raw`\mathrm{d}`)
    .build();

const expr = parser.parse(String.raw`f: \RR \to \CC`);
// Expands to: f: \mathbb{R} \to \mathbb{C}

API Reference

  • Rust: mathhook_core::parser::extensions
  • Python: mathhook.parser.extensions
  • JavaScript: mathhook.parser.extensions

See Also