Arg substitution leaves placeholder marker in the stream for empty arguments. Those need to be skipped when searching for a fnlike macro invocation in the replacement list itself. See testcase.
Unified · Split
| @@ -3023,7 +3023,7 @@ static int next_argstream(Sym **nested_list, int can_read_stream, TokenString *w · 3022 unmodified lines | ||
| 3023 | 3023 | if (macro_ptr) { |
| 3024 | 3024 | p = macro_ptr, t = *p; |
| 3025 | 3025 | if (ws_str) { |
| 3026 | − while (is_space(t) || TOK_LINEFEED == t) | |
| 3026 | + while (is_space(t) || TOK_LINEFEED == t || TOK_PLCHLDR == t) | |
| 3027 | 3027 | tok_str_add(ws_str, t), t = *++p; |
| 3028 | 3028 | } |
| 3029 | 3029 | if (t == 0 && can_read_stream) { |
| @@ -3152,7 +3152,9 @@ static int macro_subst_tok( · 122 unmodified lines | ||
| 3152 | 3152 | } else { |
| 3153 | 3153 | tok_str_free_str(ws_str.str); |
| 3154 | 3154 | } |
| 3155 | − next_nomacro(); /* eat '(' */ | |
| 3155 | + do { | |
| 3156 | + next_nomacro(); /* eat '(' */ | |
| 3157 | + } while (tok == TOK_PLCHLDR); | |
| 3156 | 3158 | |
| 3157 | 3159 | /* argument macro */ |
| 3158 | 3160 | args = NULL; |
| @@ -0,0 +1,15 @@ | ||
| 1 | +#define M_RETI_ARG27(x,y,z,aa, ...) aa | |
| 2 | +#define M_RET_ARG27(...) M_RETI_ARG27(__VA_ARGS__) | |
| 3 | +#define M_COMMA_P(...) M_RET_ARG27(__VA_ARGS__, 1, 1, 0, useless) | |
| 4 | +#define M_EMPTYI_DETECT(...) 0, 1, | |
| 5 | +#define M_EMPTYI_P_C1(...) M_COMMA_P(M_EMPTYI_DETECT __VA_ARGS__ () ) | |
| 6 | +#define EX | |
| 7 | +#define empty(x) | |
| 8 | +#define fnlike(x) yeah x | |
| 9 | +/* If the following macro is called with empty arg (X183), the use | |
| 10 | + of 'x' between fnlike and '(' doesn't hinder the recognition of this | |
| 11 | + being a further fnlike macro invocation. */ | |
| 12 | +#define usefnlike(x) fnlike x (x) | |
| 13 | +X181 M_EMPTYI_P_C1() | |
| 14 | +X182 M_EMPTYI_P_C1(x) | |
| 15 | +X183 usefnlike() | |
| @@ -0,0 +1,3 @@ | ||
| 1 | +X181 1 | |
| 2 | +X182 0 | |
| 3 | +X183 yeah | |