Implement new "if... else if... endif" semantics.
authorPaul Smith <psmith@gnu.org>
Fri, 13 May 2005 12:45:30 +0000 (12:45 +0000)
committerPaul Smith <psmith@gnu.org>
Fri, 13 May 2005 12:45:30 +0000 (12:45 +0000)
ChangeLog
NEWS
doc/make.texi
read.c
tests/ChangeLog
tests/scripts/features/conditionals

index 9712532fd50235e771d139df380417e0fe307c29..5bd7bea5611cefc6275d10f197ea3985b6fcd2dd 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+2005-05-13  Paul D. Smith  <psmith@gnu.org>
+
+       Implement "if... else if... endif" syntax.
+
+       * read.c (eval): Push all checks for conditional words ("ifeq",
+       "else", etc.) down into the conditional_line() function.
+       (conditional_line): Rework to allow "else if..." clause.  New
+       return value -2 for lines which are not conditionals.  The
+       ignoring flag can now also be 2, which means "already parsed a
+       true branch".  If that value is seen no other branch of this
+       conditional can be considered true.  In the else parsing if there
+       is extra text after the else, invoke conditional_line()
+       recursively to see if it's another conditional.  If not, it's an
+       error.  If so, raise the conditional value to this level instead
+       of creating a new conditional nesting level.  Special check for
+       "else" and "endif", which aren't allowed on the "else" line.
+       * doc/make.texi (Conditional Syntax): Document the new syntax.
+
 2005-05-09  Paul D. Smith  <psmith@gnu.org>
 
        * Makefile.am (EXTRA_make_SOURCES): Add vmsjobs.c
diff --git a/NEWS b/NEWS
index 5ee75acdeb2db622af46030bd6df3956844dbf64..7b863a5a3f48890bd485d3cd47c36c072083a3a1 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,6 @@
 GNU make NEWS                                               -*-indented-text-*-
   History of user-visible changes.
-  3 March 2005
+  13 May 2005
 
 Copyright (C) 2002,2003,2004,2005  Free Software Foundation, Inc.
 See the end for copying conditions.
@@ -34,6 +34,10 @@ Version 3.81beta3
   used to resolve target files.  The default behavior remains as it
   always has: use the modification time of the actual target file only.
 
+* The "else" conditional line can now be followed by any other legal
+  conditional on the same line: this does not increase the depth of the
+  conditional nesting.
+
 * All pattern-specific variables that match a given target are now used
   (previously only the first match was used).
 
index fb40d290b243a22656845a3d2c2a74eecaf12873..4ffefb9e233b65b9fdb7184f10c49e1fd0afc434 100644 (file)
@@ -5723,14 +5723,30 @@ else
 endif
 @end example
 
+or:
+
+@example
+@var{conditional-directive}
+@var{text-if-one-is-true}
+else @var{conditional-directive}
+@var{text-if-true}
+else
+@var{text-if-false}
+endif
+@end example
+
 @noindent
-If the condition is true, @var{text-if-true} is used; otherwise,
-@var{text-if-false} is used instead.  The @var{text-if-false} can be any
-number of lines of text.
+There can be as many ``@code{else} @var{conditional-directive}''
+clauses as necessary.  Once a given condition is true,
+@var{text-if-true} is used and no other clause is used; if no
+condition is true then @var{text-if-false} is used.  The
+@var{text-if-true} and @var{text-if-false} can be any number of lines
+of text.
 
 The syntax of the @var{conditional-directive} is the same whether the
-conditional is simple or complex.  There are four different directives that
-test different conditions.  Here is a table of them:
+conditional is simple or complex; after an @code{else} or not.  There
+are four different directives that test different conditions.  Here is
+a table of them:
 
 @table @code
 @item ifeq (@var{arg1}, @var{arg2})
diff --git a/read.c b/read.c
index 404c80341edf4587da8298a2c5d2ccf749661b8f..40ff986bbba8961f79c12b0d8f5b551c40606c3a 100644 (file)
--- a/read.c
+++ b/read.c
@@ -78,7 +78,9 @@ struct conditionals
   {
     unsigned int if_cmds;      /* Depth of conditional nesting.  */
     unsigned int allocated;    /* Elts allocated in following arrays.  */
-    char *ignoring;            /* Are we ignoring or interepreting?  */
+    char *ignoring;            /* Are we ignoring or interpreting?
+                                   0=interpreting, 1=not yet interpreted,
+                                   2=already interpreted */
     char *seen_else;           /* Have we already seen an `else'?  */
   };
 
@@ -130,7 +132,7 @@ static long readline PARAMS ((struct ebuffer *ebuf));
 static void do_define PARAMS ((char *name, unsigned int namelen,
                                enum variable_origin origin,
                                struct ebuffer *ebuf));
-static int conditional_line PARAMS ((char *line, const struct floc *flocp));
+static int conditional_line PARAMS ((char *line, int len, const struct floc *flocp));
 static void record_files PARAMS ((struct nameseq *filenames, char *pattern, char *pattern_percent,
                        struct dep *deps, unsigned int cmds_started, char *commands,
                        unsigned int commands_idx, int two_colon,
@@ -613,17 +615,17 @@ eval (struct ebuffer *ebuf, int set_default)
         ignoring anything, since they control what we will do with
         following lines.  */
 
-      if (!in_ignored_define
-         && (word1eq ("ifdef") || word1eq ("ifndef")
-             || word1eq ("ifeq") || word1eq ("ifneq")
-             || word1eq ("else") || word1eq ("endif")))
+      if (!in_ignored_define)
        {
-         int i = conditional_line (p, fstart);
-         if (i < 0)
-           fatal (fstart, _("invalid syntax in conditional"));
+         int i = conditional_line (p, len, fstart);
+          if (i != -2)
+            {
+              if (i == -1)
+                fatal (fstart, _("invalid syntax in conditional"));
 
-          ignoring = i;
-         continue;
+              ignoring = i;
+              continue;
+            }
        }
 
       if (word1eq ("endef"))
@@ -1420,67 +1422,108 @@ do_define (char *name, unsigned int namelen,
    FILENAME and LINENO are the filename and line number in the
    current makefile.  They are used for error messages.
 
-   Value is -1 if the line is invalid,
+   Value is -2 if the line is not a conditional at all,
+   -1 if the line is an invalid conditional,
    0 if following text should be interpreted,
    1 if following text should be ignored.  */
 
 static int
-conditional_line (char *line, const struct floc *flocp)
+conditional_line (char *line, int len, const struct floc *flocp)
 {
-  int notdef;
   char *cmdname;
-  register unsigned int i;
-
-  if (*line == 'i')
-    {
-      /* It's an "if..." command.  */
-      notdef = line[2] == 'n';
-      if (notdef)
-       {
-         cmdname = line[3] == 'd' ? "ifndef" : "ifneq";
-         line += cmdname[3] == 'd' ? 7 : 6;
-       }
-      else
-       {
-         cmdname = line[2] == 'd' ? "ifdef" : "ifeq";
-         line += cmdname[2] == 'd' ? 6 : 5;
-       }
-    }
+  enum { c_ifdef, c_ifndef, c_ifeq, c_ifneq, c_else, c_endif } cmdtype;
+  unsigned int i;
+  unsigned int o;
+
+  /* Compare a word, both length and contents. */
+#define        word1eq(s)      (len == sizeof(s)-1 && strneq (s, line, sizeof(s)-1))
+#define        chkword(s, t)   if (word1eq (s)) { cmdtype = (t); cmdname = (s); }
+
+  /* Make sure this line is a conditional.  */
+  chkword ("ifdef", c_ifdef)
+  else chkword ("ifndef", c_ifndef)
+  else chkword ("ifeq", c_ifeq)
+  else chkword ("ifneq", c_ifneq)
+  else chkword ("else", c_else)
+  else chkword ("endif", c_endif)
   else
-    {
-      /* It's an "else" or "endif" command.  */
-      notdef = line[1] == 'n';
-      cmdname = notdef ? "endif" : "else";
-      line += notdef ? 5 : 4;
-    }
+    return -2;
+
+  /* Found one: skip past it and any whitespace after it.  */
+  line = next_token (line + len);
 
-  line = next_token (line);
+#define EXTRANEOUS() error (flocp, _("Extraneous text after `%s' directive"), cmdname)
 
-  if (*cmdname == 'e')
+  /* An 'endif' cannot contain extra text, and reduces the if-depth by 1  */
+  if (cmdtype == c_endif)
     {
       if (*line != '\0')
-       error (flocp, _("Extraneous text after `%s' directive"), cmdname);
-      /* "Else" or "endif".  */
-      if (conditionals->if_cmds == 0)
+       EXTRANEOUS ();
+
+      if (!conditionals->if_cmds)
+       fatal (flocp, _("extraneous `%s'"), cmdname);
+
+      --conditionals->if_cmds;
+
+      goto DONE;
+    }
+
+  /* An 'else' statement can either be simple, or it can have another
+     conditional after it.  */
+  if (cmdtype == c_else)
+    {
+      const char *p;
+
+      if (!conditionals->if_cmds)
        fatal (flocp, _("extraneous `%s'"), cmdname);
-      /* NOTDEF indicates an `endif' command.  */
-      if (notdef)
-       --conditionals->if_cmds;
-      else if (conditionals->seen_else[conditionals->if_cmds - 1])
-       fatal (flocp, _("only one `else' per conditional"));
+
+      o = conditionals->if_cmds - 1;
+
+      if (conditionals->seen_else[o])
+        fatal (flocp, _("only one `else' per conditional"));
+
+      /* Change the state of ignorance.  */
+      switch (conditionals->ignoring[o])
+        {
+          case 0:
+            /* We've just been interpreting.  Never do it again.  */
+            conditionals->ignoring[o] = 2;
+            break;
+          case 1:
+            /* We've never interpreted yet.  Maybe this time!  */
+            conditionals->ignoring[o] = 0;
+            break;
+        }
+
+      /* It's a simple 'else'.  */
+      if (*line == '\0')
+        {
+          conditionals->seen_else[o] = 1;
+          goto DONE;
+        }
+
+      /* The 'else' has extra text.  That text must be another conditional
+         and cannot be an 'else' or 'endif'.  */
+
+      /* Find the length of the next word.  */
+      for (p = line+1; *p != '\0' && !isspace ((unsigned char)*p); ++p)
+        ;
+      len = p - line;
+
+      /* If it's 'else' or 'endif' or an illegal conditional, fail.  */
+      if (word1eq("else") || word1eq("endif")
+          || conditional_line (line, len, flocp) < 0)
+       EXTRANEOUS ();
       else
-       {
-         /* Toggle the state of ignorance.  */
-         conditionals->ignoring[conditionals->if_cmds - 1]
-           = !conditionals->ignoring[conditionals->if_cmds - 1];
-         /* Record that we have seen an `else' in this conditional.
-            A second `else' will be erroneous.  */
-         conditionals->seen_else[conditionals->if_cmds - 1] = 1;
-       }
-      for (i = 0; i < conditionals->if_cmds; ++i)
-       if (conditionals->ignoring[i])
-         return 1;
-      return 0;
+        {
+          /* conditional_line() created a new level of conditional.
+             Raise it back to this level.  */
+          if (conditionals->ignoring[o] < 2)
+            conditionals->ignoring[o] = conditionals->ignoring[o+1];
+          --conditionals->if_cmds;
+        }
+
+      goto DONE;
     }
 
   if (conditionals->allocated == 0)
@@ -1490,7 +1533,7 @@ conditional_line (char *line, const struct floc *flocp)
       conditionals->seen_else = (char *) xmalloc (conditionals->allocated);
     }
 
-  ++conditionals->if_cmds;
+  o = conditionals->if_cmds++;
   if (conditionals->if_cmds > conditionals->allocated)
     {
       conditionals->allocated += 5;
@@ -1501,25 +1544,24 @@ conditional_line (char *line, const struct floc *flocp)
     }
 
   /* Record that we have seen an `if...' but no `else' so far.  */
-  conditionals->seen_else[conditionals->if_cmds - 1] = 0;
+  conditionals->seen_else[o] = 0;
 
   /* Search through the stack to see if we're already ignoring.  */
-  for (i = 0; i < conditionals->if_cmds - 1; ++i)
+  for (i = 0; i < o; ++i)
     if (conditionals->ignoring[i])
       {
-       /* We are already ignoring, so just push a level
-          to match the next "else" or "endif", and keep ignoring.
-          We don't want to expand variables in the condition.  */
-       conditionals->ignoring[conditionals->if_cmds - 1] = 1;
+       /* We are already ignoring, so just push a level to match the next
+          "else" or "endif", and keep ignoring.  We don't want to expand
+          variables in the condition.  */
+       conditionals->ignoring[o] = 1;
        return 1;
       }
 
-  if (cmdname[notdef ? 3 : 2] == 'd')
+  if (cmdtype == c_ifdef || cmdtype == c_ifndef)
     {
-      /* "Ifdef" or "ifndef".  */
       char *var;
       struct variable *v;
-      register char *p;
+      char *p;
 
       /* Expand the thing we're looking up, so we can use indirect and
          constructed variable names.  */
@@ -1533,9 +1575,10 @@ conditional_line (char *line, const struct floc *flocp)
        return -1;
 
       var[i] = '\0';
-      v = lookup_variable (var, strlen (var));
-      conditionals->ignoring[conditionals->if_cmds - 1]
-       = (v != 0 && *v->value != '\0') == notdef;
+      v = lookup_variable (var, i);
+
+      conditionals->ignoring[o] =
+        ((v != 0 && *v->value != '\0') == (cmdtype == c_ifndef));
 
       free (var);
     }
@@ -1553,7 +1596,7 @@ conditional_line (char *line, const struct floc *flocp)
       /* Find the end of the first string.  */
       if (termin == ',')
        {
-         register int count = 0;
+         int count = 0;
          for (; *line != '\0'; ++line)
            if (*line == '(')
              ++count;
@@ -1627,13 +1670,13 @@ conditional_line (char *line, const struct floc *flocp)
       *line = '\0';
       line = next_token (++line);
       if (*line != '\0')
-       error (flocp, _("Extraneous text after `%s' directive"), cmdname);
+       EXTRANEOUS ();
 
       s2 = variable_expand (s2);
-      conditionals->ignoring[conditionals->if_cmds - 1]
-       = streq (s1, s2) == notdef;
+      conditionals->ignoring[o] = (streq (s1, s2) == (cmdtype == c_ifneq));
     }
 
+ DONE:
   /* Search through the stack to see if we're ignoring.  */
   for (i = 0; i < conditionals->if_cmds; ++i)
     if (conditionals->ignoring[i])
index 2a19918a52da6a3a8ef5c9d242a7e617ebda8e7a..914a67f98b89698ed13d666505989ca403169f4a 100644 (file)
@@ -1,3 +1,8 @@
+2005-05-13  Paul D. Smith  <psmith@gnu.org>
+
+       * scripts/features/conditionals: Add tests for the new if... else
+       if... endif syntax.
+
 2005-05-03  Paul D. Smith  <psmith@gnu.org>
 
        * scripts/variables/DEFAULT_GOAL: Rename DEFAULT_TARGET to
index 36cba2313f9f539b747aa2ac511d8423c69d5cab..2ece60bbd66d3fa43f177f44c5f87949548f7dc6 100644 (file)
@@ -3,12 +3,7 @@ $description = "Check GNU make conditionals.";
 
 $details = "Attempt various different flavors of GNU make conditionals.";
 
-open(MAKEFILE,"> $makefile");
-
-# The Contents of the MAKEFILE ...
-
-print MAKEFILE <<'EOMAKE';
-objects = foo.obj
+run_make_test('
 arg1 = first
 arg2 = second
 arg3 = third
@@ -22,13 +17,13 @@ else
        @echo arg1 NOT equal arg2
 endif
 
-ifeq '$(arg2)' "$(arg5)"
+ifeq \'$(arg2)\' "$(arg5)"
        @echo arg2 equals arg5
 else
        @echo arg2 NOT equal arg5
 endif
 
-ifneq '$(arg3)' '$(arg4)'
+ifneq \'$(arg3)\' \'$(arg4)\'
        @echo arg3 NOT equal arg4
 else
        @echo arg3 equal arg4
@@ -43,32 +38,18 @@ ifdef arg4
        @echo arg4 is defined
 else
        @echo arg4 is NOT defined
-endif
-
-EOMAKE
-
-close(MAKEFILE);
-
-&run_make_with_options($makefile,"",&get_logfile,0);
-
-$answer = "arg1 NOT equal arg2
+endif',
+              '',
+              'arg1 NOT equal arg2
 arg2 equals arg5
 arg3 NOT equal arg4
 variable is undefined
-arg4 is defined
-";
-
-&compare_output($answer,&get_logfile(1));
+arg4 is defined');
 
 
 # Test expansion of variables inside ifdef.
 
-$makefile2 = &get_tmpfile;
-
-open(MAKEFILE, "> $makefile2");
-
-print MAKEFILE <<'EOF';
-
+run_make_test('
 foo = 1
 
 FOO = foo
@@ -92,15 +73,73 @@ ifdef $(call FUNC,DEF)3
   DEF3 = yes
 endif
 
-all:; @echo DEF=$(DEF) DEF2=$(DEF2) DEF3=$(DEF3)
+all:; @echo DEF=$(DEF) DEF2=$(DEF2) DEF3=$(DEF3)',
+              '',
+              'DEF=yes DEF2=yes DEF3=yes');
+
+
+# Test all the different "else if..." constructs
+
+run_make_test('
+arg1 = first
+arg2 = second
+arg3 = third
+arg4 = cc
+arg5 = fifth
 
-EOF
+result =
 
-close(MAKEFILE)
+ifeq ($(arg1),$(arg2))
+  result += arg1 equals arg2
+else ifeq \'$(arg2)\' "$(arg5)"
+  result += arg2 equals arg5
+else ifneq \'$(arg3)\' \'$(arg3)\'
+  result += arg3 NOT equal arg4
+else ifndef arg5
+  result += variable is undefined
+else ifdef undefined
+  result += arg4 is defined
+else
+  result += success
+endif
+
+
+all: ; @echo $(result)',
+              '',
+              'success');
+
+
+# Test some random "else if..." construct nesting
+
+run_make_test('
+arg1 = first
+arg2 = second
+arg3 = third
+arg4 = cc
+arg5 = second
+
+ifeq ($(arg1),$(arg2))
+  $(info failed 1)
+else ifeq \'$(arg2)\' "$(arg2)"
+  ifdef undefined
+    $(info failed 2)
+  else
+    $(info success)
+  endif
+else ifneq \'$(arg3)\' \'$(arg3)\'
+  $(info failed 3)
+else ifdef arg5
+  $(info failed 4)
+else ifdef undefined
+  $(info failed 5)
+else
+  $(info failed 6)
+endif
 
-&run_make_with_options($makefile2,"",&get_logfile,0);
-$answer = "DEF=yes DEF2=yes DEF3=yes\n";
-&compare_output($answer,&get_logfile(1));
+.PHONY: all
+all: ; @:',
+              '',
+              'success');
 
 
 # This tells the test driver that the perl test script executed properly.