| @@ -0,0 +1,3 @@ | |||
| .idea/ | |||
| *.iml | |||
| /out | |||
| @@ -0,0 +1,5 @@ | |||
| public class Main { | |||
| public static void main(String[] args) { | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -0,0 +1,4 @@ | |||
| package expression; | |||
| abstract public class Expression { | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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<Program> 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> program() { | |||
| List<Program> statements = new ArrayList<>(); | |||
| boolean run = true; | |||
| int start = position; | |||
| while (run) { | |||
| Optional<Program> statement = statement(); | |||
| statement.ifPresent(stmt -> statements.add(stmt)); | |||
| if (statement.isPresent()) { | |||
| start = position; | |||
| run = token(";"); | |||
| } else { | |||
| position = start; | |||
| run = false; | |||
| } | |||
| } | |||
| Optional<Program> 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<Program> statement() { | |||
| int start = position; | |||
| Optional<Program> result = assignment(); | |||
| if (!result.isPresent()) { | |||
| position = start; | |||
| result = conditional(); | |||
| if (!result.isPresent()) { | |||
| position = start; | |||
| result = loop(); | |||
| } | |||
| } | |||
| return result; | |||
| } | |||
| private Optional<Program> loop() { | |||
| if (token("while") && token("(")) { | |||
| Optional<Expression> condition = expression(); | |||
| return condition.flatMap(cond -> { | |||
| if (token(")") && token("{")) { | |||
| Optional<Program> program = program(); | |||
| return program.filter(pgm -> token("}")).map(pgm -> new Loop(cond, pgm)); | |||
| } | |||
| return Optional.empty(); | |||
| }); | |||
| } | |||
| return Optional.empty(); | |||
| } | |||
| private Optional<Program> conditional() { | |||
| if (token("if") && token("(")) { | |||
| Optional<Expression> condition = expression(); | |||
| condition.flatMap(cond -> { | |||
| if (token(")") && token("then") && token("{")) { | |||
| Optional<Program> thenCase = program(); | |||
| thenCase.flatMap(thenC -> { | |||
| if (token("}") && token("else") && token("{")) { | |||
| Optional<Program> 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<Program> assignment() { | |||
| Optional<Identifier> identifier = identifier(); | |||
| return identifier.flatMap(id -> { | |||
| if (token(":=")) { | |||
| Optional<Expression> 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> expression() { | |||
| List<OperatorWithExpression> atoms = new ArrayList<>(); | |||
| Operator operator = Operator.PLUS; | |||
| int start = position; | |||
| while (operator != Operator.NONE) { | |||
| Optional<Expression> 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> 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<Expression> atom() { | |||
| int start = position; | |||
| Optional<Expression> result; | |||
| if (token("(")) { | |||
| Optional<Expression> 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<Expression> 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> 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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -0,0 +1,4 @@ | |||
| package program; | |||
| abstract public class Program { | |||
| } | |||
| @@ -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); | |||
| } | |||
| } | |||