Implement new "if... else if... endif" semantics.

This commit is contained in:
Paul Smith 2005-05-13 12:45:30 +00:00
parent 26d8d00cb7
commit e50e0fdf88
6 changed files with 239 additions and 114 deletions

View File

@ -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

6
NEWS
View File

@ -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).

View 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})

193
read.c
View File

@ -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])

View 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

View 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');
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.