Browse Source

Add documentation of Interpreter

pull/1/head
Malte Schmitz 9 years ago
parent
commit
3e70da3d70
14 changed files with 195 additions and 55 deletions
  1. +9
    -10
      src/main/java/expression/Expression.java
  2. +2
    -2
      src/main/java/expression/Identifier.java
  3. +1
    -1
      src/main/java/expression/Int.java
  4. +47
    -0
      src/main/java/interpreter/Evaluator.java
  5. +59
    -0
      src/main/java/interpreter/Interpreter.java
  6. +11
    -0
      src/main/java/interpreter/InterpreterException.java
  7. +21
    -0
      src/main/java/interpreter/Visitor.java
  8. +15
    -13
      src/main/java/parser/Parser.java
  9. +12
    -10
      src/main/java/parser/SyntaxException.java
  10. +2
    -2
      src/main/java/program/Assignment.java
  11. +2
    -2
      src/main/java/program/Composition.java
  12. +3
    -3
      src/main/java/program/Conditional.java
  13. +2
    -2
      src/main/java/program/Loop.java
  14. +9
    -10
      src/main/java/program/Program.java

+ 9
- 10
src/main/java/expression/Expression.java View File

@@ -1,21 +1,20 @@
/*!! Expression*/
/*!! 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) [Algebraic Data Type (ADT)](https://en.wikipedia.org/wiki/Algebraic_data_type)


Expression = Addition(leftHandSide: Expression, rightHandSide: Expression) Expression = Addition(leftHandSide: Expression, rightHandSide: Expression)
| Subtraction(leftHandSide: Expression, rightHandSide: Expression) | Subtraction(leftHandSide: Expression, rightHandSide: Expression)
| Identifier(name: String) | Identifier(name: String)
| Int(value: int) | 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 { }

+ 2
- 2
src/main/java/expression/Identifier.java View File

@@ -1,11 +1,11 @@
/*!! Expression*/
/*!! Expression */


/*! /*!
Identifier Identifier
========== ==========
*/ */


/*! Header*/
/*!- Header */
package expression; package expression;


/*! An `Identifier` consists only of the `name` of the identifier. This class is only needed as a wrapper which allows /*! An `Identifier` consists only of the `name` of the identifier. This class is only needed as a wrapper which allows


+ 1
- 1
src/main/java/expression/Int.java View File

@@ -1,4 +1,4 @@
/*!! Expression*/
/*!! Expression */


/*! /*!
Int_(eger)_ Int_(eger)_


+ 47
- 0
src/main/java/interpreter/Evaluator.java View File

@@ -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; package interpreter;


import expression.*; import expression.*;
@@ -5,6 +28,17 @@ import expression.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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> { public class Evaluator extends Visitor<Integer> {


final Expression expression; final Expression expression;
@@ -19,20 +53,33 @@ public class Evaluator extends Visitor<Integer> {
return visit(expression); return visit(expression);
} }


/*!
eval(e1 "+" e2, v) = eval(e1, v) + eval(e2, v)
*/
public Integer visitAddition(Addition addition) { public Integer visitAddition(Addition addition) {
return visit(addition.leftHandSide) + visit(addition.rightHandSide); return visit(addition.leftHandSide) + visit(addition.rightHandSide);
} }


/*!
eval(e1 "-" e2, v) = eval(e1, v) - eval(e2, v)
*/
public Integer visitSubtraction(Subtraction subtraction) { public Integer visitSubtraction(Subtraction subtraction) {
return visit(subtraction.leftHandSide) - visit(subtraction.rightHandSide); return visit(subtraction.leftHandSide) - visit(subtraction.rightHandSide);
} }


/*!
eval(x, v) = v(x)
*/
public Integer visitInt(Int integer) { public Integer visitInt(Int integer) {
return integer.value; return integer.value;
} }


public Integer visitIdentifier(Identifier identifier) { 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)) { if (valuation.containsKey(identifier.name)) {
/*!
eval(z, v) = z
*/
return valuation.get(identifier.name); return valuation.get(identifier.name);
} else { } else {
throw new InterpreterException("Identifier " + identifier.name + " not found."); throw new InterpreterException("Identifier " + identifier.name + " not found.");


+ 59
- 0
src/main/java/interpreter/Interpreter.java View File

@@ -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; package interpreter;


import program.*; import program.*;
@@ -5,6 +38,16 @@ import program.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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 { public class Interpreter extends Visitor {
final Program program; final Program program;
final Map<String, Integer> valuation = new HashMap<String, Integer>(); final Map<String, Integer> valuation = new HashMap<String, Integer>();
@@ -20,16 +63,27 @@ public class Interpreter extends Visitor {
visit(program); visit(program);
} }


/*!
sem(x ":=" e, v) = v.update(x, eval(e, v))
*/
public void visitAssignment(Assignment assignment) { public void visitAssignment(Assignment assignment) {
Evaluator evaluator = new Evaluator(assignment.expression, valuation); Evaluator evaluator = new Evaluator(assignment.expression, valuation);
valuation.put(assignment.identifier.name, evaluator.eval()); valuation.put(assignment.identifier.name, evaluator.eval());
} }


/*!
sem(c1 ";" c2) = sem(c2, sem(c1, v))
*/
public void visitComposition(Composition composition) { public void visitComposition(Composition composition) {
visit(composition.first); visit(composition.first);
visit(composition.second); 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) { public void visitConditional(Conditional conditional) {
Evaluator evaluator = new Evaluator(conditional.condition, valuation); Evaluator evaluator = new Evaluator(conditional.condition, valuation);
if (evaluator.eval() != 0) { 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) { public void visitLoop(Loop loop) {
Evaluator evaluator = new Evaluator(loop.condition, valuation); Evaluator evaluator = new Evaluator(loop.condition, valuation);
if (evaluator.eval() != 0) { if (evaluator.eval() != 0) {


+ 11
- 0
src/main/java/interpreter/InterpreterException.java View File

@@ -1,5 +1,16 @@
/*!! Interpreter */

/*!
InterpreterException
====================
*/

/*- Header */
package interpreter; 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 class InterpreterException extends RuntimeException {
public InterpreterException(String error) { public InterpreterException(String error) {
super(error); super(error);


+ 21
- 0
src/main/java/interpreter/Visitor.java View File

@@ -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; 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> { public abstract class Visitor<T> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public T visit(Object object) { public T visit(Object object) {
try { 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); return (T) this.getClass().getMethod("visit" + object.getClass().getSimpleName(), object.getClass()).invoke(this, object);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);


+ 15
- 13
src/main/java/parser/Parser.java View File

@@ -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 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 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() { private void whitespace() {
while(position < input.length() && Character.isWhitespace(input.charAt(position))) { while(position < input.length() && Character.isWhitespace(input.charAt(position))) {
position += 1; position += 1;
@@ -79,7 +79,7 @@ public class Parser {
sub-parsers. sub-parsers.


The `consume` method consumes the given string by incrementing the `position`. It raises a `SyntaxException` 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) { private void consume(String token) {
whitespace(); whitespace();
if (position + token.length() <= input.length() && input.substring(position, position + token.length()).equals(token)) { if (position + token.length() <= input.length() && input.substring(position, position + token.length()).equals(token)) {
@@ -177,7 +177,7 @@ public class Parser {
private Operator operator() { private Operator operator() {
whitespace(); whitespace();
/*! Only check the character at the current position in the input if the current /*! 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; char next = (char) 0;
if (position < input.length()) { if (position < input.length()) {
next = input.charAt(position); next = input.charAt(position);
@@ -226,7 +226,7 @@ public class Parser {
} catch (SyntaxException se) { } catch (SyntaxException se) {
/*! Reset the position. The `identifier` parser has failed, but it might have changed the global /*! 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 `position` before raising the `SyntaxException` so we need to reset the position before trying
another parser.*/
another parser. */
position = start; position = start;
try { try {
result = integer(); 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() { Expression integer() {
whitespace(); whitespace();
int start = position; 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) == '-'; boolean minus = position < input.length() && input.charAt(position) == '-';
if (minus) { if (minus) {
position += 1; position += 1;
@@ -327,7 +327,7 @@ public class Parser {
} }
/*! We use the first statement as initial result */ /*! We use the first statement as initial result */
Program program = firstStatement; 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) { for (Program statement: moreStatements) {
program = new Composition(program, statement); program = new Composition(program, statement);
} }
@@ -335,10 +335,12 @@ public class Parser {
} }


/*! Parsing a statement boils down to trying to parse /*! Parsing a statement boils down to trying to parse

- an assignment and if that fails - an assignment and if that fails
- a conditional and if that fails - a conditional and if that fails
- a loop and if that fails - a loop and if that fails
- fail completely.*/
- fail completely.
*/
Program statement() { Program statement() {
int start = position; int start = position;
Program statement; Program statement;
@@ -357,7 +359,7 @@ public class Parser {
} }


/*! Parsing a loop is very straight forward and just follows the rule /*! Parsing a loop is very straight forward and just follows the rule
`"while" "(" Expr ")" "{" Prog "}"`.*/
`"while" "(" Expr ")" "{" Prog "}"`. */
Program loop() { Program loop() {
consume("while"); consume("while");
consume("("); consume("(");
@@ -370,7 +372,7 @@ public class Parser {
} }


/*! Parsing a conditional simply follows the rule /*! Parsing a conditional simply follows the rule
`"if" "(" Expr ")" "then" "{" Prog "}" "else" "{" Prog "}"`.*/
`"if" "(" Expr ")" "then" "{" Prog "}" "else" "{" Prog "}"`. */
Program conditional() { Program conditional() {
consume("if"); consume("if");
consume("("); consume("(");
@@ -387,7 +389,7 @@ public class Parser {
return new Conditional(condition, thenCase, elseCase); 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() { Program assignment() {
Identifier identifier = identifier(); Identifier identifier = identifier();
consume(":="); 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 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 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 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() { public Program parse() {
position = 0; position = 0;
Program program = program(); Program program = program();
/*! Whitespace is the only thing allowed after the program.*/
/*! Whitespace is the only thing allowed after the program. */
whitespace(); whitespace();
if (position < input.length()) { if (position < input.length()) {
throw new SyntaxException("End of input", position); throw new SyntaxException("End of input", position);


+ 12
- 10
src/main/java/parser/SyntaxException.java View File

@@ -1,20 +1,22 @@
/*!! Parser */

/*!
SyntaxException
===============
*/

/*- Header */
package parser; 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 { 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) { public SyntaxException(String expected, int atPosition) {
super(expected + " expected at position " + atPosition); super(expected + " expected at position " + atPosition);
this.expected = expected; this.expected = expected;
this.position = atPosition; this.position = atPosition;
} }

public String getExpected() {
return expected;
}

public int getPosition() {
return position;
}
} }

+ 2
- 2
src/main/java/program/Assignment.java View File

@@ -1,4 +1,4 @@
/*!! Program*/
/*!! Program */


/*! /*!
Assignment Assignment
@@ -38,7 +38,7 @@ public class Assignment extends Program {
return identifier + " := " + expression; return identifier + " := " + expression;
} }


/*!- generated equals method*/
/*!- generated equals method */
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;


+ 2
- 2
src/main/java/program/Composition.java View File

@@ -1,11 +1,11 @@
/*!! Program*/
/*!! Program */


/*! /*!
Composition Composition
=========== ===========
*/ */


/*!- Header*/
/*!- Header */
package program; package program;


/*! A `Composition` combines two programs (`first` and `second`) with the intended semantics of sequential /*! A `Composition` combines two programs (`first` and `second`) with the intended semantics of sequential


+ 3
- 3
src/main/java/program/Conditional.java View File

@@ -1,11 +1,11 @@
/*!! Program*/
/*!! Program */


/*! /*!
Conditional Conditional
=========== ===========
*/ */


/*!- Header*/
/*!- Header */
package program; package program;


import expression.Expression; import expression.Expression;
@@ -37,7 +37,7 @@ public class Conditional extends Program {


} }


/*!- String serialization*/
/*!- String serialization */
@Override @Override
public String toString() { public String toString() {
return "if (" + condition + ") then { " + thenCase + " } else { " + elseCase + " }"; return "if (" + condition + ") then { " + thenCase + " } else { " + elseCase + " }";


+ 2
- 2
src/main/java/program/Loop.java View File

@@ -1,11 +1,11 @@
/*!! Program*/
/*!! Program */


/*! /*!
Loop Loop
==== ====
*/ */


/*!- Header*/
/*!- Header */
package program; package program;


import expression.Expression; import expression.Expression;


+ 9
- 10
src/main/java/program/Program.java View File

@@ -1,21 +1,20 @@
/*!! Program*/
/*!! 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) [Algebraic Data Type (ADT)](https://en.wikipedia.org/wiki/Algebraic_data_type)


Program = Assignment(identifier: Identifier, expression: Expression) Program = Assignment(identifier: Identifier, expression: Expression)
| Composition(first: Program, second: Program) | Composition(first: Program, second: Program)
| Loop(condition: Expression, program: Program) | Loop(condition: Expression, program: Program)
| Conditional(condition: Expression, thenCase: Program, elseCase: 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 { }

Loading…
Cancel
Save