/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1989-2011 AT&T Intellectual Property *
* and is licensed under the *
* Eclipse Public License, Version 1.0 *
* by AT&T Intellectual Property *
* *
* A copy of the License is available at *
* http://www.eclipse.org/org/documents/epl-v10.html *
* (with md5 checksum b35adb5213ca9657e911e9befb180842) *
* *
* Information and Software Systems Research *
* AT&T Research *
* Florham Park NJ *
* *
* Glenn Fowler <gsf@research.att.com> *
* *
***********************************************************************/
#pragma prototyped
/*
* Glenn Fowler
* AT&T Bell Laboratories
*
* convert a make abstract machine stream on stdin
* to an oldmake makefile on stdout
*/
static const char usage[] =
"[-?\n@(#)$Id: mamold (AT&T Research) 1989-03-22 $\n]"
USAGE_LICENSE
"[+NAME?mamold - make abstract machine to oldmake makefile conversion filter]"
"[+DESCRIPTION?\bmamold\b reads MAM (Make Abstract Machine) target and"
" prerequisite file descriptions from the standard input and writes"
" an equivalent \bgmake\b(1) makefile on the standard output. Mamfiles"
" are generated by the \b--mam\b option of \bnmake\b(1) and"
" \bgmake\b(1).]"
"[+?Symbolic information is preserved where possible in the output makefile."
" Comments, however, are lost in the conversion. Recursive makefiles"
" are not converted; each makefile level must be converted separately.]"
"[d:debug?]#[level]"
"[g:graph?Verify the dependency graph but do not generate a makefile on"
" the standard output.]"
"[h:header?Use \atext\as instead of the default for the generated makefile"
" header.]#[text]"
"[x:omit?Omit pathnames with directory \aprefix\a.]#[prefix]"
"[+SEE ALSO?\bmamnew\b(1), \bgmake\b(1), \bnmake\b(1)]"
"[+REFERENCES]{"
" [+A Make abstract machine, Glenn Fowler, 1994,?"
" http://www.research.att.com/~gsf/mam/]"
"}"
;
#include <ast.h>
#include <mam.h>
#include <ctype.h>
#include <error.h>
#include <stdio.h>
#define LONGLINE 72 /* too long output line length */
#define A_listprereq (A_LAST<<1) /* prereqs listed */
#define A_listtarg (A_LAST<<2) /* listed as target */
struct state /* program state */
{
int graph; /* output dependency graph info */
int header; /* header supplied */
int heredoc; /* last value had << */
struct mam* mam; /* make abstract machine info */
struct block* omit; /* dir prefixes to omit */
};
static struct state state;
/*
* clear listprereq for all prerequisites
*/
static void
clrprereqs(register struct rule* r)
{
register struct list* p;
r->attributes &= ~A_listprereq;
for (p = r->prereqs; p; p = p->next)
if (p->rule->attributes & A_listprereq)
clrprereqs(p->rule);
for (p = r->implicit; p; p = p->next)
if (p->rule->attributes & A_listprereq)
clrprereqs(p->rule);
}
/*
* dump a value that may be expanded by oldmake
*/
static int
dumpvalue(register int col, register char* s, int sep)
{
register int c;
register char* v;
int dollar;
int escape = 0;
int quote = 0;
if (sep)
{
sfputc(sfstdout, sep);
col++;
}
for (;;)
switch (c = *s++)
{
case 0:
if (sep && sep != '\t')
{
col = 1;
sfputc(sfstdout, '\n');
}
return col;
case ' ':
case '\t':
if (sep == '\t' && state.heredoc || col < LONGLINE - 8)
goto emit;
while (isspace(*s))
s++;
if (*s)
{
sfputr(sfstdout, " \\\n\t", -1);
col = 8;
}
break;
case '#':
sfputr(sfstdout, "$(sharp)", -1);
col += 8;
break;
case '<':
if (sep == '\t' && *s == c)
state.heredoc = 1;
goto emit;
case '\'':
quote = !quote;
goto emit;
case '\\':
if (*s != '$' && *s != '\'')
goto emit;
s++;
escape = -1;
/*FALLTHROUGH*/
case '$':
escape++;
dollar = 1;
if (isalpha(*s) || *s == '_')
{
for (v = s; isalnum(*v) || *v == '_'; v++);
c = *v;
*v = 0;
if (getvar(state.mam->main, s))
{
sfprintf(sfstdout, "$(%s)", s);
col += (v - s) + 3;
*(s = v) = c;
escape = 0;
break;
}
*v = c;
}
if (escape)
{
escape = 0;
if (!quote)
switch (*s)
{
case '{':
if (*(v = s + 1))
v++;
while (isalnum(*v) || *v == '_')
v++;
switch (*v)
{
case ':':
case '-':
case '+':
break;
default:
dollar = 0;
break;
}
break;
case '$':
s--;
do
{
sfputc(sfstdout, '$');
col++;
} while (*++s == '$');
break;
default:
sfputc(sfstdout, '\\');
col++;
break;
}
}
else
dollar = 0;
c = '$';
if (dollar)
{
sfputc(sfstdout, c);
col++;
}
/*FALLTHROUGH*/
default:
emit:
sfputc(sfstdout, c);
col++;
break;
}
}
/*
* dump a name keeping track of the right margin
*/
static int
dumpname(int col, char* s)
{
register int n;
if (!state.graph)
{
n = strlen(s);
if (col + n >= LONGLINE)
{
sfputr(sfstdout, " \\\n\t\t", -1);
col = 16;
}
else if (col <= 1)
col = 1;
else
{
sfputc(sfstdout, ' ');
col++;
}
col += n;
}
else if (col++ > 1)
sfputc(sfstdout, ' ');
dumpvalue(0, s, 0);
return col;
}
/*
* dump an action
*/
static void
dumpaction(register struct block* p)
{
if (p)
{
state.heredoc = 0;
for (;;)
{
dumpvalue(0, p->data, '\t');
if (!(p = p->next))
break;
sfputr(sfstdout, "$(newline)", -1);
if (!state.heredoc)
sfputr(sfstdout, " \\\n", -1);
}
sfputc(sfstdout, '\n');
}
}
/*
* dump r and its implicit prerequisites
*/
static int
dumpprereqs(register int col, register struct rule* r)
{
register struct block* d;
register struct list* p;
if (!(r->attributes & A_listprereq))
{
r->attributes |= A_listprereq;
for (d = state.omit; d; d = d->next)
if (strmatch(r->name, d->data))
return col;
col = dumpname(col, r->name);
for (p = r->implicit; p; p = p->next)
col = dumpprereqs(col, p->rule);
}
return col;
}
/*
* dump the rules
*/
static void
dump(register struct rule* r)
{
register int col;
register struct list* p;
if (!(r->attributes & (A_listtarg|A_metarule)))
{
r->attributes |= A_listtarg;
if (r->action || r->prereqs)
{
clrprereqs(r);
r->attributes |= A_listprereq;
sfputc(sfstdout, '\n');
col = dumpname(1, r->name);
col = dumpname(col, ":");
for (p = r->prereqs; p; p = p->next)
if (!(p->rule->attributes & A_listprereq))
{
clrprereqs(p->rule);
col = dumpprereqs(col, p->rule);
}
sfputc(sfstdout, '\n');
if (!state.graph)
dumpaction(r->action);
}
for (p = r->prereqs; p; p = p->next)
if (p->rule != r)
dump(p->rule);
for (p = r->implicit; p; p = p->next)
if (p->rule != r)
dump(p->rule);
}
}
/*
* dump var definition
*/
static int
dumpvar(const char* an, char* av, void* handle)
{
char* name = (char*)an;
struct var* v = (struct var*)av;
register char* s;
register char* t;
register int c;
NoP(handle);
if (*v->value)
{
s = t = v->value;
while (c = *t++ = *s++)
{
if (c == '\\')
{
if (!(*t++ = *s++))
break;
}
else if (c == '"')
t--;
}
dumpvalue(dumpname(0, name), v->value, '=');
}
return 0;
}
/*
* add prefix to list of dir prefixes to omit
*/
static void
omit(char* prefix)
{
int n;
struct block* p;
n = strlen(prefix);
p = newof(0, struct block, 1, n + 1);
strcpy(p->data = (char*)p + sizeof(struct block), prefix);
strcpy(p->data + n, "*");
p->next = state.omit;
state.omit = p;
}
int
main(int argc, char** argv)
{
register struct list* p;
NoP(argc);
error_info.id = "mamold";
for (;;)
{
switch (optget(argv, usage))
{
case 'd':
error_info.trace = -opt_info.num;
continue;
case 'g':
state.graph = 1;
continue;
case 'h':
state.header = 1;
sfputr(sfstdout, opt_info.arg, '\n');
continue;
case 'x':
omit(opt_info.arg);
continue;
case '?':
error(ERROR_USAGE|4, opt_info.arg);
break;
case ':':
error(2, opt_info.arg);
break;
}
break;
}
if (error_info.errors)
error(ERROR_USAGE|4, "%s", optusage(NiL));
/*
* initialize
*/
omit("/usr/include");
omit("/");
/*
* scan, collect and dump
*/
if (!state.graph && !state.header)
{
sfprintf(sfstdout, "# # oldmake makefile generated by mamold # #\n");
sfprintf(sfstdout, "# oldmake ... null='' sharp='$(null)#' newline='$(null)\n");
sfprintf(sfstdout, "# '\n");
sfprintf(sfstdout, "newline=;\n");
}
if (!(state.mam = mamalloc()))
error(3, "cannot initialize");
mamvar(state.mam->main, "HOME", "");
mamvar(state.mam->main, "newline", "");
if (mamscan(state.mam, NiL) < 0)
error(3, "invalid input");
if (!state.graph)
hashwalk(state.mam->main->vars, 0, dumpvar, NiL);
for (p = state.mam->main->root->prereqs; p; p = p->next)
dump(p->rule);
exit(error_info.errors != 0);
}