Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 15 Dec 2005 12:34:28 +0100
From:      Hans Petter Selasky <hselasky@c2i.net>
To:        Peter Jeremy <PeterJeremy@optushome.com.au>
Cc:        freebsd-hackers@freebsd.org
Subject:   Re: Standard C-macro scripting
Message-ID:  <200512151234.29933.hselasky@c2i.net>
In-Reply-To: <20051215093059.GL77268@cirb503493.alcatel.com.au>
References:  <200512131936.11640.hselasky@c2i.net> <200512140105.46945.hselasky@c2i.net> <20051215093059.GL77268@cirb503493.alcatel.com.au>

next in thread | previous in thread | raw e-mail | index | archive | help
On Thursday 15 December 2005 10:30, Peter Jeremy wrote:
> On Wed, 2005-Dec-14 01:05:45 +0100, Hans Petter Selasky wrote:
> >On Tuesday 13 December 2005 20:48, John Baldwin wrote:
> >> Honestly, I think I've now been scarred for life. :-/  I think that this
> >> stuff would be so obscure that no one else would be able to help with
> >> maintenace.
>
> I tend to agree.
>
> >Macros are easy. It is just concat, stringify and expand. Maybe you have
> > to think more about it before you get it. I am probably too used to it.
>
> When you have nested macros and have to work out what is stringified at
> each level of expansion, it can get very difficult to follow.
>
> >What is the alternative? An Awk script would require a lot more code and
> > it cannot be called from a C-program when it is compiling.
>
> The approach I use (borrowed from gcc) is multiple includes.  To take
> your enum example:
>
> ========== foo.def ===================
> m(MY_ENUM_UNKNOWN  ,,"unknown")
> m(MY_ENUM_YYY      ,,"yyy ...")
> m(MY_ENUM_DAIC     ,,"zzz ...")
> ========== foo.c =====================
> enum MY_ENUM {
> #define m(a,b,c) a b,
> #include "foo.def"
> #undef m
> };
>
> static const char * const MY_ENUMS_DEFAULT_DRIVER_DESC[] = {
> #define m(a,b,c) c,
> #include "foo.def"
> #undef m
> };
> ========== EOF ======================
>
> Personally, I think this is far easier to understand - a single macro
> which is redefined to have a number of simple macro expansions rather
> than seven macros with carefully organised nesting to ensure correct
> stringification.  I agree that this requires re-reading the definition
> file but it will be cached after the first pass so this isn't particularly
> expensive.  You also have the advantage that the data (foo.def) doesn't
> have to be a single statement so you can (more easily) embed comments or
> other preprocessor directives.
>
> gcc uses this for various *.def files, redefining macros to suit
> requirements.
>
> As a real (cut-down) example from an interpreter I'm maintaining (in
> reality optab.h has 146 entries with 5 different opX_XX() macros):
>
> =============== optab.h ==================
> opd_dd(ex_add,    ADD,    "add",)         /* 2   A+B    */
> opd_d( ex_plus,   PLUS,   "plus",)        /* 3    +B    */
> opd_dd(ex_sub,    SUB,    "sub",)         /* 4   A-B    */
> opd_d( ex_minus,  MINUS,  "minus",)       /* 5    -B    */
> opd_dd(ex_pwr,    PWR,    "pwr",)         /* 16  A*B    */
> opd_d( ex_exp,    EXP,    "exp",)         /* 17   *B    */
> opd_dd(ex_comb,   COMB,   "comb",)        /* 22  A!B    */
> opd_d( ex_fac,    FAC,    "fac",)         /* 23   !B    */
>
> #undef opd_d
> #undef opd_dd
> ===========  opt_codes.h ============
> enum OpCode {
> #define opd_d(a,b,c,d)  b,
> #define opd_dd(a,b,c,d) b,
> #include "optab.h"
>     OPT_MAX     /* largest opt-code */
> };
> =============== optable.c ==============
> const pfd_d exop2[] = {
> #define opd_d(a,b,c,d)  a,
> #define opd_dd(a,b,c,d) ex_botch2,
> #include "optab.h"
> };
>
> const pfd_dd exop3[] = {
> #define opd_d(a,b,c,d)  ex_botch3,
> #define opd_dd(a,b,c,d) a,
> #include "optab.h"
> };
>
> const char *opname[] = {
> #define opd_d(a,b,c,d)  c,
> #define opd_dd(a,b,c,d) c,
> #include "optab.h"
> };
>
> =============== main.h ==============
> #define opd_d(a,b,c,d)  data a(data) d;
> #define opd_dd(a,b,c,d) data a(data, data) d;
> #include "optab.h"

I would prefer:

OPTAB_DEF_H(opd_d, opd_dd);

Because then one can put more definitions in the same file. And also it allows 
one to reuse macros without having to redefine them. Maybe you're right, that 
expanding as much as possible first is better. But in case a macro is nesting 
in one or two levels, I see no problem.

>
> ========== EOF ======================
>
> >Here is a real example of a state machine:
> >
> >#define L3_STATES(m)/* \
>
> ...
>
> >m( ST_L3_UC_TO    ,, 4/*hz*/, ST_L3_U0    , "Disconnected"          , 0x0C
> >  )\ /**/
> >
> >Isn't the state-machine above easy to edit and understand ?
>
> It looks the same as my definitions file (modulo a backslash on each line
> and not having it in a separate file).
>
> >What is wrong about that?
>
> The complexity of the macros you need to expand it.  There's also no
> scope for conditional inclusion (what if you wanted a single state
> machine description to conditionally compile for several protocol
> variants).

Yes, you can do conditionally expressions with macros, just by adding another 
field to the definition where one specifies the protcol, and it will look 
good also, because now each line will contain the version for which the line 
is valid:

#define MY_ENUMS(m)\
m(PROT_V1, MY_ENUM_UNKNOWN  ,,"unknown")\
m(PROT_V2, MY_ENUM_YYY      ,,"yyy ...")\
m(PROT_V3, MY_ENUM_DAIC     ,,"zzz ...")\

#if xxx
#define PROT_V1 YES
#define PROT_V2 NO
#deifne PROT_V3 NO
#else
...
#endif

#define MY_GENERATOR(enabled, enum, value, desc) \
 enabled (#enum ":" desc "\n")

static const char * const desc = MY_ENUMS(MY_GENERATOR);

I'm sure that one can also make logical macros that performs logical 
evaluation! Look at this:

testM.h:

#define YES(...) __VA_ARGS__
#define NO(...)

#define __AND(a,b) COND_TEST_##a##b
#define _AND(a,b) __AND(a,b)  /* pass the arguments like this so that 
                               * they are expanded 
                               */
#define AND(a,b) _AND(a(1),b(1))

#define COND_TEST(...) /* equivalent to NO */
#define COND_TEST_1(...) /* equivalent to NO */
#define COND_TEST_11(...) __VA_ARGS__ /* equivalent to YES */

/* this should be a self-explaining statement: */

/* if */AND(AND(YES,YES),YES)( keep this )

%cpp testM.h
# 1 "testM.h"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "testM.h"
# 12 "testM.h"
keep this

> >And how would you solve it?
>
> See above.

It is just that one has to build up the macro C-scripting language with some 
helper macros like NOT/AND/OR/XOR/YES/NO/SWITCH ... to make it easy to use.

For some purposes I still think that using macros directly instead of using an 
external script is more easy, though it might be an idea to expand more of 
these macros before-hand to make understanding the code easier.

--HPS



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200512151234.29933.hselasky>