+package de.yasc.example.petrinet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+
+public final class PetrinetParser implements AutoCloseable {
+
+ private final BufferedReader reader;
+
+ public PetrinetParser(InputStream stream) {
+ reader = new BufferedReader(new InputStreamReader(stream));
+ }
+
+ private static class Line {
+
+ static enum Type {
+ PLACES, TRANSITION, MARKING, COMMENT, UNKNOWN;
+ }
+
+ Type type;
+ Integer val0;
+ Integer val1[];
+ Integer val2[];
+
+ private Integer parsePositiveInt(String token) throws IOException {
+ try {
+ var val = Integer.valueOf(token);
+ if (val < 0) {
+ throw new IOException("invalid input - '" + token + "' is not positive");
+ }
+ return val;
+ } catch (NumberFormatException ex) {
+ throw new IOException("invalid input - '" + token + "' is not an integer");
+ }
+ }
+
+ private void parsePlaces(String tokens[]) throws IOException {
+ if (tokens.length != 2) {
+ throw new IOException("invalid input - syntax of places is 'p <#n>'");
+ }
+ val0 = parsePositiveInt(tokens[1]);
+ }
+
+ private void parseTransition(String tokens[]) throws IOException {
+ if (tokens.length < 4) {
+ throw new IOException("invalid input - syntax of transition is 't <pre_1> ... <pre_m> / <post_1> ... <post_n>'");
+ }
+ var vals = new ArrayList<>();
+ int i = 1;
+ for (; i < tokens.length; i++) {
+ if (tokens[i].equals("/")) {
+ i++;
+ break;
+ }
+ vals.add(parsePositiveInt(tokens[i]));
+ }
+ val1 = vals.toArray(new Integer[0]);
+ if (i == tokens.length) {
+ throw new IOException("invalid input - transition has no postset");
+ }
+ if (val1.length == 0) {
+ throw new IOException("invalid input - transition has no preset");
+ }
+ vals.clear();
+ for (; i < tokens.length; i++) {
+ vals.add(parsePositiveInt(tokens[i]));
+ }
+ val2 = vals.toArray(new Integer[0]);
+ }
+
+ private void parseMarking(String tokens[]) throws IOException {
+ if (tokens.length < 2) {
+ throw new IOException("invalid input - syntax of marking is 'm <place_1> ... <place_n>");
+ }
+ var vals = new ArrayList<>();
+ int i = 1;
+ for (; i < tokens.length; i++) {
+ vals.add(parsePositiveInt(tokens[i]));
+ }
+ val1 = vals.toArray(new Integer[0]);
+ }
+
+ Line(String line) throws IOException {
+ if (line.isBlank()) {
+ type = Type.COMMENT;
+ } else {
+ var tokens = line.split("\\s");
+ assert tokens.length > 0;
+ switch (tokens[0]) {
+ case "p":
+ type = Type.PLACES;
+ parsePlaces(tokens);
+ break;
+ case "t":
+ type = Type.TRANSITION;
+ parseTransition(tokens);
+ break;
+ case "m":
+ type = Type.MARKING;
+ parseMarking(tokens);
+ break;
+ case "#":
+ type = Type.COMMENT;
+ break;
+ default:
+ type = Type.UNKNOWN;
+ }
+ }
+ }
+ }
+
+ public PetriNet read() throws IOException {
+ PetriNet result = null;
+
+ String input;
+ int line = 0;
+ while ((input = reader.readLine()) != null) {
+ line++;
+ try {
+ var parsedLine = new Line(input);
+ switch (parsedLine.type) {
+ case COMMENT: break;
+ case PLACES:
+ if (result == null) {
+ result = new PetriNet(parsedLine.val0);
+ } else {
+ throw new IOException("places command must occur once");
+ }
+ break;
+ case TRANSITION:
+ if (result == null)
+ throw new IOException("the first command must be 'p' to define the places");
+ result.addTransistion(parsedLine.val1, parsedLine.val2);
+ break;
+ case MARKING:
+ if (result == null)
+ throw new IOException("the first command must be 'p' to define the places");
+ result.addMarking(parsedLine.val1);
+ break;
+ default:
+ throw new IOException("unknown command");
+ }
+ } catch (IndexOutOfBoundsException ex) {
+ throw new IOException(String.format("a place with the specified index does not exist (line %d)", line), ex);
+ } catch (IOException ex) {
+ throw new IOException(ex.getMessage() + String.format(" (line %d)", line), ex);
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public void close() throws Exception {
+ reader.close();
+ }
+}