[committed] preprocessor: Support C2X #elifdef, #elifndef

Message ID alpine.DEB.2.22.394.2105112354390.21144@digraph.polyomino.org.uk
State New
Headers show
Series
  • [committed] preprocessor: Support C2X #elifdef, #elifndef
Related show

Commit Message

Joseph Myers May 11, 2021, 11:55 p.m.
C2X adds #elifdef and #elifndef preprocessor directives; these have
also been proposed for C++.  Implement these directives in libcpp
accordingly.

In this implementation, #elifdef and #elifndef are treated as
non-directives for any language version other than c2x and gnu2x (if
the feature is accepted for C++, it can trivially be enabled for
relevant C++ versions).  In strict conformance modes for prior
language versions, this is required, as illustrated by the
c11-elifdef-1.c test added.

Bootstrapped with no regressions for x86_64-pc-linux-gnu.  Applied to 
mainline.

libcpp/
	* include/cpplib.h (struct cpp_options): Add elifdef.
	* init.c (struct lang_flags): Add elifdef.
	(lang_defaults): Update to include elifdef initializers.
	(cpp_set_lang): Set elifdef for pfile based on language.
	* directives.c (STDC2X, ELIFDEF): New macros.
	(EXTENSION): Increase value to 3.
	(DIRECTIVE_TABLE): Add #elifdef and #elifndef.
	(_cpp_handle_directive): Do not treat ELIFDEF directives as
	directives for language versions without the #elifdef feature.
	(do_elif): Handle #elifdef and #elifndef.
	(do_elifdef, do_elifndef): New functions.

gcc/testsuite/
	* gcc.dg/cpp/c11-elifdef-1.c, gcc.dg/cpp/c2x-elifdef-1.c,
	gcc.dg/cpp/c2x-elifdef-2.c: New tests.


-- 
Joseph S. Myers
joseph@codesourcery.com

Patch

diff --git a/gcc/testsuite/gcc.dg/cpp/c11-elifdef-1.c b/gcc/testsuite/gcc.dg/cpp/c11-elifdef-1.c
new file mode 100644
index 00000000000..2d5809a8378
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/cpp/c11-elifdef-1.c
@@ -0,0 +1,16 @@ 
+/* Test #elifdef and #elifndef not in C11.  */
+/* { dg-do preprocess } */
+/* { dg-options "-std=c11 -pedantic-errors" } */
+
+#define A
+#undef B
+
+#if 0
+#elifdef A
+#error "#elifdef A applied"
+#endif
+
+#if 0
+#elifndef B
+#error "#elifndef B applied"
+#endif
diff --git a/gcc/testsuite/gcc.dg/cpp/c2x-elifdef-1.c b/gcc/testsuite/gcc.dg/cpp/c2x-elifdef-1.c
new file mode 100644
index 00000000000..b23e3117daf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/cpp/c2x-elifdef-1.c
@@ -0,0 +1,57 @@ 
+/* Test #elifdef and #elifndef in C2x.  */
+/* { dg-do preprocess } */
+/* { dg-options "-std=c2x -pedantic-errors" } */
+
+#define A
+#undef B
+
+#if 0
+#elifdef A
+#define M1 1
+#endif
+
+#if M1 != 1
+#error "#elifdef A did not apply"
+#endif
+
+#if 0
+#elifdef B
+#error "#elifdef B applied"
+#endif
+
+#if 0
+#elifndef A
+#error "#elifndef A applied"
+#endif
+
+#if 0
+#elifndef B
+#define M2 2
+#endif
+
+#if M2 != 2
+#error "#elifndef B did not apply"
+#endif
+
+#if 0
+#elifdef A
+#else
+#error "#elifdef A did not apply"
+#endif
+
+#if 0
+#elifndef B
+#else
+#error "#elifndef B did not apply"
+#endif
+
+/* As with #elif, the syntax of the new directives is relaxed after a
+   non-skipped group.  */
+
+#if 1
+#elifdef x * y
+#endif
+
+#if 1
+#elifndef !
+#endif
diff --git a/gcc/testsuite/gcc.dg/cpp/c2x-elifdef-2.c b/gcc/testsuite/gcc.dg/cpp/c2x-elifdef-2.c
new file mode 100644
index 00000000000..9132832416d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/cpp/c2x-elifdef-2.c
@@ -0,0 +1,63 @@ 
+/* Test #elifdef and #elifndef in C2x: erroneous usages.  */
+/* { dg-do preprocess } */
+/* { dg-options "-std=c2x -pedantic-errors" } */
+
+#define A
+#undef B
+
+#elifdef A /* { dg-error "#elifdef without #if" } */
+#elifdef B /* { dg-error "#elifdef without #if" } */
+#elifndef A /* { dg-error "#elifndef without #if" } */
+#elifndef B /* { dg-error "#elifndef without #if" } */
+
+#if 1 /* { dg-error "-:began here" } */
+#else
+#elifdef A /* { dg-error "#elifdef after #else" } */
+#endif
+
+#if 1 /* { dg-error "-:began here" } */
+#else
+#elifdef B /* { dg-error "#elifdef after #else" } */
+#endif
+
+#if 1 /* { dg-error "-:began here" } */
+#else
+#elifndef A /* { dg-error "#elifndef after #else" } */
+#endif
+
+#if 1 /* { dg-error "-:began here" } */
+#else
+#elifndef B /* { dg-error "#elifndef after #else" } */
+#endif
+
+#if 0
+#elifdef A = /* { dg-error "extra tokens at end of #elifdef directive" } */
+#endif
+
+#if 0
+#elifdef B = /* { dg-error "extra tokens at end of #elifdef directive" } */
+#endif
+
+#if 0
+#elifndef A = /* { dg-error "extra tokens at end of #elifndef directive" } */
+#endif
+
+#if 0
+#elifndef B = /* { dg-error "extra tokens at end of #elifndef directive" } */
+#endif
+
+#if 0
+#elifdef /* { dg-error "no macro name given in #elifdef directive" } */
+#endif
+
+#if 0
+#elifndef /* { dg-error "no macro name given in #elifndef directive" } */
+#endif
+
+#if 0
+#elifdef , /* { dg-error "macro names must be identifiers" } */
+#endif
+
+#if 0
+#elifndef , /* { dg-error "macro names must be identifiers" } */
+#endif
diff --git a/libcpp/directives.c b/libcpp/directives.c
index 795f93e664b..261a584c550 100644
--- a/libcpp/directives.c
+++ b/libcpp/directives.c
@@ -56,10 +56,12 @@  struct pragma_entry
 
 /* Values for the origin field of struct directive.  KANDR directives
    come from traditional (K&R) C.  STDC89 directives come from the
-   1989 C standard.  EXTENSION directives are extensions.  */
+   1989 C standard.  STDC2X directives come from the C2X standard.  EXTENSION
+   directives are extensions.  */
 #define KANDR		0
 #define STDC89		1
-#define EXTENSION	2
+#define STDC2X		2
+#define EXTENSION	3
 
 /* Values for the flags field of struct directive.  COND indicates a
    conditional; IF_COND an opening conditional.  INCL means to treat
@@ -67,13 +69,17 @@  struct pragma_entry
    means this directive should be handled even if -fpreprocessed is in
    effect (these are the directives with callback hooks).
 
-   EXPAND is set on directives that are always macro-expanded.  */
+   EXPAND is set on directives that are always macro-expanded.
+
+   ELIFDEF is set on directives that are only handled for standards with the
+   #elifdef / #elifndef feature.  */
 #define COND		(1 << 0)
 #define IF_COND		(1 << 1)
 #define INCL		(1 << 2)
 #define IN_I		(1 << 3)
 #define EXPAND		(1 << 4)
 #define DEPRECATED	(1 << 5)
+#define ELIFDEF		(1 << 6)
 
 /* Defines one #-directive, including how to handle it.  */
 typedef void (*directive_handler) (cpp_reader *);
@@ -148,6 +154,8 @@  static void cpp_pop_definition (cpp_reader *, struct def_pragma_macro *);
   D(undef,	T_UNDEF,	KANDR,     IN_I)			\
   D(line,	T_LINE,		KANDR,     EXPAND)			\
   D(elif,	T_ELIF,		STDC89,    COND | EXPAND)		\
+  D(elifdef,	T_ELIFDEF,	STDC2X,    COND | ELIFDEF)		\
+  D(elifndef,	T_ELIFNDEF,	STDC2X,    COND | ELIFDEF)		\
   D(error,	T_ERROR,	STDC89,    0)				\
   D(pragma,	T_PRAGMA,	STDC89,    IN_I)			\
   D(warning,	T_WARNING,	EXTENSION, 0)				\
@@ -437,7 +445,11 @@  _cpp_handle_directive (cpp_reader *pfile, bool indented)
   if (dname->type == CPP_NAME)
     {
       if (dname->val.node.node->is_directive)
-	dir = &dtable[dname->val.node.node->directive_index];
+	{
+	  dir = &dtable[dname->val.node.node->directive_index];
+	  if ((dir->flags & ELIFDEF) && !CPP_OPTION (pfile, elifdef))
+	    dir = 0;
+	}
     }
   /* We do not recognize the # followed by a number extension in
      assembler code.  */
@@ -2079,8 +2091,8 @@  do_else (cpp_reader *pfile)
     }
 }
 
-/* Handle a #elif directive by not changing if_stack either.  See the
-   comment above do_else.  */
+/* Handle a #elif, #elifdef or #elifndef directive by not changing if_stack
+   either.  See the comment above do_else.  */
 static void
 do_elif (cpp_reader *pfile)
 {
@@ -2088,12 +2100,13 @@  do_elif (cpp_reader *pfile)
   struct if_stack *ifs = buffer->if_stack;
 
   if (ifs == NULL)
-    cpp_error (pfile, CPP_DL_ERROR, "#elif without #if");
+    cpp_error (pfile, CPP_DL_ERROR, "#%s without #if", pfile->directive->name);
   else
     {
       if (ifs->type == T_ELSE)
 	{
-	  cpp_error (pfile, CPP_DL_ERROR, "#elif after #else");
+	  cpp_error (pfile, CPP_DL_ERROR, "#%s after #else",
+		     pfile->directive->name);
 	  cpp_error_with_line (pfile, CPP_DL_ERROR, ifs->line, 0,
 			       "the conditional began here");
 	}
@@ -2107,8 +2120,29 @@  do_elif (cpp_reader *pfile)
 	pfile->state.skipping = 1;
       else
 	{
-	  pfile->state.skipping = ! _cpp_parse_expr (pfile, false);
-	  ifs->skip_elses = ! pfile->state.skipping;
+	  if (pfile->directive == &dtable[T_ELIF])
+	    pfile->state.skipping = !_cpp_parse_expr (pfile, false);
+	  else
+	    {
+	      cpp_hashnode *node = lex_macro_node (pfile, false);
+
+	      if (node)
+		{
+		  bool macro_defined = _cpp_defined_macro_p (node);
+		  if (!_cpp_maybe_notify_macro_use (pfile, node,
+						    pfile->directive_line))
+		    /* It wasn't a macro after all.  */
+		    macro_defined = false;
+		  bool skip = (pfile->directive == &dtable[T_ELIFDEF]
+			       ? !macro_defined
+			       : macro_defined);
+		  if (pfile->cb.used)
+		    pfile->cb.used (pfile, pfile->directive_line, node);
+		  check_eol (pfile, false);
+		  pfile->state.skipping = skip;
+		}
+	    }
+	  ifs->skip_elses = !pfile->state.skipping;
 	}
 
       /* Invalidate any controlling macro.  */
@@ -2116,6 +2150,20 @@  do_elif (cpp_reader *pfile)
     }
 }
 
+/* Handle a #elifdef directive.  */
+static void
+do_elifdef (cpp_reader *pfile)
+{
+  do_elif (pfile);
+}
+
+/* Handle a #elifndef directive.  */
+static void
+do_elifndef (cpp_reader *pfile)
+{
+  do_elif (pfile);
+}
+
 /* #endif pops the if stack and resets pfile->state.skipping.  */
 static void
 do_endif (cpp_reader *pfile)
diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h
index 41d75d9e731..2cdaf19362a 100644
--- a/libcpp/include/cpplib.h
+++ b/libcpp/include/cpplib.h
@@ -497,6 +497,9 @@  struct cpp_options
   /* Nonzero for the '::' token.  */
   unsigned char scope;
 
+  /* Nonzero for the '#elifdef' and '#elifndef' directives.  */
+  unsigned char elifdef;
+
   /* Nonzero means tokenize C++20 module directives.  */
   unsigned char module_directives;
 
diff --git a/libcpp/init.c b/libcpp/init.c
index 18a2341c2d0..d5d4e994d2a 100644
--- a/libcpp/init.c
+++ b/libcpp/init.c
@@ -95,34 +95,35 @@  struct lang_flags
   char scope;
   char dfp_constants;
   char size_t_literals;
+  char elifdef;
 };
 
 static const struct lang_flags lang_defaults[] =
-{ /*              c99 c++ xnum xid c11 std digr ulit rlit udlit bincst digsep trig u8chlit vaopt scope dfp szlit */
-  /* GNUC89   */  { 0,  0,  1,  0,  0,  0,  1,   0,   0,   0,    0,     0,     0,   0,      1,   1,     0,   0 },
-  /* GNUC99   */  { 1,  0,  1,  1,  0,  0,  1,   1,   1,   0,    0,     0,     0,   0,      1,   1,     0,   0 },
-  /* GNUC11   */  { 1,  0,  1,  1,  1,  0,  1,   1,   1,   0,    0,     0,     0,   0,      1,   1,     0,   0 },
-  /* GNUC17   */  { 1,  0,  1,  1,  1,  0,  1,   1,   1,   0,    0,     0,     0,   0,      1,   1,     0,   0 },
-  /* GNUC2X   */  { 1,  0,  1,  1,  1,  0,  1,   1,   1,   0,    1,     1,     0,   1,      1,   1,     1,   0 },
-  /* STDC89   */  { 0,  0,  0,  0,  0,  1,  0,   0,   0,   0,    0,     0,     1,   0,      0,   0,     0,   0 },
-  /* STDC94   */  { 0,  0,  0,  0,  0,  1,  1,   0,   0,   0,    0,     0,     1,   0,      0,   0,     0,   0 },
-  /* STDC99   */  { 1,  0,  1,  1,  0,  1,  1,   0,   0,   0,    0,     0,     1,   0,      0,   0,     0,   0 },
-  /* STDC11   */  { 1,  0,  1,  1,  1,  1,  1,   1,   0,   0,    0,     0,     1,   0,      0,   0,     0,   0 },
-  /* STDC17   */  { 1,  0,  1,  1,  1,  1,  1,   1,   0,   0,    0,     0,     1,   0,      0,   0,     0,   0 },
-  /* STDC2X   */  { 1,  0,  1,  1,  1,  1,  1,   1,   0,   0,    1,     1,     1,   1,      0,   1,     1,   0 },
-  /* GNUCXX   */  { 0,  1,  1,  1,  0,  0,  1,   0,   0,   0,    0,     0,     0,   0,      1,   1,     0,   0 },
-  /* CXX98    */  { 0,  1,  0,  1,  0,  1,  1,   0,   0,   0,    0,     0,     1,   0,      0,   1,     0,   0 },
-  /* GNUCXX11 */  { 1,  1,  1,  1,  1,  0,  1,   1,   1,   1,    0,     0,     0,   0,      1,   1,     0,   0 },
-  /* CXX11    */  { 1,  1,  0,  1,  1,  1,  1,   1,   1,   1,    0,     0,     1,   0,      0,   1,     0,   0 },
-  /* GNUCXX14 */  { 1,  1,  1,  1,  1,  0,  1,   1,   1,   1,    1,     1,     0,   0,      1,   1,     0,   0 },
-  /* CXX14    */  { 1,  1,  0,  1,  1,  1,  1,   1,   1,   1,    1,     1,     1,   0,      0,   1,     0,   0 },
-  /* GNUCXX17 */  { 1,  1,  1,  1,  1,  0,  1,   1,   1,   1,    1,     1,     0,   1,      1,   1,     0,   0 },
-  /* CXX17    */  { 1,  1,  1,  1,  1,  1,  1,   1,   1,   1,    1,     1,     0,   1,      0,   1,     0,   0 },
-  /* GNUCXX20 */  { 1,  1,  1,  1,  1,  0,  1,   1,   1,   1,    1,     1,     0,   1,      1,   1,     0,   0 },
-  /* CXX20    */  { 1,  1,  1,  1,  1,  1,  1,   1,   1,   1,    1,     1,     0,   1,      1,   1,     0,   0 },
-  /* GNUCXX23 */  { 1,  1,  1,  1,  1,  0,  1,   1,   1,   1,    1,     1,     0,   1,      1,   1,     0,   1 },
-  /* CXX23    */  { 1,  1,  1,  1,  1,  1,  1,   1,   1,   1,    1,     1,     0,   1,      1,   1,     0,   1 },
-  /* ASM      */  { 0,  0,  1,  0,  0,  0,  0,   0,   0,   0,    0,     0,     0,   0,      0,   0,     0,   0 }
+{ /*              c99 c++ xnum xid c11 std digr ulit rlit udlit bincst digsep trig u8chlit vaopt scope dfp szlit elifdef */
+  /* GNUC89   */  { 0,  0,  1,  0,  0,  0,  1,   0,   0,   0,    0,     0,     0,   0,      1,   1,     0,   0,   0 },
+  /* GNUC99   */  { 1,  0,  1,  1,  0,  0,  1,   1,   1,   0,    0,     0,     0,   0,      1,   1,     0,   0,   0 },
+  /* GNUC11   */  { 1,  0,  1,  1,  1,  0,  1,   1,   1,   0,    0,     0,     0,   0,      1,   1,     0,   0,   0 },
+  /* GNUC17   */  { 1,  0,  1,  1,  1,  0,  1,   1,   1,   0,    0,     0,     0,   0,      1,   1,     0,   0,   0 },
+  /* GNUC2X   */  { 1,  0,  1,  1,  1,  0,  1,   1,   1,   0,    1,     1,     0,   1,      1,   1,     1,   0,   1 },
+  /* STDC89   */  { 0,  0,  0,  0,  0,  1,  0,   0,   0,   0,    0,     0,     1,   0,      0,   0,     0,   0,   0 },
+  /* STDC94   */  { 0,  0,  0,  0,  0,  1,  1,   0,   0,   0,    0,     0,     1,   0,      0,   0,     0,   0,   0 },
+  /* STDC99   */  { 1,  0,  1,  1,  0,  1,  1,   0,   0,   0,    0,     0,     1,   0,      0,   0,     0,   0,   0 },
+  /* STDC11   */  { 1,  0,  1,  1,  1,  1,  1,   1,   0,   0,    0,     0,     1,   0,      0,   0,     0,   0,   0 },
+  /* STDC17   */  { 1,  0,  1,  1,  1,  1,  1,   1,   0,   0,    0,     0,     1,   0,      0,   0,     0,   0,   0 },
+  /* STDC2X   */  { 1,  0,  1,  1,  1,  1,  1,   1,   0,   0,    1,     1,     1,   1,      0,   1,     1,   0,   1 },
+  /* GNUCXX   */  { 0,  1,  1,  1,  0,  0,  1,   0,   0,   0,    0,     0,     0,   0,      1,   1,     0,   0,   0 },
+  /* CXX98    */  { 0,  1,  0,  1,  0,  1,  1,   0,   0,   0,    0,     0,     1,   0,      0,   1,     0,   0,   0 },
+  /* GNUCXX11 */  { 1,  1,  1,  1,  1,  0,  1,   1,   1,   1,    0,     0,     0,   0,      1,   1,     0,   0,   0 },
+  /* CXX11    */  { 1,  1,  0,  1,  1,  1,  1,   1,   1,   1,    0,     0,     1,   0,      0,   1,     0,   0,   0 },
+  /* GNUCXX14 */  { 1,  1,  1,  1,  1,  0,  1,   1,   1,   1,    1,     1,     0,   0,      1,   1,     0,   0,   0 },
+  /* CXX14    */  { 1,  1,  0,  1,  1,  1,  1,   1,   1,   1,    1,     1,     1,   0,      0,   1,     0,   0,   0 },
+  /* GNUCXX17 */  { 1,  1,  1,  1,  1,  0,  1,   1,   1,   1,    1,     1,     0,   1,      1,   1,     0,   0,   0 },
+  /* CXX17    */  { 1,  1,  1,  1,  1,  1,  1,   1,   1,   1,    1,     1,     0,   1,      0,   1,     0,   0,   0 },
+  /* GNUCXX20 */  { 1,  1,  1,  1,  1,  0,  1,   1,   1,   1,    1,     1,     0,   1,      1,   1,     0,   0,   0 },
+  /* CXX20    */  { 1,  1,  1,  1,  1,  1,  1,   1,   1,   1,    1,     1,     0,   1,      1,   1,     0,   0,   0 },
+  /* GNUCXX23 */  { 1,  1,  1,  1,  1,  0,  1,   1,   1,   1,    1,     1,     0,   1,      1,   1,     0,   1,   0 },
+  /* CXX23    */  { 1,  1,  1,  1,  1,  1,  1,   1,   1,   1,    1,     1,     0,   1,      1,   1,     0,   1,   0 },
+  /* ASM      */  { 0,  0,  1,  0,  0,  0,  0,   0,   0,   0,    0,     0,     0,   0,      0,   0,     0,   0,   0 }
 };
 
 /* Sets internal flags correctly for a given language.  */
@@ -151,6 +152,7 @@  cpp_set_lang (cpp_reader *pfile, enum c_lang lang)
   CPP_OPTION (pfile, scope)			 = l->scope;
   CPP_OPTION (pfile, dfp_constants)		 = l->dfp_constants;
   CPP_OPTION (pfile, size_t_literals)		 = l->size_t_literals;
+  CPP_OPTION (pfile, elifdef)			 = l->elifdef;
 }
 
 /* Initialize library global state.  */