| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- /// mathtool2 风格表达式 → Symja 语法的兼容转换。
- ///
- /// 为什么需要:工具页那两个绘图工具原来用 `math_expressions`,输入是
- /// `sin(x)`、`ln(x)`、`pi` 这种小写括号写法。统一到 Symja 之后,直接扔掉这套
- /// 语法对习惯了它的人是退步,所以这里保留兼容。
- ///
- /// **界面上会显示转换后的表达式** —— 转换这种事最怕悄悄改错,让用户看得见。
- ///
- /// 只做**保守**的转换,不确定的一律原样保留:
- /// `sin(x)` → `Sin[x]` 函数名(大小写不敏感)+ 紧跟的括号
- /// `pi` → `Pi` 常量
- /// `e` → `E` 仅当它独立成词、且不与数字相邻(避免毁掉 `1e-3`)
- /// `2x` → `2*x` 数字紧跟字母时补乘号
- /// `(x+1)*2` → 原样 普通括号不是函数调用,不动
- library;
- /// 小写函数名 → Symja 名称。
- const _functions = <String, String>{
- 'sin': 'Sin',
- 'cos': 'Cos',
- 'tan': 'Tan',
- 'cot': 'Cot',
- 'sec': 'Sec',
- 'csc': 'Csc',
- 'asin': 'ArcSin',
- 'acos': 'ArcCos',
- 'atan': 'ArcTan',
- 'arcsin': 'ArcSin',
- 'arccos': 'ArcCos',
- 'arctan': 'ArcTan',
- 'sinh': 'Sinh',
- 'cosh': 'Cosh',
- 'tanh': 'Tanh',
- 'ln': 'Log',
- 'log': 'Log',
- 'lg': 'Log10',
- 'sqrt': 'Sqrt',
- 'abs': 'Abs',
- 'exp': 'Exp',
- 'floor': 'Floor',
- 'ceil': 'Ceiling',
- 'round': 'Round',
- 'sign': 'Sign',
- 'max': 'Max',
- 'min': 'Min',
- 'mod': 'Mod',
- };
- const _constants = <String, String>{
- 'pi': 'Pi',
- 'inf': 'Infinity',
- 'infinity': 'Infinity',
- };
- bool _isLetter(String c) {
- final u = c.codeUnitAt(0);
- return (u >= 0x41 && u <= 0x5A) || (u >= 0x61 && u <= 0x7A) || c == '_';
- }
- bool _isDigit(String c) {
- if (c.isEmpty) return false; // 空串不是数字 —— 边界处会传进来,别让它崩
- final u = c.codeUnitAt(0);
- return u >= 0x30 && u <= 0x39;
- }
- bool _isIdentChar(String c) => _isLetter(c) || _isDigit(c);
- /// 从 [from] 起看是不是"指数部分":可选正负号后紧跟数字。
- bool _looksLikeExponent(String s, int from) {
- var k = from;
- if (k < s.length && (s[k] == '+' || s[k] == '-')) k++;
- return k < s.length && _isDigit(s[k]);
- }
- /// 把 [input] 转换成 Symja 语法。无法识别的地方原样保留。
- String toSymjaSyntax(String input) {
- final out = StringBuffer();
- // 括号栈:记住每个左括号应该被闭合成什么
- final closers = <String>[];
- var i = 0;
- while (i < input.length) {
- final c = input[i];
- if (_isLetter(c)) {
- var j = i;
- while (j < input.length && _isIdentChar(input[j])) {
- j++;
- }
- final word = input.substring(i, j);
- // 跳过空白看后面是不是紧跟 '('
- var k = j;
- while (k < input.length && input[k] == ' ') {
- k++;
- }
- final isCall = k < input.length && input[k] == '(';
- final fn = _functions[word.toLowerCase()];
- if (isCall && fn != null) {
- out.write(fn);
- out.write('[');
- closers.add(']');
- i = k + 1; // 吃掉 '('
- continue;
- }
- // 常量:'e' 要额外小心——别把 1e-3 这种科学计数法拆了
- final lower = word.toLowerCase();
- if (lower == 'e') {
- final prev = out.isEmpty ? '' : out.toString()[out.length - 1];
- final next = j < input.length ? input[j] : '';
- if (!_isDigit(prev) && !_isDigit(next)) { // 空串会被 _isDigit 判成非数字,正是想要的
- out.write('E');
- i = j;
- continue;
- }
- } else if (_constants[lower] != null) {
- out.write(_constants[lower]);
- i = j;
- continue;
- }
- out.write(word);
- i = j;
- continue;
- }
- if (c == '(') {
- out.write('(');
- closers.add(')');
- i++;
- continue;
- }
- if (c == ')') {
- out.write(closers.isNotEmpty ? closers.removeLast() : ')');
- i++;
- continue;
- }
- // `2x` -> `2*x`:数字后面紧跟字母时补乘号
- if (_isDigit(c)) {
- var j = i;
- while (j < input.length && (_isDigit(input[j]) || input[j] == '.')) {
- j++;
- }
- out.write(input.substring(i, j));
- if (j < input.length && _isLetter(input[j])) {
- // 例外:`1e-3`、`2.5E10` 是科学计数法,补了乘号就毁了
- final isExponent = (input[j] == 'e' || input[j] == 'E') &&
- _looksLikeExponent(input, j + 1);
- if (!isExponent) out.write('*');
- }
- i = j;
- continue;
- }
- out.write(c);
- i++;
- }
- return out.toString();
- }
- /// 转换过(即与原串不同)才返回新串,否则返回 null —— 界面据此决定要不要提示。
- String? symjaRewriteOf(String input) {
- final converted = toSymjaSyntax(input);
- return converted == input ? null : converted;
- }
|