jaosber/desmos-latex-parser
GitHub: jaosber/desmos-latex-parser
从 Desmos 图形计算器生产构建中提取的独立 LaTeX 数学解析器,实现了从 LaTeX 字符串到可求值数学表达式的完整四阶段转换流水线。
Stars: 0 | Forks: 0
# desmos-latex-parser
从 Desmos 图形计算器(2018 年生产版本)中提取和分离 LaTeX 数学解析器。
## 背景
`src/calculator.js` 是一个约 43,500 行的单一 JavaScript 文件,包含了发布到生产环境的整个 Desmos 图形计算器。该代码库最初是用 **TypeScript** 编写的,编译为 **ES5**,使用 **AMD** (RequireJS/Almond) 打包,并压缩成一个文件。
计算器在 **Web Worker** 中运行其所有的数学处理——这是一个不会阻塞 UI 的独立线程。整个 worker 代码作为一个巨大的字符串字面量(约 35,000 行)嵌入在主文件中。当计算器启动时,它会将此字符串注入到 `new Worker()` 中。
该 worker 内部包含一个完整的 LaTeX 数学解析器、求值器和绘图器。本项目的目标是**提取解析器**,将其从系统的其余部分中分离出来,并使其能够独立运行。
## 解析器流水线
解析器通过 4 个阶段将 LaTeX 数学字符串转换为可求值的表达式:
| 阶段 | 模块 | 输入 | 输出 |
| --------------------- | ------------------- | -------------------------- | -------------------------------------------------------------------------- |
| 1 - Latex 词法分析器 | `latex-lexer` | 原始字符串 `\frac{3+1}{2}` | Tokens: `Frac`, `{`, `Digit(3)`, `Symbol(+)`, ... |
| 2 - Latex 解析器 | `latex-parser` | Token 流 | LaTeX 树: `Frac { num: Group[...], den: Group[...] }` |
| 3 - 表达式解析器 | `expression-parser` | LaTeX 树 | 带有运算符优先级的数学表面节点 |
| 4 - Lower | `lower` | 表面节点 | 最终解析节点: `Divide([Add([Constant(3), Constant(1)]), Constant(2)])` |
阶段 1-2 理解 LaTeX 的**结构**(分数、根号、上标)。阶段 3-4 理解**数学语义**(优先级、函数调用、隐式乘法)。
在阶段 4 之后,解析节点通过 `getEvalStrings()` → `new Function()` 被编译为 JavaScript,并针对一个提供了 `cos`、`sin`、`pow` 等功能的 `BuiltIn` 对象执行。
## 状态
- [x] 阶段 1 — Latex 词法分析器(已提取,可正常运行)
- [x] 阶段 2 — Latex 解析器(已提取,可正常运行)
- [x] 阶段 3 — 表达式解析器
- [ ] 阶段 4 — Lower
- [ ] 求值 (`getEvalStrings` + `BuiltIn`)
## 测试
```
node ./tests/test.js
node ./tests/surface-eval.js
```
输出
```
===== LEXER TEST =====
latex string: \pi^{\operatorname{mod}\left(x,2\right)}+a^{b^{c}}+\sqrt{\frac{1}{2.7182+y}}\left(-1\right)\cdotcos\left(x+y\right)
Cmd "\\pi"
^ "^"
{ "{"
OperatorName "\\operatorname"
{ "{"
Letter "m"
Letter "o"
Letter "d"
} "}"
Left "\\left"
Symbol "("
Letter "x"
Symbol ","
Digit "2"
Right "\\right"
Symbol ")"
} "}"
Symbol "+"
Letter "a"
^ "^"
{ "{"
Letter "b"
^ "^"
{ "{"
Letter "c"
} "}"
} "}"
Symbol "+"
Sqrt "\\sqrt"
{ "{"
Frac "\\frac"
{ "{"
Digit "1"
} "}"
{ "{"
Digit "2"
Symbol "."
Digit "7"
Digit "1"
Digit "8"
Digit "2"
Symbol "+"
Letter "y"
} "}"
} "}"
Left "\\left"
Symbol "("
Symbol "-"
Digit "1"
Right "\\right"
Symbol ")"
Cmd "\\cdotcos"
Left "\\left"
Symbol "("
Letter "x"
Symbol "+"
Letter "y"
Right "\\right"
Symbol ")"
===== LATEX PARSER TEST =====
Input: \pi^{\operatorname{mod}\left(x,2\right)}+a^{b^{c}}+\sqrt{\frac{1}{2.7182+y}}\left(-1\right)\cdotcos\left(x+y\right)
Tree: {
"type": "Group",
"args": [
{
"type": "Cmd",
"val": "\\pi"
},
{
"type": "SupSub",
"nprimes": 0,
"sup": {
"type": "Group",
"args": [
{
"type": "OperatorName",
"arg": {
"type": "Group",
"args": [
{
"type": "Letter",
"val": "m"
},
{
"type": "Letter",
"val": "o"
},
{
"type": "Letter",
"val": "d"
}
]
}
},
{
"type": "LeftRight",
"arg": {
"type": "Group",
"args": [
{
"type": "Letter",
"val": "x"
},
{
"type": "Symbol",
"val": ","
},
{
"type": "Digit",
"val": "2"
}
]
},
"left": {
"type": "Symbol",
"val": "("
},
"right": {
"type": "Symbol",
"val": ")"
}
}
]
}
},
{
"type": "Symbol",
"val": "+"
},
{
"type": "Letter",
"val": "a"
},
{
"type": "SupSub",
"nprimes": 0,
"sup": {
"type": "Group",
"args": [
{
"type": "Letter",
"val": "b"
},
{
"type": "SupSub",
"nprimes": 0,
"sup": {
"type": "Group",
"args": [
{
"type": "Letter",
"val": "c"
}
]
}
}
]
}
},
{
"type": "Symbol",
"val": "+"
},
{
"type": "Sqrt",
"arg": {
"type": "Group",
"args": [
{
"type": "Frac",
"num": {
"type": "Group",
"args": [
{
"type": "Digit",
"val": "1"
}
]
},
"den": {
"type": "Group",
"args": [
{
"type": "Digit",
"val": "2"
},
{
"type": "Symbol",
"val": "."
},
{
"type": "Digit",
"val": "7"
},
{
"type": "Digit",
"val": "1"
},
{
"type": "Digit",
"val": "8"
},
{
"type": "Digit",
"val": "2"
},
{
"type": "Symbol",
"val": "+"
},
{
"type": "Letter",
"val": "y"
}
]
}
}
]
}
},
{
"type": "LeftRight",
"arg": {
"type": "Group",
"args": [
{
"type": "Symbol",
"val": "-"
},
{
"type": "Digit",
"val": "1"
}
]
},
"left": {
"type": "Symbol",
"val": "("
},
"right": {
"type": "Symbol",
"val": ")"
}
},
{
"type": "Cmd",
"val": "\\cdotcos"
},
{
"type": "LeftRight",
"arg": {
"type": "Group",
"args": [
{
"type": "Letter",
"val": "x"
},
{
"type": "Symbol",
"val": "+"
},
{
"type": "Letter",
"val": "y"
}
]
},
"left": {
"type": "Symbol",
"val": "("
},
"right": {
"type": "Symbol",
"val": ")"
}
}
]
}
===== EXPRESSION PARSER TEST =====
Input: \pi^{\operatorname{mod}\left(x,2\right)}+a^{b^{c}}+\sqrt{\frac{1}{2.7182+y}}\left(-1\right)\cdotcos\left(x+y\right)
{
"type": "Add",
"args": [
{
"type": "Add",
"args": [
{
"type": "Superscript",
"args": [
{
"type": "Cmd",
"val": "pi"
},
{
"type": "Call",
"args": [
{
"type": "Cmd",
"val": "mod"
},
{
"type": "Seq",
"args": [
{
"type": "Letter",
"val": "x"
},
{
"type": "Decimal",
"val": "2"
}
]
}
]
}
]
},
{
"type": "Superscript",
"args": [
{
"type": "Letter",
"val": "a"
},
{
"type": "Superscript",
"args": [
{
"type": "Letter",
"val": "b"
},
{
"type": "Letter",
"val": "c"
}
]
}
]
}
]
},
{
"type": "Juxt",
"args": [
{
"type": "Juxt",
"args": [
{
"type": "Sqrt",
"args": [
{
"type": "Frac",
"args": [
{
"type": "Decimal",
"val": "1"
},
{
"type": "Add",
"args": [
{
"type": "Decimal",
"val": "2.7182"
},
{
"type": "Letter",
"val": "y"
}
]
}
]
}
]
},
{
"type": "Paren",
"args": [
{
"type": "Neg",
"args": [
{
"type": "Decimal",
"val": "1"
}
]
}
]
}
]
},
{
"type": "Call",
"args": [
{
"type": "Cmd",
"val": "cdotcos"
},
{
"type": "Add",
"args": [
{
"type": "Letter",
"val": "x"
},
{
"type": "Letter",
"val": "y"
}
]
}
]
}
]
}
]
}
===== BASIC ARITHMETIC =====
PASS Integer addition 3+2 = 5
PASS Integer subtraction 10-7 = 3
PASS Integer multiplication 3*4 = 12
PASS Cdot multiplication 3\cdot 4 = 12
PASS Division \frac{10}{4} = 2.5
PASS Negative -5+3 = -2
PASS Nested fraction \frac{\frac{1}{2}}{\frac{1}{4}} = 2
PASS Mixed operations 2+3*4 = 14
PASS Precedence 1+2*3+4 = 11
PASS Parentheses (2+3)*4 = 20
===== EXPONENTS =====
PASS Square 2^{2} = 4
PASS Cube 2^{3} = 8
PASS Fractional exponent 8^{\frac{1}{3}} = 2
PASS Right associative 2^{3^{2}} = 512
===== SPECIAL OPERATIONS =====
PASS Factorial 5! = 120
PASS Factorial zero 0! = 1
PASS Absolute value \left|-5\right| = 5
PASS Square root \sqrt{16} = 4
PASS Cube root \sqrt[3]{27} = 3
PASS Implicit multiply 2x = 6.283185307179586
PASS Module \operatorname{mod}(3.5,2) = 1.5
===== CONSTANTS =====
PASS Pi \pi = 3.141592653589793
PASS Euler e = 2.718281828459045
PASS Tau \tau = 6.283185307179586
===== TRIG FUNCTIONS =====
PASS cos(0) \cos(0) = 1
PASS sin(0) \sin(0) = 0
PASS sin(pi/2) \sin(\frac{\pi}{2}) = 1
PASS tan(0) \tan(0) = 0
PASS cos with \left\right \cos\left(0\right) = 1
===== LOGARITHMS =====
PASS ln(1) \ln(1) = 0
PASS ln(e) \ln(e) = 1
PASS log(100) \log(100) = 2
PASS exp(0) \exp(0) = 1
PASS exp(1) \exp(1) = 2.718281828459045
===== VARIABLES =====
PASS Simple variable a+1 = 6
PASS Two variables a+b = 10
PASS Variable expression a*b+1 = 22
PASS Pi variable \pi*r^{2} = 78.53981633974483
PASS Variable chain b = 5
PASS Subscripted variable x_{1}+5 = 15
===== USER FUNCTIONS =====
PASS Simple function f(3) = 4
PASS Quadratic function f(4) = 17
PASS Trig function f(0) = 1
PASS Composed g(3) = 10
PASS Two params f(3,4) = 7
PASS Three params f(2,3,1) = 7
PASS Complex function f(0) = 1
PASS Function with constant f(0) = 0
===== COMBINED =====
PASS Full expression f(a)+\frac{1}{2} = 0.7836621854632262
PASS Quadratic formula part \frac{-b+\sqrt{b^{2}-4*a*c}}{2*a} = 2
PASS Circle area \pi*r^{2} = 28.274333882308138
PASS Compound function g(f(3))+1 = 4
PASS Physics: kinetic energy \frac{1}{2}*m*v^{2} = 45
PASS Nested functions f(g(f(1))) = 5
===== INEQUALITIES =====
PASS Less than true 3<5 = 1
PASS Less than false 5<3 = 0
PASS Greater equal 5\ge 5 = 1
PASS Equality 3=3 = 1
PASS Equality false 3=4 = 0
```
标签:AMD, CMS安全, Desmos计算器, JavaScript, LaTeX解析器, odt, Pratt解析器, RequireJS, TypeScript, Web Worker, 云资产清单, 代码提取, 安全插件, 抽象语法树, 数学公式解析, 数据可视化, 编译器, 自定义脚本, 表达式求值, 词法分析器, 逆向工程