1use std::collections::HashMap;
4
5use crate::parser::{EnvRow, EqNumber, MathNode, MatrixStyle};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum NumberStyle {
10 Arabic,
12 Roman,
14 Alphabetic,
16}
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum NumberFormat {
21 Parenthesized,
23 Bracketed,
25 Plain,
27}
28
29#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct NumberingConfig {
43 pub style: NumberStyle,
45 pub start: usize,
47 pub format: NumberFormat,
49}
50
51impl Default for NumberingConfig {
52 fn default() -> Self {
53 Self {
54 style: NumberStyle::Arabic,
55 start: 1,
56 format: NumberFormat::Parenthesized,
57 }
58 }
59}
60
61impl NumberingConfig {
62 #[must_use]
64 pub fn new() -> Self {
65 Self::default()
66 }
67}
68
69#[derive(Clone, Debug)]
80pub struct NumberingState {
81 config: NumberingConfig,
82 next: usize,
83 labels: HashMap<String, String>,
84 assigned: Vec<Option<String>>,
85}
86
87impl Default for NumberingState {
88 fn default() -> Self {
89 Self::new(NumberingConfig::default())
90 }
91}
92
93impl NumberingState {
94 #[must_use]
96 pub fn new(config: NumberingConfig) -> Self {
97 let next = config.start;
98 Self {
99 config,
100 next,
101 labels: HashMap::new(),
102 assigned: Vec::new(),
103 }
104 }
105
106 #[must_use]
108 pub fn label(&self, key: &str) -> Option<&str> {
109 self.labels.get(key).map(String::as_str)
110 }
111
112 pub(crate) fn lookup(&self, key: &str) -> Option<&str> {
113 self.label(key)
114 }
115
116 pub(crate) fn assigned(&self, i: usize) -> Option<&str> {
117 self.assigned.get(i).and_then(|o| o.as_deref())
118 }
119
120 pub fn collect(&mut self, node: &MathNode) -> usize {
123 let start = self.assigned.len();
124 collect_node(node, self);
125 start
126 }
127
128 fn wrap(&self, body: &str) -> String {
129 match self.config.format {
130 NumberFormat::Parenthesized => format!("({body})"),
131 NumberFormat::Bracketed => format!("[{body}]"),
132 NumberFormat::Plain => body.to_string(),
133 }
134 }
135
136 fn auto_body(&self, n: usize) -> String {
137 match self.config.style {
138 NumberStyle::Arabic => n.to_string(),
139 NumberStyle::Roman => to_roman(n),
140 NumberStyle::Alphabetic => to_alpha(n),
141 }
142 }
143
144 fn auto_display(&mut self) -> String {
145 let n = self.next;
146 self.next = self.next.saturating_add(1);
147 self.wrap(&self.auto_body(n))
148 }
149
150 fn bind(&mut self, labels: &[String], display: &str) {
151 for k in labels {
152 self.labels.insert(k.clone(), display.to_string());
153 }
154 }
155}
156
157fn collect_node(node: &MathNode, st: &mut NumberingState) {
158 match node {
159 MathNode::Matrix(style, _, rows) => collect_matrix(*style, rows, st),
160 MathNode::Row(v) | MathNode::Substack(v) => {
161 for n in v {
162 collect_node(n, st);
163 }
164 }
165 MathNode::Fraction(a, b)
166 | MathNode::Superscript(a, b)
167 | MathNode::Subscript(a, b)
168 | MathNode::CancelTo(a, b) => {
169 collect_node(a, st);
170 collect_node(b, st);
171 }
172 MathNode::SubSup(a, b, c) => {
173 collect_node(a, st);
174 collect_node(b, st);
175 collect_node(c, st);
176 }
177 MathNode::Radical(deg, r) => {
178 if let Some(d) = deg {
179 collect_node(d, st);
180 }
181 collect_node(r, st);
182 }
183 MathNode::Delimited(_, b, _)
184 | MathNode::Accent(b, _)
185 | MathNode::Color(_, b)
186 | MathNode::TextColor(_, b)
187 | MathNode::ColorBox(_, b)
188 | MathNode::Phantom(_, b)
189 | MathNode::Intertext(b)
190 | MathNode::Tag { body: b, .. } => collect_node(b, st),
191 MathNode::FColorBox(_, _, b) => collect_node(b, st),
192 MathNode::Sum(lo, hi) | MathNode::Product(lo, hi) | MathNode::Integral(_, lo, hi) => {
193 if let Some(n) = lo {
194 collect_node(n, st);
195 }
196 if let Some(n) = hi {
197 collect_node(n, st);
198 }
199 }
200 MathNode::Limit(lo) => {
201 if let Some(n) = lo {
202 collect_node(n, st);
203 }
204 }
205 MathNode::OverUnder(b, over, under) => {
206 collect_node(b, st);
207 if let Some(n) = over {
208 collect_node(n, st);
209 }
210 if let Some(n) = under {
211 collect_node(n, st);
212 }
213 }
214 MathNode::Atom(_, _)
215 | MathNode::SizedDelim(_, _, _)
216 | MathNode::Text(_, _)
217 | MathNode::Space(_)
218 | MathNode::Operator(_, _)
219 | MathNode::Symbol(_)
220 | MathNode::Strut(_, _)
221 | MathNode::Ref(_)
222 | MathNode::Label(_)
223 | MathNode::NoNumber
224 | MathNode::Hline => {}
225 }
226}
227
228fn collect_matrix(style: MatrixStyle, rows: &[EnvRow], st: &mut NumberingState) {
229 for row in rows {
230 match row {
231 EnvRow::Cells { cells, .. } => {
232 for c in cells {
233 collect_node(c, st);
234 }
235 }
236 EnvRow::Intertext(n) => collect_node(n, st),
237 EnvRow::Hline => {}
238 }
239 }
240 if style.numbers_rows() {
241 for row in rows {
242 match row {
243 EnvRow::Intertext(_) | EnvRow::Hline => {}
244 EnvRow::Cells { number, labels, .. } => {
245 let display = assign(number, st);
246 if let Some(d) = &display {
247 st.bind(labels, d);
248 }
249 st.assigned.push(display);
250 }
251 }
252 }
253 } else if style.numbers_once() {
254 let mut number = EqNumber::Default;
255 let mut labels = Vec::new();
256 for row in rows {
257 if let EnvRow::Cells {
258 number: n,
259 labels: l,
260 ..
261 } = row
262 {
263 match n {
264 EqNumber::Default => {}
265 other => number = other.clone(),
266 }
267 labels.extend(l.iter().cloned());
268 }
269 }
270 let display = assign(&number, st);
271 if let Some(d) = &display {
272 st.bind(&labels, d);
273 }
274 st.assigned.push(display);
275 }
276}
277
278fn assign(number: &EqNumber, st: &mut NumberingState) -> Option<String> {
279 match number {
280 EqNumber::Suppress => None,
281 EqNumber::Tag { star, body } => {
282 let plain = node_plain(body);
283 let display = if *star { plain } else { st.wrap(&plain) };
284 Some(display)
285 }
286 EqNumber::Default => Some(st.auto_display()),
287 }
288}
289
290fn node_plain(n: &MathNode) -> String {
291 match n {
292 MathNode::Atom(c, _) => c.to_string(),
293 MathNode::Text(s, _) => s.clone(),
294 MathNode::Symbol(name) => name.clone(),
295 MathNode::Row(v) => v.iter().map(node_plain).collect(),
296 MathNode::Tag { body, .. } => node_plain(body),
297 other => other.gold(),
298 }
299}
300
301fn to_roman(mut n: usize) -> String {
302 if n == 0 {
303 return "0".into();
304 }
305 let pairs: [(usize, &str); 13] = [
306 (1000, "m"),
307 (900, "cm"),
308 (500, "d"),
309 (400, "cd"),
310 (100, "c"),
311 (90, "xc"),
312 (50, "l"),
313 (40, "xl"),
314 (10, "x"),
315 (9, "ix"),
316 (5, "v"),
317 (4, "iv"),
318 (1, "i"),
319 ];
320 let mut s = String::new();
321 for (v, g) in pairs {
322 while n >= v {
323 s.push_str(g);
324 n -= v;
325 }
326 }
327 s
328}
329
330fn to_alpha(mut n: usize) -> String {
331 if n == 0 {
332 return "0".into();
333 }
334 let mut s = String::new();
335 while n > 0 {
336 n -= 1;
337 s.insert(0, char::from(b'a' + (n % 26) as u8));
338 n /= 26;
339 }
340 s
341}