This patch is for gcc-5.2.0 adds the XXX option to the preprocessor that enables to escape commas when passing macro arguments. This feature is useful in C++ when the macro argument is a template with more than one argument, and adding extra ( ) is not possible, as shown in this example:
template <class X, class Y> struct S; #define Macro(arg) S arg Macro(<int, int>); // error: passing 2 arguments Adding extra ( ) as a workaround doesn't work either: Macro((<int, int>)); // expands to struct S (<int, int>) Despite there are workarounds using variadic macros (as already discussed in the C++' std-proposals mailing list) and using indirect macros (such as defining a COMMA macro or alike), this patch enables to escape the comma to prevent it behave as an argument separator, allowing to write the example above as: Macro(<int\, int>); // only one argument Other people in the same mailing list proposed to teach the preprocessor to do tokens balancing, but this approach is both more complicated and fails in the following examples: #define IF(condition1, condition2) if(lower condition1 && condition2 upper) IF(<b, c>) return 1; #define BRACKET(x) x] = 1 BRACKET(a[1); While we start a discussion with the C committee regarding the standarization of this feature, we think that this is a useful nonstandard addition to be early adopted. I ran all the test and passed. Thanks, Andrés. diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 983f4a8..41fa9dd 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -181,6 +181,10 @@ A C ObjC C++ ObjC++ Joined Separate MissingArgError(assertion missing after %qs) -A<question>=<answer> Assert the <answer> to <question>. Putting '-' before <question> disables the <answer> to <question> +fmacro-escaped-commas +C ObjC C++ ObjC++ CPP(macro_escaped_commas) Var(cpp_macro_escaped_commas) Init(0) +Allows using escaped commas in macros arguments + C C ObjC C++ ObjC++ Do not discard comments diff --git a/gcc/testsuite/gcc.dg/cpp/macro-escaped-commas.c b/gcc/testsuite/gcc.dg/cpp/macro-escaped-commas.c new file mode 100644 index 0000000..e56df04 --- /dev/null +++ b/gcc/testsuite/gcc.dg/cpp/macro-escaped-commas.c @@ -0,0 +1,11 @@ +/* { dg-do run } */ +/* { dg-options "-fmacro-escaped-commas" } */ + +#define MACRO_DEFINITION0(a, b) a + b +#define MACRO_DEFINITION1(a) 0 +int main(void) +{ + int a = MACRO_DEFINITION1({1\,2\,3}); + int b = MACRO_DEFINITION0(MACRO_DEFINITION1({1\,2\,3}), 1); + return 0; +} diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h index 5e08014..c3d404b 100644 --- a/libcpp/include/cpplib.h +++ b/libcpp/include/cpplib.h @@ -481,6 +481,9 @@ struct cpp_options /* True if dependencies should be restored from a precompiled header. */ bool restore_pch_deps; + /* True to use escaped commas in macros*/ + bool macro_escaped_commas; + /* True if warn about differences between C90 and C99. */ signed char cpp_warn_c90_c99_compat; diff --git a/libcpp/macro.c b/libcpp/macro.c index 1e0a0b5..f9eba81 100644 --- a/libcpp/macro.c +++ b/libcpp/macro.c @@ -811,6 +811,7 @@ collect_args (cpp_reader *pfile, const cpp_hashnode *node, source_location virt_loc; bool track_macro_expansion_p = CPP_OPTION (pfile, track_macro_expansion); unsigned num_args_alloced = 0; + const bool escaped_commas_p = CPP_OPTION(pfile, macro_escaped_commas); macro = node->value.macro; if (macro->paramc) @@ -835,6 +836,8 @@ collect_args (cpp_reader *pfile, const cpp_hashnode *node, few. Hence the slightly bizarre usage of "argc" and "arg". */ do { + bool prev_backslash_p = false; + bool token_taken_p = false; unsigned int paren_depth = 0; unsigned int ntokens = 0; unsigned virt_locs_capacity = DEFAULT_NUM_TOKENS_PER_MACRO_ARG; @@ -867,8 +870,9 @@ collect_args (cpp_reader *pfile, const cpp_hashnode *node, arg->virt_locs, virt_locs_capacity); } - - token = cpp_get_token_1 (pfile, &virt_loc); + //If a token wasn't already taken, get a token + if (!token_taken_p) + token = cpp_get_token_1 (pfile, &virt_loc); if (token->type == CPP_PADDING) { @@ -886,9 +890,12 @@ collect_args (cpp_reader *pfile, const cpp_hashnode *node, else if (token->type == CPP_COMMA) { /* A comma does not terminate an argument within - parentheses or as part of a variable argument. */ + parentheses, as part of a variable argument or + if the macro_escaped_commas flag is on and + if its preceded by a backslash. */ if (paren_depth == 0 - && ! (macro->variadic && argc == macro->paramc)) + && ! (macro->variadic && argc == macro->paramc) + && ! (prev_backslash_p && escaped_commas_p)) break; } else if (token->type == CPP_EOF @@ -939,10 +946,32 @@ collect_args (cpp_reader *pfile, const cpp_hashnode *node, else continue; } - set_arg_token (arg, token, virt_loc, - ntokens, MACRO_ARG_TOKEN_NORMAL, - CPP_OPTION (pfile, track_macro_expansion)); - ntokens++; + prev_backslash_p = (token->type == CPP_OTHER && token->val.str.text[0] == '\\'); + token_taken_p = escaped_commas_p && prev_backslash_p && paren_depth == 0; + /* If we have the macros-escaped-commas flag on, the current token is a backslash + and we are not between parenthesis, get a new token and check if it's a comma. + In that case we are going to ignore the backslash. + */ + if (token_taken_p) + { + const cpp_token *peek_tok = cpp_get_token_1 (pfile, &virt_loc); + //If the next token is a comma, ignore the backslash. + if (peek_tok->type != CPP_COMMA) + { + set_arg_token (arg, token, virt_loc, + ntokens, MACRO_ARG_TOKEN_NORMAL, + CPP_OPTION (pfile, track_macro_expansion)); + ntokens++; + } + token = peek_tok; + } + else + { + set_arg_token (arg, token, virt_loc, + ntokens, MACRO_ARG_TOKEN_NORMAL, + CPP_OPTION (pfile, track_macro_expansion)); + ntokens++; + } } /* Drop trailing padding. */