A datatype definition can be refined in a few ways:
For example, the following datatype Exp is inherited from some user
defined classes AST
and Object
. Furthermore, a virtual
member function called print is defined.
datatype Exp : public AST, virtual public Object = INT int with { ostream& print(ostream&); } | PLUS Exp, Exp with { ostream& print(ostream&); } | MINUS Exp, Exp with { ostream& print(ostream&); } | MULT Exp, Exp with { ostream& print(ostream&); } | DIV Exp, Exp with { ostream& print(ostream&); } | VAR { name : const char *, typ : Type } with { ostream& print(ostream&); } public: { virtual ostream& print (ostream&) = 0; void * operator new (size_t); } ;
The allocator and printing methods can be defined as follows:
void * classof Exp::operator new(size_t) { ... } ostream& classof INT::print(ostream& s) { return s << INT; } ostream& classof PLUS::print(ostream& s) { return s << '(' << this->#1 << '+' << this->#2 <<')'; } ostream& classof MINUS::print(ostream& s) { return s << '(' << this->#1 << '-' << this->#2 <<')'; } ostream& classof MULT::print(ostream& s) { return s << '(' << this->#1 << '*' << this->#2 <<')'; } ostream& classof DIV::print(ostream& s) { return s << '(' << this->#1 << '/' << this->#2 <<')'; } ostream& classof VAR::print(ostream& s) { return s << name; }
The special type form classof con is used to reference
the type of a constructor. Arguments of a constructor uses the following
naming convention: (i) if the constructor C takes only one argument,
then the name of the argument is C; (ii) if C takes two or more
unnamed arguments, then these are named #1
, #2
, etc;
(iii) finally, labeled arguments are given the same name as the label.
For readability reasons, it is often useful to separate a datatype definition from the member functions and inheritance definitions. The refine declaration can be used in this situation. For example, the above example can be restated more succinctly as follows:
// // Datatype definition section // datatype Exp = INT int | PLUS Exp, Exp | MINUS Exp, Exp | MULT Exp, Exp | DIV Exp, Exp | VAR { name : const char *, typ : Type } ; // // Refinement section // refine Exp : public AST, virtual public Object { virtual ostream& print (ostream&) = 0; void * operator new (size_t); } and INT, PLUS, MINUS, MULT, DIV, VAR { ostream& print(ostream&); } ;
The general syntax of the refine declaration is as follows:
Here, Id refers to either a constructor name or a datatype name.
Refine_Decl ::= refine Refine_Spec and ¼ and Refine_Spec ; Refine_Spec ::= Id, ¼ , Id [ : Inherit_List ] [ :: Datatype_Qualifiers ] [ { Datatype_Body } ]