FerroTeX Public API Reference
This document provides a comprehensive reference for the public API surface of all core FerroTeX crates.
Table of Contents
- Overview
- Crate Dependency Graph
- ferrotex-syntax
- ferrotex-analysis
- ferrotex-core
- ferrotex-dap
- ferrotex-build
- ferrotex-package
- ferrotex-log
- ferrotex-math-semantics
Overview
FerroTeX is a modular, research-grade LaTeX platform built with Rust. The architecture follows a layered approach:
- Syntax Layer (
ferrotex-syntax): Provides fault-tolerant parsing of LaTeX source code - Log Analysis Layer (
ferrotex-log): Streaming parser for LaTeX engine logs - Analysis Layer (
ferrotex-analysis): Abstract interpretation for macro analysis - Core Layer (
ferrotex-core): Package management and validation utilities - Build Layer (
ferrotex-build): Content-addressable build system - Debug Layer (
ferrotex-dap): Debug Adapter Protocol implementation - Package Layer (
ferrotex-package): LaTeX package indexing and metadata - Math Semantics (
ferrotex-math-semantics): Formal semantics for math environments
Crate Dependency Graph
┌─────────────────┐
│ ferrotex-cli │
└────────┬────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ ferrotex-dap │ │ferrotex-analysis│ │ ferrotex-build │
└────────┬──────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ ferrotex-syntax │◄──────────────────┘
│ └─────────────────┘ │
│ ▲ │
│ │ ▼
│ ┌─────────┴─────────┐ ┌─────────────────┐
└───────────────►│ferrotex-math-semantics│ │ ferrotex-core │
└─────────────────────────┘ └────────┬────────┘
│
▼
┌─────────────────┐
│ ferrotex-package│
└─────────────────┘
│
▼
┌─────────────────┐
│ ferrotex-log │
└─────────────────┘
Dependency Summary:
| Crate | Dependencies |
|---|---|
ferrotex-syntax |
rowan |
ferrotex-log |
serde, serde_json, thiserror, regex |
ferrotex-analysis |
ferrotex-syntax, serde, thiserror |
ferrotex-math-semantics |
ferrotex-syntax, serde, thiserror, rowan |
ferrotex-build |
serde, anyhow, sha2, hex |
ferrotex-package |
serde, regex, walkdir, dirs, log, anyhow |
ferrotex-core |
ferrotex-build, tokio, anyhow, serde, which, once_cell, async-trait |
ferrotex-dap |
ferrotex-build, serde, anyhow, log (optional: tectonic) |
ferrotex-syntax
Fault-tolerant LaTeX parser providing a lossless Concrete Syntax Tree (CST).
Primary Entry Points
parse(source: &str) -> ParseResult
Parses LaTeX source code into a syntax tree.
use ferrotex_syntax::parse;
let source = r"\section{Introduction}";
let result = parse(source);
let root = result.syntax();
Returns: ParseResult containing the root SyntaxNode and any parse errors.
Key Types
SyntaxKind
Enumeration of all token and node types in the LaTeX grammar.
pub enum SyntaxKind {
// Tokens
LBrace, // Left brace `{`
RBrace, // Right brace `}`
LBracket, // Left bracket `[`
RBracket, // Right bracket `]`
Command, // LaTeX command (e.g., `\section`)
Dollar, // Dollar sign `$` for math
Whitespace, // Whitespace characters
Comment, // Comment starting with `%`
Text, // Regular text content
Error, // Lexer error token
// Composite Nodes
Root, // Root of the syntax tree
Group, // Braced group `{ ... }`
Environment, // `\begin{...} ... \end{...}`
Section, // Section command
Include, // `\input`, `\include`
LabelDefinition, // `\label{...}`
LabelReference, // `\ref{...}`
Citation, // `\cite{...}`
Bibliography, // `\bibliography{...}`
Eof, // End of file
}
SyntaxNode
A node in the Concrete Syntax Tree. Provides traversal and inspection methods.
pub type SyntaxNode = rowan::SyntaxNode<FerroTexLanguage>;
// Example usage
let result = parse(source);
let root = result.syntax();
for child in root.children() {
println!("{:?} at {:?}", child.kind(), child.text_range());
}
SyntaxToken
A terminal token (leaf) in the CST.
pub type SyntaxToken = rowan::SyntaxToken<FerroTexLanguage>;
SyntaxElement
A syntax element that can be either a node or a token.
pub type SyntaxElement = rowan::SyntaxElement<FerroTexLanguage>;
Re-exports from rowan
TextRange: Represents a range of text positionsTextSize: Represents a position in the source text
Error Types
Parse errors are collected in ParseResult::errors as SyntaxError structs containing:
message: Error descriptionrange:TextRangewhere the error occurred
Modules
lexer: Tokenization (Lexer::new(source))parser: Parsing implementation (Parser,ParseResult)bibtex: Specialized parsing for BibTeX files
ferrotex-analysis
Abstract interpretation framework for analyzing TeX macro behavior.
Primary Entry Points
AbstractMachine::new() -> Self
Creates a new abstract machine for macro analysis.
use ferrotex_analysis::AbstractMachine;
let mut machine = AbstractMachine::new();
Key Types
AbstractValue
Represents a set of possible concrete TeX values in abstract interpretation.
pub enum AbstractValue {
Any, // Top: any possible token list
ControlSequence(String), // Specific control sequence
Group, // Braced group `{ ... }`
Dimension, // Dimension value
Integer, // Integer value
Bottom, // Bottom: unreachable
Token(String), // Simple token
AnalysisError(String), // Analysis error
}
Example:
use ferrotex_analysis::AbstractValue;
let cs = AbstractValue::ControlSequence("\\def".to_string());
let dim = AbstractValue::Dimension;
AbstractState
The state of the abstract machine during analysis.
pub struct AbstractState {
pub input_stack: Vec<AbstractValue>,
pub registers: HashMap<String, AbstractValue>,
}
AbstractMachine
Simplified abstract machine for analyzing TeX macro expansion.
pub struct AbstractMachine {
pub state: AbstractState,
pub expansion_depth: usize,
pub max_depth: usize,
pub call_stack: Vec<String>,
}
impl AbstractMachine {
pub fn new() -> Self;
pub fn step(&mut self) -> Option<AbstractValue>;
}
Example:
use ferrotex_analysis::{AbstractMachine, AbstractValue};
let mut machine = AbstractMachine::new();
machine.state.input_stack.push(AbstractValue::ControlSequence("\\def".to_string()));
while let Some(token) = machine.step() {
println!("Processed: {:?}", token);
}
Error Types
AbstractValue::AnalysisError(String): Reports analysis errors including:- Maximum recursion depth exceeded
- Infinite recursion detected
ferrotex-core
Core utilities including package management and math validation.
Module: package_manager
TeX distribution abstraction supporting tlmgr and MiKTeX.
Primary Entry Points
PackageManager::new() -> Self
Auto-detects and creates a package manager for the available TeX distribution.
use ferrotex_core::package_manager::PackageManager;
let pm = PackageManager::new();
if pm.is_available() {
let status = pm.install("amsmath")?;
}
Key Types
PackageManager
High-level facade for package management operations.
impl PackageManager {
pub fn new() -> Self; // Auto-detect backend
pub fn with_backend(backend: Arc<dyn PackageBackend>) -> Self; // Custom backend
pub fn install(&self, package: &str) -> Result<InstallStatus>;
pub fn search(&self, query: &str) -> Result<Vec<String>>;
pub fn is_available(&self) -> bool;
pub fn get_ctan_link(filename: &str) -> Option<String>;
}
PackageBackend (Trait)
Interface for TeX package manager backends.
pub trait PackageBackend: Debug + Send + Sync {
fn install(&self, package: &str) -> Result<InstallStatus>;
fn search(&self, query: &str) -> Result<Vec<String>>;
fn name(&self) -> &'static str;
}
Implementations:
TlmgrBackend: TeX Live Manager backendMiktexBackend: MiKTeX Package Manager backendNoOpBackend: Fallback when no package manager is available
InstallStatus
Result of a package installation attempt.
pub struct InstallStatus {
pub name: String,
pub state: InstallState,
pub message: Option<String>,
}
pub enum InstallState {
Complete,
Pending,
Failed,
Unknown,
}
CommandExecutor (Trait)
Abstraction for executing system commands (enables testing).
pub trait CommandExecutor: Send + Sync + Debug {
fn execute(&self, program: &Path, args: &[&str]) -> Result<std::process::Output>;
}
Module: math_validator
Validation tools for LaTeX mathematical expressions.
Key Types
DelimiterValidator
Stack-based delimiter matching validator.
use ferrotex_core::math_validator::{Delimiter, DelimiterKind, DelimiterValidator};
let mut validator = DelimiterValidator::new();
let delimiters = vec![
Delimiter { kind: DelimiterKind::LeftParen, position: 0, is_left_command: false },
Delimiter { kind: DelimiterKind::RightParen, position: 10, is_left_command: false },
];
validator.validate(&delimiters);
if validator.has_errors() {
for error in validator.errors() {
eprintln!("{}", error.to_diagnostic_message());
}
}
DelimiterKind
Enumeration of delimiter types.
pub enum DelimiterKind {
LeftParen, RightParen,
LeftBracket, RightBracket,
LeftBrace, RightBrace,
LeftAngle, RightAngle, // \langle, \rangle
LeftFloor, RightFloor, // \lfloor, \rfloor
LeftCeil, RightCeil, // \lceil, \rceil
}
Delimiter
Represents a delimiter occurrence in source code.
pub struct Delimiter {
pub kind: DelimiterKind,
pub position: usize,
pub is_left_command: bool, // Was created with \left/\right
### Error Types
#### `MathError`
Math validation errors with diagnostic information.
```rust
pub enum MathError {
MismatchedDelimiter { left_pos, right_pos, left_kind, right_kind },
UnmatchedOpening { pos, kind },
UnmatchedClosing { pos, kind },
IncorrectArgumentCount { command, position, expected, actual },
}
Unified Error System
The core crate re-exports the unified error types from ferrotex-build for convenience across the platform:
FerroTeXError: Unified error typeFerroTeXResult<T>: Alias forResult<T, FerroTeXError>SourceLocation: Line and columnSourceSpan: Source range
Function: get_expected_args
Returns expected argument count for common math commands.
use ferrotex_core::math_validator::get_expected_args;
assert_eq!(get_expected_args("frac"), Some(2));
assert_eq!(get_expected_args("sqrt"), Some(1));
assert_eq!(get_expected_args("unknown"), None);
ferrotex-dap
Debug Adapter Protocol (DAP) implementation for TeX debugging.
Primary Entry Points
run_mock_session() -> Result<()>
Starts a standard DAP loop on stdin/stdout for testing.
use ferrotex_dap::run_mock_session;
run_mock_session()?;
Key Types
ProtocolMessage
DAP message types for requests, responses, and events.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ProtocolMessage {
Request {
seq: i64,
command: String,
arguments: Option<serde_json::Value>,
},
Response {
seq: i64,
request_seq: i64,
success: bool,
command: String,
message: Option<String>,
body: Option<serde_json::Value>,
},
Event {
seq: i64,
event: String,
body: Option<serde_json::Value>,
},
}
Example:
use ferrotex_dap::ProtocolMessage;
use serde_json::json;
let msg = ProtocolMessage::Request {
seq: 1,
command: "initialize".to_string(),
arguments: Some(json!({"adapterID": "ferrotex"})),
};
DebugAdapter (Trait)
Core trait for Debug Adapter implementations.
pub trait DebugAdapter {
fn initialize(&mut self, args: serde_json::Value) -> Result<serde_json::Value>;
fn launch(&mut self, args: serde_json::Value) -> Result<()>;
fn continue_execution(&mut self) -> Result<()>;
fn next(&mut self) -> Result<()>;
fn step_in(&mut self) -> Result<()>;
fn scopes(&mut self, args: serde_json::Value) -> Result<serde_json::Value>;
fn variables(&mut self, args: serde_json::Value) -> Result<serde_json::Value>;
fn disconnect(&mut self) -> Result<()>;
fn evaluate(&mut self, args: serde_json::Value) -> Result<serde_json::Value>;
fn attach(&mut self, args: serde_json::Value) -> Result<serde_json::Value>;
}
DebugSession<A: DebugAdapter>
Generic session handler managing the DAP protocol loop.
pub struct DebugSession<A: DebugAdapter> {
adapter: A,
seq: i64,
}
impl<A: DebugAdapter> DebugSession<A> {
pub fn new(adapter: A) -> Self;
pub fn run_loop(&mut self) -> Result<()>;
}
DebugDriver
Driver trait for debug session implementations.
pub trait DebugDriver {
type Event;
type Command;
fn spawn(self) -> (Sender<Self::Command>, Receiver<Self::Event>);
}
Error Types
Errors are returned as anyhow::Result with descriptive messages.
ferrotex-build
Content-addressable build system using a Directed Acyclic Graph (DAG).
Primary Entry Points
Lockfile::new() -> Self
Creates a new lockfile for reproducible builds.
use ferrotex_build::Lockfile;
use std::path::Path;
let mut lock = Lockfile::new();
lock.entries.insert("main.tex".to_string(), "abc123hash".to_string());
lock.save(Path::new("ferrotex.lock"))?;
Key Types
Lockfile
Captures workspace state with SHA-256 content fingerprints.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Lockfile {
pub version: String,
pub entries: HashMap<String, String>, // path -> sha256 hash
}
impl Lockfile {
pub fn new() -> Self;
pub fn save(&self, path: &Path) -> anyhow::Result<()>;
pub fn load(path: &Path) -> anyhow::Result<Self>;
}
Artifact (Trait)
Represents an input or output of the build process.
pub trait Artifact {
fn id(&self) -> ArtifactId;
fn fingerprint(&self) -> String;
fn path(&self) -> Option<PathBuf>;
}
ArtifactId
Unique identifier for artifacts.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ArtifactId(pub String);
FileArtifact
Concrete artifact implementation for files.
use ferrotex_build::FileArtifact;
use std::path::PathBuf;
let artifact = FileArtifact::new(PathBuf::from("main.tex"));
let id = artifact.id();
let hash = artifact.fingerprint();
Transform (Trait)
Build step that converts input artifacts to output artifacts.
pub trait Transform {
fn description(&self) -> String;
fn inputs(&self) -> HashSet<ArtifactId>;
fn outputs(&self) -> HashSet<ArtifactId>;
fn execute(&self) -> Result<(), String>;
}
BuildGraph
DAG of all transforms and artifacts.
pub struct BuildGraph {
artifacts: HashMap<ArtifactId, Box<dyn Artifact>>,
transforms: Vec<Box<dyn Transform>>,
}
impl BuildGraph {
pub fn new() -> Self;
pub fn add_artifact(&mut self, artifact: Box<dyn Artifact>);
pub fn add_transform(&mut self, transform: Box<dyn Transform>);
pub fn validate(&self) -> Result<(), String>; // Detects cycles
}
Compiler
Configuration for executing external TeX engines.
pub struct Compiler {
pub engine: String, // e.g., "pdflatex", "xelatex"
pub output_dir: PathBuf,
pub extra_args: Vec<String>,
}
impl Compiler {
pub fn new(engine: &str, output_dir: PathBuf) -> Self;
pub fn with_args(self, args: Vec<String>) -> Self;
}
ShellTransform
Executes shell commands as build steps.
use ferrotex_build::{ShellTransform, ArtifactId};
use std::collections::HashSet;
let mut inputs = HashSet::new();
inputs.insert(ArtifactId("in.tex".to_string()));
let mut outputs = HashSet::new();
outputs.insert(ArtifactId("out.pdf".to_string()));
let transform = ShellTransform::new(
"Compile LaTeX",
inputs,
outputs,
"pdflatex",
vec!["-interaction=nonstopmode".to_string()],
);
PdfLatexTransform
Convenience wrapper for pdflatex compilation.
use ferrotex_build::PdfLatexTransform;
let transform = PdfLatexTransform::new(
ArtifactId("input.tex".to_string()),
ArtifactId("output.pdf".to_string()),
PathBuf::from("input.tex"),
PathBuf::from("build"),
);
Error Types
All build operations return FerroTeXResult<T>, where FerroTeXError provides unified error handling with source locations and rich context.
FerroTeXError: Unified error type (re-exported fromerrormodule)FerroTeXResult<T>: Alias forResult<T, FerroTeXError>SourceLocation: 1-indexed line and columnSourceSpan: Range between twoSourceLocations
Modules
ferrotex-package
LaTeX package indexing and metadata management.
Primary Entry Points
PackageIndex::new() -> Self
Creates a new empty package index.
use ferrotex_package::PackageIndex;
let mut index = PackageIndex::new();
Key Types
PackageIndex
Persistent index of LaTeX packages and their metadata.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PackageIndex {
pub packages: HashMap<String, PackageMetadata>,
}
impl PackageIndex {
pub fn new() -> Self;
pub fn insert(&mut self, name: String, metadata: PackageMetadata);
pub fn get(&self, name: &str) -> Option<&PackageMetadata>;
pub fn cache_path() -> Option<std::path::PathBuf>;
pub fn save_to_cache(&self) -> std::io::Result<()>;
pub fn load_from_cache() -> Option<Self>;
pub fn save_to_path(&self, path: &std::path::Path) -> std::io::Result<()>;
pub fn load_from_path(path: &std::path::Path) -> Option<Self>;
}
Example:
use ferrotex_package::PackageIndex;
// Load from cache
if let Some(index) = PackageIndex::load_from_cache() {
if let Some(meta) = index.get("amsmath") {
println!("Commands: {:?}", meta.commands);
}
}
PackageMetadata
Metadata for a single LaTeX package.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PackageMetadata {
pub commands: Vec<String>, // e.g., ["\text", "\dfrac"]
pub environments: Vec<String>, // e.g., ["align", "gather"]
}
PackageScanner
Scans TeX distribution to populate the package index.
use ferrotex_package::scanner::PackageScanner;
let scanner = PackageScanner::new();
let index = scanner.scan();
Located in: ferrotex_package::scanner::PackageScanner
Error Types
std::io::Error: File I/O errors during cache operationsserde_json::Error: JSON serialization errors (mapped to io::Error)
ferrotex-log
Streaming parser for TeX engine log files with structured event output.
Primary Entry Points
LogParser::new() -> Self
Creates a new log parser with an empty file stack.
use ferrotex_log::LogParser;
let mut parser = LogParser::new();
LogParser::update(&mut self, chunk: &str) -> Vec<LogEvent>
Processes a chunk of log output and returns any events discovered.
LogParser::parse(&self, content: &str) -> Vec<LogEvent>
Convenience method for one-shot parsing of complete log content.
Key Types
LogEvent
Base event with span, confidence, and payload.
pub struct LogEvent {
pub payload: EventPayload,
pub span: SourceSpan,
pub confidence: f32,
}
EventPayload
Discriminated union of event types.
pub enum EventPayload {
FileEnter { path: String },
FileExit,
ErrorStart { message: String },
Warning { message: String },
ErrorLineRef { line: usize, excerpt: String },
}
ferrotex-math-semantics
Formal semantics and shape analysis for LaTeX math environments.
Primary Entry Points
Direct type usage for shape analysis.
use ferrotex_math_semantics::{Shape, Dimension};
let mat = Shape::Matrix {
rows: Dimension::Finite(2),
cols: Dimension::Finite(3),
};
assert!(mat.is_compatible_add(&mat));
Key Types
Shape
Represents the dimensionality of mathematical objects.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Shape {
Scalar,
Vector(Dimension),
Matrix { rows: Dimension, cols: Dimension },
Tensor(Vec<Dimension>),
Unknown,
Invalid(String),
}
impl Shape {
pub fn is_compatible_add(&self, other: &Shape) -> bool;
pub fn is_compatible_mul(&self, other: &Shape) -> bool;
}
Example:
use ferrotex_math_semantics::{Shape, Dimension};
let vec1 = Shape::Vector(Dimension::Finite(3));
let vec2 = Shape::Vector(Dimension::Finite(3));
assert!(vec1.is_compatible_add(&vec2));
let mat = Shape::Matrix {
rows: Dimension::Finite(3),
cols: Dimension::Symbolic("n".to_string()),
};
Dimension
Represents a single dimension size (concrete or symbolic).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Dimension {
Finite(usize), // Known size (e.g., 3)
Symbolic(String), // Symbolic size (e.g., "n")
Unknown, // Unknown size
}
Modules
analysis: Shape inference algorithmsdelimiters: Math delimiter analysis
Error Types
Uses thiserror for structured error handling. See module-level documentation for specific error types.
Version Information
All core crates are versioned together:
Current Version: 0.22.0
Feature Flags
ferrotex-dap
jxoesneon-tectonic-engine: Enables integration with the Tectonic TeX engine for live debugging
Other Crates
Currently, other crates do not define feature flags. All functionality is available by default.
Additional Resources
- FerroTeX Repository
- rowan Documentation (syntax tree library)
- Debug Adapter Protocol