katex-rs 默认无 mhchem 解析器,\ce/\pu 化学公式渲染为红字(实测正确页面 648 的 137 个公式中 32 处化学公式坏掉,含 \ce×24 + \pu×8)。 从 mhchemParser 4.2.2(Apache-2.0)机械移植状态机转译器为纯 Rust: - src/api/mhchem.rs:数据模型 + 模式匹配 + go() 主循环 + texify 输出 + 公开 API - src/api/mhchem_tables.rs:14 个状态机转移表 + 机器局部动作(ce/pu/...) - 模式含 lookahead (?=)/(?!),Rust regex crate 不支持,故用 fancy-regex (已被 syntect 间接引入,此处显式声明为直接 server 依赖) - ce/pu 包裹 catch_unwind,任何内部异常回退原样输出,绝不 panic - katex.rs 新增 expand_chem:渲染前嵌套花括号配对扫描 \ce/\pu 预转译 - 行尾 ^ 气体符号转译为 \uparrow,消解原 mhchem 解析错误 - 8 个 mhchem 单测 + 5 个 katex 集成测试(水/反应箭头/气体箭头/单位/络离子)
1548 lines
53 KiB
Rust
1548 lines
53 KiB
Rust
// mhchem 状态机转移表 + 机器局部动作(由 mhchem.rs `include!`)。
|
||
// 数据机械移植自 mhchemParser 4.2.2 的 `stateMachines`。
|
||
// (被 include 进 mhchem.rs 中部,故不能用 `//!` 内部文档注释。)
|
||
|
||
#[allow(clippy::needless_pass_by_value)]
|
||
|
||
use super::*;
|
||
|
||
// ── 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<String, Vec<Transition>> {
|
||
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<HashMap<String, Vec<Transition>>> = 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<HashMap<String, Vec<Transition>>> = 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<HashMap<String, Vec<Transition>>> = 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<HashMap<String, Vec<Transition>>> = 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<HashMap<String, Vec<Transition>>> = 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<HashMap<String, Vec<Transition>>> = 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<HashMap<String, Vec<Transition>>> = 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<HashMap<String, Vec<Transition>>> = 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<HashMap<String, Vec<Transition>>> = 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<HashMap<String, Vec<Transition>>> = 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<HashMap<String, Vec<Transition>>> = 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<HashMap<String, Vec<Transition>>> = 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<HashMap<String, Vec<Transition>>> = 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<HashMap<String, Vec<Transition>>> = 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<String>,
|
||
type_: &str,
|
||
) -> Option<Out> {
|
||
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<String>, m: &str) -> Field {
|
||
Field::Nodes(go(opt_str.as_deref().unwrap_or(""), m))
|
||
}
|
||
|
||
fn arrow_sub(buf_field: &Option<String>, rdt: &Option<String>, 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<String>, type_: &str) -> Option<Out> {
|
||
let out = match type_ {
|
||
"o after d" => {
|
||
let mut ret: Vec<Parsed> = Vec::new();
|
||
let d_is_int = buf
|
||
.d
|
||
.as_ref()
|
||
.map(|d| re!("^[1-9][0-9]*$").is_match(d).unwrap_or(false))
|
||
.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<Parsed> = 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<Parsed> = 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<Parsed> = 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<Parsed> = 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<Out> {
|
||
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<Out> {
|
||
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<Out> {
|
||
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<Out> {
|
||
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<Out> {
|
||
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<String>, type_: &str) -> Option<Out> {
|
||
let out = match type_ {
|
||
"enumber" => {
|
||
let v = match m {
|
||
MVal::V(v) => v.clone(),
|
||
MVal::S(s) => vec![s.clone()],
|
||
};
|
||
let mut ret: Vec<Parsed> = Vec::new();
|
||
let g0 = v.get(0).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<Parsed> = Vec::new();
|
||
let g0 = v.get(0).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<Parsed> = 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<Out> {
|
||
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<Parsed> = 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<Out> {
|
||
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<Parsed> {
|
||
let mut ret: Vec<Parsed> = 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
|
||
}
|