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
- *
- */
- #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...] (target line)
- */
- parse(fd)
- FILE *fd;
- {
- char *input;
- char *ip;
- char *colonp;
- int ntargs, npreqs, nshell;
- int tmax, pmax, smax;
- targptr *targs;
- fileptr *preqs;
- shellptr *shells;
-
- if (fd == NULL)
- return (FALSE);
-
- /* 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 ", 8))
- {
- /* skip spaces AFTER "include " */
- for (ip+=8; isspace(*ip); ++ip);
- if (!parse(fopen(ip, "r")))
- terror(1, tstrcat("cannot open ", ip));
- continue; /* get next input line */
- }
-
- /* display the makefile line ? */
- if (display)
- puts(input);
-
- /* see if we have a shell command */
- if (isspace(*input))
- {
- if (ntargs == 0)
- terror(1, "rules must be after target");
-
- 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;
- }
-
- if ((colonp = strchr(ip, '#')) != NULL)
- *colonp = '\0'; /* get rid of comment */
-
- /* 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);
- 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);
- }
-
- /*
- * 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);
-
- /* link the pre-req's */
- while (ip)
- {
- if (npreqs == pmax)
- preqs = (fileptr *) grow_list((char **) preqs,
- &pmax);
- preqs[npreqs++] = add_file(ip);
- ip = token(NULL, NULL);
- }
-
- /* 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 (TRUE);
- }
-
-
- /*
- * link_targs - force a list of targs to point to same preq's and shell's
- */
- link_targs(targs, preqs, shells)
- REGISTER targptr *targs;
- fileptr *preqs;
- shellptr *shells;
- {
- REGISTER int plen;
-
- for (plen = 0; preqs[plen] != NULL; ++plen);
-
- while (targs && *targs)
- {
- /* process some special targets */
- if ((*targs)->tfile->fname[0] == '.')
- {
- if (equal((*targs)->tfile->fname, ".SILENT"))
- silent = 1;
- else if (equal((*targs)->tfile->fname, ".IGNORE"))
- 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 (plen == 0)
- {
- tfree((*targs)->tpreq);
- (*targs)->tpreq = NULL;
- }
- }
-
- /* each target in the list points to the preq's and shell's */
- (*targs)->tpreq = append_preq((*targs)->tpreq, preqs);
- (*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;
- {
- REGISTER char *eqsign;
- REGISTER char *value;
- symptr symp;
-
- /* gotta have an '=' to be a macro */
- eqsign = strchr(input, '=');
- if (eqsign == NULL)
- return (FALSE);
-
- /* 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 (TRUE);
- 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 (TRUE);
- }
- }
-
- add_symbol(input, value, scmd);
- return (TRUE);
- }
-
-
- /*
- * 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;
- {
- REGISTER 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;
- {
- REGISTER symptr symp;
- REGISTER t_mask mask;
-
- /* use `mask' to screen out most string comparisons */
- mask = hash_name(name);
-
- /* 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 (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(sp, svalue)
- symptr sp;
- char *svalue;
- {
- symptr nsp;
-
- nsp = tnew(symnode); /* allocate symbol node */
- nsp->smask = sp->smask; /* record mask for later */
- nsp->sname = tstrcpy(sp->sname);/* allocate string and copy name */
- nsp->svalue = (svalue == NULL) ? NULL : tstrcpy(svalue);
- nsp->scmd = sp->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;
- REGISTER 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;
- {
- REGISTER targptr targp;
- REGISTER t_mask mask;
-
- /* use `mask' to screen out most string comparisons */
- mask = hash_name(name);
-
- /* 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);
- }
-
-
- /*
- * hash_name - transform a character string into an integer
- */
- t_mask
- hash_name(name)
- char *name;
- {
- REGISTER char c;
- REGISTER t_mask mask;
- int bucket;
-
- /*
- * take the sum of each pair of characters, and set a bit
- * for this sum modulo the mask size (word size) in bits.
- *
- * e.g. string: c0, c1, c2, c3
- * set bit for (c0+c1) % mask_size
- * set bit for (c1+c2) % mask_size
- * set bit for (c2+c3) % mask_size
- * set bit for (c3) % mask_size (end condition)
- *
- * this gives a fair distribution, with not TOO much work
- */
- mask = 0;
- while (*name) {
- c = *name++;
- mask |= 1L << ((c + *name) % (sizeof(mask) * 8));
- }
-
- return (mask);
- }
-
- /*
- * add_file - return a found or created file node
- */
- fileptr
- add_file(name)
- char *name;
- {
- t_mask mask;
- REGISTER 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;
- {
- REGISTER fileptr filep;
- REGISTER t_mask mask;
-
- /* use `mask' to screen out most string comparisons */
- mask = hash_name(name);
-
- /* 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;
- {
- REGISTER 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;
- {
- REGISTER 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->snext = NULL; /* no list of shells for a targ, yet */
-
- snode->slink = shell_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)
- REGISTER char *input;
- {
- char *dest, *dend;
- REGISTER 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 '(':
- 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;
- ++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 */
- }
-