最好的方法是使用预处理器解析它。我确实相信预处理器可以成为构建 EDSL(嵌入式领域特定语言)的非常强大的工具,但您必须首先了解预处理器解析事物的局限性。预处理器只能解析出预定义的标记。所以必须通过在表达式周围放置括号来稍微改变语法,并且FREEZE
还必须围绕它一个宏(我刚刚选择了 FREEZE,它可以被称为任何东西):
FREEZE(take(x*x) with(x, container))
使用此语法,您可以将其转换为预处理器序列(当然,使用Boost.Preprocessor库)。一旦你把它作为一个预处理器序列,你就可以对它应用很多算法来把它转换成你喜欢的样子。类似的方法是使用 C++ 的Linq库完成的,您可以在其中编写:
LINQ(from(x, numbers) where(x > 2) select(x * x))
现在,要首先转换为 pp 序列,您需要定义要解析的关键字,如下所示:
#define KEYWORD(x) BOOST_PP_CAT(KEYWORD_, x)
#define KEYWORD_take (take)
#define KEYWORD_with (with)
所以它的工作方式是当你调用KEYWORD(take(x*x) with(x, container))
它时它会扩展为(take)(x*x) with(x, container)
,这是将它转换为 pp 序列的第一步。现在继续我们需要使用 Boost.Preprocessor 库中的 while 构造,但首先我们需要定义一些小宏来帮助我们:
// Detects if the first token is parenthesis
#define IS_PAREN(x) IS_PAREN_CHECK(IS_PAREN_PROBE x)
#define IS_PAREN_CHECK(...) IS_PAREN_CHECK_N(__VA_ARGS__,0)
#define IS_PAREN_PROBE(...) ~, 1,
#define IS_PAREN_CHECK_N(x, n, ...) n
// Detect if the parameter is empty, works even if parenthesis are given
#define IS_EMPTY(x) BOOST_PP_CAT(IS_EMPTY_, IS_PAREN(x))(x)
#define IS_EMPTY_0(x) BOOST_PP_IS_EMPTY(x)
#define IS_EMPTY_1(x) 0
// Retrieves the first element of the sequence
// Example:
// HEAD((1)(2)(3)) // Expands to (1)
#define HEAD(x) PICK_HEAD(MARK x)
#define MARK(...) (__VA_ARGS__),
#define PICK_HEAD(...) PICK_HEAD_I(__VA_ARGS__,)
#define PICK_HEAD_I(x, ...) x
// Retrieves the tail of the sequence
// Example:
// TAIL((1)(2)(3)) // Expands to (2)(3)
#define TAIL(x) EAT x
#define EAT(...)
这提供了对括号和空的更好的检测。它提供了一个HEAD
和TAIL
宏,其工作方式与BOOST_PP_SEQ_HEAD
. (Boost.Preprocessor 无法处理具有 vardiac 参数的序列)。现在我们如何定义一个TO_SEQ
使用 while 构造的宏:
#define TO_SEQ(x) TO_SEQ_WHILE_M \
( \
BOOST_PP_WHILE(TO_SEQ_WHILE_P, TO_SEQ_WHILE_O, (,x)) \
)
#define TO_SEQ_WHILE_P(r, state) TO_SEQ_P state
#define TO_SEQ_WHILE_O(r, state) TO_SEQ_O state
#define TO_SEQ_WHILE_M(state) TO_SEQ_M state
#define TO_SEQ_P(prev, tail) BOOST_PP_NOT(IS_EMPTY(tail))
#define TO_SEQ_O(prev, tail) \
BOOST_PP_IF(IS_PAREN(tail), \
TO_SEQ_PAREN, \
TO_SEQ_KEYWORD \
)(prev, tail)
#define TO_SEQ_PAREN(prev, tail) \
(prev (HEAD(tail)), TAIL(tail))
#define TO_SEQ_KEYWORD(prev, tail) \
TO_SEQ_REPLACE(prev, KEYWORD(tail))
#define TO_SEQ_REPLACE(prev, tail) \
(prev HEAD(tail), TAIL(tail))
#define TO_SEQ_M(prev, tail) prev
现在,当你打电话时,TO_SEQ(take(x*x) with(x, container))
你应该得到一个序列(take)((x*x))(with)((x, container))
。
现在,这个序列更容易使用(因为 Boost.Preprocessor 库)。您现在可以对其进行反转、转换、过滤、折叠等。这非常强大,并且比将它们定义为宏要灵活得多。例如,在 Linq 库中,查询from(x, numbers) where(x > 2) select(x * x)
被转换为这些宏:
LINQ_WHERE(x, numbers)(x > 2) LINQ_SELECT(x, numbers)(x * x)
对于这些宏,它将生成用于列表理解的 lambda,但是在生成 lambda 时它们还有更多工作要做。在你的库中也可以这样做,take(x*x) with(x, container)
可以转换成这样的东西:
FREEZE_TAKE(x, container, x*x)
另外,您没有定义take
侵入全局空间的宏。
注意:这里的这些宏需要 C99 预处理器,因此在 MSVC 中不起作用。(虽然有解决方法)