diff --git a/ChangeLog b/ChangeLog index 9712532f..5bd7bea5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,21 @@ +2005-05-13 Paul D. Smith + + 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 * Makefile.am (EXTRA_make_SOURCES): Add vmsjobs.c diff --git a/NEWS b/NEWS index 5ee75acd..7b863a5a 100644 --- 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). diff --git a/doc/make.texi b/doc/make.texi index fb40d290..4ffefb9e 100644 --- a/doc/make.texi +++ b/doc/make.texi @@ -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 404c8034..40ff986b 100644 --- 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; + enum { c_ifdef, c_ifndef, c_ifeq, c_ifneq, c_else, c_endif } cmdtype; + unsigned int i; + unsigned int o; - 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; - } - } + /* 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; - line = next_token (line); + /* Found one: skip past it and any whitespace after it. */ + line = next_token (line + len); - if (*cmdname == 'e') +#define EXTRANEOUS() error (flocp, _("Extraneous text after `%s' directive"), cmdname) + + /* 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); - /* 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")); + + --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); + + 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]) diff --git a/tests/ChangeLog b/tests/ChangeLog index 2a19918a..914a67f9 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,8 @@ +2005-05-13 Paul D. Smith + + * scripts/features/conditionals: Add tests for the new if... else + if... endif syntax. + 2005-05-03 Paul D. Smith * scripts/variables/DEFAULT_GOAL: Rename DEFAULT_TARGET to diff --git a/tests/scripts/features/conditionals b/tests/scripts/features/conditionals index 36cba231..2ece60bb 100644 --- a/tests/scripts/features/conditionals +++ b/tests/scripts/features/conditionals @@ -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'); -EOF -close(MAKEFILE) +# Test all the different "else if..." constructs -&run_make_with_options($makefile2,"",&get_logfile,0); -$answer = "DEF=yes DEF2=yes DEF3=yes\n"; -&compare_output($answer,&get_logfile(1)); +run_make_test(' +arg1 = first +arg2 = second +arg3 = third +arg4 = cc +arg5 = fifth + +result = + +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 + +.PHONY: all +all: ; @:', + '', + 'success'); # This tells the test driver that the perl test script executed properly.