Skip to main content

yggdrasil/api/
mhchem_tables.rs

1// mhchem 状态机转移表 + 机器局部动作(由 mhchem.rs `include!`)。
2// 数据机械移植自 mhchemParser 4.2.2 的 `stateMachines`。
3// (被 include 进 mhchem.rs 中部,故不能用 `//!` 内部文档注释。)
4
5// ── Task 构造器 ──────────────────────────────────────────────────────────
6
7fn task(acts: &[(&str, Option<&str>)], next: Option<&str>, revisit: bool, cont: bool) -> Task {
8    Task {
9        actions: acts
10            .iter()
11            .map(|(t, o)| ActionRef {
12                type_: (*t).to_string(),
13                option: o.map(|s| s.to_string()),
14            })
15            .collect(),
16        next_state: next.map(|s| s.to_string()),
17        revisit,
18        to_continue: cont,
19    }
20}
21
22fn raw(patterns: &'static str, states: &'static str, task: Task) -> RawEntry {
23    RawEntry {
24        patterns,
25        states,
26        task,
27    }
28}
29
30// ── 转移表取用 ────────────────────────────────────────────────────────────
31
32fn transitions_for(machine: &str) -> &'static HashMap<String, Vec<Transition>> {
33    match machine {
34        "ce" => &CE,
35        "a" => &A,
36        "o" => &O,
37        "text" => &TEXT,
38        "pq" => &PQ,
39        "bd" => &BD,
40        "oxidation" => &OXIDATION,
41        "tex-math" => &TEX_MATH,
42        "tex-math tight" => &TEX_MATH_TIGHT,
43        "9,9" => &NUM99,
44        "pu" => &PU,
45        "pu-2" => &PU2,
46        "pu-9,9" => &PU99,
47        _ => &CE,
48    }
49}
50
51// =========================================================================
52// ce 状态机(主解析器)
53// =========================================================================
54
55static CE: LazyLock<HashMap<String, Vec<Transition>>> = LazyLock::new(|| {
56    build_transitions(&[
57        raw("empty", "*", task(&[("output", None)], None, false, false)),
58        raw(
59            "else",
60            "0|1|2",
61            task(&[("beginsWithBond=false", None)], None, true, true),
62        ),
63        raw(
64            "oxidation$",
65            "0",
66            task(&[("oxidation-output", None)], None, false, false),
67        ),
68        raw(
69            "CMT",
70            "r",
71            task(&[("rdt=", None)], Some("rt"), false, false),
72        ),
73        raw(
74            "CMT",
75            "rd",
76            task(&[("rqt=", None)], Some("rdt"), false, false),
77        ),
78        raw(
79            "arrowUpDown",
80            "0|1|2|as",
81            task(&[("sb=false", None), ("output", None), ("operator", None)], Some("1"), false, false),
82        ),
83        raw(
84            "uprightEntities",
85            "0|1|2",
86            task(&[("o=", None), ("output", None)], Some("1"), false, false),
87        ),
88        raw("orbital", "0|1|2|3", task(&[("o=", None)], Some("o"), false, false)),
89        raw("->", "0|1|2|3", task(&[("r=", None)], Some("r"), false, false)),
90        raw(
91            "->",
92            "a|as",
93            task(&[("output", None), ("r=", None)], Some("r"), false, false),
94        ),
95        raw(
96            "->",
97            "*",
98            task(&[("output", None), ("r=", None)], Some("r"), false, false),
99        ),
100        raw("+", "o", task(&[("d= kv", None)], Some("d"), false, false)),
101        raw("+", "d|D", task(&[("d=", None)], Some("d"), false, false)),
102        raw("+", "q", task(&[("d=", None)], Some("qd"), false, false)),
103        raw("+", "qd|qD", task(&[("d=", None)], Some("qd"), false, false)),
104        raw(
105            "+",
106            "dq",
107            task(&[("output", None), ("d=", None)], Some("d"), false, false),
108        ),
109        raw(
110            "+",
111            "3",
112            task(&[("sb=false", None), ("output", None), ("operator", None)], Some("0"), false, false),
113        ),
114        raw("amount", "0|2", task(&[("a=", None)], Some("a"), false, false)),
115        raw(
116            "pm-operator",
117            "0|1|2|a|as",
118            task(&[("sb=false", None), ("output", None), ("operator", Some("\\pm"))], Some("0"), false, false),
119        ),
120        raw(
121            "operator",
122            "0|1|2|a|as",
123            task(&[("sb=false", None), ("output", None), ("operator", None)], Some("0"), false, false),
124        ),
125        raw(
126            "-$",
127            "o|q",
128            task(&[("charge or bond", None), ("output", None)], Some("qd"), false, false),
129        ),
130        raw("-$", "d", task(&[("d=", None)], Some("d"), false, false)),
131        raw(
132            "-$",
133            "D",
134            task(&[("output", None), ("bond", Some("-"))], Some("3"), false, false),
135        ),
136        raw("-$", "q", task(&[("d=", None)], Some("qd"), false, false)),
137        raw("-$", "qd", task(&[("d=", None)], Some("qd"), false, false)),
138        raw(
139            "-$",
140            "qD|dq",
141            task(&[("output", None), ("bond", Some("-"))], Some("3"), false, false),
142        ),
143        raw(
144            "-9",
145            "3|o",
146            task(&[("output", None), ("insert", Some("hyphen"))], Some("3"), false, false),
147        ),
148        raw(
149            "- orbital overlap",
150            "o",
151            task(&[("output", None), ("insert", Some("hyphen"))], Some("2"), false, false),
152        ),
153        raw(
154            "- orbital overlap",
155            "d",
156            task(&[("output", None), ("insert", Some("hyphen"))], Some("2"), false, false),
157        ),
158        raw(
159            "-",
160            "0|1|2",
161            task(&[("output", Some("1")), ("beginsWithBond=true", None), ("bond", Some("-"))], Some("3"), false, false),
162        ),
163        raw("-", "3", task(&[("bond", Some("-"))], None, false, false)),
164        raw(
165            "-",
166            "a",
167            task(&[("output", None), ("insert", Some("hyphen"))], Some("2"), false, false),
168        ),
169        raw(
170            "-",
171            "as",
172            task(&[("output", Some("2")), ("bond", Some("-"))], Some("3"), false, false),
173        ),
174        raw("-", "b", task(&[("b=", None)], None, false, false)),
175        raw(
176            "-",
177            "o",
178            task(&[("- after o/d", Some("false"))], Some("2"), false, false),
179        ),
180        raw(
181            "-",
182            "q",
183            task(&[("- after o/d", Some("false"))], Some("2"), false, false),
184        ),
185        raw(
186            "-",
187            "d|qd|dq",
188            task(&[("- after o/d", Some("true"))], Some("2"), false, false),
189        ),
190        raw(
191            "-",
192            "D|qD|p",
193            task(&[("output", None), ("bond", Some("-"))], Some("3"), false, false),
194        ),
195        raw("amount2", "1|3", task(&[("a=", None)], Some("a"), false, false)),
196        raw(
197            "letters",
198            "0|1|2|3|a|as|b|p|bp|o",
199            task(&[("o=", None)], Some("o"), false, false),
200        ),
201        raw(
202            "letters",
203            "q|dq",
204            task(&[("output", None), ("o=", None)], Some("o"), false, false),
205        ),
206        raw(
207            "letters",
208            "d|D|qd|qD",
209            task(&[("o after d", None)], Some("o"), false, false),
210        ),
211        raw("digits", "o", task(&[("q=", None)], Some("q"), false, false)),
212        raw("digits", "d|D", task(&[("q=", None)], Some("dq"), false, false)),
213        raw(
214            "digits",
215            "q",
216            task(&[("output", None), ("o=", None)], Some("o"), false, false),
217        ),
218        raw("digits", "a", task(&[("o=", None)], Some("o"), false, false)),
219        raw("space A", "b|p|bp", task(&[], None, false, false)),
220        raw("space", "a", task(&[], Some("as"), false, false)),
221        raw("space", "0", task(&[("sb=false", None)], None, false, false)),
222        raw("space", "1|2", task(&[("sb=true", None)], None, false, false)),
223        raw(
224            "space",
225            "r|rt|rd|rdt|rdq",
226            task(&[("output", None)], Some("0"), false, false),
227        ),
228        raw(
229            "space",
230            "*",
231            task(&[("output", None), ("sb=true", None)], Some("1"), false, false),
232        ),
233        raw(
234            "1st-level escape",
235            "1|2",
236            task(&[("output", None), ("insert+p1", Some("1st-level escape"))], None, false, false),
237        ),
238        raw(
239            "1st-level escape",
240            "*",
241            task(&[("output", None), ("insert+p1", Some("1st-level escape"))], Some("0"), false, false),
242        ),
243        raw("[(...)]", "r|rt", task(&[("rd=", None)], Some("rd"), false, false)),
244        raw(
245            "[(...)]",
246            "rd|rdt",
247            task(&[("rq=", None)], Some("rdq"), false, false),
248        ),
249        raw(
250            "...",
251            "o|d|D|dq|qd|qD",
252            task(&[("output", None), ("bond", Some("..."))], Some("3"), false, false),
253        ),
254        raw(
255            "...",
256            "*",
257            task(&[("output", Some("1")), ("insert", Some("ellipsis"))], Some("1"), false, false),
258        ),
259        raw(
260            ". __* ",
261            "*",
262            task(&[("output", None), ("insert", Some("addition compound"))], Some("1"), false, false),
263        ),
264        raw(
265            "state of aggregation $",
266            "*",
267            task(&[("output", None), ("state of aggregation", None)], Some("1"), false, false),
268        ),
269        raw(
270            "{[(",
271            "a|as|o",
272            task(&[("o=", None), ("output", None), ("parenthesisLevel++", None)], Some("2"), false, false),
273        ),
274        raw(
275            "{[(",
276            "0|1|2|3",
277            task(&[("o=", None), ("output", None), ("parenthesisLevel++", None)], Some("2"), false, false),
278        ),
279        raw(
280            "{[(",
281            "*",
282            task(&[("output", None), ("o=", None), ("output", None), ("parenthesisLevel++", None)], Some("2"), false, false),
283        ),
284        raw(
285            ")]}",
286            "0|1|2|3|b|p|bp|o",
287            task(&[("o=", None), ("parenthesisLevel--", None)], Some("o"), false, false),
288        ),
289        raw(
290            ")]}",
291            "a|as|d|D|q|qd|qD|dq",
292            task(&[("output", None), ("o=", None), ("parenthesisLevel--", None)], Some("o"), false, false),
293        ),
294        raw(
295            ", ",
296            "*",
297            task(&[("output", None), ("comma", None)], Some("0"), false, false),
298        ),
299        raw("^_", "*", task(&[], None, false, false)),
300        raw(
301            "^{(...)}|^($...$)",
302            "0|1|2|as",
303            task(&[("b=", None)], Some("b"), false, false),
304        ),
305        raw(
306            "^{(...)}|^($...$)",
307            "p",
308            task(&[("b=", None)], Some("bp"), false, false),
309        ),
310        raw(
311            "^{(...)}|^($...$)",
312            "3|o",
313            task(&[("d= kv", None)], Some("D"), false, false),
314        ),
315        raw(
316            "^{(...)}|^($...$)",
317            "q",
318            task(&[("d=", None)], Some("qD"), false, false),
319        ),
320        raw(
321            "^{(...)}|^($...$)",
322            "d|D|qd|qD|dq",
323            task(&[("output", None), ("d=", None)], Some("D"), false, false),
324        ),
325        raw(
326            "^a|^\\x{}{}|^\\x{}|^\\x|'",
327            "0|1|2|as",
328            task(&[("b=", None)], Some("b"), false, false),
329        ),
330        raw(
331            "^a|^\\x{}{}|^\\x{}|^\\x|'",
332            "p",
333            task(&[("b=", None)], Some("bp"), false, false),
334        ),
335        raw(
336            "^a|^\\x{}{}|^\\x{}|^\\x|'",
337            "3|o",
338            task(&[("d= kv", None)], Some("d"), false, false),
339        ),
340        raw(
341            "^a|^\\x{}{}|^\\x{}|^\\x|'",
342            "q",
343            task(&[("d=", None)], Some("qd"), false, false),
344        ),
345        raw(
346            "^a|^\\x{}{}|^\\x{}|^\\x|'",
347            "d|qd|D|qD",
348            task(&[("d=", None)], None, false, false),
349        ),
350        raw(
351            "^a|^\\x{}{}|^\\x{}|^\\x|'",
352            "dq",
353            task(&[("output", None), ("d=", None)], Some("d"), false, false),
354        ),
355        raw(
356            "_{(state of aggregation)}$",
357            "d|D|q|qd|qD|dq",
358            task(&[("output", None), ("q=", None)], Some("q"), false, false),
359        ),
360        raw(
361            "_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x",
362            "0|1|2|as",
363            task(&[("p=", None)], Some("p"), false, false),
364        ),
365        raw(
366            "_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x",
367            "b",
368            task(&[("p=", None)], Some("bp"), false, false),
369        ),
370        raw(
371            "_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x",
372            "3|o",
373            task(&[("q=", None)], Some("q"), false, false),
374        ),
375        raw(
376            "_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x",
377            "d|D",
378            task(&[("q=", None)], Some("dq"), false, false),
379        ),
380        raw(
381            "_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x",
382            "q|qd|qD|dq",
383            task(&[("output", None), ("q=", None)], Some("q"), false, false),
384        ),
385        raw(
386            "=<>",
387            "0|1|2|3|a|as|o|q|d|D|qd|qD|dq",
388            task(&[("output", Some("2")), ("bond", None)], Some("3"), false, false),
389        ),
390        raw(
391            "#",
392            "0|1|2|3|a|as|o",
393            task(&[("output", Some("2")), ("bond", Some("#"))], Some("3"), false, false),
394        ),
395        raw(
396            "{}^",
397            "*",
398            task(&[("output", Some("1")), ("insert", Some("tinySkip"))], Some("1"), false, false),
399        ),
400        raw("{}", "*", task(&[("output", Some("1"))], Some("1"), false, false)),
401        raw(
402            "{...}",
403            "0|1|2|3|a|as|b|p|bp",
404            task(&[("o=", None)], Some("o"), false, false),
405        ),
406        raw(
407            "{...}",
408            "o|d|D|q|qd|qD|dq",
409            task(&[("output", None), ("o=", None)], Some("o"), false, false),
410        ),
411        raw("$...$", "a", task(&[("a=", None)], None, false, false)),
412        raw(
413            "$...$",
414            "0|1|2|3|as|b|p|bp|o",
415            task(&[("o=", None)], Some("o"), false, false),
416        ),
417        raw("$...$", "as|o", task(&[("o=", None)], None, false, false)),
418        raw(
419            "$...$",
420            "q|d|D|qd|qD|dq",
421            task(&[("output", None), ("o=", None)], Some("o"), false, false),
422        ),
423        raw(
424            "\\bond{(...)}",
425            "*",
426            task(&[("output", Some("2")), ("bond", None)], Some("3"), false, false),
427        ),
428        raw(
429            "\\frac{(...)}",
430            "*",
431            task(&[("output", Some("1")), ("frac-output", None)], Some("3"), false, false),
432        ),
433        raw(
434            "\\overset{(...)}",
435            "*",
436            task(&[("output", Some("2")), ("overset-output", None)], Some("3"), false, false),
437        ),
438        raw(
439            "\\underset{(...)}",
440            "*",
441            task(&[("output", Some("2")), ("underset-output", None)], Some("3"), false, false),
442        ),
443        raw(
444            "\\underbrace{(...)}",
445            "*",
446            task(&[("output", Some("2")), ("underbrace-output", None)], Some("3"), false, false),
447        ),
448        raw(
449            "\\color{(...)}{(...)}",
450            "*",
451            task(&[("output", Some("2")), ("color-output", None)], Some("3"), false, false),
452        ),
453        raw(
454            "\\color{(...)}",
455            "*",
456            task(&[("output", Some("2")), ("color0-output", None)], None, false, false),
457        ),
458        raw(
459            "\\ce{(...)}",
460            "*",
461            task(&[("output", Some("2")), ("ce", None)], Some("3"), false, false),
462        ),
463        raw(
464            "\\,",
465            "*",
466            task(&[("output", Some("1")), ("copy", None)], Some("1"), false, false),
467        ),
468        raw(
469            "\\pu{(...)}",
470            "*",
471            task(&[("output", None), ("write", Some("{")), ("pu", None), ("write", Some("}"))], Some("3"), false, false),
472        ),
473        raw(
474            "\\x{}{}|\\x{}|\\x",
475            "0|1|2|3|a|as|b|p|bp|o|c0",
476            task(&[("o=", None), ("output", None)], Some("3"), false, false),
477        ),
478        raw(
479            "\\x{}{}|\\x{}|\\x",
480            "*",
481            task(&[("output", None), ("o=", None), ("output", None)], Some("3"), false, false),
482        ),
483        raw(
484            "others",
485            "*",
486            task(&[("output", Some("1")), ("copy", None)], Some("3"), false, false),
487        ),
488        raw(
489            "else2",
490            "a",
491            task(&[("a to o", None)], Some("o"), true, false),
492        ),
493        raw(
494            "else2",
495            "as",
496            task(&[("output", None), ("sb=true", None)], Some("1"), true, false),
497        ),
498        raw(
499            "else2",
500            "r|rt|rd|rdt|rdq",
501            task(&[("output", None)], Some("0"), true, false),
502        ),
503        raw(
504            "else2",
505            "*",
506            task(&[("output", None), ("copy", None)], Some("3"), true, false),
507        ),
508    ])
509});
510
511// =========================================================================
512// a / o / text / pq / bd / oxidation / tex-math / tex-math tight / 9,9
513// =========================================================================
514
515static A: LazyLock<HashMap<String, Vec<Transition>>> = LazyLock::new(|| {
516    build_transitions(&[
517        raw("empty", "*", task(&[], None, false, false)),
518        raw("1/2$", "0", task(&[("1/2", None)], None, false, false)),
519        raw("else", "0", task(&[], Some("1"), true, false)),
520        raw(
521            "${(...)}$__$(...)$",
522            "*",
523            task(&[("tex-math tight", None)], Some("1"), false, false),
524        ),
525        raw(
526            ",",
527            "*",
528            task(&[("insert", Some("commaDecimal"))], None, false, false),
529        ),
530        raw("else2", "*", task(&[("copy", None)], None, false, false)),
531    ])
532});
533
534static O: LazyLock<HashMap<String, Vec<Transition>>> = LazyLock::new(|| {
535    build_transitions(&[
536        raw("empty", "*", task(&[], None, false, false)),
537        raw("1/2$", "0", task(&[("1/2", None)], None, false, false)),
538        raw("else", "0", task(&[], Some("1"), true, false)),
539        raw("letters", "*", task(&[("rm", None)], None, false, false)),
540        raw(
541            "\\ca",
542            "*",
543            task(&[("insert", Some("circa"))], None, false, false),
544        ),
545        raw(
546            "\\pu{(...)}",
547            "*",
548            task(&[("write", Some("{")), ("pu", None), ("write", Some("}"))], None, false, false),
549        ),
550        raw("\\x{}{}|\\x{}|\\x", "*", task(&[("copy", None)], None, false, false)),
551        raw(
552            "${(...)}$__$(...)$",
553            "*",
554            task(&[("tex-math", None)], None, false, false),
555        ),
556        raw(
557            "{(...)}",
558            "*",
559            task(&[("write", Some("{")), ("text", None), ("write", Some("}"))], None, false, false),
560        ),
561        raw("else2", "*", task(&[("copy", None)], None, false, false)),
562    ])
563});
564
565static TEXT: LazyLock<HashMap<String, Vec<Transition>>> = LazyLock::new(|| {
566    build_transitions(&[
567        raw("empty", "*", task(&[("output", None)], None, false, false)),
568        raw("{...}", "*", task(&[("text=", None)], None, false, false)),
569        raw(
570            "${(...)}$__$(...)$",
571            "*",
572            task(&[("tex-math", None)], None, false, false),
573        ),
574        raw(
575            "\\greek",
576            "*",
577            task(&[("output", None), ("rm", None)], None, false, false),
578        ),
579        raw(
580            "\\pu{(...)}",
581            "*",
582            task(&[("output", None), ("write", Some("{")), ("pu", None), ("write", Some("}"))], None, false, false),
583        ),
584        raw(
585            "\\,|\\x{}{}|\\x{}|\\x",
586            "*",
587            task(&[("output", None), ("copy", None)], None, false, false),
588        ),
589        raw("else", "*", task(&[("text=", None)], None, false, false)),
590    ])
591});
592
593static PQ: LazyLock<HashMap<String, Vec<Transition>>> = LazyLock::new(|| {
594    build_transitions(&[
595        raw("empty", "*", task(&[], None, false, false)),
596        raw(
597            "state of aggregation $",
598            "*",
599            task(&[("state of aggregation", None)], None, false, false),
600        ),
601        raw("i$", "0", task(&[], Some("!f"), true, false)),
602        raw("(KV letters),", "0", task(&[("rm", None)], Some("0"), false, false)),
603        raw("formula$", "0", task(&[], Some("f"), true, false)),
604        raw("1/2$", "0", task(&[("1/2", None)], None, false, false)),
605        raw("else", "0", task(&[], Some("!f"), true, false)),
606        raw(
607            "${(...)}$__$(...)$",
608            "*",
609            task(&[("tex-math", None)], None, false, false),
610        ),
611        raw("{(...)}", "*", task(&[("text", None)], None, false, false)),
612        raw("a-z", "f", task(&[("tex-math", None)], None, false, false)),
613        raw("letters", "*", task(&[("rm", None)], None, false, false)),
614        raw("-9.,9", "*", task(&[("9,9", None)], None, false, false)),
615        raw(
616            ",",
617            "*",
618            task(&[("insert+p1", Some("comma enumeration S"))], None, false, false),
619        ),
620        raw(
621            "\\color{(...)}{(...)}",
622            "*",
623            task(&[("color-output", None)], None, false, false),
624        ),
625        raw(
626            "\\color{(...)}",
627            "*",
628            task(&[("color0-output", None)], None, false, false),
629        ),
630        raw("\\ce{(...)}", "*", task(&[("ce", None)], None, false, false)),
631        raw(
632            "\\pu{(...)}",
633            "*",
634            task(&[("write", Some("{")), ("pu", None), ("write", Some("}"))], None, false, false),
635        ),
636        raw("\\,|\\x{}{}|\\x{}|\\x", "*", task(&[("copy", None)], None, false, false)),
637        raw("else2", "*", task(&[("copy", None)], None, false, false)),
638    ])
639});
640
641static BD: LazyLock<HashMap<String, Vec<Transition>>> = LazyLock::new(|| {
642    build_transitions(&[
643        raw("empty", "*", task(&[], None, false, false)),
644        raw("x$", "0", task(&[], Some("!f"), true, false)),
645        raw("formula$", "0", task(&[], Some("f"), true, false)),
646        raw("else", "0", task(&[], Some("!f"), true, false)),
647        raw("-9.,9 no missing 0", "*", task(&[("9,9", None)], None, false, false)),
648        raw(
649            ".",
650            "*",
651            task(&[("insert", Some("electron dot"))], None, false, false),
652        ),
653        raw("a-z", "f", task(&[("tex-math", None)], None, false, false)),
654        raw("x", "*", task(&[("insert", Some("KV x"))], None, false, false)),
655        raw("letters", "*", task(&[("rm", None)], None, false, false)),
656        raw("'", "*", task(&[("insert", Some("prime"))], None, false, false)),
657        raw(
658            "${(...)}$__$(...)$",
659            "*",
660            task(&[("tex-math", None)], None, false, false),
661        ),
662        raw("{(...)}", "*", task(&[("text", None)], None, false, false)),
663        raw(
664            "\\color{(...)}{(...)}",
665            "*",
666            task(&[("color-output", None)], None, false, false),
667        ),
668        raw(
669            "\\color{(...)}",
670            "*",
671            task(&[("color0-output", None)], None, false, false),
672        ),
673        raw("\\ce{(...)}", "*", task(&[("ce", None)], None, false, false)),
674        raw(
675            "\\pu{(...)}",
676            "*",
677            task(&[("write", Some("{")), ("pu", None), ("write", Some("}"))], None, false, false),
678        ),
679        raw("\\,|\\x{}{}|\\x{}|\\x", "*", task(&[("copy", None)], None, false, false)),
680        raw("else2", "*", task(&[("copy", None)], None, false, false)),
681    ])
682});
683
684static OXIDATION: LazyLock<HashMap<String, Vec<Transition>>> = LazyLock::new(|| {
685    build_transitions(&[
686        raw("empty", "*", task(&[("roman-numeral", None)], None, false, false)),
687        raw(
688            "pm-operator",
689            "*",
690            task(&[("o=+p1", Some("\\pm"))], None, false, false),
691        ),
692        raw("else", "*", task(&[("o=", None)], None, false, false)),
693    ])
694});
695
696static TEX_MATH: LazyLock<HashMap<String, Vec<Transition>>> = LazyLock::new(|| {
697    build_transitions(&[
698        raw("empty", "*", task(&[("output", None)], None, false, false)),
699        raw(
700            "\\ce{(...)}",
701            "*",
702            task(&[("output", None), ("ce", None)], None, false, false),
703        ),
704        raw(
705            "\\pu{(...)}",
706            "*",
707            task(&[("output", None), ("write", Some("{")), ("pu", None), ("write", Some("}"))], None, false, false),
708        ),
709        raw("{...}|\\,|\\x{}{}|\\x{}|\\x", "*", task(&[("o=", None)], None, false, false)),
710        raw("else", "*", task(&[("o=", None)], None, false, false)),
711    ])
712});
713
714static TEX_MATH_TIGHT: LazyLock<HashMap<String, Vec<Transition>>> = LazyLock::new(|| {
715    build_transitions(&[
716        raw("empty", "*", task(&[("output", None)], None, false, false)),
717        raw(
718            "\\ce{(...)}",
719            "*",
720            task(&[("output", None), ("ce", None)], None, false, false),
721        ),
722        raw(
723            "\\pu{(...)}",
724            "*",
725            task(&[("output", None), ("write", Some("{")), ("pu", None), ("write", Some("}"))], None, false, false),
726        ),
727        raw("{...}|\\,|\\x{}{}|\\x{}|\\x", "*", task(&[("o=", None)], None, false, false)),
728        raw("-|+", "*", task(&[("tight operator", None)], None, false, false)),
729        raw("else", "*", task(&[("o=", None)], None, false, false)),
730    ])
731});
732
733static NUM99: LazyLock<HashMap<String, Vec<Transition>>> = LazyLock::new(|| {
734    build_transitions(&[
735        raw("empty", "*", task(&[], None, false, false)),
736        raw(",", "*", task(&[("comma", None)], None, false, false)),
737        raw("else", "*", task(&[("copy", None)], None, false, false)),
738    ])
739});
740
741// =========================================================================
742// pu / pu-2 / pu-9,9 状态机
743// =========================================================================
744
745static PU: LazyLock<HashMap<String, Vec<Transition>>> = LazyLock::new(|| {
746    build_transitions(&[
747        raw("empty", "*", task(&[("output", None)], None, false, false)),
748        raw(
749            "space$",
750            "*",
751            task(&[("output", None), ("space", None)], None, false, false),
752        ),
753        raw("{[(|)]}", "0|a", task(&[("copy", None)], None, false, false)),
754        raw(
755            "(-)(9)^(-9)",
756            "0",
757            task(&[("number^", None)], Some("a"), false, false),
758        ),
759        raw(
760            "(-)(9.,9)(e)(99)",
761            "0",
762            task(&[("enumber", None)], Some("a"), false, false),
763        ),
764        raw("space", "0|a", task(&[], None, false, false)),
765        raw(
766            "pm-operator",
767            "0|a",
768            task(&[("operator", Some("\\pm"))], Some("0"), false, false),
769        ),
770        raw("operator", "0|a", task(&[("copy", None)], Some("0"), false, false)),
771        raw("//", "d", task(&[("o=", None)], Some("/"), false, false)),
772        raw("/", "d", task(&[("o=", None)], Some("/"), false, false)),
773        raw(
774            "{...}|else",
775            "0|d",
776            task(&[("d=", None)], Some("d"), false, false),
777        ),
778        raw(
779            "{...}|else",
780            "a",
781            task(&[("space", None), ("d=", None)], Some("d"), false, false),
782        ),
783        raw(
784            "{...}|else",
785            "/|q",
786            task(&[("q=", None)], Some("q"), false, false),
787        ),
788    ])
789});
790
791static PU2: LazyLock<HashMap<String, Vec<Transition>>> = LazyLock::new(|| {
792    build_transitions(&[
793        raw("empty", "*", task(&[("output", None)], None, false, false)),
794        raw(
795            "*",
796            "*",
797            task(&[("output", None), ("cdot", None)], Some("0"), false, false),
798        ),
799        raw("\\x", "*", task(&[("rm=", None)], None, false, false)),
800        raw(
801            "space",
802            "*",
803            task(&[("output", None), ("space", None)], Some("0"), false, false),
804        ),
805        raw(
806            "^{(...)}|^(-1)",
807            "1",
808            task(&[("^(-1)", None)], None, false, false),
809        ),
810        raw("-9.,9", "0", task(&[("rm=", None)], Some("0"), false, false)),
811        raw(
812            "-9.,9",
813            "1",
814            task(&[("^(-1)", None)], Some("0"), false, false),
815        ),
816        raw("{...}|else", "*", task(&[("rm=", None)], Some("1"), false, false)),
817    ])
818});
819
820static PU99: LazyLock<HashMap<String, Vec<Transition>>> = LazyLock::new(|| {
821    build_transitions(&[
822        raw("empty", "0", task(&[("output-0", None)], None, false, false)),
823        raw("empty", "o", task(&[("output-o", None)], None, false, false)),
824        raw(
825            ",",
826            "0",
827            task(&[("output-0", None), ("comma", None)], Some("o"), false, false),
828        ),
829        raw(
830            ".",
831            "0",
832            task(&[("output-0", None), ("copy", None)], Some("o"), false, false),
833        ),
834        raw("else", "*", task(&[("text=", None)], None, false, false)),
835    ])
836});
837
838// =========================================================================
839// 机器局部动作分发
840// =========================================================================
841
842fn machine_action(
843    machine: &str,
844    buf: &mut Buffer,
845    m: &MVal,
846    opt: &Option<String>,
847    type_: &str,
848) -> Option<Out> {
849    match machine {
850        "ce" => ce_action(buf, m, opt, type_),
851        "text" => text_action(buf, type_),
852        "pq" => pq_action(buf, m, type_),
853        "bd" => bd_action(buf, m, type_),
854        "oxidation" => oxidation_action(buf, type_),
855        "tex-math" | "tex-math tight" => texmath_action(machine, buf, m, type_),
856        "9,9" => Some(num99_action(type_)),
857        "pu" => pu_action(buf, m, opt, type_),
858        "pu-2" => pu2_action(buf, m, type_),
859        "pu-9,9" => pu99_action(buf, type_),
860        _ => None,
861    }
862}
863
864fn gof(opt_str: &Option<String>, m: &str) -> Field {
865    Field::Nodes(go(opt_str.as_deref().unwrap_or(""), m))
866}
867
868fn arrow_sub(buf_field: &Option<String>, rdt: &Option<String>, ce_m: &str) -> Field {
869    match rdt.as_deref() {
870        Some("M") => Field::Nodes(go(buf_field.as_deref().unwrap_or(""), "tex-math")),
871        Some("T") => Field::Nodes(vec![Parsed::N(NodeData {
872            type_: "text".into(),
873            p1: Some(Field::Str(buf_field.clone().unwrap_or_default())),
874            ..Default::default()
875        })]),
876        _ => Field::Nodes(go(buf_field.as_deref().unwrap_or(""), ce_m)),
877    }
878}
879
880fn ce_action(buf: &mut Buffer, m: &MVal, opt: &Option<String>, type_: &str) -> Option<Out> {
881    let out = match type_ {
882        "o after d" => {
883            let mut ret: Vec<Parsed> = Vec::new();
884            let d_is_int = buf
885                .d
886                .as_ref()
887                .map(|d| re!("^[1-9][0-9]*$").is_match(d))
888                .unwrap_or(false);
889            if d_is_int {
890                let tmp = buf.d.take();
891                concat(&mut ret, ce_action(buf, m, &None, "output").unwrap_or(Out::None));
892                ret.push(Parsed::N(NodeData {
893                    type_: "tinySkip".into(),
894                    ..Default::default()
895                }));
896                buf.b = tmp;
897            } else {
898                concat(&mut ret, ce_action(buf, m, &None, "output").unwrap_or(Out::None));
899            }
900            append_field(&mut buf.o, m);
901            Out::Many(ret)
902        }
903        "d= kv" => {
904            buf.d = Some(mval_str(m));
905            buf.d_type = Some("kv".into());
906            Out::None
907        }
908        "charge or bond" => {
909            if buf.begins_with_bond {
910                let mut ret: Vec<Parsed> = Vec::new();
911                concat(&mut ret, ce_action(buf, m, &None, "output").unwrap_or(Out::None));
912                ret.push(Parsed::N(NodeData {
913                    type_: "bond".into(),
914                    kind_: Some("-".into()),
915                    ..Default::default()
916                }));
917                Out::Many(ret)
918            } else {
919                buf.d = Some(mval_str(m));
920                Out::None
921            }
922        }
923        "- after o/d" => {
924            let is_after_d = opt.as_deref() == Some("true");
925            let o_str = buf.o.clone().unwrap_or_default();
926            let c1 = match_pattern("orbital", &o_str);
927            let c2 = match_pattern("one lowercase greek letter $", &o_str);
928            let c3 = match_pattern("one lowercase latin letter $", &o_str);
929            let c4 = match_pattern("$one lowercase latin letter$ $", &o_str);
930            let m_str = mval_str(m);
931            let hyphen_follows = m_str == "-"
932                && ((c1.as_ref().map(|x| x.remainder.is_empty()).unwrap_or(false))
933                    || c2.is_some()
934                    || c3.is_some()
935                    || c4.is_some());
936            if hyphen_follows
937                && buf.a.is_none()
938                && buf.b.is_none()
939                && buf.p.is_none()
940                && buf.d.is_none()
941                && buf.q.is_none()
942                && c1.is_none()
943                && c3.is_some()
944            {
945                let ov = buf.o.take().unwrap_or_default();
946                buf.o = Some(format!("${}$", ov));
947            }
948            let mut ret: Vec<Parsed> = Vec::new();
949            if hyphen_follows {
950                concat(&mut ret, ce_action(buf, m, &None, "output").unwrap_or(Out::None));
951                ret.push(Parsed::N(NodeData {
952                    type_: "hyphen".into(),
953                    ..Default::default()
954                }));
955            } else {
956                let d_str = buf.d.clone().unwrap_or_default();
957                let c1d = match_pattern("digits", &d_str);
958                if is_after_d && c1d.as_ref().map(|x| x.remainder.is_empty()).unwrap_or(false) {
959                    append_field(&mut buf.d, m);
960                    concat(&mut ret, ce_action(buf, m, &None, "output").unwrap_or(Out::None));
961                } else {
962                    concat(&mut ret, ce_action(buf, m, &None, "output").unwrap_or(Out::None));
963                    ret.push(Parsed::N(NodeData {
964                        type_: "bond".into(),
965                        kind_: Some("-".into()),
966                        ..Default::default()
967                    }));
968                }
969            }
970            Out::Many(ret)
971        }
972        "a to o" => {
973            buf.o = buf.a.take();
974            Out::None
975        }
976        "sb=true" => {
977            buf.sb = true;
978            Out::None
979        }
980        "sb=false" => {
981            buf.sb = false;
982            Out::None
983        }
984        "beginsWithBond=true" => {
985            buf.begins_with_bond = true;
986            Out::None
987        }
988        "beginsWithBond=false" => {
989            buf.begins_with_bond = false;
990            Out::None
991        }
992        "parenthesisLevel++" => {
993            buf.parenthesis_level += 1;
994            Out::None
995        }
996        "parenthesisLevel--" => {
997            buf.parenthesis_level -= 1;
998            Out::None
999        }
1000        "state of aggregation" => Out::One(Parsed::N(NodeData {
1001            type_: "state of aggregation".into(),
1002            p1: Some(Field::Nodes(go(&mval_str(m), "o"))),
1003            ..Default::default()
1004        })),
1005        "comma" => {
1006            let raw = mval_str(m);
1007            let a = raw.trim_end();
1008            let with_space = a != raw;
1009            let kind = if with_space && buf.parenthesis_level == 0 {
1010                "comma enumeration L"
1011            } else {
1012                "comma enumeration M"
1013            };
1014            Out::One(Parsed::N(NodeData {
1015                type_: kind.into(),
1016                p1: Some(Field::Str(a.to_string())),
1017                ..Default::default()
1018            }))
1019        }
1020        "output" => {
1021            let entity_follows = match opt.as_deref() {
1022                Some("1") => 1,
1023                Some("2") => 2,
1024                _ => 0,
1025            };
1026            let ret: Vec<Parsed> = if buf.r.is_none() {
1027                let mut ret = Vec::new();
1028                let empty = buf.a.is_none()
1029                    && buf.b.is_none()
1030                    && buf.p.is_none()
1031                    && buf.o.is_none()
1032                    && buf.q.is_none()
1033                    && buf.d.is_none()
1034                    && entity_follows == 0;
1035                if !empty {
1036                    if buf.sb {
1037                        ret.push(Parsed::N(NodeData {
1038                            type_: "entitySkip".into(),
1039                            ..Default::default()
1040                        }));
1041                    }
1042                    let o_none = buf.o.is_none();
1043                    let q_none = buf.q.is_none();
1044                    let d_none = buf.d.is_none();
1045                    let b_none = buf.b.is_none();
1046                    let p_none = buf.p.is_none();
1047                    if o_none && q_none && d_none && b_none && p_none && entity_follows != 2 {
1048                        buf.o = buf.a.take();
1049                    } else if o_none && q_none && d_none && (buf.b.is_some() || buf.p.is_some()) {
1050                        buf.o = buf.a.take();
1051                        buf.d = buf.b.take();
1052                        buf.q = buf.p.take();
1053                    } else if buf.o.is_some()
1054                        && buf.d_type.as_deref() == Some("kv")
1055                        && match_pattern("d-oxidation$", &buf.d.clone().unwrap_or_default()).is_some()
1056                    {
1057                        buf.d_type = Some("oxidation".into());
1058                    } else if buf.o.is_some() && buf.d_type.as_deref() == Some("kv") && buf.q.is_none() {
1059                        buf.d_type = None;
1060                    }
1061                    let d_machine = if buf.d_type.as_deref() == Some("oxidation") {
1062                        "oxidation"
1063                    } else {
1064                        "bd"
1065                    };
1066                    ret.push(Parsed::N(NodeData {
1067                        type_: "chemfive".into(),
1068                        a: Some(gof(&buf.a.clone(), "a")),
1069                        b: Some(gof(&buf.b.clone(), "bd")),
1070                        p: Some(gof(&buf.p.clone(), "pq")),
1071                        o: Some(gof(&buf.o.clone(), "o")),
1072                        q: Some(gof(&buf.q.clone(), "pq")),
1073                        d: Some(gof(&buf.d.clone(), d_machine)),
1074                        d_type: buf.d_type.clone(),
1075                        ..Default::default()
1076                    }));
1077                }
1078                ret
1079            } else {
1080                let rd = arrow_sub(&buf.rd.clone(), &buf.rdt.clone(), "ce");
1081                let rq = arrow_sub(&buf.rq.clone(), &buf.rqt.clone(), "ce");
1082                vec![Parsed::N(NodeData {
1083                    type_: "arrow".into(),
1084                    r: buf.r.clone(),
1085                    rd: Some(rd),
1086                    rq: Some(rq),
1087                    ..Default::default()
1088                })]
1089            };
1090            buf.clear(true);
1091            Out::Many(ret)
1092        }
1093        "oxidation-output" => {
1094            let mut ret: Vec<Parsed> = vec![Parsed::S("{".into())];
1095            ret.extend(go(&mval_str(m), "oxidation"));
1096            ret.push(Parsed::S("}".into()));
1097            Out::Many(ret)
1098        }
1099        "frac-output" | "overset-output" | "underset-output" | "underbrace-output" => {
1100            let (node_type, p1m, p2m) = match type_ {
1101                "frac-output" => ("frac-ce", 0usize, 1usize),
1102                "overset-output" => ("overset", 0, 1),
1103                "underset-output" => ("underset", 0, 1),
1104                _ => ("underbrace", 0, 1),
1105            };
1106            let (g1, g2) = mval_pair(m, p1m, p2m);
1107            Out::One(Parsed::N(NodeData {
1108                type_: node_type.into(),
1109                p1: Some(Field::Nodes(go(&g1, "ce"))),
1110                p2: Some(Field::Nodes(go(&g2, "ce"))),
1111                ..Default::default()
1112            }))
1113        }
1114        "color-output" => {
1115            let (g1, g2) = mval_pair(m, 0, 1);
1116            Out::One(Parsed::N(NodeData {
1117                type_: "color".into(),
1118                color1: Some(g1),
1119                color2: Some(Field::Nodes(go(&g2, "ce"))),
1120                ..Default::default()
1121            }))
1122        }
1123        "r=" => {
1124            buf.r = Some(mval_str(m));
1125            Out::None
1126        }
1127        "rdt=" => {
1128            buf.rdt = Some(mval_str(m));
1129            Out::None
1130        }
1131        "rd=" => {
1132            buf.rd = Some(mval_str(m));
1133            Out::None
1134        }
1135        "rqt=" => {
1136            buf.rqt = Some(mval_str(m));
1137            Out::None
1138        }
1139        "rq=" => {
1140            buf.rq = Some(mval_str(m));
1141            Out::None
1142        }
1143        "operator" => Out::One(Parsed::N(NodeData {
1144            type_: "operator".into(),
1145            kind_: Some(opt.clone().unwrap_or_else(|| mval_str(m))),
1146            ..Default::default()
1147        })),
1148        _ => return None,
1149    };
1150    Some(out)
1151}
1152
1153fn mval_pair(m: &MVal, i: usize, j: usize) -> (String, String) {
1154    match m {
1155        MVal::V(v) => (
1156            v.get(i).cloned().unwrap_or_default(),
1157            v.get(j).cloned().unwrap_or_default(),
1158        ),
1159        MVal::S(s) => (s.clone(), String::new()),
1160    }
1161}
1162
1163fn text_action(buf: &mut Buffer, type_: &str) -> Option<Out> {
1164    if type_ == "output" {
1165        let ret = buf.text_.take().map(|t| {
1166            Out::One(Parsed::N(NodeData {
1167                type_: "text".into(),
1168                p1: Some(Field::Str(t)),
1169                ..Default::default()
1170            }))
1171        });
1172        buf.clear(false);
1173        Some(ret.unwrap_or(Out::None))
1174    } else {
1175        None
1176    }
1177}
1178
1179fn pq_action(_buf: &mut Buffer, m: &MVal, type_: &str) -> Option<Out> {
1180    match type_ {
1181        "state of aggregation" => Some(Out::One(Parsed::N(NodeData {
1182            type_: "state of aggregation subscript".into(),
1183            p1: Some(Field::Nodes(go(&mval_str(m), "o"))),
1184            ..Default::default()
1185        }))),
1186        "color-output" => {
1187            let (g1, g2) = mval_pair(m, 0, 1);
1188            Some(Out::One(Parsed::N(NodeData {
1189                type_: "color".into(),
1190                color1: Some(g1),
1191                color2: Some(Field::Nodes(go(&g2, "pq"))),
1192                ..Default::default()
1193            })))
1194        }
1195        _ => None,
1196    }
1197}
1198
1199fn bd_action(_buf: &mut Buffer, m: &MVal, type_: &str) -> Option<Out> {
1200    if type_ == "color-output" {
1201        let (g1, g2) = mval_pair(m, 0, 1);
1202        Some(Out::One(Parsed::N(NodeData {
1203            type_: "color".into(),
1204            color1: Some(g1),
1205            color2: Some(Field::Nodes(go(&g2, "bd"))),
1206            ..Default::default()
1207        })))
1208    } else {
1209        None
1210    }
1211}
1212
1213fn oxidation_action(buf: &mut Buffer, type_: &str) -> Option<Out> {
1214    if type_ == "roman-numeral" {
1215        Some(Out::One(Parsed::N(NodeData {
1216            type_: "roman numeral".into(),
1217            p1: Some(Field::Str(buf.o.clone().unwrap_or_default())),
1218            ..Default::default()
1219        })))
1220    } else {
1221        None
1222    }
1223}
1224
1225fn texmath_action(machine: &str, buf: &mut Buffer, m: &MVal, type_: &str) -> Option<Out> {
1226    match type_ {
1227        "tight operator" => {
1228            append_str(&mut buf.o, &format!("{{{}}}", mval_str(m)));
1229            Some(Out::None)
1230        }
1231        "output" => {
1232            let ret = buf.o.take().map(|o| {
1233                Out::One(Parsed::N(NodeData {
1234                    type_: "tex-math".into(),
1235                    p1: Some(Field::Str(o)),
1236                    ..Default::default()
1237                }))
1238            });
1239            buf.clear(false);
1240            let _ = machine;
1241            Some(ret.unwrap_or(Out::None))
1242        }
1243        _ => None,
1244    }
1245}
1246
1247fn num99_action(type_: &str) -> Out {
1248    if type_ == "comma" {
1249        Out::One(Parsed::N(NodeData {
1250            type_: "commaDecimal".into(),
1251            ..Default::default()
1252        }))
1253    } else {
1254        Out::None
1255    }
1256}
1257
1258fn pu_action(buf: &mut Buffer, m: &MVal, opt: &Option<String>, type_: &str) -> Option<Out> {
1259    let out = match type_ {
1260        "enumber" => {
1261            let v = match m {
1262                MVal::V(v) => v.clone(),
1263                MVal::S(s) => vec![s.clone()],
1264            };
1265            let mut ret: Vec<Parsed> = Vec::new();
1266            let g0 = v.first().map(|s| s.as_str()).unwrap_or("");
1267            if g0 == "+-" || g0 == "+/-" {
1268                ret.push(Parsed::S("\\pm ".into()));
1269            } else if !g0.is_empty() {
1270                ret.push(Parsed::S(g0.to_string()));
1271            }
1272            let g1 = v.get(1).cloned().unwrap_or_default();
1273            if !g1.is_empty() {
1274                ret.extend(go(&g1, "pu-9,9"));
1275                let g2 = v.get(2).cloned().unwrap_or_default();
1276                if !g2.is_empty() {
1277                    if g2.contains(',') || g2.contains('.') {
1278                        ret.extend(go(&g2, "pu-9,9"));
1279                    } else {
1280                        ret.push(Parsed::S(g2));
1281                    }
1282                }
1283                let g3 = v.get(3).map(|s| s.as_str()).unwrap_or("");
1284                let g4 = v.get(4).map(|s| s.as_str()).unwrap_or("");
1285                if !g3.is_empty() || !g4.is_empty() {
1286                    if g3 == "e" || g4 == "*" {
1287                        ret.push(Parsed::N(NodeData {
1288                            type_: "cdot".into(),
1289                            ..Default::default()
1290                        }));
1291                    } else {
1292                        ret.push(Parsed::N(NodeData {
1293                            type_: "times".into(),
1294                            ..Default::default()
1295                        }));
1296                    }
1297                }
1298            }
1299            let g5 = v.get(5).cloned().unwrap_or_default();
1300            if !g5.is_empty() {
1301                ret.push(Parsed::S(format!("10^{{{}}}", g5)));
1302            }
1303            Out::Many(ret)
1304        }
1305        "number^" => {
1306            let v = match m {
1307                MVal::V(v) => v.clone(),
1308                MVal::S(s) => vec![s.clone()],
1309            };
1310            let mut ret: Vec<Parsed> = Vec::new();
1311            let g0 = v.first().map(|s| s.as_str()).unwrap_or("");
1312            if g0 == "+-" || g0 == "+/-" {
1313                ret.push(Parsed::S("\\pm ".into()));
1314            } else if !g0.is_empty() {
1315                ret.push(Parsed::S(g0.to_string()));
1316            }
1317            let g1 = v.get(1).cloned().unwrap_or_default();
1318            ret.extend(go(&g1, "pu-9,9"));
1319            let g2 = v.get(2).cloned().unwrap_or_default();
1320            ret.push(Parsed::S(format!("^{{{}}}", g2)));
1321            Out::Many(ret)
1322        }
1323        "operator" => Out::One(Parsed::N(NodeData {
1324            type_: "operator".into(),
1325            kind_: Some(opt.clone().unwrap_or_else(|| mval_str(m))),
1326            ..Default::default()
1327        })),
1328        "space" => Out::One(Parsed::N(NodeData {
1329            type_: "pu-space-1".into(),
1330            ..Default::default()
1331        })),
1332        "output" => {
1333            // {(...)} 解包
1334            if let Some(d) = &buf.d {
1335                if let Some(md) = match_pattern("{(...)}", d) {
1336                    if md.remainder.is_empty() {
1337                        buf.d = Some(mval_str(&md.m));
1338                    }
1339                }
1340            }
1341            if let Some(q) = &buf.q {
1342                if let Some(mq) = match_pattern("{(...)}", q) {
1343                    if mq.remainder.is_empty() {
1344                        buf.q = Some(mval_str(&mq.m));
1345                    }
1346                }
1347            }
1348            fn rep_c_f(s: &str) -> String {
1349                s.replace("\u{00B0}C", "{}^{\\circ}C")
1350                    .replace("^oC", "{}^{\\circ}C")
1351                    .replace("^{o}C", "{}^{\\circ}C")
1352                    .replace("\u{00B0}F", "{}^{\\circ}F")
1353                    .replace("^oF", "{}^{\\circ}F")
1354                    .replace("^{o}F", "{}^{\\circ}F")
1355            }
1356            if let Some(d) = buf.d.as_mut() {
1357                *d = rep_c_f(d);
1358            }
1359            let ret: Vec<Parsed> = if let Some(q) = buf.q.as_mut() {
1360                *q = rep_c_f(q);
1361                let bd = go(&buf.d.clone().unwrap_or_default(), "pu");
1362                let bq = go(&buf.q.clone().unwrap_or_default(), "pu");
1363                if buf.o.as_deref() == Some("//") {
1364                    vec![Parsed::N(NodeData {
1365                        type_: "pu-frac".into(),
1366                        p1: Some(Field::Nodes(bd)),
1367                        p2: Some(Field::Nodes(bq)),
1368                        ..Default::default()
1369                    })]
1370                } else {
1371                    let mut ret = bd;
1372                    if ret.len() > 1 || bq.len() > 1 {
1373                        ret.push(Parsed::N(NodeData {
1374                            type_: " / ".into(),
1375                            ..Default::default()
1376                        }));
1377                    } else {
1378                        ret.push(Parsed::N(NodeData {
1379                            type_: "/".into(),
1380                            ..Default::default()
1381                        }));
1382                    }
1383                    ret.extend(bq);
1384                    ret
1385                }
1386            } else {
1387                go(&buf.d.clone().unwrap_or_default(), "pu-2")
1388            };
1389            buf.clear(false);
1390            Out::Many(ret)
1391        }
1392        _ => return None,
1393    };
1394    Some(out)
1395}
1396
1397fn pu2_action(buf: &mut Buffer, m: &MVal, type_: &str) -> Option<Out> {
1398    let out = match type_ {
1399        "cdot" => Out::One(Parsed::N(NodeData {
1400            type_: "tight cdot".into(),
1401            ..Default::default()
1402        })),
1403        "^(-1)" => {
1404            if let Some(rm) = buf.rm.as_mut() {
1405                rm.push_str(&format!("^{{{}}}", mval_str(m)));
1406            }
1407            Out::None
1408        }
1409        "space" => Out::One(Parsed::N(NodeData {
1410            type_: "pu-space-2".into(),
1411            ..Default::default()
1412        })),
1413        "output" => {
1414            let ret: Vec<Parsed> = if let Some(rm) = &buf.rm {
1415                if let Some(mrm) = match_pattern("{(...)}", rm) {
1416                    if mrm.remainder.is_empty() {
1417                        go(&mval_str(&mrm.m), "pu")
1418                    } else {
1419                        vec![Parsed::N(NodeData {
1420                            type_: "rm".into(),
1421                            p1: Some(Field::Str(rm.clone())),
1422                            ..Default::default()
1423                        })]
1424                    }
1425                } else {
1426                    vec![Parsed::N(NodeData {
1427                        type_: "rm".into(),
1428                        p1: Some(Field::Str(rm.clone())),
1429                        ..Default::default()
1430                    })]
1431                }
1432            } else {
1433                Vec::new()
1434            };
1435            buf.clear(false);
1436            Out::Many(ret)
1437        }
1438        _ => return None,
1439    };
1440    Some(out)
1441}
1442
1443fn pu99_action(buf: &mut Buffer, type_: &str) -> Option<Out> {
1444    let out = match type_ {
1445        "comma" => Out::One(Parsed::N(NodeData {
1446            type_: "commaDecimal".into(),
1447            ..Default::default()
1448        })),
1449        "output-0" => {
1450            let text = buf.text_.clone().unwrap_or_default();
1451            let ret = thousands_split(&text, true);
1452            buf.clear(false);
1453            Out::Many(ret)
1454        }
1455        "output-o" => {
1456            let text = buf.text_.clone().unwrap_or_default();
1457            let ret = thousands_split(&text, false);
1458            buf.clear(false);
1459            Out::Many(ret)
1460        }
1461        _ => return None,
1462    };
1463    Some(out)
1464}
1465
1466/// 千分位拆分(pu-9,9 的 output-0 / output-o)。
1467fn thousands_split(text: &str, reverse: bool) -> Vec<Parsed> {
1468    let mut ret: Vec<Parsed> = Vec::new();
1469    if text.len() > 4 {
1470        if reverse {
1471            let mut a = text.len() % 3;
1472            if a == 0 {
1473                a = 3;
1474            }
1475            let mut i = text.len().saturating_sub(3);
1476            while i > 0 {
1477                ret.push(Parsed::S(text[i..i + 3].to_string()));
1478                ret.push(Parsed::N(NodeData {
1479                    type_: "1000 separator".into(),
1480                    ..Default::default()
1481                }));
1482                i = i.saturating_sub(3);
1483            }
1484            ret.push(Parsed::S(text[..a].to_string()));
1485            ret.reverse();
1486        } else {
1487            let a = text.len() - 3;
1488            let mut i = 0;
1489            while i < a {
1490                ret.push(Parsed::S(text[i..i + 3].to_string()));
1491                ret.push(Parsed::N(NodeData {
1492                    type_: "1000 separator".into(),
1493                    ..Default::default()
1494                }));
1495                i += 3;
1496            }
1497            ret.push(Parsed::S(text[i..].to_string()));
1498        }
1499    } else {
1500        ret.push(Parsed::S(text.to_string()));
1501    }
1502    ret
1503}