LTX
An extremely fast LaTeX project manager, written in Rust.
LTX scaffolds structured projects, lints your source, drives tectonic
(and pdflatex/xelatex/lualatex), and gets out of your way — built for
people who’d rather be writing than babysitting a build.
Status: pre-1.0. The CLI surface is stabilizing; expect minor breaking changes between minor versions until
1.0.
Why LTX?
Most LaTeX workflows are stitched together from shell scripts, latexmk, and
muscle memory. LTX treats your document the way a modern build tool treats a
codebase:
- Correct by construction — one
ltx.tomldescribes your project; builds behave the same on your machine, your co-author’s machine, and in CI. - Fast — a Rust core and a native engine mean large multi-file papers compile quickly, without a full TeX distribution installed.
- Predictable — the diagnostics pipeline gives you one consistent, source-spanning report for syntax and configuration problems.
Architecture
LTX is a Rust workspace composed of seven crates. Each crate owns its domain, and errors are defined in the crate that produces them — never centralized.
| Crate | Role |
|---|---|
| ltx_utils | Low-level filesystem helpers (create_dir, write_file, resolve_main_file) |
| ltx_diagnostics | Pure diagnostic infrastructure — spans, source maps, miette rendering (defines no domain errors) |
| ltx_lexer | Byte-level tokenizer — converts .tex source into a stream of typed tokens |
| ltx_parser | Recursive-descent parser — consumes the token stream and produces an AST |
| ltx_config | Manifest model + validation + scaffolding — reads ltx.toml, generates project layouts |
| ltx_compiler | Compilation orchestration — dispatches to an engine (currently tectonic) |
| ltx_cli | Binary entry point — subcommand dispatch, user-facing output, exit codes |
The pipeline
Source text flows through the toolchain in this order:
.tex source
│
▼
LtxLexer ──► TokenStream (LtxToken + LtxSpan)
│
▼
LtxParser ──► AST (Document, Command, Environment, …)
│
▼
ParserErrorHandler ──► LtxDiagnosticSink (absorbs lexer + parser diagnostics)
│
▼
miette renderer ──► Terminal output
- Lexer scans raw bytes, applies TeX catcode rules, and emits
LtxTokens with source spans. Problems are recorded by its ownLexerErrorHandler. - Parser consumes the token stream via
TokenStream(peek/bump/checkpoint/rewind), builds the AST, and reports through its ownParserErrorHandler, which absorbs the lexer’s diagnostics on construction. - Diagnostics accumulate in an
LtxDiagnosticSinkand render withmiettefor rich terminal output or serialize to JSON.
Error codes
Every diagnostic code is namespaced by the phase that owns it — 30 total:
| Namespace | Count | Range |
|---|---|---|
LTX::LEXER::E0xx | 11 | Tokenization |
LTX::PARSER::E0xx | 6 | Structural parsing |
LTX::CONFIG::E0xx | 8 | Manifest / scaffolding |
LTX::COMPILER::E0xx / W0xx | 5 | Compilation |
Run ltx code to list them, filtered by phase (--lexer, --parser,
--config, --compiler) or severity (-e, -w).
Quick start
# Scaffold a new project
ltx new my-paper
cd my-paper
# Check for errors
ltx check main.tex
# Build a PDF (tectonic is bundled; no TeX distribution needed)
ltx build
# List diagnostic codes
ltx code
Documentation pages
- Installation — prerequisites, cargo install, from-source
- CLI Usage — subcommands, flags, examples, exit codes
- Configuration —
ltx.tomlreference, scaffolding options - API Overview — architecture + per-crate library reference
- Lexer Errors —
LTX::LEXER::E0xx - Parser Errors —
LTX::PARSER::E0xx - Config Errors —
LTX::CONFIG::E0xx - Compiler Errors —
LTX::COMPILER::E0xx/W0xx
Installation
Prerequisites
- Rust 1.86+ — install via rustup
That’s it. LTX compiles with Tectonic, a self-contained, native-Rust TeX
engine — no separate TeX distribution is required. The ltx build default
uses tectonic and downloads its TeX Live bundle on first use.
Optional: if you’d rather use
pdflatex,xelatex, orlualatex(or need a package not yet supported by Tectonic), install a traditional distribution and make sure its binaries are on yourPATH:
Install via Cargo
cargo install ltx
This downloads the latest published version from crates.io and compiles a release binary.
Install from source
git clone https://github.com/Abdogouhmad/ltx.git
cd ltx
cargo install --path .
This builds and installs the current main branch.
Verify installation
ltx --help
ltx --version
ltx code --lexer # list the lexer diagnostic codes
You should see the CLI help text, the installed version, and the code table.
Platform notes
Linux
No special steps. If you use a traditional TeX distribution alongside
Tectonic, ensure its binaries are on your PATH (TeX Live installs to
/usr/local/texlive/… by default).
macOS
If you installed MacTeX, the binaries are at /Library/TeX/texbin/. Add this
to your PATH if it isn’t already:
export PATH="/Library/TeX/texbin:$PATH"
Windows
MiKTeX or TeX Live should register themselves on the system PATH during
installation.
Next steps
- CLI Usage — learn the available commands
- Configuration — set up
ltx.tomlfor your project
CLI Usage
LTX provides five subcommands. Run ltx --help for the full reference.
ltx [COMMAND]
ltx new
Create a new LTX project with starter files.
ltx new <name> [OPTIONS]
| Flag | Short | Description | Default |
|---|---|---|---|
--engine | -e | LaTeX compiler to use (pdflatex, xelatex, lualatex, tectonic) | tectonic |
--src | Use src/ directory layout for source files | off | |
--bib | Include bibliography support (bib/ directory) | off |
Examples
# Flat layout (main.tex in project root)
ltx new my-paper
# With src/ directory
ltx new my-paper --src
# With bib/ directory and xelatex engine
ltx new my-paper --bib --engine xelatex
# Full layout: src/ + bib/ + tectonic
ltx new my-paper --src --bib --engine tectonic
Scaffolded layout
Without flags:
my-paper/
├── main.tex
├── references.bib
├── ltx.toml
└── .gitignore
With --src:
my-paper/
├── src/
│ ├── main.tex
│ └── sections/
├── references.bib
├── ltx.toml
└── .gitignore
With --bib:
my-paper/
├── main.tex
├── bib/
│ └── references.bib
├── ltx.toml
└── .gitignore
ltx check
Check a .tex file for syntax errors by running the full lex → parse → diagnostics pipeline.
ltx check <path>
| Argument | Description |
|---|---|
path | Path to a .tex file to check |
Exit codes
| Code | Meaning |
|---|---|
0 | Check passed (no errors; warnings are OK) |
1 | Failed to read the file (missing, not UTF-8, etc.) |
4 | Diagnostics with errors were found |
Example
$ ltx check main.tex
Check passed — no issues found.
If errors are found, LTX renders them with source locations and help messages:
LTX::LEXER::E003
× unmatched brace detected: `{`
╭─[main.tex:5:12]
4 │ \section{Introduction
5 │ % missing closing brace
· ────┬────
· ╰── here
6 │
╰────
help: Verify that every opening brace `{` has a matching closing brace `}`.
ltx build
Compile the project described in ltx.toml into a PDF.
ltx build
The manifest is validated first — missing keys, typos, and a missing main
file fail fast with a source-spanning LTX::CONFIG::E0xx diagnostic. The
selected engine then compiles the document:
tectonic— compiled in-process by thetectoniccrate (the default).pdflatex/xelatex/lualatex— not wired up yet; aLTX::COMPILER::W001warning is printed and the build exits successfully.
Output is always written to the project’s target/ directory.
ltx clean
Remove the target/ directory and print a summary of deleted files and total
size, akin to cargo clean.
ltx clean
| Flag | Description |
|---|---|
-v | Verbose output |
-vv | More verbose output |
ltx code
List all registered diagnostic error codes with their descriptions, severities, and owning phase.
ltx code [FILTER]
| Flag | Description |
|---|---|
--lexer | Show only lexer codes (LTX::LEXER::E0xx) |
--parser | Show only parser codes (LTX::PARSER::E0xx) |
--config | Show only config codes (LTX::CONFIG::E0xx) |
--compiler | Show only compiler codes (LTX::COMPILER::E0xx / W0xx) |
--all | Show all codes (default) |
-e / --errors | Show only error-severity codes |
-w / --warnings | Show only warning-severity codes |
Output:
CODE DESCRIPTION SEVERITY PHASE
--------------------------------------------------------------------------------
LTX::LEXER::E001 Unexpected Token error lexer
LTX::LEXER::E002 Unexpected End of File error lexer
LTX::LEXER::E003 Unmatched Brace error lexer
...
LTX::COMPILER::W001 Engine Not Implemented warning compiler
30 total codes
Global flags
| Flag | Description |
|---|---|
--manifest-path <path> | Path to a manifest file (ltx.toml); relative paths resolve against its directory. |
--message-format <human|json> | Output format for status messages. |
-v | Verbosity level (repeatable). |
--help | Show help information |
--version | Show version number |
Engine options
LTX supports four LaTeX compilers, set via ltx new --engine or in the
[build] section of ltx.toml:
| Engine | Binary | Notes |
|---|---|---|
tectonic | tectonic | Default. Self-contained, Cargo-like LaTeX toolchain. |
pdflatex | pdflatex | Most common engine. |
xelatex | xelatex | Unicode and system-font support. |
lualatex | lualatex | Lua-extensible engine. |
Next steps
- Configuration — customize your project with
ltx.toml - Lexer Errors — the
LTX::LEXER::E0xxcode table
Configuration
Every LTX project is described by an ltx.toml at its root. The ltx new
command generates this file automatically. Only two sections exist:
[project] for metadata and [build] for compilation settings.
Full reference
[project]
name = "my-paper" # Required. Project name.
version = "0.1.0" # Optional. Semver version string.
author = ["Author Name"] # Optional. List of authors.
main = "src/main.tex" # Required (validation). Path to the main .tex file.
[build]
name = "my-paper" # Required (validation). Output PDF name, no extension.
engine = "tectonic" # Required. pdflatex | xelatex | lualatex | tectonic
engine_args = ["-synctex=1"] # Optional. Extra compiler arguments.
[build.options] # Optional. Tectonic compilation options.
keep_logs = true # Optional. Keep the .log file. Default: true
keep_intermediates = false # Optional. Keep .aux/.synctex.gz. Default: false
synctex = true # Optional. Emit SyncTeX data. Default: true
only_cached = false # Optional. Never hit the network. Default: false
The compiled PDF is always written to the project’s target/ directory —
there is no output-directory option.
Sections
[project]
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Project name. |
version | string | No | Semver version string. |
author | list of strings | No | Project authors. |
main | string | Yes* | Path to the main .tex file relative to the project root. |
* main is optional in the data model but required by validation —
ltx build and ltx check refuse to run without it. See
LTX::CONFIG::E001 in the Config Errors table.
[build]
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes* | Output PDF filename (without .pdf extension). |
engine | string | Yes | LaTeX engine: pdflatex, xelatex, lualatex, or tectonic. |
engine_args | list of strings | No | Extra command-line arguments passed to the compiler. |
* name is required by validation (LTX::CONFIG::E003).
[build.options]
Tectonic compilation options. Every field defaults to a sensible value when omitted, so a project only sets the options it wants to override.
| Field | Type | Default | Description |
|---|---|---|---|
keep_logs | bool | true | Keep the .log file produced by the compiler. |
keep_intermediates | bool | false | Keep intermediate build artifacts (.aux, .synctex.gz). |
synctex | bool | true | Emit SyncTeX data for editor / PDF synchronization. |
only_cached | bool | false | If true, never hit the network — fail if the bundle isn’t cached. |
Validation rules
ltx build validates the manifest before compiling. Malformed TOML and
unknown keys (e.g. a typo like [build.option]) are rejected loudly instead
of silently ignored. The structural rules enforced by validate_manifest():
[project].mainis set and points to an existing file on disk (LTX::CONFIG::E001,LTX::CONFIG::E004).- A
[build]section is present (LTX::CONFIG::E002). [build].nameis non-empty (LTX::CONFIG::E003).
Scaffolding options
When you run ltx new, the directory layout is controlled by flags that map
to internal options:
SrcLayout — source file placement
| Flag | Layout | Result |
|---|---|---|
| (none) | Flat | main.tex in project root |
--src | WithSrcDir | src/main.tex + src/sections/ |
BibLayout — bibliography placement
| Flag | Layout | Result |
|---|---|---|
| (none) | Flat | references.bib in project root |
--bib | WithBibDir | bib/references.bib |
These flags combine freely. For example, ltx new paper --src --bib creates:
paper/
├── src/
│ ├── main.tex
│ └── sections/
├── bib/
│ └── references.bib
├── ltx.toml
└── .gitignore
Minimal config
A project with just the essentials (generated by ltx new my-paper):
[project]
name = "my-paper"
main = "main.tex"
[build]
name = "my-paper"
engine = "tectonic"
Example: bibliography project
[project]
name = "thesis"
version = "0.1.0"
author = ["Jane Doe <jane@example.com>"]
main = "src/main.tex"
[build]
name = "thesis"
engine = "tectonic"
[build.options]
keep_logs = false
only_cached = true
Related
- CLI Usage — the
build/check/codecommands - Config Errors — the
LTX::CONFIG::E0xxcode table
API Overview
Reference for the LTX library crates, for Rust consumers embedding LTX as a
library. The workspace is seven crates; each owns its domain and its errors.
Errors flow through the shared ltx_diagnostics infrastructure to render,
but are defined in the crate that produces them.
Crate map
| Crate | Responsibility | Errors owned |
|---|---|---|
| ltx_diagnostics | Pure infrastructure — spans, source maps, sinks, miette rendering, ErrorCode registry | none (infra only) |
| ltx_lexer | Byte-level tokenizer; catcode handling | LexerError (LTX::LEXER::E001–E011) |
| ltx_parser | Recursive-descent parser; AST construction | ParserError (LTX::PARSER::E001–E006) |
| ltx_config | ltx.toml model, validation, scaffolding | ConfigError (LTX::CONFIG::E001–E008) |
| ltx_compiler | Compilation orchestration; engine dispatch | CompilerError (LTX::COMPILER::E001–E004, W001) |
| ltx_utils | Low-level filesystem helpers | none |
| ltx_cli | Binary entry point; subcommand dispatch | CliError (CLI-only) |
Dependency direction
Dependencies point inward toward reusable infrastructure. ltx_diagnostics
never depends on the other crates, so the graph stays acyclic:
ltx_utils ──► ltx_config ──► ltx_compiler ──► ltx_cli
│ │
▼ ▼
ltx_lexer ──► ltx_parser ──► ltx_diagnostics (shared rendering)
The diagnostic pipeline
Any error type implements LtxDiagnosticSource and is
wrapped in an LtxDiagnostic together with the
LtxSourceMap needed to render it. Diagnostics accumulate in an
LtxDiagnosticSink, then render with miette or serialize to JSON — the
diagnostics crate never needs to know which phase produced an error.
Lexer ──► LexerError ──┐
├─► LtxDiagnostic ──► LtxDiagnosticSink ──► miette renderer
Parser ──► ParserError ┘ └─────► JSON
Code registry
Each crate exports a pub const ALL_CODES: &[ErrorCode] table. The CLI
aggregates all of them for ltx code:
| Namespace | Count | Severities |
|---|---|---|
LTX::LEXER::E0xx | 11 | errors |
LTX::PARSER::E0xx | 6 | errors |
LTX::CONFIG::E0xx | 8 | errors |
LTX::COMPILER::E0xx, W0xx | 5 | errors + 1 warning |
See the Error codes section for full tables.
ltx_diagnostics
Pure diagnostic infrastructure for the LTX toolchain. This crate defines no domain errors — it only provides the building blocks other crates plug into to report, render, and serialize diagnostics.
Responsibilities
- source file management (
LtxSourceMap,LtxSourceFile) - byte-range span utilities (
LtxSpan,LtxFileId) - severity classification (
LtxSeverity: Error / Warning / Hint) mietteintegration and rendering- batch diagnostic reporting (
LtxDiagnosticSink) - the
LtxDiagnosticSourcehelper trait that every crate’s error enum implements - the
ErrorCoderegistry metadata struct
Key types
| Type | Role |
|---|---|
LtxDiagnostic | Wraps Arc<dyn LtxDiagnosticSource> + Arc<LtxSourceMap> so any phase error can be rendered. |
LtxDiagnosticSink | Accumulates diagnostics across phases for batch reporting (never panics). |
LtxSourceMap / LtxSourceFile | Source-text registry; byte-offset → line:column resolution. |
LtxSpan / LtxFileId | Byte-range location in a specific file. |
LtxSeverity | Error / Warning / Hint classification. |
ErrorCode | Registry entry describing one diagnostic code (code, description, severity, phase). |
LtxDiagnosticSource | Trait implemented by every phase-owned error; exposes its primary span. |
Error ownership
Domain errors live in the crates that produce them, not here:
| Crate | Error enum | Codes |
|---|---|---|
ltx_lexer | LexerError | LTX::LEXER::E001–E011 |
ltx_parser | ParserError | LTX::PARSER::E001–E006 |
ltx_config | ConfigError | LTX::CONFIG::E001–E008 |
ltx_compiler | CompilerError | LTX::COMPILER::E001–E004, W001 |
Each of those crates exports a pub const ALL_CODES: &[ErrorCode] registry
that the CLI aggregates for ltx code.
Rendering
render_pretty(diagnostic)/render_pretty_into(...)— miette graphical outputrender_json_into(sink, writer)— JSON-serializable diagnostics for tooling
Usage
#![allow(unused)]
fn main() {
use ltx_diagnostics::{LtxSourceMap, LtxDiagnosticSink, LtxDiagnostic, LtxDiagnosticSource};
let mut source_map = LtxSourceMap::new();
let file_id = source_map.add_file("main.tex")?;
// ... produce an error implementing LtxDiagnosticSource ...
let diagnostic = LtxDiagnostic::new(error, source_map.into());
}
Design constraints (see AGENT.md)
- Never define domain errors here.
- Never depend on lexer / parser / config / compiler crates (keeps the graph acyclic).
ltx_lexer
Byte-level tokenizer for LaTeX source files. Converts raw .tex text
into a stream of LtxTokens, each carrying an LtxTokenKind, an LtxSpan,
and the source slice it was parsed from. Mode-aware (Normal / Math) with
catcode state tracking per the TeX specification.
Responsibilities
- lexical analysis and token generation
- category code handling (
LtxCatCode,LtxCatCodeState) - lexer-specific diagnostics (owned by this crate)
Key types
| Type | Role |
|---|---|
LtxLexer | Streaming iterator — .next() / .next_token() yields one token at a time. |
TokenStream | Eagerly drains LtxLexer; cursor API (peek, bump, checkpoint/rewind) for the parser. |
LtxToken / LtxTokenKind | A single token and its classification. |
LtxMode | Normal or Math operating mode. |
LtxCatCode / LtxCatCodeState | TeX category codes and the current lookup table. |
LexerError | All 11 lexer diagnostics (LTX::LEXER::E001–E011). |
LexerErrorHandler | Collects LtxDiagnostics during lexing; wraps them with the source map. |
Error ownership
LexerError (in src/error.rs) owns every lexer diagnostic: unexpected
token, unexpected EOF, unmatched brace, invalid math delimiter, unterminated
argument, invalid escape sequence, invalid unicode, illegal parameter char,
unterminated verbatim, invalid character, and mismatched environment. Codes
are registered in ALL_CODES under the LTX::LEXER::E0xx namespace. See the
Lexer Errors table.
Usage
#![allow(unused)]
fn main() {
use ltx_lexer::{LtxLexer, TokenStream};
use ltx_diagnostics::LtxSourceMap;
let mut source_map = LtxSourceMap::new();
let file_id = source_map.add_file("main.tex")?;
let stream = TokenStream::new(LtxLexer::new(source, file_id, source_map.into()));
// Hand the stream to the parser, or drive LtxLexer directly.
}
Design notes
- Zero string allocations for tokens — tokens borrow from the source text.
- Most consumers create a
TokenStreamand pass it toltx_parser; only streaming consumers needLtxLexerdirectly.
ltx_parser
Recursive-descent parser for LaTeX. Consumes a ltx_lexer::TokenStream
and produces an AST.
Responsibilities
- syntax parsing and AST generation
- parser-specific diagnostics (owned by this crate)
Architecture
parser::LtxParserwraps aTokenStreamand exposes cursor methods (peek,bump,checkpoint/rewind,skip_ws).parser_traits::Parseis the trait every AST node implements.astcontains the node types:Document,Command,Environment,Math,Group,Text,Comment,UsePackage,DocumentClassDecl,Arg, …parse_documentis the top-level convenience entry point.
AST nodes
| Node | Description |
|---|---|
Document | Top-level root containing preamble and body. |
PreambleItem | Preamble items (\documentclass, \usepackage, …). |
DocumentClassDecl | \documentclass declaration. |
UsePackage | \usepackage declaration. |
Command | Control sequence with its arguments. |
Arg / OptionalArg | Required and optional argument variants. |
Environment | \begin{...}...\end{...} block. |
Group | Balanced {...} group. |
Math | Math expression. |
Text | Plain text run. |
Comment | LaTeX comment. |
Error ownership
ParserError (in src/error.rs) owns all 6 parser diagnostics
(LTX::PARSER::E001–E006):
| Code | Variant |
|---|---|
E001 | ExpectedToken |
E002 | UnexpectedEOF |
E003 | UnclosedEnvironment |
E004 | MismatchedEnvironment |
E005 | MissingClosingBrace |
E006 | UnexpectedEOFWhileParsing |
ParserErrorHandler collects these. When LtxParser::new is constructed it
drains the lexer’s diagnostics into the same handler, so a single sink
reports both phases. See the Parser Errors table.
Usage
#![allow(unused)]
fn main() {
use ltx_parser::{LtxParser, parse_document};
let mut parser = LtxParser::new(stream);
let doc = parse_document(&mut parser);
let handler = parser.error_handler_mut();
if handler.has_errors() {
eprintln!("{}", handler.render_pretty());
}
}
Design notes
- Zero token clones: AST nodes store
Range<usize>token spans or zero-copy&'src strslices. - Diagnostics flow through
ltx_diagnosticsfor rendering — the parser owns the errors, the diagnostics crate renders them.
ltx_config
Configuration and project scaffolding. Provides the data model for
ltx.toml, manifest validation, and project generation.
Responsibilities
- manifest parsing (
LtxManifest) - configuration validation (
validate_manifest) - project scaffolding (
scaffold) - config-specific diagnostics (owned by this crate)
Key types
| Type | Role |
|---|---|
LtxManifest | Top-level ltx.toml structure; LtxManifest::from_file() reads + parses + validates in one step. |
Project | [project] table — name, version, author, main file. |
Build | [build] table — output name, engine, compile options. |
CompileOptions | [build.options] — keep_logs, keep_intermediates, synctex, only_cached (all with sensible defaults). |
CompilerEngine | Enum: PdfLaTeX, XeLaTeX, LuaLaTeX, Tectonic. |
ScaffoldOptions | Options for ltx new (name, engine, src, bib). |
SrcLayout / BibLayout | Flat vs src/ / bib/ directory layouts. |
ConfigError | All 8 config diagnostics (LTX::CONFIG::E001–E008). |
Error ownership
ConfigError (in src/error.rs) owns every manifest/scaffold diagnostic,
including the merged former ManifestDiagnostic + ScaffoldError:
| Code | Variant |
|---|---|
E001 | MissingMain — missing main in [project] |
E002 | MissingBuild — missing [build] section |
E003 | MissingBuildName — missing name in [build] |
E004 | MainFileNotFound — [project].main points to a missing file |
E005 | InvalidToml — malformed TOML / unknown keys |
E006 | ReadFailed — ltx.toml unreadable |
E007 | Io — scaffold I/O error |
E008 | AlreadyExists — project directory already exists |
Validation errors embed the raw source text and a byte span so they render with miette snippets pointing at the offending table or key. See the Config Errors table.
Usage
#![allow(unused)]
fn main() {
use ltx_config::{LtxManifest, Project, Build, CompilerEngine, scaffold, ScaffoldOptions};
// Read + validate in one step
let manifest = LtxManifest::from_file("ltx.toml")?;
// Scaffold a new project
scaffold(&project_dir, &ScaffoldOptions::new("my-paper", CompilerEngine::Tectonic))?;
}
Design notes
- Unknown keys in
ltx.tomlare rejected loudly (serde deny_unknown_fields) instead of silently ignored. - No dependency on lexer/parser — the config layer only talks to
ltx_diagnostics(forErrorCode) andltx_utils(filesystem helpers).
ltx_compiler
Compilation orchestration. Turns a validated manifest into a compiled PDF
by dispatching to a configured engine. Currently only the tectonic engine
is wired up; the other engines emit a warning.
Responsibilities
- compilation pipeline (
build) - engine abstraction (
CompilerConfig, engine dispatch) - tectonic integration (
tectonic_compile) - file watching for rebuild-on-save (
watch) - compiler-specific diagnostics (owned by this crate)
Key types
| Type | Role |
|---|---|
CompilerConfig | Engine, output name, main file, and compile options resolved from a manifest. |
CompilerError | All compiler diagnostics (LTX::COMPILER::E001–E006, W001). |
build::build | Entry point: resolves the main file, then dispatches to the engine. |
tectonic::tectonic_compile | Drives tectonic’s ProcessingSessionBuilder to produce a PDF. |
watch::WatchConfig | Debounced, recursive watcher that rebuilds on relevant changes. |
Error ownership
CompilerError (in src/error.rs):
| Code | Variant |
|---|---|
E001 | MissingMain — no [project].main at compile time |
E002 | MissingBuild — no [build] section |
E003 | MainFileNotFound — main file missing on disk |
E004 | TectonicError — bundle fetch / session creation / compilation failed |
E005 | Init — file watcher failed to start |
E006 | ChannelClosed — watch event channel disconnected |
W001 | EngineNotImplemented — engine not wired up yet (warning) |
CompilerError implements miette::Diagnostic so it converts into
miette::Report and can be returned directly from miette::Result
functions. See the Compiler Errors table.
Engine behavior
tectonic— real compilation via thetectoniccrate.pdflatex/xelatex/lualatex— emitLTX::COMPILER::W001throughmietteto stderr and returnOk(())(a warning never fails the build).
Usage
#![allow(unused)]
fn main() {
use ltx_compiler::{CompilerConfig, build};
let manifest = ltx_config::LtxManifest::from_file("ltx.toml")?;
let config = CompilerConfig::from_manifest(&manifest)?;
build::build(&config, project_root)?;
}
Design notes
- Input is a
CompilerConfig; output always goes totarget/. watch.rswatches only thesrc/root (structuredltx new --srcprojects) or the main file itself (single-structureltx newprojects), plus the manifest, and rebuilds throughbuild::buildon every relevant change (.tex,.sty,.cls,.bib). The compiler’s owntarget/output is never watched, so builds cannot feed back into a rebuild loop. Driven by theltx watchCLI command.
ltx_utils
Low-level filesystem helpers. Small, dependency-free utilities shared by the other crates for common I/O operations.
Functions
| Function | Description |
|---|---|
create_dir(path) | Creates a directory, including all missing parents. |
create_file(path) | Creates a file, including its parent directories. |
write_file(path, contents) | Writes contents to a file, creating parent dirs as needed. |
resolve_main_file(path) | Returns the main entry path, defaulting to main.tex; errors with NotFound if the given path doesn’t exist. |
Usage
#![allow(unused)]
fn main() {
use ltx_utils::{create_dir, write_file};
create_dir(Path::new("my-paper/src/sections"))?;
write_file(Path::new("my-paper/main.tex"), r"\documentclass{article}")?;
}
Design notes
- No dependencies on other
ltx_*crates — usable from anywhere in the workspace. - All functions return
std::io::Result; no panicking variants.
Lexer Errors (LTX::LEXER::E001-E011)
The lexer transforms source text into a token stream during the initial compilation phase. This section enumerates all lexer-related errors, their causes, and recommended resolutions.
Error Reference Table
| Code | Variant | Diagnostic Message | Remediation |
|---|---|---|---|
LTX::LEXER::E001 | UnexpectedToken | Unexpected token encountered | Validate input for invalid characters, malformed commands, or unsupported syntax at the reported position. |
LTX::LEXER::E002 | UnexpectedEOF | Unexpected end of file | Confirm all environments, brace pairs, and command arguments are properly terminated. |
LTX::LEXER::E003 | UnmatchedBrace | Unmatched brace detected | Ensure every opening { has a corresponding closing } and braces are correctly nested. |
LTX::LEXER::E004 | InvalidMathDelimiter | Invalid math delimiter detected | Validate usage of math delimiters: $, $$, \(, \), \[, and \]. |
LTX::LEXER::E005 | UnterminatedArgument | Command argument not terminated | Append missing closing brace } to the command argument. |
LTX::LEXER::E006 | InvalidEscapeSequence | Invalid escape sequence | Verify the command name following the backslash \ is valid. |
LTX::LEXER::E007 | InvalidUnicode | Invalid UTF-8 sequence detected | Re-save the source file with UTF-8 encoding. |
LTX::LEXER::E008 | IllegalParameterChar | Illegal parameter character usage | Ensure # is used only in macro definitions and follows correct syntax. |
LTX::LEXER::E009 | UnterminatedVerbatim | Verbatim environment not terminated | Close the verbatim environment with appropriate termination markers. |
LTX::LEXER::E010 | InvalidCharacter | Invalid character encountered | Remove or replace unsupported character at the reported position. |
LTX::LEXER::E011 | MismatchedEnvironment | Environment close tag mismatch | Close each \begin{env} with an \end{env} of the same name. |
Error Categories
Syntax Errors (LTX::LEXER::E001-E006)
These errors occur when the input violates lexical grammar rules.
LTX::LEXER::E001 - UnexpectedToken
The lexer encountered a sequence that does not form a valid token in the current state. Check for:
- Special characters in invalid contexts
- Malformed commands or arguments
- Syntax that does not conform to the language specification
LTX::LEXER::E002 - UnexpectedEOF
The source input terminated prematurely. Verify that:
- All environment blocks are closed
- Every opening brace has a matching closure
- Command argument lists are complete
LTX::LEXER::E003 - UnmatchedBrace
Brace matching failed during tokenization. Inspect:
- Brace pair count (each
{requires a}) - Brace nesting order
- Arguments enclosed within braces
LTX::LEXER::E004 - InvalidMathDelimiter
Math mode delimiters are incorrectly specified. Valid delimiters:
- Inline:
$...$or\(...\) - Display:
$$...$$or\[...\]
LTX::LEXER::E005 - UnterminatedArgument
A command argument lacks its closing brace. Each { that starts an argument must have a matching }.
LTX::LEXER::E006 - InvalidEscapeSequence
The sequence following a backslash does not form a valid command. Ensure the command name consists of valid characters and exists in the context.
Encoding and Character Errors (LTX::LEXER::E007-E010)
These errors relate to character encoding and invalid character handling.
LTX::LEXER::E007 - InvalidUnicode
The source file contains bytes that do not form valid UTF-8 sequences. Re-encode the file using UTF-8.
LTX::LEXER::E008 - IllegalParameterChar
The # character is used in an invalid context. Proper usage:
- Macro parameter references:
#1,#2, etc. - Macro definitions only
LTX::LEXER::E009 - UnterminatedVerbatim
A verbatim environment was opened but not closed. Ensure the verbatim block terminates correctly.
LTX::LEXER::E010 - InvalidCharacter
The lexer encountered a character not permitted in the current context.
Remove or replace the offending character.
Environment Errors (LTX::LEXER::E011)
LTX::LEXER::E011 - MismatchedEnvironment
An \end{...} names a different environment than the one opened by the matching \begin{...},
or an \end appears with no corresponding \begin. Every environment must be closed with the
same name it was opened with.
Diagnostic Example
🔍 Found 1 issue(s):
LTX::LEXER::E001
× unexpected token `@`
╭─[main.tex:3:19]
2 │ % E001: Unexpected Token
3 │ \newcommand{\foo} @invalid
· ────┬───
· ╰── here
4 │
╰────
help: Check for invalid characters, malformed commands, or unsupported syntax near the highlighted position.
Related Topics
- ltx_lexer API — the crate that owns these errors
- Parser Errors — errors produced during AST construction
Parser Errors (LTX::PARSER::E001-E006)
The parser analyzes the token stream produced by the lexer and constructs an Abstract Syntax Tree (AST) according to the language grammar. This section lists all parser-related errors, their causes, and recommended fixes.
Error Reference Table
| Code | Variant | Diagnostic Message | Remediation |
|---|---|---|---|
LTX::PARSER::E001 | ExpectedToken | Expected token not found | Check the syntax near the highlighted position — a token is missing or misplaced. |
LTX::PARSER::E002 | UnexpectedEOF | Unexpected end of file | Close the construct with its matching delimiter before the file end. |
LTX::PARSER::E003 | UnclosedEnvironment | Environment was not closed | Add a matching \end{...} for every \begin{...}. |
LTX::PARSER::E004 | MismatchedEnvironment | Environment closing tag mismatch | Ensure \end{...} matches the corresponding \begin{...} name. |
LTX::PARSER::E005 | MissingClosingBrace | Missing closing brace | Add the matching closing brace } to terminate the group. |
LTX::PARSER::E006 | UnexpectedEOFWhileParsing | End of file reached while parsing a structure | Ensure required structures (e.g. \begin{document}) are present. |
Error Categories
Token Errors (LTX::PARSER::E001-E002)
LTX::PARSER::E001 - ExpectedToken
The parser expected a specific token — such as {, }, or an environment — but found something else or reached the end of the stream.
% ❌ Incorrect (missing closing brace)
\newcommand{\foo}{Hello
% ✅ Correct
\newcommand{\foo}{Hello}
LTX::PARSER::E002 - UnexpectedEOF
A construct being parsed (for example inline math) ran to the end of the file without its closing delimiter.
% ❌ Incorrect
A sentence with $x + y = z$
% ✅ Correct
A sentence with $x + y = z$.
Environment Errors (LTX::PARSER::E003-E004)
LTX::PARSER::E003 - UnclosedEnvironment
An environment was opened with \begin{...} but never closed with \end{...}.
% ❌ Incorrect
\begin{itemize}
\item First item
\item Second item
% ✅ Correct
\begin{itemize}
\item First item
\item Second item
\end{itemize}
LTX::PARSER::E004 - MismatchedEnvironment
The environment closing tag doesn’t match the opening tag.
% ❌ Incorrect
\begin{itemize}
\item First item
\end{enumerate}
% ✅ Correct
\begin{itemize}
\item First item
\end{itemize}
Group Errors (LTX::PARSER::E005)
LTX::PARSER::E005 - MissingClosingBrace
A braced group { ... reached the end of the token stream without a matching }.
% ❌ Incorrect
\section{Introduction
% ✅ Correct
\section{Introduction}
Document Structure Errors (LTX::PARSER::E006)
LTX::PARSER::E006 - UnexpectedEOFWhileParsing
The top-level document parsing ended before a required structure — such as \begin{document} — was found.
% ❌ Incorrect
\documentclass{article}
Hello world
% ✅ Correct
\documentclass{article}
\begin{document}
Hello world
\end{document}
Diagnostic Example
🔍 Found 1 issue(s):
LTX::PARSER::E004
× mismatched environment: expected `\end{itemize}`, found `\end{enumerate}`
╭─[main.tex:18:1]
17 │ \begin{itemize}
18 │ \end{enumerate}
· ────────┬───────
· ╰── here
19 │
╰────
help: Environments must be closed with the same name they were opened with.
Best Practices
- Always use matching
\beginand\endpairs - Keep environments properly nested - Verify command spelling - Use autocompletion or reference documentation
- Load required packages - Ensure all necessary packages are imported
- Close every brace and argument - Each
{needs a matching} - Structure the document correctly - Include
\begin{document}after the preamble
Related Topics
- ltx_parser API — the crate that owns these errors
- Lexer Errors - Errors during tokenization
- Config Errors - Manifest validation errors
Config Errors (LTX::CONFIG::E001-E008)
The config crate owns every error it can produce while reading or validating
ltx.toml and while scaffolding a new project. Manifest-validation errors
embed the raw source text and a byte span so they render with a miette snippet
pointing at the offending table or key; read failures and scaffold I/O errors
have no source snippet.
Error Reference Table
| Code | Variant | Diagnostic Message | Remediation |
|---|---|---|---|
LTX::CONFIG::E001 | MissingMain | missing main in the [project] section | Add main = "src/main.tex" (or the path to your main file). |
LTX::CONFIG::E002 | MissingBuild | missing [build] section | Add a [build] section with name = "..." and engine = "tectonic". |
LTX::CONFIG::E003 | MissingBuildName | missing name in the [build] section | Add name = "output" (without the .pdf extension). |
LTX::CONFIG::E004 | MainFileNotFound | main file not found: {path} | Create the file or fix the main value in the [project] section. |
LTX::CONFIG::E005 | InvalidToml | invalid ltx.toml: {reason} | Fix the TOML syntax or remove the unknown key. |
LTX::CONFIG::E006 | ReadFailed | failed to read {path}: {error} | Make sure the file exists and is readable. |
LTX::CONFIG::E007 | Io | (transparent I/O error) | Fix the underlying filesystem failure. |
LTX::CONFIG::E008 | AlreadyExists | project directory {0} already exists | Remove the directory or choose a different project name. |
Validation rules
validate_manifest() (called by ltx build and LtxManifest::from_file())
rejects a manifest when:
[project].mainis missing (E001) or points to a file that does not exist (E004).- The
[build]section is absent (E002). [build].nameis missing (E003).
Malformed TOML and unknown keys (e.g. a typo like [build.option]) are
reported as E005 before any semantic checks run.
Diagnostic Example
LTX::CONFIG::E001
× missing `main` in the `[project]` section
╭─[ltx.toml:1:1]
1 │ [project]
· ────┬────
· ╰── the `main` key is required here
2 │ name = "demo"
╰────
help: add `main = "src/main.tex"` (or the path to your main file)
Related Topics
- Configuration — the
ltx.tomlreference - Compiler Errors — errors produced during compilation
Compiler Errors (LTX::COMPILER::E001-E004, W001)
The compiler crate owns every error it can produce while turning a manifest
into a compiled PDF: missing configuration, missing input files, and engine
(tectonic) failures. CompilerError implements miette::Diagnostic, so it
converts into a miette::Report and is returned directly from
miette::Result functions.
Error Reference Table
| Code | Variant | Diagnostic Message | Remediation |
|---|---|---|---|
LTX::COMPILER::E001 | MissingMain | no main file set in ltx.toml | Add main = "main.tex" under [project]. |
LTX::COMPILER::E002 | MissingBuild | no [build] section in ltx.toml | Add a [build] section with engine and name. |
LTX::COMPILER::E003 | MainFileNotFound | main file {path} not found | Create the file or fix the main value in [project]. |
LTX::COMPILER::E004 | TectonicError | engine failure {message} | Check the engine logs; verify your network connection for bundle issues. |
LTX::COMPILER::W001 | EngineNotImplemented | {engine} engine is not implemented yet | Use engine = "tectonic", or wait for this engine to land. |
Error Categories
Configuration Errors (E001-E002)
These mirror the config-layer validation but occur at compile time, when the
resolved CompilerConfig is missing information it needs.
E001 - MissingMain
The [project] section has no main entry, so the compiler doesn’t know
which file to compile.
E002 - MissingBuild
The manifest has no [build] section, so the engine and output name are
unknown.
Input Errors (E003)
E003 - MainFileNotFound
The main input file resolved from [project].main does not exist on disk at
build time. Note the config layer catches this earlier during validation
(LTX::CONFIG::E004); this variant covers paths resolved at compile time.
Engine Errors (E004)
E004 - TectonicError
The selected engine failed to fetch its support bundle, create its processing
session, or finish the compilation. The message carries the underlying engine
error.
Warnings (W001)
W001 - EngineNotImplemented
The selected engine is not wired up yet — only tectonic is available. The
warning is rendered through miette and the build still exits successfully.
Diagnostic Example
LTX::COMPILER::W001
⚠ `pdflatex` engine is not implemented yet — only `tectonic` is available
help: use `engine = "tectonic"` in `ltx.toml`, or wait for this engine to
land
Related Topics
- Configuration — engine selection in
ltx.toml - Config Errors — manifest validation errors