home *** CD-ROM | disk | FTP | other *** search
- /*
- * parse.c
- *
- * 88-10-01 v1.0 created by greg yachuk, placed in the public domain
- * 88-10-06 v1.1 changed prerequisite list handling
- * 88-11-11 v1.2 fixed some bugs and added environment variables
- * 89-07-12 v1.3 stop appending shell commands, and flush output
- * 89-08-01 v1.4 AB lots of new options and code
- * 89-10-30 v1.5 -f -S -q options, took some changes from v1.4
- * 90-04-18 v1.6 -b -- -W options, emulate <<, non-BSD cleanup
- */
- #include <stdio.h>
- #include <ctype.h>
- #include <string.h>
- #ifdef MSDOS
- #include <stdlib.h>
- #endif
-
- #include "make.h"
- #include "tstring.h"
- #include "decl.h"
-
- /*
- * parse - read (text) makefile, and parse
- * - close file before returing
- *
- * lines have the following format:
- * # with or without preceeding spaces/tabs (comment line)
- * <TAB> commands (shell line)
- * name = stuff (macro)
- * name += stuff (macro)
- * targ [targ...] : [pre-req...] [; shell cmd ] (target line)
- */
- parse(fd)
- FILE *fd;
- {
- char *input;
- char *ip;
- char *colonp;
- char schar;
- int ntargs, npreqs, nshell;
- int tmax, pmax, smax;
- targptr *targs;
- fileptr *preqs;
- shellptr *shells;
-
- if (fd == NULL)
- return (0);
-
- /* start off with a short list of targets */
- targs = (targptr *) grow_list(NULL, &tmax);
- preqs = (fileptr *) grow_list(NULL, &pmax);
- shells = (shellptr *) grow_list(NULL, &smax);
-
- ntargs = npreqs = nshell = 0;
-
- /* maximize buffering */
- setvbuf(fd, NULL, _IOFBF, 2048);
-
- while ((input = tgets(fd)) != NULL)
- {
- /* punt on comments and blank lines */
- for (ip = input; isspace(*ip); ++ip);
- if (*ip == '#' || *ip == '\0')
- continue;
-
- /* process include files */
- if (!strncmp(ip, "include", 7))
- {
- /* skip spaces AFTER "include" */
- for (ip += 7; isspace(*ip); ++ip);
-
- /* process macros in the filename */
- ip = breakout(ip);
-
- /* parse the makefile */
- if (!parse(fopen(ip, "r")))
- terror(1, tstrcat("cannot open ", ip));
-
- /* free up the broken-out string */
- tfree(ip);
- continue; /* get next input line */
- }
-
- /* display the makefile line ? */
- if (opts.display)
- puts(input);
-
- /* get rid of comments and preceeding spaces */
- for (colonp = ip; *colonp && *colonp != '#'; ++colonp)
- {
- if (*colonp == '\'' || *colonp == '"')
- colonp = tstrspan(colonp);
- }
-
- for (--colonp; colonp >= ip && isspace(*colonp); --colonp);
-
- /* we *know* that some non-space is on this line, from above */
- if (colonp >= ip)
- *++colonp = '\0';
-
- /* see if we have a shell command */
- if (isspace(*input))
- {
- if (ntargs == 0)
- terror(1, "rules must be after target");
- got_shell:
- if (nshell == smax)
- {
- shells = (shellptr *)
- grow_list((char **) shells, &smax);
- }
- shells[nshell++] = add_shell(ip);
- continue;
- }
-
- /* not a shell line, so must be a target or a macro */
- if (ntargs != 0)
- {
- /* link previous preq's and shell's */
- targs[ntargs] = NULL;
- preqs[npreqs] = NULL;
- shells[nshell] = NULL;
- link_targs(targs, preqs, shells);
- ntargs = npreqs = nshell = 0;
- }
-
- /* don't break out symbols until macro is invoked */
- if (add_macro(ip, 0))
- continue;
-
- /* okay, we have a target line; break out macro symbols */
- input = breakout(ip);
-
- /* just look for tokens with standard isspace() separators */
- ip = token(input, NULL, &schar);
- while (ip)
- {
- colonp = strchr(ip, ':');
- #ifdef MSDOS
- /* need to allow c:/bin/make.exe as a target */
- if (colonp && colonp - ip == 1)
- colonp = strchr(colonp + 1, ':');
- #endif
- if (colonp)
- {
- /* got a separator */
- *colonp = '\0';
-
- /* if at front of token, target is done */
- if (colonp == ip)
- break;
- }
-
- if (ntargs == tmax)
- targs = (targptr *) grow_list((char **) targs,
- &tmax);
- targs[ntargs] = add_target(ip);
-
- /* make sure we don't save .INIT as our 1st target */
- if (first_targ == NULL && *ip != '.')
- first_targ = targs[ntargs];
- ++ntargs;
-
- if (colonp)
- break;
- ip = token(NULL, NULL, &schar);
- }
-
- /* a target line without a colon? naughty, naughty! */
- if (!colonp)
- terror(-1, "Unexpected end of line seen");
-
- /*
- * taking care of four possible cases:
- * 1) object : source
- * 2) object: source
- * 3) object :source
- * 4) object:source
- */
-
- if (colonp && *++colonp)
- ip = colonp;
- else
- ip = token(NULL, NULL, &schar);
-
- /* link the pre-req's */
- while (ip)
- {
- if ((colonp = strchr(ip, ';')) != NULL)
- {
- ip[strlen(ip)] = schar;
- *colonp = '\0';
- }
-
- if (*ip)
- {
- if (npreqs == pmax)
- {
- preqs = (fileptr *)
- grow_list((char **) preqs,
- &pmax);
- }
-
- preqs[npreqs++] = add_file(ip);
- }
-
- if (colonp)
- {
- ip = colonp + 1;
- goto got_shell;
- }
-
- ip = token(NULL, NULL, &schar);
- }
-
- /* gotta free the line allocated by breakout() */
- tfree(input);
- }
-
- /* link up any dangling dependants */
- if (ntargs != 0)
- {
- targs[ntargs] = NULL;
- preqs[npreqs] = NULL;
- shells[nshell] = NULL;
- link_targs(targs, preqs, shells);
- }
-
- /* clean up our mallocs */
- tfree(targs);
- tfree(preqs);
- tfree(shells);
-
- fclose(fd);
- return (1);
- }
-
-
- /*
- * link_targs - force a list of targs to point to same preq's and shell's
- */
- link_targs(targs, preqs, shells)
- targptr *targs;
- fileptr *preqs;
- shellptr *shells;
- {
- while (targs && *targs)
- {
- /* process some special targets */
- if ((*targs)->tfile->fname[0] == '.')
- {
- if (equal((*targs)->tfile->fname, ".SILENT"))
- opts.silent = 1;
- else
- if (equal((*targs)->tfile->fname, ".IGNORE"))
- opts.ignore = 1;
- else
- if (equal((*targs)->tfile->fname, ".SUFFIXES"))
- /*
- * set `suffix_targ' to speed up
- * `default_rule'
- */
- suffix_targ = *targs;
-
- /* special rule has preq's reset */
- /* normally, preq's are merely appended */
- if (*preqs == NULL && (*targs)->tpreq != NULL)
- {
- tfree((*targs)->tpreq);
- (*targs)->tpreq = NULL;
- }
-
- /* special rules have their shell commands replaced */
- if ((*targs)->tshell != NULL && *shells != NULL)
- {
- shellptr *sp;
-
- for (sp = (*targs)->tshell; *sp; ++sp)
- tfree(*sp);
- tfree((*targs)->tshell);
- (*targs)->tshell = NULL;
- }
- }
-
- /* each target in the list points to the preq's and shell's */
- (*targs)->tpreq = append_preq((*targs)->tpreq, preqs);
-
- /* we cannot expand the list of shell commands */
- if ((*targs)->tshell != NULL && *shells != NULL)
- {
- terror(1, tstrcat("Too many rules defined for target ",
- (*targs)->tfile->fname));
- }
- (*targs)->tshell = append_shell((*targs)->tshell, shells);
- ++targs;
- }
- }
-
-
- /* macros must have the format: WORD = more stuff
- * WORD= more stuff
- * WORD =more stuff
- * WORD=more stuff
- * or: WORD += more stuff
- * WORD +=more stuff
- *
- * it is assumed that there is no leading whitespace in `input'
- */
- add_macro(input, scmd)
- char *input;
- int scmd;
- {
- char *eqsign;
- char *value;
- symptr symp;
-
- /* gotta have an '=' to be a macro */
- eqsign = strchr(input, '=');
- if (eqsign == NULL)
- return (0);
-
- /* make sure we catch imbedded '='s (e.g. MACRO=STUFF) */
- for (value = input; *value && !isspace(*value); ++value);
- if (value > eqsign)
- value = eqsign;
-
- /* terminate the macro name */
- *value = '\0';
-
- /* find start of value */
- for (value = eqsign + 1; isspace(*value); ++value);
-
- /* look for concat character */
- --eqsign;
-
- if (eqsign < input || (eqsign == input && *eqsign == '+'))
- terror(1, "Badly formed macro");
-
- if (*eqsign == '+')
- {
- /* append to the current macro definition */
- *eqsign = '\0';
- symp = get_symbol(input, scmd);
- if (symp->scmd && !scmd)
- return (1);
- if (symp->slevel < make_level)
- symp = dup_symbol(symp, symp->svalue);
- if (symp->svalue)
- {
- eqsign = tstrcat(symp->svalue, " ");
- value = tstrcat(eqsign, value);
- tfree(eqsign);
- tfree(symp->svalue);
- symp->svalue = value;
- return (1);
- }
- }
-
- add_symbol(input, value, scmd);
- return (1);
- }
-
-
- /*
- * add_symbol - add a <name,value> pair to the symbol table
- * - override existing symbol value
- * - mark as either command-line macro or not
- */
- add_symbol(name, value, scmd)
- char *name;
- char *value;
- int scmd;
- {
- symptr symp;
-
- symp = get_symbol(name, scmd);
- if (symp->scmd & !scmd)
- return;
- if (symp->slevel < make_level)
- symp = dup_symbol(symp, NULL); /* don't dup the value */
- if (symp->svalue)
- tfree(symp->svalue);
- symp->svalue = tstrcpy(value);
- symp->scmd = scmd;
- }
-
-
- /*
- * get_symbol - find a symbol in the symbol table
- * - if non-extant, create <name,NULL>
- * - return created or found symbol node
- */
- symptr get_symbol(name, scmd)
- char *name;
- int scmd;
- {
- symptr symp;
- t_mask mask;
- char *np;
-
- /* use `mask' to screen out most string comparisons */
- mask = 0;
- np = name;
- while (*np)
- mask += *np++;
-
- /* linear search through symbol list */
- for (symp = symbol_list; symp != NULL; symp = symp->snext)
- {
- if (mask != symp->smask)
- continue;
-
- if (equal(name, symp->sname))
- return (symp);
- }
-
- symp = tnew(symnode); /* allocate symbol node */
- symp->smask = mask; /* record mask for later */
- symp->sname = tstrcpy(name); /* allocate string and copy name */
- symp->scmd = scmd; /* command line macro? */
- symp->slevel = make_level; /* current new_make() level */
-
- /* get the value from the environment, if it is there */
-
- if ((symp->svalue = getenv(name)) != NULL)
- {
- symp->svalue = tstrcpy(symp->svalue);
-
- /*
- * if `-e', let command line macros override, but not macro
- * assignments in the makefile.
- */
- if (opts.envirn)
- symp->scmd = 1;
- }
-
- symp->snext = symbol_list; /* link to head of symbol list */
- symbol_list = symp;
-
- return (symp);
- }
-
-
- /*
- * dup_sym - duplicate a symbol node, but at current new_make() level
- */
- symptr dup_symbol(symp, value)
- symptr symp;
- char *value;
- {
- symptr nsp;
-
- nsp = tnew(symnode); /* allocate symbol node */
- nsp->smask = symp->smask; /* record mask for later */
- nsp->sname = tstrcpy(symp->sname); /* allocate string and copy
- * name */
- nsp->svalue = (value == NULL) ? NULL : tstrcpy(value);
- nsp->scmd = symp->scmd; /* command line macro? */
- nsp->slevel = make_level; /* current new_make() level */
-
- nsp->snext = symbol_list; /* link to head of symbol list */
- symbol_list = nsp;
-
- return (nsp);
- }
-
-
- /*
- * add_target - return extant target node, or create new one
- */
- targptr add_target(name)
- char *name;
- {
- t_mask mask;
- targptr targp;
- fileptr filep;
-
- /* each target must have a file node */
- filep = add_file(name);
-
- /* see if target already exists */
- targp = hash_target(name, &mask);
- if (targp)
- return (targp);
-
- /* oh well, gotta create one */
- targp = tnew(targnode); /* allocate a target node */
- targp->tmask = mask; /* save mask for later */
- targp->tfile = filep; /* save pointer to file node */
- targp->tpreq = NULL; /* no pre-req's yet */
- targp->tshell = NULL; /* no shell lines yet */
-
- targp->tnext = target_list; /* link to front of target list */
- target_list = targp;
-
- return (targp);
- }
-
-
- /*
- * hash_target - look up target (by name) in target list
- * - return target node or NULL
- * - if requested, also return the mask
- */
- targptr hash_target(name, maskp)
- char *name;
- t_mask *maskp;
- {
- targptr targp;
- t_mask mask;
- char *np;
-
- /* use `mask' to screen out most string comparisons */
- mask = 0;
- np = name;
- while (*np)
- mask += *np++;
-
- /* see if we gotta return it */
- if (maskp != NULL)
- *maskp = mask;
-
- /* linear search through target list */
- for (targp = target_list; targp != NULL; targp = targp->tnext)
- {
- if (mask != targp->tmask)
- continue;
-
- /* target name is ONLY stored in the file node */
- if (equal(name, targp->tfile->fname))
- return (targp);
- }
-
- /* nope, no target here */
- return (NULL);
- }
-
-
- /*
- * add_file - return a found or created file node
- */
- fileptr add_file(name)
- char *name;
- {
- t_mask mask;
- fileptr filep;
-
- /* see if file node already exists */
- filep = hash_file(name, &mask);
- if (filep)
- return (filep);
-
- filep = tnew(filenode); /* allocate new file node */
- filep->fmask = mask; /* save mask for later */
- filep->fname = tstrcpy(name); /* allocate string and copy name */
- filep->ftime = MAXNEGTIME; /* init MODIFY time to long time ago */
-
- filep->fnext = file_list; /* link to head of file list */
- file_list = filep;
-
- return (filep);
- }
-
-
- /*
- * hash_file - look up file (by name) in file list
- * - return file node or NULL
- * - if requested, also return the mask
- */
- fileptr hash_file(name, maskp)
- char *name;
- t_mask *maskp;
- {
- fileptr filep;
- t_mask mask;
- char *np;
-
- /* use `mask' to screen out most string comparisons */
- mask = 0;
- np = name;
- while (*np)
- mask += *np++;
-
- /* see if we gotta return it */
- if (maskp != NULL)
- *maskp = mask;
-
- /* linear search through file list */
- for (filep = file_list; filep != NULL; filep = filep->fnext)
- {
- if (filep->fmask != mask)
- continue;
-
- if (equal(filep->fname, name))
- return (filep);
- }
-
- /* nope, no file here */
- return (NULL);
- }
-
-
- /*
- * append_node - add a node to the end of an array of nodes
- */
- char **append_node(node, adds, size)
- char **node;
- char **adds;
- int size;
- {
- int addlen, len;
-
- for (addlen = 0; adds[addlen] != NULL; ++addlen);
- if (addlen++ == 0)
- return (node);
-
- len = 0;
-
- if (node != NULL)
- {
- for (; node[len] != NULL; ++len);
- node = (char **) trealloc((char *) node, (len + addlen) * size);
- }
- else
- node = (char **) talloc(addlen * size);
-
- memcpy(node + len, adds, addlen * size);
- return (node);
- }
-
- /*
- * add_shell - create a new shell node, and add to end of given list
- */
- shellptr add_shell(input)
- char *input;
- {
- shellptr snode;
-
- snode = tnew(shellnode);/* allocate a new shell node */
- snode->s_shell = snode->s_ignore = snode->s_silent = 0;
-
- for (; isspace(*input); ++input); /* skip over leading spaces */
- for (;; ++input)
- {
- if (*input == '+')
- snode->s_shell = 1; /* must use command.com */
- else
- if (*input == '-')
- snode->s_ignore = 1; /* ignore return value */
- else
- if (*input == '@')
- snode->s_silent = 1; /* don't echo command */
- else
- break;
- }
-
- snode->scmd = tstrcpy(input); /* allocate string and copy command */
-
- snode->slink = shell_list; /* attach to global list */
- shell_list = snode;
-
- return (snode);
- }
-
-
- /*
- * breakout - replace macro names with values
- * - apply recursively
- * note: allocates (and returns) a string which must be freed
- */
- char *breakout(input)
- char *input;
- {
- char *dest, *dend;
- char *dp;
- int dlen;
- int tlen;
- int state;
- char symname[100];
- char *sp;
- symptr symp;
- int slen;
- char endch;
-
- /* allocate a string twice as long as input string */
-
- dlen = strlen(input) * 2;
- dest = dp = talloc(dlen);
- dend = dest + dlen;
-
- /*
- * state machine with 4 states
- * 0) normal text -- just copy
- * 1) starting macro -- define end char (e.g. ')', '}')
- * 2) macro name -- copy to a buffer
- * 3) end of macro -- look up value, and copy
- */
- state = 0;
-
- while (*input || state == 3)
- {
- /* if we don't have enough room, double size of string */
- if (dp == dend)
- {
- dlen *= 2;
- tlen = dp - dest;
- dest = trealloc(dest, dlen);
- dp = dest + tlen;
- dend = dest + dlen;
- }
-
- switch (state)
- {
- case 0:
- if (*input == '$')
- state = 1; /* found a macro */
- else
- *dp++ = *input++;
- break;
-
- case 1:
- state = 2; /* only in this state for 1 char */
- sp = symname;
- switch (*++input)
- {
- case '$':
- *dp++ = '$';
- state = 0;
- break;
- case '(':
- endch = ')';
- break;
- case '{':
- endch = '}';
- break;
- default:
- /* single char; go to state 3 immediately */
- *sp++ = *input;
- state = 3;
- break;
- }
- ++input;/* skip bracket (or character) */
- break;
-
- case 2:
- if (*input == endch)
- state = 3;
- else
- *sp++ = *input;
-
- if ((sp - symname) >= (sizeof symname / sizeof symname[0]))
- {
- sp[-1] = '\0';
- terror(1,
- tstrcat("Macro too long (limit 100 chars): ",
- symname));
- }
-
- ++input;/* make sure we skip end char */
- break;
-
- case 3:
- *sp = '\0';
- symp = get_symbol(symname, 0);
- sp = symp->svalue;
- slen = -1;
- while (sp && *sp)
- {
- /*
- * if value has a macro in it, we must
- * process recursively
- */
- if (*sp == '$')
- {
- sp = breakout(symp->svalue);
- /* now guaranteed not to have a '$' */
- slen = strlen(sp);
- break;
- }
- ++sp;
- }
-
- if (slen == -1)
- {
- /* value did NOT have a macro */
- slen = (sp - symp->svalue);
- sp = symp->svalue;
- }
-
- /* if we have not enough room, expand */
- if (slen >= (dend - dp))
- {
- /* use slen to make sure that we can fit */
- dlen = dlen * 2 + slen;
- tlen = dp - dest;
- dest = trealloc(dest, dlen);
- dp = dest + tlen;
- dend = dest + dlen;
- }
-
- /* if length is zero, don't bother to copy */
- if (slen)
- {
- strcpy(dp, sp);
- dp += slen;
- }
-
- if (sp != symp->svalue)
- tfree(sp); /* must've called `breakout' */
-
- state = 0; /* and we are back to text */
- break;
- }
- }
-
- if (state != 0)
- terror(1, tstrcat("Improper macro.\n", dest));
-
- *dp = '\0'; /* terminate the string */
- return (dest); /* and return it */
- }
-