expr_parse_test.dart 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  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 fn = t.children.single as FuncNode;
  43. expect(fn.args.first.isEmpty, isTrue);
  44. expect(symjaOf(t), 'Integrate[Null, x]');
  45. });
  46. });
  47. group('运算符与词法', () {
  48. test('多字符运算符', () {
  49. expect(rt('x->0'), 'x->0');
  50. expect(rt('x^2-1==0'), 'x^2-1==0');
  51. expect(rt('f[x_]:=x^2'), 'f[x_]:=x^2');
  52. });
  53. test('一元负号', () {
  54. expect(rt('-x'), '-x');
  55. expect(rt('-1/2'), '-1/2');
  56. });
  57. test('科学计数法不能被拆成并置乘法', () {
  58. expect(rt('1e-3'), '1e-3');
  59. expect(rt('2.5E10'), '2.5E10');
  60. });
  61. test(r'标识符里的下划线和 $', () {
  62. expect(rt('x_'), 'x_');
  63. expect(rt(r'$ans'), r'$ans');
  64. });
  65. test('空白随便加', () {
  66. expect(rt(' 2 x + 1 '), '2x+1');
  67. });
  68. });
  69. group('不认识就报错,不猜', () {
  70. void bad(String s) {
  71. expect(() => parseSymjaExpr(s), throwsA(isA<ExprParseException>()),
  72. reason: '「$s」应当解析失败');
  73. }
  74. test('不支持的运算符', () {
  75. bad('a@b');
  76. bad('a&b');
  77. bad('x;;y');
  78. bad('a//b');
  79. });
  80. test('括号不配平', () {
  81. bad('(x+1');
  82. bad('Sin[x');
  83. bad('Abs[x]]');
  84. });
  85. test('多余的尾巴', () {
  86. bad('x y z ]');
  87. bad('1+2)');
  88. });
  89. test('空串', () {
  90. bad('');
  91. });
  92. test('报错信息带位置', () {
  93. try {
  94. parseSymjaExpr('x@y');
  95. fail('应当抛异常');
  96. } on ExprParseException catch (e) {
  97. expect(e.position, 1);
  98. expect(e.toString(), contains('第 2 个字符'));
  99. }
  100. });
  101. });
  102. }