|
|
Table of Contents |
|
Recipe 12.5. Generating Pretty PrintersProblemYou need tools to help debug your application. In particular, you want the ability to render binary messages in human-readable form. SolutionWhen developing messaging applications, developers often hand-code pretty printers because they make debugging these applications considerably easier. However, this kind of code can be generated if you have a message repository. This solution shows how to reuse the message switch generator from Recipe 12.2: <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xslt [
<!--Used to control code intenting -->
<!ENTITY INDENT " ">
<!ENTITY INDENT2 "&INDENT;&INDENT;">
<!ENTITY LS "<<">
]>
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- This pretty-printer generator needs a message switch so we -->
<!-- reuse the one we already wrote. -->
<xsl:import href="messageSwitch.xslt"/>
<!--The directory to generate code -->
<xsl:param name="generationDir" select=" 'src/' "/>
<!--The C++ header file name -->
<xsl:param name="prettyPrintHeader" select=" 'prettyPrint.h' "/>
<!--The C++ source file name -->
<xsl:param name="prettyPrintSource" select=" 'prettyPrint.C' "/>
<!--Key to locate data types by name -->
<xsl:key name="dataTypes" match="Structure" use="Name" />
<xsl:key name="dataTypes" match="Primitive" use="Name" />
<xsl:key name="dataTypes" match="Array" use="Name" />
<xsl:key name="dataTypes" match="Enumeration" use="Name" />
<xsl:template match="MessageRepository">
<xsl:document href="{concat($generationDir,$prettyPrintHeader)}">
<xsl:text>void prettyPrintMessage</xsl:text>
<xsl:text>(ostream& stream, const Message& msg);
</xsl:text>
<xsl:apply-templates select="DataTypes/Structure" mode="declare"/>
</xsl:document>
<xsl:document href="{concat($generationDir,$prettyPrintSource)}">
<xsl:apply-imports/>
<xsl:apply-templates select="DataTypes/Structure" mode="printers"/>
</xsl:document>
</xsl:template>
<!--Override the message processing function name from -->
<!-- messageSwitch.xslt to customize the function -->
<!-- signature to take a stream -->
<xsl:template name="process-function">
<xsl:text>void prettyPrintMessage</xsl:text>
<xsl:text>(ostream& stream, const Message& msg)</xsl:text>
</xsl:template>
<!--Override case action from messageSwitch.xslt to generate -->
<!-- call to prettyPrinter for message data -->
<xsl:template name="case-action">
<xsl:text> prettyPrint(stream, *static_cast<const </xsl:text>
<xsl:value-of select="DataTypeName"/>
<xsl:text>*>(msg.getData( ))) ;
break;</xsl:text>
</xsl:template>
<!--Generate declarations for each message data type -->
<xsl:template match="Structure" mode="declare">
<!--Forward declare the message data class -->
<xsl:text>class </xsl:text>
<xsl:value-of select="Name"/>
<xsl:text> ;
</xsl:text>
<!--Forward declare the message prettyPrint function -->
<xsl:text>ostream prettyPrint(ostream & stream, const </xsl:text>
<xsl:value-of select="Name"/>
<xsl:text>& data);
</xsl:text>
</xsl:template>
<!--Generate the body of a pretty-printer -->
<xsl:template match="Structure" mode="printers">
<xsl:text>ostream prettyPrint(ostream & stream, const </xsl:text>
<xsl:value-of select="Name"/>
<xsl:text>& data)
</xsl:text>
<xsl:text>{
</xsl:text>
<xsl:text>&INDENT;stream 
</xsl:text>
<xsl:text>&INDENT2;&LS; "</xsl:text>
<xsl:value-of select="Name"/>
<xsl:text>" &LS; endl &LS; "{" &LS; endl 
</xsl:text>
<xsl:for-each select="Members/Member">
<xsl:text>&INDENT2;&LS; "</xsl:text>
<xsl:value-of select="Name"/>: " &LS; <xsl:text/>
<xsl:apply-templates
select="key('dataTypes',DataTypeName)" mode="print">
<xsl:with-param name="name" select="Name"/>
</xsl:apply-templates>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:text>&INDENT2;&LS; "}" &LS; endl ; 
</xsl:text>
<xsl:text>&INDENT;return stream ;
</xsl:text>
<xsl:text>}

</xsl:text>
</xsl:template>
<!--Nested structures invoke the pretty-printer for that structure -->
<xsl:template match="Structure" mode="print">
<xsl:param name="name"/>
<xsl:text>prettyPrint(stream, data.get_</xsl:text>
<xsl:value-of select="$name"/><xsl:text>( ))</xsl:text>
</xsl:template>
<!--We assume there is a get function for each -->
<!-- primitive component of the message -->
<xsl:template match="*" mode="print">
<xsl:param name="name"/>
<xsl:text>data.get_</xsl:text>
<xsl:value-of select="$name"/>( ) << endl<xsl:text/>
</xsl:template>
</xsl:stylesheet>The following source file is generated. We omit the header since it contains only declarations: #include <messages/ADD_STOCK_ORDER.h>
#include <messages/ADD_STOCK_ORDER_ACK.h>
#include <messages/ADD_STOCK_ORDER_NACK.h>
#include <messages/CANCEL_STOCK_ORDER.h>
#include <messages/CANCEL_STOCK_ORDER_ACK.h>
#include <messages/CANCEL_STOCK_ORDER_NACK.h>
#include <messages/TEST.h>
#include <transport/Message.h>
#include <transport/MESSAGE_IDS.h>
void prettyPrintMessage(ostream& stream, const Message& msg)
{
switch (msg.getId( ))
{
case ADD_STOCK_ORDER_ID:
prettyPrint(stream, *static_cast<const
AddStockOrderData*>(msg.getData( ))) ;
break;
case ADD_STOCK_ORDER_ACK_ID:
prettyPrint(stream, *static_cast<const
AddStockOrderAckData*>(msg.getData( ))) ;
break;
case ADD_STOCK_ORDER_NACK_ID:
prettyPrint(stream, *static_cast<const
AddStockOrderNackData*>(msg.getData( ))) ;
break;
case CANCEL_STOCK_ORDER_ID:
prettyPrint(stream, *static_cast<const
CancelStockOrderData*>(msg.getData( ))) ;
break;
case CANCEL_STOCK_ORDER_ACK_ID:
prettyPrint(stream, *static_cast<const
CancelStockOrderAckData*>(msg.getData( ))) ;
break;
case CANCEL_STOCK_ORDER_NACK_ID:
prettyPrint(stream, *static_cast<const
CancelStockOrderNackData*>(msg.getData( ))) ;
break;
case TEST_ID:
prettyPrint(stream, *static_cast<const TestData*>(msg.getData( ))) ;
break;
return false ;
}
}
ostream prettyPrint(ostream & stream, const TestData& data)
{
stream
<< "TestData" << endl << "{" << endl
<< "order: " << prettyPrint(stream, data.get_order( ))
<< "cancel: " << prettyPrint(stream, data.get_cancel( ))
<< "}" << endl ;
return stream ;
}
ostream prettyPrint(ostream & stream, const AddStockOrderData& data)
{
stream
<< "AddStockOrderData" << endl << "{" << endl
<< "symbol: " << data.get_symbol( ) << endl
<< "quantity: " << data.get_quantity( ) << endl
<< "side: " << data.get_side( ) << endl
<< "type: " << data.get_type( ) << endl
<< "price: " << data.get_price( ) << endl
<< "}" << endl ;
return stream ;
}
ostream prettyPrint(ostream & stream, const AddStockOrderAckData& data)
{
stream
<< "AddStockOrderAckData" << endl << "{" << endl
<< "orderId: " << data.get_orderId( ) << endl
<< "}" << endl ;
return stream ;
}
ostream prettyPrint(ostream & stream, const AddStockOrderNackData& data)
{
stream
<< "AddStockOrderNackData" << endl << "{" << endl
<< "reason: " << data.get_reason( ) << endl
<< "}" << endl ;
return stream ;
}
ostream prettyPrint(ostream & stream, const CancelStockOrderData& data)
{
stream
<< "CancelStockOrderData" << endl << "{" << endl
<< "orderId: " << data.get_orderId( ) << endl
<< "quantity: " << data.get_quantity( ) << endl
<< "}" << endl ;
return stream ;
}
ostream prettyPrint(ostream & stream, const CancelStockOrderAckData& data)
{
stream
<< "CancelStockOrderAckData" << endl << "{" << endl
<< "orderId: " << data.get_orderId( ) << endl
<< "quantityRemaining: " << data.get_quantityRemaining( ) << endl
<< "}" << endl ;
return stream ;
}
ostream prettyPrint(ostream & stream, const CancelStockOrderNackData& data)
{
stream
<< "CancelStockOrderNackData" << endl << "{" << endl
<< "orderId: " << data.get_orderId( ) << endl
<< "reason: " << data.get_reason( ) << endl
<< "}" << endl ;
return stream ;
}DiscussionThis code-generation recipe attacks the pretty-printing problem head on by literally generating the pretty-print code for each message. Following this example is simple, and the results are effective. However, you could approach the problem more generally, and in the process create a more useful code generator. Specifically, you can break the pretty-printing process into two stages. One stage is the process of parsing a monolithic message into its constituent parts. The other is the process of taking those parts and formatting them into human-readable text. Looking at the problem in this way changes the solution from the generation of a single-purpose set of functions (a pretty printer) to the generation of a more generic message parser. Such parsers are usually event driven. Readers familiar with the Simple API for XML (SAX) will recognize this style of processing. The stylesheet used to generate a message parser is a variation of the pretty-print generator. Instead of sending message components to a stream, it sends parse events to a handler: <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xslt [
<!--Used to control code intenting -->
<!ENTITY INDENT " ">
<!ENTITY INDENT2 "&INDENT;&INDENT;">
<!ENTITY LS "<<">
]>
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- This message parse generator needs a message switch so we -->
<!-- reuse the one we already wrote. -->
<xsl:import href="messageSwitch.xslt"/>
<!--The directory to generate code -->
<xsl:param name="generationDir" select=" 'src/' "/>
<!--The C++ header file name -->
<xsl:param name="msgParseHeader" select=" 'msgParse.h' "/>
<!--The C++ source file name -->
<xsl:param name="msgParseSource" select=" 'msgParse.C' "/>
<!--Key to locate data types by name -->
<xsl:key name="dataTypes" match="Structure" use="Name" />
<xsl:key name="dataTypes" match="Primitive" use="Name" />
<xsl:key name="dataTypes" match="Array" use="Name" />
<xsl:key name="dataTypes" match="Enumeration" use="Name" />
<xsl:template match="MessageRepository">
<xsl:document href="{concat($generationDir,$msgParseHeader)}">
<xsl:text>void parseMessage</xsl:text>
<xsl:text>(MessageHandler& handler, const Message& msg);

</xsl:text>
<xsl:apply-templates select="DataTypes/Structure" mode="declare"/>
</xsl:document>
<xsl:document href="{concat($generationDir,$msgParseSource)}">
<xsl:apply-imports/>
<xsl:apply-templates select="DataTypes/Structure" mode="parsers"/>
</xsl:document>
</xsl:template>
<!--Override the message-processing function name from -->
<!-- messageSwitch.xslt to customize the function signature -->
<!-- to take a handler -->
<xsl:template name="process-function">
<xsl:text>void parseMessage</xsl:text>
<xsl:text>(MessageHandler& handler, const Message& msg)</xsl:text>
</xsl:template>
<!--Override case action from messageSwitch.xslt to generate -->
<!-- call to parse for message data -->
<xsl:template name="case-action">
<xsl:text> parse(handler, *static_cast<const </xsl:text>
<xsl:value-of select="DataTypeName"/>
<xsl:text>*>(msg.getData( ))) ;
break;</xsl:text>
</xsl:template>
<!--Generate declarations for each message data type -->
<xsl:template match="Structure" mode="declare">
<!--Forward declare the message data class -->
<xsl:text>class </xsl:text>
<xsl:value-of select="Name"/>
<xsl:text> ;
</xsl:text>
<!--Forward declare the message parse function -->
<xsl:text>void parse(MessageHandler & handler, const </xsl:text>
<xsl:value-of select="Name"/>
<xsl:text>& data);
</xsl:text>
</xsl:template>
<!--Generate the body of a parser -->
<xsl:template match="Structure" mode="parsers">
<xsl:text>void parse(MessageHandler & handler, const </xsl:text>
<xsl:value-of select="Name"/>
<xsl:text>& data)
</xsl:text>
<xsl:text>{
</xsl:text>
<xsl:text>&INDENT;handler.beginStruct("</xsl:text>
<xsl:value-of select="Name"/>
<xsl:text>") ;
</xsl:text>
<xsl:for-each select="Members/Member">
<xsl:apply-templates
select="key('dataTypes',DataTypeName)" mode="parse">
<xsl:with-param name="name" select="Name"/>
</xsl:apply-templates>
</xsl:for-each>
<xsl:text>&INDENT;handler.endStruct("</xsl:text>
<xsl:value-of select="Name"/>
<xsl:text>") ;
</xsl:text>
<xsl:text>}

</xsl:text>
</xsl:template>
<!--Nested structures invoke the parser for that structure -->
<xsl:template match="Structure" mode="parse">
<xsl:param name="name"/>
<xsl:text>&INDENT;parse(handler, data.get_</xsl:text>
<xsl:value-of select="$name"/><xsl:text>( ));
</xsl:text>
</xsl:template>
<!--We assume there is a get function for each -->
<!-- primitive component of the message -->
<xsl:template match="*" mode="parse">
<xsl:param name="name"/>
<xsl:text>&INDENT;handler.field("</xsl:text>
<xsl:value-of select="$name"/>","<xsl:text/>
<xsl:value-of select="Name"/>",<xsl:text/>
<xsl:text>data.get_</xsl:text>
<xsl:value-of select="$name"/>( )<xsl:text/>
<xsl:text>);
</xsl:text>
</xsl:template>
</xsl:stylesheet>It produces parse functions that look like the following code: void parse(MessageHandler & handler, const AddStockOrderData& data)
{
handler.beginStruct("AddStockOrderData") ;
handler.field("symbol","StkSymbol",data.get_symbol( ));
handler.field("quantity","Shares",data.get_quantity( ));
handler.field("side","BuyOrSell",data.get_side( ));
handler.field("type","OrderType",data.get_type( ));
handler.field("price","Real",data.get_price( ));
handler.endStruct("AddStockOrderData")
;
} |
|
|
Table of Contents |
|