Cmm represents C++ source as a parse tree whose nodes are each derived
from a base Node
class. Cmm outputs a parse tree as C++ text by
calling the virtual Node::Output()
method. Each node type
overrides this virtual method to output appropriate text.
To output a parse tree in a different form, one could alter these virtual methods as required. However this would involve modifying the Cmm source code.
A different way would be to add a different virtual method to the base
Node
class, e.g. OutputCustom()
, and provide
implementations of this method in all the derived classes. This is not a very
easy thing to do though - there are lots of different node classes.
A better solution is to use multimethods themselves. We rebuild Cmm's parser as a Cmm programme, and instead of outputing a parse tree using virtual methods, we output it using a multimethod. The crucial difference is that one can easily override multimethods without changing the original source code.
The multimethod in question has the following prototype:
Output2( virtual const Parser::Node& node, virtual Parser::OutStream& out);
The default implementation of this multimethod looks like:
Output2_( const Parser::Node& node, Parser::OutStream& out) { node.Output( out); }
-i.e. it simple calls the node's virtual Output()
method.
One can now provide different implementations of the
Output2()
multimethod. For example, to customise the output of
function bodies, one would supply the following function:
void Output2_( const Parser::FnBody& fnbody, Parser::OutStream& out) { ... }
As a bonus, the second parameter in the multimethod is also virtual. This
allows one to specialise on the output as well as the node type. For example,
we can arange for specialised code to be called for all nodes, by
deriving a new class from Parser::OutStream
:
struct CustomOutStream : Parser::OutStream {...}; void Output2_( const Parser::Node& node, CustomOutStream& out) { ... }
Cmm's parser code has been modified to make the above system work.
The Node::Output()
method is never called directly -
instead outputing a node is done by calling the global function
Output2()
. For example, most of the implementations of the
virtual Node::Output()
methods themselves need to output other
nodes; they always do this by calling the Output2()
function,
never by calling a node's Output()
method directly.
In normal builds of Cmm, Output2()
is declared as a
conventional C++ function, and it's implementation simply calls the node's
virtual Output()
method.
However, if CMM_MULTIMETHODS
is defined,
Output2()
is declared as a multimethod function, and the
implementation is renamed Output2_()
, so that it acts as a
multimethod implementation. Hence outputing a node always uses multimethod
dispatch.
You can see this in Cmm's source code. ../cmm/parser.h declares Output2()
as
either a conventional function or a multimethod function, depending on
whether CMM_MULTIMETHODS
is defined.
Similarly, ../cmm/parser-nodemethods.cpp
defines the Ouput2()
or Output2_()
functions
depending on whether CMM_MULTIMETHODS
is defined. It also has
many different implementations of the Node::Output()
method,
which you will notice call Output2()
.