diff --git a/CHANGELOG.md b/CHANGELOG.md index effe43c..874edcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ -## 1.0.0 +# 1.2.0 + +- Fix README +- Moved integrating features to a separate package library `math_parser_integrate` + + +# 1.1.0 + +- Custom variables support. +- `MathFunctionX`deprecated. +- `MathVariable` introduced. +- You need to pass an instance of `MathVariableValues` instead of a num to the `calc()` function now + + +# 1.0.0 - Initial version. diff --git a/README.md b/README.md index 8187893..20a4035 100644 --- a/README.md +++ b/README.md @@ -19,15 +19,16 @@ All the child classes names begin with the family they belong to. ## Evaluation You can evaluate a MathNode and its subnodes recursively by calling -`MathNode.calc(num x)` and passing custom `x` variable value. +`MathNode.calc(MathVariableValues values)` and passing custom +variable values. Example: Calculate `x + 3`, where `x = 5`. ```dart MathOperatorAddition( - MathFunctionX(), + MathVariable('x'), const MathValue(3), -).calc(5); +).calc(MathVariableValues.x(5)); ``` ## Parsing String to MathNode @@ -43,8 +44,15 @@ and return them as a machine-readable `MathNode` using ### Parse priority: 1. Parentheses () [] -2. Variables: x, e, pi (π) -3. Functions: +2. Variables: e, pi (π) and custom ones. `x` is being interpreted as a var + by default, but you can override this behavior with the variableNames + parameter. You can rewrite e and pi by defining it in variableNames and + mentioning it during the calc call. + First character must be a letter, others - letters, digits, or + underscore. Letters may be latin or Greek, both lower or capital case. + You can't use built-in function names like sin, cos, etc. Variable names + are case-sensitive +3. Functions (case-sensitive): - sin, cos, tan (tg), cot (ctg) - sqrt (√) (interpreted as power of 1/2), complex numbers not supported - ln (base=E), lg (base=2), log\[base\]\(x\) @@ -65,19 +73,25 @@ MathNode fromString( /// Allows skipping the multiplication (*) operator bool isImplicitMultiplication = true, + + /// Expressions which should be marked as variables + Set variableNames = const {'x'}, }); ``` -Example for parsing a string and evaluating it with `x = 20`: +Example for parsing a string and evaluating it with `x = 20` +and `y = 5`: ```dart final expression = MathNodeExpression.fromString( - '(2x)^(e^3 + 4)', + '(2x)^(e^3 + 4) + y', +).calc( + MathVariableValues({'x': 20, 'y': 5}), ); -print(expression.calc(20)); - ``` +More complicated work with variables is shown off in example. + ## Other Features ### Numerical methods for Definite Integrals diff --git a/example/math_parser_example.dart b/example/math_parser_example.dart index 7460c79..be473c8 100644 --- a/example/math_parser_example.dart +++ b/example/math_parser_example.dart @@ -1,13 +1,27 @@ import 'package:math_parser/math_parser.dart'; +import 'package:math_parser/integrate.dart'; +/// Example function to calculate an expression from string void main() { // Parsing string to a MathNode final expression = MathNodeExpression.fromString( - '((2x)^(e^3 + 4) + cos(3)x) / log[x + 3^2e](2 + (3x)^2)^5 * (2 + x)(x^2 + 3) + arcctg(x)', + '((2x)^(e^3 + 4) + cos(3)x) / log[x_1*2 + 3^2e](2 + (3y)^2)^5 * (2 + y)(x^2 + 3) + arcctg(Θ)', + variableNames: {'x', 'y', 'Θ', 'x_1'}, ); // Display the parsed expression in human-readable form print(expression); - // Evaluate the expression with `x = 20` and display result - print(expression.calc(20)); + // Evaluate the expression with `x = 20`, `y = 2`, `theta = 1/2` + // and display result + print(expression.calc( + MathVariableValues({'x': 20, 'y': 2, 'Θ': 0.5, 'x_1': 3}), + )); +} + +/// Integrate library example +void integrate() { + print( + MathNodeExpression.fromString('cos(x)') + .definiteIntegralBySimpson(10, 0, 3.14), + ); } diff --git a/lib/integrate.dart b/lib/integrate.dart new file mode 100644 index 0000000..d2caa56 --- /dev/null +++ b/lib/integrate.dart @@ -0,0 +1,11 @@ +/// Numerical methods for integrating extension +/// +/// Following methods available: +/// - By rectangles (left, middle, right) +/// - Simpson's method +/// - By trapezoids +/// +/// In order to use the functionality import the `integrate.dart` file +library math_parser_integrate; + +export 'src/extensions/integrate.dart'; diff --git a/lib/math_parser.dart b/lib/math_parser.dart index 70666b7..a657e5e 100644 --- a/lib/math_parser.dart +++ b/lib/math_parser.dart @@ -11,7 +11,7 @@ /// /// void main() { /// final expression = MathNodeExpression.fromString( -/// '((2x)^(e^3 + 4)', +/// '(2x)^(e^3 + 4)', /// ); /// print(expression.calc(20)); /// } @@ -20,7 +20,5 @@ library math_parser; export 'src/math_node.dart'; export 'src/parse.dart'; +export 'src/math_errors.dart'; export 'src/parse_errors.dart'; - -// Extensions -export 'src/extensions/integrate.dart'; diff --git a/lib/src/extensions/integrate.dart b/lib/src/extensions/integrate.dart index 7d6fb7d..b5c688a 100644 --- a/lib/src/extensions/integrate.dart +++ b/lib/src/extensions/integrate.dart @@ -122,6 +122,6 @@ Iterable _stepsAtMiddle(int n, num lowerLimit, num upperLimit) sync* { Iterable _calculateMathNodeAtPoints( MathNode expression, Iterable points) sync* { for (final point in points) { - yield expression.calc(point); + yield expression.calc(MathVariableValues.x(point)); } } diff --git a/lib/src/math_errors.dart b/lib/src/math_errors.dart new file mode 100644 index 0000000..60563fd --- /dev/null +++ b/lib/src/math_errors.dart @@ -0,0 +1,25 @@ +/// Math parsing error +/// +/// Errors must extends this class +abstract class MathException implements Exception { + /// Creates a new Math Exception + const MathException(); +} + +/// Undefined Variable Exception +/// +/// Thrown when there's a variable referenced in calculations which wasn't passed +/// to the [MathNode.calc] function +class UndefinedVariableException extends MathException { + @override + String toString() { + return 'UndefinedVariableException: Variable "$name" was not defined in ' + 'the calc() function. Variable names are case-sensitive'; + } + + /// The missing variable + final String name; + + /// Created a new Undefined Variable Exception + const UndefinedVariableException(this.name); +} diff --git a/lib/src/math_node.dart b/lib/src/math_node.dart index b22daf3..096acb4 100644 --- a/lib/src/math_node.dart +++ b/lib/src/math_node.dart @@ -1,5 +1,40 @@ import 'dart:math' as math; +import 'package:math_parser/src/math_errors.dart'; + +/// Variables dictionary +/// +/// The class must be passed to the [MathNode.calc] function. +/// Map key value is the variable name, with a corresponding numeric value +class MathVariableValues { + /// The map containing values + final Map _values; + + /// Get the variable value + /// + /// Throws UndefinedVariableException if variable is not set + num operator [](String variableName) { + final v = _values[variableName]; + if (v is num) { + return v; + } else { + throw UndefinedVariableException(variableName); + } + } + + /// Creates new variables dictionary + /// + /// - Use [MathVariableValues.x] if you only need to set x + /// - Use [MathVariableValues.none] if you are not using variables + const MathVariableValues(this._values); + + /// Empty variables dictionary + static const MathVariableValues none = MathVariableValues({}); + + /// Creates a new x variable dictionary + factory MathVariableValues.x(num x) => MathVariableValues({'x': x}); +} + /// Basic math expression unit /// /// Defines the [calc] method for every child to implement. @@ -17,7 +52,7 @@ abstract class MathNode { /// constant with the isConst() method of the ExtensionConstant* extension /// family. num calc( - num x, + MathVariableValues values, ); /// Creates a new math expression node @@ -84,7 +119,7 @@ class MathValue extends MathNode { final num value; @override - num calc(num x) => value; + num calc(MathVariableValues values) => value; /// Creates a new constant value const MathValue(this.value); @@ -93,13 +128,33 @@ class MathValue extends MathNode { String toString() => 'VALUE($value)'; } +/// A math variable +/// +/// This value is being set from the [MathNode.calc] method and being passed to +/// every subnode. +/// +/// The variable is being replaced by the passed value during calculation. +class MathVariable extends MathNode { + /// Designed variable name + final String variableName; + + @override + num calc(MathVariableValues values) => values[variableName]; + + /// Creates a new variable which will be replaced by a corresponding value + const MathVariable(this.variableName); + + @override + String toString() => 'VAR[$variableName]'; +} + /// Addition operator (+) /// /// Both operands are addends. This expression evaluates to the sum of left and /// right operands. class MathOperatorAddition extends MathOperator { @override - num calc(num x) => left.calc(x) + right.calc(x); + num calc(MathVariableValues values) => left.calc(values) + right.calc(values); /// Creates a new addition operation const MathOperatorAddition( @@ -120,7 +175,7 @@ class MathOperatorAddition extends MathOperator { /// the difference `left - right` class MathOperatorSubtraction extends MathOperator { @override - num calc(num x) => left.calc(x) - right.calc(x); + num calc(MathVariableValues values) => left.calc(values) - right.calc(values); /// Creates a new subtraction operation const MathOperatorSubtraction( @@ -140,7 +195,7 @@ class MathOperatorSubtraction extends MathOperator { /// Both operands are both factors. This expression evaluates to the product class MathOperatorMultiplication extends MathOperator { @override - num calc(num x) => left.calc(x) * right.calc(x); + num calc(MathVariableValues values) => left.calc(values) * right.calc(values); /// Creates multiplication operator const MathOperatorMultiplication(MathNode left, MathNode right) @@ -166,7 +221,7 @@ class MathOperatorMultiplication extends MathOperator { /// the quotient of these two. class MathOperatorDivision extends MathOperator { @override - num calc(num x) => left.calc(x) / right.calc(x); + num calc(MathVariableValues values) => left.calc(values) / right.calc(values); /// Creates division operator const MathOperatorDivision(MathNode left, MathNode right) @@ -190,7 +245,7 @@ class MathOperatorDivision extends MathOperator { /// Returns the opposite value for the underlying node class MathFunctionNegative extends MathFunction { @override - num calc(num x) => -x1.calc(x); + num calc(MathVariableValues values) => -x1.calc(values); /// Creates a negative value const MathFunctionNegative(MathNode x) : super(x); @@ -199,29 +254,13 @@ class MathFunctionNegative extends MathFunction { String toString() => '(-$x1)'; } -/// The X variable -/// -/// This value is being passed from the [MathNode.calc] method to every subnode. -/// -/// The function is being replaced by the passed value during calculation. -class MathFunctionX extends MathFunction { - @override - num calc(num x) => x; - - /// Creates the X value - const MathFunctionX() : super(const MathValue(1)); - - @override - String toString() => '[x]'; -} - /// The Exponent in power constant /// /// Value that evaluates to the natural exponent in given number. The power /// of 1 is the default value. class MathFunctionE extends MathFunction { @override - num calc(num x) => math.exp(x1.calc(x)); + num calc(MathVariableValues values) => math.exp(x1.calc(values)); /// Creates an exponent value const MathFunctionE({MathNode x1 = const MathValue(1)}) : super(x1); @@ -246,7 +285,8 @@ class MathValuePi extends MathValue { /// Evaluates to [left] in the power of [right]. class MathOperatorPower extends MathOperator { @override - num calc(num x) => math.pow(left.calc(x), right.calc(x)); + num calc(MathVariableValues values) => + math.pow(left.calc(values), right.calc(values)); /// Creates the power operation const MathOperatorPower(MathNode base, MathNode exponent) @@ -261,7 +301,7 @@ class MathOperatorPower extends MathOperator { /// Evaluates to the value of sin at the point of [x1] class MathFunctionSin extends MathFunction { @override - num calc(num x) => math.sin(x1.calc(x)); + num calc(MathVariableValues values) => math.sin(x1.calc(values)); /// Creates the sin function const MathFunctionSin(MathNode x1) : super(x1); @@ -275,7 +315,7 @@ class MathFunctionSin extends MathFunction { /// Evaluates to the value of cos at the point of [x1] class MathFunctionCos extends MathFunction { @override - num calc(num x) => math.cos(x1.calc(x)); + num calc(MathVariableValues values) => math.cos(x1.calc(values)); /// Creates the cos function const MathFunctionCos(MathNode x1) : super(x1); @@ -289,7 +329,7 @@ class MathFunctionCos extends MathFunction { /// Evaluates to the value of tan at the point of [x1] class MathFunctionTan extends MathFunction { @override - num calc(num x) => math.tan(x1.calc(x)); + num calc(MathVariableValues values) => math.tan(x1.calc(values)); /// Creates the tan function const MathFunctionTan(MathNode x1) : super(x1); @@ -303,7 +343,7 @@ class MathFunctionTan extends MathFunction { /// Evaluates to the value of cot at the point of [x1] class MathFunctionCot extends MathFunction { @override - num calc(num x) => 1 / math.tan(x1.calc(x)); + num calc(MathVariableValues values) => 1 / math.tan(x1.calc(values)); /// Creates the cot function const MathFunctionCot(MathNode x1) : super(x1); @@ -317,7 +357,7 @@ class MathFunctionCot extends MathFunction { /// Evaluates to the value of asin at the point of [x1] class MathFunctionAsin extends MathFunction { @override - num calc(num x) => math.asin(x1.calc(x)); + num calc(MathVariableValues values) => math.asin(x1.calc(values)); /// Creates the asin function const MathFunctionAsin(MathNode x1) : super(x1); @@ -331,7 +371,7 @@ class MathFunctionAsin extends MathFunction { /// Evaluates to the value of acos at the point of [x1] class MathFunctionAcos extends MathFunction { @override - num calc(num x) => math.acos(x1.calc(x)); + num calc(MathVariableValues values) => math.acos(x1.calc(values)); /// Creates the acos function const MathFunctionAcos(MathNode x1) : super(x1); @@ -345,7 +385,7 @@ class MathFunctionAcos extends MathFunction { /// Evaluates to the value of atan at the point of [x1] class MathFunctionAtan extends MathFunction { @override - num calc(num x) => math.atan(x1.calc(x)); + num calc(MathVariableValues values) => math.atan(x1.calc(values)); /// Creates the atan function const MathFunctionAtan(MathNode x1) : super(x1); @@ -359,7 +399,7 @@ class MathFunctionAtan extends MathFunction { /// Evaluates to the value of acot at the point of [x1] class MathFunctionAcot extends MathFunction { @override - num calc(num x) => math.atan(1 / x1.calc(x)); + num calc(MathVariableValues values) => math.atan(1 / x1.calc(values)); /// Creates the acot function const MathFunctionAcot(MathNode x1) : super(x1); @@ -375,7 +415,8 @@ class MathFunctionAcot extends MathFunction { /// By default it's natural logarithm (base equals 10) class MathFunctionLog extends MathFunctionWithTwoArguments { @override - num calc(num x) => math.log(x1.calc(x)) / math.log(x2.calc(x)); + num calc(MathVariableValues values) => + math.log(x1.calc(values)) / math.log(x2.calc(values)); /// Creates the log function const MathFunctionLog(MathNode x1, {MathNode x2 = const MathValue(10)}) diff --git a/lib/src/parse.dart b/lib/src/parse.dart index 06ace60..6b086a7 100644 --- a/lib/src/parse.dart +++ b/lib/src/parse.dart @@ -1,3 +1,5 @@ +import 'package:math_parser/src/math_errors.dart'; + import 'math_node.dart'; import 'parse_errors.dart'; @@ -10,12 +12,20 @@ extension MathNodeExpression on MathNode { /// Returns a single [MathNode]. Throws [MathException] if parsing fails. /// - Set [isMinusNegativeFunction] to `true` to interpret minus operator as a /// sum of two values, right of which will be negative: X - Y turns to X + (-Y) - /// - Set [isImplicitMultiplication] to `false` to disable implicit multiplication + /// - Set [isImplicitMultiplication] to `false` to disable implicit + /// multiplication /// /// Parse priority: /// 1. Parentheses () [] - /// 2. Variables: x, e, pi (π) - /// 3. Functions: + /// 2. Variables: e, pi (π) and custom ones. + /// `x` is being interpreted as a var by default, but you can override + /// this behavior with the variableNames parameter. You can rewrite e and pi + /// by defining it in variableNames and mentioning it during the calc call. + /// First character must be a letter, others - letters, digits, or + /// underscore. Letters may be latin or Greek, both lower or capital case. + /// You can't use built-in function names like sin, cos, etc. Variable names + /// are case-sensitive + /// 3. Functions (case-sensitive): /// - sin, cos, tan (tg), cot (ctg) /// - sqrt (√) (interpreted as power of 1/2), complex numbers not supported /// - ln (base=E), lg (base=2), log\[base\](x) @@ -34,12 +44,30 @@ extension MathNodeExpression on MathNode { /// Allows skipping the multiplication (*) operator bool isImplicitMultiplication = true, + + /// Expressions which should be marked as variables + Set variableNames = const {'x'}, }) { try { + for (final e in variableNames) { + _checkVariableName(e); + } + + final variableNamesList = variableNames.toList(); + variableNamesList.sort((a, b) => b.length.compareTo(a.length)); + return _parseMathString( expression, isMinusNegativeFunction: isMinusNegativeFunction, isImplicitMultiplication: isImplicitMultiplication, + variableNames: variableNames, + regexp: RegExp( + '(' + + variableNamesList.join('|') + + (variableNamesList.isNotEmpty ? '|' : '') + + r'(\d+(\.\d+)?)|\+|-|\^|/|\*|x|e|asin|acos|atan|acot|' + r'arcsin|arccos|arctg|arcctg|cos|tan|tg|cot|ctg|sqrt|√|ln|log|lg|pi|π)', + ), ); } on MathException { rethrow; @@ -49,16 +77,45 @@ extension MathNodeExpression on MathNode { } } -// Tokenizer regex -final _regex = RegExp( - r'((\d+(\.\d+)?)|\+|-|\^|/|\*|x|e|asin|acos|atan|acot|' - r'arcsin|arccos|arctg|arcctg|cos|tan|tg|cot|ctg|sqrt|√|ln|log|lg|pi|π)', -); +const _bracketFuncs = [ + 'sin', + 'cos', + 'tan', + 'tg', + 'cot', + 'ctg', + 'sqrt', + '√', + 'ln', + 'lg', + 'log', + 'asin', + 'acos', + 'atan', + 'acot', + 'arcsin', + 'arccos', + 'arctg', + 'arcctg' +]; + +void _checkVariableName(String name) { + if (!(RegExp(r'^[a-zA-Zα-ωΑ-Ω]([a-zA-Zα-ωΑ-Ω0-9_]+)?$').hasMatch(name)) || + _bracketFuncs.contains(name)) { + throw InvalidVariableNameException(name); + } +} + +const _priority1 = ['^']; +const _priority2 = ['/', '*']; +const _priority3 = ['-', '+']; MathNode _parseMathString( String s, { required bool isMinusNegativeFunction, required bool isImplicitMultiplication, + required Set variableNames, + required RegExp regexp, }) { final List<_UnprocessedMathString> tempNodes = []; @@ -134,10 +191,10 @@ MathNode _parseMathString( // Splitting string to tokens for (final item in tempNodes) { if (item is! _UnprocessedBrackets) { - final str = item.contents.replaceAll(' ', '').toLowerCase(); + final str = item.contents.replaceAll(' ', ''); int start = 0; - for (final match in _regex.allMatches(str, 0)) { + for (final match in regexp.allMatches(str, 0)) { var r = str.substring(start, match.start); if (r.isNotEmpty) nodes.add(_MathNodePartString(r)); r = match[0]!; @@ -159,47 +216,26 @@ MathNode _parseMathString( item.contents, isMinusNegativeFunction: isMinusNegativeFunction, isImplicitMultiplication: isImplicitMultiplication, + variableNames: variableNames, + regexp: regexp, ))); } } - const bracketFuncs = [ - 'sin', - 'cos', - 'tan', - 'tg', - 'cot', - 'ctg', - 'sqrt', - '√', - 'ln', - 'lg', - 'log', - 'asin', - 'acos', - 'atan', - 'acot', - 'arcsin', - 'arccos', - 'arctg', - 'arcctg' - ]; - - const priority1 = ['^']; - const priority2 = ['/', '*']; - const priority3 = ['-', '+']; - // Looking for variables for (int i = nodes.length - 1; i >= 0; i--) { final item = nodes[i]; - if (item.str == 'e') { + if (item is _MathNodePartString && variableNames.contains(item.str)) { + nodes.removeAt(i); + nodes.insert(i, _MathNodePartParsed(MathVariable(item.str))); + } else if (item.str == 'e') { const el = MathFunctionE(); nodes.removeAt(i); nodes.insert(i, _MathNodePartParsed(el)); } else if (item.str == 'x') { - const el = MathFunctionX(); + const el = MathVariable('x'); nodes.removeAt(i); nodes.insert(i, _MathNodePartParsed(el)); @@ -215,7 +251,7 @@ MathNode _parseMathString( for (int i = 0; i < nodes.length; i++) { final item = nodes[i]; - if (bracketFuncs.contains(item.str)) { + if (_bracketFuncs.contains(item.str)) { if (i + 1 == nodes.length) { throw MissingFunctionArgumentListException(item.toString()); } @@ -226,6 +262,8 @@ MathNode _parseMathString( te.str, isMinusNegativeFunction: isMinusNegativeFunction, isImplicitMultiplication: isImplicitMultiplication, + variableNames: variableNames, + regexp: regexp, )); } else if (te is _MathNodePartParsed) { op = te; @@ -307,6 +345,8 @@ MathNode _parseMathString( right.str, isMinusNegativeFunction: isMinusNegativeFunction, isImplicitMultiplication: isImplicitMultiplication, + variableNames: variableNames, + regexp: regexp, )); } @@ -326,7 +366,7 @@ MathNode _parseMathString( for (int i = 0; i < nodes.length; i++) { final item = nodes[i]; - if (priority1.contains(item.str)) { + if (_priority1.contains(item.str)) { if (i + 1 == nodes.length || i == 0) { throw MissingOperatorOperandException(item.toString()); } @@ -339,6 +379,8 @@ MathNode _parseMathString( left.str, isMinusNegativeFunction: isMinusNegativeFunction, isImplicitMultiplication: isImplicitMultiplication, + variableNames: variableNames, + regexp: regexp, )); } if (right is _MathNodePartString) { @@ -346,6 +388,8 @@ MathNode _parseMathString( right.str, isMinusNegativeFunction: isMinusNegativeFunction, isImplicitMultiplication: isImplicitMultiplication, + variableNames: variableNames, + regexp: regexp, )); } @@ -414,7 +458,7 @@ MathNode _parseMathString( for (int i = 0; i < nodes.length; i++) { final item = nodes[i]; - if (priority2.contains(item.str)) { + if (_priority2.contains(item.str)) { if (i + 1 == nodes.length || i == 0) { throw MissingOperatorOperandException(item.toString()); } @@ -427,6 +471,8 @@ MathNode _parseMathString( left.str, isMinusNegativeFunction: isMinusNegativeFunction, isImplicitMultiplication: isImplicitMultiplication, + variableNames: variableNames, + regexp: regexp, )); } if (right is _MathNodePartString) { @@ -434,6 +480,8 @@ MathNode _parseMathString( right.str, isMinusNegativeFunction: isMinusNegativeFunction, isImplicitMultiplication: isImplicitMultiplication, + variableNames: variableNames, + regexp: regexp, )); } @@ -469,7 +517,7 @@ MathNode _parseMathString( for (int i = 0; i < nodes.length; i++) { final item = nodes[i]; - if (priority3.contains(item.str)) { + if (_priority3.contains(item.str)) { if (i + 1 == nodes.length || i == 0) { throw MissingOperatorOperandException(item.toString()); } @@ -482,6 +530,8 @@ MathNode _parseMathString( left.str, isMinusNegativeFunction: isMinusNegativeFunction, isImplicitMultiplication: isImplicitMultiplication, + variableNames: variableNames, + regexp: regexp, )); } if (right is _MathNodePartString) { @@ -489,6 +539,8 @@ MathNode _parseMathString( right.str, isMinusNegativeFunction: isMinusNegativeFunction, isImplicitMultiplication: isImplicitMultiplication, + variableNames: variableNames, + regexp: regexp, )); } diff --git a/lib/src/parse_errors.dart b/lib/src/parse_errors.dart index eff6933..5a45ab9 100644 --- a/lib/src/parse_errors.dart +++ b/lib/src/parse_errors.dart @@ -1,8 +1,13 @@ -/// Math parsing error +import 'package:math_parser/src/math_errors.dart'; + +/// Math Parse Exception /// -/// Errors must extends this class -abstract class MathException implements Exception { - const MathException(); +/// All sorts of errors related to parsing a string to [MathNode] used by +/// [MathNodeExpression]. All actuall exceptions are being extended from this +/// class. +abstract class MathParseException extends MathException { + /// Creates a new Math Parse Exception + const MathParseException(); } /// Missing Operator Operand Exception @@ -10,10 +15,11 @@ abstract class MathException implements Exception { /// Thrown when an operator lacks its left or right operand. /// /// The operator which caused the problem is stored in [operator] -class MissingOperatorOperandException extends MathException { +class MissingOperatorOperandException extends MathParseException { @override String toString() { - return 'MissingOperatorOperandException: "$operator" has insufficient neighboring expressions'; + return 'MissingOperatorOperandException: "$operator" has insufficient ' + 'neighboring expressions'; } /// The operator the error happened in @@ -28,10 +34,11 @@ class MissingOperatorOperandException extends MathException { /// Thrown when a function receives less arguments than it expects. /// /// The function which caused the problem is stored in [func] -class MissingFunctionArgumentListException extends MathException { +class MissingFunctionArgumentListException extends MathParseException { @override String toString() { - return 'MissingFunctionArgumentListException: "$func" has insufficient arguments fed'; + return 'MissingFunctionArgumentListException: "$func" has insufficient ' + 'arguments fed'; } /// The function which caused the problem @@ -46,10 +53,11 @@ class MissingFunctionArgumentListException extends MathException { /// Thrown when the parser finds syntax it doesn't understand /// /// The part which caused the problem is stored in [operation] -class UnknownOperationException extends MathException { +class UnknownOperationException extends MathParseException { @override String toString() { - return 'UnknownOperationException: "$operation" is an unknown operation in given context'; + return 'UnknownOperationException: "$operation" is an unknown operation ' + 'in given context'; } /// The part which caused the problem @@ -64,13 +72,15 @@ class UnknownOperationException extends MathException { /// Thrown when some parts of the expression were left unprocessed /// /// The unprocessed parts are stored as a string in [parts] -class CantProcessExpressionException extends MathException { +class CantProcessExpressionException extends MathParseException { @override String toString() { return 'CantProcessExpressionException: ' 'Some parts of the expression were left unprocessed: $parts.' - '\nThis often happens if you haven\'t specified the multiplication ' - 'operator explicitly and don\'t have isImplicitMultiplication turned on'; + '\nThis often happens if you used an undefined variable in ' + 'variableNames parameter of the parse function or you haven\'t ' + 'specified the multiplication operator explicitly and don\'t have ' + 'isImplicitMultiplication turned on'; } /// The unprocessed parts @@ -85,7 +95,7 @@ class CantProcessExpressionException extends MathException { /// Thrown when parsing fails for an unknown reason /// /// The error is stored in [error] -class ParsingFailedException extends MathException { +class ParsingFailedException extends MathParseException { @override String toString() { return 'ParsingFailedException: $error'; @@ -103,10 +113,11 @@ class ParsingFailedException extends MathException { /// Thrown when an unexpected closing bracket is met by the parser /// /// The type of bracket is stored in [type] -class UnexpectedClosingBracketException extends MathException { +class UnexpectedClosingBracketException extends MathParseException { @override String toString() { - return 'UnexpectedClosingBracketException: A bracket of type "$type" was found in unexpected context'; + return 'UnexpectedClosingBracketException: A bracket of type "$type" was ' + 'found in unexpected context'; } /// The type of bracket @@ -122,10 +133,11 @@ class UnexpectedClosingBracketException extends MathException { /// correct parsing of the expression /// /// The type of bracket is stored in [type] -class BracketsNotClosedException extends MathException { +class BracketsNotClosedException extends MathParseException { @override String toString() { - return "BracketsNotClosedException: A bracket of type \"$type\" wasn't closed so correct expression parsing can't be guaranteed"; + return "BracketsNotClosedException: A bracket of type \"$type\" wasn't " + "closed so correct expression parsing can't be guaranteed"; } /// The type of bracket @@ -134,3 +146,25 @@ class BracketsNotClosedException extends MathException { /// Creates a new Brackets Not Closed Exception const BracketsNotClosedException(this.type); } + +/// Invalid Variable Name Exception +/// +/// Thrown when variable has incorrect name +/// +/// The name which caused the error is stored in [name] +class InvalidVariableNameException extends MathParseException { + @override + String toString() { + return "InvalidVariableNameException: A variable with name \"$name\" can't" + ' be defined. First character must be a letter, others - letters, ' + 'digits, or underscore. Letters may be latin or Greek, both lower or ' + 'capital case. You can\'t use built-in function names like sin, cos, ' + 'etc. Variable names are case-sensitive'; + } + + /// The missing variable + final String name; + + /// Created a new Undefined Variable Exception + const InvalidVariableNameException(this.name); +} diff --git a/pubspec.yaml b/pubspec.yaml index 6f49573..3b30c97 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: math_parser description: Process math expressions, convert them to machine-readable form, and calculate them. -version: 1.0.0 +version: 1.2.0 homepage: https://github.com/Sominemo/Math-Parser-Dart environment: