diff --git a/tcc.h b/tcc.h index 384e8de..e9d907e 100644 --- a/tcc.h +++ b/tcc.h @@ -880,6 +880,8 @@ struct TCCState { #define TOK_DOTS 0xcc /* three dots */ #define TOK_SHR 0xcd /* unsigned shift right */ #define TOK_NOSUBST 0xcf /* means following token has already been pp'd */ +#define TOK_GNUCOMMA 0xd0 /* ,## preprocessing token */ +#define TOK_QHASH 0xd1 /* quoted hash character */ #define TOK_SHL 0x01 /* shift left */ #define TOK_SAR 0x02 /* signed shift right */ diff --git a/tccpp.c b/tccpp.c index 8a06300..a04e050 100644 --- a/tccpp.c +++ b/tccpp.c @@ -82,6 +82,7 @@ static const unsigned char tok_two_chars[] = '-','>', TOK_ARROW, '.','.', 0xa8, // C++ token ? '#','#', TOK_TWOSHARPS, + '\\','#', TOK_QHASH, 0 }; @@ -439,6 +440,8 @@ static int handle_stray_noerror(void) goto fail; file->line_num++; inp(); + } else if (ch == '#') { + ch = TOK_QHASH; } else { fail: return 1; @@ -1305,7 +1308,7 @@ ST_FUNC void parse_define(void) --str.len; spc = 2; } else if ('#' == tok) { - spc = 2; + spc = 3; } else if (check_space(tok, &spc)) { goto skip; } @@ -2247,6 +2250,7 @@ static inline void next_nomacro1(void) p = file->buf_ptr; redo_no_start: c = *p; + redo_no_start_2: switch(c) { case ' ': case '\t': @@ -2273,7 +2277,8 @@ static inline void next_nomacro1(void) ch = *p; handle_stray(); p = file->buf_ptr; - goto redo_no_start; + c = ch; + goto redo_no_start_2; } parse_eof: { @@ -2666,6 +2671,7 @@ maybe_newline: case '?': case '~': case '@': /* only used in assembler */ + case TOK_QHASH: parse_simple: tok = c; p++; @@ -2771,14 +2777,11 @@ static int *macro_arg_subst(Sym **nested_list, const int *macro_str, Sym *args) if (gnu_ext && s->type.t && last_tok == TOK_TWOSHARPS && str.len >= 2 && str.str[str.len - 2] == ',') { - if (*st == TOK_PLCHLDR) { - /* suppress ',' '##' */ - str.len -= 2; - } else { - /* suppress '##' and add variable */ - str.len--; - goto add_var; - } + str.len -= 2; + tok_str_add(&str, TOK_GNUCOMMA); + str.str[str.len] = 0; // not actually needed. + + goto add_var; } else { int t1; add_var: @@ -2789,7 +2792,7 @@ static int *macro_arg_subst(Sym **nested_list, const int *macro_str, Sym *args) tok_str_add2(&str, t1, &cval); } } - } else if (*st != TOK_PLCHLDR) { + } else { /* NOTE: the stream cannot be read when macro substituing an argument */ macro_subst(&str, nested_list, st, NULL); @@ -2802,6 +2805,8 @@ static int *macro_arg_subst(Sym **nested_list, const int *macro_str, Sym *args) } last_tok = t; } + if (str.len == 0) + tok_str_add(&str, TOK_PLCHLDR); tok_str_add(&str, 0); return str.str; } @@ -2862,6 +2867,7 @@ static int macro_subst_tok(TokenString *tok_str, tok_str_add2(tok_str, t1, &cval); cstr_free(&cstr); } else { + int mtok = tok; mstr = s->d; mstr_allocated = 0; if (s->type.t == MACRO_FUNC) { @@ -2931,20 +2937,25 @@ static int macro_subst_tok(TokenString *tok_str, while ((parlevel > 0 || (tok != ')' && (tok != ',' || sa->type.t))) && - tok != -1) { + tok != -1 && + tok != 0) { if (tok == '(') parlevel++; else if (tok == ')') parlevel--; if (tok == TOK_LINEFEED) tok = ' '; + if (tok == TOK_EOF || tok == 0) + break; if (!check_space(tok, &spc)) tok_str_add2(&str, tok, &tokc); next_nomacro_spc(); } - if (!str.len) - tok_str_add(&str, TOK_PLCHLDR); + if (parlevel) + return -1; str.len -= spc; + if (str.len == 0) + tok_str_add(&str, TOK_PLCHLDR); tok_str_add(&str, 0); sa1 = sym_push2(&args, sa->v & ~SYM_FIELD, sa->type.t, 0); sa1->d = str.str; @@ -2957,8 +2968,30 @@ static int macro_subst_tok(TokenString *tok_str, else break; } - if (tok != ',') - expect(","); + /* + * #define f(a) f(a) + * #define h f(r + * h 5) + */ + if (tok != ',') { + /* Argh! Not a macro invocation after all, at this + * point, so put everything back onto mstr that's + * been skipped since we saw the '(' )*/ + tok_str_new(&str); + tok_str_add(&str, mtok); + tok_str_add(&str, '('); + for (sa = s->next; sa; sa = sa->next) { + int *p = sa->d; + while (p && *p) { + tok_str_add(&str, *p); + p++; + } + mstr = str.str; + /* leak memory */; + mstr_allocated = 0; + goto free_memory; + } + } next_nomacro(); } if (sa) { @@ -2968,6 +3001,7 @@ static int macro_subst_tok(TokenString *tok_str, /* now subst each arg */ mstr = macro_arg_subst(nested_list, mstr, args); + free_memory: /* free memory */ sa = args; while (sa) { @@ -2996,6 +3030,7 @@ static inline int *macro_twosharps(const int *macro_str) { const int *ptr; int t; + int last_tok = -1; TokenString macro_str1; CString cstr; int n, start_of_nosubsts; @@ -3023,6 +3058,13 @@ static inline int *macro_twosharps(const int *macro_str) if (tok == TOK_NOSUBST && start_of_nosubsts < 0) start_of_nosubsts = macro_str1.len; while (*ptr == TOK_TWOSHARPS) { + if (tok == '#') { + CValue cval; + tok_str_add2(¯o_str1, tok, &tokc); + tok = *ptr++; + ptr++; + break; + } /* given 'a##b', remove nosubsts preceding 'a' */ if (start_of_nosubsts >= 0) macro_str1.len = start_of_nosubsts; @@ -3039,13 +3081,15 @@ static inline int *macro_twosharps(const int *macro_str) if (tok != TOK_PLCHLDR) cstr_cat(&cstr, get_tok_str(tok, &tokc)); n = cstr.size; - if (t != TOK_PLCHLDR || tok == TOK_PLCHLDR) + if (t != TOK_PLCHLDR) cstr_cat(&cstr, get_tok_str(t, &cval)); cstr_ccat(&cstr, '\0'); tcc_open_bf(tcc_state, ":paste:", cstr.size); memcpy(file->buffer, cstr.data, cstr.size); for (;;) { + if (0 == *file->buf_ptr) + break; next_nomacro1(); if (0 == *file->buf_ptr) break; @@ -3061,8 +3105,8 @@ static inline int *macro_twosharps(const int *macro_str) tok_str_add2(¯o_str1, tok, &tokc); tok = ' '; start_of_nosubsts = -1; - } - tok_str_add2(¯o_str1, tok, &tokc); + } else + tok_str_add2(¯o_str1, tok, &tokc); } tok_str_add(¯o_str1, 0); return macro_str1.str; @@ -3082,6 +3126,7 @@ static void macro_subst(TokenString *tok_str, Sym **nested_list, CValue cval; struct macro_level ml; int force_blank; + int gnucomma_index = -1; /* first scan for '##' operator handling */ ptr = macro_str; @@ -3106,8 +3151,16 @@ static void macro_subst(TokenString *tok_str, Sym **nested_list, TOK_GET(&t, &ptr, &cval); goto no_subst; } + if (t == TOK_GNUCOMMA) { + if (gnucomma_index != -1) + tcc_error("two GNU commas in the same macro"); + gnucomma_index = tok_str->len; + tok_str_add(tok_str, ','); + TOK_GET(&t, &ptr, &cval); + } s = define_find(t); if (s != NULL) { + int old_length = tok_str->len; /* if nested substitution, do nothing */ if (sym_find2(*nested_list, t)) { /* and mark it as TOK_NOSUBST, so it doesn't get subst'd again */ @@ -3124,8 +3177,15 @@ static void macro_subst(TokenString *tok_str, Sym **nested_list, macro_ptr = ml.p; if (can_read_stream && *can_read_stream == &ml) *can_read_stream = ml.prev; - if (ret != 0) - goto no_subst; + if (ret != 0) { + if (!check_space(t, &spc)) + tok_str_add2(tok_str, t, &cval); + tok_str_add(tok_str, ' '); + spc = 1; + force_blank = 0; + } + if (ret == 0 && old_length == tok_str->len) + tok_str_add(tok_str, TOK_PLCHLDR); if (parse_flags & PARSE_FLAG_SPACES) force_blank = 1; } else { @@ -3138,6 +3198,18 @@ static void macro_subst(TokenString *tok_str, Sym **nested_list, if (!check_space(t, &spc)) tok_str_add2(tok_str, t, &cval); } + if (gnucomma_index != -1 && tok_str->len >= gnucomma_index+2) { + if (tok_str->str[gnucomma_index+1] == TOK_PLCHLDR) + tok_str->str[gnucomma_index] = ' '; + gnucomma_index = -1; + } + if (tok_str->len && tok_str->str[tok_str->len-1] == TOK_PLCHLDR) + tok_str->len--; + } + if (0 && ret != 0 && force_blank) { + tok_str_add(tok_str, ' '); + spc = 1; + force_blank = 0; } if (macro_str1) tok_str_free(macro_str1); @@ -3173,6 +3245,8 @@ ST_FUNC void next(void) macro_ptr_allocated = str.str; goto redo; } + if (str.len && str.str[str.len-1] == TOK_PLCHLDR) + str.len--; } } } else { diff --git a/tests/tests2/80_macros.c b/tests/tests2/80_macros.c new file mode 100644 index 0000000..20c7f4b --- /dev/null +++ b/tests/tests2/80_macros.c @@ -0,0 +1,36 @@ +#include + +#define A(a,b...) g(a,##b,##b) +#define B(x...) x +#define C \ + +#define D(x,y) x ## y +#define E(x,y,z) x ## y ## z +#define F(x) x +#define G C + +#define STRINGIFY2(x) #x +#define STRINGIFY(x) STRINGIFY2(x) + +int main(void) +{ + printf("%s\n", STRINGIFY()); // should produce the empty string + printf("%s\n", STRINGIFY(C)); // should produce the empty string + printf("%s\n", STRINGIFY( + A(a,) + A(a,b) + A(a,b,c) + )); // should produce g(a ) g(a,b,b)g(a,b,c,b,c) + printf("%s\n", STRINGIFY(B())); // should produce the empty string + printf("%s\n", STRINGIFY(B(C))); // should produce the empty string + printf("%s\n", STRINGIFY(D(,))); // should produce the empty string + printf("%s\n", STRINGIFY(E(1,,))); // should produce 1 + printf("%s\n", STRINGIFY(E(,2,))); // should produce 2 + printf("%s\n", STRINGIFY(E(,,3))); // should produce 3 + printf("%s\n", STRINGIFY(E(1,2,3))); // should produce 123 + + // should produce g(a ) g(a )g(a )g(a )g(a ) + printf("%s\n", STRINGIFY(A(a,F()) A(a,C) A(a,G) A(a,) A(a))); + + return 0; +} diff --git a/tests/tests2/80_macros.expect b/tests/tests2/80_macros.expect new file mode 100644 index 0000000..7f575a8 --- /dev/null +++ b/tests/tests2/80_macros.expect @@ -0,0 +1,11 @@ + + +g(a ) g(a,b,b)g(a,b,c,b,c) + + + +1 +2 +3 +123 +g(a ) g(a )g(a )g(a )g(a ) diff --git a/tests/tests2/Makefile b/tests/tests2/Makefile index a441674..97c22dc 100644 --- a/tests/tests2/Makefile +++ b/tests/tests2/Makefile @@ -98,7 +98,8 @@ TESTS = \ 76_dollars_in_identifiers.test \ 77_push_pop_macro.test \ 78_vla_label.test \ - 79_vla_continue.test + 79_vla_continue.test \ + 80_macros.test # 34_array_assignment.test -- array assignment is not in C standard