katex_parser/environments/
registry.rs1use 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)]
17pub 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
31pub 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#[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
47pub 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)]
56pub 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)]
145pub 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
261pub 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}