/* SI 413 Fall 2011
* ast.hpp
* This is a C++ header file for the AST class hierarchy.
*/
#ifndef AST_HPP
#define AST_HPP
#include <fstream>
#include <string>
#include <vector>
#include <stack>
#include <set>
#include <map>
#include <cstdlib>
using namespace std;
#include "value.hpp"
#include "st.hpp"
// This enum type gives codes to the different kinds of operators.
enum Oper {
ADD, SUB,
MUL, DIV,
LT, GT, LE, GE,
EQ, NE,
AND, OR, NOTOP
};
// Takes an Oper code and returns the corresponding operator string.
// This is used to generate the text in AST nodes, and for debugging.
const char* opstring(Oper op);
/* The AST class is the super-class for abstract syntax trees.
* Every type of AST (or AST node) has its own subclass.
*/
class AST {
private:
/* Adds this node and all children to the output stream in DOT format. */
int addToDot(ostream& out, int& nodes);
protected:
/* Writes the label for this AST node to the given output stream. */
virtual void writeLabel(ostream& out) = 0;
public:
/* These two methods define the tree structure of the AST. */
virtual int numChildren() = 0;
virtual AST* getChild(int i) = 0;
/* Writes this AST to a .dot file as named. */
void writeDot(const char* fname);
};
/* A Stmt is anything that can be evaluated at the top level such
* as I/O, assignments, and control structures.
* The last child of any statement is the next statement in sequence.
*/
class Stmt :public AST {
private:
Stmt* next;
protected:
// From AST
void writeLabel(ostream& out) {
out << getType() << ":stmt" << flush;
}
/* Returns a string indicating the type of statement this is. */
virtual const char* getType() = 0;
/* The last child (next statement in sequence) is handled in this
* superclass, Stmt. All the other children are called "parts" and
* are indicated and returned with the following two functions that
* subclasses must implement.
*/
virtual int numParts() = 0;
virtual AST* getPart(int i) = 0;
public:
/* This static method is for building sequences of statements by the
* parser. It takes two statements, and appends one after the other.
* The returned value is a pointer to the new statement representing
* the sequence.
*/
static Stmt* append(Stmt* a, Stmt* b);
/* Default constructor. The next statement pointer will be set to an
* object of the NullStmt class. */
Stmt();
/* Using this constructor manually specifies the next statement in
* sequence. */
Stmt (Stmt* nextStmt) :next(nextStmt) { }
/* This is the command that must be implemented everywhere to
* execute this Stmt - that is, do whatever it is that this statement
* says to do. */
virtual void exec()
{ cerr << "Exec not yet implemented in " << getType() << endl; }
/* This method just executes the current statement, and then
* executes all the following statements, in sequence.
* It can be overridden in sub-classes but usually won't need to be. */
virtual void execAll() {
exec();
next->execAll();
}
/* Gets and sets the next statement in the sequence. */
Stmt* getNext() { return next; }
void setNext(Stmt* n) { next = n; }
/* From the AST class - doesn't need to be overridden by subclasses. */
virtual int numChildren() { return numParts() + 1; }
/* From the AST class - doesn't need to be overridden by subclasses. */
virtual AST* getChild(int i)
{ if (i == numParts()) return next; else return getPart(i); }
/* Just indicates whether this is an instance of the NullStmt class. */
virtual bool isNull() { return false; }
};
/* Every AST node that is not a Stmt is an Exp.
* These represent actual computations that return something
* (in particular, a Value object).
*/
class Exp :public AST {
public:
/* This is the method that must be overridden by all subclasses.
* It should perform the computation specified by this node, and
* return the resulting value that gets computed. */
virtual Value eval() {
cerr << "Eval not yet implemented in ";
writeLabel(cerr);
cerr << endl;
return 0;
}
};
/* A leaf in the AST tree for literal types (id, num, bool). */
class Atom :public Exp {
protected:
void writeLabel(ostream& out);
/* Returns a string for the type of this Atom. */
virtual const char* getType() = 0;
/* Writes the value stored in this atom to the output stream. */
virtual void writeValue(ostream& out) = 0;
public:
// Two methods from AST:
int numChildren() { return 0; }
AST* getChild(int i) { return NULL; }
};
/* An identifier, i.e. variable or function name. */
class Id :public Atom {
private:
string val;
protected:
const char* getType() { return "id"; }
void writeValue(ostream& out) { out << val; }
public:
// Constructor from a C-style string
Id(const char* v) :val(v) { }
// Returns a reference to the stored string value.
string& getVal() { return val; }
};
/* A literal number in the program. */
class Num :public Atom {
private:
int val;
protected:
const char* getType() { return "num"; }
void writeValue(ostream& out) { out << val; }
public:
Num(int v) :val(v) { }
// To evaluate, just return the number!
Value eval() { return val; }
};
/* A literal boolean value like "true" or "false" */
class BoolExp :public Atom {
private:
bool val;
protected:
const char* getType() { return "bool"; }
void writeValue(ostream& out) { out << (val ? "true" : "false"); }
public:
BoolExp(bool v) :val(v) { }
};
/* This class represents a binary operation. That is, an operation with
* exactly two arguments, such as +, *, /, and, <=, etc. */
class BinOp :public Exp {
protected:
Exp* args[2];
Oper op;
void writeLabel(ostream& out)
{ out << "binop:exp\\n" << opstring(op) << flush; }
public:
BinOp(Exp* l, Oper o, Exp* r) :op(o)
{ args[0] = l; args[1] = r; }
/* Two methods from the AST superclass will be taken care of here. */
int numChildren() { return 2; }
AST* getChild(int i) { return args[i]; }
};
/* A binary opration for arithmetic, like + or *. */
class ArithOp :public BinOp {
public:
ArithOp(Exp* l, Oper o, Exp* r) :BinOp(l,o,r) { }
};
/* A binary operation for comparison, like < or !=. */
class CompOp :public BinOp {
public:
CompOp(Exp* l, Oper o, Exp* r) :BinOp(l,o,r) { }
};
/* A binary operation for boolean logic, like "and". */
class BoolOp :public BinOp {
public:
BoolOp(Exp* l, Oper o, Exp* r) :BinOp(l,o,r) { }
};
/* This class represents a unary operation. That is, an operation with
* just one argument, such as "not" and -. */
class UnarOp :public Exp {
protected:
Oper op;
Exp* arg;
void writeLabel(ostream& out)
{ out << "unarop:exp\\n" << opstring(op) << flush; }
public:
UnarOp(Oper o, Exp* l) :op(o), arg(l) { }
/* Two methods from AST superclass are taken care of here. */
int numChildren() { return 1; }
AST* getChild(int i) { return arg; }
};
/* This class represents a unary negation operation. */
class NegOp :public UnarOp {
public:
NegOp(Exp* l) :UnarOp(SUB,l) { }
};
/* This class represents a unary "not" operation. */
class NotOp :public UnarOp {
public:
NotOp(Exp* l) :UnarOp(NOTOP,l) { }
};
/* This is a statement for a block of code, i.e., code enclosed
* in curly braces { and }.
* Eventually, this is where scopes will begin and end.
*/
class Block :public Stmt {
private:
Stmt* body;
protected:
const char* getType() { return "block"; }
int numParts() { return 1; }
AST* getPart(int i) { return body; }
public:
Block(Stmt* b) :body(b) { }
};
/* This class is necessary to terminate a sequence of statements. */
class NullStmt :public Stmt {
protected:
const char* getType() { return "null"; }
int numParts() { return 0; }
AST* getPart(int i) { return NULL; }
public:
NullStmt() :Stmt(NULL) { }
/* These AST methods are overridden because NullStmt is very special. */
int numChildren() { return 0; }
AST* getChild(int i) { return NULL; }
bool isNull() { return true; }
// Nothing to execute!
void execAll() { }
};
/* This class is for plain "if" statements without an else clause. */
class IfStmt :public Stmt {
private:
Exp* clause;
Stmt* ifblock;
protected:
const char* getType() { return "if"; }
int numParts() { return 2; }
AST* getPart(int i)
{ if (i == 0) return clause; else return ifblock; }
public:
IfStmt(Exp* e, Stmt* s) :clause(e), ifblock(s) { }
};
/* This class is for if-else statements. */
class IfElse :public Stmt {
private:
Exp* clause;
Stmt* ifblock;
Stmt* elseblock;
protected:
const char* getType() { return "ifelse"; }
int numParts() { return 3; }
AST* getPart(int i);
public:
IfElse(Exp* e, Stmt* ib, Stmt* eb)
:clause(e), ifblock(ib), elseblock(eb)
{ }
};
/* Class for while(...) {...} statements. */
class WhileStmt :public Stmt {
private:
Exp* clause;
Stmt* block;
protected:
const char* getType() { return "while"; }
int numParts() { return 2; }
AST* getPart(int i)
{ if (i == 0) return clause; else return block; }
public:
WhileStmt(Exp* c, Stmt* b) :clause(c), block(b) { }
};
/* An assignment statement. This represents a RE-binding in the symbol table. */
class Asn :public Stmt {
protected:
Id* lhs;
Exp* rhs;
/* Returns the Id that we are assigning to. */
Id* getLhs() { return lhs; }
virtual const char* getType() { return "asn"; }
int numParts() { return 2; }
AST* getPart(int i) { return (i == 0 ? lhs : rhs); }
public:
Asn(Id* l, Exp* r) :lhs(l), rhs(r) { }
};
/* A "new" statement is a subclass of assignment because it
* has the same functionality as assignment, plus the additional
* functionality that a new binding is created. */
class NewStmt :public Asn {
protected:
const char* getType() { return "new"; }
public:
NewStmt(Id* l, Exp* r) :Asn(l,r) { }
};
/* A write statement. */
class Write :public Stmt {
private:
Exp* val;
protected:
const char* getType() { return "write"; }
virtual int numParts() { return 1; }
virtual AST* getPart(int i) { return val; }
public:
Write(Exp* v) :val(v) { }
// Executes this statement by writing the value of the expression to
// standard out.
void exec() { val->eval().writeTo(cout); cout << endl; }
};
/* A read statement. */
class Read :public Stmt {
private:
Id* var;
protected:
const char* getType() { return "read"; }
virtual int numParts() { return 1; }
virtual AST* getPart(int i) { return var; }
public:
Read(Id* v) :var(v) { }
};
/* A lambda expression consists of a parameter name and a body. */
class Lambda :public Exp {
private:
Id* var;
Block* body;
protected:
void writeLabel(ostream& out) { out << "lambda:exp" << flush; }
public:
Lambda(Id* v, Block* b) :var(v), body(b) { }
// These getter methods are necessary to support actually calling
// the lambda sometime after it gets created.
string& getVar() { return var->getVal(); }
Block* getBody() { return body; }
int numChildren() { return 2; }
AST* getChild(int i)
{ if (i == 0) return var; else return body; }
};
/* A function call consists of the function name, and the actual argument.
* Note that all functions are unary. */
class Funcall :public Exp {
private:
Exp* funexp;
Exp* arg;
protected:
void writeLabel(ostream& out) { out << "funcall:exp" << flush; }
public:
Funcall(Exp* f, Exp* a) :funexp(f), arg(a) { }
int numChildren() { return 2; }
AST* getChild(int i) { return (i==0 ? funexp : arg); }
};
#endif //AST_HPP