♻️ New calculator pending to replace math_expressions
This commit is contained in:
		
							
								
								
									
										539
									
								
								lib/calculator.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										539
									
								
								lib/calculator.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,539 @@ | ||||
| // === 在 abstract class Expr 中添加声明 === | ||||
| import 'dart:math' show sqrt, cos, sin, tan; | ||||
|  | ||||
| abstract class Expr { | ||||
|   Expr simplify(); | ||||
|  | ||||
|   /// 新增:对表达式进行“求值/数值化”——尽可能把可算的部分算出来 | ||||
|   Expr evaluate(); | ||||
|  | ||||
|   @override | ||||
|   String toString(); | ||||
|  | ||||
|   MulExpr operator *(Expr other) => MulExpr(this, other); | ||||
|   AddExpr operator +(Expr other) => AddExpr(this, other); | ||||
|   SubExpr operator -(Expr other) => SubExpr(this, other); | ||||
|   DivExpr operator /(Expr other) => DivExpr(this, other); | ||||
| } | ||||
|  | ||||
| // === IntExpr === | ||||
| class IntExpr extends Expr { | ||||
|   final int value; | ||||
|   IntExpr(this.value); | ||||
|  | ||||
|   @override | ||||
|   Expr simplify() => this; | ||||
|  | ||||
|   @override | ||||
|   Expr evaluate() => this; | ||||
|  | ||||
|   @override | ||||
|   String toString() => value.toString(); | ||||
| } | ||||
|  | ||||
| // === DoubleExpr === | ||||
| class DoubleExpr extends Expr { | ||||
|   final double value; | ||||
|   DoubleExpr(this.value); | ||||
|  | ||||
|   @override | ||||
|   Expr simplify() => this; | ||||
|  | ||||
|   @override | ||||
|   Expr evaluate() => this; | ||||
|  | ||||
|   @override | ||||
|   String toString() => value.toString(); | ||||
| } | ||||
|  | ||||
| // === FractionExpr.evaluate === | ||||
| class FractionExpr extends Expr { | ||||
|   final int numerator; | ||||
|   final int denominator; | ||||
|  | ||||
|   FractionExpr(this.numerator, this.denominator) { | ||||
|     if (denominator == 0) throw Exception("分母不能为0"); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Expr simplify() { | ||||
|     int g = _gcd(numerator.abs(), denominator.abs()); | ||||
|     int n = numerator ~/ g; | ||||
|     int d = denominator ~/ g; | ||||
|  | ||||
|     // 分母负数转移到分子 | ||||
|     if (d < 0) { | ||||
|       n = -n; | ||||
|       d = -d; | ||||
|     } | ||||
|  | ||||
|     if (d == 1) return IntExpr(n); // 化简成整数 | ||||
|     return FractionExpr(n, d); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Expr evaluate() => simplify(); | ||||
|  | ||||
|   @override | ||||
|   String toString() => "$numerator/$denominator"; | ||||
| } | ||||
|  | ||||
| // === AddExpr.evaluate: 把可算的合并(整数、分数、以及同类 sqrt 项) === | ||||
| class AddExpr extends Expr { | ||||
|   final Expr left, right; | ||||
|   AddExpr(this.left, this.right); | ||||
|  | ||||
|   @override | ||||
|   Expr simplify() { | ||||
|     var l = left.simplify(); | ||||
|     var r = right.simplify(); | ||||
|  | ||||
|     if (l is FractionExpr && r is FractionExpr) { | ||||
|       return FractionExpr( | ||||
|         l.numerator * r.denominator + r.numerator * l.denominator, | ||||
|         l.denominator * r.denominator, | ||||
|       ).simplify(); | ||||
|     } | ||||
|     if (l is IntExpr && r is FractionExpr) { | ||||
|       return FractionExpr( | ||||
|         l.value * r.denominator + r.numerator, | ||||
|         r.denominator, | ||||
|       ).simplify(); | ||||
|     } | ||||
|     if (l is FractionExpr && r is IntExpr) { | ||||
|       return FractionExpr( | ||||
|         l.numerator + r.value * l.denominator, | ||||
|         l.denominator, | ||||
|       ).simplify(); | ||||
|     } | ||||
|  | ||||
|     return AddExpr(l, r); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Expr evaluate() { | ||||
|     var l = left.evaluate(); | ||||
|     var r = right.evaluate(); | ||||
|  | ||||
|     // 纯整数相加 -> 整数 | ||||
|     if (l is IntExpr && r is IntExpr) { | ||||
|       return IntExpr(l.value + r.value); | ||||
|     } | ||||
|  | ||||
|     // 分数相加 / 分数与整数相加 | ||||
|     if (l is FractionExpr && r is FractionExpr) { | ||||
|       return FractionExpr( | ||||
|         l.numerator * r.denominator + r.numerator * l.denominator, | ||||
|         l.denominator * r.denominator, | ||||
|       ).simplify(); | ||||
|     } | ||||
|     if (l is IntExpr && r is FractionExpr) { | ||||
|       return FractionExpr( | ||||
|         l.value * r.denominator + r.numerator, | ||||
|         r.denominator, | ||||
|       ).simplify(); | ||||
|     } | ||||
|     if (l is FractionExpr && r is IntExpr) { | ||||
|       return FractionExpr( | ||||
|         l.numerator + r.value * l.denominator, | ||||
|         l.denominator, | ||||
|       ).simplify(); | ||||
|     } | ||||
|  | ||||
|     // 合并同类的 sqrt 项: a*sqrt(X) + b*sqrt(X) = (a+b)*sqrt(X) | ||||
|     var a = _asSqrtTerm(l); | ||||
|     var b = _asSqrtTerm(r); | ||||
|     if (a != null && b != null && a.inner.toString() == b.inner.toString()) { | ||||
|       return MulExpr(IntExpr(a.coef + b.coef), SqrtExpr(a.inner)).simplify(); | ||||
|     } | ||||
|  | ||||
|     return AddExpr(l, r); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String toString() => "($left + $right)"; | ||||
| } | ||||
|  | ||||
| // === SubExpr.evaluate 类似 AddExpr,但做减法 === | ||||
| class SubExpr extends Expr { | ||||
|   final Expr left, right; | ||||
|   SubExpr(this.left, this.right); | ||||
|  | ||||
|   @override | ||||
|   Expr simplify() { | ||||
|     var l = left.simplify(); | ||||
|     var r = right.simplify(); | ||||
|  | ||||
|     if (l is IntExpr && r is IntExpr) { | ||||
|       return IntExpr(l.value - r.value); | ||||
|     } | ||||
|     if (l is FractionExpr && r is FractionExpr) { | ||||
|       return FractionExpr( | ||||
|         l.numerator * r.denominator - r.numerator * l.denominator, | ||||
|         l.denominator * r.denominator, | ||||
|       ).simplify(); | ||||
|     } | ||||
|     return SubExpr(l, r); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Expr evaluate() { | ||||
|     var l = left.evaluate(); | ||||
|     var r = right.evaluate(); | ||||
|  | ||||
|     if (l is IntExpr && r is IntExpr) { | ||||
|       return IntExpr(l.value - r.value); | ||||
|     } | ||||
|     if (l is FractionExpr && r is FractionExpr) { | ||||
|       return FractionExpr( | ||||
|         l.numerator * r.denominator - r.numerator * l.denominator, | ||||
|         l.denominator * r.denominator, | ||||
|       ).simplify(); | ||||
|     } | ||||
|     if (l is IntExpr && r is FractionExpr) { | ||||
|       return FractionExpr( | ||||
|         l.value * r.denominator - r.numerator, | ||||
|         r.denominator, | ||||
|       ).simplify(); | ||||
|     } | ||||
|     if (l is FractionExpr && r is IntExpr) { | ||||
|       return FractionExpr( | ||||
|         l.numerator - r.value * l.denominator, | ||||
|         l.denominator, | ||||
|       ).simplify(); | ||||
|     } | ||||
|  | ||||
|     // 处理同类 sqrt 项: a*sqrt(X) - b*sqrt(X) = (a-b)*sqrt(X) | ||||
|     var a = _asSqrtTerm(l); | ||||
|     var b = _asSqrtTerm(r); | ||||
|     if (a != null && b != null && a.inner.toString() == b.inner.toString()) { | ||||
|       return MulExpr(IntExpr(a.coef - b.coef), SqrtExpr(a.inner)).simplify(); | ||||
|     } | ||||
|  | ||||
|     return SubExpr(l, r); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String toString() => "($left - $right)"; | ||||
| } | ||||
|  | ||||
| // === MulExpr.evaluate === | ||||
| class MulExpr extends Expr { | ||||
|   final Expr left, right; | ||||
|   MulExpr(this.left, this.right); | ||||
|  | ||||
|   @override | ||||
|   Expr simplify() { | ||||
|     var l = left.simplify(); | ||||
|     var r = right.simplify(); | ||||
|  | ||||
|     if (l is IntExpr && l.value == 1) return r; | ||||
|     if (r is IntExpr && r.value == 1) return l; | ||||
|     if (l is IntExpr && l.value == -1) return SubExpr(IntExpr(0), r).simplify(); | ||||
|     if (r is IntExpr && r.value == -1) return SubExpr(IntExpr(0), l).simplify(); | ||||
|  | ||||
|     if (l is IntExpr && r is IntExpr) { | ||||
|       return IntExpr(l.value * r.value); | ||||
|     } | ||||
|     if (l is FractionExpr && r is IntExpr) { | ||||
|       return FractionExpr(l.numerator * r.value, l.denominator).simplify(); | ||||
|     } | ||||
|     if (l is IntExpr && r is FractionExpr) { | ||||
|       return FractionExpr(l.value * r.numerator, r.denominator).simplify(); | ||||
|     } | ||||
|     if (l is FractionExpr && r is FractionExpr) { | ||||
|       return FractionExpr( | ||||
|         l.numerator * r.numerator, | ||||
|         l.denominator * r.denominator, | ||||
|       ).simplify(); | ||||
|     } | ||||
|     if (l is SqrtExpr && r is FractionExpr) { | ||||
|       return FractionExpr(1, r.denominator).simplify() * | ||||
|           MulExpr(l, IntExpr(r.numerator)).simplify(); | ||||
|     } | ||||
|  | ||||
|     return MulExpr(l, r); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Expr evaluate() { | ||||
|     var l = left.evaluate(); | ||||
|     var r = right.evaluate(); | ||||
|  | ||||
|     if (l is IntExpr && l.value == 1) return r; | ||||
|     if (r is IntExpr && r.value == 1) return l; | ||||
|     if (l is IntExpr && l.value == -1) return SubExpr(IntExpr(0), r).simplify(); | ||||
|     if (r is IntExpr && r.value == -1) return SubExpr(IntExpr(0), l).simplify(); | ||||
|  | ||||
|     if (l is IntExpr && r is IntExpr) { | ||||
|       return IntExpr(l.value * r.value); | ||||
|     } | ||||
|     if (l is FractionExpr && r is IntExpr) { | ||||
|       return FractionExpr(l.numerator * r.value, l.denominator).simplify(); | ||||
|     } | ||||
|     if (l is IntExpr && r is FractionExpr) { | ||||
|       return FractionExpr(l.value * r.numerator, r.denominator).simplify(); | ||||
|     } | ||||
|     if (l is FractionExpr && r is FractionExpr) { | ||||
|       return FractionExpr( | ||||
|         l.numerator * r.numerator, | ||||
|         l.denominator * r.denominator, | ||||
|       ).simplify(); | ||||
|     } | ||||
|  | ||||
|     // sqrt * sqrt: sqrt(a)*sqrt(a) = a | ||||
|     if (l is SqrtExpr && | ||||
|         r is SqrtExpr && | ||||
|         l.inner.toString() == r.inner.toString()) { | ||||
|       return l.inner.simplify(); | ||||
|     } | ||||
|  | ||||
|     // int * sqrt -> 保留形式,之后 simplify() 再处理约分 | ||||
|     if ((l is IntExpr && r is SqrtExpr) || (l is SqrtExpr && r is IntExpr)) { | ||||
|       return MulExpr(l, r).simplify(); | ||||
|     } | ||||
|  | ||||
|     return MulExpr(l, r); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String toString() => "($left * $right)"; | ||||
| } | ||||
|  | ||||
| // === DivExpr.evaluate === | ||||
| class DivExpr extends Expr { | ||||
|   final Expr left, right; | ||||
|   DivExpr(this.left, this.right); | ||||
|  | ||||
|   @override | ||||
|   Expr simplify() { | ||||
|     var l = left.simplify(); | ||||
|     var r = right.simplify(); | ||||
|  | ||||
|     if (l is IntExpr && r is IntExpr) { | ||||
|       return FractionExpr(l.value, r.value).simplify(); | ||||
|     } | ||||
|     if (l is FractionExpr && r is IntExpr) { | ||||
|       return FractionExpr(l.numerator, l.denominator * r.value).simplify(); | ||||
|     } | ||||
|     if (l is IntExpr && r is FractionExpr) { | ||||
|       return FractionExpr(l.value * r.denominator, r.numerator).simplify(); | ||||
|     } | ||||
|     if (l is FractionExpr && r is FractionExpr) { | ||||
|       return FractionExpr( | ||||
|         l.numerator * r.denominator, | ||||
|         l.denominator * r.numerator, | ||||
|       ).simplify(); | ||||
|     } | ||||
|     if (l is MulExpr && | ||||
|         l.left is IntExpr && | ||||
|         l.right is SqrtExpr && | ||||
|         r is IntExpr) { | ||||
|       int coeff = (l.left as IntExpr).value; | ||||
|       int denom = r.value; | ||||
|       int g = _gcd(coeff.abs(), denom.abs()); | ||||
|       return MulExpr( | ||||
|         IntExpr(coeff ~/ g), | ||||
|         DivExpr(l.right, IntExpr(denom ~/ g)).simplify(), | ||||
|       ).simplify(); | ||||
|     } | ||||
|     return DivExpr(l, r); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Expr evaluate() { | ||||
|     var l = left.evaluate(); | ||||
|     var r = right.evaluate(); | ||||
|  | ||||
|     if (l is IntExpr && r is IntExpr) { | ||||
|       return FractionExpr(l.value, r.value).simplify(); | ||||
|     } | ||||
|     if (l is FractionExpr && r is IntExpr) { | ||||
|       return FractionExpr(l.numerator, l.denominator * r.value).simplify(); | ||||
|     } | ||||
|     if (l is IntExpr && r is FractionExpr) { | ||||
|       return FractionExpr(l.value * r.denominator, r.numerator).simplify(); | ||||
|     } | ||||
|     if (l is FractionExpr && r is FractionExpr) { | ||||
|       return FractionExpr( | ||||
|         l.numerator * r.denominator, | ||||
|         l.denominator * r.numerator, | ||||
|       ).simplify(); | ||||
|     } | ||||
|  | ||||
|     // handle (k * sqrt(X)) / d 约分 | ||||
|     if (l is MulExpr && | ||||
|         l.left is IntExpr && | ||||
|         l.right is SqrtExpr && | ||||
|         r is IntExpr) { | ||||
|       int coeff = (l.left as IntExpr).value; | ||||
|       int denom = r.value; | ||||
|       int g = _gcd(coeff.abs(), denom.abs()); | ||||
|       return MulExpr( | ||||
|         IntExpr(coeff ~/ g), | ||||
|         DivExpr(l.right, IntExpr(denom ~/ g)).evaluate(), | ||||
|       ).evaluate(); | ||||
|     } | ||||
|  | ||||
|     return DivExpr(l, r); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String toString() => "($left / $right)"; | ||||
| } | ||||
|  | ||||
| // === SqrtExpr.evaluate === | ||||
| class SqrtExpr extends Expr { | ||||
|   final Expr inner; | ||||
|   SqrtExpr(this.inner); | ||||
|  | ||||
|   @override | ||||
|   Expr simplify() { | ||||
|     var i = inner.simplify(); | ||||
|     if (i is IntExpr) { | ||||
|       int n = i.value; | ||||
|       int root = sqrt(n).floor(); | ||||
|       if (root * root == n) { | ||||
|         return IntExpr(root); // 完全平方数 | ||||
|       } | ||||
|       // 尝试拆分 sqrt,比如 sqrt(8) = 2*sqrt(2) | ||||
|       for (int k = root; k > 1; k--) { | ||||
|         if (n % (k * k) == 0) { | ||||
|           return MulExpr( | ||||
|             IntExpr(k), | ||||
|             SqrtExpr(IntExpr(n ~/ (k * k))), | ||||
|           ).simplify(); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return SqrtExpr(i); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Expr evaluate() { | ||||
|     var i = inner.evaluate(); | ||||
|     if (i is IntExpr) { | ||||
|       int n = i.value; | ||||
|       int root = sqrt(n).floor(); | ||||
|       if (root * root == n) return IntExpr(root); | ||||
|       // 拆平方因子并返回 k * sqrt(remain) | ||||
|       for (int k = root; k > 1; k--) { | ||||
|         if (n % (k * k) == 0) { | ||||
|           return MulExpr( | ||||
|             IntExpr(k), | ||||
|             SqrtExpr(IntExpr(n ~/ (k * k))), | ||||
|           ).evaluate(); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return SqrtExpr(i); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String toString() => "sqrt($inner)"; | ||||
| } | ||||
|  | ||||
| // === CosExpr === | ||||
| class CosExpr extends Expr { | ||||
|   final Expr inner; | ||||
|   CosExpr(this.inner); | ||||
|  | ||||
|   @override | ||||
|   Expr simplify() => CosExpr(inner.simplify()); | ||||
|  | ||||
|   @override | ||||
|   Expr evaluate() { | ||||
|     var i = inner.evaluate(); | ||||
|     if (i is IntExpr) { | ||||
|       return DoubleExpr(cos(i.value.toDouble())); | ||||
|     } | ||||
|     if (i is FractionExpr) { | ||||
|       return DoubleExpr(cos(i.numerator / i.denominator)); | ||||
|     } | ||||
|     if (i is DoubleExpr) { | ||||
|       return DoubleExpr(cos(i.value)); | ||||
|     } | ||||
|     return CosExpr(i); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String toString() => "cos($inner)"; | ||||
| } | ||||
|  | ||||
| // === SinExpr === | ||||
| class SinExpr extends Expr { | ||||
|   final Expr inner; | ||||
|   SinExpr(this.inner); | ||||
|  | ||||
|   @override | ||||
|   Expr simplify() => SinExpr(inner.simplify()); | ||||
|  | ||||
|   @override | ||||
|   Expr evaluate() { | ||||
|     var i = inner.evaluate(); | ||||
|     if (i is IntExpr) { | ||||
|       return DoubleExpr(sin(i.value.toDouble())); | ||||
|     } | ||||
|     if (i is FractionExpr) { | ||||
|       return DoubleExpr(sin(i.numerator / i.denominator)); | ||||
|     } | ||||
|     if (i is DoubleExpr) { | ||||
|       return DoubleExpr(sin(i.value)); | ||||
|     } | ||||
|     return SinExpr(i); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String toString() => "sin($inner)"; | ||||
| } | ||||
|  | ||||
| // === TanExpr === | ||||
| class TanExpr extends Expr { | ||||
|   final Expr inner; | ||||
|   TanExpr(this.inner); | ||||
|  | ||||
|   @override | ||||
|   Expr simplify() => TanExpr(inner.simplify()); | ||||
|  | ||||
|   @override | ||||
|   Expr evaluate() { | ||||
|     var i = inner.evaluate(); | ||||
|     if (i is IntExpr) { | ||||
|       return DoubleExpr(tan(i.value.toDouble())); | ||||
|     } | ||||
|     if (i is FractionExpr) { | ||||
|       return DoubleExpr(tan(i.numerator / i.denominator)); | ||||
|     } | ||||
|     if (i is DoubleExpr) { | ||||
|       return DoubleExpr(tan(i.value)); | ||||
|     } | ||||
|     return TanExpr(i); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String toString() => "tan($inner)"; | ||||
| } | ||||
|  | ||||
| // === 辅助:识别 a * sqrt(X) 形式 === | ||||
| class _SqrtTerm { | ||||
|   final int coef; | ||||
|   final Expr inner; | ||||
|   _SqrtTerm(this.coef, this.inner); | ||||
| } | ||||
|  | ||||
| _SqrtTerm? _asSqrtTerm(Expr e) { | ||||
|   if (e is SqrtExpr) return _SqrtTerm(1, e.inner); | ||||
|   if (e is MulExpr) { | ||||
|     // 可能为 Int * Sqrt or Sqrt * Int | ||||
|     if (e.left is IntExpr && e.right is SqrtExpr) { | ||||
|       return _SqrtTerm((e.left as IntExpr).value, (e.right as SqrtExpr).inner); | ||||
|     } | ||||
|     if (e.right is IntExpr && e.left is SqrtExpr) { | ||||
|       return _SqrtTerm((e.right as IntExpr).value, (e.left as SqrtExpr).inner); | ||||
|     } | ||||
|   } | ||||
|   return null; | ||||
| } | ||||
|  | ||||
| /// 辗转相除法求 gcd | ||||
| int _gcd(int a, int b) => b == 0 ? a : _gcd(b, a % b); | ||||
							
								
								
									
										111
									
								
								lib/parser.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								lib/parser.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| import 'package:simple_math_calc/calculator.dart'; | ||||
|  | ||||
| class Parser { | ||||
|   final String input; | ||||
|   int pos = 0; | ||||
|  | ||||
|   Parser(this.input); | ||||
|  | ||||
|   bool get isEnd => pos >= input.length; | ||||
|   String get current => isEnd ? '' : input[pos]; | ||||
|  | ||||
|   void eat() => pos++; | ||||
|  | ||||
|   void skipSpaces() { | ||||
|     while (!isEnd && input[pos] == ' ') { | ||||
|       eat(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Expr parse() => parseAdd(); | ||||
|  | ||||
|   Expr parseAdd() { | ||||
|     var expr = parseMul(); | ||||
|     skipSpaces(); | ||||
|     while (!isEnd && (current == '+' || current == '-')) { | ||||
|       var op = current; | ||||
|       eat(); | ||||
|       var right = parseMul(); | ||||
|       expr = op == '+' ? AddExpr(expr, right) : SubExpr(expr, right); | ||||
|       skipSpaces(); | ||||
|     } | ||||
|     return expr; | ||||
|   } | ||||
|  | ||||
|   Expr parseMul() { | ||||
|     var expr = parseAtom(); | ||||
|     skipSpaces(); | ||||
|     while (!isEnd && (current == '*' || current == '/')) { | ||||
|       var op = current; | ||||
|       eat(); | ||||
|       var right = parseAtom(); | ||||
|       if (op == '*') { | ||||
|         expr = MulExpr(expr, right); | ||||
|       } else { | ||||
|         expr = DivExpr(expr, right); | ||||
|       } | ||||
|       skipSpaces(); | ||||
|     } | ||||
|     return expr; | ||||
|   } | ||||
|  | ||||
|   Expr parseAtom() { | ||||
|     skipSpaces(); | ||||
|     if (current == '(') { | ||||
|       eat(); | ||||
|       var expr = parse(); | ||||
|       if (current != ')') throw Exception("缺少 )"); | ||||
|       eat(); | ||||
|       return expr; | ||||
|     } | ||||
|  | ||||
|     if (input.startsWith("sqrt", pos)) { | ||||
|       pos += 4; | ||||
|       if (current != '(') throw Exception("sqrt 缺少 ("); | ||||
|       eat(); | ||||
|       var inner = parse(); | ||||
|       if (current != ')') throw Exception("sqrt 缺少 )"); | ||||
|       eat(); | ||||
|       return SqrtExpr(inner); | ||||
|     } | ||||
|  | ||||
|     if (input.startsWith("cos", pos)) { | ||||
|       pos += 3; | ||||
|       if (current != '(') throw Exception("cos 缺少 ("); | ||||
|       eat(); | ||||
|       var inner = parse(); | ||||
|       if (current != ')') throw Exception("cos 缺少 )"); | ||||
|       eat(); | ||||
|       return CosExpr(inner); | ||||
|     } | ||||
|  | ||||
|     if (input.startsWith("sin", pos)) { | ||||
|       pos += 3; | ||||
|       if (current != '(') throw Exception("sin 缺少 ("); | ||||
|       eat(); | ||||
|       var inner = parse(); | ||||
|       if (current != ')') throw Exception("sin 缺少 )"); | ||||
|       eat(); | ||||
|       return SinExpr(inner); | ||||
|     } | ||||
|  | ||||
|     if (input.startsWith("tan", pos)) { | ||||
|       pos += 3; | ||||
|       if (current != '(') throw Exception("tan 缺少 ("); | ||||
|       eat(); | ||||
|       var inner = parse(); | ||||
|       if (current != ')') throw Exception("tan 缺少 )"); | ||||
|       eat(); | ||||
|       return TanExpr(inner); | ||||
|     } | ||||
|  | ||||
|     // 解析整数 | ||||
|     var buf = ''; | ||||
|     while (!isEnd && RegExp(r'\d').hasMatch(current)) { | ||||
|       buf += current; | ||||
|       eat(); | ||||
|     } | ||||
|     if (buf.isEmpty) throw Exception("无法解析: $current"); | ||||
|     return IntExpr(int.parse(buf)); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										228
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										228
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -1,6 +1,22 @@ | ||||
| # Generated by pub | ||||
| # See https://dart.dev/tools/pub/glossary#lockfile | ||||
| packages: | ||||
|   _fe_analyzer_shared: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: _fe_analyzer_shared | ||||
|       sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "85.0.0" | ||||
|   analyzer: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: analyzer | ||||
|       sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "7.7.1" | ||||
|   ansicolor: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -57,6 +73,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.0.4" | ||||
|   cli_config: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: cli_config | ||||
|       sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.2.0" | ||||
|   cli_util: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -81,6 +105,22 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.19.1" | ||||
|   convert: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: convert | ||||
|       sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.1.2" | ||||
|   coverage: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: coverage | ||||
|       sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.15.0" | ||||
|   crypto: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -121,6 +161,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.1.4" | ||||
|   file: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: file | ||||
|       sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "7.0.1" | ||||
|   flutter: | ||||
|     dependency: "direct main" | ||||
|     description: flutter | ||||
| @@ -176,14 +224,30 @@ packages: | ||||
|     description: flutter | ||||
|     source: sdk | ||||
|     version: "0.0.0" | ||||
|   frontend_server_client: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: frontend_server_client | ||||
|       sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "4.0.0" | ||||
|   glob: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: glob | ||||
|       sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.1.3" | ||||
|   go_router: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: go_router | ||||
|       sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3 | ||||
|       sha256: eb059dfe59f08546e9787f895bd01652076f996bcbf485a8609ef990419ad227 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "14.8.1" | ||||
|     version: "16.2.1" | ||||
|   google_fonts: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -208,6 +272,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.5.0" | ||||
|   http_multi_server: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: http_multi_server | ||||
|       sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.2.2" | ||||
|   http_parser: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -224,6 +296,22 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "4.5.4" | ||||
|   io: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: io | ||||
|       sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.0.5" | ||||
|   js: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: js | ||||
|       sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.7.2" | ||||
|   json_annotation: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -312,6 +400,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.16.0" | ||||
|   mime: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: mime | ||||
|       sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.0.0" | ||||
|   nested: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -320,6 +416,22 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.0.0" | ||||
|   node_preamble: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: node_preamble | ||||
|       sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.0.2" | ||||
|   package_config: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: package_config | ||||
|       sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.2.0" | ||||
|   path: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -408,6 +520,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.1.8" | ||||
|   pool: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: pool | ||||
|       sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.5.1" | ||||
|   posix: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -424,6 +544,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "6.1.5+1" | ||||
|   pub_semver: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: pub_semver | ||||
|       sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.2.0" | ||||
|   rational: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -432,11 +560,59 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.2.3" | ||||
|   shelf: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: shelf | ||||
|       sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.4.2" | ||||
|   shelf_packages_handler: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: shelf_packages_handler | ||||
|       sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.0.2" | ||||
|   shelf_static: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: shelf_static | ||||
|       sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.1.3" | ||||
|   shelf_web_socket: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: shelf_web_socket | ||||
|       sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.0.0" | ||||
|   sky_engine: | ||||
|     dependency: transitive | ||||
|     description: flutter | ||||
|     source: sdk | ||||
|     version: "0.0.0" | ||||
|   source_map_stack_trace: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: source_map_stack_trace | ||||
|       sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.1.2" | ||||
|   source_maps: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: source_maps | ||||
|       sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.10.13" | ||||
|   source_span: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -477,6 +653,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.2.2" | ||||
|   test: | ||||
|     dependency: "direct dev" | ||||
|     description: | ||||
|       name: test | ||||
|       sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.26.2" | ||||
|   test_api: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -485,6 +669,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.7.6" | ||||
|   test_core: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: test_core | ||||
|       sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.6.11" | ||||
|   tuple: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -613,6 +805,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "15.0.2" | ||||
|   watcher: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: watcher | ||||
|       sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.1.3" | ||||
|   web: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -621,6 +821,30 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.1.1" | ||||
|   web_socket: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: web_socket | ||||
|       sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.0.1" | ||||
|   web_socket_channel: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: web_socket_channel | ||||
|       sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.0.3" | ||||
|   webkit_inspection_protocol: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: webkit_inspection_protocol | ||||
|       sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.2.1" | ||||
|   xdg_directories: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
| @@ -37,8 +37,8 @@ dependencies: | ||||
|   math_expressions: ^3.1.0 | ||||
|   latext: ^0.5.1 | ||||
|   google_fonts: ^6.3.1 | ||||
|   go_router: ^14.2.0 | ||||
|   url_launcher: ^6.3.0 | ||||
|   go_router: ^16.2.1 | ||||
|   url_launcher: ^6.3.2 | ||||
|   rational: ^2.2.3 | ||||
|  | ||||
| dev_dependencies: | ||||
| @@ -53,6 +53,7 @@ dev_dependencies: | ||||
|   flutter_lints: ^6.0.0 | ||||
|   flutter_native_splash: ^2.4.6 | ||||
|   flutter_launcher_icons: ^0.14.4 | ||||
|   test: ^1.26.2 | ||||
|  | ||||
| # For information on the generic Dart part of this file, see the | ||||
| # following page: https://dart.dev/tools/pub/pubspec | ||||
|   | ||||
							
								
								
									
										104
									
								
								test/calculator_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								test/calculator_test.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| import 'package:simple_math_calc/parser.dart'; | ||||
| import 'package:test/test.dart'; | ||||
|  | ||||
| void main() { | ||||
|   group('整数', () { | ||||
|     test('加法', () { | ||||
|       var expr = Parser("2 + 3").parse(); | ||||
|       expect(expr.evaluate().toString(), "5"); | ||||
|     }); | ||||
|  | ||||
|     test('乘法', () { | ||||
|       var expr = Parser("4 * 5").parse(); | ||||
|       expect(expr.evaluate().toString(), "20"); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   group('分数', () { | ||||
|     test('简单分数', () { | ||||
|       var expr = Parser("1/2").parse(); | ||||
|       expect(expr.evaluate().toString(), "1/2"); | ||||
|     }); | ||||
|  | ||||
|     test('分数加法', () { | ||||
|       var expr = Parser("1/2 + 3/4").parse(); | ||||
|       expect(expr.evaluate().toString().replaceAll(' ', ''), "5/4"); | ||||
|     }); | ||||
|  | ||||
|     test('分数与整数相乘', () { | ||||
|       var expr = Parser("2 * 3/4").parse(); | ||||
|       expect(expr.evaluate().toString(), "3/2"); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   group('开平方', () { | ||||
|     test('完全平方数', () { | ||||
|       var expr = Parser("sqrt(9)").parse(); | ||||
|       expect(expr.evaluate().toString(), "3"); | ||||
|     }); | ||||
|  | ||||
|     test('非完全平方数', () { | ||||
|       var expr = Parser("sqrt(8)").parse(); | ||||
|       expect(expr.simplify().toString().replaceAll(' ', ''), "(2*sqrt(2))"); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   group('组合表达式', () { | ||||
|     test('sqrt + 整数', () { | ||||
|       var expr = Parser("2 + sqrt(9)").parse(); | ||||
|       expect(expr.simplify().toString().replaceAll(' ', ''), "(2+3)"); | ||||
|     }); | ||||
|  | ||||
|     test('分数 + sqrt', () { | ||||
|       var expr = Parser("sqrt(8)/4 + 1/2").parse(); | ||||
|       expect( | ||||
|         expr.evaluate().toString().replaceAll(' ', ''), | ||||
|         "((sqrt(2)/2)+1/2)", | ||||
|       ); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   group('加减除优先级', () { | ||||
|     test('减法', () { | ||||
|       var expr = Parser("5 - 2").parse(); | ||||
|       expect(expr.evaluate().toString(), "3"); | ||||
|     }); | ||||
|  | ||||
|     test('除法', () { | ||||
|       var expr = Parser("6 / 3").parse(); | ||||
|       expect(expr.evaluate().toString(), "2"); | ||||
|     }); | ||||
|  | ||||
|     test('加法和乘法优先级', () { | ||||
|       var expr = Parser("1 + 2 * 3").parse(); | ||||
|       expect(expr.evaluate().toString(), "7"); | ||||
|     }); | ||||
|  | ||||
|     test('加减混合', () { | ||||
|       var expr = Parser("10 - 3 + 2").parse(); | ||||
|       expect(expr.evaluate().toString(), "9"); | ||||
|     }); | ||||
|  | ||||
|     test('括号优先级', () { | ||||
|       var expr = Parser("(1 + 2) * 3").parse(); | ||||
|       expect(expr.evaluate().toString(), "9"); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   group('三角函数', () { | ||||
|     test('cos(0)', () { | ||||
|       var expr = Parser("cos(0)").parse(); | ||||
|       expect(expr.evaluate().toString(), "1.0"); | ||||
|     }); | ||||
|  | ||||
|     test('sin(0)', () { | ||||
|       var expr = Parser("sin(0)").parse(); | ||||
|       expect(expr.evaluate().toString(), "0.0"); | ||||
|     }); | ||||
|  | ||||
|     test('tan(0)', () { | ||||
|       var expr = Parser("tan(0)").parse(); | ||||
|       expect(expr.evaluate().toString(), "0.0"); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user