// mhchem 状态机转移表 + 机器局部动作(由 mhchem.rs `include!`)。 // 数据机械移植自 mhchemParser 4.2.2 的 `stateMachines`。 // (被 include 进 mhchem.rs 中部,故不能用 `//!` 内部文档注释。) // ── Task 构造器 ────────────────────────────────────────────────────────── fn task(acts: &[(&str, Option<&str>)], next: Option<&str>, revisit: bool, cont: bool) -> Task { Task { actions: acts .iter() .map(|(t, o)| ActionRef { type_: (*t).to_string(), option: o.map(|s| s.to_string()), }) .collect(), next_state: next.map(|s| s.to_string()), revisit, to_continue: cont, } } fn raw(patterns: &'static str, states: &'static str, task: Task) -> RawEntry { RawEntry { patterns, states, task, } } // ── 转移表取用 ──────────────────────────────────────────────────────────── fn transitions_for(machine: &str) -> &'static HashMap> { match machine { "tex" => &TEX, "ce" => &CE, "a" => &A, "o" => &O, "text" => &TEXT, "pq" => &PQ, "bd" => &BD, "oxidation" => &OXIDATION, "tex-math" => &TEX_MATH, "tex-math tight" => &TEX_MATH_TIGHT, "9,9" => &NUM99, "pu" => &PU, "pu-2" => &PU2, "pu-9,9" => &PU99, _ => &CE, } } // ========================================================================= // tex 状态机 // ========================================================================= static TEX: LazyLock>> = LazyLock::new(|| { build_transitions(&[ raw("empty", "*", task(&[("copy", None)], None, false, false)), raw( "\\ce{(...)}", "0", task( &[ ("write", Some("{")), ("ce", None), ("write", Some("}")), ], None, false, false, ), ), raw( "\\pu{(...)}", "0", task( &[ ("write", Some("{")), ("pu", None), ("write", Some("}")), ], None, false, false, ), ), raw("else", "0", task(&[("copy", None)], None, false, false)), ]) }); // ========================================================================= // ce 状态机(主解析器) // ========================================================================= static CE: LazyLock>> = LazyLock::new(|| { build_transitions(&[ raw("empty", "*", task(&[("output", None)], None, false, false)), raw( "else", "0|1|2", task(&[("beginsWithBond=false", None)], None, true, true), ), raw( "oxidation$", "0", task(&[("oxidation-output", None)], None, false, false), ), raw( "CMT", "r", task(&[("rdt=", None)], Some("rt"), false, false), ), raw( "CMT", "rd", task(&[("rqt=", None)], Some("rdt"), false, false), ), raw( "arrowUpDown", "0|1|2|as", task(&[("sb=false", None), ("output", None), ("operator", None)], Some("1"), false, false), ), raw( "uprightEntities", "0|1|2", task(&[("o=", None), ("output", None)], Some("1"), false, false), ), raw("orbital", "0|1|2|3", task(&[("o=", None)], Some("o"), false, false)), raw("->", "0|1|2|3", task(&[("r=", None)], Some("r"), false, false)), raw( "->", "a|as", task(&[("output", None), ("r=", None)], Some("r"), false, false), ), raw( "->", "*", task(&[("output", None), ("r=", None)], Some("r"), false, false), ), raw("+", "o", task(&[("d= kv", None)], Some("d"), false, false)), raw("+", "d|D", task(&[("d=", None)], Some("d"), false, false)), raw("+", "q", task(&[("d=", None)], Some("qd"), false, false)), raw("+", "qd|qD", task(&[("d=", None)], Some("qd"), false, false)), raw( "+", "dq", task(&[("output", None), ("d=", None)], Some("d"), false, false), ), raw( "+", "3", task(&[("sb=false", None), ("output", None), ("operator", None)], Some("0"), false, false), ), raw("amount", "0|2", task(&[("a=", None)], Some("a"), false, false)), raw( "pm-operator", "0|1|2|a|as", task(&[("sb=false", None), ("output", None), ("operator", Some("\\pm"))], Some("0"), false, false), ), raw( "operator", "0|1|2|a|as", task(&[("sb=false", None), ("output", None), ("operator", None)], Some("0"), false, false), ), raw( "-$", "o|q", task(&[("charge or bond", None), ("output", None)], Some("qd"), false, false), ), raw("-$", "d", task(&[("d=", None)], Some("d"), false, false)), raw( "-$", "D", task(&[("output", None), ("bond", Some("-"))], Some("3"), false, false), ), raw("-$", "q", task(&[("d=", None)], Some("qd"), false, false)), raw("-$", "qd", task(&[("d=", None)], Some("qd"), false, false)), raw( "-$", "qD|dq", task(&[("output", None), ("bond", Some("-"))], Some("3"), false, false), ), raw( "-9", "3|o", task(&[("output", None), ("insert", Some("hyphen"))], Some("3"), false, false), ), raw( "- orbital overlap", "o", task(&[("output", None), ("insert", Some("hyphen"))], Some("2"), false, false), ), raw( "- orbital overlap", "d", task(&[("output", None), ("insert", Some("hyphen"))], Some("2"), false, false), ), raw( "-", "0|1|2", task(&[("output", Some("1")), ("beginsWithBond=true", None), ("bond", Some("-"))], Some("3"), false, false), ), raw("-", "3", task(&[("bond", Some("-"))], None, false, false)), raw( "-", "a", task(&[("output", None), ("insert", Some("hyphen"))], Some("2"), false, false), ), raw( "-", "as", task(&[("output", Some("2")), ("bond", Some("-"))], Some("3"), false, false), ), raw("-", "b", task(&[("b=", None)], None, false, false)), raw( "-", "o", task(&[("- after o/d", Some("false"))], Some("2"), false, false), ), raw( "-", "q", task(&[("- after o/d", Some("false"))], Some("2"), false, false), ), raw( "-", "d|qd|dq", task(&[("- after o/d", Some("true"))], Some("2"), false, false), ), raw( "-", "D|qD|p", task(&[("output", None), ("bond", Some("-"))], Some("3"), false, false), ), raw("amount2", "1|3", task(&[("a=", None)], Some("a"), false, false)), raw( "letters", "0|1|2|3|a|as|b|p|bp|o", task(&[("o=", None)], Some("o"), false, false), ), raw( "letters", "q|dq", task(&[("output", None), ("o=", None)], Some("o"), false, false), ), raw( "letters", "d|D|qd|qD", task(&[("o after d", None)], Some("o"), false, false), ), raw("digits", "o", task(&[("q=", None)], Some("q"), false, false)), raw("digits", "d|D", task(&[("q=", None)], Some("dq"), false, false)), raw( "digits", "q", task(&[("output", None), ("o=", None)], Some("o"), false, false), ), raw("digits", "a", task(&[("o=", None)], Some("o"), false, false)), raw("space A", "b|p|bp", task(&[], None, false, false)), raw("space", "a", task(&[], Some("as"), false, false)), raw("space", "0", task(&[("sb=false", None)], None, false, false)), raw("space", "1|2", task(&[("sb=true", None)], None, false, false)), raw( "space", "r|rt|rd|rdt|rdq", task(&[("output", None)], Some("0"), false, false), ), raw( "space", "*", task(&[("output", None), ("sb=true", None)], Some("1"), false, false), ), raw( "1st-level escape", "1|2", task(&[("output", None), ("insert+p1", Some("1st-level escape"))], None, false, false), ), raw( "1st-level escape", "*", task(&[("output", None), ("insert+p1", Some("1st-level escape"))], Some("0"), false, false), ), raw("[(...)]", "r|rt", task(&[("rd=", None)], Some("rd"), false, false)), raw( "[(...)]", "rd|rdt", task(&[("rq=", None)], Some("rdq"), false, false), ), raw( "...", "o|d|D|dq|qd|qD", task(&[("output", None), ("bond", Some("..."))], Some("3"), false, false), ), raw( "...", "*", task(&[("output", Some("1")), ("insert", Some("ellipsis"))], Some("1"), false, false), ), raw( ". __* ", "*", task(&[("output", None), ("insert", Some("addition compound"))], Some("1"), false, false), ), raw( "state of aggregation $", "*", task(&[("output", None), ("state of aggregation", None)], Some("1"), false, false), ), raw( "{[(", "a|as|o", task(&[("o=", None), ("output", None), ("parenthesisLevel++", None)], Some("2"), false, false), ), raw( "{[(", "0|1|2|3", task(&[("o=", None), ("output", None), ("parenthesisLevel++", None)], Some("2"), false, false), ), raw( "{[(", "*", task(&[("output", None), ("o=", None), ("output", None), ("parenthesisLevel++", None)], Some("2"), false, false), ), raw( ")]}", "0|1|2|3|b|p|bp|o", task(&[("o=", None), ("parenthesisLevel--", None)], Some("o"), false, false), ), raw( ")]}", "a|as|d|D|q|qd|qD|dq", task(&[("output", None), ("o=", None), ("parenthesisLevel--", None)], Some("o"), false, false), ), raw( ", ", "*", task(&[("output", None), ("comma", None)], Some("0"), false, false), ), raw("^_", "*", task(&[], None, false, false)), raw( "^{(...)}|^($...$)", "0|1|2|as", task(&[("b=", None)], Some("b"), false, false), ), raw( "^{(...)}|^($...$)", "p", task(&[("b=", None)], Some("bp"), false, false), ), raw( "^{(...)}|^($...$)", "3|o", task(&[("d= kv", None)], Some("D"), false, false), ), raw( "^{(...)}|^($...$)", "q", task(&[("d=", None)], Some("qD"), false, false), ), raw( "^{(...)}|^($...$)", "d|D|qd|qD|dq", task(&[("output", None), ("d=", None)], Some("D"), false, false), ), raw( "^a|^\\x{}{}|^\\x{}|^\\x|'", "0|1|2|as", task(&[("b=", None)], Some("b"), false, false), ), raw( "^a|^\\x{}{}|^\\x{}|^\\x|'", "p", task(&[("b=", None)], Some("bp"), false, false), ), raw( "^a|^\\x{}{}|^\\x{}|^\\x|'", "3|o", task(&[("d= kv", None)], Some("d"), false, false), ), raw( "^a|^\\x{}{}|^\\x{}|^\\x|'", "q", task(&[("d=", None)], Some("qd"), false, false), ), raw( "^a|^\\x{}{}|^\\x{}|^\\x|'", "d|qd|D|qD", task(&[("d=", None)], None, false, false), ), raw( "^a|^\\x{}{}|^\\x{}|^\\x|'", "dq", task(&[("output", None), ("d=", None)], Some("d"), false, false), ), raw( "_{(state of aggregation)}$", "d|D|q|qd|qD|dq", task(&[("output", None), ("q=", None)], Some("q"), false, false), ), raw( "_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x", "0|1|2|as", task(&[("p=", None)], Some("p"), false, false), ), raw( "_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x", "b", task(&[("p=", None)], Some("bp"), false, false), ), raw( "_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x", "3|o", task(&[("q=", None)], Some("q"), false, false), ), raw( "_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x", "d|D", task(&[("q=", None)], Some("dq"), false, false), ), raw( "_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x", "q|qd|qD|dq", task(&[("output", None), ("q=", None)], Some("q"), false, false), ), raw( "=<>", "0|1|2|3|a|as|o|q|d|D|qd|qD|dq", task(&[("output", Some("2")), ("bond", None)], Some("3"), false, false), ), raw( "#", "0|1|2|3|a|as|o", task(&[("output", Some("2")), ("bond", Some("#"))], Some("3"), false, false), ), raw( "{}^", "*", task(&[("output", Some("1")), ("insert", Some("tinySkip"))], Some("1"), false, false), ), raw("{}", "*", task(&[("output", Some("1"))], Some("1"), false, false)), raw( "{...}", "0|1|2|3|a|as|b|p|bp", task(&[("o=", None)], Some("o"), false, false), ), raw( "{...}", "o|d|D|q|qd|qD|dq", task(&[("output", None), ("o=", None)], Some("o"), false, false), ), raw("$...$", "a", task(&[("a=", None)], None, false, false)), raw( "$...$", "0|1|2|3|as|b|p|bp|o", task(&[("o=", None)], Some("o"), false, false), ), raw("$...$", "as|o", task(&[("o=", None)], None, false, false)), raw( "$...$", "q|d|D|qd|qD|dq", task(&[("output", None), ("o=", None)], Some("o"), false, false), ), raw( "\\bond{(...)}", "*", task(&[("output", Some("2")), ("bond", None)], Some("3"), false, false), ), raw( "\\frac{(...)}", "*", task(&[("output", Some("1")), ("frac-output", None)], Some("3"), false, false), ), raw( "\\overset{(...)}", "*", task(&[("output", Some("2")), ("overset-output", None)], Some("3"), false, false), ), raw( "\\underset{(...)}", "*", task(&[("output", Some("2")), ("underset-output", None)], Some("3"), false, false), ), raw( "\\underbrace{(...)}", "*", task(&[("output", Some("2")), ("underbrace-output", None)], Some("3"), false, false), ), raw( "\\color{(...)}{(...)}", "*", task(&[("output", Some("2")), ("color-output", None)], Some("3"), false, false), ), raw( "\\color{(...)}", "*", task(&[("output", Some("2")), ("color0-output", None)], None, false, false), ), raw( "\\ce{(...)}", "*", task(&[("output", Some("2")), ("ce", None)], Some("3"), false, false), ), raw( "\\,", "*", task(&[("output", Some("1")), ("copy", None)], Some("1"), false, false), ), raw( "\\pu{(...)}", "*", task(&[("output", None), ("write", Some("{")), ("pu", None), ("write", Some("}"))], Some("3"), false, false), ), raw( "\\x{}{}|\\x{}|\\x", "0|1|2|3|a|as|b|p|bp|o|c0", task(&[("o=", None), ("output", None)], Some("3"), false, false), ), raw( "\\x{}{}|\\x{}|\\x", "*", task(&[("output", None), ("o=", None), ("output", None)], Some("3"), false, false), ), raw( "others", "*", task(&[("output", Some("1")), ("copy", None)], Some("3"), false, false), ), raw( "else2", "a", task(&[("a to o", None)], Some("o"), true, false), ), raw( "else2", "as", task(&[("output", None), ("sb=true", None)], Some("1"), true, false), ), raw( "else2", "r|rt|rd|rdt|rdq", task(&[("output", None)], Some("0"), true, false), ), raw( "else2", "*", task(&[("output", None), ("copy", None)], Some("3"), true, false), ), ]) }); // ========================================================================= // a / o / text / pq / bd / oxidation / tex-math / tex-math tight / 9,9 // ========================================================================= static A: LazyLock>> = LazyLock::new(|| { build_transitions(&[ raw("empty", "*", task(&[], None, false, false)), raw("1/2$", "0", task(&[("1/2", None)], None, false, false)), raw("else", "0", task(&[], Some("1"), true, false)), raw( "${(...)}$__$(...)$", "*", task(&[("tex-math tight", None)], Some("1"), false, false), ), raw( ",", "*", task(&[("insert", Some("commaDecimal"))], None, false, false), ), raw("else2", "*", task(&[("copy", None)], None, false, false)), ]) }); static O: LazyLock>> = LazyLock::new(|| { build_transitions(&[ raw("empty", "*", task(&[], None, false, false)), raw("1/2$", "0", task(&[("1/2", None)], None, false, false)), raw("else", "0", task(&[], Some("1"), true, false)), raw("letters", "*", task(&[("rm", None)], None, false, false)), raw( "\\ca", "*", task(&[("insert", Some("circa"))], None, false, false), ), raw( "\\pu{(...)}", "*", task(&[("write", Some("{")), ("pu", None), ("write", Some("}"))], None, false, false), ), raw("\\x{}{}|\\x{}|\\x", "*", task(&[("copy", None)], None, false, false)), raw( "${(...)}$__$(...)$", "*", task(&[("tex-math", None)], None, false, false), ), raw( "{(...)}", "*", task(&[("write", Some("{")), ("text", None), ("write", Some("}"))], None, false, false), ), raw("else2", "*", task(&[("copy", None)], None, false, false)), ]) }); static TEXT: LazyLock>> = LazyLock::new(|| { build_transitions(&[ raw("empty", "*", task(&[("output", None)], None, false, false)), raw("{...}", "*", task(&[("text=", None)], None, false, false)), raw( "${(...)}$__$(...)$", "*", task(&[("tex-math", None)], None, false, false), ), raw( "\\greek", "*", task(&[("output", None), ("rm", None)], None, false, false), ), raw( "\\pu{(...)}", "*", task(&[("output", None), ("write", Some("{")), ("pu", None), ("write", Some("}"))], None, false, false), ), raw( "\\,|\\x{}{}|\\x{}|\\x", "*", task(&[("output", None), ("copy", None)], None, false, false), ), raw("else", "*", task(&[("text=", None)], None, false, false)), ]) }); static PQ: LazyLock>> = LazyLock::new(|| { build_transitions(&[ raw("empty", "*", task(&[], None, false, false)), raw( "state of aggregation $", "*", task(&[("state of aggregation", None)], None, false, false), ), raw("i$", "0", task(&[], Some("!f"), true, false)), raw("(KV letters),", "0", task(&[("rm", None)], Some("0"), false, false)), raw("formula$", "0", task(&[], Some("f"), true, false)), raw("1/2$", "0", task(&[("1/2", None)], None, false, false)), raw("else", "0", task(&[], Some("!f"), true, false)), raw( "${(...)}$__$(...)$", "*", task(&[("tex-math", None)], None, false, false), ), raw("{(...)}", "*", task(&[("text", None)], None, false, false)), raw("a-z", "f", task(&[("tex-math", None)], None, false, false)), raw("letters", "*", task(&[("rm", None)], None, false, false)), raw("-9.,9", "*", task(&[("9,9", None)], None, false, false)), raw( ",", "*", task(&[("insert+p1", Some("comma enumeration S"))], None, false, false), ), raw( "\\color{(...)}{(...)}", "*", task(&[("color-output", None)], None, false, false), ), raw( "\\color{(...)}", "*", task(&[("color0-output", None)], None, false, false), ), raw("\\ce{(...)}", "*", task(&[("ce", None)], None, false, false)), raw( "\\pu{(...)}", "*", task(&[("write", Some("{")), ("pu", None), ("write", Some("}"))], None, false, false), ), raw("\\,|\\x{}{}|\\x{}|\\x", "*", task(&[("copy", None)], None, false, false)), raw("else2", "*", task(&[("copy", None)], None, false, false)), ]) }); static BD: LazyLock>> = LazyLock::new(|| { build_transitions(&[ raw("empty", "*", task(&[], None, false, false)), raw("x$", "0", task(&[], Some("!f"), true, false)), raw("formula$", "0", task(&[], Some("f"), true, false)), raw("else", "0", task(&[], Some("!f"), true, false)), raw("-9.,9 no missing 0", "*", task(&[("9,9", None)], None, false, false)), raw( ".", "*", task(&[("insert", Some("electron dot"))], None, false, false), ), raw("a-z", "f", task(&[("tex-math", None)], None, false, false)), raw("x", "*", task(&[("insert", Some("KV x"))], None, false, false)), raw("letters", "*", task(&[("rm", None)], None, false, false)), raw("'", "*", task(&[("insert", Some("prime"))], None, false, false)), raw( "${(...)}$__$(...)$", "*", task(&[("tex-math", None)], None, false, false), ), raw("{(...)}", "*", task(&[("text", None)], None, false, false)), raw( "\\color{(...)}{(...)}", "*", task(&[("color-output", None)], None, false, false), ), raw( "\\color{(...)}", "*", task(&[("color0-output", None)], None, false, false), ), raw("\\ce{(...)}", "*", task(&[("ce", None)], None, false, false)), raw( "\\pu{(...)}", "*", task(&[("write", Some("{")), ("pu", None), ("write", Some("}"))], None, false, false), ), raw("\\,|\\x{}{}|\\x{}|\\x", "*", task(&[("copy", None)], None, false, false)), raw("else2", "*", task(&[("copy", None)], None, false, false)), ]) }); static OXIDATION: LazyLock>> = LazyLock::new(|| { build_transitions(&[ raw("empty", "*", task(&[("roman-numeral", None)], None, false, false)), raw( "pm-operator", "*", task(&[("o=+p1", Some("\\pm"))], None, false, false), ), raw("else", "*", task(&[("o=", None)], None, false, false)), ]) }); static TEX_MATH: LazyLock>> = LazyLock::new(|| { build_transitions(&[ raw("empty", "*", task(&[("output", None)], None, false, false)), raw( "\\ce{(...)}", "*", task(&[("output", None), ("ce", None)], None, false, false), ), raw( "\\pu{(...)}", "*", task(&[("output", None), ("write", Some("{")), ("pu", None), ("write", Some("}"))], None, false, false), ), raw("{...}|\\,|\\x{}{}|\\x{}|\\x", "*", task(&[("o=", None)], None, false, false)), raw("else", "*", task(&[("o=", None)], None, false, false)), ]) }); static TEX_MATH_TIGHT: LazyLock>> = LazyLock::new(|| { build_transitions(&[ raw("empty", "*", task(&[("output", None)], None, false, false)), raw( "\\ce{(...)}", "*", task(&[("output", None), ("ce", None)], None, false, false), ), raw( "\\pu{(...)}", "*", task(&[("output", None), ("write", Some("{")), ("pu", None), ("write", Some("}"))], None, false, false), ), raw("{...}|\\,|\\x{}{}|\\x{}|\\x", "*", task(&[("o=", None)], None, false, false)), raw("-|+", "*", task(&[("tight operator", None)], None, false, false)), raw("else", "*", task(&[("o=", None)], None, false, false)), ]) }); static NUM99: LazyLock>> = LazyLock::new(|| { build_transitions(&[ raw("empty", "*", task(&[], None, false, false)), raw(",", "*", task(&[("comma", None)], None, false, false)), raw("else", "*", task(&[("copy", None)], None, false, false)), ]) }); // ========================================================================= // pu / pu-2 / pu-9,9 状态机 // ========================================================================= static PU: LazyLock>> = LazyLock::new(|| { build_transitions(&[ raw("empty", "*", task(&[("output", None)], None, false, false)), raw( "space$", "*", task(&[("output", None), ("space", None)], None, false, false), ), raw("{[(|)]}", "0|a", task(&[("copy", None)], None, false, false)), raw( "(-)(9)^(-9)", "0", task(&[("number^", None)], Some("a"), false, false), ), raw( "(-)(9.,9)(e)(99)", "0", task(&[("enumber", None)], Some("a"), false, false), ), raw("space", "0|a", task(&[], None, false, false)), raw( "pm-operator", "0|a", task(&[("operator", Some("\\pm"))], Some("0"), false, false), ), raw("operator", "0|a", task(&[("copy", None)], Some("0"), false, false)), raw("//", "d", task(&[("o=", None)], Some("/"), false, false)), raw("/", "d", task(&[("o=", None)], Some("/"), false, false)), raw( "{...}|else", "0|d", task(&[("d=", None)], Some("d"), false, false), ), raw( "{...}|else", "a", task(&[("space", None), ("d=", None)], Some("d"), false, false), ), raw( "{...}|else", "/|q", task(&[("q=", None)], Some("q"), false, false), ), ]) }); static PU2: LazyLock>> = LazyLock::new(|| { build_transitions(&[ raw("empty", "*", task(&[("output", None)], None, false, false)), raw( "*", "*", task(&[("output", None), ("cdot", None)], Some("0"), false, false), ), raw("\\x", "*", task(&[("rm=", None)], None, false, false)), raw( "space", "*", task(&[("output", None), ("space", None)], Some("0"), false, false), ), raw( "^{(...)}|^(-1)", "1", task(&[("^(-1)", None)], None, false, false), ), raw("-9.,9", "0", task(&[("rm=", None)], Some("0"), false, false)), raw( "-9.,9", "1", task(&[("^(-1)", None)], Some("0"), false, false), ), raw("{...}|else", "*", task(&[("rm=", None)], Some("1"), false, false)), ]) }); static PU99: LazyLock>> = LazyLock::new(|| { build_transitions(&[ raw("empty", "0", task(&[("output-0", None)], None, false, false)), raw("empty", "o", task(&[("output-o", None)], None, false, false)), raw( ",", "0", task(&[("output-0", None), ("comma", None)], Some("o"), false, false), ), raw( ".", "0", task(&[("output-0", None), ("copy", None)], Some("o"), false, false), ), raw("else", "*", task(&[("text=", None)], None, false, false)), ]) }); // ========================================================================= // 机器局部动作分发 // ========================================================================= fn machine_action( machine: &str, buf: &mut Buffer, m: &MVal, opt: &Option, type_: &str, ) -> Option { match machine { "ce" => ce_action(buf, m, opt, type_), "text" => text_action(buf, type_), "pq" => pq_action(buf, m, type_), "bd" => bd_action(buf, m, type_), "oxidation" => oxidation_action(buf, type_), "tex-math" | "tex-math tight" => texmath_action(machine, buf, m, type_), "9,9" => Some(num99_action(type_)), "pu" => pu_action(buf, m, opt, type_), "pu-2" => pu2_action(buf, m, type_), "pu-9,9" => pu99_action(buf, type_), _ => None, } } fn gof(opt_str: &Option, m: &str) -> Field { Field::Nodes(go(opt_str.as_deref().unwrap_or(""), m)) } fn arrow_sub(buf_field: &Option, rdt: &Option, ce_m: &str) -> Field { match rdt.as_deref() { Some("M") => Field::Nodes(go(buf_field.as_deref().unwrap_or(""), "tex-math")), Some("T") => Field::Nodes(vec![Parsed::N(NodeData { type_: "text".into(), p1: Some(Field::Str(buf_field.clone().unwrap_or_default())), ..Default::default() })]), _ => Field::Nodes(go(buf_field.as_deref().unwrap_or(""), ce_m)), } } fn ce_action(buf: &mut Buffer, m: &MVal, opt: &Option, type_: &str) -> Option { let out = match type_ { "o after d" => { let mut ret: Vec = Vec::new(); let d_is_int = buf .d .as_ref() .map(|d| re!("^[1-9][0-9]*$").is_match(d)) .unwrap_or(false); if d_is_int { let tmp = buf.d.take(); concat(&mut ret, ce_action(buf, m, &None, "output").unwrap_or(Out::None)); ret.push(Parsed::N(NodeData { type_: "tinySkip".into(), ..Default::default() })); buf.b = tmp; } else { concat(&mut ret, ce_action(buf, m, &None, "output").unwrap_or(Out::None)); } append_field(&mut buf.o, m); Out::Many(ret) } "d= kv" => { buf.d = Some(mval_str(m)); buf.d_type = Some("kv".into()); Out::None } "charge or bond" => { if buf.begins_with_bond { let mut ret: Vec = Vec::new(); concat(&mut ret, ce_action(buf, m, &None, "output").unwrap_or(Out::None)); ret.push(Parsed::N(NodeData { type_: "bond".into(), kind_: Some("-".into()), ..Default::default() })); Out::Many(ret) } else { buf.d = Some(mval_str(m)); Out::None } } "- after o/d" => { let is_after_d = opt.as_deref() == Some("true"); let o_str = buf.o.clone().unwrap_or_default(); let c1 = match_pattern("orbital", &o_str); let c2 = match_pattern("one lowercase greek letter $", &o_str); let c3 = match_pattern("one lowercase latin letter $", &o_str); let c4 = match_pattern("$one lowercase latin letter$ $", &o_str); let m_str = mval_str(m); let hyphen_follows = m_str == "-" && ((c1.as_ref().map(|x| x.remainder.is_empty()).unwrap_or(false)) || c2.is_some() || c3.is_some() || c4.is_some()); if hyphen_follows && buf.a.is_none() && buf.b.is_none() && buf.p.is_none() && buf.d.is_none() && buf.q.is_none() && c1.is_none() && c3.is_some() { let ov = buf.o.take().unwrap_or_default(); buf.o = Some(format!("${}$", ov)); } let mut ret: Vec = Vec::new(); if hyphen_follows { concat(&mut ret, ce_action(buf, m, &None, "output").unwrap_or(Out::None)); ret.push(Parsed::N(NodeData { type_: "hyphen".into(), ..Default::default() })); } else { let d_str = buf.d.clone().unwrap_or_default(); let c1d = match_pattern("digits", &d_str); if is_after_d && c1d.as_ref().map(|x| x.remainder.is_empty()).unwrap_or(false) { append_field(&mut buf.d, m); concat(&mut ret, ce_action(buf, m, &None, "output").unwrap_or(Out::None)); } else { concat(&mut ret, ce_action(buf, m, &None, "output").unwrap_or(Out::None)); ret.push(Parsed::N(NodeData { type_: "bond".into(), kind_: Some("-".into()), ..Default::default() })); } } Out::Many(ret) } "a to o" => { buf.o = buf.a.take(); Out::None } "sb=true" => { buf.sb = true; Out::None } "sb=false" => { buf.sb = false; Out::None } "beginsWithBond=true" => { buf.begins_with_bond = true; Out::None } "beginsWithBond=false" => { buf.begins_with_bond = false; Out::None } "parenthesisLevel++" => { buf.parenthesis_level += 1; Out::None } "parenthesisLevel--" => { buf.parenthesis_level -= 1; Out::None } "state of aggregation" => Out::One(Parsed::N(NodeData { type_: "state of aggregation".into(), p1: Some(Field::Nodes(go(&mval_str(m), "o"))), ..Default::default() })), "comma" => { let raw = mval_str(m); let a = raw.trim_end(); let with_space = a != raw; let kind = if with_space && buf.parenthesis_level == 0 { "comma enumeration L" } else { "comma enumeration M" }; Out::One(Parsed::N(NodeData { type_: kind.into(), p1: Some(Field::Str(a.to_string())), ..Default::default() })) } "output" => { let entity_follows = match opt.as_deref() { Some("1") => 1, Some("2") => 2, _ => 0, }; let ret: Vec = if buf.r.is_none() { let mut ret = Vec::new(); let empty = buf.a.is_none() && buf.b.is_none() && buf.p.is_none() && buf.o.is_none() && buf.q.is_none() && buf.d.is_none() && entity_follows == 0; if !empty { if buf.sb { ret.push(Parsed::N(NodeData { type_: "entitySkip".into(), ..Default::default() })); } let o_none = buf.o.is_none(); let q_none = buf.q.is_none(); let d_none = buf.d.is_none(); let b_none = buf.b.is_none(); let p_none = buf.p.is_none(); if o_none && q_none && d_none && b_none && p_none && entity_follows != 2 { buf.o = buf.a.take(); } else if o_none && q_none && d_none && (buf.b.is_some() || buf.p.is_some()) { buf.o = buf.a.take(); buf.d = buf.b.take(); buf.q = buf.p.take(); } else if buf.o.is_some() && buf.d_type.as_deref() == Some("kv") && match_pattern("d-oxidation$", &buf.d.clone().unwrap_or_default()).is_some() { buf.d_type = Some("oxidation".into()); } else if buf.o.is_some() && buf.d_type.as_deref() == Some("kv") && buf.q.is_none() { buf.d_type = None; } let d_machine = if buf.d_type.as_deref() == Some("oxidation") { "oxidation" } else { "bd" }; ret.push(Parsed::N(NodeData { type_: "chemfive".into(), a: Some(gof(&buf.a.clone(), "a")), b: Some(gof(&buf.b.clone(), "bd")), p: Some(gof(&buf.p.clone(), "pq")), o: Some(gof(&buf.o.clone(), "o")), q: Some(gof(&buf.q.clone(), "pq")), d: Some(gof(&buf.d.clone(), d_machine)), d_type: buf.d_type.clone(), ..Default::default() })); } ret } else { let rd = arrow_sub(&buf.rd.clone(), &buf.rdt.clone(), "ce"); let rq = arrow_sub(&buf.rq.clone(), &buf.rqt.clone(), "ce"); vec![Parsed::N(NodeData { type_: "arrow".into(), r: buf.r.clone(), rd: Some(rd), rq: Some(rq), ..Default::default() })] }; buf.clear(true); Out::Many(ret) } "oxidation-output" => { let mut ret: Vec = vec![Parsed::S("{".into())]; ret.extend(go(&mval_str(m), "oxidation")); ret.push(Parsed::S("}".into())); Out::Many(ret) } "frac-output" | "overset-output" | "underset-output" | "underbrace-output" => { let (node_type, p1m, p2m) = match type_ { "frac-output" => ("frac-ce", 0usize, 1usize), "overset-output" => ("overset", 0, 1), "underset-output" => ("underset", 0, 1), _ => ("underbrace", 0, 1), }; let (g1, g2) = mval_pair(m, p1m, p2m); Out::One(Parsed::N(NodeData { type_: node_type.into(), p1: Some(Field::Nodes(go(&g1, "ce"))), p2: Some(Field::Nodes(go(&g2, "ce"))), ..Default::default() })) } "color-output" => { let (g1, g2) = mval_pair(m, 0, 1); Out::One(Parsed::N(NodeData { type_: "color".into(), color1: Some(g1), color2: Some(Field::Nodes(go(&g2, "ce"))), ..Default::default() })) } "r=" => { buf.r = Some(mval_str(m)); Out::None } "rdt=" => { buf.rdt = Some(mval_str(m)); Out::None } "rd=" => { buf.rd = Some(mval_str(m)); Out::None } "rqt=" => { buf.rqt = Some(mval_str(m)); Out::None } "rq=" => { buf.rq = Some(mval_str(m)); Out::None } "operator" => Out::One(Parsed::N(NodeData { type_: "operator".into(), kind_: Some(opt.clone().unwrap_or_else(|| mval_str(m))), ..Default::default() })), _ => return None, }; Some(out) } fn mval_pair(m: &MVal, i: usize, j: usize) -> (String, String) { match m { MVal::V(v) => ( v.get(i).cloned().unwrap_or_default(), v.get(j).cloned().unwrap_or_default(), ), MVal::S(s) => (s.clone(), String::new()), } } fn text_action(buf: &mut Buffer, type_: &str) -> Option { if type_ == "output" { let ret = buf.text_.take().map(|t| { Out::One(Parsed::N(NodeData { type_: "text".into(), p1: Some(Field::Str(t)), ..Default::default() })) }); buf.clear(false); Some(ret.unwrap_or(Out::None)) } else { None } } fn pq_action(_buf: &mut Buffer, m: &MVal, type_: &str) -> Option { match type_ { "state of aggregation" => Some(Out::One(Parsed::N(NodeData { type_: "state of aggregation subscript".into(), p1: Some(Field::Nodes(go(&mval_str(m), "o"))), ..Default::default() }))), "color-output" => { let (g1, g2) = mval_pair(m, 0, 1); Some(Out::One(Parsed::N(NodeData { type_: "color".into(), color1: Some(g1), color2: Some(Field::Nodes(go(&g2, "pq"))), ..Default::default() }))) } _ => None, } } fn bd_action(_buf: &mut Buffer, m: &MVal, type_: &str) -> Option { if type_ == "color-output" { let (g1, g2) = mval_pair(m, 0, 1); Some(Out::One(Parsed::N(NodeData { type_: "color".into(), color1: Some(g1), color2: Some(Field::Nodes(go(&g2, "bd"))), ..Default::default() }))) } else { None } } fn oxidation_action(buf: &mut Buffer, type_: &str) -> Option { if type_ == "roman-numeral" { Some(Out::One(Parsed::N(NodeData { type_: "roman numeral".into(), p1: Some(Field::Str(buf.o.clone().unwrap_or_default())), ..Default::default() }))) } else { None } } fn texmath_action(machine: &str, buf: &mut Buffer, m: &MVal, type_: &str) -> Option { match type_ { "tight operator" => { append_str(&mut buf.o, &format!("{{{}}}", mval_str(m))); Some(Out::None) } "output" => { let ret = buf.o.take().map(|o| { Out::One(Parsed::N(NodeData { type_: "tex-math".into(), p1: Some(Field::Str(o)), ..Default::default() })) }); buf.clear(false); let _ = machine; Some(ret.unwrap_or(Out::None)) } _ => None, } } fn num99_action(type_: &str) -> Out { if type_ == "comma" { Out::One(Parsed::N(NodeData { type_: "commaDecimal".into(), ..Default::default() })) } else { Out::None } } fn pu_action(buf: &mut Buffer, m: &MVal, opt: &Option, type_: &str) -> Option { let out = match type_ { "enumber" => { let v = match m { MVal::V(v) => v.clone(), MVal::S(s) => vec![s.clone()], }; let mut ret: Vec = Vec::new(); let g0 = v.first().map(|s| s.as_str()).unwrap_or(""); if g0 == "+-" || g0 == "+/-" { ret.push(Parsed::S("\\pm ".into())); } else if !g0.is_empty() { ret.push(Parsed::S(g0.to_string())); } let g1 = v.get(1).cloned().unwrap_or_default(); if !g1.is_empty() { ret.extend(go(&g1, "pu-9,9")); let g2 = v.get(2).cloned().unwrap_or_default(); if !g2.is_empty() { if g2.contains(',') || g2.contains('.') { ret.extend(go(&g2, "pu-9,9")); } else { ret.push(Parsed::S(g2)); } } let g3 = v.get(3).map(|s| s.as_str()).unwrap_or(""); let g4 = v.get(4).map(|s| s.as_str()).unwrap_or(""); if !g3.is_empty() || !g4.is_empty() { if g3 == "e" || g4 == "*" { ret.push(Parsed::N(NodeData { type_: "cdot".into(), ..Default::default() })); } else { ret.push(Parsed::N(NodeData { type_: "times".into(), ..Default::default() })); } } } let g5 = v.get(5).cloned().unwrap_or_default(); if !g5.is_empty() { ret.push(Parsed::S(format!("10^{{{}}}", g5))); } Out::Many(ret) } "number^" => { let v = match m { MVal::V(v) => v.clone(), MVal::S(s) => vec![s.clone()], }; let mut ret: Vec = Vec::new(); let g0 = v.first().map(|s| s.as_str()).unwrap_or(""); if g0 == "+-" || g0 == "+/-" { ret.push(Parsed::S("\\pm ".into())); } else if !g0.is_empty() { ret.push(Parsed::S(g0.to_string())); } let g1 = v.get(1).cloned().unwrap_or_default(); ret.extend(go(&g1, "pu-9,9")); let g2 = v.get(2).cloned().unwrap_or_default(); ret.push(Parsed::S(format!("^{{{}}}", g2))); Out::Many(ret) } "operator" => Out::One(Parsed::N(NodeData { type_: "operator".into(), kind_: Some(opt.clone().unwrap_or_else(|| mval_str(m))), ..Default::default() })), "space" => Out::One(Parsed::N(NodeData { type_: "pu-space-1".into(), ..Default::default() })), "output" => { // {(...)} 解包 if let Some(d) = &buf.d { if let Some(md) = match_pattern("{(...)}", d) { if md.remainder.is_empty() { buf.d = Some(mval_str(&md.m)); } } } if let Some(q) = &buf.q { if let Some(mq) = match_pattern("{(...)}", q) { if mq.remainder.is_empty() { buf.q = Some(mval_str(&mq.m)); } } } fn rep_c_f(s: &str) -> String { s.replace("\u{00B0}C", "{}^{\\circ}C") .replace("^oC", "{}^{\\circ}C") .replace("^{o}C", "{}^{\\circ}C") .replace("\u{00B0}F", "{}^{\\circ}F") .replace("^oF", "{}^{\\circ}F") .replace("^{o}F", "{}^{\\circ}F") } if let Some(d) = buf.d.as_mut() { *d = rep_c_f(d); } let ret: Vec = if let Some(q) = buf.q.as_mut() { *q = rep_c_f(q); let bd = go(&buf.d.clone().unwrap_or_default(), "pu"); let bq = go(&buf.q.clone().unwrap_or_default(), "pu"); if buf.o.as_deref() == Some("//") { vec![Parsed::N(NodeData { type_: "pu-frac".into(), p1: Some(Field::Nodes(bd)), p2: Some(Field::Nodes(bq)), ..Default::default() })] } else { let mut ret = bd; if ret.len() > 1 || bq.len() > 1 { ret.push(Parsed::N(NodeData { type_: " / ".into(), ..Default::default() })); } else { ret.push(Parsed::N(NodeData { type_: "/".into(), ..Default::default() })); } ret.extend(bq); ret } } else { go(&buf.d.clone().unwrap_or_default(), "pu-2") }; buf.clear(false); Out::Many(ret) } _ => return None, }; Some(out) } fn pu2_action(buf: &mut Buffer, m: &MVal, type_: &str) -> Option { let out = match type_ { "cdot" => Out::One(Parsed::N(NodeData { type_: "tight cdot".into(), ..Default::default() })), "^(-1)" => { if let Some(rm) = buf.rm.as_mut() { rm.push_str(&format!("^{{{}}}", mval_str(m))); } Out::None } "space" => Out::One(Parsed::N(NodeData { type_: "pu-space-2".into(), ..Default::default() })), "output" => { let ret: Vec = if let Some(rm) = &buf.rm { if let Some(mrm) = match_pattern("{(...)}", rm) { if mrm.remainder.is_empty() { go(&mval_str(&mrm.m), "pu") } else { vec![Parsed::N(NodeData { type_: "rm".into(), p1: Some(Field::Str(rm.clone())), ..Default::default() })] } } else { vec![Parsed::N(NodeData { type_: "rm".into(), p1: Some(Field::Str(rm.clone())), ..Default::default() })] } } else { Vec::new() }; buf.clear(false); Out::Many(ret) } _ => return None, }; Some(out) } fn pu99_action(buf: &mut Buffer, type_: &str) -> Option { let out = match type_ { "comma" => Out::One(Parsed::N(NodeData { type_: "commaDecimal".into(), ..Default::default() })), "output-0" => { let text = buf.text_.clone().unwrap_or_default(); let ret = thousands_split(&text, true); buf.clear(false); Out::Many(ret) } "output-o" => { let text = buf.text_.clone().unwrap_or_default(); let ret = thousands_split(&text, false); buf.clear(false); Out::Many(ret) } _ => return None, }; Some(out) } /// 千分位拆分(pu-9,9 的 output-0 / output-o)。 fn thousands_split(text: &str, reverse: bool) -> Vec { let mut ret: Vec = Vec::new(); if text.len() > 4 { if reverse { let mut a = text.len() % 3; if a == 0 { a = 3; } let mut i = text.len().saturating_sub(3); while i > 0 { ret.push(Parsed::S(text[i..i + 3].to_string())); ret.push(Parsed::N(NodeData { type_: "1000 separator".into(), ..Default::default() })); i = i.saturating_sub(3); } ret.push(Parsed::S(text[..a].to_string())); ret.reverse(); } else { let a = text.len() - 3; let mut i = 0; while i < a { ret.push(Parsed::S(text[i..i + 3].to_string())); ret.push(Parsed::N(NodeData { type_: "1000 separator".into(), ..Default::default() })); i += 3; } ret.push(Parsed::S(text[i..].to_string())); } } else { ret.push(Parsed::S(text.to_string())); } ret }