diff --git a/.gitignore b/.gitignore index f4669f3..3112014 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,4 @@ fabric.properties # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* +/.metadata/ diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/XPPCompiler.xml b/.idea/runConfigurations/XPPCompiler.xml new file mode 100644 index 0000000..af8592a --- /dev/null +++ b/.idea/runConfigurations/XPPCompiler.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..11eba31 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + + { + "type": "java", + "name": "Debug (Launch) - Current File", + "request": "launch", + "mainClass": "${file}" + }, + { + "type": "java", + "name": "Debug (Launch)-Main", + "request": "launch", + "mainClass": "Main", + "projectName": "java" + } + ] +} \ No newline at end of file diff --git a/bin/test b/bin/test new file mode 100644 index 0000000..59392a8 --- /dev/null +++ b/bin/test @@ -0,0 +1,10 @@ +//this is a simple comment to test + +x = /*fucking comment */ 10; +int x = 10; //será que ignora? +int y = 10; +batata = 20; +triangulo = 70; +tri%%@ 43566¨ + +=== 1/**/2 \ No newline at end of file diff --git a/bin/test_preprocessed.txt b/bin/test_preprocessed.txt new file mode 100644 index 0000000..a0bd0c5 --- /dev/null +++ b/bin/test_preprocessed.txt @@ -0,0 +1,11 @@ + + +x = 10; +int x = 10; +int y = 10; +batata = 20; +triangulo = 70; +tri%%@ 43566¨ + +=== 12 +$ diff --git a/src/java/Parser.java b/src/java/Parser.java deleted file mode 100644 index aa7e4e0..0000000 --- a/src/java/Parser.java +++ /dev/null @@ -1,3 +0,0 @@ -public class Parser { - -} \ No newline at end of file diff --git a/src/java/Token.java b/src/java/Token.java deleted file mode 100644 index c80884b..0000000 --- a/src/java/Token.java +++ /dev/null @@ -1,3 +0,0 @@ -public class Token { - -} diff --git a/src/java/TokenGenerator.java b/src/java/TokenGenerator.java deleted file mode 100644 index d9fbe4a..0000000 --- a/src/java/TokenGenerator.java +++ /dev/null @@ -1,3 +0,0 @@ -public class TokenGenerator { - -} diff --git a/src/java/TokenType.java b/src/java/TokenType.java deleted file mode 100644 index 52e0a8a..0000000 --- a/src/java/TokenType.java +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Enum used to group all possible token types. - * @author Gabriel Chiquetto, Leonardo-Rocha -*/ -public enum TokenType { - /** Undefined - used when doesn't match any type of token. */ - UNDEF, - /** A sequence of letters, numbers and underscores. */ - IDENTIFIER, - /** A sequence of decimal digits. */ - INTEGER_LITERAL, - /** Relational operator '<'. */ - LESSTHAN, - /** Relational operator '>'. */ - GREATER_THAN, - /** Relational operator '<='. */ - LESS_OR_EQUAL, - /** Relational operator '>='. */ - GREATER_OR_EQUAL, - /** Relational operator '=='. */ - EQUAL, - /** Relational operator '!='. */ - NOT_EQUAL, - /** Addition operator '+'. */ - PLUS, - /** Subtraction operator '-'. */ - MINUS, - /** Multiplication operator '*'. */ - TIMES, - /** Division operator '/'. */ - DIV, - /** Modulo operator '%'. */ - MOD, - /** Attribution operator '='. */ - ATTRIB, - /** Left parenthesis separator '('. */ - LPAREN, - /** Right parenthesis separator ')'. */ - RPAREN, - /** Left bracket separator '['. */ - LBRACKET, - /** Right bracket separator ']'. */ - RBRACKET, - /** Left brace separator '{'. */ - LBRACE, - /** Right brace separator '}'. */ - RBRACE, - /** End of instruction indicator ';'. */ - SEMICOLON, - /** Floating point number or object field accessor '.'. */ - DOT, - /** Array element separator ',' - e.g. String[2] test = {"test1", "test2"}. */ - COMMA, - /** String literal delimitation - e.g. String test = "teste". */ - DOUBLE_QUOTATION, - /** Cpp-style single-line comment using "//". */ - LINE_COMMENT, - /** C-style multi-line comment start using "/*".*/ - LBLOCK_COMMENT, - /** C-style multi-line comment end using "*/".*/ - RBLOCK_COMMENT, - /** End of file indicator. */ - EOF -} diff --git a/src/java/core/CompilerMain.java b/src/java/core/CompilerMain.java new file mode 100644 index 0000000..f2b8f87 --- /dev/null +++ b/src/java/core/CompilerMain.java @@ -0,0 +1,76 @@ +package core; + +import utils.LexicalError; +import utils.Preprocessor; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +/** + * core.CompilerMain class. + * + * @author Leonardo-Rocha, Gabriel Chiquetto. + */ +public class CompilerMain { + + public static void main(String[] args) throws IOException { + + if (args.length > 0) { + File filePath = openFile(args[0]); + + filePath = openFile(preprocessFile(filePath)); + + runTest(filePath); + + LexicalError.computeErrorLog(); + + System.out.println("Process terminated."); + } else { + System.out.println("Please, insert a valid file path."); + } + } + + /** + * @param path path of the file to be open. + * @return open file reference. + */ + private static File openFile(String path) { + return new File(path); + } + + /** + * Runs a test with the TokenGenerator to visually check if it's working. + * + * @param source file to run the token generator. + * @throws IOException if an error occurs during getNextToken(). + */ + private static void runTest(File source) throws IOException { + TokenGenerator tokenizer = new TokenGenerator(source); + Token currentToken = tokenizer.getNextToken(); + while (!currentToken.equalsTokenType(TokenType.EOF)) { + currentToken.showCase(); + currentToken = tokenizer.getNextToken(); + } + + } + + /** + * @param rawSource source to preprocess. + * @return preprocessed file path. + */ + private static String preprocessFile(File rawSource) { + System.out.println("Test file: " + rawSource.getAbsolutePath()); + System.out.println("Pre-processing test file..."); + try { + Preprocessor preprocessor = new Preprocessor(); + preprocessor.preProcess(rawSource); + System.out.println("Preprocess successful."); + } catch (FileNotFoundException ex) { + System.out.println("Unable to open file '" + rawSource + "'"); + } catch (IOException ex) { + System.out.println("Error reading file '" + rawSource + "'"); + } + return rawSource + "_preprocessed.txt"; + } +} \ No newline at end of file diff --git a/src/java/core/Parser.java b/src/java/core/Parser.java new file mode 100644 index 0000000..9941dc5 --- /dev/null +++ b/src/java/core/Parser.java @@ -0,0 +1,47 @@ +package core; + +import java.io.File; +import java.io.IOException; + +public class Parser { + + private TokenGenerator tokenGenerator; + + private Token currentToken; + + private void advanceToken() throws IOException{ + currentToken = this.tokenGenerator.getNextToken(); + } + + public Parser(File sourceCode) throws IOException { + tokenGenerator = new TokenGenerator(sourceCode); + advanceToken(); + } + + public void match(TokenType type){ + //if(type != currentToken.getAttribute()) + } + + public void program(){ + if(currentToken.equalsTokenType(TokenType.CLASS)){ + classList(); + } + } + + private void classList(){ + classDecl(); + classListLinha(); + } + + private void classListLinha(){ + if(currentToken.equalsTokenType(TokenType.CLASS)){ + classList(); + } + } + + private void classDecl(){ + //match(CLASS); + //match(IDENTIFIER); + //classDeclLinha(); + } +} \ No newline at end of file diff --git a/src/java/core/Token.java b/src/java/core/Token.java new file mode 100644 index 0000000..59b6079 --- /dev/null +++ b/src/java/core/Token.java @@ -0,0 +1,111 @@ +package core; + +/** + * This class represents every recognized single unit(token) of the source code. + * A token is a Pair and the attribute is optional. + * + * @author Gabriel Chiquetto, Leonardo-Rocha + */ +class Token { + /** + * Abstract symbol that represents a type of lexical unit. + */ + private final TokenType tokenType; + /** + * Optional value of a token. + */ + private TokenType attribute; + /** + * Sequence of characters that represents the token in the source code. + */ + private String lexeme; + + /** + * Initializes a token with the main fields. + * + * @param tokenType type of the generated token. + * @param attribute value of the token. + */ + public Token(TokenType tokenType, TokenType attribute) { + this.tokenType = tokenType; + this.attribute = attribute; + } + + /** + * Initializes a token without an attribute. + * + * @param tokenType type of the generated token. + */ + public Token(TokenType tokenType) { + this(tokenType, TokenType.UNDEF); + } + + /** + * Override of equals method. + * @param obj Object to evaluate. + * @return true if they're equal. + */ + @Override + public boolean equals(Object obj) { + Token token = (Token) obj; + return this.getTokenType() == token.getTokenType(); + } + + /** + * Compare token types. + * @param tokentype + * @return true if the type of this token equals the given token type. + */ + public boolean equalsTokenType(TokenType tokentype){ + return this.getTokenType() == tokentype; + } + + /** + * Print token informations for debuggin purposes. + */ + public void showCase() { + System.out.print("lexeme:" + this.getLexeme()); + System.out.print(", core.TokenType: " + getTokenType()); + System.out.println(", Attribute: " + getAttribute() + '.'); + } + + /** + * + * @return type of the token. + */ + public TokenType getTokenType() { + return tokenType; + } + + /** + * + * @return attribute of the token. + */ + public TokenType getAttribute() { + return attribute; + } + + /** + * Set the value of the field attribute. + * @param attribute value to set. + */ + public void setAttribute(TokenType attribute){ + this.attribute = attribute; + } + + /** + * + * @return lexeme of the token. + */ + public String getLexeme() { + return lexeme; + } + + /** + * Should only be used once. + * @param lexeme string to set. + */ + void setLexeme(String lexeme) { + this.lexeme = lexeme; + } +} diff --git a/src/java/core/TokenGenerator.java b/src/java/core/TokenGenerator.java new file mode 100644 index 0000000..64f34a6 --- /dev/null +++ b/src/java/core/TokenGenerator.java @@ -0,0 +1,294 @@ +package core; + +import utils.LexicalError; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.LineNumberReader; + +import static java.lang.Character.*; + +/** + * Also known as Scanner or Lexer. This class does the lexical analysis and returns the tokens to the parser. + * + * @author Leonardo-Rocha, Gabriel Chiquetto. + */ +class TokenGenerator { + /** + * Scanner to parse the input. + */ + private LineNumberReader lineNumberReader; + + /** + * Line being evaluated. + */ + private String currentLine; + + /** + * Char at the current position. + */ + private char currentChar; + + /** + * Current position. + */ + private int currentLinePosition; + + private int lexemeStartPosition; + + private char lastLineEndChar; + + /** + * Position of the last returned token. + */ + private int lastTokenEndPosition; + + /** + * Lexeme of the last token returned. + */ + private String lastLexeme; + + private static final String OpString = "+-/%*"; + + /** + * Constructor. + * + * @param sourceCode File reference. + * @throws IOException if an error occurs during bufferedReader readline. + */ + TokenGenerator(File sourceCode) throws IOException { + try { + FileReader fileReader = new FileReader(sourceCode); + lineNumberReader = new LineNumberReader(fileReader); + + assert lineNumberReader != null; + currentLine = lineNumberReader.readLine(); + currentLinePosition = 0; + lastTokenEndPosition = 0; + updateCurrentChar(); + } catch (FileNotFoundException ex) { + System.out.println("Unable to open file '" + sourceCode + "'"); + } catch (NullPointerException e) { + currentChar = '$'; + } + } + + /** + * @return next core.Token in the input. + * @throws IOException if an error occurs during advanceInput(). + */ + public Token getNextToken() throws IOException { + + Token token = null; + + while(isWhitespace(currentChar)){ + advanceInput(); + } + lexemeStartPosition = currentLinePosition; + + if (isLetter(currentChar) || currentChar == '_') { + advanceInput(); + while (isLetterOrDigit(currentChar) || currentChar == '_') + advanceInput(); + + token = new Token(TokenType.IDENTIFIER); + } else if (isDigit(currentChar)) { + advanceInput(); + while (isDigit(currentChar)) { + advanceInput(); + } + + token = new Token(TokenType.INTEGER_LITERAL); + } else if (currentChar == '<') { + advanceInput(); + if (currentChar == '=') { + advanceInput(); + + token = new Token(TokenType.REL_OP, TokenType.LESS_OR_EQUAL); + } + else{ + token = new Token(TokenType.REL_OP, TokenType.LESS_THAN); + } + + } else if (currentChar == '>') { + advanceInput(); + if (currentChar == '=') { + advanceInput(); + + token = new Token(TokenType.REL_OP, TokenType.GREATER_OR_EQUAL); + } + else { + token = new Token(TokenType.REL_OP, TokenType.GREATER_THAN); + } + } else if (currentChar == '=') { + advanceInput(); + if (currentChar == '=') { + advanceInput(); + + token = new Token(TokenType.REL_OP, TokenType.EQUAL); + } else { + token = new Token(TokenType.ATTRIB); + } + } else if (currentChar == '!') { + advanceInput(); + if (currentChar == '=') { + advanceInput(); + + token = new Token(TokenType.NOT_EQUAL); + } else { + LexicalError.expectedChar('=', lineNumberReader.getLineNumber(), currentLinePosition); + } + } else if (currentChar == '+') { + advanceInput(); + + token = new Token(TokenType.PLUS); + } else if (currentChar == '-') { + advanceInput(); + + token = new Token(TokenType.MINUS); + } else if (currentChar == '*') { + advanceInput(); + + token = new Token(TokenType.TIMES); + } else if (currentChar == '/') { + advanceInput(); + + token = new Token(TokenType.DIV); + } else if (currentChar == '%') { + advanceInput(); + + token = new Token(TokenType.MOD); + } else if (currentChar == '(') { + advanceInput(); + + token = new Token(TokenType.LPAREN); + } else if (currentChar == ')') { + advanceInput(); + + token = new Token(TokenType.RPAREN); + } else if (currentChar == '{') { + advanceInput(); + + token = new Token(TokenType.LBRACE); + } else if (currentChar == '}') { + advanceInput(); + + token = new Token(TokenType.RBRACE); + } else if (currentChar == '[') { + advanceInput(); + + token = new Token(TokenType.LBRACKET); + } else if (currentChar == ']') { + advanceInput(); + + token = new Token(TokenType.RBRACKET); + } else if (currentChar == ';') { + advanceInput(); + token = new Token(TokenType.SEMICOLON); + } else if (currentChar == '.') { + advanceInput(); + token = new Token(TokenType.DOT); + } else if (currentChar == ',') { + advanceInput(); + + token = new Token(TokenType.COMMA); + } else if (currentChar == '"') { + advanceInput(); + + token = new Token(TokenType.DOUBLE_QUOTATION); + } else if (currentChar == '$') { + token = new Token(TokenType.EOF); + } else{ + LexicalError.unexpectedChar(currentChar, lineNumberReader.getLineNumber(), currentLinePosition); + advanceInput(); + token = new Token(TokenType.UNDEF); + } + updateLexeme(); + token.setLexeme(lastLexeme); + if (token.equalsTokenType(TokenType.IDENTIFIER)) { + verifyKeywords(token); + } + return token; + } + + /** + * Update lexeme according to the last valid token position. + */ + private void updateLexeme() { + if(currentLinePosition != 0){ + lastTokenEndPosition = currentLinePosition; + lastLexeme = currentLine.substring(lexemeStartPosition, lastTokenEndPosition); + } + else + lastLexeme = "" + lastLineEndChar; + + } + + /** + * Advance on input incrementing the line position and updating the current + * char. + * + * @throws IOException if an error occurs during bufferedReader readline. + */ + private void advanceInput() throws IOException { + if (currentLinePosition + 1 != currentLine.length()) { + currentLinePosition++; + } else { + lastLineEndChar = currentChar; + currentLine = lineNumberReader.readLine(); + currentLinePosition = 0; + } + updateCurrentChar(); + } + + private void updateCurrentChar() { + if (currentLine != null && currentLine.isEmpty()) { + currentChar = ' '; + } else { + currentChar = currentLine.charAt(currentLinePosition); + } + } + + /** + * @param expectedOp char to evaluate. + * @return true if the expectedOp is an operator.. + */ + private boolean isOperator(char expectedOp) { + return OpString.contains("" + expectedOp); + } + + private void verifyKeywords(Token identifier){ + String lexeme = identifier.getLexeme(); + switch(lexeme){ + case "class": + identifier.setAttribute(TokenType.CLASS); + case "extends": + identifier.setAttribute(TokenType.EXTENDS); + case "int": + identifier.setAttribute(TokenType.INT); + case "String": + identifier.setAttribute(TokenType.STRING); + case "break": + identifier.setAttribute(TokenType.BREAK); + case "print": + identifier.setAttribute(TokenType.PRINT); + case "read": + identifier.setAttribute(TokenType.READ); + case "return": + identifier.setAttribute(TokenType.SUPER); + case "if": + identifier.setAttribute(TokenType.IF); + case "else": + identifier.setAttribute(TokenType.ELSE); + case "for": + identifier.setAttribute(TokenType.FOR); + case "new": + identifier.setAttribute(TokenType.NEW); + case "constructor": + identifier.setAttribute(TokenType.CONSTRUCTOR); + default: + } + } +} \ No newline at end of file diff --git a/src/java/core/TokenType.java b/src/java/core/TokenType.java new file mode 100644 index 0000000..1e73904 --- /dev/null +++ b/src/java/core/TokenType.java @@ -0,0 +1,173 @@ +package core; + +/** + * Enum used to group all possible token types. + * + * @author Gabriel Chiquetto, Leonardo-Rocha + */ +enum TokenType { + /** + * Undefined - used when doesn't match any type of token. + */ + UNDEF, + /** + * A sequence of letters, numbers and underscores. + */ + IDENTIFIER, + /** + * Used for class declarations. + */ + CLASS, + /** + * Used for class inheritance. + */ + EXTENDS, + /** + * Primitive type declaration "integer". + */ + INT, + /** + * String reference declaration. + */ + STRING, + /** + * Terminates execution of current scope. + */ + BREAK, + /** + * Show a message in the standard output. + */ + PRINT, + /** + * Read from standard input. + */ + READ, + /** + * It causes program control to transfer back to the caller of the method. + */ + RETURN, + /** + * References the immediate parent of a class. + */ + SUPER, + /** + * Conditional statement that executes a block when the expression is true. + */ + IF, + /** + * Executes when the Boolean expression of the matching "if" is false. + */ + ELSE, + /** + * Iterative Loop control structure. + */ + FOR, + /** + * Memory allocation for a new object. + */ + NEW, + /** + * Method called when the object is allocated. + */ + CONSTRUCTOR, + /** + * A sequence of decimal digits. + */ + INTEGER_LITERAL, + /** + * Relational operator attribute. + */ + REL_OP, + /** + * Relational operator '<'. + */ + LESS_THAN, + /** + * Relational operator '>'. + */ + GREATER_THAN, + /** + * Relational operator '<='. + */ + LESS_OR_EQUAL, + /** + * Relational operator '>='. + */ + GREATER_OR_EQUAL, + /** + * Relational operator '=='. + */ + EQUAL, + /** + * Relational operator '!='. + */ + NOT_EQUAL, + /** + * Addition operator '+'. + */ + PLUS, + /** + * Subtraction operator '-'. + */ + MINUS, + /** + * Multiplication operator '*'. + */ + TIMES, + /** + * Division operator '/'. + */ + DIV, + /** + * Modulo operator '%'. + */ + MOD, + /** + * Attribution operator '='. + */ + ATTRIB, + /** + * Left parenthesis separator '('. + */ + LPAREN, + /** + * Right parenthesis separator ')'. + */ + RPAREN, + /** + * Left bracket separator '['. + */ + LBRACKET, + /** + * Right bracket separator ']'. + */ + RBRACKET, + /** + * Left brace separator '{'. + */ + LBRACE, + /** + * Right brace separator '}'. + */ + RBRACE, + /** + * End of instruction indicator ';'. + */ + SEMICOLON, + /** + * Floating point number or object field accessor '.'. + */ + DOT, + /** + * Array element separator ',' - e.g. String[2] test = {"test1", "test2"}. + */ + COMMA, + /** + * String literal delimitation - e.g. String test = "test". + */ + DOUBLE_QUOTATION, + /** + * End of file indicator. + */ + EOF +} diff --git a/src/java/gui/GUIController.java b/src/java/gui/GUIController.java new file mode 100644 index 0000000..d599915 --- /dev/null +++ b/src/java/gui/GUIController.java @@ -0,0 +1,131 @@ +package gui; + +import core.CompilerMain; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TextArea; +import javafx.stage.FileChooser; +import javafx.stage.Stage; + +import javax.swing.*; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class GUIController { + + @FXML + private MenuItem openFileButton; + @FXML + private MenuItem newFileButton; + @FXML + private MenuItem saveFileButton; + @FXML + private Menu compileButton; + @FXML + private TextArea editorTextArea; + @FXML + private TextArea consoleTextArea; + + private FileChooser fileChooser; + private Stage stage; + private File currentFile; + + public void actionOpenFile(ActionEvent actionEvent) throws IOException { + System.out.println("Opening file..."); + + if (fileChooser == null || stage == null) + return; + + currentFile = fileChooser.showOpenDialog(stage); + openCurrentFile(); + } + + public void actionNewFile(ActionEvent actionEvent) throws IOException { + System.out.println("Creating a new file..."); + if (fileChooser == null || stage == null) + return; + + currentFile = fileChooser.showSaveDialog(stage); + openCurrentFile(); + } + + public void actionSaveFile(ActionEvent actionEvent) throws IOException { + System.out.println("Saving file..."); + String buffer = editorTextArea.getText(); + try { + writeStringToFile(currentFile, buffer); + JOptionPane.showMessageDialog(null, "File saved successfully!"); + } + catch (IOException e) { + JOptionPane.showMessageDialog(null, "Failed to save file."); + } + } + + public void writeStringToFile(File file, String text) throws IOException { + if (file == null) + throw new IllegalArgumentException("File cannot be null."); + + if (text == null) + throw new IllegalArgumentException("Text cannot be null."); + + String filePath = file.getAbsolutePath(); + + try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(filePath))) { + writer.write(text); + } + } + + public void actionCompileProgram(ActionEvent actionEvent) { + System.out.println("Compiling..."); + try { + String[] parameters = {currentFile.getAbsolutePath()}; + CompilerMain.main(parameters); + } + catch (IOException e) { + System.out.println("Invalid file."); + } + } + + private void openCurrentFile() throws IOException { + if (currentFile == null) + throw new IllegalArgumentException("File cannot be null."); + + editorTextArea.setText(""); + LineNumberReader lineNumberReader = new LineNumberReader(new FileReader(currentFile)); + String currentLine; + while ((currentLine = lineNumberReader.readLine()) != null) { + editorTextArea.appendText(currentLine + "\n"); + } + } + + public MenuItem getOpenFileButton() { + return openFileButton; + } + + public MenuItem getNewFileButton() { + return newFileButton; + } + + public MenuItem getSaveFileButton() { + return saveFileButton; + } + + public Menu getCompileButton() { + return compileButton; + } + + public FileChooser getFileChooser() { + return fileChooser; + } + + public void setFileChooser(FileChooser fileChooser) { + this.fileChooser = fileChooser; + } + + public void setStage(Stage stage) { + this.stage = stage; + } +} diff --git a/src/java/gui/MainGUI.java b/src/java/gui/MainGUI.java new file mode 100644 index 0000000..051f2b7 --- /dev/null +++ b/src/java/gui/MainGUI.java @@ -0,0 +1,32 @@ +package gui; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.FileChooser; +import javafx.stage.Stage; + +public class MainGUI extends Application { + + @Override + public void start(Stage primaryStage) throws Exception{ + + FXMLLoader loader = new FXMLLoader(getClass().getResource("/gui/xpp-compiler-gui.fxml")); + Parent root = loader.load(); + primaryStage.setTitle("XPP Compiler"); + primaryStage.setScene(new Scene(root, 300, 275)); + + FileChooser fileChooser = new FileChooser(); + + GUIController controller = loader.getController(); + controller.setFileChooser(fileChooser); + controller.setStage(primaryStage); + + primaryStage.show(); + } + + public static void main(String[] args) { + launch(args); + } +} diff --git a/src/java/gui/xpp-compiler-gui.css b/src/java/gui/xpp-compiler-gui.css new file mode 100644 index 0000000..1f4e77b --- /dev/null +++ b/src/java/gui/xpp-compiler-gui.css @@ -0,0 +1,45 @@ +.root { + -fx-background-color: #3c3f41; +} +.label { + -fx-font-size: 12; + -fx-text-fill: white; +} + +.border-pane { + -fx-background-color: #313335; + -fx-border-color: #ffff; +} + +.text-area .content { + -fx-background-color: #2b2b2b; +} + +.text-area { + -fx-font-size: 12; + -fx-text-fill: white; +} + +.scroll-pane { + -fx-background-color: #2b2b2b; + -fx-fill: #2b2b2b; +} + +.separator { + -fx-color-label-visible: #3c3f41; +} + +.anchor-pane { + -fx-background-color: #2b2b2b; + -fx-fill: #2b2b2b; +} + +.menu-bar { + -fx-background-color: #3c3f41; + -fx-border-color: #ffff; +} + +.menu-item { + -fx-background-color: #3c3f41; + -fx-text-fill: #bbbbbb; +} \ No newline at end of file diff --git a/src/java/gui/xpp-compiler-gui.fxml b/src/java/gui/xpp-compiler-gui.fxml new file mode 100644 index 0000000..a2fd6ee --- /dev/null +++ b/src/java/gui/xpp-compiler-gui.fxml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+
+
+
+
diff --git a/src/java/java.iml b/src/java/java.iml index c90834f..b16c4e5 100644 --- a/src/java/java.iml +++ b/src/java/java.iml @@ -4,8 +4,10 @@ + + \ No newline at end of file diff --git a/src/java/utils/LexicalError.java b/src/java/utils/LexicalError.java new file mode 100644 index 0000000..1983079 --- /dev/null +++ b/src/java/utils/LexicalError.java @@ -0,0 +1,37 @@ +package utils; + +public class LexicalError { + /** + * Indicates the error state. + */ + private static boolean errorState = false; + + /** + * Error log message. + */ + private static String errorLog = ""; + + /** + * Unexpected char error. + * + * @param unexpectedChar char to display why is an error. + */ + public static void unexpectedChar(char unexpectedChar, int line, int position) { + errorLog = errorLog + ("Unexpected char found: '" + unexpectedChar + "'in line: "+ line + ":" + position + "\n"); + errorState = true; + } + + public static void expectedChar(char expectedChar, int line, int position) { + errorLog = errorLog + ("expected char missing: '" + expectedChar + "'in line: "+ line + ":" + position + "\n"); + errorState = true; + } + + /** + * Log error message. + */ + public static void computeErrorLog() { + if (errorState) { + System.out.println(errorLog); + } + } +} \ No newline at end of file diff --git a/src/java/utils/Preprocessor.java b/src/java/utils/Preprocessor.java new file mode 100644 index 0000000..9c40344 --- /dev/null +++ b/src/java/utils/Preprocessor.java @@ -0,0 +1,224 @@ +package utils; + +import java.io.*; + +/** + * Class to Preprocess files. + * + * @author Leonardo-Rocha. + */ +public class Preprocessor { + /** + * Definition of End Of File Marker. + */ + private static final char EOF = '$'; + + /** + * Maximum number of lines in the source code. + */ + private static final int MAX_BUFFER_SIZE = 8192; + + /** + * Preprocessed output. + */ + private String[] output; + + /** + * Buffered reader containing the source code. + */ + private LineNumberReader lineNumberReader; + + /** + * Current line being read. + */ + private String currentLine; + + /** + * PreProcess file. + * + * @param filePath path of the file to be preprocessed. + * @throws IOException if something goes wrong during removeCommentsAndAddEOF. + */ + public void preProcess(File filePath) throws IOException { + output = new String[MAX_BUFFER_SIZE]; + FileReader fileReader = new FileReader(filePath); + lineNumberReader = new LineNumberReader(fileReader); + + removeCommentsAndAddEOF(); + + lineNumberReader.close(); + + printOutputAndWriteToFile(filePath); + } + + /** + * Remove single-line/multi-line comments and add EOF to the end of the file. + * + * @throws IOException if something goes wrong during buffer line reading. + */ + private void removeCommentsAndAddEOF() throws IOException { + + int outputLineIndex = 0; + + while ((currentLine = lineNumberReader.readLine()) != null) { + if (isBeginMultiLineComment()) { + outputLineIndex = handleMultiLineComments(outputLineIndex); + } else if (isSingleLineComment()) { + outputLineIndex = handleSingleLineComments(outputLineIndex); + } else { + output[outputLineIndex++] = currentLine; + } + } + output[outputLineIndex] = "" + EOF; + } + + /** + * @param outputLineIndex last valid output line index. + * @return outputLineIndex. + * @throws IOException if something goes wrong during splitMultiLineComment. + */ + private int handleMultiLineComments(int outputLineIndex) throws IOException { + String[] validCode = splitMultiLineComment(); + for (String line : validCode) { + if (isValidLine(line)) + output[outputLineIndex++] = line; + } + return outputLineIndex; + } + + /** + * @param outputLineIndex last valid output line index. + * @return outputLineIndex. + */ + private int handleSingleLineComments(int outputLineIndex) { + String[] splitStrings; + String processedLine = ""; + if (currentLine.contains("//")){ + splitStrings = currentLine.split("//"); + processedLine = splitStrings[0]; + } + output[outputLineIndex++] = processedLine; + + return outputLineIndex; + } + + /** + * Print preprocessed code and write in a new file with the same name + _preprocessed.txt. + * + * @param filePath original file path. + */ + private void printOutputAndWriteToFile(File filePath) throws IOException { + + BufferedWriter outputWriter; + outputWriter = new BufferedWriter(new FileWriter(filePath + "_preprocessed.txt")); + + for (String line : getOutput()) { + System.out.println(line); + if (isValidLine(line)) { + outputWriter.write(line); + outputWriter.newLine(); + } + if (line.equals("" + EOF)) + break; + } + outputWriter.close(); + } + + /** + * @param line line to be evaluated. + * @return true if the line is valid. + */ + private boolean isValidLine(String line) { + return line != null; + } + + /** + * @return true if the current line begins a Multi-line comment. + */ + private boolean isBeginMultiLineComment() { + return currentLine != null && currentLine.contains("/*"); + } + + /** + * @return true if the current line NOT ends a Multi-line comment + */ + private boolean isNotEndMultiLineComment() { + String EOF = "" + Preprocessor.EOF; + boolean NotEndMultiLineComment = currentLine != null && !currentLine.contains("*/"); + return NotEndMultiLineComment && !currentLine.contains(EOF); + } + + /** + * @return true if the current line is a single-line comment. + */ + private boolean isSingleLineComment() { + return currentLine != null && currentLine.contains("//"); + } + + /** + * @return processed String[] without Multi-line comment. + * @throws IOException if something goes wrong during buffer line reading or string splitting.. + */ + private String[] splitMultiLineComment() throws IOException { + int linesDistance = 0; + String[] output = new String[2]; + + String[] splitStrings = currentLine.split("/\\*"); + + output[0] = getValidStatementAtPosition(splitStrings, 0); + linesDistance = findEndMultiLineComment(linesDistance); + + if (currentLine == null) { + output[1] = ""; + } else if (linesDistance == 0) { + splitStrings = currentLine.split("\\*/"); + output[0] += getValidStatementAtPosition(splitStrings, 1); + } else { + splitStrings = currentLine.split("\\*/"); + output[1] = getValidStatementAtPosition(splitStrings, 1); + } + + return output; + } + + /** + * @param linesDistance Distance between the initial read line and the line where the end Multi-line is found. + * @return linesDistance after the search. + * @throws IOException if an error occurs during buffer line reading. + */ + private int findEndMultiLineComment(int linesDistance) throws IOException { + while (isNotEndMultiLineComment()) { + currentLine = lineNumberReader.readLine(); + linesDistance++; + } + return linesDistance; + } + + /** + * Check the array length and access the given position. + * + * @param splitStrings array of strings to be filtered. + * @param position array position. + * @return validStatement string. + */ + private static String getValidStatementAtPosition(String[] splitStrings, int position) { + String validStatement; + + if (splitStrings != null && splitStrings.length > position) + validStatement = splitStrings[position]; + else + validStatement = ""; + + return validStatement; + } + + /** + * Get the preprocessed output. + * + * @return output. + */ + @SuppressWarnings("WeakerAccess") + public String[] getOutput() { + return output; + } +}