expr_parse_test.dart 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. // 粘贴用的解析器:把 Symja 文本转回节点树。
  2. //
  3. // 重点是**往返一致**:复制出来的东西粘回去应当还是同一个表达式;
  4. // 以及**不认识就报错**,绝不猜着解析(猜错等于给用户一个算错的答案)。
  5. import 'package:flutter_test/flutter_test.dart';
  6. import 'package:cal/expr_node.dart';
  7. import 'package:cal/expr_parse.dart';
  8. /// 解析再序列化,应当和输入一模一样。
  9. String rt(String s) => symjaOf(parseSymjaExpr(s));
  10. void main() {
  11. group('基本往返', () {
  12. test('数字与四则', () {
  13. expect(rt('2'), '2');
  14. expect(rt('2+3'), '2+3');
  15. expect(rt('2x+1'), '2x+1');
  16. expect(rt('2*x+1'), '2*x+1');
  17. expect(rt('2.5'), '2.5');
  18. });
  19. test('函数调用', () {
  20. expect(rt('Sin[x]'), 'Sin[x]');
  21. expect(rt('Log[2x+1]'), 'Log[2x+1]');
  22. expect(rt('Integrate[x^2, x]'), 'Integrate[x^2, x]');
  23. expect(rt('Sin[]'), 'Sin[]');
  24. });
  25. test('矩阵能解析成嵌套花括号并往返', () {
  26. final t = parseSymjaExpr('{{1,2},{3,4}}');
  27. final outer = t.children.single as BraceNode;
  28. expect(outer.inner.children.whereType<BraceNode>().length, 2);
  29. // 解析时逗号统一成 ", "(语义不变,Symja 也不在乎空格)
  30. expect(symjaOf(t), '{{1, 2}, {3, 4}}');
  31. expect(symjaOf(parseSymjaExpr('{{1, 2}, {3, 4}}')), '{{1, 2}, {3, 4}}');
  32. });
  33. test('幂与分式做成结构(要竖排/上标显示)', () {
  34. final doc = parseSymjaExpr('x^2');
  35. expect(doc.children.single, isA<PowNode>());
  36. final frac = parseSymjaExpr('1/3');
  37. expect(frac.children.single, isA<FracNode>());
  38. });
  39. test('分式分母是复合表达式时序列化补括号', () {
  40. expect(rt('1/(x+1)'), '1/(x+1)');
  41. });
  42. test('括号与花括号', () {
  43. expect(rt('(x+1)'), '(x+1)');
  44. expect(rt('2(x+1)'), '2(x+1)'); // 并置乘法原样保留,Symja 认这个写法
  45. expect(rt('{n, 1, Infinity}'), '{n, 1, Infinity}');
  46. expect(rt('Sum[f, {n, 1, Infinity}]'), 'Sum[f, {n, 1, Infinity}]');
  47. });
  48. test('空槽写成 Null,解析回来还是空槽', () {
  49. final t = parseSymjaExpr('Integrate[Null, x]');
  50. final op = t.children.single as BigOpNode;
  51. expect(op.body.isEmpty, isTrue);
  52. expect(symjaOf(t), 'Integrate[Null, x]');
  53. });
  54. test('定积分/求和/极限认成大运算符并能往返', () {
  55. final def = parseSymjaExpr('Integrate[x^2, {x, 0, 1}]');
  56. expect(def.children.single, isA<BigOpNode>());
  57. final op = def.children.single as BigOpNode;
  58. expect(op.kind, 'Integrate');
  59. expect(op.lower.isEmpty, isFalse);
  60. expect(op.upper.isEmpty, isFalse);
  61. expect(symjaOf(def), 'Integrate[x^2, {x, 0, 1}]');
  62. expect(symjaOf(parseSymjaExpr('Sum[f, {n, 1, Infinity}]')),
  63. 'Sum[f, {n, 1, Infinity}]');
  64. expect(symjaOf(parseSymjaExpr('Product[f, {n, 1, m}]')),
  65. 'Product[f, {n, 1, m}]');
  66. expect(symjaOf(parseSymjaExpr('Limit[Sin[x]/x, x -> 0]')),
  67. 'Limit[Sin[x]/x, x -> 0]');
  68. });
  69. });
  70. group('运算符与词法', () {
  71. test('多字符运算符', () {
  72. expect(rt('x->0'), 'x->0');
  73. expect(rt('x^2-1==0'), 'x^2-1==0');
  74. expect(rt('f[x_]:=x^2'), 'f[x_]:=x^2');
  75. });
  76. test('一元负号', () {
  77. expect(rt('-x'), '-x');
  78. expect(rt('-1/2'), '-1/2');
  79. });
  80. test('科学计数法不能被拆成并置乘法', () {
  81. expect(rt('1e-3'), '1e-3');
  82. expect(rt('2.5E10'), '2.5E10');
  83. });
  84. test(r'标识符里的下划线和 $', () {
  85. expect(rt('x_'), 'x_');
  86. expect(rt(r'$ans'), r'$ans');
  87. });
  88. test('空白随便加', () {
  89. expect(rt(' 2 x + 1 '), '2x+1');
  90. });
  91. });
  92. group('不认识就报错,不猜', () {
  93. void bad(String s) {
  94. expect(() => parseSymjaExpr(s), throwsA(isA<ExprParseException>()),
  95. reason: '「$s」应当解析失败');
  96. }
  97. test('不支持的运算符', () {
  98. bad('a@b');
  99. bad('a&b');
  100. bad('x;;y');
  101. bad('a//b');
  102. });
  103. test('括号不配平', () {
  104. bad('(x+1');
  105. bad('Sin[x');
  106. bad('Abs[x]]');
  107. });
  108. test('多余的尾巴', () {
  109. bad('x y z ]');
  110. bad('1+2)');
  111. });
  112. test('空串', () {
  113. bad('');
  114. });
  115. test('报错信息带位置', () {
  116. try {
  117. parseSymjaExpr('x@y');
  118. fail('应当抛异常');
  119. } on ExprParseException catch (e) {
  120. expect(e.position, 1);
  121. expect(e.toString(), contains('第 2 个字符'));
  122. }
  123. });
  124. });
  125. }