diff --git a/lib/calculator.dart b/lib/calculator.dart new file mode 100644 index 0000000..c2792b2 --- /dev/null +++ b/lib/calculator.dart @@ -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); diff --git a/lib/parser.dart b/lib/parser.dart new file mode 100644 index 0000000..f280afc --- /dev/null +++ b/lib/parser.dart @@ -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)); + } +} diff --git a/pubspec.lock b/pubspec.lock index e38716b..cff86c1 100644 --- a/pubspec.lock +++ b/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: diff --git a/pubspec.yaml b/pubspec.yaml index 2d1b13c..4b298ac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 diff --git a/test/calculator_test.dart b/test/calculator_test.dart new file mode 100644 index 0000000..421c4b5 --- /dev/null +++ b/test/calculator_test.dart @@ -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"); + }); + }); +}