expr_parse.dart 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. /// 把 Symja 语法的表达式文本解析成节点树。
  2. ///
  3. /// 用途是**粘贴**:计算器的输入是结构化的,从剪贴板进来的却是一串文本,
  4. /// 得先转成树才能继续用按钮编辑。
  5. ///
  6. /// 设计原则:**只解析一个明确的子集,遇到不认识的东西就明确报错,绝不猜。**
  7. /// 猜错了用户看到的是算错的答案,比直接报错糟糕得多。
  8. ///
  9. /// 支持的语法:
  10. /// 数字(含小数与 `1e-3` 科学计数法)
  11. /// `+ - * / ^`、一元负号、并置乘法(`2x`、`2(x+1)`)
  12. /// `Name[...]`、`( … )`、`{ … }`
  13. /// `-> == :=`(当普通运算符节点)
  14. /// 标识符可以带 `_` 和 `$`(`x_`、`$ans`)
  15. ///
  16. /// 不支持(会报错):`@ /;; & 过长的上下文反引号` 等等。
  17. library;
  18. import 'expr_node.dart';
  19. /// 解析失败。带上出错位置,方便定位。
  20. class ExprParseException implements Exception {
  21. final String message;
  22. final int position;
  23. const ExprParseException(this.message, this.position);
  24. @override
  25. String toString() => position >= 0 ? '第 ${position + 1} 个字符:$message' : message;
  26. }
  27. /// 解析成功返回根槽;失败抛 [ExprParseException]。
  28. SeqNode parseSymjaExpr(String source) => _Parser(source).parse();
  29. class _Parser {
  30. final String src;
  31. int i = 0;
  32. _Parser(this.src);
  33. bool get _eof => i >= src.length;
  34. String get _cur => src[i];
  35. void _ws() {
  36. while (!_eof && const {' ', '\t', '\n', '\r'}.contains(_cur)) {
  37. i++;
  38. }
  39. }
  40. void _expect(String ch) {
  41. _ws();
  42. if (_eof) throw ExprParseException('表达式不完整,缺少「$ch」', i);
  43. if (_cur != ch) throw ExprParseException('期望「$ch」,实际是「$_cur」', i);
  44. i++;
  45. }
  46. SeqNode parse() {
  47. final seq = SeqNode(_expr());
  48. _ws();
  49. if (!_eof) {
  50. throw ExprParseException('多余的内容「${src.substring(i)}」', i);
  51. }
  52. return seq;
  53. }
  54. // ---------------------------------------------------------------- 加减层
  55. //
  56. // 树里没有"加法节点"——加减乘都是序列里的字面量运算符,优先级交给 Symja
  57. // 求值时处理。只有 / 和 ^ 做成结构(分式、幂),因为它们要竖排/上标显示。
  58. List<ExprNode> _expr() {
  59. final out = <ExprNode>[..._term()];
  60. while (true) {
  61. _ws();
  62. if (_eof) break;
  63. // 多字符运算符要先判,否则 `x->0` 会被当成 `x` 减 `>0`
  64. if (src.startsWith('->', i)) {
  65. i += 2;
  66. out.add(LitNode('->'));
  67. out.addAll(_term());
  68. } else if (src.startsWith('==', i)) {
  69. i += 2;
  70. out.add(LitNode('=='));
  71. out.addAll(_term());
  72. } else if (src.startsWith(':=', i)) {
  73. i += 2;
  74. out.add(LitNode(':='));
  75. out.addAll(_term());
  76. } else if (_cur == '+' || _cur == '-') {
  77. out.add(LitNode(_cur));
  78. i++;
  79. out.addAll(_term());
  80. } else {
  81. break;
  82. }
  83. }
  84. return out;
  85. }
  86. // ---------------------------------------------------------------- 乘除层
  87. List<ExprNode> _term() {
  88. var out = <ExprNode>[..._unary()];
  89. while (true) {
  90. _ws();
  91. if (_eof) break;
  92. if (_cur == '*') {
  93. i++;
  94. out = [...out, LitNode('*'), ..._unary()];
  95. } else if (_cur == '/') {
  96. i++;
  97. out = [
  98. FracNode(num: SeqNode(out), den: SeqNode(_unary())),
  99. ];
  100. } else if (_startsValue(_cur)) {
  101. // 并置即乘法:2x、2(x+1)。不插 `*` 节点 —— 序列化出来还是 `2x`,
  102. // Symja 认这个写法,而且用户看到的和打进去的一样。
  103. out = [...out, ..._unary()];
  104. } else {
  105. break;
  106. }
  107. }
  108. return out;
  109. }
  110. bool _startsValue(String c) =>
  111. _isDigit(c) || c == '.' || _isIdentStart(c) || c == '(' || c == '{';
  112. // ---------------------------------------------------------------- 一元 / 幂
  113. List<ExprNode> _unary() {
  114. _ws();
  115. if (_eof) throw ExprParseException('表达式不完整', i);
  116. if (_cur == '-') {
  117. i++;
  118. return [LitNode('-'), ..._unary()];
  119. }
  120. if (_cur == '+') {
  121. i++;
  122. return _unary();
  123. }
  124. return _power();
  125. }
  126. List<ExprNode> _power() {
  127. final base = _primary();
  128. _ws();
  129. if (!_eof && _cur == '^') {
  130. i++;
  131. final exp = _unary(); // 右结合
  132. return [PowNode(base: SeqNode(base), exp: SeqNode(exp))];
  133. }
  134. return base;
  135. }
  136. // ---------------------------------------------------------------- 原子
  137. List<ExprNode> _primary() {
  138. _ws();
  139. if (_eof) throw ExprParseException('表达式不完整', i);
  140. final c = _cur;
  141. if (c == '(') {
  142. i++;
  143. final inner = _expr();
  144. _expect(')');
  145. return [GroupNode(SeqNode(inner))];
  146. }
  147. if (c == '{') {
  148. i++;
  149. // 花括号里是**逗号分隔的列表**(`{n, 1, Infinity}`),不是单个表达式。
  150. // 逗号当成字面量节点留着,序列化时拼回去。
  151. final inner = <ExprNode>[];
  152. _ws();
  153. if (!_eof && _cur != '}') {
  154. while (true) {
  155. if (inner.isNotEmpty) inner.add(LitNode(', '));
  156. inner.addAll(_expr());
  157. _ws();
  158. if (!_eof && _cur == ',') {
  159. i++;
  160. continue;
  161. }
  162. break;
  163. }
  164. }
  165. _expect('}');
  166. return [BraceNode(SeqNode(inner))];
  167. }
  168. if (_isDigit(c) || c == '.') return [LitNode(_number())];
  169. if (_isIdentStart(c)) {
  170. final name = _ident();
  171. _ws();
  172. if (!_eof && _cur == '[') {
  173. i++;
  174. final args = _args();
  175. _expect(']');
  176. // ∫ / ∑ / ∏ / lim 的习惯写法认成大运算符,这样复制→粘贴能闭环
  177. final big = _tryBigOp(name, args);
  178. return [big ?? FuncNode(name, args)];
  179. }
  180. return [LitNode(name)];
  181. }
  182. throw ExprParseException('看不懂的字符「$c」', i);
  183. }
  184. /// 认得出来就返回大运算符节点,认不出返回 null(交给普通的 FuncNode)。
  185. static ExprNode? _tryBigOp(String name, List<SeqNode> args) {
  186. if (args.length != 2) return null;
  187. if (name == 'Limit') {
  188. // Limit[f, x -> v]
  189. final parts = _splitTopLevel(args[1], '->');
  190. if (parts == null) return null;
  191. return BigOpNode(
  192. kind: 'Limit',
  193. body: args[0],
  194. variable: parts[0],
  195. lower: parts[1],
  196. upper: SeqNode(),
  197. );
  198. }
  199. if (name != 'Integrate' && name != 'Sum' && name != 'Product') return null;
  200. // Integrate[f, x] —— 不定积分,没有上下限
  201. if (args[1].children.length != 1 || args[1].children.first is! BraceNode) {
  202. return BigOpNode(
  203. kind: name,
  204. body: args[0],
  205. variable: args[1],
  206. lower: SeqNode(),
  207. upper: SeqNode(),
  208. );
  209. }
  210. // Integrate[f, {x, a, b}]
  211. final inner = (args[1].children.first as BraceNode).inner;
  212. final parts = _splitTopLevel(inner, ',');
  213. if (parts == null || parts.length != 3) return null;
  214. return BigOpNode(
  215. kind: name,
  216. body: args[0],
  217. variable: parts[0],
  218. lower: parts[1],
  219. upper: parts[2],
  220. );
  221. }
  222. /// 按顶层出现的 [sep] 字面量把槽切开。没出现就返回 null。
  223. ///
  224. /// 逗号在嵌套结构里(比如 `{Sin[x, y], 1}` 的内层)不算 —— 这里的
  225. /// 子节点本来就是树,所以"顶层"天然成立。
  226. static List<SeqNode>? _splitTopLevel(SeqNode seq, String sep) {
  227. final hits = <int>[
  228. for (var k = 0; k < seq.children.length; k++)
  229. if (seq.children[k] is LitNode &&
  230. ((seq.children[k] as LitNode).text == sep ||
  231. (seq.children[k] as LitNode).text == '$sep '))
  232. k,
  233. ];
  234. if (hits.isEmpty) return null;
  235. if (hits.length == 1 && sep == '->') {
  236. final k = hits.first;
  237. return [
  238. SeqNode(seq.children.sublist(0, k)),
  239. SeqNode(seq.children.sublist(k + 1)),
  240. ];
  241. }
  242. if (sep != ',') return null;
  243. final out = <SeqNode>[];
  244. var start = 0;
  245. for (final k in hits) {
  246. out.add(SeqNode(seq.children.sublist(start, k)));
  247. start = k + 1;
  248. }
  249. out.add(SeqNode(seq.children.sublist(start)));
  250. return out;
  251. }
  252. List<SeqNode> _args() {
  253. final out = <SeqNode>[];
  254. _ws();
  255. if (!_eof && _cur == ']') return out; // Sin[] 这种空参数
  256. while (true) {
  257. final nodes = _expr();
  258. // 空槽序列化出来是 Null;解析时还原成空槽,这样"复制→粘贴"能闭环
  259. final isNull = nodes.length == 1 &&
  260. nodes.first is LitNode &&
  261. (nodes.first as LitNode).text == 'Null';
  262. out.add(isNull ? SeqNode() : SeqNode(nodes));
  263. _ws();
  264. if (!_eof && _cur == ',') {
  265. i++;
  266. continue;
  267. }
  268. break;
  269. }
  270. return out;
  271. }
  272. String _number() {
  273. final start = i;
  274. while (!_eof && (_isDigit(_cur) || _cur == '.')) {
  275. i++;
  276. }
  277. // 科学计数法
  278. if (!_eof && (_cur == 'e' || _cur == 'E')) {
  279. var k = i + 1;
  280. if (k < src.length && (src[k] == '+' || src[k] == '-')) k++;
  281. if (k < src.length && _isDigit(src[k])) {
  282. i = k;
  283. while (!_eof && _isDigit(_cur)) {
  284. i++;
  285. }
  286. }
  287. }
  288. return src.substring(start, i);
  289. }
  290. String _ident() {
  291. final start = i;
  292. while (!_eof && _isIdentPart(_cur)) {
  293. i++;
  294. }
  295. return src.substring(start, i);
  296. }
  297. }
  298. bool _isDigit(String c) {
  299. final u = c.codeUnitAt(0);
  300. return u >= 0x30 && u <= 0x39;
  301. }
  302. bool _isLetter(String c) {
  303. final u = c.codeUnitAt(0);
  304. return (u >= 0x41 && u <= 0x5A) || (u >= 0x61 && u <= 0x7A) || c == '√';
  305. }
  306. bool _isIdentStart(String c) => _isLetter(c) || c == '_' || c == r'$';
  307. bool _isIdentPart(String c) => _isLetter(c) || _isDigit(c) || c == '_' || c == r'$';