An XSLT stylesheet, usually installed as
/usr/share/libcxx/threadmsgdispatcher.xsl
or
/usr/local/share/libcxx/threadmsgdispatcher.xsl
, produces
generic code for a message dispatching-based object from a convenient
stylesheet that produces most of the spaghetti code for a typical
dispatcher object.
An XML file lists the names of public methods
and the parameters to the private class methods, and the stylesheet
generates the supporting code.
<class name="mythreadObj"> <method name="logerror"> <comment> //! logerror() function</comment> <param> <decl>const char *file</decl> <default>""</default> </param> <param> <decl>int line</decl> <default>-1</default> </param> <param> <comment>//! Descriptive error message</comment> <decl>const std::string &error_message</decl> <default>"unknown error"</default> </param> <attributes> noexcept</attributes> </method> <method name="testas"> <virtual /> <param> <decl>testas_class param</decl> <as>int</as> </param> </method> </class>
Use an XSLT processor, such as xsltproc to run the stylesheet:
$ xsltproc /usr/share/libcxx/threadmsgdispatcher.xml errorthread.xml >errorthread.inc.H
This results in the following contents of errorthread.inc.H
(with some reformatting for readability):
// AUTOGENERATED -- do not edit #ifndef libx_autogen_suppress_ public: //! logerror() function template <typename param_type_1, typename param_type_2, typename param_type_3> void logerror( param_type_1 && param_1, param_type_2 && param_2, param_type_3 && param_3) { this->sendevent(&mythreadObj::dispatch_logerror, this, std::forward<param_type_1>(param_1), std::forward<param_type_2>(param_2), std::forward<param_type_3>(param_3)); } //! Provide default value void logerror() { logerror(""); } //! Provide default value template <typename param_type_1> void logerror( param_type_1 && param_1) { logerror( std::forward<param_type_1>(param_1), -1); } //! Provide default value template < typename param_type_1, typename param_type_2> void logerror( param_type_1 && param_1, param_type_2 && param_2) { logerror( std::forward<param_type_1>(param_1), std::forward<param_type_2>(param_2), "unknown error"); } template < typename param_type_1> void testas( param_type_1 && param_1) { this->sendevent(&mythreadObj::dispatch_testas, this, int(std::forward<param_type_1>(param_1))); } private: //! Internal implementation of the logerror() message void dispatch_logerror( //! Message parameter const char *file, //! Message parameter int line, //! Descriptive error message const std::string &error_message) noexcept; //! Internal implementation of the testas() message virtual void dispatch_testas( //! Message parameter testas_class param);
This generated code gets inserted directly into a class declaration:
#include <string> #include <x/threadmsgdispatcher.H> class errorthread : public x::threadmsgdispatcherObj { #include "errorthread.inc.H" public: void run(x::ptr<x::obj> &threadmsgdispatcher_mcguffin) };
All that's left is the actual implementation of the two
dispatch_
()
methods. The final product is a
class with two public API methods:
name
logerror
() that takes a filename, a line number,
and an error message string; and
test_as
() that takes a custom class name
as a parameter.
The end result: define the method names and their parameters in the
XML definition file, the stylesheet generates the
stub code for those methods, then
implement the
dispatch_
()
private class method.
name
Using the same XML file as an example:
<class name="mythreadObj"> <method name="logerror"> <comment> //! logerror() function</comment> <param> <decl>const char *file</decl> <default>""</default> </param> <param> <decl>int line</decl> <default>-1</default> </param> <param> <comment>//! Descriptive error message</comment> <decl>const std::string &error_message</decl> <default>"unknown error"</default> </param> <attributes> noexcept</attributes> </method> <method name="testas"> <virtual /> <param> <decl>testas_class param</decl> <as>int</as> </param> </method> </class>
The
<class>
element has a required “name” attribute that gives the
name of the class for the generated methods, and contains one or more
<method>
elements.
Each
<method>
has a required attribute, “name” that's essentially the
name of the public API method, and
contains an optional <comment>
element,
and a list of
<param>
elements, one for each parameter to the public API function.
This list may be empty, if the public API function takes no parameters.
<method>
has an optional “attributes” parameter that, if present
gets inserted immediately after the class keyword, verbatim. This can
be used to specify noexcept
(as in the example),
or set gcc extensions on the method declaration.
<param>
has an optional
<comment>
element, and a required
<decl>
element that contains a literal declaration of a C++ variable.
<method>
has an optional “virtual” element that
prepends “virtual
” to the
private dispatch_
method.
name
The
<method>
element has an optional
“default” element.
Normally, the stylesheet generates a public method that
forwards its arguments to sendevent
().
For example, given following stylesheet:
<class name="mythreadObj"> <method name="logerror"> <comment> //! logerror() function</comment> <param> <decl>const char *file</decl> </param> <param> <decl>int line</decl> </param> <param> <comment>//! Descriptive error message</comment> <decl>const std::string &error_message</decl> </param> <attributes> noexcept</attributes> </method> </classname>
This results in the following code getting generated for this public method:
template < typename param_type_1, typename param_type_2, typename param_type_3> void logerror( param_type_1 && param_1, param_type_2 && param_2, param_type_3 && param_3) { this->sendevent(&mythreadObj::dispatch_logerror, this, std::forward<param_type_1>(param_1), std::forward<param_type_2>(param_2), std::forward<param_type_3>(param_3)); }
The following stylesheet specifies default values for all parameters:
<class name="mythreadObj"> <method name="logerror"> <comment> //! logerror() function</comment> <param> <decl>const char *file</decl> <default>""</default> </param> <param> <decl>int line</decl> <default>-1</default> </param> <param> <comment>//! Descriptive error message</comment> <decl>const std::string &error_message</decl> <default>"unknown error"</default> </param> <attributes> noexcept</attributes> </method> </class>
This generates additional overloaded public methods that supply the default values for the parameters:
//! logerror() function template < typename param_type_1, typename param_type_2, typename param_type_3> void logerror( param_type_1 && param_1, param_type_2 && param_2, param_type_3 && param_3) { this->sendevent(&mythreadObj::dispatch_logerror, this, std::forward<param_type_1>(param_1), std::forward<param_type_2>(param_2), std::forward<param_type_3>(param_3)); } //! Provide default value void logerror() { logerror(""); } //! Provide default value template < typename param_type_1> void logerror( param_type_1 && param_1) { logerror( std::forward<param_type_1>(param_1), -1); } //! Provide default value template < typename param_type_1, typename param_type_2> void logerror( param_type_1 && param_1, param_type_2 && param_2) { logerror( std::forward<param_type_1>(param_1), std::forward<param_type_2>(param_2), "unknown error"); }
<comment>
elements in
<method>
and
<param>
provide a mechanism for
inserting Doxygen tags:
<method name="write"> <comment>/*! Write an object */</comment> <param type="class"> <comment>/*! Object to be written */</comment> <decl>objref object</decl></param> </method>
The resulting output from the stylesheet looks like this (reformatted for readability).
// AUTOGENERATED -- do not edit #ifndef libcxx_autogen_suppress_inner /* ... */ /*! Write an object */ void write(/*! Object to be written */ const objref &object_arg) { /* ... */ #endif
A GNU make rule is
installed by default as
/usr/share/libcxx-
or
version
/libcxx.mk/usr/local/share/libcxx-
,
and contains
a couple of useful gmake macros.
This rule requires GNU
make
and xsltproc:
version
/libcxx.mk
include /usr/share/libcxx-version
/libcxx.mk $(call THREADMSGDISPATCHER_GEN,errorlog.H,errorlog.xml)
This example creates a rule for the target
errorlog.H
with a dependency errorlog.xml
.
The target gets created by running the
threadmsgdispatcher.xsl
stylesheet.
In configure.ac
:
LIBCXX_INIT
In Makefile.am
:
@LIBCXX_AM@ # Followed by macro calls: $(call THREADMSGDISPATCHER_GEN,errorlog.H,errorlog.xml) # ...
The “LIBCXX_INIT”
autoconf
macro sets LIBCXX_AM
to the include statement
that pulls in libcxx.mk
.
libcxx.mk
does a rudimentary check
if AUTOMAKE
is set. If so, it automatically
generates a make distclean rule to remove
all targets generated by the macros. The
LIBCXX_CLEANFILES
variable
accumulates the list of targets produced by all macro invocations,
and may be utilized by non-automake makefiles for similar purposes.
Additionally, all .xml
dependencies get added
to EXTRA_DIST
. Any assignments to
EXTRA_DIST
in Makefile.am
must occur before inclusion of libcxx.mk
.
It is permitted to use +=
to add to
EXTRA_DIST
after the inclusion of
libcxx.mk
.