From 812f144c697955459653b4fa8d662cd313c50745 Mon Sep 17 00:00:00 2001 From: Malte Schmitz Date: Thu, 24 Nov 2016 00:52:46 +0100 Subject: [PATCH] Initial version --- .gitignore | 3 + src/Main.java | 5 + src/expression/Addition.java | 35 ++++++ src/expression/Expression.java | 4 + src/expression/Identifier.java | 30 +++++ src/expression/Number.java | 30 +++++ src/expression/Subtraction.java | 35 ++++++ src/parser/Parser.java | 243 ++++++++++++++++++++++++++++++++++++++++ src/parser/SyntaxException.java | 20 ++++ src/program/Assignment.java | 38 +++++++ src/program/Composition.java | 35 ++++++ src/program/Conditional.java | 41 +++++++ src/program/Loop.java | 37 ++++++ src/program/Program.java | 4 + test/parser/ParserTest.java | 25 +++++ 15 files changed, 585 insertions(+) create mode 100644 .gitignore create mode 100644 src/Main.java create mode 100644 src/expression/Addition.java create mode 100644 src/expression/Expression.java create mode 100644 src/expression/Identifier.java create mode 100644 src/expression/Number.java create mode 100644 src/expression/Subtraction.java create mode 100644 src/parser/Parser.java create mode 100644 src/parser/SyntaxException.java create mode 100644 src/program/Assignment.java create mode 100644 src/program/Composition.java create mode 100644 src/program/Conditional.java create mode 100644 src/program/Loop.java create mode 100644 src/program/Program.java create mode 100644 test/parser/ParserTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de447e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +*.iml +/out diff --git a/src/Main.java b/src/Main.java new file mode 100644 index 0000000..6054a64 --- /dev/null +++ b/src/Main.java @@ -0,0 +1,5 @@ +public class Main { + public static void main(String[] args) { + + } +} diff --git a/src/expression/Addition.java b/src/expression/Addition.java new file mode 100644 index 0000000..e0c82a4 --- /dev/null +++ b/src/expression/Addition.java @@ -0,0 +1,35 @@ +package expression; + +public class Addition extends Expression { + public final Expression leftHandSide; + public final Expression rightHandSide; + + public Addition(Expression leftHandSide, Expression rightHandSide) { + this.leftHandSide = leftHandSide; + this.rightHandSide = rightHandSide; + } + + @Override + public String toString() { + return "(" + leftHandSide + " + " + rightHandSide + ")"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Addition addition = (Addition) o; + + if (!leftHandSide.equals(addition.leftHandSide)) return false; + return rightHandSide.equals(addition.rightHandSide); + + } + + @Override + public int hashCode() { + int result = leftHandSide.hashCode(); + result = 31 * result + rightHandSide.hashCode(); + return result; + } +} diff --git a/src/expression/Expression.java b/src/expression/Expression.java new file mode 100644 index 0000000..9e91fa8 --- /dev/null +++ b/src/expression/Expression.java @@ -0,0 +1,4 @@ +package expression; + +abstract public class Expression { +} diff --git a/src/expression/Identifier.java b/src/expression/Identifier.java new file mode 100644 index 0000000..d3b977b --- /dev/null +++ b/src/expression/Identifier.java @@ -0,0 +1,30 @@ +package expression; + +public class Identifier extends Expression { + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Identifier that = (Identifier) o; + + return name.equals(that.name); + + } + + @Override + public String toString() { + return name; + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + public final String name; + + public Identifier(String name) { + this.name = name; + } +} diff --git a/src/expression/Number.java b/src/expression/Number.java new file mode 100644 index 0000000..eb491fb --- /dev/null +++ b/src/expression/Number.java @@ -0,0 +1,30 @@ +package expression; + +public class Number extends Expression { + public final int value; + + public Number(int value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Number number = (Number) o; + + return value == number.value; + + } + + @Override + public String toString() { + return Integer.toString(value); + } + + @Override + public int hashCode() { + return value; + } +} diff --git a/src/expression/Subtraction.java b/src/expression/Subtraction.java new file mode 100644 index 0000000..52d4571 --- /dev/null +++ b/src/expression/Subtraction.java @@ -0,0 +1,35 @@ +package expression; + +public class Subtraction extends Expression { + public final Expression leftHandSide; + public final Expression rightHandSide; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Subtraction that = (Subtraction) o; + + if (!leftHandSide.equals(that.leftHandSide)) return false; + return rightHandSide.equals(that.rightHandSide); + + } + + @Override + public String toString() { + return "(" + leftHandSide + " - " + rightHandSide + ")"; + } + + @Override + public int hashCode() { + int result = leftHandSide.hashCode(); + result = 31 * result + rightHandSide.hashCode(); + return result; + } + + public Subtraction(Expression leftHandSide, Expression rightHandSide) { + this.leftHandSide = leftHandSide; + this.rightHandSide = rightHandSide; + } +} diff --git a/src/parser/Parser.java b/src/parser/Parser.java new file mode 100644 index 0000000..7f2e338 --- /dev/null +++ b/src/parser/Parser.java @@ -0,0 +1,243 @@ +package parser; + +import expression.*; +import expression.Number; +import program.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class Parser { + + private int position = 0; + private String input; + + public Parser(String input) { + this.input = input; + } + + public Program parse() { + Optional programOpt = program(); + Program program = programOpt.orElseThrow(() -> new SyntaxException("Program", position)); + whitespace(); + if (position < input.length()) { + throw new SyntaxException("End of input", position); + } + return program; + } + + private Optional program() { + List statements = new ArrayList<>(); + boolean run = true; + int start = position; + while (run) { + Optional statement = statement(); + statement.ifPresent(stmt -> statements.add(stmt)); + if (statement.isPresent()) { + start = position; + run = token(";"); + } else { + position = start; + run = false; + } + } + Optional program = Optional.empty(); + for (Program statement: statements) { + if (!program.isPresent()) { + program = Optional.of(statement); + } else { + program = program.map(pgm -> new Composition(pgm, statement)); + } + } + return program; + } + + private Optional statement() { + int start = position; + Optional result = assignment(); + if (!result.isPresent()) { + position = start; + result = conditional(); + if (!result.isPresent()) { + position = start; + result = loop(); + } + } + return result; + } + + private Optional loop() { + if (token("while") && token("(")) { + Optional condition = expression(); + return condition.flatMap(cond -> { + if (token(")") && token("{")) { + Optional program = program(); + return program.filter(pgm -> token("}")).map(pgm -> new Loop(cond, pgm)); + } + return Optional.empty(); + }); + } + return Optional.empty(); + } + + private Optional conditional() { + if (token("if") && token("(")) { + Optional condition = expression(); + condition.flatMap(cond -> { + if (token(")") && token("then") && token("{")) { + Optional thenCase = program(); + thenCase.flatMap(thenC -> { + if (token("}") && token("else") && token("{")) { + Optional elseCase = program(); + return elseCase.filter(elseC -> token("}")).map(elseC -> new Conditional(cond, thenC, elseC)); + } + return Optional.empty(); + }); + } + return Optional.empty(); + }); + } + return Optional.empty(); + } + + private Optional assignment() { + Optional identifier = identifier(); + return identifier.flatMap(id -> { + if (token(":=")) { + Optional expression = expression(); + return expression.map(exp -> new Assignment(id, exp)); + } + return Optional.empty(); + }); + } + + private static class OperatorWithExpression { + private final Operator operator; + private final Expression expression; + + private OperatorWithExpression(Operator operator, Expression expression) { + this.operator = operator; + this.expression = expression; + } + } + + private enum Operator { + PLUS, MINUS, NONE; + + Expression toOperation(Expression leftHandSide, Expression rightHandSide) { + if (this == Operator.PLUS) { + return new Addition(leftHandSide, rightHandSide); + } else if (this == Operator.MINUS) { + return new Subtraction(leftHandSide, rightHandSide); + } else { + throw new RuntimeException("Operator invalid"); + } + } + } + + private Optional expression() { + List atoms = new ArrayList<>(); + Operator operator = Operator.PLUS; + int start = position; + while (operator != Operator.NONE) { + Optional atom = atom(); + Operator op = operator; + atom.ifPresent(at -> atoms.add(new OperatorWithExpression(op, at))); + if (atom.isPresent()) { + start = position; + operator = operator(); + } else { + operator = Operator.NONE; + position = start; + } + } + Optional expression = Optional.empty(); + for (OperatorWithExpression atom: atoms) { + if (!expression.isPresent()) { + expression = Optional.of(atom.expression); + } else { + expression = expression.map(expr -> atom.operator.toOperation(expr, atom.expression)); + } + } + return expression; + } + + private Operator operator() { + if (token("+")) { + return Operator.PLUS; + } else if (token("-")) { + return Operator.MINUS; + } else { + return Operator.NONE; + } + } + + private Optional atom() { + int start = position; + Optional result; + if (token("(")) { + Optional expression = expression(); + result = expression.filter(exp -> token(")")); + } else { + position = start; + result = number(); + if (!result.isPresent()) { + position = start; + result = identifier().map(id -> id); + } + } + return result; + } + + private boolean isLowerLetter(char ch) { + return ch >= 'a' && ch <= 'z'; + } + + private Optional number() { + whitespace(); + int start = position; + boolean minus = position < input.length() && input.charAt(position) == '-'; + if (minus) { + position += 1; + } + boolean digitsFound = false; + while (position < input.length() && Character.isDigit(input.charAt(position))) { + position += 1; + digitsFound = true; + } + if (digitsFound) { + return Optional.of(new Number(Integer.parseInt(input.substring(start, position)))); + } else { + return Optional.empty(); + } + } + + private Optional identifier() { + whitespace(); + int start = position; + while (position < input.length() && isLowerLetter(input.charAt(position))) { + position += 1; + } + if (position > start) { + Identifier identifier = new Identifier(input.substring(start, position)); + return Optional.of(identifier); + } + return Optional.empty(); + } + + private void whitespace() { + while(position < input.length() && Character.isWhitespace(input.charAt(position))) { + position += 1; + } + } + + private boolean token(String token) { + whitespace(); + boolean success = position + token.length() <= input.length() && input.substring(position, position + token.length()).equals(token); + if (success) { + position += token.length(); + } + return success; + } +} diff --git a/src/parser/SyntaxException.java b/src/parser/SyntaxException.java new file mode 100644 index 0000000..494deaf --- /dev/null +++ b/src/parser/SyntaxException.java @@ -0,0 +1,20 @@ +package parser; + +public class SyntaxException extends RuntimeException { + private String expected; + private int position; + + public SyntaxException(String expected, int atPosition) { + super(expected + " expected at position " + atPosition); + this.expected = expected; + this.position = atPosition; + } + + public String getExpected() { + return expected; + } + + public int getPosition() { + return position; + } +} diff --git a/src/program/Assignment.java b/src/program/Assignment.java new file mode 100644 index 0000000..f0a077f --- /dev/null +++ b/src/program/Assignment.java @@ -0,0 +1,38 @@ +package program; + +import expression.Expression; +import expression.Identifier; + +public class Assignment extends Program { + public final Identifier identifier; + public final Expression expression; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Assignment that = (Assignment) o; + + if (!identifier.equals(that.identifier)) return false; + return expression.equals(that.expression); + + } + + @Override + public String toString() { + return identifier + " := " + expression; + } + + @Override + public int hashCode() { + int result = identifier.hashCode(); + result = 31 * result + expression.hashCode(); + return result; + } + + public Assignment(Identifier identifier, Expression expression) { + this.identifier = identifier; + this.expression = expression; + } +} diff --git a/src/program/Composition.java b/src/program/Composition.java new file mode 100644 index 0000000..cc11f8a --- /dev/null +++ b/src/program/Composition.java @@ -0,0 +1,35 @@ +package program; + +public class Composition extends Program { + public final Program first; + public final Program second; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Composition that = (Composition) o; + + if (!first.equals(that.first)) return false; + return second.equals(that.second); + + } + + @Override + public String toString() { + return first + " ; " + second; + } + + @Override + public int hashCode() { + int result = first.hashCode(); + result = 31 * result + second.hashCode(); + return result; + } + + public Composition(Program first, Program second) { + this.first = first; + this.second = second; + } +} diff --git a/src/program/Conditional.java b/src/program/Conditional.java new file mode 100644 index 0000000..4202622 --- /dev/null +++ b/src/program/Conditional.java @@ -0,0 +1,41 @@ +package program; + +import expression.Expression; + +public class Conditional extends Program { + public final Expression condition; + public final Program thenCase; + public final Program elseCase; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Conditional that = (Conditional) o; + + if (!condition.equals(that.condition)) return false; + if (!thenCase.equals(that.thenCase)) return false; + return elseCase.equals(that.elseCase); + + } + + @Override + public String toString() { + return "if (" + condition + ") then { " + thenCase + " } else { " + elseCase + " }"; + } + + @Override + public int hashCode() { + int result = condition.hashCode(); + result = 31 * result + thenCase.hashCode(); + result = 31 * result + elseCase.hashCode(); + return result; + } + + public Conditional(Expression condition, Program thenCase, Program elseCase) { + this.condition = condition; + this.thenCase = thenCase; + this.elseCase = elseCase; + } +} diff --git a/src/program/Loop.java b/src/program/Loop.java new file mode 100644 index 0000000..bb1691b --- /dev/null +++ b/src/program/Loop.java @@ -0,0 +1,37 @@ +package program; + +import expression.Expression; + +public class Loop extends Program { + public final Expression condition; + public final Program program; + + public Loop(Expression condition, Program program) { + this.condition = condition; + this.program = program; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Loop loop = (Loop) o; + + if (!condition.equals(loop.condition)) return false; + return program.equals(loop.program); + + } + + @Override + public String toString() { + return "while (" + condition + ") { " + program + " }"; + } + + @Override + public int hashCode() { + int result = condition.hashCode(); + result = 31 * result + program.hashCode(); + return result; + } +} diff --git a/src/program/Program.java b/src/program/Program.java new file mode 100644 index 0000000..c7969be --- /dev/null +++ b/src/program/Program.java @@ -0,0 +1,4 @@ +package program; + +abstract public class Program { +} diff --git a/test/parser/ParserTest.java b/test/parser/ParserTest.java new file mode 100644 index 0000000..68d7c7e --- /dev/null +++ b/test/parser/ParserTest.java @@ -0,0 +1,25 @@ +package parser; + +import expression.Addition; +import expression.Identifier; +import expression.Number; +import expression.Subtraction; +import org.junit.Assert; +import org.junit.Test; +import program.Assignment; +import program.Composition; +import program.Loop; +import program.Program; + +public class ParserTest { + @Test + public void testParse() { + String program = "a := 2 ; b := 4 ; r := 0 ; while (a) { r := r + b ; a := a - 1 }"; + Parser parser = new Parser(program); + Program initialization = new Composition(new Composition(new Assignment(new Identifier("a"), new Number(2)), new Assignment(new Identifier("b"), new Number(4))), new Assignment(new Identifier("r"), new Number(0))); + Program loop = new Loop(new Identifier("a"), new Composition(new Assignment(new Identifier("r"), new Addition(new Identifier("r"), new Identifier("b"))), new Assignment(new Identifier("a"), new Subtraction(new Identifier("a"), new Number(1))))); + Program expected = new Composition(initialization, loop); + Program actual = parser.parse(); + Assert.assertEquals(expected, actual); + } +}