Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Auto-generate stream operator for struct/class

Is there a tool to auto-generate the ostream << operator for a struct or class?

Input (taken from One Debug-Print function to rule them all):

typedef struct ReqCntrlT    /* Request control record */
{
  int             connectionID;
  int             dbApplID;
  char            appDescr[MAX_APPDSCR];
  int             reqID;
  int         resubmitFlag;
  unsigned int    resubmitNo;
  char            VCIver[MAX_VCIVER];
  int             loginID;
}   ReqCntrlT;

Output:

std::ostream& operator <<(std::ostream& os, const ReqCntrlT& r) 
{
   os << "reqControl { "
      << "\n\tconnectionID: " << r.connectionID 
      << "\n\tdbApplID: " << r.dbApplID 
      << "\n\tappDescr: " << r.appDescr
      << "\n\treqID: " << r.reqID
      << "\n\tresubmitFlag: " << r.resubmitFlag
      << "\n\tresubmitNo: " << r.resubmitNo
      << "\n\tVCIver: " << r.VCIver
      << "\n\tloginID: " << r.loginID
      << "\n}";
   return os; 
}

Any tool would be fine, Python / Ruby scripts would be preferred.

like image 664
Christopher Oezbek Avatar asked May 08 '12 12:05

Christopher Oezbek


2 Answers

What is needed for this is a tool that can parse C++ accurately, enumerate the various classes/structs, determine the and generate your "serializations" on a per class/struct basis, and then park the generated code in the "right place" (presumbably the same scope in which the struct was found). It needs a full preprocessor to handle expansion of directives in real code.

Our DMS Software Reengineering Toolkit with its C++11 front end could do this. DMS enables the construction of custom tools by providing generic parsing/AST building, symbol table construction, flow and custom analysis, transformation and source code regeneration capability. The C++ front enables DMS to parse C++ and build accurate symbol tables, as well as to pretty print modified or new ASTs back to compilable source form. DMS and its C++ front end have been used to carry out massive transformations on C++ code.

You have to explain to DMS what you want to do; seems straightforward to enumerate symbol tables entries, ask if struct/class type declarations, determine scope of the declaration (recorded in the symbol table entry), construct an AST by composing surface syntax patterns, and then apply a transformation to insert the constructed AST.

The core surface syntax patterns needed are those for slots and for the function body:

 pattern ostream_on_slot(i:IDENTIFIER):expression =
   " << "\n\t" << \tostring\(\i\) << r.\i "; -- tostring is a function that generates "<name>"

 pattern ostream_on_struct(i:IDENTIFIER,ostream_on_slots:expression): declaration =
   " std::ostream& operator <<(std::ostream& os, const \i& r) 
     { os << \tostring\(\i\) << " { " << \ostream_on_slots << "\n}";
       return os; 
     }

One has to compose the individual trees for ostream_on_slot:

 pattern compound_ostream(e1:expression, e2:expression): expression
     = " \e1 << \e2 ";

With these patterns it is straightforward to enumerate the slots of struct, construct the ostream for the body, and insert that into the overall function for a struct.

like image 151
Ira Baxter Avatar answered Sep 22 '22 21:09

Ira Baxter


There are two main ways to do this:

  • using an external parsing tool (such as a Python script hooked up on Clang bindings)
  • using metaprogramming technics

.. and of course they can be mixed.

I don't have enough knowledge about Clang Python bindings to answer using them, so I will concentrate on metapogramming.


Basically, what you are asking for requires introspection. C++ does not support full introspection, however using metaprogramming tricks (and template matching) it can support a limited subset of introspection technics at compilation time, which is sufficient for our purpose.

In order to easily mix metaprogramming and runtime operation, it's easier to bring a library into play: Boost.Fusion.

If you tweak your structure such that its attributes are descrited in terms of a Boost.Fusion sequence, then you can apply plenty of algorithm on the sequence automatically. Here, an associate sequence is best.

Because we are talking metaprogramming, the map associates a type to a typed value.

You can then iterate over that sequence using for_each.


I'll gloss over the details, simply because it's been a while and I don't remember the syntax involved, but basically the idea is to get to:

// Can be created using Boost.Preprocessor, but makes array types a tad difficult
DECL_ATTRIBUTES((connectionId, int)
                (dbApplId, int)
                (appDescr, AppDescrType)
                ...
                );

which is syntactic sugar to be declaring the Fusion Map and its associated tags:

struct connectionIdTag {};
struct dbApplIdTag {};

typedef boost::fusion::map<
    std::pair<connectionIdTag, int>,
    std::pair<dbApplIdTag, int>,
    ...
    > AttributesType;
AttributesType _attributes;

Then, any operation that need be applied on the attributes can be built simply with:

// 1. A predicate:
struct Predicate {
    template <typename T, typename U>
    void operator()(std::pair<T, U> const&) const { ... }
};

// 2. The for_each function
for_each(_attributes, Predicate());
like image 30
Matthieu M. Avatar answered Sep 23 '22 21:09

Matthieu M.