From fed70f3ac817ed4943a230cb901fcd65dc6fdd0c Mon Sep 17 00:00:00 2001 From: Maƫl Gassmann Date: Fri, 11 Jun 2021 20:55:12 +0200 Subject: [~] First totaly functional Calculator, reorganised the files, verified and added error messages --- .../src/main/java/ch/bfh/CalculatorLexer.java | 106 --------------------- calculator-java/src/main/java/ch/bfh/Main.java | 7 +- calculator-java/src/main/java/ch/bfh/Token.java | 23 ----- .../java/ch/bfh/exceptions/LexerException.java | 5 - .../java/ch/bfh/exceptions/ParserException.java | 5 - .../main/java/ch/bfh/lexer/CalculatorLexer.java | 105 ++++++++++++++++++++ .../src/main/java/ch/bfh/lexer/LexerException.java | 5 + .../src/main/java/ch/bfh/lexer/Token.java | 23 +++++ .../main/java/ch/bfh/parser/ExpressionParser.java | 36 ++++--- .../src/main/java/ch/bfh/parser/FactorParser.java | 15 +-- .../src/main/java/ch/bfh/parser/Parser.java | 7 +- .../main/java/ch/bfh/parser/ParserException.java | 8 ++ .../main/java/ch/bfh/parser/StatementParser.java | 13 ++- .../src/main/java/ch/bfh/parser/TermParser.java | 13 +-- .../src/test/java/StatementParserTest.java | 3 + 15 files changed, 189 insertions(+), 185 deletions(-) delete mode 100644 calculator-java/src/main/java/ch/bfh/CalculatorLexer.java delete mode 100644 calculator-java/src/main/java/ch/bfh/Token.java delete mode 100644 calculator-java/src/main/java/ch/bfh/exceptions/LexerException.java delete mode 100644 calculator-java/src/main/java/ch/bfh/exceptions/ParserException.java create mode 100644 calculator-java/src/main/java/ch/bfh/lexer/CalculatorLexer.java create mode 100644 calculator-java/src/main/java/ch/bfh/lexer/LexerException.java create mode 100644 calculator-java/src/main/java/ch/bfh/lexer/Token.java create mode 100644 calculator-java/src/main/java/ch/bfh/parser/ParserException.java diff --git a/calculator-java/src/main/java/ch/bfh/CalculatorLexer.java b/calculator-java/src/main/java/ch/bfh/CalculatorLexer.java deleted file mode 100644 index 48d009e..0000000 --- a/calculator-java/src/main/java/ch/bfh/CalculatorLexer.java +++ /dev/null @@ -1,106 +0,0 @@ -package ch.bfh; -import ch.bfh.exceptions.LexerException; - -// Lexer for classical arithmetic expressions with identifiers and assignments. -// Scans a source string char by char. - -public class CalculatorLexer { - - private String src; // source string for lexical analysis - private int idx; // current index in source - private int len; // length of source - - public CalculatorLexer() { } - - public void initLexer(String source) { - this.src = source; - idx = 0; - len = src.length(); - } - - // Consumes letters only and builds an identifier - private String identifier() { - StringBuffer s = new StringBuffer(); - - do { - s.append(src.charAt(idx)); - idx++; - } while (idx < len && Character.isLetter(src.charAt(idx))); - return s.toString(); - } - - // Consumes digits and convert integer part and decimal part - // Convert characters using the formula - // "3456.253" = [(((3+0)*10+4)*10+5)*10+6]+[0.1*2+0.01*5+0.001*3] - private double number() throws LexerException { - double v = 0; // accumulates the result - double factor = 0.1; // factor for decimal part - - do { // integer part - v = v * 10 + Character.digit(src.charAt(idx),30); - idx++; - } while (idx < len && Character.isDigit(src.charAt(idx))); - if (idx < len && src.charAt(idx) == '.') { // decimal point - idx++; - if (idx < len && Character.isDigit(src.charAt(idx))) { // decimal part - while (idx < len && Character.isDigit(src.charAt(idx))) { - v = v + (factor * Character.digit(src.charAt(idx),30)); - factor = factor * 0.1; - idx++; - } - } - else throw new LexerException("Illegal number: decimal part missing"); - } - return v; - } - - // Skips blanks, tabs, newlines - private void skip() { - char c; - while (idx < len) { - c = src.charAt(idx); - if (c==' ' || c=='\t' || c=='\n') idx++; - else break; - } - } - - // returns next token - public Token nextToken() throws LexerException { - Token tok = new Token(); - - skip(); - if (idx>=len) { - tok.str="EOL"; - tok.type=Token.EOL; - } - else - // is it a positive number? - if (Character.isDigit(src.charAt(idx))) { - tok.value = number(); - tok.type = Token.NUM; - tok.str = Double.toString(tok.value); - } - else - if (Character.isLetter(src.charAt(idx))) { - tok.value = 0; - tok.type = Token.ID; - tok.str = identifier(); - if (tok.str.compareTo("let")==0) tok.type = Token.LET; - if (tok.str.compareTo("exit")==0) tok.type = Token.END; - } - else { - switch (src.charAt(idx)) { - case '+': tok.type = Token.ADD; tok.str = "+"; break; - case '-': tok.type = Token.SUB; tok.str = "-"; break; - case '*': tok.type = Token.MUL; tok.str = "*"; break; - case '/': tok.type = Token.DIV; tok.str = "/"; break; - case '(': tok.type = Token.PAL; tok.str = "("; break; - case ')': tok.type = Token.PAR; tok.str = ")"; break; - case '=': tok.type = Token.EQU; tok.str = "="; break; - default : throw new LexerException("Illegal Token: '" + src.charAt(idx) + "'"); - } - idx++; - } - return tok; - } -} \ No newline at end of file diff --git a/calculator-java/src/main/java/ch/bfh/Main.java b/calculator-java/src/main/java/ch/bfh/Main.java index 5dc8a91..ea01185 100644 --- a/calculator-java/src/main/java/ch/bfh/Main.java +++ b/calculator-java/src/main/java/ch/bfh/Main.java @@ -1,7 +1,8 @@ package ch.bfh; +import ch.bfh.lexer.LexerException; +import ch.bfh.parser.ParserException; import ch.bfh.parser.StatementParser; - import java.util.Scanner; public class Main { @@ -16,8 +17,8 @@ public class Main { try { sp.parseStatement(expression); - System.out.println(sp.getValue()); - } catch (Exception e) { + System.out.println("Result: " + sp.getValue()); + } catch (LexerException | ParserException e) { System.out.println(e.getMessage()); } } diff --git a/calculator-java/src/main/java/ch/bfh/Token.java b/calculator-java/src/main/java/ch/bfh/Token.java deleted file mode 100644 index 93d4d02..0000000 --- a/calculator-java/src/main/java/ch/bfh/Token.java +++ /dev/null @@ -1,23 +0,0 @@ -package ch.bfh; - -// Various Tokens for arithmetic expressions based on integers -// with identifiers and assignments - -public class Token { - public int type; // token type - public double value; // numerical value for NUM - public String str; // token string - - public static final int EOL=0; // End Of Line - public static final int PAL=1; // Left Parenthesis - public static final int PAR=2; // Right Parenthesis - public static final int ADD=3; // operators - public static final int SUB=4; - public static final int MUL=5; - public static final int DIV=6; - public static final int NUM=7; // number - public static final int EQU=8; // equal - public static final int LET=9; // let - public static final int ID=10; // identifier - public static final int END=11; // exit -} diff --git a/calculator-java/src/main/java/ch/bfh/exceptions/LexerException.java b/calculator-java/src/main/java/ch/bfh/exceptions/LexerException.java deleted file mode 100644 index 082beee..0000000 --- a/calculator-java/src/main/java/ch/bfh/exceptions/LexerException.java +++ /dev/null @@ -1,5 +0,0 @@ -package ch.bfh.exceptions; - -public class LexerException extends RuntimeException { - public LexerException(String s) { super(s); } -} \ No newline at end of file diff --git a/calculator-java/src/main/java/ch/bfh/exceptions/ParserException.java b/calculator-java/src/main/java/ch/bfh/exceptions/ParserException.java deleted file mode 100644 index ca6d037..0000000 --- a/calculator-java/src/main/java/ch/bfh/exceptions/ParserException.java +++ /dev/null @@ -1,5 +0,0 @@ -package ch.bfh.exceptions; - -public class ParserException extends RuntimeException { - public ParserException(String s) { super(s); } -} \ No newline at end of file diff --git a/calculator-java/src/main/java/ch/bfh/lexer/CalculatorLexer.java b/calculator-java/src/main/java/ch/bfh/lexer/CalculatorLexer.java new file mode 100644 index 0000000..0ba9535 --- /dev/null +++ b/calculator-java/src/main/java/ch/bfh/lexer/CalculatorLexer.java @@ -0,0 +1,105 @@ +package ch.bfh.lexer; + +// Lexer for classical arithmetic expressions with identifiers and assignments. +// Scans a source string char by char. + +public class CalculatorLexer { + + private String src; // source string for lexical analysis + private int idx; // current index in source + private int len; // length of source + + public CalculatorLexer() { } + + public void initLexer(String source) { + this.src = source; + idx = 0; + len = src.length(); + } + + // Consumes letters only and builds an identifier + private String identifier() { + StringBuffer s = new StringBuffer(); + + do { + s.append(src.charAt(idx)); + idx++; + } while (idx < len && Character.isLetter(src.charAt(idx))); + return s.toString(); + } + + // Consumes digits and convert integer part and decimal part + // Convert characters using the formula + // "3456.253" = [(((3+0)*10+4)*10+5)*10+6]+[0.1*2+0.01*5+0.001*3] + private double number() throws LexerException { + double v = 0; // accumulates the result + double factor = 0.1; // factor for decimal part + + do { // integer part + v = v * 10 + Character.digit(src.charAt(idx),30); + idx++; + } while (idx < len && Character.isDigit(src.charAt(idx))); + if (idx < len && src.charAt(idx) == '.') { // decimal point + idx++; + if (idx < len && Character.isDigit(src.charAt(idx))) { // decimal part + while (idx < len && Character.isDigit(src.charAt(idx))) { + v = v + (factor * Character.digit(src.charAt(idx),30)); + factor = factor * 0.1; + idx++; + } + } + else throw new LexerException("Illegal number: decimal part missing"); + } + return v; + } + + // Skips blanks, tabs, newlines + private void skip() { + char c; + while (idx < len) { + c = src.charAt(idx); + if (c==' ' || c=='\t' || c=='\n') idx++; + else break; + } + } + + // returns next token + public Token nextToken() throws LexerException { + Token tok = new Token(); + + skip(); + if (idx>=len) { + tok.str="EOL"; + tok.type=Token.EOL; + } + else + // is it a positive number? + if (Character.isDigit(src.charAt(idx))) { + tok.value = number(); + tok.type = Token.NUM; + tok.str = Double.toString(tok.value); + } + else + if (Character.isLetter(src.charAt(idx))) { + tok.value = 0; + tok.type = Token.ID; + tok.str = identifier(); + if (tok.str.compareTo("let")==0) tok.type = Token.LET; + if (tok.str.compareTo("exit")==0) tok.type = Token.END; + } + else { + switch (src.charAt(idx)) { + case '+': tok.type = Token.ADD; tok.str = "+"; break; + case '-': tok.type = Token.SUB; tok.str = "-"; break; + case '*': tok.type = Token.MUL; tok.str = "*"; break; + case '/': tok.type = Token.DIV; tok.str = "/"; break; + case '(': tok.type = Token.PAL; tok.str = "("; break; + case ')': tok.type = Token.PAR; tok.str = ")"; break; + case '=': tok.type = Token.EQU; tok.str = "="; break; + default : throw new LexerException("Illegal Token: '" + src.charAt(idx) + "'"); + } + idx++; + } + return tok; + } +} \ No newline at end of file diff --git a/calculator-java/src/main/java/ch/bfh/lexer/LexerException.java b/calculator-java/src/main/java/ch/bfh/lexer/LexerException.java new file mode 100644 index 0000000..c5f8671 --- /dev/null +++ b/calculator-java/src/main/java/ch/bfh/lexer/LexerException.java @@ -0,0 +1,5 @@ +package ch.bfh.lexer; + +public class LexerException extends RuntimeException { + public LexerException(String s) { super(s); } +} \ No newline at end of file diff --git a/calculator-java/src/main/java/ch/bfh/lexer/Token.java b/calculator-java/src/main/java/ch/bfh/lexer/Token.java new file mode 100644 index 0000000..b021075 --- /dev/null +++ b/calculator-java/src/main/java/ch/bfh/lexer/Token.java @@ -0,0 +1,23 @@ +package ch.bfh.lexer; + +// Various Tokens for arithmetic expressions based on integers +// with identifiers and assignments + +public class Token { + public int type; // token type + public double value; // numerical value for NUM + public String str; // token string + + public static final int EOL=0;// // End Of Line + public static final int PAL=1;// // Left Parenthesis + public static final int PAR=2;// // Right Parenthesis + public static final int ADD=3;// // operators + public static final int SUB=4;// + public static final int MUL=5; + public static final int DIV=6; + public static final int NUM=7;// // number + public static final int EQU=8; // equal + public static final int LET=9; // let + public static final int ID=10;// // identifier + public static final int END=11; // exit +} \ 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 3ec0194..e68983b 100644 --- a/calculator-java/src/main/java/ch/bfh/parser/ExpressionParser.java +++ b/calculator-java/src/main/java/ch/bfh/parser/ExpressionParser.java @@ -1,22 +1,15 @@ package ch.bfh.parser; -import ch.bfh.CalculatorLexer; -import ch.bfh.Token; -import ch.bfh.exceptions.ParserException; - +import ch.bfh.lexer.CalculatorLexer; +import ch.bfh.lexer.Token; import java.util.ArrayList; -public class ExpressionParser extends Parser { +class ExpressionParser extends Parser { protected ArrayList parsers = new ArrayList<>(); protected ArrayList ops = new ArrayList<>(); private boolean isParenthesised = false; - public ExpressionParser(CalculatorLexer cl) { - this.cl = cl; - parse(); - } - protected ExpressionParser(CalculatorLexer cl, Token lastToken, boolean isParenthesised) { this.cl = cl; this.lastToken = lastToken; @@ -31,8 +24,8 @@ public class ExpressionParser extends Parser { token = lastToken; else token = cl.nextToken(); - Parser l = null; - Parser r = null; // left and right expressions + TermParser l = null; + TermParser r = null; // left and right Terms Token op = null; // operation of the current l and right expressions loop: @@ -48,9 +41,9 @@ public class ExpressionParser extends Parser { op = token; this.ops.add(op); }else if (l == null) - throw new ParserException("Factor cannot start with '+'."); + throw new ParserException("Redundant use of '+' is forbidden."); else - throw new ParserException("Invalid use of '+' was found in the Expression."); + throw new ParserException(token, "repetition of operator -> a term was expected."); break; case Token.SUB: if (l != null && op == null) { // if the '-' sign is between two term (op) @@ -82,14 +75,19 @@ public class ExpressionParser extends Parser { break loop; } else throw new ParserException("No matching opening parenthesis were found."); - default: - //TODO - Replace default by each individual case - throw new ParserException("Malformed Expression."); + case Token.MUL: + case Token.DIV: + throw new ParserException(token,"a term was expected."); + case Token.EQU: + case Token.LET: + throw new ParserException("The inputted token '"+token.str+"' can only be placed in a variable declaration context (let var = Expression)."); + case Token.END: + throw new ParserException("The keyword 'exit' can only be placed at the beginning of an expression."); } token = cl.nextToken(); } if (op != null && r == null) - throw new ParserException("Missing expression after last operation '"+op.str+"'"); + throw new ParserException("Missing term after the last operator '"+op.str+"'."); } @Override @@ -111,4 +109,4 @@ public class ExpressionParser extends Parser { } return result; } -} +} \ No newline at end of file diff --git a/calculator-java/src/main/java/ch/bfh/parser/FactorParser.java b/calculator-java/src/main/java/ch/bfh/parser/FactorParser.java index 8bb5ef7..fd54cf4 100644 --- a/calculator-java/src/main/java/ch/bfh/parser/FactorParser.java +++ b/calculator-java/src/main/java/ch/bfh/parser/FactorParser.java @@ -1,8 +1,7 @@ package ch.bfh.parser; -import ch.bfh.CalculatorLexer; -import ch.bfh.Token; -import ch.bfh.exceptions.ParserException; +import ch.bfh.lexer.CalculatorLexer; +import ch.bfh.lexer.Token; class FactorParser extends Parser { @@ -72,9 +71,11 @@ class FactorParser extends Parser { 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."); + case Token.EQU: + case Token.LET: + throw new ParserException("The inputted token '"+token.str+"' can only be placed in a variable declaration context (let var = Expression)."); + case Token.END: + throw new ParserException("The keyword 'exit' can only be placed at the beginning of an expression."); } token = cl.nextToken(); } @@ -91,4 +92,4 @@ class FactorParser extends Parser { return value * (-1); return value; } -} +} \ No newline at end of file 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 333b942..6a03fe1 100644 --- a/calculator-java/src/main/java/ch/bfh/parser/Parser.java +++ b/calculator-java/src/main/java/ch/bfh/parser/Parser.java @@ -1,14 +1,13 @@ package ch.bfh.parser; -import ch.bfh.CalculatorLexer; -import ch.bfh.Token; - +import ch.bfh.lexer.CalculatorLexer; +import ch.bfh.lexer.Token; import java.util.HashMap; import java.util.Map; abstract class Parser{ - protected static Map variables = new HashMap<>(); //'Persisted' Expressions when 'let' token was specified + protected static Map variables = new HashMap<>(); //Persisted Expressions aka variables protected CalculatorLexer cl; protected Token lastToken; diff --git a/calculator-java/src/main/java/ch/bfh/parser/ParserException.java b/calculator-java/src/main/java/ch/bfh/parser/ParserException.java new file mode 100644 index 0000000..a85e3b2 --- /dev/null +++ b/calculator-java/src/main/java/ch/bfh/parser/ParserException.java @@ -0,0 +1,8 @@ +package ch.bfh.parser; + +import ch.bfh.lexer.Token; + +public class ParserException extends RuntimeException { + public ParserException(String s) { super(s); } + public ParserException(Token token, String s) { super("Last read token: '"+token.str+"', "+s); } +} \ 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 index c85398b..482ef34 100644 --- a/calculator-java/src/main/java/ch/bfh/parser/StatementParser.java +++ b/calculator-java/src/main/java/ch/bfh/parser/StatementParser.java @@ -1,8 +1,7 @@ package ch.bfh.parser; -import ch.bfh.CalculatorLexer; -import ch.bfh.Token; -import ch.bfh.exceptions.ParserException; +import ch.bfh.lexer.CalculatorLexer; +import ch.bfh.lexer.Token; public class StatementParser extends Parser{ @@ -27,15 +26,15 @@ public class StatementParser extends Parser{ 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."); + throw new ParserException("The keyword 'let' can only be placed 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)."); + throw new ParserException("The token '=' can only be placed in a variable declaration context (let var = 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."); + throw new ParserException("The keyword 'exit' can only be placed at the beginning of an expression."); else System.exit(0); case Token.ID: @@ -58,7 +57,7 @@ public class StatementParser extends Parser{ if (lastToken == null) return; if (lastToken.type == Token.LET || lastToken.type == Token.EQU) - throw new ParserException("Incomplete variable declaration"); + throw new ParserException("Incomplete variable declaration. Expected: let var = Expression."); } @Override diff --git a/calculator-java/src/main/java/ch/bfh/parser/TermParser.java b/calculator-java/src/main/java/ch/bfh/parser/TermParser.java index 9a97659..9e49fa8 100644 --- a/calculator-java/src/main/java/ch/bfh/parser/TermParser.java +++ b/calculator-java/src/main/java/ch/bfh/parser/TermParser.java @@ -1,8 +1,7 @@ package ch.bfh.parser; -import ch.bfh.CalculatorLexer; -import ch.bfh.Token; -import ch.bfh.exceptions.ParserException; +import ch.bfh.lexer.CalculatorLexer; +import ch.bfh.lexer.Token; class TermParser extends ExpressionParser { @@ -61,9 +60,11 @@ class TermParser extends ExpressionParser { 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."); + case Token.EQU: + case Token.LET: + throw new ParserException("The inputted token '"+token.str+"' can only be placed in a variable declaration context (let var = Expression)."); + case Token.END: + throw new ParserException("The keyword 'exit' can only be placed at the beginning of an expression."); } token = cl.nextToken(); } diff --git a/calculator-java/src/test/java/StatementParserTest.java b/calculator-java/src/test/java/StatementParserTest.java index 8c2db59..ee4ab6b 100644 --- a/calculator-java/src/test/java/StatementParserTest.java +++ b/calculator-java/src/test/java/StatementParserTest.java @@ -111,6 +111,9 @@ public class StatementParserTest { sp.parseStatement("b/a"); assertEquals(50.0, sp.getValue(), 0.0); + sp.parseStatement("b--a"); + assertEquals(255.0, sp.getValue(), 0.0); + sp.parseStatement("b+a/2"); assertEquals(252.5, sp.getValue(), 0.0); -- cgit v1.2.3