home *** CD-ROM | disk | FTP | other *** search
/ Frozen Fish 2: PC / frozenfish_august_1995.bin / bbs / d09xx / d0975.lha / PCal / pcalutil.c < prev    next >
C/C++ Source or Header  |  1992-02-19  |  21KB  |  981 lines

  1. /*
  2.  * pcalutil.c - utility routines for Pcal
  3.  *
  4.  * Contents:
  5.  *
  6.  *        alloc
  7.  *        calc_day
  8.  *        calc_weekday
  9.  *        calc_year_day
  10.  *        ci_strcmp
  11.  *        ci_strncmp
  12.  *        copy_text
  13.  *        cvt_escape
  14.  *        find_executable
  15.  *        getline
  16.  *        is_valid
  17.  *        loadwords
  18.  *        mk_filespec
  19.  *        mk_path
  20.  *        normalize
  21.  *        note_box
  22.  *        note_day
  23.  *        split_date
  24.  *        trnlog
  25.  *
  26.  * Revision history:
  27.  *
  28.  *    4.3    AWR    11/22/91    added special case to loadwords():
  29.  *                    split -<flag>"<text>" into two words
  30.  *
  31.  *                    removed octal/hex escape functionality
  32.  *                    from getline() to new routine
  33.  *                    cvt_escape() (for use elsewhere)
  34.  *
  35.  *            10/25/91    added parameters to loadwords() and
  36.  *                    getline() to avoid always using same
  37.  *                    global data
  38.  *
  39.  *            10/15/91    revised UN*X mk_filespec() to translate
  40.  *                    "~/" in path name as well as file name
  41.  *
  42.  *    4.2    AWR    10/08/91    support -[kK] flags (cf. note_box())
  43.  *
  44.  *            10/03/91    added note_box(), note_day()
  45.  *
  46.  *    4.11    AWR    08/20/91    documented find_executable()
  47.  *
  48.  *    4.02    AWR    06/07/91    added find_executable()
  49.  *
  50.  *    4.0    AWR    02/24/91    Revised getline() and copy_text() to
  51.  *                    handle C-style escapes of characters
  52.  *                    and octal/hex numbers
  53.  *
  54.  *        AWR    02/19/91    Added support for negative ordinals
  55.  *                    in calc_day(), calc_year_day()
  56.  *
  57.  *        AWR    02/04/91    Added calc_year_day()
  58.  *
  59.  *        AWR    01/15/91    Extracted from pcal.c
  60.  *
  61.  */
  62.  
  63.  
  64. /*
  65.  * Standard headers:
  66.  */
  67.  
  68. #include <stdio.h>
  69. #include <ctype.h>
  70. #include <string.h>
  71.  
  72. /*
  73.  * Pcal-specific definitions:
  74.  */
  75.  
  76. #include "pcaldefs.h"
  77. #include "pcalglob.h"
  78. #include "pcallang.h"
  79.  
  80. /*
  81.  * Macros:
  82.  */
  83.  
  84. /* skip over numeric field and subsequent non-numeric characters */
  85. #define SKIP_FIELD(p) \
  86.     do { while (*p && isdigit(*p)) p++; \
  87.          while (*p && !isdigit(*p)) p++; } while (0)
  88.  
  89. /* guarantee that a path is terminated by the END_PATH character */
  90. #define TERM_PATH(path) \
  91.     do { char *p;    \
  92.         if ((p = P_LASTCHAR(path)) && *p != END_PATH) \
  93.             *++p = END_PATH, *++p = '\0'; } while (0)
  94.  
  95.  
  96. /*
  97.  * General-purpose utility routines
  98.  */
  99.  
  100.  
  101. /*
  102.  * alloc - interface to calloc(); terminates if unsuccessful
  103.  */
  104. #ifdef PROTOS
  105. char *alloc(int size)
  106. #else
  107. char *alloc(size)
  108.     int size;
  109. #endif
  110. {
  111.     char *p;
  112.  
  113.     if (size == 0)        /* not all calloc()s like null requests */
  114.         size = 1;
  115.  
  116.     if ((p = calloc(1, size)) == NULL) {
  117.         FPR(stderr, E_ALLOC_ERR, progname);
  118.         exit(EXIT_FAILURE);
  119.     }
  120.  
  121.     return p;
  122. }
  123.  
  124.  
  125. /*
  126.  * ci_str{n}cmp - case-insensitive flavors of strcmp(), strncmp()
  127.  */
  128. #ifdef PROTOS
  129. int ci_strcmp(register char *s1,
  130.           register char *s2)
  131. #else
  132. int ci_strcmp(s1, s2)
  133.     register char *s1, *s2;
  134. #endif
  135. {
  136.     register char c1, c2;
  137.  
  138.     for ( ; (c1 = TOLOWER(*s1)) == (c2 = TOLOWER(*s2)); s1++, s2++)
  139.         if (c1 == '\0')
  140.             return 0;
  141.  
  142.     return c1 - c2;
  143. }
  144.  
  145.  
  146. #ifdef PROTOS
  147. int ci_strncmp(register char *s1,
  148.            register char *s2,
  149.            int n)
  150. #else
  151. int ci_strncmp(s1, s2, n)
  152.     register char *s1, *s2;
  153.     int n;
  154. #endif
  155. {
  156.     register char c1, c2;
  157.  
  158.     for ( ; --n >= 0 && (c1 = TOLOWER(*s1)) == (c2 = TOLOWER(*s2)); s1++, s2++)
  159.         if (c1 == '\0')
  160.             return 0;
  161.  
  162.     return n < 0 ? 0 : c1 - c2;
  163. }
  164.  
  165.  
  166. /*
  167.  * Date calculation routines (see also macros in pcaldefs.h)
  168.  */
  169.  
  170.  
  171. /*
  172.  * normalize - adjust day in case it has crossed month (or year) bounds 
  173.  */
  174. #ifdef PROTOS
  175. void normalize(DATE *pd)
  176. #else
  177. void normalize(pd)
  178.     DATE *pd;        /* pointer to date */
  179. #endif
  180. {
  181.     int len;
  182.  
  183.     /* adjust if day is in previous or following month */
  184.  
  185.     while (pd->dd < 1) {
  186.         pd->yy = PREV_YEAR(pd->mm, pd->yy);
  187.         pd->mm = PREV_MONTH(pd->mm, pd->yy);
  188.         pd->dd += LENGTH_OF(pd->mm, pd->yy);
  189.     }
  190.  
  191.     while (pd->dd > (len = LENGTH_OF(pd->mm, pd->yy))) {
  192.         pd->dd -= len;
  193.         pd->yy = NEXT_YEAR(pd->mm, pd->yy);
  194.         pd->mm = NEXT_MONTH(pd->mm, pd->yy);
  195.     }
  196. }
  197.  
  198.  
  199. /*
  200.  * calc_day - calculate calendar date from ordinal date (e.g., "first Friday
  201.  * in November", "last day in October"); return calendar date if it exists, 
  202.  * 0 if it does not
  203.  */
  204. #ifdef PROTOS
  205. int calc_day(int ord,
  206.          int wkd,
  207.          int mm)
  208. #else
  209. int calc_day(ord, wkd, mm)
  210.     int ord;
  211.     int wkd;
  212.     int mm;
  213. #endif
  214. {
  215. #ifdef PROTOS
  216.     int first, last, day, (*pfcn)(int, int, int);
  217. #else
  218.     int first, last, day, (*pfcn)();
  219. #endif
  220.  
  221.     if (IS_WILD(wkd)) {    /* "day", "weekday", "workday", or "holiday" */
  222.         pfcn = pdatefcn[wkd - WILD_FIRST];
  223.         last = LENGTH_OF(mm, curr_year);
  224.  
  225.         if (ord < 0) {            /* search backwards */
  226.             for (day = last; 
  227.                  day >= 1 &&
  228.                 !((*pfcn)(mm, day, curr_year) && ++ord == 0);
  229.                  day--)
  230.                 ;
  231.         } else {            /* search forwards */
  232.             for (day = 1; 
  233.                  day <= last && 
  234.                 !((*pfcn)(mm, day, curr_year) && --ord == 0);
  235.                  day++)    
  236.                 ;
  237.         }
  238.         return is_valid(mm, day, curr_year) ? day : 0; 
  239.  
  240.     } else {        /* fixed weekday - calculate it */
  241.         first = (wkd - FIRST_OF(mm, curr_year) + 7) % 7 + 1;
  242.         if (ord < 0) {        /* get last (try 5th, then 4th) */
  243.             if (!is_valid(mm, last = first + 28, curr_year))
  244.                 last -= 7;
  245.             if (!is_valid(mm, day = last + 7 * (ord + 1),
  246.                 curr_year))
  247.                 day = 0;    
  248.         }
  249.         else 
  250.             if (!is_valid(mm, day = first + 7 * (ord - 1),
  251.                 curr_year))
  252.                 day = 0;
  253.  
  254.         return day;
  255.     }
  256.  
  257. }
  258.  
  259.  
  260. /*
  261.  * calc_year_day - calculate calendar date from ordinal date within year
  262.  * (e.g., "last Friday in year", "10th holiday in year"); if date exists,
  263.  * fill in pdate and return TRUE; else return FALSE
  264.  */
  265. #ifdef PROTOS
  266. int calc_year_day(int ord,
  267.           int wkd,
  268.           DATE *pdate)
  269. #else
  270. int calc_year_day(ord, wkd, pdate)
  271.     int ord;
  272.     int wkd;
  273.     DATE *pdate;
  274. #endif
  275. {
  276. #ifdef PROTOS
  277.     int incr, (*pfcn)(int, int, int);
  278. #else
  279.     int incr, (*pfcn)();
  280. #endif
  281.     DATE date;
  282.  
  283.     if (IS_WILD(wkd)) {    /* "day", "weekday", "workday", or "holiday" */
  284.         pfcn = pdatefcn[wkd - WILD_FIRST];
  285.  
  286.         if (ord < 0) {            /* nth occurrence backwards */
  287.             MAKE_DATE(date, DEC, 31, curr_year);
  288.             ord = -ord;
  289.             incr = -1;
  290.         } else {            /* nth occurrence forwards */
  291.             MAKE_DATE(date, JAN, 1, curr_year);
  292.             incr = 1;
  293.         }
  294.  
  295.         /* search for selected occurrence of specified wildcard */
  296.  
  297.         while (date.yy == curr_year &&
  298.                !((*pfcn)(date.mm, date.dd, date.yy) && --ord == 0)) {
  299.             date.dd += incr;
  300.             normalize(&date);
  301.         }
  302.  
  303.     } else {        /* fixed weekday - calculate it */
  304.         if (ord < 0)
  305.             MAKE_DATE(date, DEC,
  306.                   calc_day(-1, wkd, DEC) + 7 * (ord + 1),
  307.                   curr_year);
  308.         else
  309.             MAKE_DATE(date, JAN,
  310.                   calc_day(1, wkd, JAN) + 7 * (ord - 1),
  311.                   curr_year);
  312.         normalize(&date);
  313.     }
  314.  
  315.     return date.yy == curr_year ? (*pdate = date, TRUE) : FALSE;
  316. }
  317.  
  318.  
  319. /*
  320.  * calc_weekday - return the weekday (0-6) of mm/dd/yy (mm: 1-12)
  321.  */
  322. #ifdef PROTOS
  323. int calc_weekday(int mm,
  324.          int dd,
  325.          int yy)
  326. #else
  327. int calc_weekday(mm, dd, yy)
  328.     int mm;
  329.     int dd;
  330.     int yy;
  331. #endif
  332. {
  333.     return (yy + (yy-1)/4 - (yy-1)/100 + (yy-1)/400 + OFFSET_OF(mm, yy) +
  334.         (dd-1)) % 7;
  335. }
  336.  
  337.  
  338. /*
  339.  * note_day - translate n (from "note/<n>" spec) to appropriate note text
  340.  * day for mm/yy; return note text day if in range, 0 if not
  341.  */
  342. #ifdef PROTOS
  343. int note_day(int mm,
  344.          int n,
  345.          int yy)
  346. #else
  347. int note_day(mm, n, yy)
  348.     int mm;
  349.     int n;
  350.     int yy;
  351. #endif
  352. {
  353.     int day, lastday;
  354.  
  355.     if (n == 0)            /* convert 0 to appropriate default */
  356.         n = NOTE_DEFAULT;
  357.  
  358.     /* lastday depends on month length and presence (but not position) of
  359.      * small calendars
  360.      */
  361.     lastday = LAST_NOTE_DAY - (LENGTH_OF(mm, yy) - 28) -
  362.             (small_cal_pos == SC_NONE ? 0 : 2);
  363.  
  364.     /* count forward if n is positive, backward if negative */
  365.     day = (n > 0) ? FIRST_NOTE_DAY + n - 1 : lastday + n + 1;
  366.  
  367.     /* make sure result is valid for this month/year */
  368.     return (day >= FIRST_NOTE_DAY && day <= lastday) ? day : 0;
  369. }
  370.  
  371.  
  372. /*
  373.  * note_box - translate dd from note text day to box number for mm/yy,
  374.  * adjusting for presence and position of small calendars
  375.  */
  376. #ifdef PROTOS
  377. int note_box(int mm,
  378.          int dd,
  379.          int yy)
  380. #else
  381. int note_box(mm, dd, yy)
  382.     int mm;
  383.     int dd;
  384.     int yy;
  385. #endif
  386. {
  387.     int startbox = START_BOX(mm, yy), pc, nc;
  388.  
  389.     /* move starting box to second row if conflict with small calendars */
  390.     if ((pc = prev_cal_box[small_cal_pos]) == startbox ||
  391.         (nc = next_cal_box[small_cal_pos]) == startbox)
  392.         startbox += 7;
  393.  
  394.     dd -= FIRST_NOTE_DAY;        /* convert to note box number 0..13 */
  395.     dd += (pc == 0) + (nc == 1);    /* adjust for small calendars in 0, 1 */
  396.  
  397.     /* position box after calendar body if no room before */
  398.     return dd < startbox ? dd : dd + LENGTH_OF(mm, yy);
  399. }
  400.  
  401.  
  402. /*
  403.  * is_valid - return TRUE if m/d/y is a valid date
  404.  */
  405. #ifdef PROTOS
  406. int is_valid(register int m,
  407.          register int d,
  408.          register int y)
  409. #else
  410. int is_valid(m, d, y)
  411.     register int m, d, y;
  412. #endif
  413. {
  414.     return m >= JAN && m <= DEC && 
  415.         d >= 1 && d <= LENGTH_OF(m, y);
  416. }
  417.  
  418.  
  419.  
  420. /*
  421.  * Token parsing/remerging routines:
  422.  */
  423.  
  424.  
  425. /*
  426.  * loadwords - tokenize buffer 'buf' into array 'words' and return word count;
  427.  * differs from old loadwords() in that it handles quoted (" or ') strings
  428.  * and removes escaped quotes
  429.  */
  430. #ifdef PROTOS
  431. int loadwords(char **words,
  432.           char *buf)
  433. #else
  434. int loadwords(words, buf)
  435.     char **words;
  436.     char *buf;
  437. #endif
  438. {
  439.     register char *ptok;
  440.     char *delim, **ap, *p1, *p2, c;
  441.     int nwords;
  442.  
  443.     for (ptok = buf, ap = words; TRUE; ap++) {
  444.  
  445.         ptok += strspn(ptok, WHITESPACE); /* find next token */
  446.  
  447.         if (! *ptok) {            /* end of buf? */
  448.             *ap = NULL;        /* add null ptr at end */
  449.             nwords = ap - words;    /* number of non-null ptrs */
  450.             break;            /* exit loop */
  451.             }
  452.  
  453.         delim =    *ptok == '"'  ? "\"" :    /* set closing delimiter */
  454.             *ptok == '\'' ? "'"  :
  455.             WHITESPACE;
  456.  
  457.         /* split flag followed by quoted string into two words */
  458.         if (*ptok == '-' && isalpha(ptok[1]) &&
  459.             ((c = ptok[2]) == '"' || c == '\'')) {
  460.             delim = c == '"' ? "\"" : "'";
  461.             *ap++ = ptok;
  462.             ptok[2] = '\0';        /* terminate first token */
  463.             ptok += 3;        /* start second token */
  464.         }
  465.         else if (*ptok == *delim)
  466.             ptok++;            /* skip opening quote */
  467.  
  468.         *ap = ptok;            /* save token ptr */
  469.  
  470.         /* find first unescaped string delimiter - handle special
  471.          * case where preceding backslash is itself escaped
  472.          */
  473.         do {
  474.             ptok += strcspn(ptok, delim);
  475.             if ((c = ptok[-1]) == '\\')
  476.                 if (ptok[-2] == '\\')
  477.                     break;    /* stop on \\" or \\' */
  478.                 else
  479.                     ptok++;
  480.         } while (c == '\\');
  481.  
  482.         if (*ptok)            /* terminate token */
  483.             *ptok++ = '\0';
  484.     }
  485.  
  486.     /* now reprocess the word list, removing remaining escapes */
  487.     for (ap = words; *ap; ap++)
  488.         for (p1 = p2 = *ap; c = *p2 = *p1++; *p2++)
  489.             if (c == '\\')
  490.                 *p2 = *p1++;
  491.  
  492.     return nwords;                /* return word count */
  493.  
  494. }
  495.  
  496.  
  497. /*
  498.  * copy_text - retrieve remaining text in pbuf and copy to output string,
  499.  * separating tokens by a single blank and condensing runs of blanks (all
  500.  * other whitespace has been converted to blanks by now) to one blank
  501.  */
  502. #ifdef PROTOS
  503. void copy_text(char *pbuf,
  504.            char **ptext)
  505. #else
  506. void copy_text(pbuf, ptext)
  507.     char *pbuf;        /* output buffer - can be pbuf itself */
  508.     char **ptext;        /* pointer to first text word in "words" */
  509. #endif
  510. {
  511.     char *p, *pb;
  512.  
  513.     /* copy words to pbuf, separating by one blank */
  514.  
  515.     for (*(pb = pbuf) = '\0'; p = *ptext; *pb++ = *++ptext ? ' ' : '\0') {
  516.         for ( ; *p; *p++)
  517.             if (! (*p == ' ' && (pb == pbuf || pb[-1] == ' ')))
  518.                 *pb++ = *p;
  519.         if (pb > pbuf && pb[-1] == ' ')
  520.             pb--;
  521.     }
  522. }
  523.  
  524.  
  525. /*
  526.  * split_date - extract 1-3 numeric fields (separated by one or more
  527.  * non-numeric characters) from date string; return number of fields
  528.  */
  529. #ifdef PROTOS
  530. int split_date(char *pstr,
  531.            int *pn1,
  532.            int *pn2,
  533.            int *pn3)
  534. #else
  535. int split_date(pstr, pn1, pn2, pn3)
  536.     char *pstr;            /* input string */
  537.     int *pn1, *pn2, *pn3;        /* output numbers */
  538. #endif
  539. {
  540.     int i, n, *pn;
  541.  
  542.     /* attempt to extract up to three numeric fields */
  543.     for (n = 0, i = 1; i <= 3; i++) {
  544.         pn = i == 1 ? pn1 : i == 2 ? pn2 : pn3;    /* crude but portable */
  545.         if (pn)
  546.             *pn = *pstr ? (n++, atoi(pstr)) : 0;
  547.         SKIP_FIELD(pstr);        /* go to next field */
  548.     }
  549.  
  550.     return n;
  551. }
  552.  
  553.  
  554.  
  555. /*
  556.  * Escape sequence conversion routines:
  557.  */
  558.  
  559.  
  560. /*
  561.  * octal_esc - read up to 3 octal digits from character string; fill in
  562.  * value of octal constant and return pointer to last character
  563.  */
  564. #ifdef PROTOS
  565. static char *octal_esc(char *buf,
  566.                char *val)
  567. #else
  568. static char *octal_esc(buf, val)
  569.     char *buf;
  570.     char *val;
  571. #endif
  572. {
  573.     int i, n, c;
  574.  
  575.     for (n = 0, i = 0; i < 3 && (c = buf[i]) && isodigit(c); i++) {
  576.         n = n * 8 + (c - '0');
  577.     }
  578.  
  579.     *val = n & 0377;    /* fill in the value */
  580.     return buf + i - 1;    /* return pointer to last character */
  581. }
  582.  
  583.  
  584. /*
  585.  * hex_esc - read 'x' or 'X' followed by 1 or 2 hex digits from character
  586.  * string; fill in value of hexadecimal constant (or letter if no hex digits
  587.  * follow) and return pointer to last character
  588.  */
  589. #ifdef PROTOS
  590. static char *hex_esc(char *buf,
  591.              char *val)
  592. #else
  593. static char *hex_esc(buf, val)
  594.     char *buf;
  595.     char *val;
  596. #endif
  597. {
  598.     int i, n, c;
  599.  
  600.     /* assume leading character is known to be 'x' or 'X'; skip it */
  601.     buf++;
  602.  
  603.     for (n = 0, i = 0; i < 2 && (c = buf[i]) && isxdigit(c); i++) {
  604.         n = n * 16 + (isupper(c) ? c - 'A' + 10 :
  605.                   islower(c) ? c - 'a' + 10 :
  606.                   c - '0');
  607.     }
  608.  
  609.     *val = i == 0 ? buf[-1] : n & 0377; /* fill in the value */
  610.     return buf + i - 1;        /* return pointer to last character */
  611. }
  612.  
  613.  
  614. /*
  615.  * cvt_escape - copy string ibuf to string obuf, converting octal/hex/whitespace
  616.  * escape sequences to the appropriate equivalents.  Escaped quotes and
  617.  * backslashes are not converted here; they need to be preserved so that
  618.  * loadwords() can parse quoted strings correctly
  619.  */
  620. #ifdef PROTOS
  621. void cvt_escape(char *obuf,
  622.         char *ibuf)
  623. #else
  624. void cvt_escape(obuf, ibuf)
  625.     char *obuf, *ibuf;
  626. #endif
  627. {
  628.     char c, c2, *po, *pi;
  629.     static char whitespace[] = "abfnrtv";    /* cf. ANSI spec, 2.2.2 */
  630.     static char no_cvt[] = "\"'\\";
  631.  
  632.     for (po = obuf, pi = ibuf; c = *pi; *po++ = c, pi++) {
  633.         /* handle escape sequences here: escaped whitespace
  634.          * and ANSI escapes are all converted to a space;
  635.          * octal and hex constants are converted in place;
  636.          * escaped single/double quotes are left escaped for
  637.          * loadwords() to handle later on
  638.          */
  639.         if (c == '\\') {
  640.             c2 = *++pi;
  641.             if (isspace(c2) || strchr(whitespace, c2)) {
  642.                 c = ' ';
  643.             }
  644.             else if (isodigit(c2)) {    /* octal */    
  645.                 pi = octal_esc(pi, &c);
  646.             }
  647.             else if (TOLOWER(c2) == 'x') {    /* hex */
  648.                 pi = hex_esc(pi, &c);
  649.             }
  650.             else {
  651.                 if (strchr(no_cvt, c2))    /* leave escaped */
  652.                     *po++ = c;
  653.                 c = c2;
  654.             }
  655.         }
  656.     }
  657.  
  658.     *po = '\0';
  659. }
  660.  
  661.  
  662.  
  663. /*
  664.  * File input routines:
  665.  */
  666.  
  667.  
  668. /*
  669.  * getline - read next non-null line of input file into buf; return 0 on EOF.
  670.  * Strip leading whitespace and trailing comments; handle escaped newlines
  671.  * and call cvt_escape() to translate other escape sequences
  672.  */
  673. #ifdef PROTOS
  674. int getline(FILE *fp,
  675.         char *buf,
  676.         int *pline)
  677. #else
  678. int getline(fp, buf, pline)
  679.     FILE *fp;
  680.     char *buf;
  681.     int *pline;
  682. #endif
  683. {
  684.     register char *cp;
  685.     register int c, c2;
  686.     int in_comment;        /* comments: from '#' to end-of-line */
  687.     char tmpbuf[LINSIZ];    /* temporary buffer to accumulate line */
  688.  
  689.     cp = tmpbuf;
  690.     *buf = '\0';        /* in case of premature EOF */
  691.  
  692.     do {
  693.         in_comment = FALSE;
  694.         while ((c = getc(fp)) != '\n' && c != EOF) {
  695.             if (c == '#')
  696.                 in_comment = TRUE;
  697.  
  698.             if (isspace(c))        /* whitespace => blank */
  699.                 c = ' ';
  700.  
  701.             /* ignore comments and leading white space */
  702.             if (in_comment || (cp == tmpbuf && c == ' '))
  703.                 continue;
  704.  
  705.             /* only handle escape newlines here; other escapes are
  706.              * now handled by cvt_escape() or loadwords() (q.v.)
  707.              */
  708.             if (c == '\\') {
  709.                 if ((c2 = getc(fp)) == EOF)
  710.                     return FALSE;
  711.  
  712.                 if (c2 == '\n') {
  713.                     c = ' ';
  714.                     (*pline)++;
  715.                 }
  716.                 else
  717.                     ungetc(c2, fp);
  718.             }
  719.             *cp++ = c;
  720.         }
  721.  
  722.         if (c == EOF)            /* no more input lines */
  723.             return FALSE;
  724.  
  725.         (*pline)++;            /* bump line number */
  726.  
  727.     } while (cp == tmpbuf);            /* ignore empty lines */
  728.  
  729.     *cp = '\0';
  730.     cvt_escape(buf, tmpbuf);        /* convert escape sequences */
  731.     return TRUE;
  732. }
  733.  
  734.  
  735. /*
  736.  * Routines dealing with translation of file specifications (VMS, Un*x)
  737.  */
  738.  
  739. #ifdef VMS
  740. /*
  741.  * mk_path - extract the path component from VMS file spec
  742.  */
  743. #ifdef PROTOS
  744. char *mk_path(char *path,
  745.           char *filespec)
  746. #else
  747. char *mk_path(path, filespec)
  748.     char *path;        /* output path */
  749.     char *filespec;        /* input filespec */
  750. #endif
  751. {
  752.     char *p;
  753.  
  754.     strcpy(path, filespec);
  755.     if (!(p = strchr(path, ']')) && !(p = strchr(path, ':')))
  756.         p = path - 1;    /* return null string if no path */
  757.     *++p = '\0';
  758.  
  759.     return path;
  760. }
  761.  
  762.  
  763. /*
  764.  * mk_filespec - merge VMS path and file names, where latter can be relative
  765.  */
  766. #ifdef PROTOS
  767. char *mk_filespec(char *filespec,
  768.           char *path,
  769.           char *name)
  770. #else
  771. char *mk_filespec(filespec, path, name)
  772.     char *filespec;        /* output filespec */
  773.     char *path;        /* input path */
  774.     char *name;        /* input file name */
  775. #endif
  776. {
  777.     char *p;
  778.  
  779.     *filespec = '\0';
  780.  
  781.     /* copy name intact if absolute; else merge path and relative name */
  782.     if (!strchr(name, ':')) {
  783.         strcpy(filespec, path);
  784.         if ((p = P_LASTCHAR(filespec)) && *p == END_PATH &&
  785.             name[0] == START_PATH && strchr(".-", name[1]))
  786.             *p = *++name == '-' ? '.' : '\0';
  787.     }
  788.  
  789.     return strcat(filespec, name);
  790. }
  791.  
  792.  
  793. /*
  794.  * trnlog - return translation of VMS logical name (null if missing)
  795.  */
  796. #ifdef PROTOS
  797. char *trnlog(char *logname)
  798. #else
  799. char *trnlog(logname)    /* look up logical name */
  800.     char *logname;
  801. #endif
  802. {
  803.     static char trnbuf[STRSIZ];
  804.     
  805.     $DESCRIPTOR(src, logname);
  806.     $DESCRIPTOR(dst, trnbuf);
  807.     short len;
  808.     int ret;
  809.     
  810.     src.dsc$w_length  = strlen(logname);
  811.     ret = LIB$SYS_TRNLOG(&src, &len, &dst);
  812.     return ret == SS$_NORMAL ? (trnbuf[len] = '\0', trnbuf) : NULL;
  813. }
  814.  
  815. #else        /* apparently DOS and Amiga can use the Un*x flavors */
  816.  
  817. /*
  818.  * mk_path - extract the path component from a Un*x file spec
  819.  */
  820. #ifdef PROTOS
  821. char *mk_path(char *path,
  822.           char *filespec)
  823. #else
  824. char *mk_path(path, filespec)
  825.     char *path;        /* output path */
  826.     char *filespec;        /* input filespec */
  827. #endif
  828. {
  829.     char *p;
  830.  
  831.     strcpy(path, filespec);
  832.     if (! (p = strrchr(path, END_PATH)) )
  833.         p = path - 1;    /* return null string if no path */
  834.  
  835.     *++p = '\0';
  836.     return path;
  837. }
  838.  
  839.  
  840. /*
  841.  * mk_filespec - merge Un*x path and file names, where latter can be relative
  842.  */
  843. #ifdef PROTOS
  844. char *mk_filespec(char *filespec,
  845.           char *path,
  846.           char *name)
  847. #else
  848. char *mk_filespec(filespec, path, name)
  849.     char *filespec;        /* output filespec */
  850.     char *path;        /* input path */
  851.     char *name;        /* input file name */
  852. #endif
  853. {
  854.     char *p;
  855.  
  856.     *filespec = '\0';
  857.  
  858.     /* copy name intact if absolute; else merge path and relative name */
  859.  
  860.     /* if name starts with "~/", translate it for user */
  861.     if (strncmp(name, "~/", 2) == 0 && (p = trnlog(HOME_DIR)) != NULL) {
  862.         strcpy(filespec, p);
  863.         TERM_PATH(filespec);
  864.         name += 2;        /* skip "~/" */
  865.     }
  866.     else if (*name != START_PATH) {        /* relative path */
  867.         /* if path starts with "~/", translate it for user */
  868.         if (strncmp(path, "~/", 2) == 0 &&
  869.             (p = trnlog(HOME_DIR)) != NULL) {
  870.             strcpy(filespec, p);
  871.             TERM_PATH(filespec);
  872.             path += 2;    /* skip "~/"; append rest below */
  873.         }
  874.         strcat(filespec, path);
  875.         TERM_PATH(filespec);
  876.     }
  877.  
  878.     return strcat(filespec, name);
  879. }
  880.  
  881.  
  882. /*
  883.  * trnlog - return translation of Un*x environment variable
  884.  */
  885. #ifdef PROTOS
  886. char *trnlog(char *logname)
  887. #else
  888. char *trnlog(logname)    /* look up logical name */
  889.     char *logname;
  890. #endif
  891. {
  892.     return getenv(logname);
  893. }
  894.  
  895. #endif
  896.  
  897. #ifdef UNIXX    /* highly Un*x-dependent; probably nobody else can use it */
  898.  
  899. #include <sys/types.h>
  900. #include <sys/stat.h>
  901.  
  902. /* X_OK is a #define'd constant used by access() to determine whether or not
  903.  * the specified file is executable by the user.  Investigation of several
  904.  * Un*x systems reveals that 01 appears to be the standard value, but it would
  905.  * be best to #include the appropriate system .h file instead of hard-coding
  906.  * the value (as below).  (The hard-coded approach was adopted reluctantly
  907.  * because there does not appear to be a standardized location for X_OK: some
  908.  * systems put it in <unistd.h>, others in <access.h>, and others elsewhere
  909.  * (if at all).  The access() man page may be helpful.)  (AWR 08/20/91)
  910.  */
  911. #define X_OK    01        /* does user have execute permission? */
  912.  
  913. /*
  914.  * find_executable - return full path name of executable
  915.  */
  916. #ifdef PROTOS
  917. char *find_executable(char *prog)
  918. #else
  919. char *find_executable(prog)
  920.     char *prog;
  921. #endif
  922. {
  923.     char pathvar[1000], *p, *pnext;
  924.     struct stat st;
  925.     static char filepath[STRSIZ];
  926.     
  927.     /* if 'prog' is an absolute/relative path name or environment
  928.      * variable 'PATH' does not exist, return 'prog'; otherwise,
  929.      * search the directories specified in 'PATH' for the executable
  930.      */
  931.     if (strchr(prog, END_PATH) == 0 && (p = getenv(PATH_ENV_VAR)) != 0) {
  932.         strcpy(pathvar, p);
  933.  
  934.         /* assumes PATH is of form "dir1:dir2:...:dirN"; strtok()
  935.          * would be handy here, but not everybody has it yet
  936.          */
  937.         for (p = pathvar; *p; p = pnext) {
  938.             if ((pnext = strchr(p, ':')) != NULL)
  939.                 *pnext++ = '\0';
  940.             else
  941.                 pnext = p + strlen(p);
  942.  
  943.             /* assume the executable lives in the path, and
  944.              * construct its complete file name
  945.              */
  946.             filepath[0] = '\0';
  947.             if (strcmp(p, ".") != 0) {
  948.                 strcat(filepath, p);
  949.                 strcat(filepath, "/");
  950.             }
  951.             strcat(filepath, prog);
  952.  
  953.             /* check that file a) exists, b) is a "normal" file,
  954.              * and c) is executable - if so, return its full path
  955.              */
  956.             if (stat(filepath, &st) >= 0 &&
  957.                (st.st_mode & S_IFMT) == S_IFREG &&
  958.                     access(filepath, X_OK) == 0)
  959.                 return filepath;
  960.         }
  961.     }
  962.  
  963.     return prog;
  964. }
  965. #else
  966.  
  967.  
  968. /*
  969.  * find_executable - return full path name of executable
  970.  */
  971. #ifdef PROTOS
  972. char *find_executable(char *prog)
  973. #else
  974. char *find_executable(prog)
  975.     char *prog;
  976. #endif
  977. {
  978.     return prog;    /* non-Un*x flavor always returns its argument */
  979. }
  980. #endif
  981.