Annotation of src/usr.bin/make/varmodifiers.c, Revision 1.1
1.1 ! espie 1: /* $OpenBSD: var.c,v 1.12 1999/09/28 21:57:04 espie Exp $ */
! 2: /* $NetBSD: var.c,v 1.18 1997/03/18 19:24:46 christos Exp $ */
! 3:
! 4: /*
! 5: * Copyright (c) 1999 Marc Espie.
! 6: *
! 7: * Extensive code changes for the OpenBSD project.
! 8: *
! 9: * Redistribution and use in source and binary forms, with or without
! 10: * modification, are permitted provided that the following conditions
! 11: * are met:
! 12: * 1. Redistributions of source code must retain the above copyright
! 13: * notice, this list of conditions and the following disclaimer.
! 14: * 2. Redistributions in binary form must reproduce the above copyright
! 15: * notice, this list of conditions and the following disclaimer in the
! 16: * documentation and/or other materials provided with the distribution.
! 17: *
! 18: * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS
! 19: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
! 20: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
! 21: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBSD
! 22: * PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
! 23: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
! 24: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
! 25: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
! 26: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
! 27: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
! 28: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
! 29: */
! 30: /*
! 31: * Copyright (c) 1988, 1989, 1990, 1993
! 32: * The Regents of the University of California. All rights reserved.
! 33: * Copyright (c) 1989 by Berkeley Softworks
! 34: * All rights reserved.
! 35: *
! 36: * This code is derived from software contributed to Berkeley by
! 37: * Adam de Boor.
! 38: *
! 39: * Redistribution and use in source and binary forms, with or without
! 40: * modification, are permitted provided that the following conditions
! 41: * are met:
! 42: * 1. Redistributions of source code must retain the above copyright
! 43: * notice, this list of conditions and the following disclaimer.
! 44: * 2. Redistributions in binary form must reproduce the above copyright
! 45: * notice, this list of conditions and the following disclaimer in the
! 46: * documentation and/or other materials provided with the distribution.
! 47: * 3. All advertising materials mentioning features or use of this software
! 48: * must display the following acknowledgement:
! 49: * This product includes software developed by the University of
! 50: * California, Berkeley and its contributors.
! 51: * 4. Neither the name of the University nor the names of its contributors
! 52: * may be used to endorse or promote products derived from this software
! 53: * without specific prior written permission.
! 54: *
! 55: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
! 56: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
! 57: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
! 58: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
! 59: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
! 60: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
! 61: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
! 62: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
! 63: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
! 64: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
! 65: * SUCH DAMAGE.
! 66: */
! 67:
! 68: /* VarModifiers_Apply is mostly a constituent function of Var_Parse. */
! 69:
! 70: #include <ctype.h>
! 71: #ifndef MAKE_BOOTSTRAP
! 72: #include <sys/types.h>
! 73: #include <regex.h>
! 74: #endif
! 75: #include "make.h"
! 76: #include "buf.h"
! 77: #include "varmodifiers.h"
! 78:
! 79: /* Var*Pattern flags */
! 80: #define VAR_SUB_GLOBAL 0x01 /* Apply substitution globally */
! 81: #define VAR_SUB_ONE 0x02 /* Apply substitution to one word */
! 82: #define VAR_SUB_MATCHED 0x04 /* There was a match */
! 83: #define VAR_MATCH_START 0x08 /* Match at start of word */
! 84: #define VAR_MATCH_END 0x10 /* Match at end of word */
! 85:
! 86: typedef struct {
! 87: char *lhs; /* String to match */
! 88: size_t leftLen; /* Length of string */
! 89: char *rhs; /* Replacement string (w/ &'s removed) */
! 90: size_t rightLen; /* Length of replacement */
! 91: int flags;
! 92: } VarPattern;
! 93:
! 94: #ifndef MAKE_BOOTSTRAP
! 95: typedef struct {
! 96: regex_t re;
! 97: int nsub;
! 98: regmatch_t *matches;
! 99: char *replace;
! 100: int flags;
! 101: } VarREPattern;
! 102: #endif
! 103:
! 104: static Boolean VarHead __P((const char *, Boolean, Buffer, void *));
! 105: static Boolean VarTail __P((const char *, Boolean, Buffer, void *));
! 106: static Boolean VarSuffix __P((const char *, Boolean, Buffer, void *));
! 107: static Boolean VarRoot __P((const char *, Boolean, Buffer, void *));
! 108: static Boolean VarMatch __P((const char *, Boolean, Buffer, void *));
! 109: #ifdef SYSVVARSUB
! 110: static Boolean VarSYSVMatch __P((const char *, Boolean, Buffer, void *));
! 111: #endif
! 112: static Boolean VarNoMatch __P((const char *, Boolean, Buffer, void *));
! 113: #ifndef MAKE_BOOTSTRAP
! 114: static void VarREError __P((int, regex_t *, const char *));
! 115: static Boolean VarRESubstitute __P((const char *, Boolean, Buffer, void *));
! 116: #endif
! 117: static Boolean VarSubstitute __P((const char *, Boolean, Buffer, void *));
! 118: static char *VarGetPattern __P((SymTable *, int, char **, int, int *, size_t *,
! 119: VarPattern *));
! 120: static char *VarQuote __P((const char *));
! 121: static char *VarModify __P((const char *, Boolean (*)(const char *, Boolean, Buffer, void *), void *));
! 122: static Boolean VarUppercase __P((const char *, Boolean, Buffer, void *));
! 123: static Boolean VarLowercase __P((const char *, Boolean, Buffer, void *));
! 124:
! 125: /*-
! 126: *-----------------------------------------------------------------------
! 127: * VarUppercase --
! 128: * Place the Upper cased word in the given buffer.
! 129: *
! 130: * Results:
! 131: * TRUE if characters were added to the buffer (a space needs to be
! 132: * added to the buffer before the next word).
! 133: *
! 134: * Side Effects:
! 135: * The word is added to the buffer.
! 136: *
! 137: *-----------------------------------------------------------------------
! 138: */
! 139: static Boolean
! 140: VarUppercase(word, addSpace, buf, dummy)
! 141: const char *word; /* Word to Upper Case */
! 142: Boolean addSpace; /* True if need to add a space to the buffer
! 143: * before sticking in the head */
! 144: Buffer buf; /* Buffer in which to store it */
! 145: void *dummy;
! 146: {
! 147: size_t len = strlen(word);
! 148:
! 149: if (addSpace)
! 150: Buf_AddSpace(buf);
! 151: while (len--)
! 152: Buf_AddChar(buf, toupper(*word++));
! 153: return TRUE;
! 154: }
! 155:
! 156: /*-
! 157: *-----------------------------------------------------------------------
! 158: * VarLowercase --
! 159: * Place the Lower cased word in the given buffer.
! 160: *
! 161: * Results:
! 162: * TRUE if characters were added to the buffer (a space needs to be
! 163: * added to the buffer before the next word).
! 164: *
! 165: * Side Effects:
! 166: * The word is added to the buffer.
! 167: *
! 168: *-----------------------------------------------------------------------
! 169: */
! 170: static Boolean
! 171: VarLowercase(word, addSpace, buf, dummy)
! 172: const char *word; /* Word to Lower Case */
! 173: Boolean addSpace; /* True if need to add a space to the buffer
! 174: * before sticking in the head */
! 175: Buffer buf; /* Buffer in which to store it */
! 176: void *dummy;
! 177: {
! 178: size_t len = strlen(word);
! 179:
! 180: if (addSpace)
! 181: Buf_AddSpace(buf);
! 182: while (len--)
! 183: Buf_AddChar(buf, tolower(*word++));
! 184: return TRUE;
! 185: }
! 186:
! 187: /*-
! 188: *-----------------------------------------------------------------------
! 189: * VarHead --
! 190: * Remove the tail of the given word and place the result in the given
! 191: * buffer.
! 192: *
! 193: * Results:
! 194: * TRUE if characters were added to the buffer (a space needs to be
! 195: * added to the buffer before the next word).
! 196: *
! 197: * Side Effects:
! 198: * The trimmed word is added to the buffer.
! 199: *
! 200: *-----------------------------------------------------------------------
! 201: */
! 202: static Boolean
! 203: VarHead(word, addSpace, buf, dummy)
! 204: const char *word; /* Word to trim */
! 205: Boolean addSpace; /* True if need to add a space to the buffer
! 206: * before sticking in the head */
! 207: Buffer buf; /* Buffer in which to store it */
! 208: void *dummy;
! 209: {
! 210: const char *slash;
! 211:
! 212: slash = strrchr(word, '/');
! 213: if (slash != NULL) {
! 214: if (addSpace)
! 215: Buf_AddSpace(buf);
! 216: Buf_AddInterval(buf, word, slash);
! 217: return TRUE;
! 218: } else {
! 219: /* If no directory part, give . (q.v. the POSIX standard) */
! 220: if (addSpace)
! 221: Buf_AddString(buf, " .");
! 222: else
! 223: Buf_AddChar(buf, '.');
! 224: }
! 225: return(dummy ? TRUE : TRUE);
! 226: }
! 227:
! 228: /*-
! 229: *-----------------------------------------------------------------------
! 230: * VarTail --
! 231: * Remove the head of the given word and place the result in the given
! 232: * buffer.
! 233: *
! 234: * Results:
! 235: * TRUE if characters were added to the buffer (a space needs to be
! 236: * added to the buffer before the next word).
! 237: *
! 238: * Side Effects:
! 239: * The trimmed word is added to the buffer.
! 240: *
! 241: *-----------------------------------------------------------------------
! 242: */
! 243: static Boolean
! 244: VarTail(word, addSpace, buf, dummy)
! 245: const char *word; /* Word to trim */
! 246: Boolean addSpace; /* TRUE if need to stick a space in the
! 247: * buffer before adding the tail */
! 248: Buffer buf; /* Buffer in which to store it */
! 249: void *dummy;
! 250: {
! 251: const char *slash;
! 252:
! 253: if (addSpace)
! 254: Buf_AddSpace(buf);
! 255: slash = strrchr(word, '/');
! 256: if (slash != NULL)
! 257: Buf_AddString(buf, slash+1);
! 258: else
! 259: Buf_AddString(buf, word);
! 260: return (dummy ? TRUE : TRUE);
! 261: }
! 262:
! 263: /*-
! 264: *-----------------------------------------------------------------------
! 265: * VarSuffix --
! 266: * Place the suffix of the given word in the given buffer.
! 267: *
! 268: * Results:
! 269: * TRUE if characters were added to the buffer (a space needs to be
! 270: * added to the buffer before the next word).
! 271: *
! 272: * Side Effects:
! 273: * The suffix from the word is placed in the buffer.
! 274: *
! 275: *-----------------------------------------------------------------------
! 276: */
! 277: static Boolean
! 278: VarSuffix(word, addSpace, buf, dummy)
! 279: const char *word; /* Word to trim */
! 280: Boolean addSpace; /* TRUE if need to add a space before placing
! 281: * the suffix in the buffer */
! 282: Buffer buf; /* Buffer in which to store it */
! 283: void *dummy;
! 284: {
! 285: const char *dot;
! 286:
! 287: dot = strrchr(word, '.');
! 288: if (dot != NULL) {
! 289: if (addSpace)
! 290: Buf_AddSpace(buf);
! 291: Buf_AddString(buf, dot+1);
! 292: addSpace = TRUE;
! 293: }
! 294: return (dummy ? addSpace : addSpace);
! 295: }
! 296:
! 297: /*-
! 298: *-----------------------------------------------------------------------
! 299: * VarRoot --
! 300: * Remove the suffix of the given word and place the result in the
! 301: * buffer.
! 302: *
! 303: * Results:
! 304: * TRUE if characters were added to the buffer (a space needs to be
! 305: * added to the buffer before the next word).
! 306: *
! 307: * Side Effects:
! 308: * The trimmed word is added to the buffer.
! 309: *
! 310: *-----------------------------------------------------------------------
! 311: */
! 312: static Boolean
! 313: VarRoot(word, addSpace, buf, dummy)
! 314: const char *word; /* Word to trim */
! 315: Boolean addSpace; /* TRUE if need to add a space to the buffer
! 316: * before placing the root in it */
! 317: Buffer buf; /* Buffer in which to store it */
! 318: void *dummy;
! 319: {
! 320: const char *dot;
! 321:
! 322: if (addSpace)
! 323: Buf_AddSpace(buf);
! 324:
! 325: dot = strrchr(word, '.');
! 326: if (dot != NULL)
! 327: Buf_AddInterval(buf, word, dot);
! 328: else
! 329: Buf_AddString(buf, word);
! 330: return (dummy ? TRUE : TRUE);
! 331: }
! 332:
! 333: /*-
! 334: *-----------------------------------------------------------------------
! 335: * VarMatch --
! 336: * Place the word in the buffer if it matches the given pattern.
! 337: * Callback function for VarModify to implement the :M modifier.
! 338: *
! 339: * Results:
! 340: * TRUE if a space should be placed in the buffer before the next
! 341: * word.
! 342: *
! 343: * Side Effects:
! 344: * The word may be copied to the buffer.
! 345: *
! 346: *-----------------------------------------------------------------------
! 347: */
! 348: static Boolean
! 349: VarMatch(word, addSpace, buf, pattern)
! 350: const char *word; /* Word to examine */
! 351: Boolean addSpace; /* TRUE if need to add a space to the
! 352: * buffer before adding the word, if it
! 353: * matches */
! 354: Buffer buf; /* Buffer in which to store it */
! 355: void *pattern; /* Pattern the word must match */
! 356: {
! 357: if (Str_Match(word, (char *) pattern)) {
! 358: if (addSpace)
! 359: Buf_AddSpace(buf);
! 360: addSpace = TRUE;
! 361: Buf_AddString(buf, word);
! 362: }
! 363: return addSpace;
! 364: }
! 365:
! 366: #ifdef SYSVVARSUB
! 367: /*-
! 368: *-----------------------------------------------------------------------
! 369: * VarSYSVMatch --
! 370: * Place the word in the buffer if it matches the given pattern.
! 371: * Callback function for VarModify to implement the System V %
! 372: * modifiers.
! 373: *
! 374: * Results:
! 375: * TRUE if a space should be placed in the buffer before the next
! 376: * word.
! 377: *
! 378: * Side Effects:
! 379: * The word may be copied to the buffer.
! 380: *
! 381: *-----------------------------------------------------------------------
! 382: */
! 383: static Boolean
! 384: VarSYSVMatch(word, addSpace, buf, patp)
! 385: const char *word; /* Word to examine */
! 386: Boolean addSpace; /* TRUE if need to add a space to the
! 387: * buffer before adding the word, if it
! 388: * matches */
! 389: Buffer buf; /* Buffer in which to store it */
! 390: void *patp; /* Pattern the word must match */
! 391: {
! 392: size_t len;
! 393: const char *ptr;
! 394: VarPattern *pat = (VarPattern *) patp;
! 395:
! 396: if (*word) {
! 397: if (addSpace)
! 398: Buf_AddSpace(buf);
! 399:
! 400: addSpace = TRUE;
! 401:
! 402: if ((ptr = Str_SYSVMatch(word, pat->lhs, &len)) != NULL)
! 403: Str_SYSVSubst(buf, pat->rhs, ptr, len);
! 404: else
! 405: Buf_AddString(buf, word);
! 406: }
! 407: return addSpace;
! 408: }
! 409: #endif
! 410:
! 411: /*-
! 412: *-----------------------------------------------------------------------
! 413: * VarNoMatch --
! 414: * Place the word in the buffer if it doesn't match the given pattern.
! 415: * Callback function for VarModify to implement the :N modifier.
! 416: *
! 417: * Results:
! 418: * TRUE if a space should be placed in the buffer before the next
! 419: * word.
! 420: *
! 421: * Side Effects:
! 422: * The word may be copied to the buffer.
! 423: *
! 424: *-----------------------------------------------------------------------
! 425: */
! 426: static Boolean
! 427: VarNoMatch(word, addSpace, buf, pattern)
! 428: const char *word; /* Word to examine */
! 429: Boolean addSpace; /* TRUE if need to add a space to the
! 430: * buffer before adding the word, if it
! 431: * matches */
! 432: Buffer buf; /* Buffer in which to store it */
! 433: void *pattern; /* Pattern the word must match */
! 434: {
! 435: if (!Str_Match(word, (char *) pattern)) {
! 436: if (addSpace)
! 437: Buf_AddSpace(buf);
! 438: addSpace = TRUE;
! 439: Buf_AddString(buf, word);
! 440: }
! 441: return(addSpace);
! 442: }
! 443:
! 444:
! 445: /*-
! 446: *-----------------------------------------------------------------------
! 447: * VarSubstitute --
! 448: * Perform a string-substitution on the given word, placing the
! 449: * result in the passed buffer.
! 450: *
! 451: * Results:
! 452: * TRUE if a space is needed before more characters are added.
! 453: *
! 454: * Side Effects:
! 455: * None.
! 456: *
! 457: *-----------------------------------------------------------------------
! 458: */
! 459: static Boolean
! 460: VarSubstitute(word, addSpace, buf, patternp)
! 461: const char *word; /* Word to modify */
! 462: Boolean addSpace; /* True if space should be added before
! 463: * other characters */
! 464: Buffer buf; /* Buffer for result */
! 465: void *patternp; /* Pattern for substitution */
! 466: {
! 467: size_t wordLen; /* Length of word */
! 468: const char *cp; /* General pointer */
! 469: VarPattern *pattern = (VarPattern *) patternp;
! 470:
! 471: wordLen = strlen(word);
! 472: if ((pattern->flags & (VAR_SUB_ONE|VAR_SUB_MATCHED)) !=
! 473: (VAR_SUB_ONE|VAR_SUB_MATCHED)) {
! 474: /*
! 475: * Still substituting -- break it down into simple anchored cases
! 476: * and if none of them fits, perform the general substitution case.
! 477: */
! 478: if ((pattern->flags & VAR_MATCH_START) &&
! 479: (strncmp(word, pattern->lhs, pattern->leftLen) == 0)) {
! 480: /*
! 481: * Anchored at start and beginning of word matches pattern
! 482: */
! 483: if ((pattern->flags & VAR_MATCH_END) &&
! 484: (wordLen == pattern->leftLen)) {
! 485: /*
! 486: * Also anchored at end and matches to the end (word
! 487: * is same length as pattern) add space and rhs only
! 488: * if rhs is non-null.
! 489: */
! 490: if (pattern->rightLen != 0) {
! 491: if (addSpace)
! 492: Buf_AddSpace(buf);
! 493: addSpace = TRUE;
! 494: Buf_AddChars(buf, pattern->rightLen, pattern->rhs);
! 495: }
! 496: pattern->flags |= VAR_SUB_MATCHED;
! 497: } else if (pattern->flags & VAR_MATCH_END) {
! 498: /*
! 499: * Doesn't match to end -- copy word wholesale
! 500: */
! 501: goto nosub;
! 502: } else {
! 503: /*
! 504: * Matches at start but need to copy in trailing characters
! 505: */
! 506: if ((pattern->rightLen + wordLen - pattern->leftLen) != 0){
! 507: if (addSpace)
! 508: Buf_AddSpace(buf);
! 509: addSpace = TRUE;
! 510: }
! 511: Buf_AddChars(buf, pattern->rightLen, pattern->rhs);
! 512: Buf_AddChars(buf, wordLen - pattern->leftLen,
! 513: word + pattern->leftLen);
! 514: pattern->flags |= VAR_SUB_MATCHED;
! 515: }
! 516: } else if (pattern->flags & VAR_MATCH_START) {
! 517: /*
! 518: * Had to match at start of word and didn't -- copy whole word.
! 519: */
! 520: goto nosub;
! 521: } else if (pattern->flags & VAR_MATCH_END) {
! 522: /*
! 523: * Anchored at end, Find only place match could occur (leftLen
! 524: * characters from the end of the word) and see if it does. Note
! 525: * that because the $ will be left at the end of the lhs, we have
! 526: * to use strncmp.
! 527: */
! 528: cp = word + (wordLen - pattern->leftLen);
! 529: if ((cp >= word) &&
! 530: (strncmp(cp, pattern->lhs, pattern->leftLen) == 0)) {
! 531: /*
! 532: * Match found. If we will place characters in the buffer,
! 533: * add a space before hand as indicated by addSpace, then
! 534: * stuff in the initial, unmatched part of the word followed
! 535: * by the right-hand-side.
! 536: */
! 537: if (((cp - word) + pattern->rightLen) != 0) {
! 538: if (addSpace)
! 539: Buf_AddSpace(buf);
! 540: addSpace = TRUE;
! 541: }
! 542: Buf_AddInterval(buf, word, cp);
! 543: Buf_AddChars(buf, pattern->rightLen, pattern->rhs);
! 544: pattern->flags |= VAR_SUB_MATCHED;
! 545: } else {
! 546: /*
! 547: * Had to match at end and didn't. Copy entire word.
! 548: */
! 549: goto nosub;
! 550: }
! 551: } else {
! 552: /*
! 553: * Pattern is unanchored: search for the pattern in the word using
! 554: * String_FindSubstring, copying unmatched portions and the
! 555: * right-hand-side for each match found, handling non-global
! 556: * substitutions correctly, etc. When the loop is done, any
! 557: * remaining part of the word (word and wordLen are adjusted
! 558: * accordingly through the loop) is copied straight into the
! 559: * buffer.
! 560: * addSpace is set FALSE as soon as a space is added to the
! 561: * buffer.
! 562: */
! 563: register Boolean done;
! 564: size_t origSize;
! 565:
! 566: done = FALSE;
! 567: origSize = Buf_Size(buf);
! 568: while (!done) {
! 569: cp = strstr(word, pattern->lhs);
! 570: if (cp != (char *)NULL) {
! 571: if (addSpace && (((cp - word) + pattern->rightLen) != 0)){
! 572: Buf_AddSpace(buf);
! 573: addSpace = FALSE;
! 574: }
! 575: Buf_AddInterval(buf, word, cp);
! 576: Buf_AddChars(buf, pattern->rightLen, pattern->rhs);
! 577: wordLen -= (cp - word) + pattern->leftLen;
! 578: word = cp + pattern->leftLen;
! 579: if (wordLen == 0 || (pattern->flags & VAR_SUB_GLOBAL) == 0){
! 580: done = TRUE;
! 581: }
! 582: pattern->flags |= VAR_SUB_MATCHED;
! 583: } else {
! 584: done = TRUE;
! 585: }
! 586: }
! 587: if (wordLen != 0) {
! 588: if (addSpace)
! 589: Buf_AddSpace(buf);
! 590: Buf_AddChars(buf, wordLen, word);
! 591: }
! 592: /*
! 593: * If added characters to the buffer, need to add a space
! 594: * before we add any more. If we didn't add any, just return
! 595: * the previous value of addSpace.
! 596: */
! 597: return (Buf_Size(buf) != origSize || addSpace);
! 598: }
! 599: return (addSpace);
! 600: }
! 601: nosub:
! 602: if (addSpace)
! 603: Buf_AddSpace(buf);
! 604: Buf_AddChars(buf, wordLen, word);
! 605: return(TRUE);
! 606: }
! 607:
! 608:
! 609: #ifndef MAKE_BOOTSTRAP
! 610: /*-
! 611: *-----------------------------------------------------------------------
! 612: * VarREError --
! 613: * Print the error caused by a regcomp or regexec call.
! 614: *
! 615: * Results:
! 616: * None.
! 617: *
! 618: * Side Effects:
! 619: * An error gets printed.
! 620: *
! 621: *-----------------------------------------------------------------------
! 622: */
! 623: static void
! 624: VarREError(err, pat, str)
! 625: int err;
! 626: regex_t *pat;
! 627: const char *str;
! 628: {
! 629: char *errbuf;
! 630: int errlen;
! 631:
! 632: errlen = regerror(err, pat, 0, 0);
! 633: errbuf = emalloc(errlen);
! 634: regerror(err, pat, errbuf, errlen);
! 635: Error("%s: %s", str, errbuf);
! 636: free(errbuf);
! 637: }
! 638:
! 639: /*-
! 640: *-----------------------------------------------------------------------
! 641: * VarRESubstitute --
! 642: * Perform a regex substitution on the given word, placing the
! 643: * result in the passed buffer.
! 644: *
! 645: * Results:
! 646: * TRUE if a space is needed before more characters are added.
! 647: *
! 648: * Side Effects:
! 649: * None.
! 650: *
! 651: *-----------------------------------------------------------------------
! 652: */
! 653: static Boolean
! 654: VarRESubstitute(word, addSpace, buf, patternp)
! 655: const char *word;
! 656: Boolean addSpace;
! 657: Buffer buf;
! 658: void *patternp;
! 659: {
! 660: VarREPattern *pat;
! 661: int xrv;
! 662: const char *wp;
! 663: char *rp;
! 664: int added;
! 665:
! 666: #define MAYBE_ADD_SPACE() \
! 667: if (addSpace && !added) \
! 668: Buf_AddSpace(buf); \
! 669: added = 1
! 670:
! 671: added = 0;
! 672: wp = word;
! 673: pat = patternp;
! 674:
! 675: if ((pat->flags & (VAR_SUB_ONE|VAR_SUB_MATCHED)) ==
! 676: (VAR_SUB_ONE|VAR_SUB_MATCHED))
! 677: xrv = REG_NOMATCH;
! 678: else {
! 679: tryagain:
! 680: xrv = regexec(&pat->re, wp, pat->nsub, pat->matches, 0);
! 681: }
! 682:
! 683: switch (xrv) {
! 684: case 0:
! 685: pat->flags |= VAR_SUB_MATCHED;
! 686: if (pat->matches[0].rm_so > 0) {
! 687: MAYBE_ADD_SPACE();
! 688: Buf_AddChars(buf, pat->matches[0].rm_so, wp);
! 689: }
! 690:
! 691: for (rp = pat->replace; *rp; rp++) {
! 692: if ((*rp == '\\') && ((rp[1] == '&') || (rp[1] == '\\'))) {
! 693: MAYBE_ADD_SPACE();
! 694: Buf_AddChar(buf, rp[1]);
! 695: rp++;
! 696: }
! 697: else if ((*rp == '&') || ((*rp == '\\') && isdigit(rp[1]))) {
! 698: int n;
! 699: const char *subbuf;
! 700: int sublen;
! 701: char errstr[3];
! 702:
! 703: if (*rp == '&') {
! 704: n = 0;
! 705: errstr[0] = '&';
! 706: errstr[1] = '\0';
! 707: } else {
! 708: n = rp[1] - '0';
! 709: errstr[0] = '\\';
! 710: errstr[1] = rp[1];
! 711: errstr[2] = '\0';
! 712: rp++;
! 713: }
! 714:
! 715: if (n > pat->nsub) {
! 716: Error("No subexpression %s", &errstr[0]);
! 717: subbuf = "";
! 718: sublen = 0;
! 719: } else if ((pat->matches[n].rm_so == -1) &&
! 720: (pat->matches[n].rm_eo == -1)) {
! 721: Error("No match for subexpression %s", &errstr[0]);
! 722: subbuf = "";
! 723: sublen = 0;
! 724: } else {
! 725: subbuf = wp + pat->matches[n].rm_so;
! 726: sublen = pat->matches[n].rm_eo - pat->matches[n].rm_so;
! 727: }
! 728:
! 729: if (sublen > 0) {
! 730: MAYBE_ADD_SPACE();
! 731: Buf_AddChars(buf, sublen, subbuf);
! 732: }
! 733: } else {
! 734: MAYBE_ADD_SPACE();
! 735: Buf_AddChar(buf, *rp);
! 736: }
! 737: }
! 738: wp += pat->matches[0].rm_eo;
! 739: if (pat->flags & VAR_SUB_GLOBAL)
! 740: goto tryagain;
! 741: if (*wp) {
! 742: MAYBE_ADD_SPACE();
! 743: Buf_AddChars(buf, strlen(wp), wp);
! 744: }
! 745: break;
! 746: default:
! 747: VarREError(xrv, &pat->re, "Unexpected regex error");
! 748: /* fall through */
! 749: case REG_NOMATCH:
! 750: if (*wp) {
! 751: MAYBE_ADD_SPACE();
! 752: Buf_AddChars(buf, strlen(wp), wp);
! 753: }
! 754: break;
! 755: }
! 756: return(addSpace||added);
! 757: }
! 758: #endif
! 759:
! 760: /*-
! 761: *-----------------------------------------------------------------------
! 762: * VarModify --
! 763: * Modify each of the words of the passed string using the given
! 764: * function. Used to implement all modifiers.
! 765: *
! 766: * Results:
! 767: * A string of all the words modified appropriately.
! 768: *
! 769: *-----------------------------------------------------------------------
! 770: */
! 771: static char *
! 772: VarModify (str, modProc, datum)
! 773: const char *str; /* String whose words should be trimmed */
! 774: /* Function to use to modify them */
! 775: Boolean (*modProc) __P((const char *, Boolean, Buffer, void *));
! 776: void *datum; /* Datum to pass it */
! 777: {
! 778: BUFFER buf; /* Buffer for the new string */
! 779: Boolean addSpace; /* TRUE if need to add a space to the
! 780: * buffer before adding the trimmed
! 781: * word */
! 782: char **av; /* word list */
! 783: char *as; /* word list memory */
! 784: int ac, i;
! 785:
! 786: Buf_Init(&buf, 0);
! 787: addSpace = FALSE;
! 788:
! 789: av = brk_string(str, &ac, FALSE, &as);
! 790:
! 791: for (i = 0; i < ac; i++)
! 792: addSpace = (*modProc)(av[i], addSpace, &buf, datum);
! 793:
! 794: free(as);
! 795: free(av);
! 796: return Buf_Retrieve(&buf);
! 797: }
! 798:
! 799: /*-
! 800: *-----------------------------------------------------------------------
! 801: * VarGetPattern --
! 802: * Pass through the tstr looking for 1) escaped delimiters,
! 803: * '$'s and backslashes (place the escaped character in
! 804: * uninterpreted) and 2) unescaped $'s that aren't before
! 805: * the delimiter (expand the variable substitution).
! 806: * Return the expanded string or NULL if the delimiter was missing
! 807: * If pattern is specified, handle escaped ampersands, and replace
! 808: * unescaped ampersands with the lhs of the pattern.
! 809: *
! 810: * Results:
! 811: * A string of all the words modified appropriately.
! 812: * If length is specified, return the string length of the buffer
! 813: * If flags is specified and the last character of the pattern is a
! 814: * $ set the VAR_MATCH_END bit of flags.
! 815: *
! 816: * Side Effects:
! 817: * None.
! 818: *-----------------------------------------------------------------------
! 819: */
! 820: static char *
! 821: VarGetPattern(ctxt, err, tstr, delim, flags, length, pattern)
! 822: SymTable *ctxt;
! 823: int err;
! 824: char **tstr;
! 825: int delim;
! 826: int *flags;
! 827: size_t *length;
! 828: VarPattern *pattern;
! 829: {
! 830: char *cp;
! 831: BUFFER buf;
! 832: size_t junk;
! 833:
! 834: Buf_Init(&buf, 0);
! 835: if (length == NULL)
! 836: length = &junk;
! 837:
! 838: #define IS_A_MATCH(cp, delim) \
! 839: ((cp[0] == '\\') && ((cp[1] == delim) || \
! 840: (cp[1] == '\\') || (cp[1] == '$') || (pattern && (cp[1] == '&'))))
! 841:
! 842: /*
! 843: * Skim through until the matching delimiter is found;
! 844: * pick up variable substitutions on the way. Also allow
! 845: * backslashes to quote the delimiter, $, and \, but don't
! 846: * touch other backslashes.
! 847: */
! 848: for (cp = *tstr; *cp && (*cp != delim); cp++) {
! 849: if (IS_A_MATCH(cp, delim)) {
! 850: Buf_AddChar(&buf, cp[1]);
! 851: cp++;
! 852: } else if (*cp == '$') {
! 853: if (cp[1] == delim) {
! 854: if (flags == NULL)
! 855: Buf_AddChar(&buf, *cp);
! 856: else
! 857: /*
! 858: * Unescaped $ at end of pattern => anchor
! 859: * pattern at end.
! 860: */
! 861: *flags |= VAR_MATCH_END;
! 862: }
! 863: else {
! 864: char *cp2;
! 865: size_t len;
! 866: Boolean freeIt;
! 867:
! 868: /*
! 869: * If unescaped dollar sign not before the
! 870: * delimiter, assume it's a variable
! 871: * substitution and recurse.
! 872: */
! 873: cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt);
! 874: Buf_AddString(&buf, cp2);
! 875: if (freeIt)
! 876: free(cp2);
! 877: cp += len - 1;
! 878: }
! 879: }
! 880: else if (pattern && *cp == '&')
! 881: Buf_AddChars(&buf, pattern->leftLen, pattern->lhs);
! 882: else
! 883: Buf_AddChar(&buf, *cp);
! 884: }
! 885:
! 886: if (*cp != delim) {
! 887: *tstr = cp;
! 888: *length = 0;
! 889: return NULL;
! 890: }
! 891: else {
! 892: *tstr = ++cp;
! 893: *length = Buf_Size(&buf);
! 894: return Buf_Retrieve(&buf);
! 895: }
! 896: }
! 897:
! 898: char *
! 899: VarModifiers_Apply(str, ctxt, err, freePtr, start, endc, lengthPtr)
! 900: char *str;
! 901: SymTable *ctxt;
! 902: Boolean err;
! 903: Boolean *freePtr;
! 904: char *start;
! 905: char endc;
! 906: size_t *lengthPtr;
! 907: {
! 908: char *tstr;
! 909: char delim;
! 910: char *cp;
! 911:
! 912: tstr = start;
! 913:
! 914: /*
! 915: * Now we need to apply any modifiers the user wants applied.
! 916: * These are:
! 917: * :M<pattern> words which match the given <pattern>.
! 918: * <pattern> is of the standard file
! 919: * wildcarding form.
! 920: * :S<d><pat1><d><pat2><d>[g]
! 921: * Substitute <pat2> for <pat1> in the value
! 922: * :C<d><pat1><d><pat2><d>[g]
! 923: * Substitute <pat2> for regex <pat1> in the value
! 924: * :H Substitute the head of each word
! 925: * :T Substitute the tail of each word
! 926: * :E Substitute the extension (minus '.') of
! 927: * each word
! 928: * :R Substitute the root of each word
! 929: * (pathname minus the suffix).
! 930: * :lhs=rhs Like :S, but the rhs goes to the end of
! 931: * the invocation.
! 932: */
! 933: while (*tstr != endc) {
! 934: char *newStr; /* New value to return */
! 935: char termc; /* Character which terminated scan */
! 936:
! 937: if (DEBUG(VAR))
! 938: printf("Applying :%c to \"%s\"\n", *tstr, str);
! 939: switch (*tstr) {
! 940: case 'N':
! 941: case 'M':
! 942: {
! 943: for (cp = tstr + 1;
! 944: *cp != '\0' && *cp != ':' && *cp != endc;
! 945: cp++) {
! 946: if (*cp == '\\' && (cp[1] == ':' || cp[1] == endc)){
! 947: cp++;
! 948: }
! 949: }
! 950: termc = *cp;
! 951: *cp = '\0';
! 952: if (*tstr == 'M')
! 953: newStr = VarModify(str, VarMatch, tstr+1);
! 954: else
! 955: newStr = VarModify(str, VarNoMatch, tstr+1);
! 956: break;
! 957: }
! 958: case 'S':
! 959: {
! 960: VarPattern pattern;
! 961:
! 962: pattern.flags = 0;
! 963: delim = tstr[1];
! 964: tstr += 2;
! 965:
! 966: /* If pattern begins with '^', it is anchored to the
! 967: * start of the word -- skip over it and flag pattern. */
! 968: if (*tstr == '^') {
! 969: pattern.flags |= VAR_MATCH_START;
! 970: tstr++;
! 971: }
! 972:
! 973: cp = tstr;
! 974: if ((pattern.lhs = VarGetPattern(ctxt, err, &cp, delim,
! 975: &pattern.flags, &pattern.leftLen, NULL)) == NULL)
! 976: goto cleanup;
! 977:
! 978: if ((pattern.rhs = VarGetPattern(ctxt, err, &cp, delim,
! 979: NULL, &pattern.rightLen, &pattern)) == NULL)
! 980: goto cleanup;
! 981:
! 982: /* Check for global substitution. If 'g' after the final
! 983: * delimiter, substitution is global and is marked that
! 984: * way. */
! 985: for (;; cp++) {
! 986: switch (*cp) {
! 987: case 'g':
! 988: pattern.flags |= VAR_SUB_GLOBAL;
! 989: continue;
! 990: case '1':
! 991: pattern.flags |= VAR_SUB_ONE;
! 992: continue;
! 993: }
! 994: break;
! 995: }
! 996:
! 997: termc = *cp;
! 998: newStr = VarModify(str, VarSubstitute, &pattern);
! 999:
! 1000: /* Free the two strings. */
! 1001: free(pattern.lhs);
! 1002: free(pattern.rhs);
! 1003: break;
! 1004: }
! 1005: #ifndef MAKE_BOOTSTRAP
! 1006: case 'C':
! 1007: {
! 1008: VarREPattern pattern;
! 1009: char *re;
! 1010: int error;
! 1011:
! 1012: pattern.flags = 0;
! 1013: delim = tstr[1];
! 1014: tstr += 2;
! 1015:
! 1016: cp = tstr;
! 1017:
! 1018: if ((re = VarGetPattern(ctxt, err, &cp, delim, NULL,
! 1019: NULL, NULL)) == NULL)
! 1020: goto cleanup;
! 1021:
! 1022: if ((pattern.replace = VarGetPattern(ctxt, err, &cp,
! 1023: delim, NULL, NULL, NULL)) == NULL) {
! 1024: free(re);
! 1025: goto cleanup;
! 1026: }
! 1027:
! 1028: for (;; cp++) {
! 1029: switch (*cp) {
! 1030: case 'g':
! 1031: pattern.flags |= VAR_SUB_GLOBAL;
! 1032: continue;
! 1033: case '1':
! 1034: pattern.flags |= VAR_SUB_ONE;
! 1035: continue;
! 1036: }
! 1037: break;
! 1038: }
! 1039:
! 1040: termc = *cp;
! 1041:
! 1042: error = regcomp(&pattern.re, re, REG_EXTENDED);
! 1043: free(re);
! 1044: if (error) {
! 1045: *lengthPtr = cp - start + 1;
! 1046: VarREError(error, &pattern.re, "RE substitution error");
! 1047: free(pattern.replace);
! 1048: return var_Error;
! 1049: }
! 1050:
! 1051: pattern.nsub = pattern.re.re_nsub + 1;
! 1052: if (pattern.nsub < 1)
! 1053: pattern.nsub = 1;
! 1054: if (pattern.nsub > 10)
! 1055: pattern.nsub = 10;
! 1056: pattern.matches = emalloc(pattern.nsub *
! 1057: sizeof(regmatch_t));
! 1058: newStr = VarModify(str, VarRESubstitute, &pattern);
! 1059: regfree(&pattern.re);
! 1060: free(pattern.replace);
! 1061: free(pattern.matches);
! 1062: break;
! 1063: }
! 1064: #endif
! 1065: case 'Q':
! 1066: if (tstr[1] == endc || tstr[1] == ':') {
! 1067: newStr = VarQuote(str);
! 1068: cp = tstr + 1;
! 1069: termc = *cp;
! 1070: break;
! 1071: }
! 1072: /*FALLTHRU*/
! 1073: case 'T':
! 1074: if (tstr[1] == endc || tstr[1] == ':') {
! 1075: newStr = VarModify(str, VarTail, NULL);
! 1076: cp = tstr + 1;
! 1077: termc = *cp;
! 1078: break;
! 1079: }
! 1080: /*FALLTHRU*/
! 1081: case 'H':
! 1082: if (tstr[1] == endc || tstr[1] == ':') {
! 1083: newStr = VarModify(str, VarHead, NULL);
! 1084: cp = tstr + 1;
! 1085: termc = *cp;
! 1086: break;
! 1087: }
! 1088: /*FALLTHRU*/
! 1089: case 'E':
! 1090: if (tstr[1] == endc || tstr[1] == ':') {
! 1091: newStr = VarModify(str, VarSuffix, NULL);
! 1092: cp = tstr + 1;
! 1093: termc = *cp;
! 1094: break;
! 1095: }
! 1096: /*FALLTHRU*/
! 1097: case 'R':
! 1098: if (tstr[1] == endc || tstr[1] == ':') {
! 1099: newStr = VarModify(str, VarRoot, NULL);
! 1100: cp = tstr + 1;
! 1101: termc = *cp;
! 1102: break;
! 1103: }
! 1104: /*FALLTHRU*/
! 1105: case 'U':
! 1106: if (tstr[1] == endc || tstr[1] == ':') {
! 1107: newStr = VarModify(str, VarUppercase, NULL);
! 1108: cp = tstr + 1;
! 1109: termc = *cp;
! 1110: break;
! 1111: }
! 1112: /*FALLTHRU*/
! 1113: case 'L':
! 1114: if (tstr[1] == endc || tstr[1] == ':') {
! 1115: newStr = VarModify(str, VarLowercase, NULL);
! 1116: cp = tstr + 1;
! 1117: termc = *cp;
! 1118: break;
! 1119: }
! 1120: /*FALLTHRU*/
! 1121: #ifdef SUNSHCMD
! 1122: case 's':
! 1123: if (tstr[1] == 'h' && (tstr[2] == endc || tstr[2] == ':')) {
! 1124: char *err;
! 1125: newStr = Cmd_Exec(str, &err);
! 1126: if (err)
! 1127: Error(err, str);
! 1128: cp = tstr + 2;
! 1129: termc = *cp;
! 1130: break;
! 1131: }
! 1132: /*FALLTHRU*/
! 1133: #endif
! 1134: default:
! 1135: {
! 1136: #ifdef SYSVVARSUB
! 1137: /* This can either be a bogus modifier or a System-V
! 1138: * substitution command. */
! 1139: VarPattern pattern;
! 1140: Boolean eqFound;
! 1141: int cnt; /* Used to count brace pairs when
! 1142: * variable in in parens or braces */
! 1143: char startc;
! 1144:
! 1145: if (endc == ')')
! 1146: startc = '(';
! 1147: else
! 1148: startc = '{';
! 1149:
! 1150: pattern.flags = 0;
! 1151: eqFound = FALSE;
! 1152: /* First we make a pass through the string trying
! 1153: * to verify it is a SYSV-make-style translation:
! 1154: * it must be: <string1>=<string2>) */
! 1155: cp = tstr;
! 1156: cnt = 1;
! 1157: while (*cp != '\0' && cnt) {
! 1158: if (*cp == '=') {
! 1159: eqFound = TRUE;
! 1160: /* continue looking for endc */
! 1161: }
! 1162: else if (*cp == endc)
! 1163: cnt--;
! 1164: else if (*cp == startc)
! 1165: cnt++;
! 1166: if (cnt)
! 1167: cp++;
! 1168: }
! 1169: if (*cp == endc && eqFound) {
! 1170:
! 1171: /* Now we break this sucker into the lhs and
! 1172: * rhs. We must null terminate them of course. */
! 1173: for (cp = tstr; *cp != '='; cp++)
! 1174: continue;
! 1175: pattern.lhs = tstr;
! 1176: pattern.leftLen = cp - tstr;
! 1177: *cp++ = '\0';
! 1178:
! 1179: pattern.rhs = cp;
! 1180: cnt = 1;
! 1181: while (cnt) {
! 1182: if (*cp == endc)
! 1183: cnt--;
! 1184: else if (*cp == startc)
! 1185: cnt++;
! 1186: if (cnt)
! 1187: cp++;
! 1188: }
! 1189: pattern.rightLen = cp - pattern.rhs;
! 1190: *cp = '\0';
! 1191:
! 1192: /* SYSV modifications happen through the whole
! 1193: * string. Note the pattern is anchored at the end. */
! 1194: newStr = VarModify(str, VarSYSVMatch, &pattern);
! 1195:
! 1196: /* Restore the nulled characters */
! 1197: pattern.lhs[pattern.leftLen] = '=';
! 1198: pattern.rhs[pattern.rightLen] = endc;
! 1199: termc = endc;
! 1200: } else
! 1201: #endif
! 1202: {
! 1203: Error ("Unknown modifier '%c'\n", *tstr);
! 1204: for (cp = tstr+1;
! 1205: *cp != ':' && *cp != endc && *cp != '\0';)
! 1206: cp++;
! 1207: termc = *cp;
! 1208: newStr = var_Error;
! 1209: }
! 1210: }
! 1211: }
! 1212: if (DEBUG(VAR))
! 1213: printf("Result is \"%s\"\n", newStr);
! 1214:
! 1215: if (*freePtr)
! 1216: free(str);
! 1217: str = newStr;
! 1218: if (str != var_Error)
! 1219: *freePtr = TRUE;
! 1220: else
! 1221: *freePtr = FALSE;
! 1222: if (termc == '\0')
! 1223: Error("Unclosed variable specification");
! 1224: else if (termc == ':')
! 1225: *cp++ = termc;
! 1226: else
! 1227: *cp = termc;
! 1228: tstr = cp;
! 1229: }
! 1230: *lengthPtr += tstr - start+1;
! 1231: return str;
! 1232:
! 1233: cleanup:
! 1234: *lengthPtr += cp - start +1;
! 1235: if (*freePtr)
! 1236: free(str);
! 1237: Error("Unclosed substitution for (%c missing)", delim);
! 1238: return var_Error;
! 1239: }
! 1240:
! 1241: /*-
! 1242: *-----------------------------------------------------------------------
! 1243: * VarQuote --
! 1244: * Quote shell meta-characters in the string
! 1245: *
! 1246: * Results:
! 1247: * The quoted string
! 1248: *
! 1249: *-----------------------------------------------------------------------
! 1250: */
! 1251: static char *
! 1252: VarQuote(str)
! 1253: const char *str;
! 1254: {
! 1255:
! 1256: BUFFER buf;
! 1257: /* This should cover most shells :-( */
! 1258: static char meta[] = "\n \t'`\";&<>()|*?{}[]\\$!#^~";
! 1259:
! 1260: Buf_Init(&buf, MAKE_BSIZE);
! 1261: for (; *str; str++) {
! 1262: if (strchr(meta, *str) != NULL)
! 1263: Buf_AddChar(&buf, '\\');
! 1264: Buf_AddChar(&buf, *str);
! 1265: }
! 1266: return Buf_Retrieve(&buf);
! 1267: }
! 1268: /*-
! 1269: *-----------------------------------------------------------------------
! 1270: * Var_GetHead --
! 1271: * Find the leading components of a (list of) filename(s).
! 1272: * XXX: VarHead does not replace foo by ., as (sun) System V make
! 1273: * does.
! 1274: *
! 1275: * Results:
! 1276: * The leading components.
! 1277: *
! 1278: * Side Effects:
! 1279: * None.
! 1280: *
! 1281: *-----------------------------------------------------------------------
! 1282: */
! 1283: char *
! 1284: Var_GetHead(file)
! 1285: char *file; /* Filename to manipulate */
! 1286: {
! 1287: return VarModify(file, VarHead, NULL);
! 1288: }
! 1289:
! 1290: /*-
! 1291: *-----------------------------------------------------------------------
! 1292: * Var_GetTail --
! 1293: * Return the tail from each of a list of words. Used to set the
! 1294: * System V local variables.
! 1295: *
! 1296: * Results:
! 1297: * The resulting string.
! 1298: *
! 1299: * Side Effects:
! 1300: * None.
! 1301: *
! 1302: *-----------------------------------------------------------------------
! 1303: */
! 1304: char *
! 1305: Var_GetTail(file)
! 1306: char *file; /* Filename to modify */
! 1307: {
! 1308: return VarModify(file, VarTail, NULL);
! 1309: }
! 1310: