From 7fe551bb0e1b30aa11c59bb2fd16e5dd69febdb6 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sat, 16 Nov 2019 19:50:23 +0100 Subject: [PATCH 01/48] Rename Scanner::getSym to processToken --- src/com/merkrafter/Merkompiler.java | 2 +- src/com/merkrafter/lexing/Scanner.java | 6 +++--- src/com/merkrafter/lexing/ScannerTest.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/com/merkrafter/Merkompiler.java b/src/com/merkrafter/Merkompiler.java index 0dd7ce47..18aa1e99 100644 --- a/src/com/merkrafter/Merkompiler.java +++ b/src/com/merkrafter/Merkompiler.java @@ -24,7 +24,7 @@ public static void main(String[] args) { final Input input = new Input(filename); final Scanner s = new Scanner(input); do { - s.getSym(); + s.processToken(); System.out.println(s.sym); } while (s.sym != TokenType.EOF); diff --git a/src/com/merkrafter/lexing/Scanner.java b/src/com/merkrafter/lexing/Scanner.java index 38de6d6e..780c3d6e 100644 --- a/src/com/merkrafter/lexing/Scanner.java +++ b/src/com/merkrafter/lexing/Scanner.java @@ -7,7 +7,7 @@ * This class can be used to tokenize an iterator of characters. * All possible types of tokens can be found in TokenType enum. * - * To use this class, call getSym() and access the sym and id/num field afterwards. + * To use this class, call processToken() and access the sym and id/num field afterwards. * * @author merkrafter ***************************************************************/ @@ -74,7 +74,7 @@ public String getNum() { * It also sets the id and num fields if appropriate. * After sym is TokenType.EOF, this Scanner is done processing the iterator. */ - public void getSym() { + public void processToken() { if (charBuffer.isPresent()) { ch = charBuffer.get(); charBuffer = Optional.empty(); @@ -236,7 +236,7 @@ public void getSym() { } while (!(lastCh == '*' && ch == '/')); // ... then read next symbol loadNextCharSuccessfully(); - getSym(); + processToken(); } else { charBuffer = Optional.of(ch); } diff --git a/src/com/merkrafter/lexing/ScannerTest.java b/src/com/merkrafter/lexing/ScannerTest.java index 95e3aea7..c6b7b578 100644 --- a/src/com/merkrafter/lexing/ScannerTest.java +++ b/src/com/merkrafter/lexing/ScannerTest.java @@ -163,7 +163,7 @@ void scanEmptyClass() { private List getTokenList(final Scanner scanner) { LinkedList tokenList = new LinkedList<>(); do { - scanner.getSym(); + scanner.processToken(); tokenList.add(scanner.sym); } while (scanner.sym != TokenType.EOF); return tokenList; From 8f3e02579b5d372b049fea1fb15e3605b1b64efe Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sat, 16 Nov 2019 19:50:56 +0100 Subject: [PATCH 02/48] Encapsulate sym of Scanner --- src/com/merkrafter/Merkompiler.java | 4 ++-- src/com/merkrafter/lexing/Scanner.java | 6 +++++- src/com/merkrafter/lexing/ScannerTest.java | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/com/merkrafter/Merkompiler.java b/src/com/merkrafter/Merkompiler.java index 18aa1e99..403a9a48 100644 --- a/src/com/merkrafter/Merkompiler.java +++ b/src/com/merkrafter/Merkompiler.java @@ -25,8 +25,8 @@ public static void main(String[] args) { final Scanner s = new Scanner(input); do { s.processToken(); - System.out.println(s.sym); - } while (s.sym != TokenType.EOF); + System.out.println(s.getSym()); + } while (s.getSym() != TokenType.EOF); } catch (FileNotFoundException e) { System.err.println(filename + " not found"); diff --git a/src/com/merkrafter/lexing/Scanner.java b/src/com/merkrafter/lexing/Scanner.java index 780c3d6e..22f39001 100644 --- a/src/com/merkrafter/lexing/Scanner.java +++ b/src/com/merkrafter/lexing/Scanner.java @@ -21,7 +21,7 @@ public class Scanner { /** * This field stores the kind of the character that was read last. */ - public TokenType sym; + private TokenType sym; /** * This field stores the character that was read last. */ @@ -56,6 +56,10 @@ public Scanner(final Iterator in) { // GETTER //============================================================== + public TokenType getSym() { + return sym; + } + public String getId() { return id; } diff --git a/src/com/merkrafter/lexing/ScannerTest.java b/src/com/merkrafter/lexing/ScannerTest.java index c6b7b578..3be5bdbe 100644 --- a/src/com/merkrafter/lexing/ScannerTest.java +++ b/src/com/merkrafter/lexing/ScannerTest.java @@ -164,8 +164,8 @@ private List getTokenList(final Scanner scanner) { LinkedList tokenList = new LinkedList<>(); do { scanner.processToken(); - tokenList.add(scanner.sym); - } while (scanner.sym != TokenType.EOF); + tokenList.add(scanner.getSym()); + } while (scanner.getSym() != TokenType.EOF); return tokenList; } From 4e4c29dab7af92a73f8cbc54ac273a8006ab474d Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sat, 16 Nov 2019 20:07:32 +0100 Subject: [PATCH 03/48] Create Token class to provide context information --- src/com/merkrafter/lexing/Token.java | 61 ++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/com/merkrafter/lexing/Token.java diff --git a/src/com/merkrafter/lexing/Token.java b/src/com/merkrafter/lexing/Token.java new file mode 100644 index 00000000..3b01cf49 --- /dev/null +++ b/src/com/merkrafter/lexing/Token.java @@ -0,0 +1,61 @@ +package com.merkrafter.lexing; + +/**** + * This class represents a token that is emitted by a Scanner. + * It holds read-only context information. + * + * @author merkrafter + ***************************************************************/ +public class Token { + // ATTRIBUTES + //============================================================== + private final TokenType type; + private final String filename; + private final long line; + private final int position; + + // CONSTRUCTORS + //============================================================== + + /**** + * Creates a new Token that stores important about information + * for the Parser. + ***************************************************************/ + public Token(final TokenType type, final String filename, final long line, final int position) { + this.type = type; + this.filename = filename; + this.line = line; + this.position = position; + } + + // GETTER + //============================================================== + + /** + * @return the type of this Token + */ + public TokenType getType() { + return type; + } + + /** + * @return the file this token is located in + */ + public String getFilename() { + return filename; + } + + /** + * @return the line number inside the file this token is located in + */ + public long getLine() { + return line; + } + + /** + * @return the position inside the line this token is located in + */ + public int getPosition() { + return position; + } +} From ae16fd7cf78bfeac515f7468f186c25f681e3f98 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sat, 16 Nov 2019 20:22:47 +0100 Subject: [PATCH 04/48] Introduce positional fields for the Scanner --- src/com/merkrafter/lexing/Scanner.java | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/com/merkrafter/lexing/Scanner.java b/src/com/merkrafter/lexing/Scanner.java index 22f39001..9f1bd92d 100644 --- a/src/com/merkrafter/lexing/Scanner.java +++ b/src/com/merkrafter/lexing/Scanner.java @@ -34,6 +34,21 @@ public class Scanner { * This field stores the name of the last number that this scanner found. */ private String num; + /** + * This field stores the current filename. + */ + private String filename; + /** + * This field stores the line inside the current file. + */ + private long line; + /** + * This field stores the position inside the current line. + */ + private int position; + + // FIXME remove; only temporary + private Token currToken; /** * This field stores characters that were found during a looking-forward action, @@ -68,6 +83,20 @@ public String getNum() { return num; } + // SETTER + //============================================================== + + /** + * Sets the current filename for this Scanner. + * This method simply sets a String and does not check whether it is + * an actual file name, the file exists or something similar. + * + * @param filename a String representing a file name + */ + public void setFilename(final String filename) { + this.filename = filename; + } + // METHODS //============================================================== // public methods From 3f547328a76a25d8402680f748af3738708b1ba7 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sat, 16 Nov 2019 20:41:00 +0100 Subject: [PATCH 05/48] Use Token > TokenType in Scanner internals --- src/com/merkrafter/lexing/Scanner.java | 46 +++++++++++++------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/com/merkrafter/lexing/Scanner.java b/src/com/merkrafter/lexing/Scanner.java index 9f1bd92d..41071e28 100644 --- a/src/com/merkrafter/lexing/Scanner.java +++ b/src/com/merkrafter/lexing/Scanner.java @@ -21,7 +21,7 @@ public class Scanner { /** * This field stores the kind of the character that was read last. */ - private TokenType sym; + private Token sym; /** * This field stores the character that was read last. */ @@ -72,7 +72,7 @@ public Scanner(final Iterator in) { // GETTER //============================================================== public TokenType getSym() { - return sym; + return sym.getType(); } public String getId() { @@ -129,7 +129,7 @@ public void processToken() { case '7': case '8': case '9': - sym = TokenType.NUMBER; + sym = new Token(TokenType.NUMBER, filename, line, position); num = ""; do { num += ch; @@ -190,7 +190,7 @@ public void processToken() { case 'X': case 'Y': case 'Z': - sym = TokenType.IDENT; + sym = new Token(TokenType.IDENT, filename, line, position); id = ""; do { id += ch; @@ -200,61 +200,61 @@ public void processToken() { } while (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9'); break; case '(': - sym = TokenType.L_PAREN; + sym = new Token(TokenType.L_PAREN, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } break; case ')': - sym = TokenType.R_PAREN; + sym = new Token(TokenType.R_PAREN, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } break; case '{': - sym = TokenType.L_BRACE; + sym = new Token(TokenType.L_BRACE, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } break; case '}': - sym = TokenType.R_BRACE; + sym = new Token(TokenType.R_BRACE, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } break; case '[': - sym = TokenType.L_SQ_BRACKET; + sym = new Token(TokenType.L_SQ_BRACKET, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } break; case ']': - sym = TokenType.R_SQ_BRACKET; + sym = new Token(TokenType.R_SQ_BRACKET, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } break; case '+': - sym = TokenType.PLUS; + sym = new Token(TokenType.PLUS, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } break; case '-': - sym = TokenType.MINUS; + sym = new Token(TokenType.MINUS, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } break; case '*': - sym = TokenType.TIMES; + sym = new Token(TokenType.TIMES, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } break; case '/': - sym = TokenType.DIVIDE; + sym = new Token(TokenType.DIVIDE, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } @@ -275,12 +275,12 @@ public void processToken() { } break; case '=': - sym = TokenType.ASSIGN; + sym = new Token(TokenType.ASSIGN, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } if (ch == '=') { - sym = TokenType.EQUAL; + sym = new Token(TokenType.EQUAL, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } @@ -289,12 +289,12 @@ public void processToken() { } break; case '<': - sym = TokenType.LOWER; + sym = new Token(TokenType.LOWER, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } if (ch == '=') { - sym = TokenType.LOWER_EQUAL; + sym = new Token(TokenType.LOWER_EQUAL, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } @@ -303,12 +303,12 @@ public void processToken() { } break; case '>': - sym = TokenType.GREATER; + sym = new Token(TokenType.GREATER, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } if (ch == '=') { - sym = TokenType.GREATER_EQUAL; + sym = new Token(TokenType.GREATER_EQUAL, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } @@ -317,13 +317,13 @@ public void processToken() { } break; case ';': - sym = TokenType.SEMICOLON; + sym = new Token(TokenType.SEMICOLON, filename, line, position); if (!this.loadNextCharSuccessfully()) { return; } break; default: - sym = TokenType.OTHER; + sym = new Token(TokenType.OTHER, filename, line, position); this.loadNextCharSuccessfully(); } } @@ -346,7 +346,7 @@ private boolean loadNextCharSuccessfully(boolean setEOF) { } else { ch = (char) 0; if (setEOF) { - sym = TokenType.EOF; + sym = new Token(TokenType.EOF, filename, line, position); } return false; } From 5abd2b040df49924dae4bb33180c6f2cd6fb1ed4 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sat, 16 Nov 2019 20:48:09 +0100 Subject: [PATCH 06/48] Change return type of Scanner::getSym --- src/com/merkrafter/Merkompiler.java | 8 +++++--- src/com/merkrafter/lexing/Scanner.java | 4 ++-- src/com/merkrafter/lexing/ScannerTest.java | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/com/merkrafter/Merkompiler.java b/src/com/merkrafter/Merkompiler.java index 403a9a48..06c117b2 100644 --- a/src/com/merkrafter/Merkompiler.java +++ b/src/com/merkrafter/Merkompiler.java @@ -26,7 +26,7 @@ public static void main(String[] args) { do { s.processToken(); System.out.println(s.getSym()); - } while (s.getSym() != TokenType.EOF); + } while (s.getSym().getType() != TokenType.EOF); } catch (FileNotFoundException e) { System.err.println(filename + " not found"); @@ -37,11 +37,13 @@ public static void main(String[] args) { /** * This enum defines all error codes that this program can exit with. */ - public enum ErrorCodes {NOT_ENOUGH_ARGUMENTS(1), FILE_NOT_FOUND(2); + public enum ErrorCodes { + NOT_ENOUGH_ARGUMENTS(1), FILE_NOT_FOUND(2); public final int id; ErrorCodes(final int id) { this.id = id; - }} + } + } } diff --git a/src/com/merkrafter/lexing/Scanner.java b/src/com/merkrafter/lexing/Scanner.java index 41071e28..91473d2a 100644 --- a/src/com/merkrafter/lexing/Scanner.java +++ b/src/com/merkrafter/lexing/Scanner.java @@ -71,8 +71,8 @@ public Scanner(final Iterator in) { // GETTER //============================================================== - public TokenType getSym() { - return sym.getType(); + public Token getSym() { + return sym; } public String getId() { diff --git a/src/com/merkrafter/lexing/ScannerTest.java b/src/com/merkrafter/lexing/ScannerTest.java index 3be5bdbe..46cd2c3e 100644 --- a/src/com/merkrafter/lexing/ScannerTest.java +++ b/src/com/merkrafter/lexing/ScannerTest.java @@ -164,8 +164,8 @@ private List getTokenList(final Scanner scanner) { LinkedList tokenList = new LinkedList<>(); do { scanner.processToken(); - tokenList.add(scanner.getSym()); - } while (scanner.getSym() != TokenType.EOF); + tokenList.add(scanner.getSym().getType()); + } while (scanner.getSym().getType() != TokenType.EOF); return tokenList; } From bca8571317f349a9e8422da23be7d02780252e34 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sat, 16 Nov 2019 20:56:48 +0100 Subject: [PATCH 07/48] Create getTokenList method in ScannerTest to extend the test cases --- src/com/merkrafter/lexing/ScannerTest.java | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/com/merkrafter/lexing/ScannerTest.java b/src/com/merkrafter/lexing/ScannerTest.java index 46cd2c3e..3ba8bd65 100644 --- a/src/com/merkrafter/lexing/ScannerTest.java +++ b/src/com/merkrafter/lexing/ScannerTest.java @@ -5,6 +5,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.stream.Collectors; import static com.merkrafter.lexing.TokenType.*; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -160,13 +161,24 @@ void scanEmptyClass() { * @param scanner the object to get the tokens from * @return a list of all tokens found */ - private List getTokenList(final Scanner scanner) { - LinkedList tokenList = new LinkedList<>(); + private List getTokenList(final Scanner scanner) { + LinkedList tokenList = new LinkedList<>(); do { scanner.processToken(); - tokenList.add(scanner.getSym().getType()); + tokenList.add(scanner.getSym()); } while (scanner.getSym().getType() != TokenType.EOF); return tokenList; + + } + + /** + * Collects all types of tokens emitted by this scanner. + * + * @param scanner the object to get the tokens from + * @return a list of all types of tokens found + */ + private List getTokenTypeList(final Scanner scanner) { + return getTokenList(scanner).stream().map(Token::getType).collect(Collectors.toList()); } /** @@ -179,7 +191,7 @@ private List getTokenList(final Scanner scanner) { */ private void shouldScan(final String programCode, final TokenType[] expectedTokenList) { stringIterator.setString(programCode); - final List actualTokenList = getTokenList(scanner); + final List actualTokenList = getTokenTypeList(scanner); assertArrayEquals(expectedTokenList, actualTokenList.toArray(), actualTokenList.toString()); } From 8221d4d2c913f478bcfdc56b66b944ffc22ec1a7 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sat, 16 Nov 2019 21:59:27 +0100 Subject: [PATCH 08/48] Implement standard methods for Token (toString, equals) --- src/com/merkrafter/lexing/Token.java | 70 ++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/com/merkrafter/lexing/Token.java b/src/com/merkrafter/lexing/Token.java index 3b01cf49..620a6df7 100644 --- a/src/com/merkrafter/lexing/Token.java +++ b/src/com/merkrafter/lexing/Token.java @@ -22,6 +22,9 @@ public class Token { * for the Parser. ***************************************************************/ public Token(final TokenType type, final String filename, final long line, final int position) { + if (type == null) { + throw new IllegalArgumentException("type must not be null"); + } this.type = type; this.filename = filename; this.line = line; @@ -58,4 +61,71 @@ public long getLine() { public int getPosition() { return position; } + + // METHODS + //============================================================== + // public methods + //-------------------------------------------------------------- + + /** + * Two tokens are equal if both have the type Token and their line numbers, positions and + * filenames are equal + * + * @param obj ideally a Token to compare this with + * @return whether this is equal to obj + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof Token) { + final Token other = (Token) obj; + return eqTypes(other) && eqLines(other) && eqPositions(other) && eqFilenames(other); + } + return false; + } + + /** + * @return whether both types are equal + */ + private boolean eqTypes(Token other) { + return type == other.type; + } + + /** + * @return whether both line numbers are equal + */ + private boolean eqLines(Token other) { + return line == other.line; + } + + /** + * @return whether both positions are equal + */ + private boolean eqPositions(Token other) { + return position == other.position; + } + + /** + * @return whether both Token's filenames are null or equal + */ + private boolean eqFilenames(Token other) { + if (filename == null) { + return other.filename == null; + } else { // filename != null + if (other.filename == null) { + return false; + } else { // both have a value + return filename.equals(other.filename); + } + } + } + + /** + * Creates a String representation of this Token in the following format: + * FILENAME(LINE,POSITION): TYPE + * @return + */ + @Override + public String toString() { + return String.format("%s(%d,%d): %s", filename, line, position, type); + } } From 68ed6baa32bf020c8d07369c75fe09a5fa1f19da Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sat, 16 Nov 2019 22:06:00 +0100 Subject: [PATCH 09/48] Create happy path test cases for Scanner's Tokens --- src/com/merkrafter/lexing/ScannerTest.java | 37 +++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/com/merkrafter/lexing/ScannerTest.java b/src/com/merkrafter/lexing/ScannerTest.java index 3ba8bd65..be0d86a2 100644 --- a/src/com/merkrafter/lexing/ScannerTest.java +++ b/src/com/merkrafter/lexing/ScannerTest.java @@ -9,6 +9,7 @@ import static com.merkrafter.lexing.TokenType.*; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * This class tests the getSym() method of the Scanner class with multiple different input strings. @@ -140,10 +141,44 @@ void scanAndIgnoreAsterisksInComments() { @org.junit.jupiter.api.Test void scanMainFunction() { final String programCode = "public static void main(String[] args) {}"; - final TokenType[] expectedTokenList = {IDENT, IDENT, IDENT, IDENT, L_PAREN, IDENT, L_SQ_BRACKET, R_SQ_BRACKET, IDENT, R_PAREN, L_BRACE, R_BRACE, EOF}; + final TokenType[] expectedTokenList = + {IDENT, IDENT, IDENT, IDENT, L_PAREN, IDENT, L_SQ_BRACKET, R_SQ_BRACKET, IDENT, + R_PAREN, L_BRACE, R_BRACE, EOF}; shouldScan(programCode, expectedTokenList); } + /** + * The scanner should start counting line and position numbers at 1 each. + */ + @org.junit.jupiter.api.Test + void startAtCorrectPosition() { + final String programCode = "a"; + stringIterator.setString(programCode); + + final long expectedLine = 1; + final int expectedPosition = 1; + final Token expectedToken = new Token(IDENT, null, expectedLine, expectedPosition); + final Token actualToken = getTokenList(scanner).get(0); + + assertEquals(expectedToken, actualToken); + } + + /** + * The scanner should recognize newlines and update line and position numbers accordingly. + */ + @org.junit.jupiter.api.Test + void recognizeNewlines() { + final String programCode = "a\nb"; + stringIterator.setString(programCode); + + final long expectedLine = 2; + final int expectedPosition = 1; + final Token expectedToken = new Token(IDENT, null, expectedLine, expectedPosition); + final Token actualToken = getTokenList(scanner).get(1); // second token 'b' + + assertEquals(expectedToken, actualToken); + } + /** * The scanner should be able to handle an empty class that has a privacy modifier, the class * keyword, a name and braces. From 7ef866c0c68b51ccbaa832c870f3763b8296b650 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sat, 16 Nov 2019 22:08:36 +0100 Subject: [PATCH 10/48] Enforce line break for long enumerations --- src/com/merkrafter/lexing/ScannerTest.java | 8 ++++++-- src/com/merkrafter/lexing/TokenType.java | 24 +++++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/com/merkrafter/lexing/ScannerTest.java b/src/com/merkrafter/lexing/ScannerTest.java index be0d86a2..aa9ca469 100644 --- a/src/com/merkrafter/lexing/ScannerTest.java +++ b/src/com/merkrafter/lexing/ScannerTest.java @@ -86,7 +86,9 @@ void scanSingleIdentifierWithMixedCase() { @org.junit.jupiter.api.Test void scanAssignmentWithSpaces() { final String programCode = "int result = a + ( b - c ) * d / e;"; - final TokenType[] expectedTokenList = {IDENT, IDENT, ASSIGN, IDENT, PLUS, L_PAREN, IDENT, MINUS, IDENT, R_PAREN, TIMES, IDENT, DIVIDE, IDENT, SEMICOLON, EOF}; + final TokenType[] expectedTokenList = + {IDENT, IDENT, ASSIGN, IDENT, PLUS, L_PAREN, IDENT, MINUS, IDENT, R_PAREN, TIMES, + IDENT, DIVIDE, IDENT, SEMICOLON, EOF}; shouldScan(programCode, expectedTokenList); } @@ -109,7 +111,9 @@ void scanSimpleAssignmentWithWhitespace() { @org.junit.jupiter.api.Test void scanAssignmentWithoutWhitespace() { final String programCode = "int result=a+(b-c)*d/e;"; - final TokenType[] expectedTokenList = {IDENT, IDENT, ASSIGN, IDENT, PLUS, L_PAREN, IDENT, MINUS, IDENT, R_PAREN, TIMES, IDENT, DIVIDE, IDENT, SEMICOLON, EOF}; + final TokenType[] expectedTokenList = + {IDENT, IDENT, ASSIGN, IDENT, PLUS, L_PAREN, IDENT, MINUS, IDENT, R_PAREN, TIMES, + IDENT, DIVIDE, IDENT, SEMICOLON, EOF}; shouldScan(programCode, expectedTokenList); } diff --git a/src/com/merkrafter/lexing/TokenType.java b/src/com/merkrafter/lexing/TokenType.java index 800964fd..ce9a6d66 100644 --- a/src/com/merkrafter/lexing/TokenType.java +++ b/src/com/merkrafter/lexing/TokenType.java @@ -5,4 +5,26 @@ * * @author merkrafter ***************************************************************/ -public enum TokenType {IDENT, NUMBER, PLUS, MINUS, TIMES, DIVIDE, ASSIGN, L_PAREN, R_PAREN, L_BRACE, R_BRACE, L_SQ_BRACKET, R_SQ_BRACKET, SEMICOLON, EQUAL, LOWER_EQUAL, LOWER, GREATER_EQUAL, GREATER, EOF, OTHER} +public enum TokenType { + IDENT, + NUMBER, + PLUS, + MINUS, + TIMES, + DIVIDE, + ASSIGN, + L_PAREN, + R_PAREN, + L_BRACE, + R_BRACE, + L_SQ_BRACKET, + R_SQ_BRACKET, + SEMICOLON, + EQUAL, + LOWER_EQUAL, + LOWER, + GREATER_EQUAL, + GREATER, + EOF, + OTHER +} From ac9784857ef3f464c92a0bb5c725a9625b7cc871 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sat, 16 Nov 2019 22:21:44 +0100 Subject: [PATCH 11/48] Make the Scanner pass the tests --- src/com/merkrafter/lexing/Scanner.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/com/merkrafter/lexing/Scanner.java b/src/com/merkrafter/lexing/Scanner.java index 91473d2a..e9ac5c14 100644 --- a/src/com/merkrafter/lexing/Scanner.java +++ b/src/com/merkrafter/lexing/Scanner.java @@ -47,9 +47,6 @@ public class Scanner { */ private int position; - // FIXME remove; only temporary - private Token currToken; - /** * This field stores characters that were found during a looking-forward action, * but can not be processed yet. @@ -67,6 +64,8 @@ public Scanner(final Iterator in) { id = ""; num = ""; charBuffer = Optional.empty(); + line = 1; + position = 0; } // GETTER @@ -342,6 +341,10 @@ public void processToken() { private boolean loadNextCharSuccessfully(boolean setEOF) { if (in.hasNext()) { ch = in.next(); + position++; + if (ch == '\n') { + processNewline(); + } return true; } else { ch = (char) 0; @@ -362,4 +365,13 @@ private boolean loadNextCharSuccessfully(boolean setEOF) { private boolean loadNextCharSuccessfully() { return loadNextCharSuccessfully(false); } + + /** + * Jointly changes line and position at a newline character, that is, line is incremented and + * position is reset to 0. + */ + private void processNewline() { + line++; + position = 0; + } } From 55bf672841de9863dd9ccb75e2f74ef4257fea6e Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sun, 17 Nov 2019 14:09:51 +0100 Subject: [PATCH 12/48] Add maven support --- pom.xml | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 pom.xml diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..7518b189 --- /dev/null +++ b/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + + UTF-8 + + + Study + Merkompiler + 0.1.0-SNAPSHOT + + + org.junit.jupiter + junit-jupiter-api + 5.4.2 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.4.2 + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.1 + + + + \ No newline at end of file From dca99147b8f3fd3c05d54f915ce6c74f90825247 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sun, 17 Nov 2019 14:11:20 +0100 Subject: [PATCH 13/48] Adjust file structure to maven standard --- merkompiler.iml | 3 ++- src/{ => main/java}/com/merkrafter/Input.java | 0 src/{ => main/java}/com/merkrafter/Merkompiler.java | 0 src/{ => main/java}/com/merkrafter/lexing/Scanner.java | 0 src/{ => main/java}/com/merkrafter/lexing/Token.java | 0 src/{ => main/java}/com/merkrafter/lexing/TokenType.java | 0 src/{ => test/java}/com/merkrafter/lexing/ScannerTest.java | 0 7 files changed, 2 insertions(+), 1 deletion(-) rename src/{ => main/java}/com/merkrafter/Input.java (100%) rename src/{ => main/java}/com/merkrafter/Merkompiler.java (100%) rename src/{ => main/java}/com/merkrafter/lexing/Scanner.java (100%) rename src/{ => main/java}/com/merkrafter/lexing/Token.java (100%) rename src/{ => main/java}/com/merkrafter/lexing/TokenType.java (100%) rename src/{ => test/java}/com/merkrafter/lexing/ScannerTest.java (100%) diff --git a/merkompiler.iml b/merkompiler.iml index 6b02489e..7232baff 100644 --- a/merkompiler.iml +++ b/merkompiler.iml @@ -3,7 +3,8 @@ - + + diff --git a/src/com/merkrafter/Input.java b/src/main/java/com/merkrafter/Input.java similarity index 100% rename from src/com/merkrafter/Input.java rename to src/main/java/com/merkrafter/Input.java diff --git a/src/com/merkrafter/Merkompiler.java b/src/main/java/com/merkrafter/Merkompiler.java similarity index 100% rename from src/com/merkrafter/Merkompiler.java rename to src/main/java/com/merkrafter/Merkompiler.java diff --git a/src/com/merkrafter/lexing/Scanner.java b/src/main/java/com/merkrafter/lexing/Scanner.java similarity index 100% rename from src/com/merkrafter/lexing/Scanner.java rename to src/main/java/com/merkrafter/lexing/Scanner.java diff --git a/src/com/merkrafter/lexing/Token.java b/src/main/java/com/merkrafter/lexing/Token.java similarity index 100% rename from src/com/merkrafter/lexing/Token.java rename to src/main/java/com/merkrafter/lexing/Token.java diff --git a/src/com/merkrafter/lexing/TokenType.java b/src/main/java/com/merkrafter/lexing/TokenType.java similarity index 100% rename from src/com/merkrafter/lexing/TokenType.java rename to src/main/java/com/merkrafter/lexing/TokenType.java diff --git a/src/com/merkrafter/lexing/ScannerTest.java b/src/test/java/com/merkrafter/lexing/ScannerTest.java similarity index 100% rename from src/com/merkrafter/lexing/ScannerTest.java rename to src/test/java/com/merkrafter/lexing/ScannerTest.java From 5477729838cdbc5647beed1dd0ad4004973f856a Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sun, 17 Nov 2019 14:19:15 +0100 Subject: [PATCH 14/48] Configurate creation of a stand-alone jar with mvn package --- pom.xml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pom.xml b/pom.xml index 7518b189..9b93f50a 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,29 @@ maven-surefire-plugin 2.22.1 + + org.apache.maven.plugins + maven-assembly-plugin + 3.1.1 + + + jar-with-dependencies + + + + com.merkrafter.Merkompiler + + + + + + package + + single + + + + \ No newline at end of file From fe63ce97d5e2ad89e94b1167d297ca8eec11ef13 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sun, 17 Nov 2019 14:38:47 +0100 Subject: [PATCH 15/48] Simplify naming of the jar --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9b93f50a..c486b32f 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ UTF-8 - Study + Merkompiler Merkompiler 0.1.0-SNAPSHOT @@ -26,6 +26,7 @@ + ${project.artifactId} org.apache.maven.plugins From 4d9701f03bcca3c69a29f8f25f49177d3f6a30ff Mon Sep 17 00:00:00 2001 From: merkrafter Date: Sun, 17 Nov 2019 15:56:07 +0100 Subject: [PATCH 16/48] Update Merkompiler.yml --- .github/workflows/Merkompiler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Merkompiler.yml b/.github/workflows/Merkompiler.yml index 7b2b46c8..7181d60b 100644 --- a/.github/workflows/Merkompiler.yml +++ b/.github/workflows/Merkompiler.yml @@ -23,5 +23,5 @@ jobs: - name: Download artifact uses: actions/download-artifact@v1.0.0 with: - name: Merkompiler.jar + name: Merkompiler-jar-with-dependencies.jar path: target/Merkompiler-jar-with-dependencies.jar From e8f2ba23895960c47d965aca0e1652dd0abcb5e6 Mon Sep 17 00:00:00 2001 From: merkrafter Date: Sun, 17 Nov 2019 16:01:00 +0100 Subject: [PATCH 17/48] Update Merkompiler.yml --- .github/workflows/Merkompiler.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/Merkompiler.yml b/.github/workflows/Merkompiler.yml index 7181d60b..62d80766 100644 --- a/.github/workflows/Merkompiler.yml +++ b/.github/workflows/Merkompiler.yml @@ -20,8 +20,8 @@ jobs: java-version: 1.8 - name: Build with Maven run: mvn -B package --file pom.xml - - name: Download artifact - uses: actions/download-artifact@v1.0.0 + - name: Upload artifact + uses: actions/upload-artifact@v1.0.0 with: - name: Merkompiler-jar-with-dependencies.jar + name: Merkompiler-standalone.jar path: target/Merkompiler-jar-with-dependencies.jar From 8f3ad7a45ae7cc5c602fd43d5a8c5685a9855799 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sun, 17 Nov 2019 16:24:58 +0100 Subject: [PATCH 18/48] Write more test cases for line and block comments --- .../com/merkrafter/lexing/ScannerTest.java | 93 ++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/merkrafter/lexing/ScannerTest.java b/src/test/java/com/merkrafter/lexing/ScannerTest.java index aa9ca469..be01fc38 100644 --- a/src/test/java/com/merkrafter/lexing/ScannerTest.java +++ b/src/test/java/com/merkrafter/lexing/ScannerTest.java @@ -118,15 +118,104 @@ void scanAssignmentWithoutWhitespace() { } /** - * The scanner should be able to handle comments, i.e. it should not tokenize anything inside those. + * The scanner should be able to handle line comments that begin a line, i.e. it + * should not tokenize anything inside those. */ @org.junit.jupiter.api.Test - void scanAndIgnoreComments() { + void scanAndIgnoreStandaloneLineComments() { + final String programCode = " //in mph\nint velocity;"; + final TokenType[] expectedTokenList = {IDENT, IDENT, SEMICOLON, EOF}; + shouldScan(programCode, expectedTokenList); + } + + /** + * The scanner should be able to handle line comments that are at the end of a line, i.e. it + * should not tokenize anything inside those. + */ + @org.junit.jupiter.api.Test + void scanAndIgnoreAppendedLineComments() { + final String programCode = "int velocity; //in mph\nint acceleration;"; + final TokenType[] expectedTokenList = + {IDENT, IDENT, SEMICOLON, IDENT, IDENT, SEMICOLON, EOF}; + shouldScan(programCode, expectedTokenList); + } + + /** + * The scanner should be able to handle line comments that are at the end of the file, i.e. it + * should not tokenize anything inside those. + */ + @org.junit.jupiter.api.Test + void scanAndIgnoreEOFLineComments() { + final String programCode = "} //end of main class"; + final TokenType[] expectedTokenList = {R_BRACE, EOF}; + shouldScan(programCode, expectedTokenList); + } + + /** + * The scanner should not recognize / / as the beginning of a comment. + */ + @org.junit.jupiter.api.Test + void scanNoLineComment() { + final String programCode = " / /velocity;"; + final TokenType[] expectedTokenList = {DIVIDE, DIVIDE, IDENT, SEMICOLON, EOF}; + shouldScan(programCode, expectedTokenList); + } + + /** + * The scanner should not recognize / * as the beginning of a comment. + */ + @org.junit.jupiter.api.Test + void scanNoBlockCommentBegin() { + final String programCode = " / *velocity;"; + final TokenType[] expectedTokenList = {DIVIDE, TIMES, IDENT, SEMICOLON, EOF}; + shouldScan(programCode, expectedTokenList); + } + + /** + * The scanner should not make errors lexing an asterisk followed by a / outside a comment. + */ + @org.junit.jupiter.api.Test + void scanNoBlockComment() { + final String programCode = " */ velocity"; + final TokenType[] expectedTokenList = {TIMES, DIVIDE, IDENT, EOF}; + shouldScan(programCode, expectedTokenList); + } + + /** + * The scanner should be able to handle multiline block comments, i.e. it should not tokenize + * anything inside those. + */ + @org.junit.jupiter.api.Test + void scanAndIgnoreBlockCommentsMultiline() { + final String programCode = + "/*\nThis is a description of the method\n*/public abstract void draw();"; + final TokenType[] expectedTokenList = + {IDENT, IDENT, IDENT, IDENT, L_PAREN, R_PAREN, SEMICOLON, EOF}; + shouldScan(programCode, expectedTokenList); + } + + /** + * The scanner should be able to handle inline block comments, i.e. it should not tokenize + * anything inside those. + */ + @org.junit.jupiter.api.Test + void scanAndIgnoreBlockCommentsInline() { final String programCode = "int a /*a really important variable*/ = 5;"; final TokenType[] expectedTokenList = {IDENT, IDENT, ASSIGN, NUMBER, SEMICOLON, EOF}; shouldScan(programCode, expectedTokenList); } + /** + * The scanner should be able to handle block comments that are at the end of a line, i.e. it + * should not tokenize anything inside those. + */ + @org.junit.jupiter.api.Test + void scanAndIgnoreBlockCommentsAtEndOfLine() { + final String programCode = "int a = 5;/*a really important variable*/"; + final TokenType[] expectedTokenList = {IDENT, IDENT, ASSIGN, NUMBER, SEMICOLON, EOF}; + shouldScan(programCode, expectedTokenList); + } + /** * The scanner should be able to handle asterisks in comments. That is, it should not stop * processing the comment then. From 0e2c7200baa362f2b36534fbc5b55471df0122ed Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sun, 17 Nov 2019 16:40:32 +0100 Subject: [PATCH 19/48] Make the Scanner pass all tests --- src/main/java/com/merkrafter/lexing/Scanner.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/merkrafter/lexing/Scanner.java b/src/main/java/com/merkrafter/lexing/Scanner.java index e9ac5c14..44562e71 100644 --- a/src/main/java/com/merkrafter/lexing/Scanner.java +++ b/src/main/java/com/merkrafter/lexing/Scanner.java @@ -257,7 +257,17 @@ public void processToken() { if (!this.loadNextCharSuccessfully()) { return; } - if (ch == '*') {//this actually is a comment + if (ch == '/') {//this actually is a line comment + // skip comment ... + do { + if (!this.loadNextCharSuccessfully(true)) { + return; + } + } while (ch != '\n'); + // ... then read next symbol + loadNextCharSuccessfully(); + processToken(); + } else if (ch == '*') {//this actually is a block comment char lastCh; // skip comment ... do { From b4311e7a2ca6b1a04e21a3d917e1e71cda10ec96 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sun, 17 Nov 2019 17:09:34 +0100 Subject: [PATCH 20/48] Add basic Config class --- src/main/java/com/merkrafter/Config.java | 80 ++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/main/java/com/merkrafter/Config.java diff --git a/src/main/java/com/merkrafter/Config.java b/src/main/java/com/merkrafter/Config.java new file mode 100644 index 00000000..676ce7a1 --- /dev/null +++ b/src/main/java/com/merkrafter/Config.java @@ -0,0 +1,80 @@ +package com.merkrafter; + +/** + * This class holds configuration data for this program. + * + * @author merkrafter + */ +public class Config { + private final String input_file; + private final String output_file; + + private final boolean verbose; + private final boolean help; + + private Config(final String input_file, final String output_file, boolean verbose, boolean help) { + this.input_file = input_file; + this.output_file = output_file; + this.verbose = verbose; + this.help = help; + } + + public String getInput_file() { + return input_file; + } + + public String getOutput_file() { + return output_file; + } + + public boolean isVerbose() { + return verbose; + } + + public boolean requestsHelp() { + return help; + } + + public static Config fromArgs(String[] args) { + String input_file = ""; + String output_file = ""; + boolean verbose = false; + boolean help = false; + + boolean output_comes_next = false; + + for (final String argument : args) { + if (output_comes_next) { + output_file = argument; + output_comes_next = false; + continue; + } + switch (argument) { + case "--verbose": + case "-v": + verbose = true; + break; + case "--help": + case "-h": + help = true; + break; + case "--output": + case "-o": + output_comes_next = true; + break; + default: + input_file = argument; + } + } + + return new Config(input_file, output_file, verbose, help); + } + + /** + * @return a String representation of this Config class + */ + @Override + public String toString() { + return String.format("Config(INPUT=%s, OUTPUT=%s, verbose=%b, help=%b)", input_file, output_file, verbose, help); + } +} From 527f32cd5e45b5fe0b8ae6c0401cf85a2ca91b4b Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sun, 17 Nov 2019 17:24:42 +0100 Subject: [PATCH 21/48] Use Config in Merkompiler class --- src/main/java/com/merkrafter/Merkompiler.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/merkrafter/Merkompiler.java b/src/main/java/com/merkrafter/Merkompiler.java index 06c117b2..fd062679 100644 --- a/src/main/java/com/merkrafter/Merkompiler.java +++ b/src/main/java/com/merkrafter/Merkompiler.java @@ -18,18 +18,20 @@ public static void main(String[] args) { System.exit(ErrorCodes.NOT_ENOUGH_ARGUMENTS.id); } - final String filename = args[0]; - + final Config config = Config.fromArgs(args); + if (config.isVerbose()) { + System.out.println(config); + } try { - final Input input = new Input(filename); - final Scanner s = new Scanner(input); + final Input input = new Input(config.getInput_file()); + final Scanner scanner = new Scanner(input); do { - s.processToken(); - System.out.println(s.getSym()); - } while (s.getSym().getType() != TokenType.EOF); + scanner.processToken(); + System.out.println(scanner.getSym()); + } while (scanner.getSym().getType() != TokenType.EOF); } catch (FileNotFoundException e) { - System.err.println(filename + " not found"); + System.err.println(config.getInput_file() + " not found"); System.exit(ErrorCodes.FILE_NOT_FOUND.id); } } From 4fa06368c04506ddb9cd57af15c034c6e8295763 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sun, 17 Nov 2019 17:29:09 +0100 Subject: [PATCH 22/48] Extract ErrorCode enum from Merkompiler class --- src/main/java/com/merkrafter/ErrorCode.java | 17 +++++++++++++++++ src/main/java/com/merkrafter/Merkompiler.java | 16 ++-------------- 2 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/merkrafter/ErrorCode.java diff --git a/src/main/java/com/merkrafter/ErrorCode.java b/src/main/java/com/merkrafter/ErrorCode.java new file mode 100644 index 00000000..3a79fcd4 --- /dev/null +++ b/src/main/java/com/merkrafter/ErrorCode.java @@ -0,0 +1,17 @@ +package com.merkrafter; + +/** + * This enum defines all error codes that this program can exit with. + * + * @author merkrafter + */ +public enum ErrorCode { + NOT_ENOUGH_ARGUMENTS(1), + FILE_NOT_FOUND(2); + + public final int id; + + ErrorCode(final int id) { + this.id = id; + } +} diff --git a/src/main/java/com/merkrafter/Merkompiler.java b/src/main/java/com/merkrafter/Merkompiler.java index fd062679..6bfa4aa2 100644 --- a/src/main/java/com/merkrafter/Merkompiler.java +++ b/src/main/java/com/merkrafter/Merkompiler.java @@ -15,7 +15,7 @@ public static void main(String[] args) { // to change the arguments in IntelliJ, press Alt+Shift+F10 if (args.length < 1) { System.err.println("Usage: java Merkompiler "); - System.exit(ErrorCodes.NOT_ENOUGH_ARGUMENTS.id); + System.exit(ErrorCode.NOT_ENOUGH_ARGUMENTS.id); } final Config config = Config.fromArgs(args); @@ -32,20 +32,8 @@ public static void main(String[] args) { } catch (FileNotFoundException e) { System.err.println(config.getInput_file() + " not found"); - System.exit(ErrorCodes.FILE_NOT_FOUND.id); + System.exit(ErrorCode.FILE_NOT_FOUND.id); } } - /** - * This enum defines all error codes that this program can exit with. - */ - public enum ErrorCodes { - NOT_ENOUGH_ARGUMENTS(1), FILE_NOT_FOUND(2); - - public final int id; - - ErrorCodes(final int id) { - this.id = id; - } - } } From 6ed50e0455b96858259f11c972cf3367fe7dafb5 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sun, 17 Nov 2019 17:37:29 +0100 Subject: [PATCH 23/48] Create Error class to wrap ErrorCodes --- src/main/java/com/merkrafter/Error.java | 47 +++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/main/java/com/merkrafter/Error.java diff --git a/src/main/java/com/merkrafter/Error.java b/src/main/java/com/merkrafter/Error.java new file mode 100644 index 00000000..964ca777 --- /dev/null +++ b/src/main/java/com/merkrafter/Error.java @@ -0,0 +1,47 @@ +package com.merkrafter; + +import jdk.nashorn.internal.runtime.OptimisticReturnFilters; + +/**** + * This class serves as a container to hold an ErrorCode together + * with a more detailed error message. + * + * @author merkrafter + ***************************************************************/ + +public class Error { + // ATTRIBUTES + //============================================================== + private final ErrorCode errorCode; + private final String errorMessage; + + // CONSTRUCTORS + //============================================================== + + /**** + * Default constructor + ***************************************************************/ + public Error(final ErrorCode errorCode, final String errorMessage) { + this.errorCode = errorCode; + this.errorMessage = errorMessage; + } + + // GETTER + //============================================================== + public ErrorCode getErrorCode() { + return errorCode; + } + + public String getErrorMessage() { + return errorMessage; + } + + // METHODS + //============================================================== + // public methods + //-------------------------------------------------------------- + @Override + public String toString() { + return String.format("Error %d: %s", errorCode.id, errorMessage); + } +} From e848d5e6227020d575e8591303e86e245c5dea1a Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sun, 17 Nov 2019 17:55:00 +0100 Subject: [PATCH 24/48] Use Error in Config and Merkompiler classes --- src/main/java/com/merkrafter/Config.java | 16 ++++++++++++++++ src/main/java/com/merkrafter/Merkompiler.java | 11 ++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/merkrafter/Config.java b/src/main/java/com/merkrafter/Config.java index 676ce7a1..0aa7150b 100644 --- a/src/main/java/com/merkrafter/Config.java +++ b/src/main/java/com/merkrafter/Config.java @@ -12,6 +12,8 @@ public class Config { private final boolean verbose; private final boolean help; + private Error error; + private Config(final String input_file, final String output_file, boolean verbose, boolean help) { this.input_file = input_file; this.output_file = output_file; @@ -35,12 +37,26 @@ public boolean requestsHelp() { return help; } + public boolean hasError() { + return error != null; + } + + public Error getError() { + return error; + } + public static Config fromArgs(String[] args) { String input_file = ""; String output_file = ""; boolean verbose = false; boolean help = false; + if (args.length < 2) { + final Config config = new Config(input_file, output_file, verbose, help); + config.error = new Error(ErrorCode.NOT_ENOUGH_ARGUMENTS, "missing input file"); + return config; + } + boolean output_comes_next = false; for (final String argument : args) { diff --git a/src/main/java/com/merkrafter/Merkompiler.java b/src/main/java/com/merkrafter/Merkompiler.java index 6bfa4aa2..84f78ece 100644 --- a/src/main/java/com/merkrafter/Merkompiler.java +++ b/src/main/java/com/merkrafter/Merkompiler.java @@ -13,15 +13,16 @@ public class Merkompiler { */ public static void main(String[] args) { // to change the arguments in IntelliJ, press Alt+Shift+F10 - if (args.length < 1) { - System.err.println("Usage: java Merkompiler "); - System.exit(ErrorCode.NOT_ENOUGH_ARGUMENTS.id); - } - final Config config = Config.fromArgs(args); if (config.isVerbose()) { System.out.println(config); } + + if (config.hasError()) { + System.err.println("Merkompiler: " + config.getError().getErrorMessage()); + System.exit(config.getError().getErrorCode().id); + } + try { final Input input = new Input(config.getInput_file()); final Scanner scanner = new Scanner(input); From f53fb64647544e9f6b1243fc4a7f5b5dc1c038be Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sun, 17 Nov 2019 17:59:04 +0100 Subject: [PATCH 25/48] Extract application logic into own method --- src/main/java/com/merkrafter/Merkompiler.java | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/merkrafter/Merkompiler.java b/src/main/java/com/merkrafter/Merkompiler.java index 84f78ece..bd8ad149 100644 --- a/src/main/java/com/merkrafter/Merkompiler.java +++ b/src/main/java/com/merkrafter/Merkompiler.java @@ -23,18 +23,31 @@ public static void main(String[] args) { System.exit(config.getError().getErrorCode().id); } + /* + * Main program + */ try { - final Input input = new Input(config.getInput_file()); - final Scanner scanner = new Scanner(input); - do { - scanner.processToken(); - System.out.println(scanner.getSym()); - } while (scanner.getSym().getType() != TokenType.EOF); - + run(config); } catch (FileNotFoundException e) { System.err.println(config.getInput_file() + " not found"); System.exit(ErrorCode.FILE_NOT_FOUND.id); } } -} + /** + * Contains the main application logic. + * Passes errors etc. to the calling method which is expected to be main. + * + * @param config configuration data for this program call + * @throws FileNotFoundException if the input or output file could not be found + */ + private static void run(final Config config) throws FileNotFoundException { + final Input input = new Input(config.getInput_file()); + final Scanner scanner = new Scanner(input); + do { + scanner.processToken(); + System.out.println(scanner.getSym()); + } while (scanner.getSym().getType() != TokenType.EOF); + + } +} \ No newline at end of file From 847c2d5d9a90ade447bdfcd662d128159ccefcc3 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sun, 17 Nov 2019 18:08:33 +0100 Subject: [PATCH 26/48] Fix wrong amount of required arguments in Config --- src/main/java/com/merkrafter/Config.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/merkrafter/Config.java b/src/main/java/com/merkrafter/Config.java index 0aa7150b..3a270eff 100644 --- a/src/main/java/com/merkrafter/Config.java +++ b/src/main/java/com/merkrafter/Config.java @@ -51,7 +51,7 @@ public static Config fromArgs(String[] args) { boolean verbose = false; boolean help = false; - if (args.length < 2) { + if (args.length < 1) { final Config config = new Config(input_file, output_file, verbose, help); config.error = new Error(ErrorCode.NOT_ENOUGH_ARGUMENTS, "missing input file"); return config; From e0c6533d4e4c6ec7e0c0ed6d20e1714f060d829a Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Sun, 17 Nov 2019 18:09:03 +0100 Subject: [PATCH 27/48] Support --output argument --- src/main/java/com/merkrafter/Merkompiler.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/merkrafter/Merkompiler.java b/src/main/java/com/merkrafter/Merkompiler.java index bd8ad149..63280581 100644 --- a/src/main/java/com/merkrafter/Merkompiler.java +++ b/src/main/java/com/merkrafter/Merkompiler.java @@ -4,6 +4,8 @@ import com.merkrafter.lexing.TokenType; import java.io.FileNotFoundException; +import java.io.PrintStream; +import java.util.Arrays; public class Merkompiler { @@ -44,9 +46,17 @@ public static void main(String[] args) { private static void run(final Config config) throws FileNotFoundException { final Input input = new Input(config.getInput_file()); final Scanner scanner = new Scanner(input); + + PrintStream out = System.out; + + // write to output file if given + if (config.getOutput_file() != null) { + out = new PrintStream(config.getOutput_file()); + } + do { scanner.processToken(); - System.out.println(scanner.getSym()); + out.println(scanner.getSym()); } while (scanner.getSym().getType() != TokenType.EOF); } From f367cda3afa19f846203019cd8f2d69e5f7f530f Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 16:34:21 +0100 Subject: [PATCH 28/48] Add basic test cases for program configuration --- src/test/java/com/merkrafter/ConfigTest.java | 160 +++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 src/test/java/com/merkrafter/ConfigTest.java diff --git a/src/test/java/com/merkrafter/ConfigTest.java b/src/test/java/com/merkrafter/ConfigTest.java new file mode 100644 index 00000000..2a0f8930 --- /dev/null +++ b/src/test/java/com/merkrafter/ConfigTest.java @@ -0,0 +1,160 @@ +package com.merkrafter; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; + +class ConfigTest { + + @Test + void parseOnlyInputFile() { + final String[] args = fromString("Test.java"); + final Config actualConfig = Config.fromArgs(args); + + final String expectedInputFilename = "Test.java"; + final String expectedOutputFilename = null; + final boolean expectedVerbosity = false; + + assertEquals(expectedInputFilename, actualConfig.getInput_file()); + assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedVerbosity, actualConfig.isVerbose()); + } + + @Test + void parseInputFileWithVerbosityShortFirst() { + final String[] args = fromString("-v Test.java"); + final Config actualConfig = Config.fromArgs(args); + + final String expectedInputFilename = "Test.java"; + final String expectedOutputFilename = null; + final boolean expectedVerbosity = true; + + assertEquals(expectedInputFilename, actualConfig.getInput_file()); + assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedVerbosity, actualConfig.isVerbose()); + } + + @Test + void parseInputFileWithVerbosityShortAfter() { + final String[] args = fromString("Test.java -v"); + final Config actualConfig = Config.fromArgs(args); + + final String expectedInputFilename = "Test.java"; + final String expectedOutputFilename = null; + final boolean expectedVerbosity = true; + + assertEquals(expectedInputFilename, actualConfig.getInput_file()); + assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedVerbosity, actualConfig.isVerbose()); + } + + @Test + void parseInputFileWithVerbosityLongFirst() { + final String[] args = fromString("--verbose Test.java"); + final Config actualConfig = Config.fromArgs(args); + + final String expectedInputFilename = "Test.java"; + final String expectedOutputFilename = null; + final boolean expectedVerbosity = true; + + assertEquals(expectedInputFilename, actualConfig.getInput_file()); + assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedVerbosity, actualConfig.isVerbose()); + } + + @Test + void parseInputFileWithVerbosityLongAfter() { + final String[] args = fromString("Test.java --verbose"); + final Config actualConfig = Config.fromArgs(args); + + final String expectedInputFilename = "Test.java"; + final String expectedOutputFilename = null; + final boolean expectedVerbosity = true; + + assertEquals(expectedInputFilename, actualConfig.getInput_file()); + assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedVerbosity, actualConfig.isVerbose()); + } + + @Test + void parseInputFileAndShortOutputFileFirst() { + final String[] args = fromString("-o OtherTest.class Test.java"); + final Config actualConfig = Config.fromArgs(args); + + final String expectedInputFilename = "Test.java"; + final String expectedOutputFilename = "OtherTest.class"; + final boolean expectedVerbosity = false; + + assertEquals(expectedInputFilename, actualConfig.getInput_file()); + assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedVerbosity, actualConfig.isVerbose()); + } + + @Test + void parseInputFileAndShortOutputFileAfter() { + final String[] args = fromString("Test.java -o OtherTest.class"); + final Config actualConfig = Config.fromArgs(args); + + final String expectedInputFilename = "Test.java"; + final String expectedOutputFilename = "OtherTest.class"; + final boolean expectedVerbosity = false; + + assertEquals(expectedInputFilename, actualConfig.getInput_file()); + assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedVerbosity, actualConfig.isVerbose()); + } + + @Test + void parseInputFileAndLongOutputFileFirst() { + final String[] args = fromString("--output OtherTest.class Test.java"); + final Config actualConfig = Config.fromArgs(args); + + final String expectedInputFilename = "Test.java"; + final String expectedOutputFilename = "OtherTest.class"; + final boolean expectedVerbosity = false; + + assertEquals(expectedInputFilename, actualConfig.getInput_file()); + assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedVerbosity, actualConfig.isVerbose()); + } + + @Test + void parseInputFileAndLongOutputFileAfter() { + final String[] args = fromString("Test.java --output OtherTest.class"); + final Config actualConfig = Config.fromArgs(args); + + final String expectedInputFilename = "Test.java"; + final String expectedOutputFilename = "OtherTest.class"; + final boolean expectedVerbosity = false; + + assertEquals(expectedInputFilename, actualConfig.getInput_file()); + assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedVerbosity, actualConfig.isVerbose()); + } + + @Test + void parseInputFileAndOutputFileAndVerbosity() { + final String[] args = fromString("--verbose Test.java --output OtherTest.class"); + final Config actualConfig = Config.fromArgs(args); + + final String expectedInputFilename = "Test.java"; + final String expectedOutputFilename = "OtherTest.class"; + final boolean expectedVerbosity = true; + + assertEquals(expectedInputFilename, actualConfig.getInput_file()); + assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedVerbosity, actualConfig.isVerbose()); + } + + /** + * Splits the String of arguments at whitespace into multiple argument tokens. + * + * @param argsAsString string of arguments as written on the command line + * @return an array of arguments + */ + private String[] fromString(final String argsAsString) { + return argsAsString.split("\\s+"); + } +} \ No newline at end of file From 044614ede5ddd56b919a027f1e23788b754be336 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 17:27:12 +0100 Subject: [PATCH 29/48] Add argparse4j dependency --- merkompiler.iml | 16 +++++++++++++--- pom.xml | 5 +++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/merkompiler.iml b/merkompiler.iml index 7232baff..9dee474e 100644 --- a/merkompiler.iml +++ b/merkompiler.iml @@ -1,10 +1,13 @@ - - - + + + + + + @@ -20,5 +23,12 @@ + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index c486b32f..dc1bcd17 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,11 @@ 5.4.2 test + + net.sourceforge.argparse4j + argparse4j + 0.8.1 + ${project.artifactId} From ff5ad96753e1fb2aaf0474f163392ca07a167a7c Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 17:29:45 +0100 Subject: [PATCH 30/48] Make the Config class use argparse to handle cli arguments --- src/main/java/com/merkrafter/Config.java | 76 ++++++++++--------- src/main/java/com/merkrafter/Merkompiler.java | 8 +- src/test/java/com/merkrafter/ConfigTest.java | 21 ++--- 3 files changed, 57 insertions(+), 48 deletions(-) diff --git a/src/main/java/com/merkrafter/Config.java b/src/main/java/com/merkrafter/Config.java index 3a270eff..b27f5bd3 100644 --- a/src/main/java/com/merkrafter/Config.java +++ b/src/main/java/com/merkrafter/Config.java @@ -1,5 +1,11 @@ package com.merkrafter; +import net.sourceforge.argparse4j.ArgumentParsers; +import net.sourceforge.argparse4j.impl.Arguments; +import net.sourceforge.argparse4j.inf.ArgumentParser; +import net.sourceforge.argparse4j.inf.ArgumentParserException; +import net.sourceforge.argparse4j.inf.Namespace; + /** * This class holds configuration data for this program. * @@ -45,45 +51,40 @@ public Error getError() { return error; } - public static Config fromArgs(String[] args) { - String input_file = ""; - String output_file = ""; - boolean verbose = false; - boolean help = false; - - if (args.length < 1) { - final Config config = new Config(input_file, output_file, verbose, help); - config.error = new Error(ErrorCode.NOT_ENOUGH_ARGUMENTS, "missing input file"); - return config; + public static Config fromArgs(final String[] args) throws ArgumentParserException { + // define the parser + final ArgumentParser parser = + ArgumentParsers.newFor("Merkompiler").build().defaultHelp(true) + .description("Compiles JavaSST files"); + + parser.addArgument("INPUT").required(true).type(String.class) + .help("JavaSST source code file"); + parser.addArgument("-v", "--verbose").action(Arguments.storeTrue()) + .help("print more information"); + parser.addArgument("-o", "--output").type(String.class).metavar("OUTPUT") + .help("output target; default is stdout"); + + + // parse the arguments + Namespace namespace = null; + try { + namespace = parser.parseArgs(args); + } catch (ArgumentParserException e) { + parser.handleError(e); + throw e; } - boolean output_comes_next = false; - - for (final String argument : args) { - if (output_comes_next) { - output_file = argument; - output_comes_next = false; - continue; - } - switch (argument) { - case "--verbose": - case "-v": - verbose = true; - break; - case "--help": - case "-h": - help = true; - break; - case "--output": - case "-o": - output_comes_next = true; - break; - default: - input_file = argument; - } - } + // build Config instance + String inputFileName = null; + String outputFileName = null; + boolean verbose = false; - return new Config(input_file, output_file, verbose, help); + if (namespace != null) { + inputFileName = namespace.getString("INPUT"); + outputFileName = namespace.getString("output"); + verbose = namespace.getBoolean("verbose"); + } + return new Config(inputFileName, outputFileName, verbose, false); } /** @@ -91,6 +92,7 @@ public static Config fromArgs(String[] args) { */ @Override public String toString() { - return String.format("Config(INPUT=%s, OUTPUT=%s, verbose=%b, help=%b)", input_file, output_file, verbose, help); + return String + .format("Config(INPUT=%s, OUTPUT=%s, verbose=%b, help=%b)", input_file, output_file, verbose, help); } } diff --git a/src/main/java/com/merkrafter/Merkompiler.java b/src/main/java/com/merkrafter/Merkompiler.java index 63280581..4247a4b5 100644 --- a/src/main/java/com/merkrafter/Merkompiler.java +++ b/src/main/java/com/merkrafter/Merkompiler.java @@ -2,6 +2,7 @@ import com.merkrafter.lexing.Scanner; import com.merkrafter.lexing.TokenType; +import net.sourceforge.argparse4j.inf.ArgumentParserException; import java.io.FileNotFoundException; import java.io.PrintStream; @@ -15,7 +16,12 @@ public class Merkompiler { */ public static void main(String[] args) { // to change the arguments in IntelliJ, press Alt+Shift+F10 - final Config config = Config.fromArgs(args); + Config config = null; + try { + config = Config.fromArgs(args); + } catch (ArgumentParserException e) { + System.exit(1); + } if (config.isVerbose()) { System.out.println(config); } diff --git a/src/test/java/com/merkrafter/ConfigTest.java b/src/test/java/com/merkrafter/ConfigTest.java index 2a0f8930..6ff3deef 100644 --- a/src/test/java/com/merkrafter/ConfigTest.java +++ b/src/test/java/com/merkrafter/ConfigTest.java @@ -1,5 +1,6 @@ package com.merkrafter; +import net.sourceforge.argparse4j.inf.ArgumentParserException; import org.junit.jupiter.api.Test; import java.util.Arrays; @@ -9,7 +10,7 @@ class ConfigTest { @Test - void parseOnlyInputFile() { + void parseOnlyInputFile() throws ArgumentParserException { final String[] args = fromString("Test.java"); final Config actualConfig = Config.fromArgs(args); @@ -23,7 +24,7 @@ void parseOnlyInputFile() { } @Test - void parseInputFileWithVerbosityShortFirst() { + void parseInputFileWithVerbosityShortFirst() throws ArgumentParserException { final String[] args = fromString("-v Test.java"); final Config actualConfig = Config.fromArgs(args); @@ -37,7 +38,7 @@ void parseInputFileWithVerbosityShortFirst() { } @Test - void parseInputFileWithVerbosityShortAfter() { + void parseInputFileWithVerbosityShortAfter() throws ArgumentParserException { final String[] args = fromString("Test.java -v"); final Config actualConfig = Config.fromArgs(args); @@ -51,7 +52,7 @@ void parseInputFileWithVerbosityShortAfter() { } @Test - void parseInputFileWithVerbosityLongFirst() { + void parseInputFileWithVerbosityLongFirst() throws ArgumentParserException { final String[] args = fromString("--verbose Test.java"); final Config actualConfig = Config.fromArgs(args); @@ -65,7 +66,7 @@ void parseInputFileWithVerbosityLongFirst() { } @Test - void parseInputFileWithVerbosityLongAfter() { + void parseInputFileWithVerbosityLongAfter() throws ArgumentParserException { final String[] args = fromString("Test.java --verbose"); final Config actualConfig = Config.fromArgs(args); @@ -79,7 +80,7 @@ void parseInputFileWithVerbosityLongAfter() { } @Test - void parseInputFileAndShortOutputFileFirst() { + void parseInputFileAndShortOutputFileFirst() throws ArgumentParserException { final String[] args = fromString("-o OtherTest.class Test.java"); final Config actualConfig = Config.fromArgs(args); @@ -93,7 +94,7 @@ void parseInputFileAndShortOutputFileFirst() { } @Test - void parseInputFileAndShortOutputFileAfter() { + void parseInputFileAndShortOutputFileAfter() throws ArgumentParserException { final String[] args = fromString("Test.java -o OtherTest.class"); final Config actualConfig = Config.fromArgs(args); @@ -107,7 +108,7 @@ void parseInputFileAndShortOutputFileAfter() { } @Test - void parseInputFileAndLongOutputFileFirst() { + void parseInputFileAndLongOutputFileFirst() throws ArgumentParserException { final String[] args = fromString("--output OtherTest.class Test.java"); final Config actualConfig = Config.fromArgs(args); @@ -121,7 +122,7 @@ void parseInputFileAndLongOutputFileFirst() { } @Test - void parseInputFileAndLongOutputFileAfter() { + void parseInputFileAndLongOutputFileAfter() throws ArgumentParserException { final String[] args = fromString("Test.java --output OtherTest.class"); final Config actualConfig = Config.fromArgs(args); @@ -135,7 +136,7 @@ void parseInputFileAndLongOutputFileAfter() { } @Test - void parseInputFileAndOutputFileAndVerbosity() { + void parseInputFileAndOutputFileAndVerbosity() throws ArgumentParserException { final String[] args = fromString("--verbose Test.java --output OtherTest.class"); final Config actualConfig = Config.fromArgs(args); From 96836ba41a7d256cd4fedee9a2509c44ea25e29c Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 17:32:17 +0100 Subject: [PATCH 31/48] Remove now obsolete Config.help field --- src/main/java/com/merkrafter/Config.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/merkrafter/Config.java b/src/main/java/com/merkrafter/Config.java index b27f5bd3..e94afec0 100644 --- a/src/main/java/com/merkrafter/Config.java +++ b/src/main/java/com/merkrafter/Config.java @@ -16,15 +16,13 @@ public class Config { private final String output_file; private final boolean verbose; - private final boolean help; private Error error; - private Config(final String input_file, final String output_file, boolean verbose, boolean help) { + private Config(final String input_file, final String output_file, boolean verbose) { this.input_file = input_file; this.output_file = output_file; this.verbose = verbose; - this.help = help; } public String getInput_file() { @@ -39,10 +37,6 @@ public boolean isVerbose() { return verbose; } - public boolean requestsHelp() { - return help; - } - public boolean hasError() { return error != null; } @@ -84,7 +78,7 @@ public static Config fromArgs(final String[] args) throws ArgumentParserExceptio outputFileName = namespace.getString("output"); verbose = namespace.getBoolean("verbose"); } - return new Config(inputFileName, outputFileName, verbose, false); + return new Config(inputFileName, outputFileName, verbose); } /** @@ -93,6 +87,6 @@ public static Config fromArgs(final String[] args) throws ArgumentParserExceptio @Override public String toString() { return String - .format("Config(INPUT=%s, OUTPUT=%s, verbose=%b, help=%b)", input_file, output_file, verbose, help); + .format("Config(INPUT=%s, OUTPUT=%s, verbose=%b)", input_file, output_file, verbose); } } From c50e474dd76e098d6541d57f179312dc6ceb1365 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 17:40:04 +0100 Subject: [PATCH 32/48] Move error handling from Config to Merkompiler --- src/main/java/com/merkrafter/Config.java | 10 ---------- src/main/java/com/merkrafter/ErrorCode.java | 2 +- src/main/java/com/merkrafter/Merkompiler.java | 7 +------ 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/merkrafter/Config.java b/src/main/java/com/merkrafter/Config.java index e94afec0..163d51a7 100644 --- a/src/main/java/com/merkrafter/Config.java +++ b/src/main/java/com/merkrafter/Config.java @@ -17,8 +17,6 @@ public class Config { private final boolean verbose; - private Error error; - private Config(final String input_file, final String output_file, boolean verbose) { this.input_file = input_file; this.output_file = output_file; @@ -37,14 +35,6 @@ public boolean isVerbose() { return verbose; } - public boolean hasError() { - return error != null; - } - - public Error getError() { - return error; - } - public static Config fromArgs(final String[] args) throws ArgumentParserException { // define the parser final ArgumentParser parser = diff --git a/src/main/java/com/merkrafter/ErrorCode.java b/src/main/java/com/merkrafter/ErrorCode.java index 3a79fcd4..93338139 100644 --- a/src/main/java/com/merkrafter/ErrorCode.java +++ b/src/main/java/com/merkrafter/ErrorCode.java @@ -6,7 +6,7 @@ * @author merkrafter */ public enum ErrorCode { - NOT_ENOUGH_ARGUMENTS(1), + ARGUMENTS_UNPARSABLE(1), FILE_NOT_FOUND(2); public final int id; diff --git a/src/main/java/com/merkrafter/Merkompiler.java b/src/main/java/com/merkrafter/Merkompiler.java index 4247a4b5..f2b36d43 100644 --- a/src/main/java/com/merkrafter/Merkompiler.java +++ b/src/main/java/com/merkrafter/Merkompiler.java @@ -20,17 +20,12 @@ public static void main(String[] args) { try { config = Config.fromArgs(args); } catch (ArgumentParserException e) { - System.exit(1); + System.exit(ErrorCode.ARGUMENTS_UNPARSABLE.id); } if (config.isVerbose()) { System.out.println(config); } - if (config.hasError()) { - System.err.println("Merkompiler: " + config.getError().getErrorMessage()); - System.exit(config.getError().getErrorCode().id); - } - /* * Main program */ From 5b39cb9569da830c354c45b854befe38b843922c Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 17:43:02 +0100 Subject: [PATCH 33/48] Move printing from Config to Merkompiler --- src/main/java/com/merkrafter/Config.java | 7 +------ src/main/java/com/merkrafter/Merkompiler.java | 1 + 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/merkrafter/Config.java b/src/main/java/com/merkrafter/Config.java index 163d51a7..9e76e48d 100644 --- a/src/main/java/com/merkrafter/Config.java +++ b/src/main/java/com/merkrafter/Config.java @@ -51,12 +51,7 @@ public static Config fromArgs(final String[] args) throws ArgumentParserExceptio // parse the arguments Namespace namespace = null; - try { - namespace = parser.parseArgs(args); - } catch (ArgumentParserException e) { - parser.handleError(e); - throw e; - } + namespace = parser.parseArgs(args); // build Config instance String inputFileName = null; diff --git a/src/main/java/com/merkrafter/Merkompiler.java b/src/main/java/com/merkrafter/Merkompiler.java index f2b36d43..f5f66785 100644 --- a/src/main/java/com/merkrafter/Merkompiler.java +++ b/src/main/java/com/merkrafter/Merkompiler.java @@ -20,6 +20,7 @@ public static void main(String[] args) { try { config = Config.fromArgs(args); } catch (ArgumentParserException e) { + e.getParser().handleError(e); System.exit(ErrorCode.ARGUMENTS_UNPARSABLE.id); } if (config.isVerbose()) { From 36c49ffdd303aed8461a94246fe9683760929c13 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 17:44:03 +0100 Subject: [PATCH 34/48] Remove redundant initializer for Namespace in Config::fromArgs --- src/main/java/com/merkrafter/Config.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/merkrafter/Config.java b/src/main/java/com/merkrafter/Config.java index 9e76e48d..456fe606 100644 --- a/src/main/java/com/merkrafter/Config.java +++ b/src/main/java/com/merkrafter/Config.java @@ -50,7 +50,7 @@ public static Config fromArgs(final String[] args) throws ArgumentParserExceptio // parse the arguments - Namespace namespace = null; + Namespace namespace; namespace = parser.parseArgs(args); // build Config instance From cf347fe6bec76e4ae0ba1478f215b556464fe260 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 17:45:06 +0100 Subject: [PATCH 35/48] Set the input file name for Scanner in Merkompiler --- src/main/java/com/merkrafter/Merkompiler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/merkrafter/Merkompiler.java b/src/main/java/com/merkrafter/Merkompiler.java index f5f66785..62500a8b 100644 --- a/src/main/java/com/merkrafter/Merkompiler.java +++ b/src/main/java/com/merkrafter/Merkompiler.java @@ -48,6 +48,7 @@ public static void main(String[] args) { private static void run(final Config config) throws FileNotFoundException { final Input input = new Input(config.getInput_file()); final Scanner scanner = new Scanner(input); + scanner.setFilename(config.getInput_file()); PrintStream out = System.out; From bb7aecf18c3f5a645d91de5b9895ee34afe83105 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 17:47:24 +0100 Subject: [PATCH 36/48] Change Config's file attribute's names to camelCase --- src/main/java/com/merkrafter/Config.java | 20 ++++----- src/main/java/com/merkrafter/Merkompiler.java | 11 +++-- src/test/java/com/merkrafter/ConfigTest.java | 42 +++++++++---------- 3 files changed, 35 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/merkrafter/Config.java b/src/main/java/com/merkrafter/Config.java index 456fe606..da2225f2 100644 --- a/src/main/java/com/merkrafter/Config.java +++ b/src/main/java/com/merkrafter/Config.java @@ -12,23 +12,23 @@ * @author merkrafter */ public class Config { - private final String input_file; - private final String output_file; + private final String inputFile; + private final String outputFile; private final boolean verbose; - private Config(final String input_file, final String output_file, boolean verbose) { - this.input_file = input_file; - this.output_file = output_file; + private Config(final String inputFile, final String outputFile, boolean verbose) { + this.inputFile = inputFile; + this.outputFile = outputFile; this.verbose = verbose; } - public String getInput_file() { - return input_file; + public String getInputFile() { + return inputFile; } - public String getOutput_file() { - return output_file; + public String getOutputFile() { + return outputFile; } public boolean isVerbose() { @@ -72,6 +72,6 @@ public static Config fromArgs(final String[] args) throws ArgumentParserExceptio @Override public String toString() { return String - .format("Config(INPUT=%s, OUTPUT=%s, verbose=%b)", input_file, output_file, verbose); + .format("Config(INPUT=%s, OUTPUT=%s, verbose=%b)", inputFile, outputFile, verbose); } } diff --git a/src/main/java/com/merkrafter/Merkompiler.java b/src/main/java/com/merkrafter/Merkompiler.java index 62500a8b..0050d394 100644 --- a/src/main/java/com/merkrafter/Merkompiler.java +++ b/src/main/java/com/merkrafter/Merkompiler.java @@ -6,7 +6,6 @@ import java.io.FileNotFoundException; import java.io.PrintStream; -import java.util.Arrays; public class Merkompiler { @@ -33,7 +32,7 @@ public static void main(String[] args) { try { run(config); } catch (FileNotFoundException e) { - System.err.println(config.getInput_file() + " not found"); + System.err.println(config.getInputFile() + " not found"); System.exit(ErrorCode.FILE_NOT_FOUND.id); } } @@ -46,15 +45,15 @@ public static void main(String[] args) { * @throws FileNotFoundException if the input or output file could not be found */ private static void run(final Config config) throws FileNotFoundException { - final Input input = new Input(config.getInput_file()); + final Input input = new Input(config.getInputFile()); final Scanner scanner = new Scanner(input); - scanner.setFilename(config.getInput_file()); + scanner.setFilename(config.getInputFile()); PrintStream out = System.out; // write to output file if given - if (config.getOutput_file() != null) { - out = new PrintStream(config.getOutput_file()); + if (config.getOutputFile() != null) { + out = new PrintStream(config.getOutputFile()); } do { diff --git a/src/test/java/com/merkrafter/ConfigTest.java b/src/test/java/com/merkrafter/ConfigTest.java index 6ff3deef..761978f9 100644 --- a/src/test/java/com/merkrafter/ConfigTest.java +++ b/src/test/java/com/merkrafter/ConfigTest.java @@ -3,8 +3,6 @@ import net.sourceforge.argparse4j.inf.ArgumentParserException; import org.junit.jupiter.api.Test; -import java.util.Arrays; - import static org.junit.jupiter.api.Assertions.*; class ConfigTest { @@ -18,8 +16,8 @@ void parseOnlyInputFile() throws ArgumentParserException { final String expectedOutputFilename = null; final boolean expectedVerbosity = false; - assertEquals(expectedInputFilename, actualConfig.getInput_file()); - assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedInputFilename, actualConfig.getInputFile()); + assertEquals(expectedOutputFilename, actualConfig.getOutputFile()); assertEquals(expectedVerbosity, actualConfig.isVerbose()); } @@ -32,8 +30,8 @@ void parseInputFileWithVerbosityShortFirst() throws ArgumentParserException { final String expectedOutputFilename = null; final boolean expectedVerbosity = true; - assertEquals(expectedInputFilename, actualConfig.getInput_file()); - assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedInputFilename, actualConfig.getInputFile()); + assertEquals(expectedOutputFilename, actualConfig.getOutputFile()); assertEquals(expectedVerbosity, actualConfig.isVerbose()); } @@ -46,8 +44,8 @@ void parseInputFileWithVerbosityShortAfter() throws ArgumentParserException { final String expectedOutputFilename = null; final boolean expectedVerbosity = true; - assertEquals(expectedInputFilename, actualConfig.getInput_file()); - assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedInputFilename, actualConfig.getInputFile()); + assertEquals(expectedOutputFilename, actualConfig.getOutputFile()); assertEquals(expectedVerbosity, actualConfig.isVerbose()); } @@ -60,8 +58,8 @@ void parseInputFileWithVerbosityLongFirst() throws ArgumentParserException { final String expectedOutputFilename = null; final boolean expectedVerbosity = true; - assertEquals(expectedInputFilename, actualConfig.getInput_file()); - assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedInputFilename, actualConfig.getInputFile()); + assertEquals(expectedOutputFilename, actualConfig.getOutputFile()); assertEquals(expectedVerbosity, actualConfig.isVerbose()); } @@ -74,8 +72,8 @@ void parseInputFileWithVerbosityLongAfter() throws ArgumentParserException { final String expectedOutputFilename = null; final boolean expectedVerbosity = true; - assertEquals(expectedInputFilename, actualConfig.getInput_file()); - assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedInputFilename, actualConfig.getInputFile()); + assertEquals(expectedOutputFilename, actualConfig.getOutputFile()); assertEquals(expectedVerbosity, actualConfig.isVerbose()); } @@ -88,8 +86,8 @@ void parseInputFileAndShortOutputFileFirst() throws ArgumentParserException { final String expectedOutputFilename = "OtherTest.class"; final boolean expectedVerbosity = false; - assertEquals(expectedInputFilename, actualConfig.getInput_file()); - assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedInputFilename, actualConfig.getInputFile()); + assertEquals(expectedOutputFilename, actualConfig.getOutputFile()); assertEquals(expectedVerbosity, actualConfig.isVerbose()); } @@ -102,8 +100,8 @@ void parseInputFileAndShortOutputFileAfter() throws ArgumentParserException { final String expectedOutputFilename = "OtherTest.class"; final boolean expectedVerbosity = false; - assertEquals(expectedInputFilename, actualConfig.getInput_file()); - assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedInputFilename, actualConfig.getInputFile()); + assertEquals(expectedOutputFilename, actualConfig.getOutputFile()); assertEquals(expectedVerbosity, actualConfig.isVerbose()); } @@ -116,8 +114,8 @@ void parseInputFileAndLongOutputFileFirst() throws ArgumentParserException { final String expectedOutputFilename = "OtherTest.class"; final boolean expectedVerbosity = false; - assertEquals(expectedInputFilename, actualConfig.getInput_file()); - assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedInputFilename, actualConfig.getInputFile()); + assertEquals(expectedOutputFilename, actualConfig.getOutputFile()); assertEquals(expectedVerbosity, actualConfig.isVerbose()); } @@ -130,8 +128,8 @@ void parseInputFileAndLongOutputFileAfter() throws ArgumentParserException { final String expectedOutputFilename = "OtherTest.class"; final boolean expectedVerbosity = false; - assertEquals(expectedInputFilename, actualConfig.getInput_file()); - assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedInputFilename, actualConfig.getInputFile()); + assertEquals(expectedOutputFilename, actualConfig.getOutputFile()); assertEquals(expectedVerbosity, actualConfig.isVerbose()); } @@ -144,8 +142,8 @@ void parseInputFileAndOutputFileAndVerbosity() throws ArgumentParserException { final String expectedOutputFilename = "OtherTest.class"; final boolean expectedVerbosity = true; - assertEquals(expectedInputFilename, actualConfig.getInput_file()); - assertEquals(expectedOutputFilename, actualConfig.getOutput_file()); + assertEquals(expectedInputFilename, actualConfig.getInputFile()); + assertEquals(expectedOutputFilename, actualConfig.getOutputFile()); assertEquals(expectedVerbosity, actualConfig.isVerbose()); } From 693d17e63d651c4857d7bcc54aff8953c754ba62 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 17:58:33 +0100 Subject: [PATCH 37/48] Simplify Merkompiler class by joining try..catch blocks --- src/main/java/com/merkrafter/Merkompiler.java | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/merkrafter/Merkompiler.java b/src/main/java/com/merkrafter/Merkompiler.java index 0050d394..5751c068 100644 --- a/src/main/java/com/merkrafter/Merkompiler.java +++ b/src/main/java/com/merkrafter/Merkompiler.java @@ -15,24 +15,14 @@ public class Merkompiler { */ public static void main(String[] args) { // to change the arguments in IntelliJ, press Alt+Shift+F10 - Config config = null; try { - config = Config.fromArgs(args); + final Config config = Config.fromArgs(args); + run(config); } catch (ArgumentParserException e) { - e.getParser().handleError(e); + e.getParser().handleError(e); // prints the help message System.exit(ErrorCode.ARGUMENTS_UNPARSABLE.id); - } - if (config.isVerbose()) { - System.out.println(config); - } - - /* - * Main program - */ - try { - run(config); } catch (FileNotFoundException e) { - System.err.println(config.getInputFile() + " not found"); + System.err.println(e.getMessage()); System.exit(ErrorCode.FILE_NOT_FOUND.id); } } @@ -45,11 +35,15 @@ public static void main(String[] args) { * @throws FileNotFoundException if the input or output file could not be found */ private static void run(final Config config) throws FileNotFoundException { + if (config.isVerbose()) { + System.out.println(config); + } + final Input input = new Input(config.getInputFile()); final Scanner scanner = new Scanner(input); scanner.setFilename(config.getInputFile()); - PrintStream out = System.out; + PrintStream out = System.out; // write to stdout by default // write to output file if given if (config.getOutputFile() != null) { @@ -60,6 +54,5 @@ private static void run(final Config config) throws FileNotFoundException { scanner.processToken(); out.println(scanner.getSym()); } while (scanner.getSym().getType() != TokenType.EOF); - } } \ No newline at end of file From 9715201877f51dbdd0677b089f86dfed41ec7884 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 18:09:03 +0100 Subject: [PATCH 38/48] Move configuration to own package so that Merkompiler::run can be package-private to allow testing --- src/main/java/com/merkrafter/Merkompiler.java | 2 ++ .../com/merkrafter/{ => config}/Config.java | 2 +- .../java/com/merkrafter/{ => config}/Error.java | 4 +--- .../com/merkrafter/{ => config}/ErrorCode.java | 2 +- src/test/java/com/merkrafter/ConfigTest.java | 1 + .../java/com/merkrafter/MerkompilerTest.java | 17 +++++++++++++++++ 6 files changed, 23 insertions(+), 5 deletions(-) rename src/main/java/com/merkrafter/{ => config}/Config.java (98%) rename src/main/java/com/merkrafter/{ => config}/Error.java (93%) rename src/main/java/com/merkrafter/{ => config}/ErrorCode.java (89%) create mode 100644 src/test/java/com/merkrafter/MerkompilerTest.java diff --git a/src/main/java/com/merkrafter/Merkompiler.java b/src/main/java/com/merkrafter/Merkompiler.java index 5751c068..a801b922 100644 --- a/src/main/java/com/merkrafter/Merkompiler.java +++ b/src/main/java/com/merkrafter/Merkompiler.java @@ -1,5 +1,7 @@ package com.merkrafter; +import com.merkrafter.config.Config; +import com.merkrafter.config.ErrorCode; import com.merkrafter.lexing.Scanner; import com.merkrafter.lexing.TokenType; import net.sourceforge.argparse4j.inf.ArgumentParserException; diff --git a/src/main/java/com/merkrafter/Config.java b/src/main/java/com/merkrafter/config/Config.java similarity index 98% rename from src/main/java/com/merkrafter/Config.java rename to src/main/java/com/merkrafter/config/Config.java index da2225f2..ed3668ca 100644 --- a/src/main/java/com/merkrafter/Config.java +++ b/src/main/java/com/merkrafter/config/Config.java @@ -1,4 +1,4 @@ -package com.merkrafter; +package com.merkrafter.config; import net.sourceforge.argparse4j.ArgumentParsers; import net.sourceforge.argparse4j.impl.Arguments; diff --git a/src/main/java/com/merkrafter/Error.java b/src/main/java/com/merkrafter/config/Error.java similarity index 93% rename from src/main/java/com/merkrafter/Error.java rename to src/main/java/com/merkrafter/config/Error.java index 964ca777..ebc12a8a 100644 --- a/src/main/java/com/merkrafter/Error.java +++ b/src/main/java/com/merkrafter/config/Error.java @@ -1,6 +1,4 @@ -package com.merkrafter; - -import jdk.nashorn.internal.runtime.OptimisticReturnFilters; +package com.merkrafter.config; /**** * This class serves as a container to hold an ErrorCode together diff --git a/src/main/java/com/merkrafter/ErrorCode.java b/src/main/java/com/merkrafter/config/ErrorCode.java similarity index 89% rename from src/main/java/com/merkrafter/ErrorCode.java rename to src/main/java/com/merkrafter/config/ErrorCode.java index 93338139..ace2f527 100644 --- a/src/main/java/com/merkrafter/ErrorCode.java +++ b/src/main/java/com/merkrafter/config/ErrorCode.java @@ -1,4 +1,4 @@ -package com.merkrafter; +package com.merkrafter.config; /** * This enum defines all error codes that this program can exit with. diff --git a/src/test/java/com/merkrafter/ConfigTest.java b/src/test/java/com/merkrafter/ConfigTest.java index 761978f9..a07ed769 100644 --- a/src/test/java/com/merkrafter/ConfigTest.java +++ b/src/test/java/com/merkrafter/ConfigTest.java @@ -1,5 +1,6 @@ package com.merkrafter; +import com.merkrafter.config.Config; import net.sourceforge.argparse4j.inf.ArgumentParserException; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/merkrafter/MerkompilerTest.java b/src/test/java/com/merkrafter/MerkompilerTest.java new file mode 100644 index 00000000..0c4b1eb7 --- /dev/null +++ b/src/test/java/com/merkrafter/MerkompilerTest.java @@ -0,0 +1,17 @@ +package com.merkrafter; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import static org.junit.jupiter.api.Assertions.*; + +class MerkompilerTest { + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } +} \ No newline at end of file From 0b9924b73f92bef79258e2cafc7c9aff248354ea Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 18:10:54 +0100 Subject: [PATCH 39/48] Make Merkompiler::run package-private to allow testing --- src/main/java/com/merkrafter/Merkompiler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/merkrafter/Merkompiler.java b/src/main/java/com/merkrafter/Merkompiler.java index a801b922..74e47df8 100644 --- a/src/main/java/com/merkrafter/Merkompiler.java +++ b/src/main/java/com/merkrafter/Merkompiler.java @@ -36,7 +36,7 @@ public static void main(String[] args) { * @param config configuration data for this program call * @throws FileNotFoundException if the input or output file could not be found */ - private static void run(final Config config) throws FileNotFoundException { + static void run(final Config config) throws FileNotFoundException { if (config.isVerbose()) { System.out.println(config); } From 4c8f22af9c9ea9a7b83d07a43d7b96a2c116370e Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 18:28:26 +0100 Subject: [PATCH 40/48] Move fromString to Config --- src/main/java/com/merkrafter/config/Config.java | 14 ++++++++++++++ src/test/java/com/merkrafter/ConfigTest.java | 10 +--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/merkrafter/config/Config.java b/src/main/java/com/merkrafter/config/Config.java index ed3668ca..a2816cc0 100644 --- a/src/main/java/com/merkrafter/config/Config.java +++ b/src/main/java/com/merkrafter/config/Config.java @@ -35,6 +35,10 @@ public boolean isVerbose() { return verbose; } + public static Config fromArgs(final String args) throws ArgumentParserException { + return fromArgs(fromString(args)); + } + public static Config fromArgs(final String[] args) throws ArgumentParserException { // define the parser final ArgumentParser parser = @@ -66,6 +70,16 @@ public static Config fromArgs(final String[] args) throws ArgumentParserExceptio return new Config(inputFileName, outputFileName, verbose); } + /** + * Splits the String of arguments at whitespace into multiple argument tokens. + * + * @param argsAsString string of arguments as written on the command line + * @return an array of arguments + */ + public static String[] fromString(final String argsAsString) { + return argsAsString.split("\\s+"); + } + /** * @return a String representation of this Config class */ diff --git a/src/test/java/com/merkrafter/ConfigTest.java b/src/test/java/com/merkrafter/ConfigTest.java index a07ed769..0ca90998 100644 --- a/src/test/java/com/merkrafter/ConfigTest.java +++ b/src/test/java/com/merkrafter/ConfigTest.java @@ -4,6 +4,7 @@ import net.sourceforge.argparse4j.inf.ArgumentParserException; import org.junit.jupiter.api.Test; +import static com.merkrafter.config.Config.fromString; import static org.junit.jupiter.api.Assertions.*; class ConfigTest { @@ -148,13 +149,4 @@ void parseInputFileAndOutputFileAndVerbosity() throws ArgumentParserException { assertEquals(expectedVerbosity, actualConfig.isVerbose()); } - /** - * Splits the String of arguments at whitespace into multiple argument tokens. - * - * @param argsAsString string of arguments as written on the command line - * @return an array of arguments - */ - private String[] fromString(final String argsAsString) { - return argsAsString.split("\\s+"); - } } \ No newline at end of file From b646f465187a8ecb4ab74399793d33243065a975 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 22:11:58 +0100 Subject: [PATCH 41/48] Add junit params dependency --- merkompiler.iml | 1 + pom.xml | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/merkompiler.iml b/merkompiler.iml index 9dee474e..9f89fe87 100644 --- a/merkompiler.iml +++ b/merkompiler.iml @@ -30,5 +30,6 @@ + \ No newline at end of file diff --git a/pom.xml b/pom.xml index dc1bcd17..72dcf5a9 100644 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,12 @@ argparse4j 0.8.1 + + org.junit.jupiter + junit-jupiter-params + 5.5.2 + test + ${project.artifactId} From a28bd232c6852ddfcaa18b6a24c1372b74ad9375 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 22:12:45 +0100 Subject: [PATCH 42/48] Create basic test environment for integration test cases --- .../com/merkrafter/MerkompilerRunTest.java | 85 +++++++++++++++++++ .../java/com/merkrafter/MerkompilerTest.java | 17 ---- src/test/resources/EmptyClass.expected | 6 ++ src/test/resources/EmptyClass.java | 1 + 4 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 src/test/java/com/merkrafter/MerkompilerRunTest.java delete mode 100644 src/test/java/com/merkrafter/MerkompilerTest.java create mode 100644 src/test/resources/EmptyClass.expected create mode 100644 src/test/resources/EmptyClass.java diff --git a/src/test/java/com/merkrafter/MerkompilerRunTest.java b/src/test/java/com/merkrafter/MerkompilerRunTest.java new file mode 100644 index 00000000..b89acca1 --- /dev/null +++ b/src/test/java/com/merkrafter/MerkompilerRunTest.java @@ -0,0 +1,85 @@ +package com.merkrafter; + +import com.merkrafter.config.Config; +import net.sourceforge.argparse4j.inf.ArgumentParserException; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * This class contains integration test cases for the Merkompiler program. + * In particular, it tests the Merkompiler class's run() method. + * + * @author merkrafter + */ +class MerkompilerRunTest { + + /** + * Temporary directory for the compiler to write to + */ + @TempDir + static Path tempDir; // access must NOT be private; otherwise JUnit could not create it + + // access can not be private; otherwise javadoc could not find the values + public static final String INPUT_FILE_SUFFIX = ".java"; + public static final String OUTPUT_FILE_SUFFIX = ".output"; + public static final String EXPECTED_FILE_SUFFIX = ".expected"; + + /** + * This test case runs the lexer on the file(s) given by ValueSource. + * It assumes that there are baseFileName{@value INPUT_FILE_SUFFIX} and + * baseFileName{@value EXPECTED_FILE_SUFFIX} present under + * src/test/resources in the project. + * The output file for this experiment will be named baseFileName{@value OUTPUT_FILE_SUFFIX} + * and is created as a temporary file by JUnit. + * + * @param baseFileName if used to find the source file name and expected file name and create the output file + * @throws ArgumentParserException if the arguments in the test case are misconfigured (should not happen) + * @throws IOException if there is a read/write error in one of the files + */ + @ParameterizedTest + @ValueSource(strings = "EmptyClass") + void runWithOutputCreatesFile(final String baseFileName) + throws ArgumentParserException, IOException { + final String inputFileName = baseFileName + INPUT_FILE_SUFFIX; // java source file to read + // file where the program output is written to + final String outputFileName = baseFileName + OUTPUT_FILE_SUFFIX; + // file that should identical content as outputFile after run method + final String expectedFileName = baseFileName + EXPECTED_FILE_SUFFIX; + + // load/create required files + final ClassLoader classLoader = getClass().getClassLoader(); + final File inputFile = new File(classLoader.getResource(inputFileName).getFile()); + final File outputFile = tempDir.resolve(outputFileName).toFile(); + final File expectedFile = new File(classLoader.getResource(expectedFileName).getFile()); + + // verify that the test setup is correct + assumeTrue(inputFile.exists(), + "Misconfigured test environment: Missing file " + inputFile.getAbsolutePath()); + assumeTrue(expectedFile.exists(), + "Misconfigured test environment: Missing file " + expectedFile.getAbsolutePath()); + + // run subject under test + final Config config = Config.fromArgs(String.format("%s --output %s", + inputFile.getAbsolutePath(), + outputFile.getAbsolutePath())); + Merkompiler.run(config); + + // check results + assertFilesEqual(expectedFile, outputFile); + } + + private static void assertFilesEqual(final File expectedFile, final File actualFile) + throws IOException { + assertEquals(Files.readAllLines(expectedFile.toPath()), + Files.readAllLines(actualFile.toPath())); + } +} \ No newline at end of file diff --git a/src/test/java/com/merkrafter/MerkompilerTest.java b/src/test/java/com/merkrafter/MerkompilerTest.java deleted file mode 100644 index 0c4b1eb7..00000000 --- a/src/test/java/com/merkrafter/MerkompilerTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.merkrafter; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; - -import static org.junit.jupiter.api.Assertions.*; - -class MerkompilerTest { - - @BeforeEach - void setUp() { - } - - @AfterEach - void tearDown() { - } -} \ No newline at end of file diff --git a/src/test/resources/EmptyClass.expected b/src/test/resources/EmptyClass.expected new file mode 100644 index 00000000..bfeb5dcc --- /dev/null +++ b/src/test/resources/EmptyClass.expected @@ -0,0 +1,6 @@ +EmptyClass.java(1,1): IDENT +EmptyClass.java(1,8): IDENT +EmptyClass.java(1,14): IDENT +EmptyClass.java(1,25): L_BRACE +EmptyClass.java(1,26): R_BRACE +EmptyClass.java(1,26): EOF diff --git a/src/test/resources/EmptyClass.java b/src/test/resources/EmptyClass.java new file mode 100644 index 00000000..36b60bf5 --- /dev/null +++ b/src/test/resources/EmptyClass.java @@ -0,0 +1 @@ +public class EmptyClass {} \ No newline at end of file From ab5d3e8bec53619ae8c8d3e16da4a5b9b37ab0df Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 22:33:41 +0100 Subject: [PATCH 43/48] Make the program output full path names with -v and short names else --- src/main/java/com/merkrafter/Merkompiler.java | 10 ++++++++-- src/main/java/com/merkrafter/config/Config.java | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/merkrafter/Merkompiler.java b/src/main/java/com/merkrafter/Merkompiler.java index 74e47df8..de8e9b31 100644 --- a/src/main/java/com/merkrafter/Merkompiler.java +++ b/src/main/java/com/merkrafter/Merkompiler.java @@ -6,6 +6,7 @@ import com.merkrafter.lexing.TokenType; import net.sourceforge.argparse4j.inf.ArgumentParserException; +import java.io.File; import java.io.FileNotFoundException; import java.io.PrintStream; @@ -41,9 +42,14 @@ static void run(final Config config) throws FileNotFoundException { System.out.println(config); } - final Input input = new Input(config.getInputFile()); + final File inputFile = new File(config.getInputFile()); + final Input input = new Input(inputFile.getAbsolutePath()); final Scanner scanner = new Scanner(input); - scanner.setFilename(config.getInputFile()); + if (config.isVerbose()) { + scanner.setFilename(inputFile.getAbsolutePath()); + } else { + scanner.setFilename(inputFile.getName()); + } PrintStream out = System.out; // write to stdout by default diff --git a/src/main/java/com/merkrafter/config/Config.java b/src/main/java/com/merkrafter/config/Config.java index a2816cc0..64c08009 100644 --- a/src/main/java/com/merkrafter/config/Config.java +++ b/src/main/java/com/merkrafter/config/Config.java @@ -8,6 +8,7 @@ /** * This class holds configuration data for this program. + * It also contains the description of this program's command line options etc. * * @author merkrafter */ @@ -48,7 +49,7 @@ public static Config fromArgs(final String[] args) throws ArgumentParserExceptio parser.addArgument("INPUT").required(true).type(String.class) .help("JavaSST source code file"); parser.addArgument("-v", "--verbose").action(Arguments.storeTrue()) - .help("print more information"); + .help("print more information (absolute paths instead of simple file names in error messages, for instance"); parser.addArgument("-o", "--output").type(String.class).metavar("OUTPUT") .help("output target; default is stdout"); From 3abedd659a2ff334c75a81c3b407821b48b12d0f Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 22:45:45 +0100 Subject: [PATCH 44/48] Simplify MerkompilerRunTest by DRY-ing --- .../com/merkrafter/MerkompilerRunTest.java | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/test/java/com/merkrafter/MerkompilerRunTest.java b/src/test/java/com/merkrafter/MerkompilerRunTest.java index b89acca1..aff36c01 100644 --- a/src/test/java/com/merkrafter/MerkompilerRunTest.java +++ b/src/test/java/com/merkrafter/MerkompilerRunTest.java @@ -49,34 +49,45 @@ class MerkompilerRunTest { @ValueSource(strings = "EmptyClass") void runWithOutputCreatesFile(final String baseFileName) throws ArgumentParserException, IOException { - final String inputFileName = baseFileName + INPUT_FILE_SUFFIX; // java source file to read - // file where the program output is written to - final String outputFileName = baseFileName + OUTPUT_FILE_SUFFIX; + // java source file to read + final File inputFile = getFileFromResource(baseFileName + INPUT_FILE_SUFFIX); // file that should identical content as outputFile after run method - final String expectedFileName = baseFileName + EXPECTED_FILE_SUFFIX; - - // load/create required files - final ClassLoader classLoader = getClass().getClassLoader(); - final File inputFile = new File(classLoader.getResource(inputFileName).getFile()); - final File outputFile = tempDir.resolve(outputFileName).toFile(); - final File expectedFile = new File(classLoader.getResource(expectedFileName).getFile()); - - // verify that the test setup is correct - assumeTrue(inputFile.exists(), - "Misconfigured test environment: Missing file " + inputFile.getAbsolutePath()); - assumeTrue(expectedFile.exists(), - "Misconfigured test environment: Missing file " + expectedFile.getAbsolutePath()); + final File expectedFile = getFileFromResource(baseFileName + EXPECTED_FILE_SUFFIX); + // file where the program output is written to + final File outputFile = tempDir.resolve(baseFileName + OUTPUT_FILE_SUFFIX).toFile(); - // run subject under test final Config config = Config.fromArgs(String.format("%s --output %s", inputFile.getAbsolutePath(), outputFile.getAbsolutePath())); Merkompiler.run(config); - // check results assertFilesEqual(expectedFile, outputFile); } + /** + * Reads the given file using this class's class loader, checks for its existence and finally + * returns it. + * + * @param fileName a file under src/test/resources + * @return a file under the resource directory as specified by fileName + */ + private File getFileFromResource(final String fileName) { + final ClassLoader classLoader = getClass().getClassLoader(); + final File file = new File(classLoader.getResource(fileName).getFile()); + + assumeTrue(file.exists(), + "Misconfigured test environment: Missing file " + file.getAbsolutePath()); + + return file; + } + + /** + * Checks whether two files are equal by comparing their contents line by line. + * + * @param expectedFile the file that defines the base line + * @param actualFile should be equal to expectedFile + * @throws IOException if there is a read/write error in one of the files + */ private static void assertFilesEqual(final File expectedFile, final File actualFile) throws IOException { assertEquals(Files.readAllLines(expectedFile.toPath()), From db949f28c324b07dc511c67e95b3a5f316f83ce2 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 23:08:23 +0100 Subject: [PATCH 45/48] Add test case for not specifying --output --- .../com/merkrafter/MerkompilerRunTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/test/java/com/merkrafter/MerkompilerRunTest.java b/src/test/java/com/merkrafter/MerkompilerRunTest.java index aff36c01..b1c876c6 100644 --- a/src/test/java/com/merkrafter/MerkompilerRunTest.java +++ b/src/test/java/com/merkrafter/MerkompilerRunTest.java @@ -6,8 +6,10 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; @@ -64,6 +66,44 @@ void runWithOutputCreatesFile(final String baseFileName) assertFilesEqual(expectedFile, outputFile); } + /** + * This test case runs the lexer on the file(s) given by ValueSource. + * It assumes that there are baseFileName{@value INPUT_FILE_SUFFIX} and + * baseFileName{@value EXPECTED_FILE_SUFFIX} present under + * src/test/resources in the project. + * The output for this experiment is not specified, hence it tests writing to stdout. + *

+ * This method resets System.out in order to test the output written to it. + * If this method throws an exception, System.out might still be unavailable. + * + * @param baseFileName if used to find the source file name and expected file name + * @throws ArgumentParserException if the arguments in the test case are misconfigured (should not happen) + * @throws IOException if there is a read/write error in one of the files + */ + @ParameterizedTest + @ValueSource(strings = "EmptyClass") + void runWithoutOutput(final String baseFileName) throws ArgumentParserException, IOException { + final PrintStream originalOut = System.out; + try { // will reset System.out in case of errors + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + + // java source file to read + final File inputFile = getFileFromResource(baseFileName + INPUT_FILE_SUFFIX); + // file that should identical content as output after run method + final File expectedFile = getFileFromResource(baseFileName + EXPECTED_FILE_SUFFIX); + // set stdout to testable output stream + System.setOut(new PrintStream(output)); + + // run main program without specifying output + final Config config = Config.fromArgs(inputFile.getAbsolutePath()); + Merkompiler.run(config); + + assertEquals(toString(expectedFile), output.toString().trim()); + } finally { + System.setOut(originalOut); // reset System.out even in case of errors + } + } + /** * Reads the given file using this class's class loader, checks for its existence and finally * returns it. @@ -93,4 +133,16 @@ private static void assertFilesEqual(final File expectedFile, final File actualF assertEquals(Files.readAllLines(expectedFile.toPath()), Files.readAllLines(actualFile.toPath())); } + + /** + * Reads the file contents and joins them to a single string. + * + * @param file the file to read + * @return a string containing the lines joined with a newline character + * + * @throws IOException if a read/write error occurs + */ + private static String toString(final File file) throws IOException { + return String.join("\n", Files.readAllLines(file.toPath())); + } } \ No newline at end of file From 53892c1955d9da2a52670830c89f1a3e5ac8edd8 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 23:17:48 +0100 Subject: [PATCH 46/48] Add test stage to ci pipeline --- .github/workflows/Merkompiler.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/Merkompiler.yml b/.github/workflows/Merkompiler.yml index 62d80766..e9fc48b6 100644 --- a/.github/workflows/Merkompiler.yml +++ b/.github/workflows/Merkompiler.yml @@ -18,6 +18,8 @@ jobs: uses: actions/setup-java@v1 with: java-version: 1.8 + - name: Test with Maven + run: mvn -B test --file pom.xml - name: Build with Maven run: mvn -B package --file pom.xml - name: Upload artifact From febe0d2a9de35380cb983240d7accd6ce537bb86 Mon Sep 17 00:00:00 2001 From: Mark Umnus Date: Mon, 18 Nov 2019 23:40:10 +0100 Subject: [PATCH 47/48] Add README.md --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..2161a375 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# Merkompiler +`Merkompiler` is a JavaSST compiler written in Java as a study project. +It is not meant to be used in production, but rather as a template and inspiration for others +that need to solve similar tasks. + +## Getting started +These instructions will help you compiling and running Merkompiler on your system. + +### JavaSST description +JavaSST is a (pretty) small subset of Java's language features. +A complete description for it can be found in this project's wiki pages. + +### Prerequisites +This program is developed under and tested with + - `Apache maven 3.6.0` (you'll only need that if you plan to test and build Merkompiler yourself) + - `Java 1.8` + +### Building +In order to compile this project yourself, you simply need to run the following commands. +If you just want to use it, skip to installing. +```bash +$ git clone https://github.com/merkrafter/Merkompiler.git +$ mvn package +``` + +### Installing +Merkompiler comes as a standalone jar, hence you don't need to do anything other than +building or downloading it from the github repository's releases tab. + +## Running the tests +Assuming you downloaded the sources via git clone or equivalent, you only have to run: +```bash +$ mvn test +``` + +## Contributing +As this is a study project, direct contributing is not allowed. +You are still invited to open an issue if you find a bug or something similar. + +## Versioning +This project uses [Semantic Versioning](https://semver.org/). + +## License +This project is released under the [MIT license](LICENSE.md). From d098d3256f4be8b0850d089e2a42a81be6e997e6 Mon Sep 17 00:00:00 2001 From: merkrafter Date: Mon, 18 Nov 2019 23:43:48 +0100 Subject: [PATCH 48/48] Create LICENSE.md --- LICENSE.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..42165110 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 merkrafter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.