Annotation of src/usr.bin/make/cond.c, Revision 1.38
1.38 ! joerg 1: /* $NetBSD: cond.c,v 1.37 2007/02/04 19:23:49 dsl Exp $ */
1.6 christos 2:
1.1 cgd 3: /*
4: * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
1.17 agc 5: * All rights reserved.
6: *
7: * This code is derived from software contributed to Berkeley by
8: * Adam de Boor.
9: *
10: * Redistribution and use in source and binary forms, with or without
11: * modification, are permitted provided that the following conditions
12: * are met:
13: * 1. Redistributions of source code must retain the above copyright
14: * notice, this list of conditions and the following disclaimer.
15: * 2. Redistributions in binary form must reproduce the above copyright
16: * notice, this list of conditions and the following disclaimer in the
17: * documentation and/or other materials provided with the distribution.
18: * 3. Neither the name of the University nor the names of its contributors
19: * may be used to endorse or promote products derived from this software
20: * without specific prior written permission.
21: *
22: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32: * SUCH DAMAGE.
33: */
34:
35: /*
1.1 cgd 36: * Copyright (c) 1988, 1989 by Adam de Boor
37: * Copyright (c) 1989 by Berkeley Softworks
38: * All rights reserved.
39: *
40: * This code is derived from software contributed to Berkeley by
41: * Adam de Boor.
42: *
43: * Redistribution and use in source and binary forms, with or without
44: * modification, are permitted provided that the following conditions
45: * are met:
46: * 1. Redistributions of source code must retain the above copyright
47: * notice, this list of conditions and the following disclaimer.
48: * 2. Redistributions in binary form must reproduce the above copyright
49: * notice, this list of conditions and the following disclaimer in the
50: * documentation and/or other materials provided with the distribution.
51: * 3. All advertising materials mentioning features or use of this software
52: * must display the following acknowledgement:
53: * This product includes software developed by the University of
54: * California, Berkeley and its contributors.
55: * 4. Neither the name of the University nor the names of its contributors
56: * may be used to endorse or promote products derived from this software
57: * without specific prior written permission.
58: *
59: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
60: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
61: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
62: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
63: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
64: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
65: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
66: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
67: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
68: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
69: * SUCH DAMAGE.
70: */
71:
1.24 ross 72: #ifndef MAKE_NATIVE
1.38 ! joerg 73: static char rcsid[] = "$NetBSD: cond.c,v 1.37 2007/02/04 19:23:49 dsl Exp $";
1.9 lukem 74: #else
1.8 christos 75: #include <sys/cdefs.h>
1.1 cgd 76: #ifndef lint
1.6 christos 77: #if 0
1.7 christos 78: static char sccsid[] = "@(#)cond.c 8.2 (Berkeley) 1/2/94";
1.6 christos 79: #else
1.38 ! joerg 80: __RCSID("$NetBSD: cond.c,v 1.37 2007/02/04 19:23:49 dsl Exp $");
1.6 christos 81: #endif
1.1 cgd 82: #endif /* not lint */
1.9 lukem 83: #endif
1.1 cgd 84:
85: /*-
86: * cond.c --
87: * Functions to handle conditionals in a makefile.
88: *
89: * Interface:
90: * Cond_Eval Evaluate the conditional in the passed line.
91: *
92: */
93:
1.4 cgd 94: #include <ctype.h>
1.13 wiz 95:
1.1 cgd 96: #include "make.h"
1.4 cgd 97: #include "hash.h"
98: #include "dir.h"
99: #include "buf.h"
1.1 cgd 100:
101: /*
102: * The parsing of conditional expressions is based on this grammar:
103: * E -> F || E
104: * E -> F
105: * F -> T && F
106: * F -> T
107: * T -> defined(variable)
108: * T -> make(target)
109: * T -> exists(file)
110: * T -> empty(varspec)
111: * T -> target(name)
1.12 christos 112: * T -> commands(name)
1.1 cgd 113: * T -> symbol
114: * T -> $(varspec) op value
115: * T -> $(varspec) == "string"
116: * T -> $(varspec) != "string"
1.23 sjg 117: * T -> "string"
1.1 cgd 118: * T -> ( E )
119: * T -> ! T
120: * op -> == | != | > | < | >= | <=
121: *
122: * 'symbol' is some other symbol to which the default function (condDefProc)
123: * is applied.
124: *
125: * Tokens are scanned from the 'condExpr' string. The scanner (CondToken)
126: * will return And for '&' and '&&', Or for '|' and '||', Not for '!',
127: * LParen for '(', RParen for ')' and will evaluate the other terminal
128: * symbols, using either the default function or the function given in the
129: * terminal, and return the result as either True or False.
130: *
131: * All Non-Terminal functions (CondE, CondF and CondT) return Err on error.
132: */
133: typedef enum {
134: And, Or, Not, True, False, LParen, RParen, EndOfFile, None, Err
135: } Token;
136:
137: /*-
138: * Structures to handle elegantly the different forms of #if's. The
139: * last two fields are stored in condInvert and condDefProc, respectively.
140: */
1.13 wiz 141: static void CondPushBack(Token);
1.16 christos 142: static int CondGetArg(char **, char **, const char *, Boolean);
1.13 wiz 143: static Boolean CondDoDefined(int, char *);
144: static int CondStrMatch(ClientData, ClientData);
145: static Boolean CondDoMake(int, char *);
146: static Boolean CondDoExists(int, char *);
147: static Boolean CondDoTarget(int, char *);
148: static Boolean CondDoCommands(int, char *);
1.19 sjg 149: static char * CondCvtArg(char *, double *);
1.13 wiz 150: static Token CondToken(Boolean);
151: static Token CondT(Boolean);
152: static Token CondF(Boolean);
153: static Token CondE(Boolean);
1.1 cgd 154:
1.36 dsl 155: static const struct If {
1.16 christos 156: const char *form; /* Form of if */
1.1 cgd 157: int formlen; /* Length of form */
158: Boolean doNot; /* TRUE if default function should be negated */
1.13 wiz 159: Boolean (*defProc)(int, char *); /* Default function to apply */
1.1 cgd 160: } ifs[] = {
1.36 dsl 161: { "def", 3, FALSE, CondDoDefined },
162: { "ndef", 4, TRUE, CondDoDefined },
163: { "make", 4, FALSE, CondDoMake },
164: { "nmake", 5, TRUE, CondDoMake },
165: { "", 0, FALSE, CondDoDefined },
1.7 christos 166: { NULL, 0, FALSE, NULL }
1.1 cgd 167: };
168:
169: static Boolean condInvert; /* Invert the default function */
1.13 wiz 170: static Boolean (*condDefProc)(int, char *); /* Default function to apply */
1.1 cgd 171: static char *condExpr; /* The expression to parse */
172: static Token condPushBack=None; /* Single push-back token used in
173: * parsing */
174:
1.37 dsl 175: static unsigned int cond_depth = 0; /* current .if nesting level */
176: static unsigned int cond_min_depth = 0; /* depth at makefile open */
1.1 cgd 177:
1.26 christos 178: static int
179: istoken(const char *str, const char *tok, size_t len)
180: {
181: return strncmp(str, tok, len) == 0 && !isalpha((unsigned char)str[len]);
182: }
183:
1.1 cgd 184: /*-
185: *-----------------------------------------------------------------------
186: * CondPushBack --
187: * Push back the most recent token read. We only need one level of
188: * this, so the thing is just stored in 'condPushback'.
189: *
1.13 wiz 190: * Input:
191: * t Token to push back into the "stream"
192: *
1.1 cgd 193: * Results:
194: * None.
195: *
196: * Side Effects:
197: * condPushback is overwritten.
198: *
199: *-----------------------------------------------------------------------
200: */
201: static void
1.13 wiz 202: CondPushBack(Token t)
1.1 cgd 203: {
204: condPushBack = t;
205: }
206:
207: /*-
208: *-----------------------------------------------------------------------
209: * CondGetArg --
210: * Find the argument of a built-in function.
211: *
1.13 wiz 212: * Input:
213: * parens TRUE if arg should be bounded by parens
214: *
1.1 cgd 215: * Results:
216: * The length of the argument and the address of the argument.
217: *
218: * Side Effects:
219: * The pointer is set to point to the closing parenthesis of the
220: * function call.
221: *
222: *-----------------------------------------------------------------------
223: */
224: static int
1.16 christos 225: CondGetArg(char **linePtr, char **argPtr, const char *func, Boolean parens)
1.1 cgd 226: {
1.13 wiz 227: char *cp;
1.1 cgd 228: int argLen;
1.13 wiz 229: Buffer buf;
1.1 cgd 230:
231: cp = *linePtr;
232: if (parens) {
233: while (*cp != '(' && *cp != '\0') {
234: cp++;
235: }
236: if (*cp == '(') {
237: cp++;
238: }
239: }
240:
241: if (*cp == '\0') {
242: /*
243: * No arguments whatsoever. Because 'make' and 'defined' aren't really
244: * "reserved words", we don't print a message. I think this is better
245: * than hitting the user with a warning message every time s/he uses
246: * the word 'make' or 'defined' at the beginning of a symbol...
247: */
1.30 christos 248: *argPtr = NULL;
1.1 cgd 249: return (0);
250: }
251:
252: while (*cp == ' ' || *cp == '\t') {
253: cp++;
254: }
255:
256: /*
257: * Create a buffer for the argument and start it out at 16 characters
258: * long. Why 16? Why not?
259: */
260: buf = Buf_Init(16);
1.7 christos 261:
1.29 christos 262: while ((strchr(" \t)&|", *cp) == NULL) && (*cp != '\0')) {
1.1 cgd 263: if (*cp == '$') {
264: /*
265: * Parse the variable spec and install it as part of the argument
266: * if it's valid. We tell Var_Parse to complain on an undefined
267: * variable, so we don't do it too. Nor do we return an error,
268: * though perhaps we should...
269: */
270: char *cp2;
271: int len;
1.30 christos 272: void *freeIt;
1.1 cgd 273:
1.30 christos 274: cp2 = Var_Parse(cp, VAR_CMD, TRUE, &len, &freeIt);
1.1 cgd 275: Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2);
1.30 christos 276: if (freeIt)
277: free(freeIt);
1.1 cgd 278: cp += len;
279: } else {
280: Buf_AddByte(buf, (Byte)*cp);
281: cp++;
282: }
283: }
284:
285: Buf_AddByte(buf, (Byte)'\0');
286: *argPtr = (char *)Buf_GetAll(buf, &argLen);
287: Buf_Destroy(buf, FALSE);
288:
289: while (*cp == ' ' || *cp == '\t') {
290: cp++;
291: }
292: if (parens && *cp != ')') {
1.25 christos 293: Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()",
1.1 cgd 294: func);
295: return (0);
296: } else if (parens) {
297: /*
298: * Advance pointer past close parenthesis.
299: */
300: cp++;
301: }
1.7 christos 302:
1.1 cgd 303: *linePtr = cp;
304: return (argLen);
305: }
306:
307: /*-
308: *-----------------------------------------------------------------------
309: * CondDoDefined --
310: * Handle the 'defined' function for conditionals.
311: *
312: * Results:
313: * TRUE if the given variable is defined.
314: *
315: * Side Effects:
316: * None.
317: *
318: *-----------------------------------------------------------------------
319: */
320: static Boolean
1.13 wiz 321: CondDoDefined(int argLen, char *arg)
1.1 cgd 322: {
323: char savec = arg[argLen];
1.5 jtc 324: char *p1;
1.1 cgd 325: Boolean result;
326:
327: arg[argLen] = '\0';
1.29 christos 328: if (Var_Value(arg, VAR_CMD, &p1) != NULL) {
1.1 cgd 329: result = TRUE;
330: } else {
331: result = FALSE;
332: }
1.5 jtc 333: if (p1)
334: free(p1);
1.1 cgd 335: arg[argLen] = savec;
336: return (result);
337: }
338:
339: /*-
340: *-----------------------------------------------------------------------
341: * CondStrMatch --
342: * Front-end for Str_Match so it returns 0 on match and non-zero
343: * on mismatch. Callback function for CondDoMake via Lst_Find
344: *
345: * Results:
346: * 0 if string matches pattern
347: *
348: * Side Effects:
349: * None
350: *
351: *-----------------------------------------------------------------------
352: */
353: static int
1.13 wiz 354: CondStrMatch(ClientData string, ClientData pattern)
1.1 cgd 355: {
1.29 christos 356: return(!Str_Match((char *)string,(char *)pattern));
1.1 cgd 357: }
358:
359: /*-
360: *-----------------------------------------------------------------------
361: * CondDoMake --
362: * Handle the 'make' function for conditionals.
363: *
364: * Results:
365: * TRUE if the given target is being made.
366: *
367: * Side Effects:
368: * None.
369: *
370: *-----------------------------------------------------------------------
371: */
372: static Boolean
1.13 wiz 373: CondDoMake(int argLen, char *arg)
1.1 cgd 374: {
375: char savec = arg[argLen];
376: Boolean result;
377:
378: arg[argLen] = '\0';
1.35 dsl 379: if (Lst_Find(create, arg, CondStrMatch) == NILLNODE) {
1.1 cgd 380: result = FALSE;
381: } else {
382: result = TRUE;
383: }
384: arg[argLen] = savec;
385: return (result);
386: }
387:
388: /*-
389: *-----------------------------------------------------------------------
390: * CondDoExists --
391: * See if the given file exists.
392: *
393: * Results:
394: * TRUE if the file exists and FALSE if it does not.
395: *
396: * Side Effects:
397: * None.
398: *
399: *-----------------------------------------------------------------------
400: */
401: static Boolean
1.13 wiz 402: CondDoExists(int argLen, char *arg)
1.1 cgd 403: {
404: char savec = arg[argLen];
405: Boolean result;
406: char *path;
407:
408: arg[argLen] = '\0';
409: path = Dir_FindFile(arg, dirSearchPath);
1.29 christos 410: if (path != NULL) {
1.1 cgd 411: result = TRUE;
412: free(path);
413: } else {
414: result = FALSE;
415: }
416: arg[argLen] = savec;
1.33 sjg 417: if (DEBUG(COND)) {
1.34 dsl 418: fprintf(debug_file, "exists(%s) result is \"%s\"\n",
1.33 sjg 419: arg, path ? path : "");
420: }
1.1 cgd 421: return (result);
422: }
423:
424: /*-
425: *-----------------------------------------------------------------------
426: * CondDoTarget --
427: * See if the given node exists and is an actual target.
428: *
429: * Results:
430: * TRUE if the node exists as a target and FALSE if it does not.
431: *
432: * Side Effects:
433: * None.
434: *
435: *-----------------------------------------------------------------------
436: */
437: static Boolean
1.13 wiz 438: CondDoTarget(int argLen, char *arg)
1.1 cgd 439: {
440: char savec = arg[argLen];
441: Boolean result;
442: GNode *gn;
443:
444: arg[argLen] = '\0';
445: gn = Targ_FindNode(arg, TARG_NOCREATE);
446: if ((gn != NILGNODE) && !OP_NOP(gn->type)) {
447: result = TRUE;
448: } else {
449: result = FALSE;
450: }
451: arg[argLen] = savec;
452: return (result);
453: }
454:
1.12 christos 455: /*-
456: *-----------------------------------------------------------------------
457: * CondDoCommands --
458: * See if the given node exists and is an actual target with commands
459: * associated with it.
460: *
461: * Results:
462: * TRUE if the node exists as a target and has commands associated with
463: * it and FALSE if it does not.
464: *
465: * Side Effects:
466: * None.
467: *
468: *-----------------------------------------------------------------------
469: */
470: static Boolean
1.13 wiz 471: CondDoCommands(int argLen, char *arg)
1.12 christos 472: {
473: char savec = arg[argLen];
474: Boolean result;
475: GNode *gn;
476:
477: arg[argLen] = '\0';
478: gn = Targ_FindNode(arg, TARG_NOCREATE);
479: if ((gn != NILGNODE) && !OP_NOP(gn->type) && !Lst_IsEmpty(gn->commands)) {
480: result = TRUE;
481: } else {
482: result = FALSE;
483: }
484: arg[argLen] = savec;
485: return (result);
486: }
1.1 cgd 487:
488: /*-
489: *-----------------------------------------------------------------------
490: * CondCvtArg --
491: * Convert the given number into a double. If the number begins
1.4 cgd 492: * with 0x, it is interpreted as a hexadecimal integer
1.1 cgd 493: * and converted to a double from there. All other strings just have
1.4 cgd 494: * strtod called on them.
1.1 cgd 495: *
496: * Results:
1.4 cgd 497: * Sets 'value' to double value of string.
1.19 sjg 498: * Returns NULL if string was fully consumed,
499: * else returns remaining input.
1.1 cgd 500: *
501: * Side Effects:
1.4 cgd 502: * Can change 'value' even if string is not a valid number.
1.7 christos 503: *
1.1 cgd 504: *
505: *-----------------------------------------------------------------------
506: */
1.19 sjg 507: static char *
1.13 wiz 508: CondCvtArg(char *str, double *value)
1.1 cgd 509: {
1.4 cgd 510: if ((*str == '0') && (str[1] == 'x')) {
1.13 wiz 511: long i;
1.1 cgd 512:
1.4 cgd 513: for (str += 2, i = 0; *str; str++) {
514: int x;
515: if (isdigit((unsigned char) *str))
516: x = *str - '0';
517: else if (isxdigit((unsigned char) *str))
518: x = 10 + *str - isupper((unsigned char) *str) ? 'A' : 'a';
519: else
1.19 sjg 520: break;
1.4 cgd 521: i = (i << 4) + x;
1.1 cgd 522: }
1.4 cgd 523: *value = (double) i;
1.19 sjg 524: return *str ? str : NULL;
525: } else {
1.4 cgd 526: char *eptr;
527: *value = strtod(str, &eptr);
1.19 sjg 528: return *eptr ? eptr : NULL;
1.1 cgd 529: }
530: }
531:
532: /*-
533: *-----------------------------------------------------------------------
1.23 sjg 534: * CondGetString --
535: * Get a string from a variable reference or an optionally quoted
536: * string. This is called for the lhs and rhs of string compares.
537: *
538: * Results:
1.30 christos 539: * Sets freeIt if needed,
1.23 sjg 540: * Sets quoted if string was quoted,
541: * Returns NULL on error,
542: * else returns string - absent any quotes.
543: *
544: * Side Effects:
545: * Moves condExpr to end of this token.
546: *
547: *
548: *-----------------------------------------------------------------------
549: */
1.30 christos 550: /* coverity:[+alloc : arg-*2] */
1.23 sjg 551: static char *
1.30 christos 552: CondGetString(Boolean doEval, Boolean *quoted, void **freeIt)
1.23 sjg 553: {
554: Buffer buf;
555: char *cp;
556: char *str;
557: int len;
558: int qt;
559: char *start;
560:
561: buf = Buf_Init(0);
562: str = NULL;
1.30 christos 563: *freeIt = NULL;
1.23 sjg 564: *quoted = qt = *condExpr == '"' ? 1 : 0;
565: if (qt)
566: condExpr++;
567: for (start = condExpr; *condExpr && str == NULL; condExpr++) {
568: switch (*condExpr) {
569: case '\\':
570: if (condExpr[1] != '\0') {
571: condExpr++;
572: Buf_AddByte(buf, (Byte)*condExpr);
573: }
574: break;
575: case '"':
576: if (qt) {
577: condExpr++; /* we don't want the quotes */
578: goto got_str;
579: } else
580: Buf_AddByte(buf, (Byte)*condExpr); /* likely? */
581: break;
582: case ')':
583: case '!':
584: case '=':
585: case '>':
586: case '<':
587: case ' ':
588: case '\t':
589: if (!qt)
590: goto got_str;
591: else
592: Buf_AddByte(buf, (Byte)*condExpr);
593: break;
594: case '$':
595: /* if we are in quotes, then an undefined variable is ok */
596: str = Var_Parse(condExpr, VAR_CMD, (qt ? 0 : doEval),
1.30 christos 597: &len, freeIt);
1.23 sjg 598: if (str == var_Error) {
1.30 christos 599: if (*freeIt) {
600: free(*freeIt);
601: *freeIt = NULL;
602: }
1.23 sjg 603: /*
604: * Even if !doEval, we still report syntax errors, which
605: * is what getting var_Error back with !doEval means.
606: */
607: str = NULL;
608: goto cleanup;
609: }
610: condExpr += len;
611: /*
612: * If the '$' was first char (no quotes), and we are
613: * followed by space, the operator or end of expression,
614: * we are done.
615: */
616: if ((condExpr == start + len) &&
617: (*condExpr == '\0' ||
618: isspace((unsigned char) *condExpr) ||
619: strchr("!=><)", *condExpr))) {
620: goto cleanup;
621: }
622: /*
623: * Nope, we better copy str to buf
624: */
625: for (cp = str; *cp; cp++) {
626: Buf_AddByte(buf, (Byte)*cp);
627: }
1.30 christos 628: if (*freeIt) {
629: free(*freeIt);
630: *freeIt = NULL;
631: }
1.23 sjg 632: str = NULL; /* not finished yet */
633: condExpr--; /* don't skip over next char */
634: break;
635: default:
636: Buf_AddByte(buf, (Byte)*condExpr);
637: break;
638: }
639: }
640: got_str:
641: Buf_AddByte(buf, (Byte)'\0');
642: str = (char *)Buf_GetAll(buf, NULL);
1.30 christos 643: *freeIt = str;
1.23 sjg 644: cleanup:
645: Buf_Destroy(buf, FALSE);
646: return str;
647: }
648:
649: /*-
650: *-----------------------------------------------------------------------
1.1 cgd 651: * CondToken --
652: * Return the next token from the input.
653: *
654: * Results:
655: * A Token for the next lexical token in the stream.
656: *
657: * Side Effects:
658: * condPushback will be set back to None if it is used.
659: *
660: *-----------------------------------------------------------------------
661: */
662: static Token
1.13 wiz 663: CondToken(Boolean doEval)
1.1 cgd 664: {
665: Token t;
666:
667: if (condPushBack == None) {
668: while (*condExpr == ' ' || *condExpr == '\t') {
669: condExpr++;
670: }
671: switch (*condExpr) {
672: case '(':
673: t = LParen;
674: condExpr++;
675: break;
676: case ')':
677: t = RParen;
678: condExpr++;
679: break;
680: case '|':
681: if (condExpr[1] == '|') {
682: condExpr++;
683: }
684: condExpr++;
685: t = Or;
686: break;
687: case '&':
688: if (condExpr[1] == '&') {
689: condExpr++;
690: }
691: condExpr++;
692: t = And;
693: break;
694: case '!':
695: t = Not;
696: condExpr++;
697: break;
1.14 sjg 698: case '#':
1.1 cgd 699: case '\n':
700: case '\0':
701: t = EndOfFile;
702: break;
1.23 sjg 703: case '"':
1.1 cgd 704: case '$': {
705: char *lhs;
706: char *rhs;
707: char *op;
1.30 christos 708: void *lhsFree;
709: void *rhsFree;
1.23 sjg 710: Boolean lhsQuoted;
711: Boolean rhsQuoted;
712:
1.27 lukem 713: rhs = NULL;
1.23 sjg 714: lhsFree = rhsFree = FALSE;
715: lhsQuoted = rhsQuoted = FALSE;
716:
1.1 cgd 717: /*
718: * Parse the variable spec and skip over it, saving its
719: * value in lhs.
720: */
721: t = Err;
1.23 sjg 722: lhs = CondGetString(doEval, &lhsQuoted, &lhsFree);
1.30 christos 723: if (!lhs) {
724: if (lhsFree)
725: free(lhsFree);
1.23 sjg 726: return Err;
1.30 christos 727: }
1.1 cgd 728: /*
729: * Skip whitespace to get to the operator
730: */
1.5 jtc 731: while (isspace((unsigned char) *condExpr))
1.1 cgd 732: condExpr++;
1.4 cgd 733:
1.1 cgd 734: /*
735: * Make sure the operator is a valid one. If it isn't a
736: * known relational operator, pretend we got a
737: * != 0 comparison.
738: */
739: op = condExpr;
740: switch (*condExpr) {
741: case '!':
742: case '=':
743: case '<':
744: case '>':
745: if (condExpr[1] == '=') {
746: condExpr += 2;
747: } else {
748: condExpr += 1;
749: }
750: break;
751: default:
1.16 christos 752: op = UNCONST("!=");
1.23 sjg 753: if (lhsQuoted)
754: rhs = UNCONST("");
755: else
756: rhs = UNCONST("0");
1.1 cgd 757:
758: goto do_compare;
759: }
1.5 jtc 760: while (isspace((unsigned char) *condExpr)) {
1.1 cgd 761: condExpr++;
762: }
763: if (*condExpr == '\0') {
764: Parse_Error(PARSE_WARNING,
765: "Missing right-hand-side of operator");
766: goto error;
767: }
1.23 sjg 768: rhs = CondGetString(doEval, &rhsQuoted, &rhsFree);
1.30 christos 769: if (!rhs) {
770: if (lhsFree)
771: free(lhsFree);
772: if (rhsFree)
773: free(rhsFree);
1.23 sjg 774: return Err;
1.30 christos 775: }
1.1 cgd 776: do_compare:
1.23 sjg 777: if (rhsQuoted || lhsQuoted) {
1.4 cgd 778: do_string_compare:
1.1 cgd 779: if (((*op != '!') && (*op != '=')) || (op[1] != '=')) {
780: Parse_Error(PARSE_WARNING,
781: "String comparison operator should be either == or !=");
782: goto error;
783: }
784:
785: if (DEBUG(COND)) {
1.34 dsl 786: fprintf(debug_file, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n",
1.23 sjg 787: lhs, rhs, op);
1.1 cgd 788: }
789: /*
790: * Null-terminate rhs and perform the comparison.
791: * t is set to the result.
792: */
793: if (*op == '=') {
1.23 sjg 794: t = strcmp(lhs, rhs) ? False : True;
1.1 cgd 795: } else {
1.23 sjg 796: t = strcmp(lhs, rhs) ? True : False;
1.1 cgd 797: }
798: } else {
799: /*
800: * rhs is either a float or an integer. Convert both the
801: * lhs and the rhs to a double and compare the two.
802: */
803: double left, right;
1.23 sjg 804: char *cp;
805:
1.19 sjg 806: if (CondCvtArg(lhs, &left))
1.4 cgd 807: goto do_string_compare;
1.23 sjg 808: if ((cp = CondCvtArg(rhs, &right)) &&
1.19 sjg 809: cp == rhs)
1.23 sjg 810: goto do_string_compare;
1.7 christos 811:
1.1 cgd 812: if (DEBUG(COND)) {
1.34 dsl 813: fprintf(debug_file, "left = %f, right = %f, op = %.2s\n", left,
1.1 cgd 814: right, op);
815: }
816: switch(op[0]) {
817: case '!':
818: if (op[1] != '=') {
819: Parse_Error(PARSE_WARNING,
820: "Unknown operator");
821: goto error;
822: }
823: t = (left != right ? True : False);
824: break;
825: case '=':
826: if (op[1] != '=') {
827: Parse_Error(PARSE_WARNING,
828: "Unknown operator");
829: goto error;
830: }
831: t = (left == right ? True : False);
832: break;
833: case '<':
834: if (op[1] == '=') {
835: t = (left <= right ? True : False);
836: } else {
837: t = (left < right ? True : False);
838: }
839: break;
840: case '>':
841: if (op[1] == '=') {
842: t = (left >= right ? True : False);
843: } else {
844: t = (left > right ? True : False);
845: }
846: break;
847: }
848: }
849: error:
1.23 sjg 850: if (lhsFree)
1.30 christos 851: free(lhsFree);
1.23 sjg 852: if (rhsFree)
1.30 christos 853: free(rhsFree);
1.1 cgd 854: break;
855: }
856: default: {
1.13 wiz 857: Boolean (*evalProc)(int, char *);
1.1 cgd 858: Boolean invert = FALSE;
1.30 christos 859: char *arg = NULL;
860: int arglen = 0;
1.7 christos 861:
1.26 christos 862: if (istoken(condExpr, "defined", 7)) {
1.1 cgd 863: /*
864: * Use CondDoDefined to evaluate the argument and
865: * CondGetArg to extract the argument from the 'function
866: * call'.
867: */
868: evalProc = CondDoDefined;
869: condExpr += 7;
1.25 christos 870: arglen = CondGetArg(&condExpr, &arg, "defined", TRUE);
1.1 cgd 871: if (arglen == 0) {
872: condExpr -= 7;
873: goto use_default;
874: }
1.26 christos 875: } else if (istoken(condExpr, "make", 4)) {
1.1 cgd 876: /*
877: * Use CondDoMake to evaluate the argument and
878: * CondGetArg to extract the argument from the 'function
879: * call'.
880: */
881: evalProc = CondDoMake;
882: condExpr += 4;
1.25 christos 883: arglen = CondGetArg(&condExpr, &arg, "make", TRUE);
1.1 cgd 884: if (arglen == 0) {
885: condExpr -= 4;
886: goto use_default;
887: }
1.26 christos 888: } else if (istoken(condExpr, "exists", 6)) {
1.1 cgd 889: /*
890: * Use CondDoExists to evaluate the argument and
891: * CondGetArg to extract the argument from the
892: * 'function call'.
893: */
894: evalProc = CondDoExists;
895: condExpr += 6;
896: arglen = CondGetArg(&condExpr, &arg, "exists", TRUE);
897: if (arglen == 0) {
898: condExpr -= 6;
899: goto use_default;
900: }
1.26 christos 901: } else if (istoken(condExpr, "empty", 5)) {
1.1 cgd 902: /*
903: * Use Var_Parse to parse the spec in parens and return
904: * True if the resulting string is empty.
905: */
906: int length;
1.30 christos 907: void *freeIt;
1.1 cgd 908: char *val;
909:
910: condExpr += 5;
911:
1.38 ! joerg 912: for (arglen = 0; condExpr[arglen] != '\0'; arglen += 1) {
! 913: if (condExpr[arglen] == '(')
! 914: break;
! 915: if (!isspace((unsigned char)condExpr[arglen]))
! 916: Parse_Error(PARSE_WARNING,
! 917: "Extra characters after \"empty\"");
! 918: }
1.5 jtc 919:
1.1 cgd 920: if (condExpr[arglen] != '\0') {
921: val = Var_Parse(&condExpr[arglen - 1], VAR_CMD,
1.30 christos 922: FALSE, &length, &freeIt);
1.1 cgd 923: if (val == var_Error) {
924: t = Err;
925: } else {
1.7 christos 926: /*
927: * A variable is empty when it just contains
1.4 cgd 928: * spaces... 4/15/92, christos
929: */
930: char *p;
1.5 jtc 931: for (p = val; *p && isspace((unsigned char)*p); p++)
1.4 cgd 932: continue;
933: t = (*p == '\0') ? True : False;
1.1 cgd 934: }
1.30 christos 935: if (freeIt) {
936: free(freeIt);
1.1 cgd 937: }
938: /*
939: * Advance condExpr to beyond the closing ). Note that
940: * we subtract one from arglen + length b/c length
941: * is calculated from condExpr[arglen - 1].
942: */
943: condExpr += arglen + length - 1;
944: } else {
945: condExpr -= 5;
946: goto use_default;
947: }
948: break;
1.26 christos 949: } else if (istoken(condExpr, "target", 6)) {
1.1 cgd 950: /*
951: * Use CondDoTarget to evaluate the argument and
952: * CondGetArg to extract the argument from the
953: * 'function call'.
954: */
955: evalProc = CondDoTarget;
956: condExpr += 6;
957: arglen = CondGetArg(&condExpr, &arg, "target", TRUE);
958: if (arglen == 0) {
959: condExpr -= 6;
1.12 christos 960: goto use_default;
961: }
1.26 christos 962: } else if (istoken(condExpr, "commands", 8)) {
1.12 christos 963: /*
964: * Use CondDoCommands to evaluate the argument and
965: * CondGetArg to extract the argument from the
966: * 'function call'.
967: */
968: evalProc = CondDoCommands;
969: condExpr += 8;
970: arglen = CondGetArg(&condExpr, &arg, "commands", TRUE);
971: if (arglen == 0) {
972: condExpr -= 8;
1.1 cgd 973: goto use_default;
974: }
975: } else {
976: /*
977: * The symbol is itself the argument to the default
978: * function. We advance condExpr to the end of the symbol
979: * by hand (the next whitespace, closing paren or
980: * binary operator) and set to invert the evaluation
981: * function if condInvert is TRUE.
982: */
983: use_default:
984: invert = condInvert;
985: evalProc = condDefProc;
986: arglen = CondGetArg(&condExpr, &arg, "", FALSE);
987: }
988:
989: /*
990: * Evaluate the argument using the set function. If invert
991: * is TRUE, we invert the sense of the function.
992: */
1.31 christos 993: t = (!doEval || (* evalProc) (arglen, arg) ?
994: (invert ? False : True) :
995: (invert ? True : False));
1.30 christos 996: if (arg)
997: free(arg);
1.1 cgd 998: break;
999: }
1000: }
1001: } else {
1002: t = condPushBack;
1003: condPushBack = None;
1004: }
1005: return (t);
1006: }
1007:
1008: /*-
1009: *-----------------------------------------------------------------------
1010: * CondT --
1011: * Parse a single term in the expression. This consists of a terminal
1012: * symbol or Not and a terminal symbol (not including the binary
1013: * operators):
1014: * T -> defined(variable) | make(target) | exists(file) | symbol
1015: * T -> ! T | ( E )
1016: *
1017: * Results:
1018: * True, False or Err.
1019: *
1020: * Side Effects:
1021: * Tokens are consumed.
1022: *
1023: *-----------------------------------------------------------------------
1024: */
1025: static Token
1.13 wiz 1026: CondT(Boolean doEval)
1.1 cgd 1027: {
1028: Token t;
1029:
1030: t = CondToken(doEval);
1031:
1032: if (t == EndOfFile) {
1033: /*
1034: * If we reached the end of the expression, the expression
1035: * is malformed...
1036: */
1037: t = Err;
1038: } else if (t == LParen) {
1039: /*
1040: * T -> ( E )
1041: */
1042: t = CondE(doEval);
1043: if (t != Err) {
1044: if (CondToken(doEval) != RParen) {
1045: t = Err;
1046: }
1047: }
1048: } else if (t == Not) {
1049: t = CondT(doEval);
1050: if (t == True) {
1051: t = False;
1052: } else if (t == False) {
1053: t = True;
1054: }
1055: }
1056: return (t);
1057: }
1058:
1059: /*-
1060: *-----------------------------------------------------------------------
1061: * CondF --
1062: * Parse a conjunctive factor (nice name, wot?)
1063: * F -> T && F | T
1064: *
1065: * Results:
1066: * True, False or Err
1067: *
1068: * Side Effects:
1069: * Tokens are consumed.
1070: *
1071: *-----------------------------------------------------------------------
1072: */
1073: static Token
1.13 wiz 1074: CondF(Boolean doEval)
1.1 cgd 1075: {
1076: Token l, o;
1077:
1078: l = CondT(doEval);
1079: if (l != Err) {
1080: o = CondToken(doEval);
1081:
1082: if (o == And) {
1083: /*
1084: * F -> T && F
1085: *
1086: * If T is False, the whole thing will be False, but we have to
1087: * parse the r.h.s. anyway (to throw it away).
1088: * If T is True, the result is the r.h.s., be it an Err or no.
1089: */
1090: if (l == True) {
1091: l = CondF(doEval);
1092: } else {
1.28 christos 1093: (void)CondF(FALSE);
1.1 cgd 1094: }
1095: } else {
1096: /*
1097: * F -> T
1098: */
1.25 christos 1099: CondPushBack(o);
1.1 cgd 1100: }
1101: }
1102: return (l);
1103: }
1104:
1105: /*-
1106: *-----------------------------------------------------------------------
1107: * CondE --
1108: * Main expression production.
1109: * E -> F || E | F
1110: *
1111: * Results:
1112: * True, False or Err.
1113: *
1114: * Side Effects:
1115: * Tokens are, of course, consumed.
1116: *
1117: *-----------------------------------------------------------------------
1118: */
1119: static Token
1.13 wiz 1120: CondE(Boolean doEval)
1.1 cgd 1121: {
1122: Token l, o;
1123:
1124: l = CondF(doEval);
1125: if (l != Err) {
1126: o = CondToken(doEval);
1127:
1128: if (o == Or) {
1129: /*
1130: * E -> F || E
1131: *
1132: * A similar thing occurs for ||, except that here we make sure
1133: * the l.h.s. is False before we bother to evaluate the r.h.s.
1134: * Once again, if l is False, the result is the r.h.s. and once
1135: * again if l is True, we parse the r.h.s. to throw it away.
1136: */
1137: if (l == False) {
1138: l = CondE(doEval);
1139: } else {
1.28 christos 1140: (void)CondE(FALSE);
1.1 cgd 1141: }
1142: } else {
1143: /*
1144: * E -> F
1145: */
1.25 christos 1146: CondPushBack(o);
1.1 cgd 1147: }
1148: }
1149: return (l);
1150: }
1.10 christos 1151:
1152: /*-
1153: *-----------------------------------------------------------------------
1154: * Cond_EvalExpression --
1155: * Evaluate an expression in the passed line. The expression
1156: * consists of &&, ||, !, make(target), defined(variable)
1157: * and parenthetical groupings thereof.
1158: *
1159: * Results:
1160: * COND_PARSE if the condition was valid grammatically
1161: * COND_INVALID if not a valid conditional.
1162: *
1163: * (*value) is set to the boolean value of the condition
1164: *
1165: * Side Effects:
1166: * None.
1167: *
1168: *-----------------------------------------------------------------------
1169: */
1170: int
1.13 wiz 1171: Cond_EvalExpression(int dosetup, char *line, Boolean *value, int eprint)
1.10 christos 1172: {
1.11 christos 1173: if (dosetup) {
1174: condDefProc = CondDoDefined;
1175: condInvert = 0;
1176: }
1.10 christos 1177:
1.11 christos 1178: while (*line == ' ' || *line == '\t')
1179: line++;
1.10 christos 1180:
1.11 christos 1181: condExpr = line;
1182: condPushBack = None;
1.10 christos 1183:
1.11 christos 1184: switch (CondE(TRUE)) {
1185: case True:
1186: if (CondToken(TRUE) == EndOfFile) {
1187: *value = TRUE;
1188: break;
1189: }
1190: goto err;
1191: /*FALLTHRU*/
1192: case False:
1193: if (CondToken(TRUE) == EndOfFile) {
1194: *value = FALSE;
1195: break;
1196: }
1197: /*FALLTHRU*/
1198: case Err:
1.10 christos 1199: err:
1.11 christos 1200: if (eprint)
1.25 christos 1201: Parse_Error(PARSE_FATAL, "Malformed conditional (%s)",
1.11 christos 1202: line);
1203: return (COND_INVALID);
1204: default:
1205: break;
1206: }
1207:
1208: return COND_PARSE;
1.10 christos 1209: }
1210:
1.1 cgd 1211:
1212: /*-
1213: *-----------------------------------------------------------------------
1214: * Cond_Eval --
1215: * Evaluate the conditional in the passed line. The line
1216: * looks like this:
1.36 dsl 1217: * .<cond-type> <expr>
1.1 cgd 1218: * where <cond-type> is any of if, ifmake, ifnmake, ifdef,
1219: * ifndef, elif, elifmake, elifnmake, elifdef, elifndef
1220: * and <expr> consists of &&, ||, !, make(target), defined(variable)
1221: * and parenthetical groupings thereof.
1222: *
1.13 wiz 1223: * Input:
1224: * line Line to parse
1225: *
1.1 cgd 1226: * Results:
1227: * COND_PARSE if should parse lines after the conditional
1228: * COND_SKIP if should skip lines after the conditional
1229: * COND_INVALID if not a valid conditional.
1230: *
1231: * Side Effects:
1232: * None.
1233: *
1.36 dsl 1234: * Note that the states IF_ACTIVE and ELSE_ACTIVE are only different in order
1235: * to detect splurious .else lines (as are SKIP_TO_ELSE and SKIP_TO_ENDIF)
1236: * otherwise .else could be treated as '.elif 1'.
1237: *
1.1 cgd 1238: *-----------------------------------------------------------------------
1239: */
1.4 cgd 1240: int
1.13 wiz 1241: Cond_Eval(char *line)
1.1 cgd 1242: {
1.36 dsl 1243: #define MAXIF 64 /* maximum depth of .if'ing */
1244: enum if_states {
1245: IF_ACTIVE, /* .if or .elif part active */
1246: ELSE_ACTIVE, /* .else part active */
1247: SEARCH_FOR_ELIF, /* searching for .elif/else to execute */
1248: SKIP_TO_ELSE, /* has been true, but not seen '.else' */
1249: SKIP_TO_ENDIF /* nothing else to execute */
1250: };
1251: static enum if_states cond_state[MAXIF + 1] = { IF_ACTIVE };
1252:
1253: const struct If *ifp;
1254: Boolean isElif;
1255: Boolean value;
1.1 cgd 1256: int level; /* Level at which to report errors. */
1.36 dsl 1257: enum if_states state;
1.1 cgd 1258:
1259: level = PARSE_FATAL;
1260:
1.36 dsl 1261: /* skip leading character (the '.') and any whitespace */
1262: for (line++; *line == ' ' || *line == '\t'; line++)
1.1 cgd 1263: continue;
1264:
1.36 dsl 1265: /* Find what type of if we're dealing with. */
1266: if (line[0] == 'e') {
1267: if (line[1] != 'l') {
1268: if (!istoken(line + 1, "ndif", 4))
1269: return COND_INVALID;
1270: /* End of conditional section */
1.37 dsl 1271: if (cond_depth == cond_min_depth) {
1.36 dsl 1272: Parse_Error(level, "if-less endif");
1273: return COND_PARSE;
1274: }
1275: /* Return state for previous conditional */
1276: cond_depth--;
1277: return cond_state[cond_depth] <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP;
1278: }
1279:
1280: /* Quite likely this is 'else' or 'elif' */
1.1 cgd 1281: line += 2;
1.36 dsl 1282: if (istoken(line, "se", 2)) {
1283: /* It is else... */
1.37 dsl 1284: if (cond_depth == cond_min_depth) {
1.36 dsl 1285: Parse_Error(level, "if-less else");
1286: return COND_INVALID;
1287: }
1288:
1289: state = cond_state[cond_depth];
1290: switch (state) {
1291: case SEARCH_FOR_ELIF:
1292: state = ELSE_ACTIVE;
1293: break;
1294: case ELSE_ACTIVE:
1295: case SKIP_TO_ENDIF:
1296: Parse_Error(PARSE_WARNING, "extra else");
1297: /* FALLTHROUGH */
1298: default:
1299: case IF_ACTIVE:
1300: case SKIP_TO_ELSE:
1301: state = SKIP_TO_ENDIF;
1302: break;
1.1 cgd 1303: }
1.36 dsl 1304: cond_state[cond_depth] = state;
1305: return state <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP;
1.1 cgd 1306: }
1.36 dsl 1307: /* Assume for now it is an elif */
1308: isElif = TRUE;
1309: } else
1310: isElif = FALSE;
1311:
1312: if (line[0] != 'i' || line[1] != 'f')
1313: /* Not an ifxxx or elifxxx line */
1314: return COND_INVALID;
1.7 christos 1315:
1.1 cgd 1316: /*
1317: * Figure out what sort of conditional it is -- what its default
1318: * function is, etc. -- by looking in the table of valid "ifs"
1319: */
1.36 dsl 1320: line += 2;
1321: for (ifp = ifs; ; ifp++) {
1322: if (ifp->form == NULL)
1323: return COND_INVALID;
1.26 christos 1324: if (istoken(ifp->form, line, ifp->formlen)) {
1.36 dsl 1325: line += ifp->formlen;
1.1 cgd 1326: break;
1327: }
1328: }
1329:
1.36 dsl 1330: /* Now we know what sort of 'if' it is... */
1331: state = cond_state[cond_depth];
1332:
1333: if (isElif) {
1.37 dsl 1334: if (cond_depth == cond_min_depth) {
1.36 dsl 1335: Parse_Error(level, "if-less elif");
1336: return COND_INVALID;
1337: }
1338: if (state == SKIP_TO_ENDIF || state == ELSE_ACTIVE)
1339: Parse_Error(PARSE_WARNING, "extra elif");
1340: if (state != SEARCH_FOR_ELIF) {
1341: /* Either just finished the 'true' block, or already SKIP_TO_ELSE */
1342: cond_state[cond_depth] = SKIP_TO_ELSE;
1343: return COND_SKIP;
1.1 cgd 1344: }
1345: } else {
1.36 dsl 1346: if (cond_depth >= MAXIF) {
1347: Parse_Error(PARSE_FATAL, "Too many nested if's. %d max.", MAXIF);
1348: return COND_INVALID;
1.1 cgd 1349: }
1.36 dsl 1350: cond_depth++;
1351: if (state > ELSE_ACTIVE) {
1352: /* If we aren't parsing the data, treat as always false */
1353: cond_state[cond_depth] = SKIP_TO_ELSE;
1354: return COND_SKIP;
1.32 christos 1355: }
1.1 cgd 1356: }
1357:
1.36 dsl 1358: /* Initialize file-global variables for parsing the expression */
1359: condDefProc = ifp->defProc;
1360: condInvert = ifp->doNot;
1361:
1362: /* And evaluate the conditional expresssion */
1363: if (Cond_EvalExpression(0, line, &value, 1) == COND_INVALID) {
1364: /* Although we get make to reprocess the line, set a state */
1365: cond_state[cond_depth] = SEARCH_FOR_ELIF;
1366: return COND_INVALID;
1367: }
1368:
1369: if (!value) {
1370: cond_state[cond_depth] = SEARCH_FOR_ELIF;
1371: return COND_SKIP;
1372: }
1373: cond_state[cond_depth] = IF_ACTIVE;
1374: return COND_PARSE;
1.1 cgd 1375: }
1.10 christos 1376:
1377:
1.1 cgd 1378:
1379: /*-
1380: *-----------------------------------------------------------------------
1381: * Cond_End --
1382: * Make sure everything's clean at the end of a makefile.
1383: *
1384: * Results:
1385: * None.
1386: *
1387: * Side Effects:
1388: * Parse_Error will be called if open conditionals are around.
1389: *
1390: *-----------------------------------------------------------------------
1391: */
1392: void
1.37 dsl 1393: Cond_restore_depth(unsigned int saved_depth)
1.1 cgd 1394: {
1.37 dsl 1395: int open_conds = cond_depth - cond_min_depth;
1396:
1397: if (open_conds != 0 || saved_depth > cond_depth) {
1398: Parse_Error(PARSE_FATAL, "%d open conditional%s", open_conds,
1399: open_conds == 1 ? "" : "s");
1400: cond_depth = cond_min_depth;
1.1 cgd 1401: }
1.37 dsl 1402:
1403: cond_min_depth = saved_depth;
1404: }
1405:
1406: unsigned int
1407: Cond_save_depth(void)
1408: {
1409: int depth = cond_min_depth;
1410:
1411: cond_min_depth = cond_depth;
1412: return depth;
1.1 cgd 1413: }
CVSweb <webmaster@jp.NetBSD.org>