20 releases

Uses new Rust 2024

0.1.21 Jun 3, 2026
0.1.20 Jun 3, 2026
0.1.19 May 27, 2026

#2540 in Text processing


Used in oxyl

MIT license

60KB
1.5K SLoC

logo

A LaTeX compiler written in Rust.

Status: very early stage - the lexer and a partial parser exit, but oxyl doesn't produce output yet. Run with --dump-tokens or --dump-ast to see what oxyl is making of your file.

What is this?

oxyl is a from-scratch LaTeX compiler. The goal is to be fast, produce helpful error messages, and eventually have proper package management built in rather than just being bolted on afterwards.

Goals

  • Parse and compile .tex files to PDF (i really want to get this done)
  • Incremental compilation - only reprocess what changed
  • Clear error messages + source locations
  • An oxyl.toml package manifest instead of the TEXMF mess

Building

git clone https://github.com/benjibrown/oxyl.git
cargo build 
./target/release/oxyl

Installing from crates.io

cargo install oxyl 
oxyl <file.tex>

Usage

oxyl <file.tex>                 parse the file and report any errors 
oxyl --dump-tokens <file.tex>   print every lexer token with its byte span 
oxyl --dump-ast <file.tex>      print the parsed AST nodes 
oxyl --color <when> <file.tex>  auto (default), always, never
oxyl --no-color <file.tex>      alias for --color=nevr
oxyl --version                  print the oxyl version
oxyl --help                     full flag list 

Supress colour output/ANSI by setting NO_COLOR in the environment, or with --color never. Pass --color=always to force colour regardless of where stderr points (auto will only use colour if it is being output to a terminal).

To force colour through a pipe: pass --color=always on the command line, or set CLICOLOR_FORCE=1 in the environment (the bixense.com/clicolors convention).

All exit codes follow the usual Unix convention: 0 on success, 1 if the file fails to lex or parse (or cannot be read), 2 if oxyl was invoked correctly (unknown flag, missing or extra arguments).

Diagnostics include a 1-based line/column and an awesome caret pointing at the offending span:

error [E020]: unclosed '{'
  --> main.tex:1:5
  |
1 | foo {bar
  |     ^

What's currently parsed

  • Plain text and paragraphs (blank lines)
  • Commands with optional and mandatory arguments: \sqrt[3]{27}
  • Brace groups { ... }
  • Inline math $ ... $
  • Display math \[ ... \]
  • Environments \begin{name} ... \end{name} (nesting works; extra args like the column spec in \begin{tabular}{cc} are kept on the AST node)
  • & (alignment tab, used between cells in tabular) and ~ (non-breaking space) - both survive into the AST as their own nodes.
  • Line comments (preserved in the AST so source-fidelity tools can access them)

Diagnostic Codes

Code Where Meaning
E010 lexer lone backslash / non-ASCII character
E020 parser unclosed {
E021 parser unclosed mandatory argument
E022 parser unclosed optional argument
E030 parser unclosed $ (inline math)
E031 parser unclosed \[ (display math)
E032 parser stray \] (no matching \[)
E040 parser \begin missing environment name
E041 parser unclosed \begin{...}
E042 parser \end{b} does not match \begin{a}
E043 parser stray \end (no matching \begin)

Dependencies