diff --git a/pom.xml b/pom.xml index 57094da..7c5546f 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ ir.ac.sbu PGen - 2.0 + 2.1 UTF-8 diff --git a/src/main/java/ir/ac/sbu/controller/EdgePropertiesController.java b/src/main/java/ir/ac/sbu/controller/EdgePropertiesController.java index c0f73e1..2b0365b 100644 --- a/src/main/java/ir/ac/sbu/controller/EdgePropertiesController.java +++ b/src/main/java/ir/ac/sbu/controller/EdgePropertiesController.java @@ -1,5 +1,6 @@ package ir.ac.sbu.controller; +import ir.ac.sbu.utility.CheckUtility; import ir.ac.sbu.utility.DialogUtility; import javafx.event.ActionEvent; import javafx.fxml.FXML; @@ -34,12 +35,14 @@ public void init(EdgeModel edge) { } public void apply(ActionEvent actionEvent) { - if (tokenText.getText().trim().isEmpty()) { - DialogUtility.showErrorDialog("Token can not be empty"); - } else { + try { + CheckUtility.checkTokenName(tokenText.getText()); + CheckUtility.checkFunctionName(funcText.getText()); Stage stage = (Stage) applyBtn.getScene().getWindow(); CommandManager.getInstance().applyCommand(new ChangeEdgeCmd(edge, tokenText.getText(), funcText.getText(), graphChk.isSelected(), globalChk.isSelected())); stage.close(); + } catch (IllegalArgumentException e) { + DialogUtility.showErrorDialog(e.getMessage()); } } } diff --git a/src/main/java/ir/ac/sbu/controller/MainController.java b/src/main/java/ir/ac/sbu/controller/MainController.java index 265394a..2a5f32f 100644 --- a/src/main/java/ir/ac/sbu/controller/MainController.java +++ b/src/main/java/ir/ac/sbu/controller/MainController.java @@ -7,6 +7,7 @@ import ir.ac.sbu.parser.LLParserGenerator; import ir.ac.sbu.service.ExportService; import ir.ac.sbu.service.SaveLoadService; +import ir.ac.sbu.utility.CheckUtility; import ir.ac.sbu.utility.DialogUtility; import ir.ac.sbu.utility.GenerateUID; import ir.ac.sbu.utility.ResourceUtility; @@ -103,22 +104,24 @@ protected void updateItem(GraphModel item, boolean empty) { renameBtn.setOnAction(event -> { Optional newValue = DialogUtility.showInputDialog(cell.getItem().getName(), "Rename Graph"); if (newValue.isPresent()) { - if (!newValue.get().trim().isEmpty()) { + try { + CheckUtility.checkGraphName(newValue.get()); cell.getItem().setName(newValue.get()); - } else { - DialogUtility.showErrorDialog("Name of graph can not be empty"); + } catch (IllegalArgumentException e) { + DialogUtility.showErrorDialog(e.getMessage()); } } }); duplicateBtn.setOnAction(event -> { Optional result = DialogUtility.showInputDialog(cell.getItem().getName(), "New Graph"); if (result.isPresent()) { - if (!result.get().trim().isEmpty()) { + try { + CheckUtility.checkGraphName(result.get()); GraphModel currentGraph = cell.getItem(); GraphModel duplicateGraph = currentGraph.createCopy(result.get()); graphs.add(duplicateGraph); - } else { - DialogUtility.showErrorDialog("Name of graph can not be empty"); + } catch (IllegalArgumentException e) { + DialogUtility.showErrorDialog(e.getMessage()); } } }); @@ -172,9 +175,9 @@ public void exportFullParser(ActionEvent actionEvent) { File directorySelected = DialogUtility.showDirectoryDialog(pane.getScene().getWindow()); try { if (directorySelected != null) { - InputStream parserSource = ResourceUtility.getResourceAsStream("parser/Parser.code"); - InputStream lexicalSource = ResourceUtility.getResourceAsStream("parser/Lexical.code"); - InputStream codeGeneratorSource = ResourceUtility.getResourceAsStream("parser/CodeGenerator.code"); + InputStream parserSource = ResourceUtility.getResourceAsStream("parser/Parser.java"); + InputStream lexicalSource = ResourceUtility.getResourceAsStream("parser/Lexical.java"); + InputStream codeGeneratorSource = ResourceUtility.getResourceAsStream("parser/CodeGenerator.java"); Path destination = Paths.get(directorySelected.getPath()); LLParserGenerator parser = new LLParserGenerator(graphs); diff --git a/src/main/java/ir/ac/sbu/parser/LLParserGenerator.java b/src/main/java/ir/ac/sbu/parser/LLParserGenerator.java index 5e89635..2782ef3 100644 --- a/src/main/java/ir/ac/sbu/parser/LLParserGenerator.java +++ b/src/main/java/ir/ac/sbu/parser/LLParserGenerator.java @@ -1,12 +1,16 @@ package ir.ac.sbu.parser; +import ir.ac.sbu.command.ChangeEdgeCmd; +import ir.ac.sbu.command.CommandManager; import ir.ac.sbu.exception.TableException; import ir.ac.sbu.parser.builder.Action; import ir.ac.sbu.parser.builder.LLCell; +import ir.ac.sbu.utility.CheckUtility; import ir.ac.sbu.utility.DialogUtility; import ir.ac.sbu.wagu.Block; import ir.ac.sbu.wagu.Board; import ir.ac.sbu.wagu.Table; +import javafx.stage.Stage; import javafx.util.Pair; import ir.ac.sbu.model.EdgeModel; import ir.ac.sbu.model.GraphModel; @@ -51,7 +55,7 @@ public LLParserGenerator(List graphs) throws TableException { checkGraphs(graphs); checkTokens(); - variableGraph = graphs.stream().collect(Collectors.toMap(GraphModel::getName, Function.identity(), (o, o2) -> o)); + variableGraph = graphs.stream().collect(Collectors.toMap(GraphModel::getName, Function.identity(), (o, v) -> o)); allNodes = graphs.stream().flatMap(graphModel -> graphModel.getNodes().stream()) .collect(Collectors.toList()); allEdges = graphs.stream() @@ -68,11 +72,25 @@ public LLParserGenerator(List graphs) throws TableException { } private void checkEdges() throws TableException { + List messages = new ArrayList<>(); + + for (EdgeModel edge : allEdges) { + try { + CheckUtility.checkFunctionName(edge.getFunction()); + } catch (IllegalArgumentException e) { + messages.add(e.getMessage()); + } + } + if (allEdges.stream() .filter(EdgeModel::isGraph) .map(EdgeModel::getToken) .anyMatch(token -> token.equals("MAIN"))) { - throw new TableException(Collections.singletonList("Graph MAIN should not used in graphs")); + messages.add("Graph MAIN should not used in graphs."); + } + + if (!messages.isEmpty()) { + throw new TableException(messages); } } @@ -82,18 +100,25 @@ private void checkTokens() throws TableException { tokenAsInt.put("$", 0); int tokenUID = 1; for (String token : tokens) { - if (token.startsWith("$")) { - messages.add("All string starting with $ are predefined tokens"); + try { + CheckUtility.checkTokenName(token); + tokenAsInt.put(token, tokenUID); + tokenUID++; + } catch (IllegalArgumentException e) { + messages.add(e.getMessage()); } - tokenAsInt.put(token, tokenUID); - tokenUID++; } for (String variable : variables) { - if (tokenAsInt.put(variable, tokenUID) != null) { - messages.add(String.format("%s Should be either a token or a graph", variable)); + try { + CheckUtility.checkGraphName(variable); + if (tokenAsInt.put(variable, tokenUID) != null) { + messages.add(String.format("%s Should be either a token or a graph.", variable)); + } + tokenUID++; + } catch (IllegalArgumentException e) { + messages.add(e.getMessage()); } - tokenUID++; } tokensSortedById = tokenAsInt.keySet().stream() @@ -119,10 +144,15 @@ private void checkGraphs(List graphs) throws TableException { .forEach(s -> messages.add(String.format("Duplicate graph %s exist", s))); List graphWithNoFinalNode = graphs.stream(). - filter(graph -> graph.getNodes().stream().noneMatch(NodeModel::isFinalNode)).collect(Collectors.toList()); - graphWithNoFinalNode.forEach(graph -> messages.add(String.format("Graph %s doesn't have final node", graph.getName()))); - List graphWithNoStartNode = graphs.stream().filter(graph -> graph.getStart() == null).collect(Collectors.toList()); - graphWithNoStartNode.forEach(graph -> messages.add(String.format("Graph %s doesn't have start node", graph.getName()))); + filter(graph -> graph.getNodes().stream().noneMatch(NodeModel::isFinalNode)) + .collect(Collectors.toList()); + graphWithNoFinalNode.forEach(graph -> + messages.add(String.format("Graph %s doesn't have final node", graph.getName()))); + List graphWithNoStartNode = graphs.stream() + .filter(graph -> graph.getStart() == null) + .collect(Collectors.toList()); + graphWithNoStartNode.forEach(graph -> + messages.add(String.format("Graph %s doesn't have start node", graph.getName()))); if (!messages.isEmpty()) { throw new TableException(messages); @@ -213,11 +243,10 @@ public void buildTable(File file) throws TableException { writer.println(allNodes.size() + " " + table[0].length); writer.println(startNode); - writer.println(String.join(" ", tokensSortedById)); + writer.println(String.join(CheckUtility.DELIMITER, tokensSortedById)); for (LLCell[] cellOfNode : table) { - for (LLCell cell : cellOfNode) { - writer.print(cell + " "); - } + writer.print(Arrays.stream(cellOfNode).map(LLCell::toString) + .collect(Collectors.joining(CheckUtility.DELIMITER))); writer.println(); } writer.println(); diff --git a/src/main/java/ir/ac/sbu/utility/CheckUtility.java b/src/main/java/ir/ac/sbu/utility/CheckUtility.java new file mode 100644 index 0000000..a4a4755 --- /dev/null +++ b/src/main/java/ir/ac/sbu/utility/CheckUtility.java @@ -0,0 +1,34 @@ +package ir.ac.sbu.utility; + +public class CheckUtility { + public static String DELIMITER = ","; + + private CheckUtility() { + } + + public static void checkGraphName(String graphName) { + if (graphName.trim().isEmpty()) { + throw new IllegalArgumentException("Graph name can not be empty."); + } else if (graphName.contains(DELIMITER)) { + throw new IllegalArgumentException("Graph can not contain '" + DELIMITER + "' character."); + } + } + + public static void checkTokenName(String tokenName) { + if (tokenName.startsWith("$")) { + throw new IllegalArgumentException("All string starting with $ are predefined tokens."); + } else if (tokenName.trim().isEmpty()) { + throw new IllegalArgumentException("Token can not be empty."); + } else if (tokenName.contains(DELIMITER)) { + throw new IllegalArgumentException("Token can not contain '" + DELIMITER + "' character."); + } + } + + public static void checkFunctionName(String functionName) { + if (functionName.contains(DELIMITER)) { + throw new IllegalArgumentException("Token can not contain '" + DELIMITER + "' character."); + } else if (functionName.contains(" ")) { + throw new IllegalArgumentException("Token can not contain ' ' character."); + } + } +} diff --git a/src/main/resources/parser/CodeGenerator.code b/src/main/resources/parser/CodeGenerator.java similarity index 100% rename from src/main/resources/parser/CodeGenerator.code rename to src/main/resources/parser/CodeGenerator.java diff --git a/src/main/resources/parser/Lexical.code b/src/main/resources/parser/Lexical.java similarity index 100% rename from src/main/resources/parser/Lexical.code rename to src/main/resources/parser/Lexical.java diff --git a/src/main/resources/parser/Parser.code b/src/main/resources/parser/Parser.java similarity index 60% rename from src/main/resources/parser/Parser.code rename to src/main/resources/parser/Parser.java index 61ba20c..f84374b 100644 --- a/src/main/resources/parser/Parser.code +++ b/src/main/resources/parser/Parser.java @@ -2,6 +2,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; +import java.util.stream.Collectors; enum Action { ERROR, SHIFT, GOTO, PUSH_GOTO, REDUCE, ACCEPT @@ -10,12 +11,12 @@ enum Action { class LLCell { private Action action; private int target; - private String function; + private List functions; - public LLCell(Action action, int target, String function) { + public LLCell(Action action, int target, List functions) { this.action = action; this.target = target; - this.function = function; + this.functions = functions; } public Action getAction() { @@ -26,15 +27,18 @@ public int getTarget() { return target; } - public String getFunction() { - return function; + public List getFunction() { + return functions; } } public class Parser { + public static String TABLE_DELIMITER = ","; + private Lexical lexical; private CodeGenerator codeGenerator; + private boolean debugMode; private String[] symbols; private LLCell[][] parseTable; @@ -43,6 +47,11 @@ public class Parser { private List recoveryState; + public Parser(Lexical lexical, CodeGenerator codeGenerator, String nptPath, boolean debugMode) { + this(lexical, codeGenerator, nptPath); + this.debugMode = debugMode; + } + public Parser(Lexical lexical, CodeGenerator codeGenerator, String nptPath) { this.lexical = lexical; this.codeGenerator = codeGenerator; @@ -55,28 +64,40 @@ public Parser(Lexical lexical, CodeGenerator codeGenerator, String nptPath) { try { Scanner in = new Scanner(new FileInputStream(nptPath)); String[] tmpArr = in.nextLine().trim().split(" "); - int rowSize = Integer.valueOf(tmpArr[0]); - int colSize = Integer.valueOf(tmpArr[1]); - startNode = Integer.valueOf(in.nextLine()); - symbols = in.nextLine().trim().split(" "); + int rowSize = Integer.parseInt(tmpArr[0]); + int colSize = Integer.parseInt(tmpArr[1]); + startNode = Integer.parseInt(in.nextLine()); + symbols = in.nextLine().trim().split(TABLE_DELIMITER); parseTable = new LLCell[rowSize][colSize]; for (int i = 0; i < rowSize; i++) { - tmpArr = in.nextLine().trim().split(" "); - if (tmpArr.length != colSize * 3) { - throw new RuntimeException("Invalid .npt file"); + tmpArr = in.nextLine().trim().split(TABLE_DELIMITER); + if (tmpArr.length != colSize) { + throw new RuntimeException("Invalid .npt file: File contains rows with length" + + " bigger than column size."); } for (int j = 0; j < colSize; j++) { - parseTable[i][j] = new LLCell(Action.values()[Integer.parseInt((tmpArr[j * 3]))], - Integer.parseInt(tmpArr[j * 3 + 1]), - tmpArr[j * 3 + 2]); + String[] cellParts = tmpArr[j].split(" "); + if (cellParts.length != 3) { + throw new RuntimeException("Invalid .npt file: Parser cells must have extactly 3 values."); + } + Action action = Action.values()[Integer.parseInt(cellParts[0])]; + int target = Integer.parseInt(cellParts[1]); + List allFunctions; + if (cellParts[2].equals("NoSem")) { + allFunctions = new ArrayList<>(); + } else { + allFunctions = Arrays.stream(cellParts[2].substring(1).split("[;]")) + .filter(s -> !s.isEmpty()).collect(Collectors.toList()); + } + parseTable[i][j] = new LLCell(action, target, allFunctions); } } } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { - throw new RuntimeException("Invalid .npt file"); + throw new RuntimeException("Invalid .npt file."); } catch (FileNotFoundException e) { - throw new RuntimeException("Unable to load .npt file", e); + throw new RuntimeException("Unable to load .npt file.", e); } } @@ -87,10 +108,19 @@ public void parse() { while (!accepted) { String tokenText = symbols[tokenID]; LLCell cell = parseTable[currentNode][tokenID]; + if (debugMode) { + System.out.println("Current token: text='" + symbols[tokenID] + "' id=" + tokenID); + System.out.println("Current node: " + currentNode); + System.out.println("Current cell of parser table: " + + "target-node=" + cell.getTarget() + + " action=" + cell.getAction() + + " function=" + cell.getFunction()); + System.out.println(String.join("", Collections.nCopies(50, "-"))); + } switch (cell.getAction()) { case ERROR: updateRecoveryState(currentNode, tokenText); - generateError("Unable to parse input"); + generateError("Unable to parse input."); case SHIFT: doSemantics(cell.getFunction()); tokenID = nextTokenID(); @@ -125,8 +155,10 @@ public void parse() { } private void generateError(String message) { + System.out.flush(); + System.out.println("Error happened while parsing ..."); for (String state : recoveryState) { - System.err.println(state); + System.out.println(state); } throw new RuntimeException(message); } @@ -145,23 +177,19 @@ private void updateRecoveryState(int currentNode, String token) { private int nextTokenID() { String token = lexical.nextToken(); for (int i = 0; i < symbols.length; i++) { - if (symbols[i].equalsIgnoreCase(token)) { + if (symbols[i].equals(token)) { return i; } } throw new RuntimeException("Undefined token: " + token); } - private void doSemantics(String semantics) { - if (semantics.equals("NoSem")) { - return; + private void doSemantics(List functions) { + if (debugMode) { + System.out.println("Execute semantic codes: " + functions); } - semantics = semantics.substring(1); - String[] allFunctions = semantics.split("[;]"); - for (String function : allFunctions) { - if (function.length() > 0) { - codeGenerator.doSemantic(function); - } + for (String function : functions) { + codeGenerator.doSemantic(function); } } }