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, 云资产清单, 代码提取, 安全插件, 抽象语法树, 数学公式解析, 数据可视化, 编译器, 自定义脚本, 表达式求值, 词法分析器, 逆向工程