Suppose we use the following alternative implementation, a
Expression
class hierarchy to represent
an expression in our AST.
Note that Exp
is an abstract base class inherited
by the classes Number
, Operator
, Identifer
, etc.
class Expression { public: // This class uses a ``wide'' interface virtual int number() const = 0; // returns a number virtual const char * ident() const = 0; // returns an identifier // returns the ith subchild virtual const Expression * child(int) const = 0; // mutable version of above virtual Expression *& child(int) = 0; }; typedef Expression * Exp; class NullExp : public Expression { ... }; class Number : public Expression { ... }; class Operator : public Expression { ... }; class Identifier : public Expression { ... };
In order to provide a suitable interface to Prop's view mechanism, we introduce an additional member function to return a variant tag:
class Expression { public: enum Variant { None, Int, Ident, Add, Sub, Mul, Div }; virtual Variant get_variant() const = 0; }
In addition, we assume the following constructor functions have been implemented:
extern Exp INT(int); extern Exp IDENT(const char *); extern Exp ADD(Exp, Exp); extern Exp SUB(Exp, Exp); extern Exp MUL(Exp, Exp); extern Exp DIV(Exp, Exp);
Now, with these definitions in place, we can define a datatype view of the
above Expression
hierarchy using the following definition:
datatype Exp :: view = match (this->get_variant()) view Expression::None => NONE | view Expression::Int => INT (int = this->number()) | view Expression::Ident => ID (const char * = this->ident()) | view Expression::Add => ADD (Exp = this->child(0), Exp = this->child(1)) | view Expression::Sub => SUB (Exp = this->child(0), Exp = this->child(1)) | view Expression::Mul => MUL (Exp = this->child(0), Exp = this->child(1)) | view Expression::Div => DIV (Exp = this->child(0), Exp = this->child(1)) ;
An evaluation function can be written using Prop's pattern matching construct:
int eval(Exp e, const Env& env) { match (e) { NONE: { return 0; } | INT i: { return i; } | ID id: { return env(id); } | ADD (x,y): { return eval(x,env) + eval(y,env); } | SUB (x,y): { return eval(x,env) - eval(y,env); } | MUL (x,y): { return eval(x,env) * eval(y,env); } | DIV (x,INT 0): { cerr << "Division by zero"; return 0; } | DIV (x,y): { return eval(x,env) / eval(y,env); } } }Note that this code works equally well with the tagged union representation defined in section 7.1.
We can also use rewriting to transform an expression tree as follows
void simplify(Exp& e) { rewrite (e) type (Exp) { ADD(INT i, INT j): INT(i+j) | SUB(INT i, INT j): INT(i-j) | MUL(INT i, INT j): INT(i*j) | DIV(INT i, INT j): INT(i/j) } }