aboutsummaryrefslogtreecommitdiff
path: root/calculator-java
diff options
context:
space:
mode:
authorMaël Gassmann <mael.gassmann@students.bfh.ch>2021-06-11 17:28:55 +0200
committerMaël Gassmann <mael.gassmann@students.bfh.ch>2021-06-11 17:28:55 +0200
commit98ddff97ec1d092b18ef2d176e83bd92f9671e03 (patch)
treeaf1457e05063421dc2860e6c033e58b28f9a8e4e /calculator-java
parent49f30529d9cd2ed62902079a45d4d3c1f45afbda (diff)
[+] Added StatementParser, [~] Restructured the Parsers
Diffstat (limited to 'calculator-java')
-rw-r--r--calculator-java/src/main/java/ch/bfh/Main.java12
-rw-r--r--calculator-java/src/main/java/ch/bfh/parser/ExpressionParser.java180
-rw-r--r--calculator-java/src/main/java/ch/bfh/parser/FactorParser.java94
-rw-r--r--calculator-java/src/main/java/ch/bfh/parser/Parser.java9
-rw-r--r--calculator-java/src/main/java/ch/bfh/parser/StatementParser.java70
-rw-r--r--calculator-java/src/main/java/ch/bfh/parser/TermParser.java93
-rw-r--r--calculator-java/src/test/java/ExpressionParserTest.java2
-rw-r--r--calculator-java/src/test/java/StatementParserTest.java91
8 files changed, 372 insertions, 179 deletions
diff --git a/calculator-java/src/main/java/ch/bfh/Main.java b/calculator-java/src/main/java/ch/bfh/Main.java
index e57fc8a..5dc8a91 100644
--- a/calculator-java/src/main/java/ch/bfh/Main.java
+++ b/calculator-java/src/main/java/ch/bfh/Main.java
@@ -1,25 +1,25 @@
package ch.bfh;
-import ch.bfh.parser.ExpressionParser;
+import ch.bfh.parser.StatementParser;
+
import java.util.Scanner;
public class Main {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
+ StatementParser sp = new StatementParser();
while (true) {
- CalculatorLexer cl = new CalculatorLexer();
System.out.print("Type your expression: ");
String expression = scanner.nextLine();
try {
- cl.initLexer(expression);
- ExpressionParser ep = new ExpressionParser(cl);
- System.out.println(ep.getValue());
+ sp.parseStatement(expression);
+ System.out.println(sp.getValue());
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
-}
+} \ No newline at end of file
diff --git a/calculator-java/src/main/java/ch/bfh/parser/ExpressionParser.java b/calculator-java/src/main/java/ch/bfh/parser/ExpressionParser.java
index f96353e..3ec0194 100644
--- a/calculator-java/src/main/java/ch/bfh/parser/ExpressionParser.java
+++ b/calculator-java/src/main/java/ch/bfh/parser/ExpressionParser.java
@@ -10,21 +10,27 @@ public class ExpressionParser extends Parser {
protected ArrayList<Parser> parsers = new ArrayList<>();
protected ArrayList<Token> ops = new ArrayList<>();
+ private boolean isParenthesised = false;
public ExpressionParser(CalculatorLexer cl) {
this.cl = cl;
parse();
}
- private ExpressionParser(CalculatorLexer cl, Token lastToken) {
+ protected ExpressionParser(CalculatorLexer cl, Token lastToken, boolean isParenthesised) {
this.cl = cl;
this.lastToken = lastToken;
- parse();
+ this.isParenthesised = isParenthesised; //If an expression starts with the lastToken set as '(' it could mean that the Expression first term will be a parenthesised expression
+ parse(); //But it could also mean that this Expression is the parenthesised one, that is why this variable is required.
}
@Override
protected void parse() {
- Token token = cl.nextToken();
+ Token token;
+ if(lastToken != null && !isParenthesised)
+ token = lastToken;
+ else
+ token = cl.nextToken();
Parser l = null;
Parser r = null; // left and right expressions
Token op = null; // operation of the current l and right expressions
@@ -58,6 +64,7 @@ public class ExpressionParser extends Parser {
this.ops.add(op);
} // Then the '-' is the start of a new expression and not an operation
case Token.PAL:
+ case Token.ID:
case Token.NUM:
if (l == null) {
l = new TermParser(cl, token);
@@ -104,171 +111,4 @@ public class ExpressionParser extends Parser {
}
return result;
}
-
- private class TermParser extends ExpressionParser {
-
- public TermParser(CalculatorLexer cl, Token lastToken) {
- super(cl, lastToken);
- }
-
- @Override
- protected void parse() {
- Token token = lastToken;
- lastToken = null;
- Parser l = null;
- Parser r = null; // left and right expressions
- Token op = null; // operation of the current l and right expressions
-
- loop:
- while (token != null && token.type != Token.EOL) {
- switch (token.type) {
- case Token.ADD:
- if (op != null) {
- lastToken = token;
- break loop; // going here on case 'n/+...' or 'n*+...' since '+' would be invalid
- }
- case Token.SUB:
- case Token.NUM:
- case Token.PAL:
- if (l == null) {
- l = new FactorParser(cl, token);
- this.parsers.add(l);
- token = l.lastToken;
- continue loop;
- } else if (op != null) {
- r = new FactorParser(cl, token);
- this.parsers.add(r);
- token = r.lastToken;
- lastToken = token; // in case we finished
- continue loop;
- } else {
- lastToken = token;
- break loop; // going here on case 'n+...' or 'n-...' since '+' and '-' are part of the parent expression
- }
- case Token.MUL:
- case Token.DIV:
- if (l != null && op == null) { // if the '*' or '/' signs are between two Factors (op)
- op = token;
- this.ops.add(op);
- } else if (l != null && r != null) { // if we already found a 'l op r' we roll
- l = r;
- r = null;
- op = token;
- this.ops.add(op);
- } else
- throw new ParserException("Two '" + token.str + "' in a row were found.");
- break;
- case Token.PAR: // Going as high as possible --> possibly the end of an expression created by a Factor :)
- lastToken = token;
- break loop;
- default:
- //TODO - Replace default by each individual case
- throw new ParserException("Malformed Expression.");
- }
- token = cl.nextToken();
- }
- if (op != null && r == null)
- throw new ParserException("Missing expression after last operation '"+op.str+"'");
- }
-
- @Override
- public double getValue(){
- double result = 0.0;
- if (!this.parsers.isEmpty()) {
- result = this.parsers.get(0).getValue();
-
- for (int i = 1; i < this.parsers.size(); i++) {
- switch (this.ops.get(i-1).type){
- case Token.MUL:
- result *= this.parsers.get(i).getValue();
- break;
- case Token.DIV:
- result /= this.parsers.get(i).getValue();
- break;
- }
- }
- }
- return result;
- }
-
- private class FactorParser extends Parser {
-
- ExpressionParser expression; // only used when parenthesis are found
- boolean isNegative = false;
- boolean somethingWasParsed = false;
-
- public FactorParser(CalculatorLexer cl, Token lastToken) {
- this.cl = cl;
- this.lastToken = lastToken;
- parse();
- }
-
- @Override
- protected void parse() {
- Token token = lastToken;
- lastToken = null;
- loop:
- while (token != null && token.type != Token.EOL) {
- switch (token.type) {
- case Token.MUL:
- case Token.DIV:
- case Token.ADD:
- if (somethingWasParsed) { // finished parsing the Factor
- lastToken = token; // sending the token to the parent
- break loop;
- } else
- throw new ParserException("Empty expression before '" + token.str + "'.");
- case Token.SUB:
- if (!somethingWasParsed)
- if (!isNegative)
- isNegative = true;
- else
- throw new ParserException("A factor cannot contain two '-'.");
- else { // Meaning we could have parsed '-4' or '4' and '-' is the next digit which is op for the overall expression
- lastToken = token;
- break loop;
- }
- break;
- case Token.NUM:
- if (!somethingWasParsed) {
- value = token.value;
- somethingWasParsed = true;
- } else
- throw new ParserException("Missing operator between the two Factors.");
- break;
- case Token.PAL:
- if (!somethingWasParsed) {
- expression = new ExpressionParser(cl, token);
- if (expression.lastToken.type != Token.PAR)
- throw new ParserException("No matching closing parenthesis were found.");
- somethingWasParsed = true;
- } else
- throw new ParserException("Missing operator between the two Factors.");
- break;
- case Token.PAR: // Going as high as possible --> possibly the end of an expression created by a Factor :)
- if (somethingWasParsed)
- lastToken = token;
- else throw new ParserException("Missing expression before closing the parenthesis.");
- break loop;
- default:
- //TODO - Replace default by each individual case
- throw new ParserException("Malformed Expression.");
- }
- token = cl.nextToken();
- }
- }
-
- @Override
- public double getValue(){
- if (expression != null)
- if (isNegative)
- return expression.getValue()*(-1);
- else
- return expression.getValue();
- if (isNegative)
- return value*(-1);
- return value;
- }
- }
- }
}
diff --git a/calculator-java/src/main/java/ch/bfh/parser/FactorParser.java b/calculator-java/src/main/java/ch/bfh/parser/FactorParser.java
new file mode 100644
index 0000000..8bb5ef7
--- /dev/null
+++ b/calculator-java/src/main/java/ch/bfh/parser/FactorParser.java
@@ -0,0 +1,94 @@
+package ch.bfh.parser;
+
+import ch.bfh.CalculatorLexer;
+import ch.bfh.Token;
+import ch.bfh.exceptions.ParserException;
+
+class FactorParser extends Parser {
+
+ ExpressionParser expression; // only used when parenthesis are found
+ boolean isNegative = false;
+ boolean somethingWasParsed = false;
+
+ public FactorParser(CalculatorLexer cl, Token lastToken) {
+ this.cl = cl;
+ this.lastToken = lastToken;
+ parse();
+ }
+
+ @Override
+ protected void parse() {
+ Token token = lastToken;
+ lastToken = null;
+ loop:
+ while (token != null && token.type != Token.EOL) {
+ switch (token.type) {
+ case Token.MUL:
+ case Token.DIV:
+ case Token.ADD:
+ if (somethingWasParsed) { // finished parsing the Factor
+ lastToken = token; // sending the token to the parent
+ break loop;
+ } else
+ throw new ParserException("Empty expression before '" + token.str + "'.");
+ case Token.SUB:
+ if (!somethingWasParsed)
+ if (!isNegative)
+ isNegative = true;
+ else
+ throw new ParserException("A factor cannot contain two '-'.");
+ else { // Meaning we could have parsed '-4' or '4' and '-' is the next digit which is op for the overall expression
+ lastToken = token;
+ break loop;
+ }
+ break;
+ case Token.NUM:
+ if (!somethingWasParsed) {
+ value = token.value;
+ somethingWasParsed = true;
+ } else
+ throw new ParserException("Missing operator between the two Factors.");
+ break;
+ case Token.ID:
+ if (!somethingWasParsed) {
+ expression = variables.get(token.str);
+ if (expression == null)
+ throw new ParserException("'"+token.str+"' is not yet defined.");
+ somethingWasParsed = true;
+ } else
+ throw new ParserException("Missing operator between the two Factors.");
+ break;
+ case Token.PAL:
+ if (!somethingWasParsed) {
+ expression = new ExpressionParser(cl, token, true);
+ if (expression.lastToken.type != Token.PAR)
+ throw new ParserException("No matching closing parenthesis were found.");
+ somethingWasParsed = true;
+ } else
+ throw new ParserException("Missing operator between the two Factors.");
+ break;
+ case Token.PAR: // Going as high as possible --> possibly the end of an expression created by a Factor :)
+ if (somethingWasParsed)
+ lastToken = token;
+ else throw new ParserException("Missing expression before closing the parenthesis.");
+ break loop;
+ default:
+ //TODO - Replace default by each individual case
+ throw new ParserException("Malformed Expression.");
+ }
+ token = cl.nextToken();
+ }
+ }
+
+ @Override
+ public double getValue() {
+ if (expression != null)
+ if (isNegative)
+ return expression.getValue() * (-1);
+ else
+ return expression.getValue();
+ if (isNegative)
+ return value * (-1);
+ return value;
+ }
+}
diff --git a/calculator-java/src/main/java/ch/bfh/parser/Parser.java b/calculator-java/src/main/java/ch/bfh/parser/Parser.java
index 9e048a6..333b942 100644
--- a/calculator-java/src/main/java/ch/bfh/parser/Parser.java
+++ b/calculator-java/src/main/java/ch/bfh/parser/Parser.java
@@ -3,12 +3,17 @@ package ch.bfh.parser;
import ch.bfh.CalculatorLexer;
import ch.bfh.Token;
+import java.util.HashMap;
+import java.util.Map;
+
abstract class Parser{
+
+ protected static Map<String, ExpressionParser> variables = new HashMap<>(); //'Persisted' Expressions when 'let' token was specified
+
protected CalculatorLexer cl;
protected Token lastToken;
protected double value;
protected abstract void parse();
public abstract double getValue();
-}
-
+} \ No newline at end of file
diff --git a/calculator-java/src/main/java/ch/bfh/parser/StatementParser.java b/calculator-java/src/main/java/ch/bfh/parser/StatementParser.java
new file mode 100644
index 0000000..c85398b
--- /dev/null
+++ b/calculator-java/src/main/java/ch/bfh/parser/StatementParser.java
@@ -0,0 +1,70 @@
+package ch.bfh.parser;
+
+import ch.bfh.CalculatorLexer;
+import ch.bfh.Token;
+import ch.bfh.exceptions.ParserException;
+
+public class StatementParser extends Parser{
+
+ String input;
+ ExpressionParser parsedExpression;
+
+ public void parseStatement(String input){
+ this.input = input;
+ cl = new CalculatorLexer();
+ cl.initLexer(input);
+ parsedExpression = null;
+ parse();
+ }
+
+ @Override
+ protected void parse() {
+ Token token = cl.nextToken();
+ String variableName = null;
+ lastToken = null;
+ loop:
+ while (token != null && token.type != Token.EOL) {
+ switch (token.type) {
+ case Token.LET:
+ if (lastToken != null)
+ throw new ParserException("The keyword 'let' cannot be placed anywhere else than at the beginning of an expression.");
+ break;
+ case Token.EQU:
+ if (lastToken == null || lastToken.type != Token.LET || variableName == null)
+ throw new ParserException("The inputted token '=' can only be placed in a variable declaration context (let variable = Expression).");
+ break;
+ case Token.END:
+ if (lastToken != null)
+ throw new ParserException("The keyword 'exit' cannot be placed anywhere else than at the beginning of an expression.");
+ else
+ System.exit(0);
+ case Token.ID:
+ if (lastToken != null && lastToken.type == Token.LET && variableName == null) { // we are defining a new variable
+ variableName = token.str;
+ token = cl.nextToken();
+ continue loop; // lastToken value will thus still be 'let' so that we could then parse the '=' token that is supposed to come next correctly.
+ }else if (variableName != null && lastToken != null && lastToken.type != Token.EQU)
+ throw new ParserException("Two consecutive variables ('"+variableName+"' and '"+token.str+"') were found in the declaration context.");
+ // the expression started with a variable -> no definition -> meaning it is an expression
+ default:
+ parsedExpression = new ExpressionParser(cl, token, false);
+ if (lastToken != null && lastToken.type == Token.EQU) // this still need to be put in the variables list
+ variables.put(variableName, parsedExpression);
+ break;
+ }
+ lastToken = token;
+ token = cl.nextToken();
+ }
+ if (lastToken == null)
+ return;
+ if (lastToken.type == Token.LET || lastToken.type == Token.EQU)
+ throw new ParserException("Incomplete variable declaration");
+ }
+
+ @Override
+ public double getValue() {
+ if (parsedExpression == null)
+ return 0.0;
+ return parsedExpression.getValue();
+ }
+} \ No newline at end of file
diff --git a/calculator-java/src/main/java/ch/bfh/parser/TermParser.java b/calculator-java/src/main/java/ch/bfh/parser/TermParser.java
new file mode 100644
index 0000000..9a97659
--- /dev/null
+++ b/calculator-java/src/main/java/ch/bfh/parser/TermParser.java
@@ -0,0 +1,93 @@
+package ch.bfh.parser;
+
+import ch.bfh.CalculatorLexer;
+import ch.bfh.Token;
+import ch.bfh.exceptions.ParserException;
+
+class TermParser extends ExpressionParser {
+
+ public TermParser(CalculatorLexer cl, Token lastToken) {
+ super(cl, lastToken, false);
+ }
+
+ @Override
+ protected void parse() {
+ Token token = lastToken;
+ lastToken = null;
+ Parser l = null;
+ Parser r = null; // left and right expressions
+ Token op = null; // operation of the current l and right expressions
+
+ loop:
+ while (token != null && token.type != Token.EOL) {
+ switch (token.type) {
+ case Token.ADD:
+ if (op != null) {
+ lastToken = token;
+ break loop; // going here on case 'n/+...' or 'n*+...' since '+' would be invalid
+ }
+ case Token.SUB:
+ case Token.NUM:
+ case Token.ID:
+ case Token.PAL:
+ if (l == null) {
+ l = new FactorParser(cl, token);
+ this.parsers.add(l);
+ token = l.lastToken;
+ continue loop;
+ } else if (op != null) {
+ r = new FactorParser(cl, token);
+ this.parsers.add(r);
+ token = r.lastToken;
+ lastToken = token; // in case we finished
+ continue loop;
+ } else {
+ lastToken = token;
+ break loop; // going here on case 'n+...' or 'n-...' since '+' and '-' are part of the parent expression
+ }
+ case Token.MUL:
+ case Token.DIV:
+ if (l != null && op == null) { // if the '*' or '/' signs are between two Factors (op)
+ op = token;
+ this.ops.add(op);
+ } else if (l != null && r != null) { // if we already found a 'l op r' we roll
+ l = r;
+ r = null;
+ op = token;
+ this.ops.add(op);
+ } else
+ throw new ParserException("Two '" + token.str + "' in a row were found.");
+ break;
+ case Token.PAR: // Going as high as possible --> possibly the end of an expression created by a Factor :)
+ lastToken = token;
+ break loop;
+ default:
+ //TODO - Replace default by each individual case
+ throw new ParserException("Malformed Expression.");
+ }
+ token = cl.nextToken();
+ }
+ if (op != null && r == null)
+ throw new ParserException("Missing expression after last operation '" + op.str + "'");
+ }
+
+ @Override
+ public double getValue() {
+ double result = 0.0;
+ if (!this.parsers.isEmpty()) {
+ result = this.parsers.get(0).getValue();
+
+ for (int i = 1; i < this.parsers.size(); i++) {
+ switch (this.ops.get(i - 1).type) {
+ case Token.MUL:
+ result *= this.parsers.get(i).getValue();
+ break;
+ case Token.DIV:
+ result /= this.parsers.get(i).getValue();
+ break;
+ }
+ }
+ }
+ return result;
+ }
+} \ No newline at end of file
diff --git a/calculator-java/src/test/java/ExpressionParserTest.java b/calculator-java/src/test/java/ExpressionParserTest.java
index 8a9814c..b15e844 100644
--- a/calculator-java/src/test/java/ExpressionParserTest.java
+++ b/calculator-java/src/test/java/ExpressionParserTest.java
@@ -110,6 +110,6 @@ public class ExpressionParserTest {
assertEquals(6.75, ep.getValue(), 0.0);
}
- //TODO - test error messages
+ //TODO - test error detection
}
diff --git a/calculator-java/src/test/java/StatementParserTest.java b/calculator-java/src/test/java/StatementParserTest.java
new file mode 100644
index 0000000..4b2e635
--- /dev/null
+++ b/calculator-java/src/test/java/StatementParserTest.java
@@ -0,0 +1,91 @@
+import ch.bfh.CalculatorLexer;
+import ch.bfh.parser.StatementParser;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class StatementParserTest {
+
+ StatementParser sp = new StatementParser();
+ // Only testing correct expressions first
+ @Test
+ public void emptyExpression() {
+ sp.parseStatement("");
+ assertEquals(0.0, sp.getValue(), 0.0);
+ }
+
+ @Test
+ public void oneFactor() {
+ sp.parseStatement("10");
+ assertEquals(10.0, sp.getValue(), 0.0);
+ }
+
+ @Test
+ public void oneParenthesisedFactor() {
+ sp.parseStatement("(10)");
+ assertEquals(10.0, sp.getValue(), 0.0);
+ }
+
+ @Test
+ public void oneNegativeFactor() {
+ sp.parseStatement("-10");
+ assertEquals(-10.0, sp.getValue(), 0.0);
+ }
+
+ @Test
+ public void oneNegativeParenthesisedFactor() {
+ sp.parseStatement("-(10)");
+ assertEquals(-10.0, sp.getValue(), 0.0);
+ }
+
+ @Test
+ public void oneNegativeParenthesisedNegativeFactor() {
+ sp.parseStatement("-(-10)");
+ assertEquals(10.0, sp.getValue(), 0.0);
+ }
+
+ @Test
+ public void twoFactorSum() {
+ sp.parseStatement("2+3");
+ assertEquals(5.0, sp.getValue(), 0.0);
+ }
+
+ @Test
+ public void twoFactorSub() {
+ sp.parseStatement("2-3");
+ assertEquals(-1.0, sp.getValue(), 0.0);
+ }
+
+ @Test
+ public void twoFactorSumWithSub() {
+ sp.parseStatement("2--3");
+ assertEquals(5.0, sp.getValue(), 0.0);
+ }
+
+ @Test
+ public void twoFactorSumWithSubParenthesised() {
+ sp.parseStatement("2-(-3)");
+ assertEquals(5.0, sp.getValue(), 0.0);
+ }
+
+ @Test
+ public void twoFactorMul() {
+ sp.parseStatement("2*3");
+ assertEquals(6.0, sp.getValue(), 0.0);
+ }
+
+ @Test
+ public void twoFactorDiv() {
+ sp.parseStatement("11/4");
+ assertEquals(2.75, sp.getValue(), 0.0);
+ }
+
+ @Test
+ public void completeArithmeticOperationWithPriorities() {
+ sp.parseStatement("(4+5)*3/4");
+ assertEquals(6.75, sp.getValue(), 0.0);
+ }
+
+ //TODO - test error detection
+
+}