File tree

11 files changed

+524
-126
lines changed

11 files changed

+524
-126
lines changed
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ In order to build a proper parser, grammar of the expressions is needed to be de
44
Working with tokens instead of raw string is a good practice when implementing a parser.
55

66
1. Grammar
7-
Expression := {Operator} Number {Operator Number}
8-
Operator := "+" | "-"
9-
Number := Digit{Digit}
7+
Expression := [ "-" ] Term { ("+" | "-") Term }
8+
Term := Factor { ( "*" | "/" ) Factor }
9+
Factor := RealNumber [ "^" "(" Factor ")" ] | "(" Expression ")"
10+
RealNumber := Digit{Digit} [ "." Digit{Digit} ]
1011
Digit := "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
1112

1213
[] - optional
Original file line numberDiff line numberDiff line change
@@ -2,76 +2,137 @@ const Tokenizer = require('./Tokenizer').Tokenizer;
22
const OperatorToken = require('./tokens/OperatorToken').OperatorToken;
33
const PlusToken = require('./tokens/PlusToken').PlusToken;
44
const MinusToken = require('./tokens/MinusToken').MinusToken;
5+
const MultiplicationToken = require('./tokens/MultiplicationToken').MultiplicationToken;
6+
const DivisionToken = require('./tokens/DivisionToken').DivisionToken;
7+
const OpeningBracketToken = require('./tokens/OpeningBracketToken').OpeningBracketToken;
8+
const ClosingBracketToken = require('./tokens/ClosingBracketToken').ClosingBracketToken;
59
const NumberToken = require('./tokens/NumberToken').NumberToken;
10+
const DotToken = require('./tokens/DotToken').DotToken;
11+
const CaretToken = require('./tokens/CaretToken').CaretToken;
612

713
module.exports.MathParser = class MathParser {
814
constructor(expression) {
915
this.tokenizer = new Tokenizer(expression);
1016
}
1117

12-
parse() {
13-
let result = this.parseExpression();
14-
const nextToken = this.tokenizer.getNext();
18+
// Expression := [ "-" ] Term { ("+" | "-") Term }
19+
parseExpression() {
20+
const isNegative = this.nextIsMinus();
21+
if (isNegative) {
22+
this.tokenizer.getNext();
23+
}
24+
let valueOfExpression = this.parseTerm();
25+
if (isNegative) {
26+
valueOfExpression = -valueOfExpression;
27+
}
28+
while (this.nextIsMinusOrPlus()) {
29+
const operand = this.tokenizer.getNext();
30+
const nextTermValue = this.parseTerm();
31+
if (operand instanceof PlusToken) {
32+
valueOfExpression += nextTermValue;
33+
} else {
34+
valueOfExpression -= nextTermValue;
35+
}
36+
}
37+
return valueOfExpression;
38+
}
39+
40+
// Term := Factor { ( "*" | "/" ) Factor }
41+
parseTerm() {
42+
let totalValue = this.parseFactor();
43+
while (this.nextIsMultiplicationOrDivision()) {
44+
const operand = this.tokenizer.getNext();
45+
const nextFactor = this.parseFactor();
46+
47+
if (operand instanceof MultiplicationToken) {
48+
totalValue *= nextFactor;
49+
} else {
50+
totalValue /= nextFactor;
51+
}
52+
}
53+
return totalValue;
54+
}
1555

16-
if (nextToken instanceof OperatorToken) {
17-
const operator = nextToken;
18-
const secondNumber = this.parse();
56+
// Factor := RealNumber [ "^" "(" Factor ")" ] | "(" Expression ")"
57+
parseFactor() {
58+
if (this.nextIsDigit()) {
59+
let number = this.parseNumber();
1960

20-
if (operator instanceof PlusToken || operator instanceof MinusToken) {
21-
return result + secondNumber;
61+
if (this.nextIsCaret()) {
62+
number = this.parseExponent(number);
2263
}
2364

24-
throw new Error(`Unknown operator: ${operator}`);
65+
return number;
2566
}
2667

27-
return result;
28-
}
29-
30-
parseExpression() {
31-
const firstToken = this.tokenizer.getCurrent();
32-
let firstNumber;
33-
34-
if (firstToken instanceof MinusToken) {
35-
this.tokenizer.moveForward();
36-
firstNumber = this.parseNumber() * -1;
37-
} else if (firstToken instanceof PlusToken) {
38-
this.tokenizer.moveForward();
39-
firstNumber = this.parseNumber();
40-
} else {
41-
firstNumber = this.parseNumber();
68+
if (!this.nextIsOpeningBracket()) {
69+
throw new Error(`Expected number or '(', but got: ${this.tokenizer.getNext()}`);
4270
}
4371

44-
const nextToken = this.tokenizer.getNext();
72+
this.tokenizer.getNext();
73+
let value = this.parseExpression();
4574

46-
if (!nextToken) {
47-
return firstNumber;
75+
if (!this.nextIsClosingBracket()) {
76+
throw new Error(`Expected ')', but got: ${this.tokenizer.getNext()}`);
4877
}
78+
this.tokenizer.getNext();
79+
if (this.nextIsCaret()) {
80+
value = this.parseExponent(value);
81+
}
82+
return value;
83+
}
4984

50-
if (nextToken instanceof OperatorToken) {
51-
const operator = nextToken;
52-
this.tokenizer.getNext();
85+
nextIsMinus() {
86+
return this.tokenizer.isNextOfType(MinusToken);
87+
}
5388

54-
const secondNumber = this.parseNumber();
89+
nextIsMinusOrPlus() {
90+
return this.tokenizer.isNextOfType(MinusToken) || this.tokenizer.isNextOfType(PlusToken);
91+
}
92+
93+
nextIsMultiplicationOrDivision() {
94+
return this.tokenizer.isNextOfType(MultiplicationToken) || this.tokenizer.isNextOfType(DivisionToken);
95+
}
5596

56-
if (operator instanceof PlusToken) {
57-
return firstNumber + secondNumber;
58-
}
97+
nextIsDigit() {
98+
return this.tokenizer.isNextOfType(NumberToken);
99+
}
59100

60-
if (operator instanceof MinusToken) {
61-
return firstNumber - secondNumber;
62-
}
101+
nextIsDot() {
102+
return this.tokenizer.isNextOfType(DotToken);
103+
}
63104

64-
throw new Error(`Unknown operator: ${operator}`);
65-
}
105+
nextIsOpeningBracket() {
106+
return this.tokenizer.isNextOfType(OpeningBracketToken);
107+
}
108+
109+
nextIsClosingBracket() {
110+
return this.tokenizer.isNextOfType(ClosingBracketToken);
111+
}
66112

67-
throw new Error(`Expected operator after number but got: ${nextToken}`);
113+
nextIsCaret() {
114+
return this.tokenizer.isNextOfType(CaretToken);
68115
}
69116

117+
// RealNumber := Digit{Digit} [ "." Digit{Digit} ]
70118
parseNumber() {
71-
const token = this.tokenizer.getCurrent();
72-
if (token instanceof NumberToken) {
73-
return token.value;
119+
const token = this.tokenizer.getNext();
120+
if (!(token instanceof NumberToken)) {
121+
throw new Error(`Expected a number but found: ${token}`);
74122
}
75-
throw new Error(`Expected a number but found: ${token}`);
123+
const digits = [];
124+
digits.push(token.value);
125+
while (this.nextIsDigit() || this.nextIsDot()) {
126+
const nextToken = this.tokenizer.getNext();
127+
digits.push(nextToken.value);
128+
}
129+
const number = digits.join("");
130+
return +number;
131+
}
132+
133+
parseExponent(base) {
134+
this.tokenizer.getNext();
135+
const exponentValue = this.parseFactor();
136+
return Math.pow(base, exponentValue);
76137
}
77138
}
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,38 @@
11
const NumberToken = require('./tokens/NumberToken').NumberToken;
22
const PlusToken = require('./tokens/PlusToken').PlusToken;
33
const MinusToken = require('./tokens/MinusToken').MinusToken;
4+
const MultiplicationToken = require('./tokens/MultiplicationToken').MultiplicationToken;
5+
const DivisionToken = require('./tokens/DivisionToken').DivisionToken;
6+
const OpeningBracketToken = require('./tokens/OpeningBracketToken').OpeningBracketToken;
7+
const ClosingBracketToken = require('./tokens/ClosingBracketToken').ClosingBracketToken;
8+
const DotToken = require('./tokens/DotToken').DotToken;
9+
const CaretToken = require('./tokens/CaretToken').CaretToken;
410

511
module.exports.Tokenizer = class Tokenizer {
612
constructor(expression) {
713
this.tokens = this.tokenize(expression);
8-
this.currIdx = 0;
14+
this.currentIndex = -1;
915
}
1016

11-
moveForward() {
12-
this.currIdx += 1;
17+
getCurrent() {
18+
return this.tokens[this.currentIndex];
19+
}
20+
21+
areThereMoreTokens() {
22+
return this.currentIndex < this.tokens.length;
1323
}
1424

1525
getNext() {
16-
this.currIdx += 1;
17-
return this.tokens[this.currIdx];
26+
this.currentIndex = this.currentIndex + 1;
27+
return this.tokens[this.currentIndex];
1828
}
1929

20-
getCurrent() {
21-
return this.tokens[this.currIdx];
30+
peekNext() {
31+
return this.tokens[this.currentIndex + 1];
32+
}
33+
34+
isNextOfType(type) {
35+
return this.peekNext() instanceof type;
2236
}
2337

2438
tokenize(expression) {
@@ -40,6 +54,36 @@ module.exports.Tokenizer = class Tokenizer {
4054
tokens.push(minusToken);
4155
}
4256

57+
else if (char === "*") {
58+
const multiplicationToken = new MultiplicationToken();
59+
tokens.push(multiplicationToken);
60+
}
61+
62+
else if (char === "/") {
63+
const divisionToken = new DivisionToken();
64+
tokens.push(divisionToken);
65+
}
66+
67+
else if (char === "(") {
68+
const openingBracketToken = new OpeningBracketToken();
69+
tokens.push(openingBracketToken);
70+
}
71+
72+
else if (char === ")") {
73+
const closingBracketToken = new ClosingBracketToken();
74+
tokens.push(closingBracketToken);
75+
}
76+
77+
else if (char === ".") {
78+
const dotToken = new DotToken(".");
79+
tokens.push(dotToken);
80+
}
81+
82+
else if (char === "^") {
83+
const caretToken = new CaretToken();
84+
tokens.push(caretToken);
85+
}
86+
4387
else {
4488
throw Error(`Unknown token: ${char}`);
4589
}

0 commit comments

Comments
 (0)