diff --git a/src/main/java/expression/Expression.java b/src/main/java/expression/Expression.java index 63c3b31..35f3beb 100644 --- a/src/main/java/expression/Expression.java +++ b/src/main/java/expression/Expression.java @@ -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) -*/ \ No newline at end of file +*/ + +/*!- Header */ +package expression; + +/*! `Expression` is the common abstract class for Expressions that can be evaluated using the `Evaluator`. */ +abstract public class Expression { } diff --git a/src/main/java/expression/Identifier.java b/src/main/java/expression/Identifier.java index 7dced40..c175498 100644 --- a/src/main/java/expression/Identifier.java +++ b/src/main/java/expression/Identifier.java @@ -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 diff --git a/src/main/java/expression/Int.java b/src/main/java/expression/Int.java index b026fc6..942a014 100644 --- a/src/main/java/expression/Int.java +++ b/src/main/java/expression/Int.java @@ -1,4 +1,4 @@ -/*!! Expression*/ +/*!! Expression */ /*! Int_(eger)_ diff --git a/src/main/java/interpreter/Evaluator.java b/src/main/java/interpreter/Evaluator.java index 6aec331..ba1eb30 100644 --- a/src/main/java/interpreter/Evaluator.java +++ b/src/main/java/interpreter/Evaluator.java @@ -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 { final Expression expression; @@ -19,20 +53,33 @@ public class Evaluator extends Visitor { 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."); diff --git a/src/main/java/interpreter/Interpreter.java b/src/main/java/interpreter/Interpreter.java index eb965f6..121df8c 100644 --- a/src/main/java/interpreter/Interpreter.java +++ b/src/main/java/interpreter/Interpreter.java @@ -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 valuation = new HashMap(); @@ -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) { diff --git a/src/main/java/interpreter/InterpreterException.java b/src/main/java/interpreter/InterpreterException.java index 4bde0ae..4fba10f 100644 --- a/src/main/java/interpreter/InterpreterException.java +++ b/src/main/java/interpreter/InterpreterException.java @@ -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); diff --git a/src/main/java/interpreter/Visitor.java b/src/main/java/interpreter/Visitor.java index 5fa2145..a55f1da 100644 --- a/src/main/java/interpreter/Visitor.java +++ b/src/main/java/interpreter/Visitor.java @@ -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 { @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); diff --git a/src/main/java/parser/Parser.java b/src/main/java/parser/Parser.java index a03ee84..76c0f3f 100644 --- a/src/main/java/parser/Parser.java +++ b/src/main/java/parser/Parser.java @@ -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); diff --git a/src/main/java/parser/SyntaxException.java b/src/main/java/parser/SyntaxException.java index 494deaf..2d1ae20 100644 --- a/src/main/java/parser/SyntaxException.java +++ b/src/main/java/parser/SyntaxException.java @@ -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; - } } diff --git a/src/main/java/program/Assignment.java b/src/main/java/program/Assignment.java index 6c7e4ed..81d9915 100644 --- a/src/main/java/program/Assignment.java +++ b/src/main/java/program/Assignment.java @@ -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; diff --git a/src/main/java/program/Composition.java b/src/main/java/program/Composition.java index 370a111..fae9d75 100644 --- a/src/main/java/program/Composition.java +++ b/src/main/java/program/Composition.java @@ -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 diff --git a/src/main/java/program/Conditional.java b/src/main/java/program/Conditional.java index 100a354..edfd477 100644 --- a/src/main/java/program/Conditional.java +++ b/src/main/java/program/Conditional.java @@ -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 + " }"; diff --git a/src/main/java/program/Loop.java b/src/main/java/program/Loop.java index ead2a92..e3ae27d 100644 --- a/src/main/java/program/Loop.java +++ b/src/main/java/program/Loop.java @@ -1,11 +1,11 @@ -/*!! Program*/ +/*!! Program */ /*! Loop ==== */ -/*!- Header*/ +/*!- Header */ package program; import expression.Expression; diff --git a/src/main/java/program/Program.java b/src/main/java/program/Program.java index 97883fa..5c2ce8d 100644 --- a/src/main/java/program/Program.java +++ b/src/main/java/program/Program.java @@ -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) -*/ \ No newline at end of file +*/ + +/*!- Header */ +package program; + +/*! `Program` is the abstract common class for programs that can be executed using the `Interpreter`. */ +abstract public class Program { }