home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power Programming
/
powerprogramming1994.iso
/
progtool
/
c
/
abmake14.arc
/
parse.c
< prev
next >
Wrap
C/C++ Source or Header
|
1989-01-29
|
16KB
|
748 lines
/*
* 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
*
*/
#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, i_level)
FILE *fd;
int i_level;
{
char *input;
char *ip;
char *colonp;
int i, 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)
{
/* display the makefile line ? */
if ( debug > 1 )
{
if ( i_level ) /* Parsing an include file ? */
printf("#%d ", i_level);
puts(input);
}
/* punt on comments and blank lines */
for (ip = input; isspace(*ip); ++ip);
if (*ip == '#' || *ip == '\0')
continue;
/* process include files */
if (!strnicmp(ip, "include ", 8))
{
/* skip spaces AFTER "include " */
for (ip+=8; isspace(*ip); ++ip)
{}
if (!parse(fopen(ip, "r"), i_level + 1))
terror(1, tstrcat("cannot open ", ip));
continue; /* get next input line */
}
/* 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))
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)
{
/* 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)
char *input;
{
REGISTER char *eqsign;
REGISTER char *value;
char *newenv;
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; !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 */
symp = get_symbol(input);
if (symp->svalue)
value = tstrcat(symp->svalue, value);
add_symbol(input, value);
tfree(value);
}
else
add_symbol(input, value);
return (TRUE);
}
/*
* add_symbol - add a <name,value> pair to the symbol table
* - override existing symbol value
*/
add_symbol(name, value)
char *name;
char *value;
{
REGISTER symptr symp;
char *newenv;
symp = get_symbol(name);
if ( touchenv || !symp->senv )
{
if (symp->svalue)
tfree(symp->svalue);
symp->svalue = tstrcpy(value);
if ( symp->senv )
{
/* after allocation of newenv, do not try to free this memory !! */
if ( (newenv = talloc(strlen(value) + strlen(symp->sname) + 2)) )
{
sprintf(newenv, "%s=%s", symp->sname, value);
if ( putenv(newenv) != 0 )
terror(1, "*** Environment allocation error.");
if ( debug )
printf("*** set %s=%s\n", symp->sname, getenv(symp->sname));
}
else
terror(1, "*** Symbol allocation error.");
}
}
}
/*
* get_symbol - find a symbol in the symbol table
* - if non-existant, create <name,NULL>
* - return created or found symbol node
*/
symptr
get_symbol(name)
char *name;
{
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->senv = 0;
if ( (symp->svalue = getenv(name)) )
if ( (symp->svalue = tstrcpy(symp->svalue)) )
symp->senv = 1;
symp->snext = symbol_list; /* link to head of symbol list */
symbol_list = symp;
return (symp);
}
/*
* 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);
/*
if ( debug )
printf("*** memfree = %u, stack free %u, heap check %d\n",
_memavl(), stackavail(), _heapchk());
*/
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 */
shell_list = snode->slink;
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) << 1;
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);
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 */
}