diff options
author | Maël Gassmann <mael.gassmann@students.bfh.ch> | 2021-06-11 17:28:55 +0200 |
---|---|---|
committer | Maël Gassmann <mael.gassmann@students.bfh.ch> | 2021-06-11 17:28:55 +0200 |
commit | 98ddff97ec1d092b18ef2d176e83bd92f9671e03 (patch) | |
tree | af1457e05063421dc2860e6c033e58b28f9a8e4e /calculator-java | |
parent | 49f30529d9cd2ed62902079a45d4d3c1f45afbda (diff) |
[+] Added StatementParser, [~] Restructured the Parsers
Diffstat (limited to 'calculator-java')
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 + +} |