Spring 2018
Your goal for this assignment is to create an interpreter for the MITScript language. The semantics of the interpreter are described here in the MITScript Language Specification: link

# Implementation notes: main

The starter code for this assignment is your Assignment 1. It is recommended that you implement your interpreter using the Visitor interface you created in Assignment 1. The main function should be modified to be like the one shown below.

int main(int argc, char** argv) {

void* scanner;
yylex_init(&scanner);
if (argc < 2) {
cout << "Expecting file name as argumeent" << endl;
return 1;
}

FILE* infile = fopen(argv[1], "r");
if (infile == NULL) {
cout << "Cannot open file " << argv[1] << endl;
return 1;
}
yyset_in(infile, scanner);
Statement* output;
int rvalue = yyparse(scanner, output);
if (rvalue == 1) {
cout << "Parsing failed" << endl;
return 1;
}
try {
Interpreter interp;
output->accept(interp);
}
catch (InterpreterException& exception) {
cout << exception.message() << endl;
return 1;
}
return 0;
}

Note that the version of main above reads from a file passed as a commandline argument (an earlier version of this read from stdin, but if you do that, you will not be able to use the input function in your programs). We will be testing your interpreter by actually executing some programs and checking their output against that of our reference implementation, so you want to make sure your interpreter does not produce any unnecessary output.

# Implementation notes: Mapping inference rules to visitors

You will be using visitors to implement the inference rules shown above. Note, however, that visit functions in the visitor map from an AST node to void, whereas the evaluation relation like $(\Gamma, h, e) \rightarrow (h,v)$ takes a stack and a heap in addition to the expression, and produces a heap and a value.
The way we are going to deal with this is that we will use instance variables to store both additional inputs as well as return values. For example, when visiting expressions, you want your visitor to return the value of the expression, but since the visit methods are void, the solution is to have an instance variable in the interpreter visitor:

Value* rval;

Then, when visiting expressions, you can enforce the property that every visit method for an expression sets the value of rval to the value of evaluating that expression. So for example, when visiting a binary expression expr, you can have code like:

expr->left->accept(*this);
Value* leftVal = rval;
expr->right->accept(*this);
Value* rightVal = rval;
rval = computeBinaryOp(expr->op, leftVal, rightVal);
return;

In fact, I found it useful to define a method:

Value* eval(Expression* exp){
exp->accept(*this);
return rval;
}

Then, the code above can be rewritten as

Value* leftVal = eval(expr->left);
Value* rightVal = eval(expr->right);
rval = computeBinaryOp(expr->op, leftVal, rightVal);
return;

You can also use an instance variable to keep track of the stack. For example, you can do

std::stack < StackFrame*> frameStack;

In the case of the heap, there is no nead to define your own map, since there is already a map from addresses to values you can use: the heap of the language itself! Thus, for example, any point where you are allocating addresses in the semantics can be implemented by actually allocating memory in the implementation (e.g. by using new).

# Deliverables

In addition to your parser, you should submit 3 tests named interptest1.mit, interptest2.mit and interptest3.mit. Your tests should not use the "input" function, but should use print. As before, submission of your assigment should be accomplished by pushing your code to a branch with the name a2-submission. The last assignment pushed to this branch before the assignment due date is the one we will grade unless you notify us otherwise.

Questions or comments regarding 6.035? Send e-mail to the TAs at 6.035-staff@mit.edu.

Top // 6.035 home // Last updated