Skip to main content

katex_parser/environments/
registry.rs

1use std::collections::HashMap;
2
3use crate::ast::{ArrayColumn, ColumnSeparationType, Mode, ParseNode, StyleLevel};
4use crate::error::ParseError;
5use crate::function_registry::ArgType;
6use crate::functions::require_function_arg;
7
8use super::alignat::alignat_environment_handler;
9use super::alignment::{aligned_environment_handler, gather_environment_handler};
10use super::cases::cases_environment_handler;
11use super::cd::cd_environment_handler;
12use super::equation::equation_environment_handler;
13use super::matrix::{matrix_environment_handler, smallmatrix_environment_handler};
14use super::subarray::subarray_environment_handler;
15
16#[derive(Debug, Clone)]
17/// Options controlling how an array environment is parsed.
18pub struct ArrayEnvironmentOptions {
19    pub columns: Option<Vec<ArrayColumn>>,
20    pub array_stretch: f64,
21    pub hskip_before_and_after: bool,
22    pub cell_style: StyleLevel,
23    pub max_columns: Option<usize>,
24    pub single_row: bool,
25    pub auto_tag: Option<bool>,
26    pub leqno: bool,
27    pub add_jot: bool,
28    pub column_separation_type: Option<ColumnSeparationType>,
29}
30
31/// Callbacks the parser exposes to environment handlers.
32pub trait EnvironmentParser {
33    fn parse_array(&mut self, options: ArrayEnvironmentOptions) -> Result<ParseNode, ParseError>;
34    fn parse_matrix_alignment(&mut self) -> Result<Option<String>, ParseError>;
35    fn parse_cd(&mut self) -> Result<ParseNode, ParseError>;
36}
37
38/// Per-environment-call context: static data about the environment.
39#[derive(Debug, Clone)]
40pub struct EnvironmentContext {
41    pub mode: Mode,
42    pub display_mode: bool,
43    pub leqno: bool,
44    pub env_name: String,
45}
46
47/// An environment handler implementation.
48pub type EnvironmentHandler = fn(
49    parser: &mut dyn EnvironmentParser,
50    context: &EnvironmentContext,
51    args: &[ParseNode],
52    opt_args: &[Option<ParseNode>],
53) -> Result<ParseNode, ParseError>;
54
55#[derive(Debug, Clone)]
56/// The declaration of an environment (name, arguments, handler).
57pub struct EnvironmentSpec {
58    pub names: Vec<String>,
59    pub num_args: usize,
60    pub num_optional_args: usize,
61    pub arg_types: Vec<ArgType>,
62    pub handler: EnvironmentHandler,
63}
64
65impl Default for EnvironmentSpec {
66    fn default() -> Self {
67        EnvironmentSpec {
68            names: Vec::new(),
69            num_args: 0,
70            num_optional_args: 0,
71            arg_types: Vec::new(),
72            handler: |_, _, _, _| Err(ParseError::InternalInvariant {
73                message: "Environment handler not set".to_string(),
74            }),
75        }
76    }
77}
78
79pub fn array_columns(arg: &ParseNode, name: &str) -> Result<Vec<ArrayColumn>, ParseError> {
80    let nodes = match arg {
81        ParseNode::OrdGroup { body, .. } => body.clone(),
82        _ => vec![arg.clone()],
83    };
84    let mut columns = Vec::new();
85    for node in nodes {
86        let text = match node {
87            ParseNode::Atom { text, .. }
88            | ParseNode::MathOrd { text, .. }
89            | ParseNode::TextOrd { text, .. }
90            | ParseNode::Spacing { text, .. } => text,
91            _ => {
92                return Err(ParseError::InvalidArgument {
93                    message: format!("Unknown column alignment in {name}"),
94                    loc: None,
95                })
96            }
97        };
98        match text.as_str() {
99            "l" | "c" | "r" => columns.push(ArrayColumn::AlignColumn {
100                alignment: text,
101                pre_gap: 0.0,
102                post_gap: 0.0,
103            }),
104            "|" | ":" => columns.push(ArrayColumn::SeparatorColumn { separator: text }),
105            _ => {
106                return Err(ParseError::InvalidArgument {
107                    message: format!("Unknown column alignment: {text}"),
108                    loc: None,
109                })
110            }
111        }
112    }
113    Ok(columns)
114}
115
116fn array_environment_handler(
117    parser: &mut dyn EnvironmentParser,
118    context: &EnvironmentContext,
119    args: &[ParseNode],
120    _opt_args: &[Option<ParseNode>],
121) -> Result<ParseNode, ParseError> {
122    let columns = array_columns(
123        &require_function_arg(args, 0, &format!("\\begin{{{}}}", context.env_name))?,
124        &context.env_name,
125    )?;
126    parser.parse_array(ArrayEnvironmentOptions {
127        columns: Some(columns.clone()),
128        array_stretch: 1.0,
129        hskip_before_and_after: true,
130        cell_style: if context.env_name == "darray" {
131            StyleLevel::DisplayStyle
132        } else {
133            StyleLevel::TextStyle
134        },
135        max_columns: Some(columns.len()),
136        single_row: false,
137        auto_tag: None,
138        leqno: false,
139        add_jot: false,
140        column_separation_type: None,
141    })
142}
143
144#[derive(Default)]
145/// A map from environment names to their specs.
146pub struct EnvironmentRegistry {
147    entries: HashMap<String, EnvironmentSpec>,
148}
149
150impl EnvironmentRegistry {
151    pub fn new() -> Self {
152        EnvironmentRegistry {
153            entries: HashMap::new(),
154        }
155    }
156
157    pub fn register(&mut self, spec: EnvironmentSpec) {
158        for name in &spec.names {
159            self.entries.insert(name.clone(), spec.clone());
160        }
161    }
162
163    pub fn get(&self, name: &str) -> Option<&EnvironmentSpec> {
164        self.entries.get(name)
165    }
166
167    pub fn keys(&self) -> Vec<String> {
168        self.entries.keys().cloned().collect()
169    }
170}
171
172pub fn builtin_environment_specs() -> Vec<EnvironmentSpec> {
173    vec![
174        EnvironmentSpec {
175            names: vec!["array".to_string(), "darray".to_string()],
176            num_args: 1,
177            handler: array_environment_handler,
178            ..Default::default()
179        },
180        EnvironmentSpec {
181            names: vec![
182                "matrix".to_string(),
183                "pmatrix".to_string(),
184                "bmatrix".to_string(),
185                "Bmatrix".to_string(),
186                "vmatrix".to_string(),
187                "Vmatrix".to_string(),
188                "matrix*".to_string(),
189                "pmatrix*".to_string(),
190                "bmatrix*".to_string(),
191                "Bmatrix*".to_string(),
192                "vmatrix*".to_string(),
193                "Vmatrix*".to_string(),
194            ],
195            handler: matrix_environment_handler,
196            ..Default::default()
197        },
198        EnvironmentSpec {
199            names: vec!["smallmatrix".to_string()],
200            handler: smallmatrix_environment_handler,
201            ..Default::default()
202        },
203        EnvironmentSpec {
204            names: vec![
205                "cases".to_string(),
206                "dcases".to_string(),
207                "rcases".to_string(),
208                "drcases".to_string(),
209            ],
210            handler: cases_environment_handler,
211            ..Default::default()
212        },
213        EnvironmentSpec {
214            names: vec!["equation".to_string(), "equation*".to_string()],
215            handler: equation_environment_handler,
216            ..Default::default()
217        },
218        EnvironmentSpec {
219            names: vec![
220                "aligned".to_string(),
221                "align".to_string(),
222                "align*".to_string(),
223                "split".to_string(),
224            ],
225            handler: aligned_environment_handler,
226            ..Default::default()
227        },
228        EnvironmentSpec {
229            names: vec![
230                "gathered".to_string(),
231                "gather".to_string(),
232                "gather*".to_string(),
233            ],
234            handler: gather_environment_handler,
235            ..Default::default()
236        },
237        EnvironmentSpec {
238            names: vec![
239                "alignat".to_string(),
240                "alignat*".to_string(),
241                "alignedat".to_string(),
242            ],
243            num_args: 1,
244            handler: alignat_environment_handler,
245            ..Default::default()
246        },
247        EnvironmentSpec {
248            names: vec!["subarray".to_string()],
249            num_args: 1,
250            handler: subarray_environment_handler,
251            ..Default::default()
252        },
253        EnvironmentSpec {
254            names: vec!["CD".to_string()],
255            handler: cd_environment_handler,
256            ..Default::default()
257        },
258    ]
259}
260
261/// Builds an environment registry from the builtin specs plus caller-provided
262/// extension specs (which override builtins sharing the same name).
263pub fn build_environment_registry(extra_specs: &[EnvironmentSpec]) -> EnvironmentRegistry {
264    let mut registry = EnvironmentRegistry::new();
265    for spec in builtin_environment_specs() {
266        registry.register(spec);
267    }
268    for spec in extra_specs {
269        registry.register(spec.clone());
270    }
271    registry
272}