// C++ object-oriented version of AST's with checking and printing.
// 
// Function bodies are specified in class definitions just
//  to keep presentation compact; normally they would be
//  in a separate file (except perhaps constructors).
//

// Defns. to make this example code typecheck 
#include <stdio.h>
typedef enum {PLUS /* , ... */ } binop;
char *binop_name[] = {"PLUS" /*, ... */ }; 
typedef enum {UMINUS, NOT /* , ... */ } unop;
char *unop_name[] = {"UMINUS", "NOT" /* ,... */};
typedef enum {VOID, INTEGER, BOOLEAN} Type; 
typedef int lineno;

// Definitions of AST node classes

class ast  // an abstract class
{
public:
  virtual Type check() = 0;
  virtual void print() = 0;
protected:
  void error(char *message) 
    { printf ("Error at line %d : %s\n", line,message); }
  lineno line;
};

class stmt : public ast // an abstract class
{
};

class exp : public ast  // an abstract class
{
};

class lvar : public ast // an abstract class
{
};

// more subclasses of ast ...

// Statements

class assign_stmt : public stmt
{
public:
  assign_stmt(lineno p,lvar* l,exp* e) 
    { line = p; lhs = l; rhs = e;
    }
  Type check()
    { if (lhs->check() != rhs->check())
	this->error("Assignment types are incompatible.");
      return VOID; 
    }
  void print() 
    { printf("(AssignSt ");
      lhs->print();
      rhs->print();
      printf(")");
    }
private:
  lvar* lhs;
  exp* rhs;
};


class if_stmt : public stmt
{
public:
  if_stmt(lineno p,exp* e,stmt* s1,stmt* s2)
    { line = p; test = e; thenstmt = s1; elsestmt = s2;
    }
  Type check()
    { if (test->check() != BOOLEAN)
	this->error("IF expression has non-boolean type.");
      thenstmt->check();
      elsestmt->check();
      return VOID; 
    }
  void print()
    { printf("(IfSt ");
      test->print();
      thenstmt->print();
      elsestmt->print();
      printf(")");
    }
private:
  exp* test;
  stmt* thenstmt;
  stmt* elsestmt;
};

// more subclasses of stmt ...

// Expressions

class binop_exp : public exp
{
public:
  binop_exp(lineno p,binop b,exp* e1,exp* e2)
    { line = p; op = b; larg = e1; rarg = e2;
    }
  Type check()
    { Type t = larg->check();
      if (t != rarg->check())  // over-simplified
	this->error("Operand types are incompatible");
      return t;
    }
  void print()
    { printf("(BinOpExp %s ", binop_name[op]); // table technique still useful
      larg->print();
      rarg->print();
      printf(")");
    }
private:
  binop op;
  exp* larg;
  exp* rarg;
};


class unop_exp : public exp
{
public:
  unop_exp(lineno p,unop u, exp* e)
    { line = p; op = u; arg = e;
    }
  Type check() 
    { Type t = arg->check();
      switch(op) {   // sometimes still useful in C++ (not object-oriented!)
      case UMINUS: if (t != INTEGER) 
	            this->error("Operand is not of integer type.");
		  break;
      case NOT:   if (t != BOOLEAN)
                    this->error("Operand is not of boolean type.");
                  break;
      };
      return t;
    }
  void print()
    { printf("(UnOpExp %s ", unop_name[op]);
      arg->print();
      printf(")");
    }
private:
  unop op;
  exp* arg;
};

// more subclasses of exp ...


