diff --git a/CHANGELOG.md b/CHANGELOG.md index f01781b..6bda8fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.5.1] - 2024-05-23 + +### Added + +- Add support for multiplying parentheses + ## [2.5.0] - 2024-04-16 ### Added diff --git a/lib/src/parser.dart b/lib/src/parser.dart index 41874c1..e524c19 100644 --- a/lib/src/parser.dart +++ b/lib/src/parser.dart @@ -16,17 +16,21 @@ class Parser { Parser() : lex = Lexer(); Map functionHandlers = {}; - /// Parses the given input string into an [Expression]. Throws a - /// [ArgumentError] if the given [inputString] is empty. Throws a - /// [StateError] if the token stream is invalid. Returns a valid - /// [Expression]. - Expression parse(String inputString) { + /// Parses the given input string into an [Expression]. If + /// [multiplyWithParentheses] is true you can multiply using + /// parentheses. Throws [ArgumentError] if the given [inputString] + /// is empty. Throws a [StateError] if the token stream is + /// invalid. Returns a valid [Expression]. + Expression parse(String inputString, {bool multiplyWithParentheses = false}) { if (inputString.trim().isEmpty) { throw FormatException('The given input string was empty.'); } final List exprStack = []; - final List inputStream = lex.tokenizeToRPN(inputString); + final List inputStream = lex.tokenizeToRPN( + inputString, + multiplyWithParentheses: multiplyWithParentheses, + ); for (Token currToken in inputStream) { Expression currExpr, left, right; @@ -208,7 +212,8 @@ class Lexer { /// Tokenizes a given input string. /// Returns a list of [Token] in infix notation. - List tokenize(String inputString) { + List tokenize(String inputString, + {bool multiplyWithParentheses = false}) { final List tempTokenStream = []; final String clearedString = inputString.replaceAll(' ', '').trim(); final RuneIterator iter = clearedString.runes.iterator; @@ -299,6 +304,27 @@ class Lexer { // There are no more symbols in the input string but there is still a variable or keyword in the varBuffer _doVarBuffer(tempTokenStream); } + if (multiplyWithParentheses) { + for (int i = 0; i < tempTokenStream.length; i++) { + if (tempTokenStream[i].type == TokenType.RBRACE && + i != tempTokenStream.length - 1) { + final nextSymbol = tempTokenStream[i + 1]; + if ([ + TokenType.RBRACE, + TokenType.DIV, + TokenType.TIMES, + TokenType.MINUS, + TokenType.PLUS, + TokenType.MOD, + ].every((element) => nextSymbol.type != element)) { + tempTokenStream.insert( + i + 1, + Token('*', TokenType.TIMES), + ); + } + } + } + } return tempTokenStream; } @@ -465,8 +491,12 @@ class Lexer { /// This method invokes the createTokenStream methode to create an infix token /// stream and then invokes the shunting yard method to transform this stream /// into a RPN (reverse polish notation) token stream. - List tokenizeToRPN(String inputString) { - final List infixStream = tokenize(inputString); + List tokenizeToRPN(String inputString, + {bool multiplyWithParentheses = false}) { + final List infixStream = tokenize( + inputString, + multiplyWithParentheses: multiplyWithParentheses, + ); return shuntingYard(infixStream); } } diff --git a/test/parser_test_set.dart b/test/parser_test_set.dart index daed2d7..ea12b2e 100644 --- a/test/parser_test_set.dart +++ b/test/parser_test_set.dart @@ -27,6 +27,7 @@ class ParserTests extends TestSet { 'Power': parsePower, 'Modulo': parseModulo, 'Multiplication': parseMultiplication, + 'MultiplicationWithParentheses': parseMultiplicationWithParentheses, 'Division': parseDivision, 'Addition': parsePlus, 'Subtraction': parseMinus, @@ -49,10 +50,19 @@ class ParserTests extends TestSet { Parser parser = Parser(); - void parameterized(Map cases) { + void parameterized(Map cases, + {bool multiplyWithParentheses = false}) { cases.forEach((key, value) { - test('$key -> $value', - () => expect(parser.parse(key).toString(), value.toString())); + test( + '$key -> $value', + () => expect( + parser + .parse( + key, + multiplyWithParentheses: multiplyWithParentheses, + ) + .toString(), + value.toString())); }); } @@ -127,6 +137,14 @@ class ParserTests extends TestSet { parameterized(cases); } + void parseMultiplicationWithParentheses() { + var cases = { + '(5)(5)': Number(5) * Number(5), + '(-2.0)5': -Number(2.0) * Number(5), + }; + parameterized(cases, multiplyWithParentheses: true); + } + void parseDivision() { var cases = { '0 / 1': Number(0) / Number(1),