// 粘贴用的解析器:把 Symja 文本转回节点树。 // // 重点是**往返一致**:复制出来的东西粘回去应当还是同一个表达式; // 以及**不认识就报错**,绝不猜着解析(猜错等于给用户一个算错的答案)。 import 'package:flutter_test/flutter_test.dart'; import 'package:cal/expr_node.dart'; import 'package:cal/expr_parse.dart'; /// 解析再序列化,应当和输入一模一样。 String rt(String s) => symjaOf(parseSymjaExpr(s)); void main() { group('基本往返', () { test('数字与四则', () { expect(rt('2'), '2'); expect(rt('2+3'), '2+3'); expect(rt('2x+1'), '2x+1'); expect(rt('2*x+1'), '2*x+1'); expect(rt('2.5'), '2.5'); }); test('函数调用', () { expect(rt('Sin[x]'), 'Sin[x]'); expect(rt('Log[2x+1]'), 'Log[2x+1]'); expect(rt('Integrate[x^2, x]'), 'Integrate[x^2, x]'); expect(rt('Sin[]'), 'Sin[]'); }); test('幂与分式做成结构(要竖排/上标显示)', () { final doc = parseSymjaExpr('x^2'); expect(doc.children.single, isA()); final frac = parseSymjaExpr('1/3'); expect(frac.children.single, isA()); }); test('分式分母是复合表达式时序列化补括号', () { expect(rt('1/(x+1)'), '1/(x+1)'); }); test('括号与花括号', () { expect(rt('(x+1)'), '(x+1)'); expect(rt('2(x+1)'), '2(x+1)'); // 并置乘法原样保留,Symja 认这个写法 expect(rt('{n, 1, Infinity}'), '{n, 1, Infinity}'); expect(rt('Sum[f, {n, 1, Infinity}]'), 'Sum[f, {n, 1, Infinity}]'); }); test('空槽写成 Null,解析回来还是空槽', () { final t = parseSymjaExpr('Integrate[Null, x]'); final fn = t.children.single as FuncNode; expect(fn.args.first.isEmpty, isTrue); expect(symjaOf(t), 'Integrate[Null, x]'); }); }); group('运算符与词法', () { test('多字符运算符', () { expect(rt('x->0'), 'x->0'); expect(rt('x^2-1==0'), 'x^2-1==0'); expect(rt('f[x_]:=x^2'), 'f[x_]:=x^2'); }); test('一元负号', () { expect(rt('-x'), '-x'); expect(rt('-1/2'), '-1/2'); }); test('科学计数法不能被拆成并置乘法', () { expect(rt('1e-3'), '1e-3'); expect(rt('2.5E10'), '2.5E10'); }); test(r'标识符里的下划线和 $', () { expect(rt('x_'), 'x_'); expect(rt(r'$ans'), r'$ans'); }); test('空白随便加', () { expect(rt(' 2 x + 1 '), '2x+1'); }); }); group('不认识就报错,不猜', () { void bad(String s) { expect(() => parseSymjaExpr(s), throwsA(isA()), reason: '「$s」应当解析失败'); } test('不支持的运算符', () { bad('a@b'); bad('a&b'); bad('x;;y'); bad('a//b'); }); test('括号不配平', () { bad('(x+1'); bad('Sin[x'); bad('Abs[x]]'); }); test('多余的尾巴', () { bad('x y z ]'); bad('1+2)'); }); test('空串', () { bad(''); }); test('报错信息带位置', () { try { parseSymjaExpr('x@y'); fail('应当抛异常'); } on ExprParseException catch (e) { expect(e.position, 1); expect(e.toString(), contains('第 2 个字符')); } }); }); }