1

I am trying to write a macro which counts the number of specific elements in a sequence. E.g. for the sequence (A)(B)(A) I want to get the count 2 for A.
Now taking the approach from https://stackoverflow.com/a/12540675/2525536 I end up with this code:

#define CAT(x, ...) CAT1(x, __VA_ARGS__)
#define CAT1(x, ...) CAT2(x, __VA_ARGS__)
#define CAT2(x, ...) x ## __VA_ARGS__

#define EXPAND(...) __VA_ARGS__
#define EAT(...)
#define DEFER(...) __VA_ARGS__ EAT()
#define OBSTRUCT(...) __VA_ARGS__ DEFER(EAT)()

#define SIZE(seq) CAT(SIZE_, SIZE_0 seq)
#define SIZE_0(...) SIZE_1
#define SIZE_1(...) SIZE_2
#define SIZE_2(...) SIZE_3
#define SIZE_3(...) SIZE_4
#define SIZE_SIZE_0 0
#define SIZE_SIZE_1 1
#define SIZE_SIZE_2 2
#define SIZE_SIZE_3 3
#define SIZE_SIZE_4 4

#define GET_FIRST(x) GET_FIRST2(GET_FIRST1 x)
#define GET_FIRST1(x) x, EAT()
#define GET_FIRST2(x) GET_FIRST3(x)
#define GET_FIRST3(x, ...) x

#define POP_FIRST(x) EAT x

#define EVAL(...)  EVAL1(EVAL1(__VA_ARGS__))
#define EVAL1(...) EVAL2(EVAL2(__VA_ARGS__))
#define EVAL2(...) EVAL3(EVAL3(__VA_ARGS__))
#define EVAL3(...) EVAL4(EVAL4(__VA_ARGS__))
#define EVAL4(...) __VA_ARGS__

#define CHECK_N(x, n, ...) n

#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)
#define CHECK_PROBE(x) x, 1, 

#define NOT(x) CHECK(CAT(NOT_, x))
#define NOT_0 ~, 1,

#define COMPL(b) CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0

#define BOOL(x) COMPL(NOT(x))

#define IIF(c) CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t

#define IF(c) IIF(BOOL(c))

#define WHEN(c) IF(c)(EXPAND, EAT)

#define INC(x) CAT(INC_, x)
#define INC_0 1
#define INC_1 2
#define INC_2 3
#define INC_3 4
#define INC_4 5

#define DEC(x) CAT(DEC_, x)
#define DEC_0 0
#define DEC_1 0
#define DEC_2 1
#define DEC_3 2
#define DEC_4 3
#define DEC_5 4

#define COUNT_IF(tpl, data, x) COUNT_IF1(0, SIZE(x), 0, tpl, data, x)
#define COUNT_IF1(i, n, count, tpl, data, x) \
    IF(n) ( \
        OBSTRUCT(CAT) ( \
            COUNT_IF3_, \
            tpl(i, data, GET_FIRST(x)) \
        ) ( \
            OBSTRUCT(COUNT_IF2)() \
                (INC(i), DEC(n), count, tpl, data, POP_FIRST(x)) /* call recursive */ \
        ) \
        , count \
    )
#define COUNT_IF2() COUNT_IF1
#define COUNT_IF3_0 EXPAND
#define COUNT_IF3_1 INC

#define A_EQUAL_A(...) 1
#define A_EQUAL_B(...) 0
#define COUNT_A(i, data, x) CAT(A_EQUAL_, x)(i)

EVAL(COUNT_IF(COUNT_A, ~, (A)))
EVAL(COUNT_IF(COUNT_A, ~, (B)))
EVAL(COUNT_IF(COUNT_A, ~, (A)(B)))
EVAL(COUNT_IF(COUNT_A, ~, (B)(A)))
EVAL(COUNT_IF(COUNT_A, ~, (A)(A)))
EVAL(COUNT_IF(COUNT_A, ~, (B)(B)))
EVAL(COUNT_IF(COUNT_A, ~, (A)(B)(A)))

This works already quite well for the first 4 examples but ends up with wrong macro name expandation for the others (where INC or EXPAND needs to be expanded more than once).

The reason for the is probably because macros are marked blue in C, which is why this works https://stackoverflow.com/a/11640759/2525536.
But I cannot find a workaround for this.
Anyone an idea?

Community
  • 1
  • 1
user2525536
  • 366
  • 1
  • 4
  • 14
  • 2
    I can't help but wonder if the C preprocessor is the right tool for this job, whatever it is. It feels like an [XY Problem](http://mywiki.wooledge.org/XyProblem). – Jonathan Leffler Sep 15 '16 at 19:57
  • Of course this is part of a different problem. I want to apply a macro with the sequence if the sequence contains a specific element/token, eventually. The shortest way in my opinion was to count the occurences of that token and test the result against 0. – user2525536 Sep 15 '16 at 21:09
  • I have to agree with Jonathan Leffler here. A workaround would be to implement a competent code generator, because the C preprocessor isn't. Even if you succeed in implementing this, the code will be unmanageable, and SO isn't really the place for Q&As on the artistry of obfuscating code. –  Sep 16 '16 at 08:22

1 Answers1

1

As I already received some comments about using the C preprocessor for this task is not an appropiate way of handling this issue I still intent to go this way not only to minimize code dependencies but also to make the best out of what C offers in my code (which may not be obvious here).
Having this said I found out why the macro was not expanding correctly and also a way to make the preprocessor do this task correctly.
The issue was that the macro generated a construct like INC(EXPAND(INC(0))) from outside (first element) to inside (last element). The preprocessor tried to solve this while always only knowing all values until the current level/element. This turned into something like INC(COUNT_IF1(...)) on which point the preprocessor started to substitute INC for example.
The correct way of handling this was to make the preprocessor extend the macro from inside to outside.
Simplifying the code a little bit gives this solution:

#define CAT(x, ...) CAT1(x, __VA_ARGS__)
#define CAT1(x, ...) CAT2(x, __VA_ARGS__)
#define CAT2(x, ...) x ## __VA_ARGS__

#define EXPAND(...) __VA_ARGS__
#define EAT(...)
#define DEFER(...) __VA_ARGS__ EAT()
#define OBSTRUCT(...) __VA_ARGS__ DEFER(EAT)()

#define SIZE(seq) CAT(SIZE_, SIZE_0 seq)
#define SIZE_0(...) SIZE_1
#define SIZE_1(...) SIZE_2
#define SIZE_2(...) SIZE_3
#define SIZE_3(...) SIZE_4
#define SIZE_SIZE_0 0
#define SIZE_SIZE_1 1
#define SIZE_SIZE_2 2
#define SIZE_SIZE_3 3
#define SIZE_SIZE_4 4

#define GET_FIRST(x) GET_FIRST2(GET_FIRST1 x)
#define GET_FIRST1(x) x, EAT()
#define GET_FIRST2(x) GET_FIRST3(x)
#define GET_FIRST3(x, ...) x

#define POP_FIRST(x) EAT x

#define EVAL(...)  EVAL1(EVAL1(__VA_ARGS__))
#define EVAL1(...) EVAL2(EVAL2(__VA_ARGS__))
#define EVAL2(...) EVAL3(EVAL3(__VA_ARGS__))
#define EVAL3(...) EVAL4(EVAL4(__VA_ARGS__))
#define EVAL4(...) __VA_ARGS__

#define CHECK_N(x, n, ...) n

#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)
#define CHECK_PROBE(x) x, 1, 

#define NOT(x) CHECK(CAT(NOT_, x))
#define NOT_0 ~, 1,

#define COMPL(b) CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0

#define BOOL(x) COMPL(NOT(x))

#define IIF(c) CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t

#define IF(c) IIF(BOOL(c))

#define WHEN(c) IF(c)(EXPAND, EAT)

#define INC(x) CAT(INC_, x)
#define INC_0 1
#define INC_1 2
#define INC_2 3
#define INC_3 4
#define INC_4 5

#define DEC(x) CAT(DEC_, x)
#define DEC_0 0
#define DEC_1 0
#define DEC_2 1
#define DEC_3 2
#define DEC_4 3
#define DEC_5 4

#define IS_PAREN(x) CHECK(IS_PARENT1 x)
#define IS_PARENT1(...) CHECK_PROBE(~)

#define IS_COMPARABLE(x) IS_PAREN(CAT(COMPARE_, x)(()))

#define BITAND(lhs, rhs) CAT(CAT(CAT(BITAND_, lhs), _), rhs)
#define BITAND_0_0 0
#define BITAND_0_1 0
#define BITAND_1_0 0
#define BITAND_1_1 1

#define NOT_EQUAL(lhs, rhs) \
    IIF(BITAND(IS_COMPARABLE(lhs), IS_COMPARABLE(rhs)))( \
        NOT_EQUAL_HELPER, \
        1 NULL \
    )(lhs, rhs)
#define NOT_EQUAL_HELPER(lhs, rhs) IS_PAREN( \
        CAT(COMPARE_, lhs)(CAT(COMPARE_, rhs))(()) \
    )

#define EQUAL(lhs, rhs) COMPL(NOT_EQUAL(lhs, rhs))

#define COUNT_IF(match, x) COUNT_IF1(SIZE(x), match, 0, x)
#define COUNT_IF1(n, match, count, x) \
    IF(n) ( \
        OBSTRUCT(COUNT_IF2)()( \
            DEC(n), \
            match, \
            IF(match(GET_FIRST(x)))(INC, EXPAND)(count), POP_FIRST(x) \
        ) /* call recursive */ \
        , count \
    )
#define COUNT_IF2() COUNT_IF1

#define COMPARE_A(x) x
#define EQUAL_A(x) EQUAL(x, A)


EVAL(COUNT_IF(EQUAL_A, (A)))
EVAL(COUNT_IF(EQUAL_A, (B)))
EVAL(COUNT_IF(EQUAL_A, (A)(B)))
EVAL(COUNT_IF(EQUAL_A, (B)(A)))
EVAL(COUNT_IF(EQUAL_A, (A)(A)))
EVAL(COUNT_IF(EQUAL_A, (B)(B)))
EVAL(COUNT_IF(EQUAL_A, (A)(B)(A)(A)))

P.S.: An 11 lines macro still seems simple enough to me to handle in terms of complexity and maintainability.

user2525536
  • 366
  • 1
  • 4
  • 14