mirror of
git://git.suckless.org/sbase
synced 2026-01-27 14:04:09 +00:00
This implementation is ported from the scc compiler with the author permission to re license it with sbase license. Using this make implementation to bootstrap sbase removes the problems found by some buggy make implementations (specially in the case of OpenBSD). It has a drawback that the options passed for parallel build with -j are ignored(improvement are expected). Due to the multi file nature of make, embedding it in sbas-box creates some problems, and for now, we keep it out of sbase-box.
1035 lines
16 KiB
C
1035 lines
16 KiB
C
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "make.h"
|
|
|
|
#define MAXREPL 30
|
|
#define TABSIZ 64
|
|
#define MAXTOKEN FILENAME_MAX
|
|
#define ITEM 128
|
|
|
|
typedef struct macro Macro;
|
|
|
|
enum inputype {
|
|
FTFILE,
|
|
FTEXPAN,
|
|
};
|
|
|
|
enum {
|
|
STBEGIN,
|
|
STINTERNAL,
|
|
STREPLACE,
|
|
STTO,
|
|
STEND,
|
|
};
|
|
|
|
struct input {
|
|
int siz;
|
|
int type;
|
|
|
|
FILE *fp;
|
|
struct loc loc;
|
|
|
|
int pos;
|
|
char *buf;
|
|
|
|
struct input *prev;
|
|
};
|
|
|
|
struct macro {
|
|
char *name;
|
|
char *value;
|
|
int where;
|
|
|
|
struct macro *next;
|
|
};
|
|
|
|
static struct input *input;
|
|
static char token[MAXTOKEN];
|
|
static int tok;
|
|
static Macro *htab[TABSIZ];
|
|
|
|
void
|
|
dumpmacros(void)
|
|
{
|
|
Macro **pp, *p;
|
|
|
|
for (pp = htab; pp < &htab[TABSIZ]; ++pp) {
|
|
for (p = *pp; p; p = p->next)
|
|
printf("%s = %s\n", p->name, getmacro(p->name));
|
|
}
|
|
}
|
|
|
|
static Macro *
|
|
lookup(char *name)
|
|
{
|
|
Macro *mp;
|
|
int h = hash(name) & TABSIZ-1;
|
|
|
|
for (mp = htab[h]; mp && strcmp(mp->name, name); mp = mp->next)
|
|
;
|
|
|
|
if (mp)
|
|
return mp;
|
|
|
|
mp = emalloc(sizeof(*mp));
|
|
mp->name = estrdup(name);
|
|
mp->value = estrdup("");
|
|
mp->next = htab[h];
|
|
mp->where = UNDEF;
|
|
htab[h] = mp;
|
|
|
|
return mp;
|
|
}
|
|
|
|
static char *
|
|
macroinfo(char *name, int *pwhere, Macro **mpp)
|
|
{
|
|
char *s, *t;
|
|
int hide, where;
|
|
Macro *mp = lookup(name);
|
|
|
|
hide = 0;
|
|
if (!strcmp(name, "SHELL") || !strcmp(name, "MAKEFLAGS"))
|
|
hide = 1;
|
|
|
|
s = mp->value;
|
|
where = mp->where;
|
|
|
|
if (!hide && (where == UNDEF || where == INTERNAL || eflag)) {
|
|
t = getenv(name);
|
|
if (t) {
|
|
where = ENVIRON;
|
|
s = t;
|
|
}
|
|
}
|
|
|
|
if (pwhere)
|
|
*pwhere = where;
|
|
if (mpp)
|
|
*mpp = mp;
|
|
|
|
return s;
|
|
}
|
|
|
|
char *
|
|
getmacro(char *name)
|
|
{
|
|
return macroinfo(name, NULL, NULL);
|
|
}
|
|
|
|
void
|
|
setmacro(char *name, char *val, int where, int export)
|
|
{
|
|
int owhere, set;
|
|
char *s;
|
|
Macro *mp;
|
|
|
|
assert(where != ENVIRON);
|
|
|
|
s = macroinfo(name, &owhere, &mp);
|
|
|
|
/*
|
|
* Default values are defined before anything else, and marked
|
|
* as INTERNAL because they are injected as parseable text, and
|
|
* MAKEFILE and INTERNAL variables are always overriden. ENVIRON
|
|
* macros are generated in macroinfo() and this is why this function
|
|
* should not receive a where == ENVIRON ever.
|
|
*/
|
|
switch (owhere) {
|
|
case UNDEF:
|
|
case INTERNAL:
|
|
case MAKEFILE:
|
|
set = 1;
|
|
break;
|
|
case ENVIRON:
|
|
set = (where == MAKEFLAGS || where == CMDLINE);
|
|
set |= (where == MAKEFILE && !eflag);
|
|
break;
|
|
case MAKEFLAGS:
|
|
set = (where == CMDLINE || where == MAKEFLAGS);
|
|
break;
|
|
case CMDLINE:
|
|
set = (where == CMDLINE);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
|
|
if (!set) {
|
|
debug("hidding override of %s from '%s' to '%s'", name, s, val);
|
|
} else {
|
|
debug("override %s from '%s' to '%s'", name, s, val);
|
|
free(mp->value);
|
|
mp->value = estrdup(val);
|
|
mp->where = where;
|
|
|
|
if (export && strcmp(name, "SHELL") != 0) {
|
|
debug("exporting macro %s", name);
|
|
exportvar(name, val);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
freeloc(struct loc *loc)
|
|
{
|
|
free(loc->fname);
|
|
}
|
|
|
|
static struct loc *
|
|
getloc(void)
|
|
{
|
|
struct input *ip;
|
|
|
|
for (ip = input; ip && ip->type != FTFILE; ip = ip->prev)
|
|
;
|
|
if (!ip)
|
|
return NULL;
|
|
|
|
return &ip->loc;
|
|
}
|
|
|
|
|
|
void
|
|
error(char *fmt, ...)
|
|
{
|
|
va_list va;
|
|
struct loc *loc;
|
|
|
|
fprintf(stderr, "make: error: ");
|
|
if ((loc = getloc()) != NULL)
|
|
fprintf(stderr, "%s:%d: ", loc->fname, loc->lineno);
|
|
|
|
va_start(va, fmt);
|
|
vfprintf(stderr, fmt, va);
|
|
va_end(va);
|
|
putc('\n', stderr);
|
|
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
void
|
|
warning(char *fmt, ...)
|
|
{
|
|
va_list va;
|
|
struct loc *loc;
|
|
|
|
fprintf(stderr, "make: warning: ");
|
|
if ((loc = getloc()) != NULL)
|
|
fprintf(stderr, "%s:%d: ", loc->fname, loc->lineno);
|
|
|
|
va_start(va, fmt);
|
|
vfprintf(stderr, fmt, va);
|
|
va_end(va);
|
|
putc('\n', stderr);
|
|
}
|
|
|
|
static void
|
|
pop(void)
|
|
{
|
|
struct input *ip = input->prev;
|
|
|
|
if (input->type == FTFILE) {
|
|
if (input->fp)
|
|
fclose(input->fp);
|
|
freeloc(&input->loc);
|
|
}
|
|
free(input->buf);
|
|
free(input);
|
|
|
|
input = ip;
|
|
}
|
|
|
|
static void
|
|
push(int type, ...)
|
|
{
|
|
int line, len, pos;
|
|
FILE *fp = NULL;
|
|
char *buf, *s, *fname = NULL;
|
|
va_list va;
|
|
struct input *ip;
|
|
|
|
va_start(va, type);
|
|
switch (type) {
|
|
case FTFILE:
|
|
fp = va_arg(va, FILE *);
|
|
s = va_arg(va, char *);
|
|
line = va_arg(va, int);
|
|
fname = estrdup(s);
|
|
buf = emalloc(BUFSIZ);
|
|
pos = len = BUFSIZ;
|
|
break;
|
|
case FTEXPAN:
|
|
s = va_arg(va, char *);
|
|
buf = estrdup(s);
|
|
line = pos = 0;
|
|
len = strlen(s);
|
|
break;
|
|
}
|
|
va_end(va);
|
|
|
|
ip = emalloc(sizeof(*ip));
|
|
ip->siz = len;
|
|
ip->buf = buf;
|
|
ip->type = type;
|
|
ip->fp = fp;
|
|
ip->loc.fname = fname;
|
|
ip->loc.lineno = line;
|
|
ip->pos = pos;
|
|
ip->prev = input;
|
|
|
|
input = ip;
|
|
}
|
|
|
|
static char *
|
|
trim(char *s)
|
|
{
|
|
size_t len;
|
|
|
|
while (isspace(*s))
|
|
s++;
|
|
|
|
for (len = strlen(s); len > 0 && isspace(s[len-1]); --len)
|
|
s[len-1] = '\0';
|
|
|
|
return s;
|
|
}
|
|
|
|
static void
|
|
include(char *s)
|
|
{
|
|
int len;
|
|
FILE *fp;
|
|
char *fil, *t;
|
|
|
|
s = trim(s);
|
|
fil = expandstring(s, NULL, getloc());
|
|
|
|
t = trim(fil);
|
|
if (strlen(t) != 0) {
|
|
debug("including '%s'", t);
|
|
if ((fp = fopen(t, "r")) == NULL)
|
|
error("opening %s:%s", t, strerror(errno));
|
|
push(FTFILE, fp, t, 0);
|
|
}
|
|
|
|
free(fil);
|
|
}
|
|
|
|
static char *
|
|
nextline(void)
|
|
{
|
|
int c;
|
|
FILE *fp;
|
|
char *s, *lim;
|
|
|
|
assert(input->type == FTFILE);
|
|
|
|
repeat:
|
|
fp = input->fp;
|
|
if (!fp || feof(fp))
|
|
return NULL;
|
|
|
|
lim = &input->buf[input->siz];
|
|
for (s = input->buf; s < lim; *s++ = c) {
|
|
c = getc(fp);
|
|
if (c == '\n' || c == EOF) {
|
|
input->loc.lineno++;
|
|
*s++ = '\n';
|
|
break;
|
|
}
|
|
if (c > UCHAR_MAX || c < 0)
|
|
error("invalid character '%c' (%d)", c, c);
|
|
}
|
|
|
|
|
|
if (s == lim)
|
|
error("too long line");
|
|
if (ferror(fp))
|
|
error(strerror(errno));
|
|
*s = '\0';
|
|
|
|
if (!strcmp(input->buf, ""))
|
|
goto repeat;
|
|
|
|
if (!strncmp(input->buf, "include", 7) && isblank(input->buf[7])) {
|
|
input->pos = input->siz;
|
|
include(input->buf+7);
|
|
goto repeat;
|
|
}
|
|
|
|
input->pos = 0;
|
|
|
|
|
|
return input->buf;
|
|
}
|
|
|
|
static int
|
|
empty(struct input *ip)
|
|
{
|
|
return ip->pos == ip->siz || ip->buf[ip->pos] == '\0';
|
|
}
|
|
|
|
static int
|
|
moreinput(void)
|
|
{
|
|
while (input) {
|
|
if (!empty(input))
|
|
break;
|
|
|
|
switch (input->type) {
|
|
case FTEXPAN:
|
|
pop();
|
|
break;
|
|
case FTFILE:
|
|
if (!nextline())
|
|
pop();
|
|
break;
|
|
}
|
|
}
|
|
|
|
return input != NULL;
|
|
}
|
|
|
|
static int
|
|
nextc(void)
|
|
{
|
|
if (!moreinput())
|
|
return EOF;
|
|
|
|
return input->buf[input->pos++];
|
|
}
|
|
|
|
/*
|
|
* This function only can be called after a call to nextc
|
|
* that didn't return EOF. It can return '\0', but as
|
|
* it is used only to check against '$' then it is not
|
|
* a problem.
|
|
*/
|
|
static int
|
|
ahead(void)
|
|
{
|
|
return input->buf[input->pos];
|
|
}
|
|
|
|
static int
|
|
back(int c)
|
|
{
|
|
if (c == EOF)
|
|
return c;
|
|
assert(input->pos > 0);
|
|
return input->buf[--input->pos] = c;
|
|
}
|
|
|
|
static void
|
|
comment(void)
|
|
{
|
|
int c;
|
|
|
|
while ((c = nextc()) != EOF && c != '\n') {
|
|
if (c == '\\' && nextc() == EOF)
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
skipspaces(void)
|
|
{
|
|
int c;
|
|
|
|
for (c = nextc(); c == ' ' || c == '\t'; c = nextc())
|
|
;
|
|
back(c);
|
|
}
|
|
|
|
static int
|
|
validchar(int c)
|
|
{
|
|
if (c == EOF)
|
|
return 0;
|
|
return c == '.' || c == '/' || c == '_' || c == '-' || isalnum(c);
|
|
}
|
|
|
|
static char *
|
|
expandmacro(char *name)
|
|
{
|
|
char *s;
|
|
|
|
s = expandstring(getmacro(name), NULL, getloc());
|
|
debug("macro %s expanded to '%s'", name, s);
|
|
|
|
return s;
|
|
}
|
|
|
|
static void
|
|
replace(char *line, char *repl, char *to)
|
|
{
|
|
int siz, at, len, replsiz, tosiz, sep, pos;
|
|
char *oline, *s, *cur, *buf;
|
|
|
|
debug("replacing '%s', with '%s' to '%s'", line, repl, to);
|
|
oline = line;
|
|
tosiz = strlen(to);
|
|
replsiz = strlen(repl);
|
|
|
|
buf = NULL;
|
|
for (pos = 0; *line; pos += siz) {
|
|
cur = NULL;
|
|
siz = 0;
|
|
|
|
for (siz = 0; *line == ' ' || *line == '\t'; ++siz) {
|
|
cur = erealloc(cur, siz+1);
|
|
cur[siz] = *line++;
|
|
}
|
|
|
|
len = strcspn(line, " \t");
|
|
at = len - replsiz;
|
|
if (at < 0 || memcmp(line + at, repl, replsiz)) {
|
|
cur = erealloc(cur, siz + len);
|
|
memcpy(cur + siz, line, len);
|
|
siz += len;
|
|
} else {
|
|
cur = erealloc(cur, siz + at + tosiz);
|
|
memcpy(cur + siz, line, at);
|
|
memcpy(cur + siz + at, to, tosiz);
|
|
siz += at + tosiz;
|
|
}
|
|
|
|
line += len;
|
|
buf = erealloc(buf, pos + siz);
|
|
memcpy(buf + pos, cur, siz);
|
|
free(cur);
|
|
}
|
|
|
|
if (pos > 0) {
|
|
buf = erealloc(buf, pos + 1);
|
|
buf[pos] = '\0';
|
|
debug("\treplace '%s' with '%s'", oline, buf);
|
|
push(FTEXPAN, buf);
|
|
}
|
|
|
|
free(buf);
|
|
}
|
|
|
|
static void
|
|
expandsimple(Target *tp)
|
|
{
|
|
char *s;
|
|
Target **p;
|
|
int len, c;
|
|
|
|
switch (c = nextc()) {
|
|
case '@':
|
|
if (!tp || !tp->target)
|
|
return;
|
|
push(FTEXPAN, tp->target);
|
|
break;
|
|
case '<':
|
|
if (!tp || !tp->req)
|
|
return;
|
|
push(FTEXPAN, tp->req);
|
|
break;
|
|
case '*':
|
|
if (!tp || !tp->target)
|
|
return;
|
|
s = strrchr(tp->target, '.');
|
|
if (!s) {
|
|
push(FTEXPAN, tp->target);
|
|
return;
|
|
}
|
|
|
|
len = s - tp->target;
|
|
s = emalloc(len+1);
|
|
memcpy(s, tp->target, len);
|
|
s[len] = '\0';
|
|
push(FTEXPAN, s);
|
|
free(s);
|
|
break;
|
|
case '?':
|
|
if (!tp)
|
|
return;
|
|
|
|
if (tp->req && stamp(tp->req) > tp->stamp) {
|
|
push(FTEXPAN, " ");
|
|
push(FTEXPAN, tp->req);
|
|
}
|
|
|
|
for (p = tp->deps; p && *p; ++p) {
|
|
if (stamp((*p)->name) > tp->stamp) {
|
|
push(FTEXPAN, " ");
|
|
push(FTEXPAN, (*p)->name);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
token[0] = c;
|
|
token[1] = '\0';
|
|
s = expandmacro(token);
|
|
push(FTEXPAN, s);
|
|
free(s);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int
|
|
internal(int ch)
|
|
{
|
|
switch (ch) {
|
|
case '@':
|
|
case '?':
|
|
case '*':
|
|
case '<':
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
expansion(Target *tp)
|
|
{
|
|
int delim, c, repli, toi, namei, st;
|
|
char name[MAXTOKEN], repl[MAXREPL], to[MAXREPL];
|
|
char *s, *erepl;
|
|
|
|
c = nextc();
|
|
if (c == '(')
|
|
delim = ')';
|
|
else if (c == '{')
|
|
delim = '}';
|
|
else
|
|
delim = 0;
|
|
|
|
if (!delim) {
|
|
back(c);
|
|
expandsimple(tp);
|
|
return;
|
|
}
|
|
|
|
s = NULL;
|
|
namei = repli = toi = 0;
|
|
st = STBEGIN;
|
|
|
|
while (st != STEND && (c = nextc()) != EOF) {
|
|
switch (st) {
|
|
case STBEGIN:
|
|
if (c == ':') {
|
|
st = STREPLACE;
|
|
name[namei] = '\0';
|
|
s = expandmacro(name);
|
|
break;
|
|
}
|
|
if (c == delim) {
|
|
name[namei] = '\0';
|
|
s = expandmacro(name);
|
|
goto no_replace;
|
|
}
|
|
if (namei == MAXTOKEN-1)
|
|
error("expansion text too long");
|
|
|
|
if (namei == 0 && internal(c)) {
|
|
name[namei++] = '$';
|
|
name[namei++] = c;
|
|
name[namei] = '\0';
|
|
st = STINTERNAL;
|
|
s = expandstring(name, tp, getloc());
|
|
break;
|
|
}
|
|
|
|
if (!validchar(c))
|
|
error("invalid macro name in expansion");
|
|
name[namei++] = c;
|
|
break;
|
|
case STINTERNAL:
|
|
if (c == delim)
|
|
goto no_replace;
|
|
if (c != ':')
|
|
error("invalid internal macro in expansion");
|
|
st = STREPLACE;
|
|
break;
|
|
case STREPLACE:
|
|
if (c == '=') {
|
|
st = STTO;
|
|
break;
|
|
}
|
|
if (c == delim)
|
|
error("invalid replacement pattern in expansion");
|
|
if (repli == MAXREPL-1)
|
|
error("macro replacement too big");
|
|
repl[repli++] = c;
|
|
break;
|
|
case STTO:
|
|
if (c == delim) {
|
|
st = STEND;
|
|
break;
|
|
}
|
|
|
|
if (toi == MAXREPL-1)
|
|
error("macro substiturion too big");
|
|
to[toi++] = c;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (c == EOF)
|
|
error("found eof while parsing expansion");
|
|
|
|
repl[repli] = '\0';
|
|
to[toi] = '\0';
|
|
|
|
erepl = expandstring(repl, tp, getloc());
|
|
replace(s, erepl, to);
|
|
|
|
free(erepl);
|
|
free(s);
|
|
return;
|
|
|
|
no_replace:
|
|
push(FTEXPAN, s);
|
|
free(s);
|
|
}
|
|
|
|
/*
|
|
* Horrible hack to do string expansion.
|
|
* We cannot use normal push and nextc because that
|
|
* would consume characters of the current file too.
|
|
* For that reason it cleans the input and it recovers
|
|
* it later.
|
|
*/
|
|
char *
|
|
expandstring(char *line, Target *tp, struct loc *loc)
|
|
{
|
|
int c, n;
|
|
char *s;
|
|
struct input *ip = input;
|
|
|
|
input = NULL;
|
|
push(FTFILE, NULL, loc->fname, loc->lineno);
|
|
push(FTEXPAN, line);
|
|
|
|
n = 0;
|
|
s = NULL;
|
|
while ((c = nextc()) != EOF) {
|
|
if (c != '$') {
|
|
s = erealloc(s, ++n);
|
|
s[n-1] = c;
|
|
continue;
|
|
}
|
|
|
|
if ((c = nextc()) == '$') {
|
|
s = erealloc(s, n += 2);
|
|
s[n-2] = '$';
|
|
s[n-1] = '$';
|
|
} else {
|
|
back(c);
|
|
expansion(tp);
|
|
}
|
|
}
|
|
|
|
s = erealloc(s, n+1);
|
|
s[n] = '\0';
|
|
input = ip;
|
|
|
|
return s;
|
|
}
|
|
|
|
static int
|
|
item(void)
|
|
{
|
|
int c;
|
|
char *s;
|
|
char buf[MAXTOKEN];
|
|
|
|
for (s = buf; s < &buf[MAXTOKEN] - 1; ) {
|
|
c = nextc();
|
|
if (c == '$' && ahead() != '$')
|
|
expansion(NULL);
|
|
else if (validchar(c))
|
|
*s++ = c;
|
|
else
|
|
break;
|
|
}
|
|
back(c);
|
|
|
|
if (s >= &buf[MAXTOKEN] - 1)
|
|
error("token too long");
|
|
if (s == buf)
|
|
error("invalid empty token");
|
|
*s++ = '\0';
|
|
memcpy(token, buf, s - buf);
|
|
|
|
return ITEM;
|
|
}
|
|
|
|
static int
|
|
next(void)
|
|
{
|
|
int c;
|
|
|
|
repeat:
|
|
/*
|
|
* It is better to avoid skipspaces() here, because
|
|
* it can generate the need for 2 calls to back(),
|
|
* and we need the character anyway.
|
|
*/
|
|
c = nextc();
|
|
if (c == ' ' || c == '\t')
|
|
goto repeat;
|
|
|
|
if (c == '\\') {
|
|
if ((c = nextc()) == '\n')
|
|
goto repeat;
|
|
back(c);
|
|
c = '\\';
|
|
}
|
|
|
|
switch (c) {
|
|
case EOF:
|
|
strcpy(token, "<EOF>");
|
|
tok = EOF;
|
|
break;
|
|
case '$':
|
|
if ((c = nextc()) == '$')
|
|
goto single;
|
|
back(c);
|
|
expansion(NULL);
|
|
goto repeat;
|
|
case '#':
|
|
comment();
|
|
c = '\n';
|
|
case ';':
|
|
case ':':
|
|
case '=':
|
|
case '\n':
|
|
single:
|
|
token[0] = c;
|
|
token[1] = '\0';
|
|
tok = c;
|
|
break;
|
|
default:
|
|
if (!validchar(c))
|
|
error("unexpected character '%c'", c);
|
|
back(c);
|
|
tok = item();
|
|
break;
|
|
}
|
|
|
|
return tok;
|
|
}
|
|
|
|
static char *
|
|
readmacrodef(void)
|
|
{
|
|
int n, c;
|
|
char *line;
|
|
|
|
n = 0;
|
|
line = NULL;
|
|
while ((c = nextc()) != EOF) {
|
|
line = erealloc(line, n+1);
|
|
if (c == '\n')
|
|
break;
|
|
if (c == '#') {
|
|
comment();
|
|
break;
|
|
}
|
|
if (c == '\\') {
|
|
if ((c = nextc()) != '\n') {
|
|
back(c);
|
|
c = '\\';
|
|
} else {
|
|
skipspaces();
|
|
c = ' ';
|
|
}
|
|
}
|
|
|
|
line[n++] = c;
|
|
}
|
|
if (c == EOF)
|
|
error("EOF while looking for end of line");
|
|
line[n] = '\0';
|
|
|
|
return line;
|
|
}
|
|
|
|
static struct action
|
|
readcmd(void)
|
|
{
|
|
int n, c;
|
|
struct loc *loc;
|
|
struct action act;
|
|
|
|
skipspaces();
|
|
|
|
loc = getloc();
|
|
act.loc.fname = estrdup(loc->fname);
|
|
act.loc.lineno = loc->lineno;
|
|
|
|
n = 0;
|
|
act.line = NULL;
|
|
while ((c = nextc()) != EOF) {
|
|
act.line = erealloc(act.line, n+1);
|
|
if (c == '\n')
|
|
break;
|
|
if (c == '\\') {
|
|
if ((c = nextc()) == '\n') {
|
|
if ((c = nextc()) != '\t')
|
|
back(c);
|
|
continue;
|
|
}
|
|
back(c);
|
|
c = '\\';
|
|
}
|
|
act.line[n++] = c;
|
|
}
|
|
if (c == EOF)
|
|
error("EOF while looking for end of command");
|
|
act.line[n] = '\0';
|
|
|
|
return act;
|
|
}
|
|
|
|
static void
|
|
rule(char *targets[], int ntargets)
|
|
{
|
|
int c, i, j, ndeps, nactions;
|
|
struct action *acts;
|
|
char **deps = NULL;
|
|
|
|
if (ntargets == 0)
|
|
error("missing target");
|
|
|
|
for (ndeps = 0; next() == ITEM; ++ndeps) {
|
|
deps = erealloc(deps, (ndeps+1) * sizeof(char *));
|
|
deps[ndeps] = estrdup(token);
|
|
}
|
|
|
|
if (tok != '\n' && tok != ';')
|
|
error("garbage at the end of the line");
|
|
|
|
nactions = 0;
|
|
acts = NULL;
|
|
if (tok == ';') {
|
|
nactions++;
|
|
acts = erealloc(acts, nactions * sizeof(*acts));
|
|
acts[nactions-1] = readcmd();
|
|
}
|
|
|
|
for (;;) {
|
|
if ((c = nextc()) == '#') {
|
|
comment();
|
|
continue;
|
|
}
|
|
if (c != '\t')
|
|
break;
|
|
nactions++;
|
|
acts = erealloc(acts, nactions * sizeof(*acts));
|
|
acts[nactions-1] = readcmd();
|
|
}
|
|
back(c);
|
|
|
|
for (i = 0; i < ntargets; i++) {
|
|
addtarget(targets[i], ndeps);
|
|
for (j = 0; j < ndeps; j++)
|
|
adddep(targets[i], deps[j]);
|
|
if (nactions > 0)
|
|
addrule(targets[i], acts, nactions);
|
|
}
|
|
|
|
for (i = 0; i < ndeps; i++)
|
|
free(deps[i]);
|
|
free(deps);
|
|
|
|
for (i = 0; i < nactions; i++) {
|
|
free(acts[i].line);
|
|
freeloc(&acts[i].loc);
|
|
}
|
|
free(acts);
|
|
}
|
|
|
|
static void
|
|
assign(char *macros[], int where, int n)
|
|
{
|
|
char *defs;
|
|
|
|
if (n != 1)
|
|
error("invalid macro definition");
|
|
|
|
skipspaces();
|
|
defs = readmacrodef();
|
|
setmacro(*macros, defs, where, NOEXPORT);
|
|
free(defs);
|
|
}
|
|
|
|
void
|
|
parseinput(int where)
|
|
{
|
|
int i, n;
|
|
char **targets;
|
|
|
|
while (moreinput()) {
|
|
n = 0;
|
|
targets = NULL;
|
|
|
|
next();
|
|
if (tok == '\n')
|
|
continue;
|
|
|
|
while (tok == ITEM) {
|
|
n++;
|
|
targets = erealloc(targets, n * sizeof(char *));
|
|
targets[n-1] = estrdup(token);
|
|
next();
|
|
}
|
|
|
|
switch (tok) {
|
|
case ':':
|
|
rule(targets, n);
|
|
break;
|
|
case '=':
|
|
assign(targets, where, n);
|
|
break;
|
|
default:
|
|
error("unexpected token '%s'(%d)", token, tok);
|
|
}
|
|
|
|
for (i = 0; i < n; i++)
|
|
free(targets[i]);
|
|
free(targets);
|
|
}
|
|
}
|
|
|
|
int
|
|
parse(char *fname)
|
|
{
|
|
FILE *fp;
|
|
|
|
if (!fname) {
|
|
fp = stdin;
|
|
fname = "<stdin>";
|
|
} else if ((fp = fopen(fname, "r")) == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
debug("parsing %s", fname);
|
|
push(FTFILE, fp, fname, 0);
|
|
parseinput(MAKEFILE);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
inject(char *s)
|
|
{
|
|
push(FTFILE, NULL, "<internal>", 0);
|
|
push(FTEXPAN, s);
|
|
parseinput(INTERNAL);
|
|
}
|