pub mod context;
pub mod engine;
mod environment;
mod markup;
mod math;
mod table;
mod utils;
pub use context::{
ConversionMode, ConversionState, EnvironmentContext, L2TOptions, LatexConverter, MERGED_SPEC,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WarningKind {
UnsupportedMacro,
PartialExpansion,
UnsupportedPrimitive,
LaTeX3Skipped,
PatternMismatch,
RunawayArgument,
MacroLoop,
ParseError,
}
impl std::fmt::Display for WarningKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WarningKind::UnsupportedMacro => write!(f, "unsupported macro"),
WarningKind::PartialExpansion => write!(f, "partial expansion"),
WarningKind::UnsupportedPrimitive => write!(f, "unsupported primitive"),
WarningKind::LaTeX3Skipped => write!(f, "LaTeX3 skipped"),
WarningKind::PatternMismatch => write!(f, "pattern mismatch"),
WarningKind::RunawayArgument => write!(f, "runaway argument"),
WarningKind::MacroLoop => write!(f, "macro loop"),
WarningKind::ParseError => write!(f, "parse error"),
}
}
}
#[derive(Debug, Clone)]
pub struct ConversionWarning {
pub kind: WarningKind,
pub message: String,
pub location: Option<String>,
}
impl ConversionWarning {
pub fn new(kind: WarningKind, message: impl Into<String>) -> Self {
ConversionWarning {
kind,
message: message.into(),
location: None,
}
}
pub fn with_location(mut self, location: impl Into<String>) -> Self {
self.location = Some(location.into());
self
}
pub fn unsupported_macro(name: &str) -> Self {
ConversionWarning::new(
WarningKind::UnsupportedMacro,
format!("Unknown macro '{}' passed through unchanged", name),
)
.with_location(name.to_string())
}
pub fn unsupported_primitive(name: &str) -> Self {
ConversionWarning::new(
WarningKind::UnsupportedPrimitive,
format!(
"Primitive '{}' is not supported and may produce incorrect output",
name
),
)
.with_location(name.to_string())
}
pub fn latex3_skipped(start: &str, end: &str) -> Self {
ConversionWarning::new(
WarningKind::LaTeX3Skipped,
format!("LaTeX3 block ({} ... {}) was skipped", start, end),
)
}
pub fn pattern_mismatch(macro_name: &str) -> Self {
ConversionWarning::new(
WarningKind::PatternMismatch,
format!("Argument pattern for '{}' did not match input", macro_name),
)
.with_location(macro_name.to_string())
}
pub fn runaway_argument(macro_name: &str) -> Self {
ConversionWarning::new(
WarningKind::RunawayArgument,
format!(
"Runaway argument while parsing '{}' (missing delimiter?)",
macro_name
),
)
.with_location(macro_name.to_string())
}
pub fn macro_loop(macro_name: &str) -> Self {
ConversionWarning::new(
WarningKind::MacroLoop,
format!("Infinite recursion detected in macro '{}'", macro_name),
)
.with_location(macro_name.to_string())
}
pub fn parse_error(msg: impl Into<String>) -> Self {
ConversionWarning::new(WarningKind::ParseError, msg)
}
}
impl std::fmt::Display for ConversionWarning {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(ref loc) = self.location {
write!(f, "[{}] {}: {}", self.kind, loc, self.message)
} else {
write!(f, "[{}] {}", self.kind, self.message)
}
}
}
impl From<ConversionWarning> for crate::utils::error::CliDiagnostic {
fn from(warning: ConversionWarning) -> Self {
use crate::utils::error::{CliDiagnostic, DiagnosticSeverity};
let severity = match warning.kind {
WarningKind::MacroLoop | WarningKind::RunawayArgument => DiagnosticSeverity::Error,
WarningKind::UnsupportedMacro
| WarningKind::PartialExpansion
| WarningKind::PatternMismatch
| WarningKind::ParseError => DiagnosticSeverity::Warning,
WarningKind::UnsupportedPrimitive | WarningKind::LaTeX3Skipped => {
DiagnosticSeverity::Info
}
};
let mut diag = CliDiagnostic::new(severity, warning.kind.to_string(), warning.message);
if let Some(loc) = warning.location {
diag = diag.with_location(loc);
}
diag
}
}
#[derive(Debug, Clone)]
pub struct ConversionResult {
pub output: String,
pub warnings: Vec<ConversionWarning>,
}
impl ConversionResult {
pub fn ok(output: String) -> Self {
ConversionResult {
output,
warnings: Vec::new(),
}
}
pub fn with_warnings(output: String, warnings: Vec<ConversionWarning>) -> Self {
ConversionResult { output, warnings }
}
pub fn has_warnings(&self) -> bool {
!self.warnings.is_empty()
}
pub fn format_warnings(&self) -> Vec<String> {
self.warnings.iter().map(|w| w.to_string()).collect()
}
}
pub fn latex_to_typst(input: &str) -> String {
let mut converter = LatexConverter::new();
converter.convert_document(input)
}
pub fn latex_math_to_typst(input: &str) -> String {
let mut converter = LatexConverter::new();
converter.convert_math(input)
}
pub fn convert_with_ast(input: &str) -> String {
latex_to_typst(input)
}
pub fn convert_with_ast_options(input: &str, options: L2TOptions) -> String {
let mut converter = LatexConverter::with_options(options);
converter.convert_document(input)
}
pub fn convert_document_with_ast(input: &str) -> String {
latex_to_typst(input)
}
pub fn convert_document_with_ast_options(input: &str, options: L2TOptions) -> String {
let mut converter = LatexConverter::with_options(options);
converter.convert_document(input)
}
pub fn convert_math_with_ast(input: &str) -> String {
latex_math_to_typst(input)
}
pub fn convert_math_with_ast_options(input: &str, options: L2TOptions) -> String {
let mut converter = LatexConverter::with_options(options);
converter.convert_math(input)
}
pub fn latex_to_typst_with_eval(input: &str) -> String {
let options = L2TOptions {
expand_macros: true,
..Default::default()
};
let mut converter = LatexConverter::with_options(options);
converter.convert_document(input)
}
pub fn latex_math_to_typst_with_eval(input: &str) -> String {
let options = L2TOptions {
expand_macros: true,
..Default::default()
};
let mut converter = LatexConverter::with_options(options);
converter.convert_math(input)
}
pub fn latex_to_typst_with_diagnostics(input: &str) -> ConversionResult {
let mut converter = LatexConverter::new();
converter.convert_document_with_diagnostics(input)
}
pub fn latex_math_to_typst_with_diagnostics(input: &str) -> ConversionResult {
let mut converter = LatexConverter::new();
converter.convert_math_with_diagnostics(input)
}