Generating Pretty Printers

Problem

You need tools to help debug your application. In particular, you want the ability to render binary messages in human-readable form.

Solution

When 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 10.2:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xslt [
  <!--Used to control code intenting -->
  <!ENTITY INDENT "    ">
  <!ENTITY INDENT2 "&INDENT;&INDENT;">
  <!ENTITY LS "&lt;&lt;">
]>
<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&amp; stream, const Message&amp; msg);&#xa;</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 -->
<!-- signiture to take a stream -->
<xsl:template name="process-function">
<xsl:text>void prettyPrintMessage</xsl:text>
<xsl:text>(ostream&amp; stream, const Message&amp; 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&lt;const </xsl:text>
 <xsl:value-of select="DataTypeName"/>
 <xsl:text>*&gt;(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> ;&#xa;</xsl:text>
<!--Forward declare the message prettyPrint function -->
<xsl:text>ostream prettyPrint(ostream &amp; stream, const </xsl:text>
<xsl:value-of select="Name"/>
<xsl:text>&amp; data);&#xa;</xsl:text>
</xsl:template>
   
<!--Generate the body of  a pretty-printer -->
<xsl:template match="Structure" mode="printers">
<xsl:text>ostream prettyPrint(ostream &amp; stream, const </xsl:text>
<xsl:value-of select="Name"/>
<xsl:text>&amp; data)&#xa;</xsl:text>
<xsl:text>{&#xa;</xsl:text>
<xsl:text>&INDENT;stream &#xa;</xsl:text>  
<xsl:text>&INDENT2;&LS; "</xsl:text>
<xsl:value-of select="Name"/>
<xsl:text>" &LS;  endl  &LS; "{"  &LS; endl &#xa;</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>&#xa;</xsl:text> 
  </xsl:for-each>
  <xsl:text>&INDENT2;&LS; "}"  &LS; endl ; &#xa;</xsl:text>
  <xsl:text>&INDENT;return stream ;&#xa;</xsl:text>
  <xsl:text>}&#xa;&#xa;</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"/>(  ) &lt;&lt; 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 ;
}

Discussion

This 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 "&lt;&lt;">
]>
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   
<!-- This mesage 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&amp; handler, const Message&amp; msg);&#xa;
       </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 signiture --> 
<!-- to take a handler -->
<xsl:template name="process-function">
<xsl:text>void parseMessage</xsl:text>
<xsl:text>(MessageHandler&amp; handler, const Message&amp; 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&lt;const </xsl:text>
 <xsl:value-of select="DataTypeName"/>
 <xsl:text>*&gt;(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> ;&#xa;</xsl:text>
<!--Forward declare the message parse function -->
<xsl:text>void parse(MessageHandler &amp; handler, const </xsl:text>
<xsl:value-of select="Name"/>
<xsl:text>&amp; data);&#xa;</xsl:text>
</xsl:template>
   
<!--Generate the body of  a parser -->
<xsl:template match="Structure" mode="parsers">
<xsl:text>void parse(MessageHandler &amp; handler, const </xsl:text>
<xsl:value-of select="Name"/>
<xsl:text>&amp; data)&#xa;</xsl:text>
<xsl:text>{&#xa;</xsl:text>
<xsl:text>&INDENT;handler.beginStruct("</xsl:text>  
<xsl:value-of select="Name"/>
<xsl:text>") ;&#xa;</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>") ;&#xa;</xsl:text>  
  <xsl:text>}&#xa;&#xa;</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>(  ));&#xa;</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>);&#xa;</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") ;







}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.54.63