| @@ -1,21 +1,20 @@ | |||
| /*!! Expression*/ | |||
| /*!! Expression */ | |||
| /*! | |||
| Expression | |||
| ============== | |||
| */ | |||
| /*!- Header */ | |||
| package expression; | |||
| /*! `Expression` is the common abstract class for Expressions that can be evaluated using the `Evaluator`. */ | |||
| abstract public class Expression { } | |||
| /*! Expression can be written as the following | |||
| Expression can be written as the following | |||
| [Algebraic Data Type (ADT)](https://en.wikipedia.org/wiki/Algebraic_data_type) | |||
| Expression = Addition(leftHandSide: Expression, rightHandSide: Expression) | |||
| | Subtraction(leftHandSide: Expression, rightHandSide: Expression) | |||
| | Identifier(name: String) | |||
| | Int(value: int) | |||
| */ | |||
| */ | |||
| /*!- Header */ | |||
| package expression; | |||
| /*! `Expression` is the common abstract class for Expressions that can be evaluated using the `Evaluator`. */ | |||
| abstract public class Expression { } | |||
| @@ -1,11 +1,11 @@ | |||
| /*!! Expression*/ | |||
| /*!! Expression */ | |||
| /*! | |||
| Identifier | |||
| ========== | |||
| */ | |||
| /*! Header*/ | |||
| /*!- Header */ | |||
| package expression; | |||
| /*! An `Identifier` consists only of the `name` of the identifier. This class is only needed as a wrapper which allows | |||
| @@ -1,4 +1,4 @@ | |||
| /*!! Expression*/ | |||
| /*!! Expression */ | |||
| /*! | |||
| Int_(eger)_ | |||
| @@ -1,3 +1,26 @@ | |||
| /*!! Interpreter */ | |||
| /*! | |||
| Evaluator | |||
| ========= | |||
| The evaluator implements the semantics defined by the function `eval: Expr * V -> Z`, where `V = Id -> Z` is the set | |||
| of all variable valuations to the set `Z` set of integers. `eval` is inductively defined as follows: | |||
| eval(e1 "+" e2, v) = eval(e1, v) + eval(e2, v) | |||
| eval(e1 "-" e2, v) = eval(e1, v) − eval(e2, v) | |||
| eval(x, v) = v(x) | |||
| eval(z, v) = z | |||
| with | |||
| - expressions `e`, `e1`, `e2`, | |||
| - a variable valuation `v`, | |||
| - and identifier `x` and | |||
| - an integer `z`. | |||
| */ | |||
| /*- Header */ | |||
| package interpreter; | |||
| import expression.*; | |||
| @@ -5,6 +28,17 @@ import expression.*; | |||
| import java.util.HashMap; | |||
| import java.util.Map; | |||
| /*! The `Evaluator` implements the evaluation function defined above with the help of the | |||
| [Visitor](${basePath}/src/main/java/interpreter/Visitor.java.html). The `Evaluator` | |||
| takes an `Expression` in the constructor and provides a method `eval()` which evaluates | |||
| the given expression and returns the result as an integer. For a given `expression` of type `Expression` | |||
| it can be used as follows | |||
| Evaluator evaluator = new Evaluator(expression) | |||
| System.out.println(evaluator.eval()); | |||
| The evaluation function `eval` takes the variable valuation `v`, which is passed on recursively. As the valuation | |||
| is not changed during the evaluation process, it can be stored in a global variable which is not changed. */ | |||
| public class Evaluator extends Visitor<Integer> { | |||
| final Expression expression; | |||
| @@ -19,20 +53,33 @@ public class Evaluator extends Visitor<Integer> { | |||
| return visit(expression); | |||
| } | |||
| /*! | |||
| eval(e1 "+" e2, v) = eval(e1, v) + eval(e2, v) | |||
| */ | |||
| public Integer visitAddition(Addition addition) { | |||
| return visit(addition.leftHandSide) + visit(addition.rightHandSide); | |||
| } | |||
| /*! | |||
| eval(e1 "-" e2, v) = eval(e1, v) - eval(e2, v) | |||
| */ | |||
| public Integer visitSubtraction(Subtraction subtraction) { | |||
| return visit(subtraction.leftHandSide) - visit(subtraction.rightHandSide); | |||
| } | |||
| /*! | |||
| eval(x, v) = v(x) | |||
| */ | |||
| public Integer visitInt(Int integer) { | |||
| return integer.value; | |||
| } | |||
| public Integer visitIdentifier(Identifier identifier) { | |||
| /*! Make sure that the identifier actually exists in the valuation and raise an exception otherwise. */ | |||
| if (valuation.containsKey(identifier.name)) { | |||
| /*! | |||
| eval(z, v) = z | |||
| */ | |||
| return valuation.get(identifier.name); | |||
| } else { | |||
| throw new InterpreterException("Identifier " + identifier.name + " not found."); | |||
| @@ -1,3 +1,36 @@ | |||
| /*!! Interpreter */ | |||
| /*! | |||
| Interpreter | |||
| =========== | |||
| The interpreter consists of the `Interpreter` defined in this file that can run a `Program` and the `Evaluator` | |||
| that can evaluate an `Expression`. | |||
| The interpreter implements the semantics defined by the function `sem: Prog * V -> V`, where `V = Id -> Z` is the set | |||
| of all variable valuations to the set `Z` set of integers. `sem` is inductively defined as follows: | |||
| sem(x ":=" e, v) = v.update(x, eval(e, v)) | |||
| sem(c1 ";" c2) = sem(c2, sem(c1, v)) | |||
| sem("if" "(" e ")" "then" "{" c1 "}" else "{" c2 "}") = | |||
| sem(c1, v) if eval(e, v) != 0 | |||
| sem(c2, v) else | |||
| sem("while" "(" e ")" "{" c "}", v) = | |||
| sem(c ";" "while" "(" e ")" "{" c "}", v) if eval(e, v) != 0 | |||
| v else | |||
| with | |||
| - a variable valuation `v`, | |||
| - an expression `e`, | |||
| - an identifier `x` and | |||
| - programs `c`, `c1`, `c2`. | |||
| The evaluation function `eval` is described at the | |||
| [Evaluator](${basePath}/src/main/java/interpreter/Evaluator.java.html). | |||
| */ | |||
| /*!- Header */ | |||
| package interpreter; | |||
| import program.*; | |||
| @@ -5,6 +38,16 @@ import program.*; | |||
| import java.util.HashMap; | |||
| import java.util.Map; | |||
| /*! The `Interpreter` implements the semantic function defined above with the help of the | |||
| [Visitor](${basePath}/src/main/java/interpreter/Visitor.java.html). The `Interpreter` | |||
| runs the given `Program` in the constructor and can be used as follows on a given `program` of type `Program`. | |||
| Interpreter interpreter = new Interpreter(program); | |||
| System.out.println(interpreter.getValuation()); | |||
| The semantic function `sem` passes along the variable valuation `v`. In the `Interpreter` the valuation is realized | |||
| as global variable `valuation`. Because of the in-order execution this global variable always represents the state | |||
| of the `v` passed to the semantic function `sem`. */ | |||
| public class Interpreter extends Visitor { | |||
| final Program program; | |||
| final Map<String, Integer> valuation = new HashMap<String, Integer>(); | |||
| @@ -20,16 +63,27 @@ public class Interpreter extends Visitor { | |||
| visit(program); | |||
| } | |||
| /*! | |||
| sem(x ":=" e, v) = v.update(x, eval(e, v)) | |||
| */ | |||
| public void visitAssignment(Assignment assignment) { | |||
| Evaluator evaluator = new Evaluator(assignment.expression, valuation); | |||
| valuation.put(assignment.identifier.name, evaluator.eval()); | |||
| } | |||
| /*! | |||
| sem(c1 ";" c2) = sem(c2, sem(c1, v)) | |||
| */ | |||
| public void visitComposition(Composition composition) { | |||
| visit(composition.first); | |||
| visit(composition.second); | |||
| } | |||
| /*! | |||
| sem("if" "(" e ")" "then" "{" c1 "}" else "{" c2 "}") = | |||
| sem(c1, v) if eval(e, v) != 0 | |||
| sem(c2, v) else | |||
| */ | |||
| public void visitConditional(Conditional conditional) { | |||
| Evaluator evaluator = new Evaluator(conditional.condition, valuation); | |||
| if (evaluator.eval() != 0) { | |||
| @@ -39,6 +93,11 @@ public class Interpreter extends Visitor { | |||
| } | |||
| } | |||
| /*! | |||
| sem("while" "(" e ")" "{" c "}", v) = | |||
| sem(c ";" "while" "(" e ")" "{" c "}", v) if eval(e, v) != 0 | |||
| v else | |||
| */ | |||
| public void visitLoop(Loop loop) { | |||
| Evaluator evaluator = new Evaluator(loop.condition, valuation); | |||
| if (evaluator.eval() != 0) { | |||
| @@ -1,5 +1,16 @@ | |||
| /*!! Interpreter */ | |||
| /*! | |||
| InterpreterException | |||
| ==================== | |||
| */ | |||
| /*- Header */ | |||
| package interpreter; | |||
| /*! The `InterpreterException` is raised if anything goes wrong during the evaluation of an `Expression` or | |||
| running a `Program`. */ | |||
| public class InterpreterException extends RuntimeException { | |||
| public InterpreterException(String error) { | |||
| super(error); | |||
| @@ -1,9 +1,30 @@ | |||
| /*!! Interpreter */ | |||
| /*! | |||
| Visitor | |||
| ======= | |||
| The interpreter (and the evaluator) are performing structural recursion on the inductive data structure `Program` and | |||
| `Expression`, respectively. We want to define one interpreter function that behaves differently depending on the | |||
| argument. In functional languages this is done with | |||
| [Pattern Matching](https://de.wikipedia.org/wiki/Pattern_Matching#Programmierung) | |||
| and in Java this is typically implemented using the | |||
| [Visitor Pattern](https://en.wikipedia.org/wiki/Visitor_pattern). | |||
| */ | |||
| /*- Header */ | |||
| package interpreter; | |||
| /*! This `Visitor` is implemented using | |||
| [Reflection](https://en.wikipedia.org/wiki/Reflection_(computer_programming)). That is kind of cheating, but simplifies | |||
| the classical Visitor pattern a lot. Of course the performance is bad, but performance is not an issue in this little | |||
| demonstration and actually there are a lot of other performance issues as well. */ | |||
| public abstract class Visitor<T> { | |||
| @SuppressWarnings("unchecked") | |||
| public T visit(Object object) { | |||
| try { | |||
| /*! Get the name of the class of the given object, search for a method with this name in self and call it. */ | |||
| return (T) this.getClass().getMethod("visit" + object.getClass().getSimpleName(), object.getClass()).invoke(this, object); | |||
| } catch (Exception e) { | |||
| throw new RuntimeException(e); | |||
| @@ -66,7 +66,7 @@ public class Parser { | |||
| Such a function is necessary, because we do the tokenization on the fly during the parsing. In more complex | |||
| projects the [tokenization](https://en.wikipedia.org/wiki/Lexical_analysis#Tokenization) would be an extra | |||
| pre-processing step which handles the whitespace removal and creates a stream of tokens out of the input string.*/ | |||
| pre-processing step which handles the whitespace removal and creates a stream of tokens out of the input string. */ | |||
| private void whitespace() { | |||
| while(position < input.length() && Character.isWhitespace(input.charAt(position))) { | |||
| position += 1; | |||
| @@ -79,7 +79,7 @@ public class Parser { | |||
| sub-parsers. | |||
| The `consume` method consumes the given string by incrementing the `position`. It raises a `SyntaxException` | |||
| if the given string is not the next token in the `input` at the current `position`.*/ | |||
| if the given string is not the next token in the `input` at the current `position`. */ | |||
| private void consume(String token) { | |||
| whitespace(); | |||
| if (position + token.length() <= input.length() && input.substring(position, position + token.length()).equals(token)) { | |||
| @@ -177,7 +177,7 @@ public class Parser { | |||
| private Operator operator() { | |||
| whitespace(); | |||
| /*! Only check the character at the current position in the input if the current | |||
| position is a valid position in the input (and not after the end of the input).*/ | |||
| position is a valid position in the input (and not after the end of the input). */ | |||
| char next = (char) 0; | |||
| if (position < input.length()) { | |||
| next = input.charAt(position); | |||
| @@ -226,7 +226,7 @@ public class Parser { | |||
| } catch (SyntaxException se) { | |||
| /*! Reset the position. The `identifier` parser has failed, but it might have changed the global | |||
| `position` before raising the `SyntaxException` so we need to reset the position before trying | |||
| another parser.*/ | |||
| another parser. */ | |||
| position = start; | |||
| try { | |||
| result = integer(); | |||
| @@ -263,11 +263,11 @@ public class Parser { | |||
| } | |||
| } | |||
| /*! Parsing an integer follows more or less the same pattern as parsing an identifier (see above).*/ | |||
| /*! Parsing an integer follows more or less the same pattern as parsing an identifier (see above). */ | |||
| Expression integer() { | |||
| whitespace(); | |||
| int start = position; | |||
| /*! We check for a unary prefix minus first.*/ | |||
| /*! We check for a unary prefix minus first. */ | |||
| boolean minus = position < input.length() && input.charAt(position) == '-'; | |||
| if (minus) { | |||
| position += 1; | |||
| @@ -327,7 +327,7 @@ public class Parser { | |||
| } | |||
| /*! We use the first statement as initial result */ | |||
| Program program = firstStatement; | |||
| /*! and then replace the result with a `Composition` combining the old result and the new statement.*/ | |||
| /*! and then replace the result with a `Composition` combining the old result and the new statement. */ | |||
| for (Program statement: moreStatements) { | |||
| program = new Composition(program, statement); | |||
| } | |||
| @@ -335,10 +335,12 @@ public class Parser { | |||
| } | |||
| /*! Parsing a statement boils down to trying to parse | |||
| - an assignment and if that fails | |||
| - a conditional and if that fails | |||
| - a loop and if that fails | |||
| - fail completely.*/ | |||
| - fail completely. | |||
| */ | |||
| Program statement() { | |||
| int start = position; | |||
| Program statement; | |||
| @@ -357,7 +359,7 @@ public class Parser { | |||
| } | |||
| /*! Parsing a loop is very straight forward and just follows the rule | |||
| `"while" "(" Expr ")" "{" Prog "}"`.*/ | |||
| `"while" "(" Expr ")" "{" Prog "}"`. */ | |||
| Program loop() { | |||
| consume("while"); | |||
| consume("("); | |||
| @@ -370,7 +372,7 @@ public class Parser { | |||
| } | |||
| /*! Parsing a conditional simply follows the rule | |||
| `"if" "(" Expr ")" "then" "{" Prog "}" "else" "{" Prog "}"`.*/ | |||
| `"if" "(" Expr ")" "then" "{" Prog "}" "else" "{" Prog "}"`. */ | |||
| Program conditional() { | |||
| consume("if"); | |||
| consume("("); | |||
| @@ -387,7 +389,7 @@ public class Parser { | |||
| return new Conditional(condition, thenCase, elseCase); | |||
| } | |||
| /*! Parsing an assignment simply follows the rule `Id ":=" Expr`.*/ | |||
| /*! Parsing an assignment simply follows the rule `Id ":=" Expr`. */ | |||
| Program assignment() { | |||
| Identifier identifier = identifier(); | |||
| consume(":="); | |||
| @@ -402,12 +404,12 @@ public class Parser { | |||
| Everything that remains to be done is checking that we reached the end of the input after we | |||
| are done. As every parser only consumes as much from the input as needed, the `program` parser | |||
| might end in the middle of the input string. In the following public interface method we call | |||
| the `program` parser and check that we have reached the end of the input afterwards.*/ | |||
| the `program` parser and check that we have reached the end of the input afterwards. */ | |||
| public Program parse() { | |||
| position = 0; | |||
| Program program = program(); | |||
| /*! Whitespace is the only thing allowed after the program.*/ | |||
| /*! Whitespace is the only thing allowed after the program. */ | |||
| whitespace(); | |||
| if (position < input.length()) { | |||
| throw new SyntaxException("End of input", position); | |||
| @@ -1,20 +1,22 @@ | |||
| /*!! Parser */ | |||
| /*! | |||
| SyntaxException | |||
| =============== | |||
| */ | |||
| /*- Header */ | |||
| package parser; | |||
| /*! A `SyntaxException` is raised by the function in the `Parser` if an expected was not found at the current | |||
| position. */ | |||
| public class SyntaxException extends RuntimeException { | |||
| private String expected; | |||
| private int position; | |||
| public final String expected; | |||
| public final 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; | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| /*!! Program*/ | |||
| /*!! Program */ | |||
| /*! | |||
| Assignment | |||
| @@ -38,7 +38,7 @@ public class Assignment extends Program { | |||
| return identifier + " := " + expression; | |||
| } | |||
| /*!- generated equals method*/ | |||
| /*!- generated equals method */ | |||
| @Override | |||
| public boolean equals(Object o) { | |||
| if (this == o) return true; | |||
| @@ -1,11 +1,11 @@ | |||
| /*!! Program*/ | |||
| /*!! Program */ | |||
| /*! | |||
| Composition | |||
| =========== | |||
| */ | |||
| /*!- Header*/ | |||
| /*!- Header */ | |||
| package program; | |||
| /*! A `Composition` combines two programs (`first` and `second`) with the intended semantics of sequential | |||
| @@ -1,11 +1,11 @@ | |||
| /*!! Program*/ | |||
| /*!! Program */ | |||
| /*! | |||
| Conditional | |||
| =========== | |||
| */ | |||
| /*!- Header*/ | |||
| /*!- Header */ | |||
| package program; | |||
| import expression.Expression; | |||
| @@ -37,7 +37,7 @@ public class Conditional extends Program { | |||
| } | |||
| /*!- String serialization*/ | |||
| /*!- String serialization */ | |||
| @Override | |||
| public String toString() { | |||
| return "if (" + condition + ") then { " + thenCase + " } else { " + elseCase + " }"; | |||
| @@ -1,11 +1,11 @@ | |||
| /*!! Program*/ | |||
| /*!! Program */ | |||
| /*! | |||
| Loop | |||
| ==== | |||
| */ | |||
| /*!- Header*/ | |||
| /*!- Header */ | |||
| package program; | |||
| import expression.Expression; | |||
| @@ -1,21 +1,20 @@ | |||
| /*!! Program*/ | |||
| /*!! Program */ | |||
| /*! | |||
| Program | |||
| ======= | |||
| */ | |||
| /*!- Header*/ | |||
| package program; | |||
| /*! `Program` is the abstract common class for programs that can be executed using the `Interpreter`. */ | |||
| abstract public class Program { } | |||
| /*! Program can be written as the following | |||
| `Program` can be written as the following | |||
| [Algebraic Data Type (ADT)](https://en.wikipedia.org/wiki/Algebraic_data_type) | |||
| Program = Assignment(identifier: Identifier, expression: Expression) | |||
| | Composition(first: Program, second: Program) | |||
| | Loop(condition: Expression, program: Program) | |||
| | Conditional(condition: Expression, thenCase: Program, elseCase: Program) | |||
| */ | |||
| */ | |||
| /*!- Header */ | |||
| package program; | |||
| /*! `Program` is the abstract common class for programs that can be executed using the `Interpreter`. */ | |||
| abstract public class Program { } | |||