Using the C preprocessor with D code

H. S. Teoh via Digitalmars-d digitalmars-d at puremagic.com
Wed Apr 5 13:50:29 PDT 2017


D doesn't have a preprocessor, and for good reason, as it can allow all
sorts of hard-to-find bugs and other issues that make code hard to
maintain / understand.

However, some time ago I found an occasion where it was useful to run D
code through a C preprocessor before handing it to the D compiler.  This
is in the context of a wrapper I wrote around libfreetype for one of my
projects.  It was easy enough to write declarations for Freetype
functions that I needed (I didn't intend for the wrapper to be
*complete*; just good enough for what I need), but when it came to
handling Freetype errors, I didn't want to manually type in error
definitions (which may change upon upgrading libfreetype).

Fortunately, Freetype itself comes with a flexible error-handling module
in the form of the header file fterrdef.h, in which errors are defined
as macros of the form FT_ERRORDEF_(name,code,msg) and
FT_NOERRORDEC_(name,code,msg). By suitably defining these two macros and
#include'ing the file, the user can generate useful things like tables
of error messages, switch statement blocks for handling translating
error codes, etc..

Of course, that's in the realm of C code, but since the C preprocessor
actually doesn't care what language it's processing (all it really cares
about is the #-directives, and on the side stripping C-style comments),
it's actually possible to do this *directly from D code*. So here's what
I did:

------------------------------------snip-----------------------------------
	module font.freetype_errors;

	alias FT_Error = int;

	/* This sets up the macros for extracting the error definitions */
	#define FT_ERRORDEF_(name,code,msg) \
	    enum FT_Err_ ## name = code;

	#define FT_NOERRORDEF_(name,code,msg) \
	    enum FT_Err_ ## name = code;

	/* This (evil!) magic does the actual emitting of the enum declaration */
	#include "freetype2/freetype/fterrdef.h"
	#undef FT_ERRORDEF_
	#undef FT_NOERRORDEF_

	string toString(FT_Error err)
	{
	    switch (err)
	    {
		/* This sets up the macros for extracting the error messages */
	#define FT_ERRORDEF_(name,code,msg) \
		case FT_Err_ ## name:   \
		    return msg;

	#define FT_NOERRORDEF_(name,code,msg) \
		case FT_Err_ ## name:   \
		    return msg;
	#include "freetype2/freetype/fterrdef.h"

		default:
		    import std.conv : to;
		    return "Freetype error " ~ to!string(cast(int)err);
	    }
	}
------------------------------------snip-----------------------------------

The D compiler, of course, can't compile this code, because it doesn't
understand the #-directives. But I *can* preprocess it explicitly by
running it through cpp and piping the output to an actual .d file that
will be imported by the rest of my code.  The preprocessor does the work
of actually expanding those error definitions into a D-style enum, as
well as generate the body of a nice function for converting FT_Error
into an error message defined by the libfreeetype sources.  Since this
is automatically done, I don't even have to change the code when
upgrading to a new version of libfreetype; any new error definitions
will automatically be created for me. :-)

Who says you can't use a preprocessor with D code? ;-)


T

-- 
Let's not fight disease by killing the patient. -- Sean 'Shaleh' Perry


More information about the Digitalmars-d mailing list