From c7175fc542285b739f79097d7e97356341b1b666 Mon Sep 17 00:00:00 2001 From: qwertzniki6 <104077966+bretzNiklas@users.noreply.github.com> Date: Tue, 10 Jan 2023 19:03:20 +0100 Subject: [PATCH] Added tests --- .idea/compiler.xml | 1 + .idea/misc.xml | 7 ++ Aufgabe4Maven/pom.xml | 39 +++++++++++ {Aufgabe4 => Aufgabe4Maven/src}/Ast.java | 2 +- .../src}/Evaluator.java | 2 +- .../src}/EvaluatorBackup.java | 2 +- Aufgabe4Maven/src/Exceptions.java | 33 +++++++++ {Aufgabe4 => Aufgabe4Maven/src}/Lexer.java | 2 + {Aufgabe4 => Aufgabe4Maven/src}/Parser.java | 66 +++++++++++------- {Aufgabe4 => Aufgabe4Maven/src}/Plotter.java | 0 .../src}/TokenType.java | 0 .../src}/ValuesToDraw.java | 0 .../src}/expressions.txt | 0 {Aufgabe4 => Aufgabe4Maven/src}/main.java | 7 +- {Aufgabe4 => Aufgabe4Maven/src}/test.java | 0 Aufgabe4Maven/tests/LexerTest.java | 60 ++++++++++++++++ out/production/inf3_git/Token.class | Bin 832 -> 0 bytes out/production/inf3_git/TokenType.class | Bin 971 -> 0 bytes out/production/inf3_git/main.class | Bin 4310 -> 0 bytes 19 files changed, 189 insertions(+), 32 deletions(-) create mode 100644 Aufgabe4Maven/pom.xml rename {Aufgabe4 => Aufgabe4Maven/src}/Ast.java (96%) rename {Aufgabe4 => Aufgabe4Maven/src}/Evaluator.java (97%) rename {Aufgabe4 => Aufgabe4Maven/src}/EvaluatorBackup.java (94%) create mode 100644 Aufgabe4Maven/src/Exceptions.java rename {Aufgabe4 => Aufgabe4Maven/src}/Lexer.java (97%) rename {Aufgabe4 => Aufgabe4Maven/src}/Parser.java (77%) rename {Aufgabe4 => Aufgabe4Maven/src}/Plotter.java (100%) rename {Aufgabe4 => Aufgabe4Maven/src}/TokenType.java (100%) rename {Aufgabe4 => Aufgabe4Maven/src}/ValuesToDraw.java (100%) rename {Aufgabe4 => Aufgabe4Maven/src}/expressions.txt (100%) rename {Aufgabe4 => Aufgabe4Maven/src}/main.java (94%) rename {Aufgabe4 => Aufgabe4Maven/src}/test.java (100%) create mode 100644 Aufgabe4Maven/tests/LexerTest.java delete mode 100644 out/production/inf3_git/Token.class delete mode 100644 out/production/inf3_git/TokenType.class delete mode 100644 out/production/inf3_git/main.class diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 0a6e501..ea6c1b3 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -6,6 +6,7 @@ <sourceOutputDir name="target/generated-sources/annotations" /> <sourceTestOutputDir name="target/generated-test-sources/test-annotations" /> <outputRelativeToContentRoot value="true" /> + <module name="Aufgabe4Maven" /> </profile> </annotationProcessing> <bytecodeTargetLevel> diff --git a/.idea/misc.xml b/.idea/misc.xml index 1700c77..19804e8 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="ExternalStorageConfigurationManager" enabled="true" /> + <component name="MavenProjectsManager"> + <option name="originalFiles"> + <list> + <option value="$PROJECT_DIR$/Aufgabe4Maven/pom.xml" /> + </list> + </option> + </component> <component name="ProjectRootManager" version="2" languageLevel="JDK_15" default="true" project-jdk-name="15" project-jdk-type="JavaSDK"> <output url="file://$PROJECT_DIR$/out" /> </component> diff --git a/Aufgabe4Maven/pom.xml b/Aufgabe4Maven/pom.xml new file mode 100644 index 0000000..ad36081 --- /dev/null +++ b/Aufgabe4Maven/pom.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <groupId>org.example</groupId> + <artifactId>Aufgabe4Maven</artifactId> + <version>1.0-SNAPSHOT</version> + + <properties> + <maven.compiler.source>15</maven.compiler.source> + <maven.compiler.target>15</maven.compiler.target> + </properties> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.13.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> + <version>5.9.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.google.java.contract</groupId> + <artifactId>cofoja</artifactId> + <version>1.1-r150</version> + </dependency> + + + </dependencies> + +</project> \ No newline at end of file diff --git a/Aufgabe4/Ast.java b/Aufgabe4Maven/src/Ast.java similarity index 96% rename from Aufgabe4/Ast.java rename to Aufgabe4Maven/src/Ast.java index 93ecf5b..2a14419 100644 --- a/Aufgabe4/Ast.java +++ b/Aufgabe4Maven/src/Ast.java @@ -68,5 +68,5 @@ class AstDecimal extends Ast { class AstOperator extends Ast { - String astOperator; + String astOperatorContent; } diff --git a/Aufgabe4/Evaluator.java b/Aufgabe4Maven/src/Evaluator.java similarity index 97% rename from Aufgabe4/Evaluator.java rename to Aufgabe4Maven/src/Evaluator.java index dd17f19..8dfd35b 100644 --- a/Aufgabe4/Evaluator.java +++ b/Aufgabe4Maven/src/Evaluator.java @@ -31,7 +31,7 @@ public class Evaluator { visit(node.astExpression1); - switch(node.astOperator.astOperator) { + switch(node.astOperator.astOperatorContent) { case "+": return visit(node.astExpression1) + visit(node.astExpression2); diff --git a/Aufgabe4/EvaluatorBackup.java b/Aufgabe4Maven/src/EvaluatorBackup.java similarity index 94% rename from Aufgabe4/EvaluatorBackup.java rename to Aufgabe4Maven/src/EvaluatorBackup.java index ba22527..315984b 100644 --- a/Aufgabe4/EvaluatorBackup.java +++ b/Aufgabe4Maven/src/EvaluatorBackup.java @@ -24,7 +24,7 @@ public class EvaluatorBackup { public void visit(AstBinaryOp node) { visit(node.astExpression1); - System.out.print(node.astOperator.astOperator); + System.out.print(node.astOperator.astOperatorContent); visit(node.astExpression2); } diff --git a/Aufgabe4Maven/src/Exceptions.java b/Aufgabe4Maven/src/Exceptions.java new file mode 100644 index 0000000..e2e0429 --- /dev/null +++ b/Aufgabe4Maven/src/Exceptions.java @@ -0,0 +1,33 @@ +public class Exceptions { + + public static Class<? extends Throwable> ParserException; + + public static class ParserException extends RuntimeException { + private String message; + + public ParserException(String string) { + this.message = string; + } + + public String toString() { + return "parser failed: " + message; + } + + /** + * + */ + private static final long serialVersionUID = 1L; + + } + + + @SuppressWarnings("serial") + public static class LexerException extends RuntimeException { + public LexerException() { + } + + public LexerException(String string) { + super(string); + } + } +} \ No newline at end of file diff --git a/Aufgabe4/Lexer.java b/Aufgabe4Maven/src/Lexer.java similarity index 97% rename from Aufgabe4/Lexer.java rename to Aufgabe4Maven/src/Lexer.java index fc81483..73240a0 100644 --- a/Aufgabe4/Lexer.java +++ b/Aufgabe4Maven/src/Lexer.java @@ -1,9 +1,11 @@ import java.util.ArrayList; import java.util.Objects; +import com.google.java.contract.*; public class Lexer { + @Requires({"s != null"}) public ArrayList<Token> lex(String s) { ArrayList<String> separatedChars = separateChars(s); diff --git a/Aufgabe4/Parser.java b/Aufgabe4Maven/src/Parser.java similarity index 77% rename from Aufgabe4/Parser.java rename to Aufgabe4Maven/src/Parser.java index 63d0511..1bfbca2 100644 --- a/Aufgabe4/Parser.java +++ b/Aufgabe4Maven/src/Parser.java @@ -1,10 +1,11 @@ +import java.text.ParseException; import java.util.ArrayList; import java.util.stream.Collectors; import java.util.stream.IntStream; public class Parser { - public AstExpression parse (ArrayList<Token> tokenList) { + public AstExpression parse (ArrayList<Token> tokenList) throws Exception { AstExpression root; @@ -13,7 +14,7 @@ public class Parser { return root; } - private AstExpression parseAstExpression (ArrayList<Token> tokenList) { + private AstExpression parseAstExpression (ArrayList<Token> tokenList) throws Exception { AstExpression toReturn = new AstExpression(); @@ -56,50 +57,57 @@ public class Parser { // Check if first and last tokens have "(" and ")" strings - if(tokenList.get(0).tokenString.equals("(") && tokenList.get(tokenList.size() - 1).tokenString.equals(")")) { + try { + + if (tokenList.get(0).tokenString.equals("(") && tokenList.get(tokenList.size() - 1).tokenString.equals(")")) { - // Check if "false alarm", e.g. ( 1 + 2 ) + ( 1 + 2 ) <- starts and ends with ( and ) but is to be treated as binaryOp + // Check if "false alarm", e.g. ( 1 + 2 ) + ( 1 + 2 ) <- starts and ends with ( and ) but is to be treated as binaryOp - boolean falseAlarm = false; + boolean falseAlarm = false; - for(int i = tokenList.size() - 2; i > 0; i--) { + for (int i = tokenList.size() - 2; i > 0; i--) { - if(tokenList.get(i).tokenString.equals(")") || tokenList.get(i).tokenString.equals("(")) { + if (tokenList.get(i).tokenString.equals(")") || tokenList.get(i).tokenString.equals("(")) { - if(tokenList.get(i).tokenString.equals(")")) { - falseAlarm = false; + if (tokenList.get(i).tokenString.equals(")")) { + falseAlarm = false; - } else { - falseAlarm = true; + } else { + falseAlarm = true; + } + break; } - break; } - } - // Parse tokenList without first and last object and save it in toReturns astExpression + // Parse tokenList without first and last object and save it in toReturns astExpression - if(!falseAlarm) { + if (!falseAlarm) { + + toReturn.astExpression = parseAstExpression( + (ArrayList<Token>) IntStream.range(1, tokenList.size() - 1) + .mapToObj(i -> tokenList.get(i)) + .collect(Collectors.toList()) // https://www.baeldung.com/java-stream-indices + ); + return toReturn; + } - toReturn.astExpression = parseAstExpression( - (ArrayList<Token>) IntStream.range(1, tokenList.size() - 1) - .mapToObj(i -> tokenList.get(i)) - .collect(Collectors.toList()) // https://www.baeldung.com/java-stream-indices - ); - return toReturn; } + } catch (IndexOutOfBoundsException indexOutOfBoundsException) { + throw new Exceptions.ParserException("String is not a valid function"); } + toReturn.astBinaryOp = parseBinaryOp(tokenList); return toReturn; } - //(5+2)+(1+2) - private AstBinaryOp parseBinaryOp (ArrayList<Token> tokenList) { + + private AstBinaryOp parseBinaryOp (ArrayList<Token> tokenList) throws Exception { AstBinaryOp toReturn = new AstBinaryOp(); @@ -141,7 +149,7 @@ public class Parser { private AstOperator parseOperator (Token operator) { AstOperator toReturn = new AstOperator(); - toReturn.astOperator = operator.tokenString; + toReturn.astOperatorContent = operator.tokenString; return toReturn; } @@ -221,7 +229,15 @@ public class Parser { } else { - toReturn.astDigitBeforeComma = parseNumber(new ArrayList<> (decimalTokens.subList(0, positionOfDecimalPoint - 1))); + + try { + + toReturn.astDigitBeforeComma = parseNumber(new ArrayList<>(decimalTokens.subList(0, positionOfDecimalPoint - 1))); + + } catch (IllegalArgumentException illegalArgumentException) { + throw new Exceptions.ParserException("String contains invalid char"); + } + } diff --git a/Aufgabe4/Plotter.java b/Aufgabe4Maven/src/Plotter.java similarity index 100% rename from Aufgabe4/Plotter.java rename to Aufgabe4Maven/src/Plotter.java diff --git a/Aufgabe4/TokenType.java b/Aufgabe4Maven/src/TokenType.java similarity index 100% rename from Aufgabe4/TokenType.java rename to Aufgabe4Maven/src/TokenType.java diff --git a/Aufgabe4/ValuesToDraw.java b/Aufgabe4Maven/src/ValuesToDraw.java similarity index 100% rename from Aufgabe4/ValuesToDraw.java rename to Aufgabe4Maven/src/ValuesToDraw.java diff --git a/Aufgabe4/expressions.txt b/Aufgabe4Maven/src/expressions.txt similarity index 100% rename from Aufgabe4/expressions.txt rename to Aufgabe4Maven/src/expressions.txt diff --git a/Aufgabe4/main.java b/Aufgabe4Maven/src/main.java similarity index 94% rename from Aufgabe4/main.java rename to Aufgabe4Maven/src/main.java index dd2853f..4442789 100644 --- a/Aufgabe4/main.java +++ b/Aufgabe4Maven/src/main.java @@ -1,11 +1,10 @@ import java.io.*; -import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Scanner; public class main { - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws Exception { @@ -13,7 +12,7 @@ public class main { ArrayList<String> stringsFromFile = new ArrayList<>(); - String filePath = "./Aufgabe4/expressions.txt"; + String filePath = "./Aufgabe4Maven/src/expressions.txt"; Scanner scan = null; try { @@ -116,7 +115,7 @@ public class main { } printAst(astExpression.astBinaryOp.astExpression1); - System.out.print(astExpression.astBinaryOp.astOperator.astOperator); + System.out.print(astExpression.astBinaryOp.astOperator.astOperatorContent); printAst(astExpression.astBinaryOp.astExpression2); } diff --git a/Aufgabe4/test.java b/Aufgabe4Maven/src/test.java similarity index 100% rename from Aufgabe4/test.java rename to Aufgabe4Maven/src/test.java diff --git a/Aufgabe4Maven/tests/LexerTest.java b/Aufgabe4Maven/tests/LexerTest.java new file mode 100644 index 0000000..29f24ac --- /dev/null +++ b/Aufgabe4Maven/tests/LexerTest.java @@ -0,0 +1,60 @@ +import org.junit.Test; +import org.junit.jupiter.api.DisplayName; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.stream.IntStream; + +public class LexerTest { + + @Test + @DisplayName("simpleExpression") + public void testSimpleExpression() { + + Lexer lexer = new Lexer(); + + String expression = "5"; + + ArrayList<Token> lexResult = lexer.lex(expression); + + ArrayList<Token> lexExpectedResult = new ArrayList<>(); + lexExpectedResult.add(new Token(TokenType.number, "5")); + + + assertEquals(lexExpectedResult.get(0).tokenType, lexResult.get(0).tokenType); + assertEquals(lexExpectedResult.get(0).tokenString, lexResult.get(0).tokenString); + } + + @Test + public void testComplicatedExpression() { + Lexer lexer = new Lexer(); + + String expression2 = "((22+3x)*5/3)"; + ArrayList<Token> lexResult2 = lexer.lex(expression2); + + ArrayList<Token> lexExpectedResult2 = new ArrayList<>(); + lexExpectedResult2.add(new Token(TokenType.special, "(")); + lexExpectedResult2.add(new Token(TokenType.special, "(")); + lexExpectedResult2.add(new Token(TokenType.number, "22")); + lexExpectedResult2.add(new Token(TokenType.special, "+")); + lexExpectedResult2.add(new Token(TokenType.number, "3")); + lexExpectedResult2.add(new Token(TokenType.var, "x")); + lexExpectedResult2.add(new Token(TokenType.special, ")")); + lexExpectedResult2.add(new Token(TokenType.special, "*")); + lexExpectedResult2.add(new Token(TokenType.number, "5")); + lexExpectedResult2.add(new Token(TokenType.special, "/")); + lexExpectedResult2.add(new Token(TokenType.number, "3")); + lexExpectedResult2.add(new Token(TokenType.special, ")")); + + for (int i = 0; i < lexResult2.size() ; i++) { + assertEquals(lexResult2.get(i).getTokenString(), lexExpectedResult2.get(i).getTokenString()); + assertEquals(lexResult2.get(i).getTokenType(), lexExpectedResult2.get(i).getTokenType()); + } + + } + + + +} + diff --git a/out/production/inf3_git/Token.class b/out/production/inf3_git/Token.class deleted file mode 100644 index 6ac75ca8d00a94234fb7504e3c701205235d5dfa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 832 zcmZ{i&rZTX5XQd++6t6{2&mv6xB(h@01OusPecz85|0HNEMhUGnD|zjNK8EV06vs) zW?P^Nrs;HdcIKPk%=YX1;}bv=l>{P)YKZAjpbBJ0=9AeNo6fLt-5*&4Pe47l9ouUO zM60!499xKMNa#pH7f`yBd&?1sd;IM_KUxCGwsi8Q$>D~MG&1CuF&)pfouNRsy{hE< zngZo&D0e-e))R<bOm3-2x@|kw)$E~fxm~kACQr6K8JOdq>Ds&xoH6gto(gEbELG5l zmbb(cNL6d0igY~vb!>!rxVo`OU$l`Ufenoq3!Oz=zPcX9f{D&#<_@e&n_EaovNP60 zg(erFE8>?%O%^;xvWk4SK`qnjhVPHh>hBQslR32d=^Uw7az(I1Hj5Zpl`P#HJqF3; z$W!*ykVk=bEY4+wTydcYt#`2(lxfhH&y@Nbv=>_V;;M8SQqWIU0zrX&6ocdzMROmj z<XfTfzsG<IfihCTk6I$B0g^xo2Z2@?B=r}huo2`CN1H%GGx9--1XB73geSriIF_xK Fe*nj`ht~iA diff --git a/out/production/inf3_git/TokenType.class b/out/production/inf3_git/TokenType.class deleted file mode 100644 index f51f9e10ede5de0de035d649744243b0ba634ef8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 971 zcmZuv?@!ZU5PsgZUAIzTR%C)q6j9cpn^W*h7lI*>kSy|{bIIbTceT!9=}7k@@xRiM z2+?Tx>>p*k_oYG!Zpq%=-E()(b9cZ0eE$W&!7CjZhJ364$&Xr}kNrFtR1+Gq4B2RO z)b(SAspe(e5rCYDyZ|U;9t(TQgeh!&aO`^l4~1{W#4HNrTW!B?zTI^h%y+3W1}xmx zQ7|!wI}Dl^_9LHRu533C`Iy&29`$Mm-9z6SItGfkt7E~$BJNS;E5I%>q!wcyj{JiU z44dUt_D;wL11IHhhj9?~oOB0KK-MprxQ}Jh+z6szxWzD^E|p~N+wB}QgVdFB$md{+ zzmH8k!I}^%X$**9*-lHPqW>c5PX9froNWe?|3(hC#k(PSmjBbuPK}GfYW6)Iws{<g zj3iBs_z`7WH$~DGhaUqXlXrt&#D}Apa7x*3)Akx(n20Q;62&MQsg_#~gW>i^vFGmv zg5%8P5mdznw(BImNJpe@FNk{q=%ureFP#HWSQIBf2#x$@tP$QOYlU9%F(#P)hFj;b zzL3RGq2H{G5o=oYdV=3TMtTj4Rn9Pf4kqxKYxt++F)vVfhN_IyJ_#i{)}hcB&@1ap zr&#(b!|8Tc={jnHI-W~Ry<mvtCPZpb$qN*-KfyZ61d7{HC(zxFHlb17RFT6A>V2Vl uFG;Tcpj_*La*Bs1P^7V<N<){%BTX8{8CFj)EvR0fOeUfB5*rlDVDm4PO0@w1 diff --git a/out/production/inf3_git/main.class b/out/production/inf3_git/main.class deleted file mode 100644 index a4e8897c9a16d33fbd487695fe33cf6e329876c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4310 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 -- GitLab