expr_parse_test.dart 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  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 doc = parseSymjaExpr('x^2');
  27. expect(doc.children.single, isA<PowNode>());
  28. final frac = parseSymjaExpr('1/3');
  29. expect(frac.children.single, isA<FracNode>());
  30. });
  31. test('分式分母是复合表达式时序列化补括号', () {
  32. expect(rt('1/(x+1)'), '1/(x+1)');
  33. });
  34. test('括号与花括号', () {
  35. expect(rt('(x+1)'), '(x+1)');
  36. expect(rt('2(x+1)'), '2(x+1)'); // 并置乘法原样保留,Symja 认这个写法
  37. expect(rt('{n, 1, Infinity}'), '{n, 1, Infinity}');
  38. expect(rt('Sum[f, {n, 1, Infinity}]'), 'Sum[f, {n, 1, Infinity}]');
  39. });
  40. test('空槽写成 Null,解析回来还是空槽', () {
  41. final t = parseSymjaExpr('Integrate[Null, x]');
  42. final op = t.children.single as BigOpNode;
  43. expect(op.body.isEmpty, isTrue);
  44. expect(symjaOf(t), 'Integrate[Null, x]');
  45. });
  46. test('定积分/求和/极限认成大运算符并能往返', () {
  47. final def = parseSymjaExpr('Integrate[x^2, {x, 0, 1}]');
  48. expect(def.children.single, isA<BigOpNode>());
  49. final op = def.children.single as BigOpNode;
  50. expect(op.kind, 'Integrate');
  51. expect(op.lower.isEmpty, isFalse);
  52. expect(op.upper.isEmpty, isFalse);
  53. expect(symjaOf(def), 'Integrate[x^2, {x, 0, 1}]');
  54. expect(symjaOf(parseSymjaExpr('Sum[f, {n, 1, Infinity}]')),
  55. 'Sum[f, {n, 1, Infinity}]');
  56. expect(symjaOf(parseSymjaExpr('Product[f, {n, 1, m}]')),
  57. 'Product[f, {n, 1, m}]');
  58. expect(symjaOf(parseSymjaExpr('Limit[Sin[x]/x, x -> 0]')),
  59. 'Limit[Sin[x]/x, x -> 0]');
  60. });
  61. });
  62. group('运算符与词法', () {
  63. test('多字符运算符', () {
  64. expect(rt('x->0'), 'x->0');
  65. expect(rt('x^2-1==0'), 'x^2-1==0');
  66. expect(rt('f[x_]:=x^2'), 'f[x_]:=x^2');
  67. });
  68. test('一元负号', () {
  69. expect(rt('-x'), '-x');
  70. expect(rt('-1/2'), '-1/2');
  71. });
  72. test('科学计数法不能被拆成并置乘法', () {
  73. expect(rt('1e-3'), '1e-3');
  74. expect(rt('2.5E10'), '2.5E10');
  75. });
  76. test(r'标识符里的下划线和 $', () {
  77. expect(rt('x_'), 'x_');
  78. expect(rt(r'$ans'), r'$ans');
  79. });
  80. test('空白随便加', () {
  81. expect(rt(' 2 x + 1 '), '2x+1');
  82. });
  83. });
  84. group('不认识就报错,不猜', () {
  85. void bad(String s) {
  86. expect(() => parseSymjaExpr(s), throwsA(isA<ExprParseException>()),
  87. reason: '「$s」应当解析失败');
  88. }
  89. test('不支持的运算符', () {
  90. bad('a@b');
  91. bad('a&b');
  92. bad('x;;y');
  93. bad('a//b');
  94. });
  95. test('括号不配平', () {
  96. bad('(x+1');
  97. bad('Sin[x');
  98. bad('Abs[x]]');
  99. });
  100. test('多余的尾巴', () {
  101. bad('x y z ]');
  102. bad('1+2)');
  103. });
  104. test('空串', () {
  105. bad('');
  106. });
  107. test('报错信息带位置', () {
  108. try {
  109. parseSymjaExpr('x@y');
  110. fail('应当抛异常');
  111. } on ExprParseException catch (e) {
  112. expect(e.position, 1);
  113. expect(e.toString(), contains('第 2 个字符'));
  114. }
  115. });
  116. });
  117. }