Skip to content

Commit

Permalink
add support for multiplying parentheses (#87)
Browse files Browse the repository at this point in the history
* add support for multiplying parentheses

* fix code review details

* clean code

---------

Co-authored-by: Juan carlos Vallejo salazar <[email protected]>
  • Loading branch information
juca1331 and Juan carlos Vallejo salazar authored Jul 31, 2024
1 parent 043351b commit 35992ba
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 12 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 39 additions & 9 deletions lib/src/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@ class Parser {
Parser() : lex = Lexer();
Map<String, dynamic> functionHandlers = <String, dynamic>{};

/// 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<Expression> exprStack = <Expression>[];
final List<Token> inputStream = lex.tokenizeToRPN(inputString);
final List<Token> inputStream = lex.tokenizeToRPN(
inputString,
multiplyWithParentheses: multiplyWithParentheses,
);

for (Token currToken in inputStream) {
Expression currExpr, left, right;
Expand Down Expand Up @@ -208,7 +212,8 @@ class Lexer {

/// Tokenizes a given input string.
/// Returns a list of [Token] in infix notation.
List<Token> tokenize(String inputString) {
List<Token> tokenize(String inputString,
{bool multiplyWithParentheses = false}) {
final List<Token> tempTokenStream = <Token>[];
final String clearedString = inputString.replaceAll(' ', '').trim();
final RuneIterator iter = clearedString.runes.iterator;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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<Token> tokenizeToRPN(String inputString) {
final List<Token> infixStream = tokenize(inputString);
List<Token> tokenizeToRPN(String inputString,
{bool multiplyWithParentheses = false}) {
final List<Token> infixStream = tokenize(
inputString,
multiplyWithParentheses: multiplyWithParentheses,
);
return shuntingYard(infixStream);
}
}
Expand Down
24 changes: 21 additions & 3 deletions test/parser_test_set.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class ParserTests extends TestSet {
'Power': parsePower,
'Modulo': parseModulo,
'Multiplication': parseMultiplication,
'MultiplicationWithParentheses': parseMultiplicationWithParentheses,
'Division': parseDivision,
'Addition': parsePlus,
'Subtraction': parseMinus,
Expand All @@ -49,10 +50,19 @@ class ParserTests extends TestSet {

Parser parser = Parser();

void parameterized(Map<String, Expression> cases) {
void parameterized(Map<String, Expression> 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()));
});
}

Expand Down Expand Up @@ -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),
Expand Down

0 comments on commit 35992ba

Please sign in to comment.