/* 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