version 1.15, 2015/02/25 14:49:14 |
version 1.21, 2015/03/01 13:39:51 |
|
|
#include <assert.h> |
#include <assert.h> |
#include <ctype.h> |
#include <ctype.h> |
#include <fcntl.h> |
#include <fcntl.h> |
#include <getopt.h> |
|
#include <libgen.h> |
|
#include <limits.h> |
#include <limits.h> |
#include <stdarg.h> |
#include <stdarg.h> |
#include <stdio.h> |
#include <stdio.h> |
Line 79 texiexit(struct texi *p) |
|
Line 77 texiexit(struct texi *p) |
|
|
|
/* Make sure we're newline-terminated. */ |
/* Make sure we're newline-terminated. */ |
if (p->outcol) |
if (p->outcol) |
putchar('\n'); |
fputc('\n', p->outfile); |
|
if (NULL != p->chapters) |
|
teximdocclose(p, 1); |
|
|
/* Unmap all files. */ |
/* Unmap all files. */ |
while (p->filepos > 0) |
while (p->filepos > 0) |
Line 180 texiputchar(struct texi *p, char c) |
|
Line 180 texiputchar(struct texi *p, char c) |
|
if (p->ign) |
if (p->ign) |
return; |
return; |
if ('.' == c && 0 == p->outcol) |
if ('.' == c && 0 == p->outcol) |
fputs("\\&", stdout); |
fputs("\\&", p->outfile); |
if ('\'' == c && 0 == p->outcol) |
if ('\'' == c && 0 == p->outcol) |
fputs("\\&", stdout); |
fputs("\\&", p->outfile); |
|
|
putchar(c); |
fputc(c, p->outfile); |
if ('\\' == c) |
if ('\\' == c) |
putchar('e'); |
fputc('e', p->outfile); |
p->seenvs = 0; |
p->seenvs = 0; |
if ('\n' == c) { |
if ('\n' == c) { |
p->outcol = 0; |
p->outcol = 0; |
Line 207 texiputchars(struct texi *p, const char *s) |
|
Line 207 texiputchars(struct texi *p, const char *s) |
|
if (p->ign) |
if (p->ign) |
return; |
return; |
if ('.' == *s && 0 == p->outcol) |
if ('.' == *s && 0 == p->outcol) |
fputs("\\&", stdout); |
fputs("\\&", p->outfile); |
if ('\'' == *s && 0 == p->outcol) |
if ('\'' == *s && 0 == p->outcol) |
fputs("\\&", stdout); |
fputs("\\&", p->outfile); |
p->outcol += fputs(s, stdout); |
p->outcol += fputs(s, p->outfile); |
p->seenvs = 0; |
p->seenvs = 0; |
} |
} |
|
|
Line 239 teximacroclose(struct texi *p) |
|
Line 239 teximacroclose(struct texi *p) |
|
return; |
return; |
|
|
if (0 == --p->outmacro) { |
if (0 == --p->outmacro) { |
putchar('\n'); |
fputc('\n', p->outfile); |
p->outcol = p->seenws = 0; |
p->outcol = p->seenws = 0; |
} |
} |
} |
} |
Line 259 teximacroopen(struct texi *p, const char *s) |
|
Line 259 teximacroopen(struct texi *p, const char *s) |
|
return; |
return; |
|
|
if (p->outcol && 0 == p->outmacro) { |
if (p->outcol && 0 == p->outmacro) { |
putchar('\n'); |
fputc('\n', p->outfile); |
p->outcol = 0; |
p->outcol = 0; |
} |
} |
|
|
if (0 == p->outmacro) |
if (0 == p->outmacro) |
putchar('.'); |
fputc('.', p->outfile); |
else |
else |
putchar(' '); |
fputc(' ', p->outfile); |
|
|
if (EOF != (rc = fputs(s, stdout))) |
if (EOF != (rc = fputs(s, p->outfile))) |
p->outcol += rc; |
p->outcol += rc; |
|
|
putchar(' '); |
fputc(' ', p->outfile); |
p->outcol++; |
p->outcol++; |
p->outmacro++; |
p->outmacro++; |
p->seenws = 0; |
p->seenws = 0; |
Line 293 teximacro(struct texi *p, const char *s) |
|
Line 293 teximacro(struct texi *p, const char *s) |
|
texierr(p, "\"%s\" in a literal scope!?", s); |
texierr(p, "\"%s\" in a literal scope!?", s); |
|
|
if (p->outcol) |
if (p->outcol) |
putchar('\n'); |
fputc('\n', p->outfile); |
|
|
putchar('.'); |
fputc('.', p->outfile); |
puts(s); |
fputs(s, p->outfile); |
|
fputc('\n', p->outfile); |
p->outcol = p->seenws = 0; |
p->outcol = p->seenws = 0; |
} |
} |
|
|
Line 330 advance(struct texi *p, size_t *pos) |
|
Line 331 advance(struct texi *p, size_t *pos) |
|
f->col = 0; |
f->col = 0; |
} else |
} else |
f->col++; |
f->col++; |
} else |
} else { |
--f->insplice; |
--f->insplice; |
|
if (0 == f->insplice) |
|
f->depth = 0; |
|
} |
|
|
(*pos)++; |
(*pos)++; |
} |
} |
Line 439 advanceto(struct texi *p, size_t *pos, size_t end) |
|
Line 443 advanceto(struct texi *p, size_t *pos, size_t end) |
|
} |
} |
|
|
static void |
static void |
texiexecmacro(struct texi *p, struct teximacro *m, size_t *pos) |
texiexecmacro(struct texi *p, struct teximacro *m, size_t sv, size_t *pos) |
{ |
{ |
size_t valsz, realsz, aasz, asz, |
size_t valsz, realsz, aasz, asz, |
ssz, i, j, k, start, end; |
ssz, i, j, k, start, end; |
Line 447 texiexecmacro(struct texi *p, struct teximacro *m, siz |
|
Line 451 texiexecmacro(struct texi *p, struct teximacro *m, siz |
|
char **args; |
char **args; |
const char *cp; |
const char *cp; |
|
|
|
/* Disregard empty macros. */ |
|
if (0 == (valsz = realsz = strlen(m->value))) |
|
return; |
|
|
|
/* |
|
* This is important: it protect us from macros that invoke more |
|
* macros, possibly going on infinitely. |
|
* We use "sv" instead of the current position because we might |
|
* be invoked at the end of the macro (i.e., insplice == 0). |
|
* The "sv" value was initialised at the start of the macro. |
|
*/ |
|
if (sv > 0) |
|
if (++p->files[p->filepos].depth > 64) |
|
texierr(p, "maximium recursive depth"); |
|
|
args = argparse(p, pos, &asz, m->argsz); |
args = argparse(p, pos, &asz, m->argsz); |
if (asz != m->argsz) |
if (asz != m->argsz) |
texiwarn(p, "invalid macro argument length"); |
texiwarn(p, "invalid macro argument length"); |
aasz = asz < m->argsz ? asz : m->argsz; |
aasz = asz < m->argsz ? asz : m->argsz; |
|
|
if (0 == aasz) { |
if (0 == aasz) { |
texisplice(p, m->value, strlen(m->value), pos); |
texisplice(p, m->value, valsz, *pos); |
return; |
return; |
} |
} |
|
|
valsz = realsz = strlen(m->value); |
|
val = strdup(m->value); |
val = strdup(m->value); |
|
|
for (i = j = 0; i < realsz; i++) { |
for (i = j = 0; i < realsz; i++) { |
Line 517 texiexecmacro(struct texi *p, struct teximacro *m, siz |
|
Line 535 texiexecmacro(struct texi *p, struct teximacro *m, siz |
|
i = end; |
i = end; |
} |
} |
|
|
texisplice(p, val, strlen(val), pos); |
texisplice(p, val, strlen(val), *pos); |
|
|
for (i = 0; i < asz; i++) |
for (i = 0; i < asz; i++) |
free(args[i]); |
free(args[i]); |
Line 574 parseword(struct texi *p, size_t *pos, char extra) |
|
Line 592 parseword(struct texi *p, size_t *pos, char extra) |
|
* index after the command name. |
* index after the command name. |
*/ |
*/ |
enum texicmd |
enum texicmd |
texicmd(struct texi *p, size_t pos, size_t *end, struct teximacro **macro) |
texicmd(const struct texi *p, size_t pos, size_t *end, struct teximacro **macro) |
{ |
{ |
size_t i, len, toksz; |
size_t i, len, toksz; |
|
|
Line 589 texicmd(struct texi *p, size_t pos, size_t *end, struc |
|
Line 607 texicmd(struct texi *p, size_t pos, size_t *end, struc |
|
return(TEXICMD__MAX); |
return(TEXICMD__MAX); |
|
|
/* Alphabetic commands are special. */ |
/* Alphabetic commands are special. */ |
if ( ! isalpha(BUF(p)[pos])) { |
if ( ! isalpha((unsigned char)BUF(p)[pos])) { |
if ((*end = pos + 1) == BUFSZ(p)) |
if ((*end = pos + 1) == BUFSZ(p)) |
return(TEXICMD__MAX); |
return(TEXICMD__MAX); |
for (i = 0; i < TEXICMD__MAX; i++) { |
for (i = 0; i < TEXICMD__MAX; i++) { |
Line 653 texicmd(struct texi *p, size_t pos, size_t *end, struc |
|
Line 671 texicmd(struct texi *p, size_t pos, size_t *end, struc |
|
int |
int |
parsearg(struct texi *p, size_t *pos, size_t num) |
parsearg(struct texi *p, size_t *pos, size_t num) |
{ |
{ |
size_t end; |
size_t end, sv; |
enum texicmd cmd; |
enum texicmd cmd; |
struct teximacro *macro; |
struct teximacro *macro; |
|
|
Line 684 parsearg(struct texi *p, size_t *pos, size_t num) |
|
Line 702 parsearg(struct texi *p, size_t *pos, size_t num) |
|
continue; |
continue; |
} |
} |
|
|
|
sv = p->files[p->filepos - 1].insplice; |
cmd = texicmd(p, *pos, &end, ¯o); |
cmd = texicmd(p, *pos, &end, ¯o); |
advanceto(p, pos, end); |
advanceto(p, pos, end); |
if (NULL != macro) |
if (NULL != macro) |
texiexecmacro(p, macro, pos); |
texiexecmacro(p, macro, sv, pos); |
if (TEXICMD__MAX == cmd) |
if (TEXICMD__MAX == cmd) |
continue; |
continue; |
if (NULL != texitoks[cmd].fp) |
if (NULL != texitoks[cmd].fp) |
Line 701 parsearg(struct texi *p, size_t *pos, size_t num) |
|
Line 720 parsearg(struct texi *p, size_t *pos, size_t num) |
|
* This will stop in the event of EOF or if we're not at a bracket. |
* This will stop in the event of EOF or if we're not at a bracket. |
*/ |
*/ |
void |
void |
parsebracket(struct texi *p, size_t *pos) |
parsebracket(struct texi *p, size_t *pos, int dostack) |
{ |
{ |
size_t end; |
size_t end, sv, stack; |
enum texicmd cmd; |
enum texicmd cmd; |
struct teximacro *macro; |
struct teximacro *macro; |
|
|
Line 714 parsebracket(struct texi *p, size_t *pos) |
|
Line 733 parsebracket(struct texi *p, size_t *pos) |
|
return; |
return; |
advance(p, pos); |
advance(p, pos); |
|
|
|
stack = 0; |
while ((*pos = advancenext(p, pos)) < BUFSZ(p)) { |
while ((*pos = advancenext(p, pos)) < BUFSZ(p)) { |
switch (BUF(p)[*pos]) { |
switch (BUF(p)[*pos]) { |
case ('}'): |
case ('}'): |
|
if (stack > 0) { |
|
stack--; |
|
advance(p, pos); |
|
texiputchar(p, '}'); |
|
continue; |
|
} |
advance(p, pos); |
advance(p, pos); |
return; |
return; |
case ('{'): |
case ('{'): |
|
if (dostack) { |
|
stack++; |
|
advance(p, pos); |
|
texiputchar(p, '{'); |
|
continue; |
|
} |
if (0 == p->ign) |
if (0 == p->ign) |
texiwarn(p, "unexpected \"{\""); |
texiwarn(p, "unexpected \"{\""); |
advance(p, pos); |
advance(p, pos); |
Line 731 parsebracket(struct texi *p, size_t *pos) |
|
Line 763 parsebracket(struct texi *p, size_t *pos) |
|
continue; |
continue; |
} |
} |
|
|
|
sv = p->files[p->filepos - 1].insplice; |
cmd = texicmd(p, *pos, &end, ¯o); |
cmd = texicmd(p, *pos, &end, ¯o); |
advanceto(p, pos, end); |
advanceto(p, pos, end); |
if (NULL != macro) |
if (NULL != macro) |
texiexecmacro(p, macro, pos); |
texiexecmacro(p, macro, sv, pos); |
if (TEXICMD__MAX == cmd) |
if (TEXICMD__MAX == cmd) |
continue; |
continue; |
if (NULL != texitoks[cmd].fp) |
if (NULL != texitoks[cmd].fp) |
Line 750 parsebracket(struct texi *p, size_t *pos) |
|
Line 783 parsebracket(struct texi *p, size_t *pos) |
|
void |
void |
parseeoln(struct texi *p, size_t *pos) |
parseeoln(struct texi *p, size_t *pos) |
{ |
{ |
size_t end; |
size_t end, sv; |
enum texicmd cmd; |
enum texicmd cmd; |
struct teximacro *macro; |
struct teximacro *macro; |
|
|
Line 779 parseeoln(struct texi *p, size_t *pos) |
|
Line 812 parseeoln(struct texi *p, size_t *pos) |
|
continue; |
continue; |
} |
} |
|
|
|
sv = p->files[p->filepos - 1].insplice; |
cmd = texicmd(p, *pos, &end, ¯o); |
cmd = texicmd(p, *pos, &end, ¯o); |
advanceto(p, pos, end); |
advanceto(p, pos, end); |
if (NULL != macro) |
if (NULL != macro) |
texiexecmacro(p, macro, pos); |
texiexecmacro(p, macro, sv, pos); |
if (TEXICMD__MAX == cmd) |
if (TEXICMD__MAX == cmd) |
continue; |
continue; |
if (NULL != texitoks[cmd].fp) |
if (NULL != texitoks[cmd].fp) |
Line 794 parseeoln(struct texi *p, size_t *pos) |
|
Line 828 parseeoln(struct texi *p, size_t *pos) |
|
} |
} |
|
|
/* |
/* |
|
* Peek to see if there's a command after subsequent whitespace. |
|
* If so, return the macro identifier. |
|
* This DOES NOT work with user-defined macros. |
|
*/ |
|
enum texicmd |
|
peekcmd(const struct texi *p, size_t pos) |
|
{ |
|
size_t end; |
|
|
|
while (pos < BUFSZ(p) && ismspace(BUF(p)[pos])) |
|
pos++; |
|
if (pos == BUFSZ(p) || '@' != BUF(p)[pos]) |
|
return(TEXICMD__MAX); |
|
return(texicmd(p, pos, &end, NULL)); |
|
} |
|
|
|
/* |
* Parse a single word or command. |
* Parse a single word or command. |
* This will return immediately at the EOF. |
* This will return immediately at the EOF. |
*/ |
*/ |
static void |
static void |
parsesingle(struct texi *p, size_t *pos) |
parsesingle(struct texi *p, size_t *pos) |
{ |
{ |
size_t end; |
size_t end, sv; |
enum texicmd cmd; |
enum texicmd cmd; |
struct teximacro *macro; |
struct teximacro *macro; |
|
|
Line 825 parsesingle(struct texi *p, size_t *pos) |
|
Line 876 parsesingle(struct texi *p, size_t *pos) |
|
return; |
return; |
} |
} |
|
|
|
sv = p->files[p->filepos - 1].insplice; |
cmd = texicmd(p, *pos, &end, ¯o); |
cmd = texicmd(p, *pos, &end, ¯o); |
advanceto(p, pos, end); |
advanceto(p, pos, end); |
if (NULL != macro) |
if (NULL != macro) |
texiexecmacro(p, macro, pos); |
texiexecmacro(p, macro, sv, pos); |
if (TEXICMD__MAX == cmd) |
if (TEXICMD__MAX == cmd) |
return; |
return; |
if (NULL != texitoks[cmd].fp) |
if (NULL != texitoks[cmd].fp) |
Line 853 parselinearg(struct texi *p, size_t *pos) |
|
Line 905 parselinearg(struct texi *p, size_t *pos) |
|
} |
} |
|
|
if (*pos < BUFSZ(p) && '{' == BUF(p)[*pos]) |
if (*pos < BUFSZ(p) && '{' == BUF(p)[*pos]) |
parsebracket(p, pos); |
parsebracket(p, pos, 0); |
else if (*pos < BUFSZ(p) && '\n' != BUF(p)[*pos]) |
else if (*pos < BUFSZ(p) && '\n' != BUF(p)[*pos]) |
parsesingle(p, pos); |
parsesingle(p, pos); |
else |
else |
Line 875 parseeof(struct texi *p) |
|
Line 927 parseeof(struct texi *p) |
|
} |
} |
|
|
void |
void |
texisplice(struct texi *p, const char *buf, size_t sz, size_t *pos) |
texisplice(struct texi *p, const char *buf, size_t sz, size_t pos) |
{ |
{ |
char *cp; |
char *cp; |
struct texifile *f; |
struct texifile *f; |
Line 892 texisplice(struct texi *p, const char *buf, size_t sz, |
|
Line 944 texisplice(struct texi *p, const char *buf, size_t sz, |
|
} |
} |
|
|
f->insplice += sz; |
f->insplice += sz; |
memmove(f->map + *pos + sz, f->map + *pos, f->mapsz - *pos); |
memmove(f->map + pos + sz, f->map + pos, f->mapsz - pos); |
memcpy(f->map + *pos, buf, sz); |
memcpy(f->map + pos, buf, sz); |
f->mapsz += sz; |
f->mapsz += sz; |
} |
} |
|
|
Line 905 texisplice(struct texi *p, const char *buf, size_t sz, |
|
Line 957 texisplice(struct texi *p, const char *buf, size_t sz, |
|
void |
void |
parseto(struct texi *p, size_t *pos, const char *endtoken) |
parseto(struct texi *p, size_t *pos, const char *endtoken) |
{ |
{ |
size_t end; |
size_t end, sv; |
enum texicmd cmd; |
enum texicmd cmd; |
size_t endtoksz; |
size_t endtoksz; |
struct teximacro *macro; |
struct teximacro *macro; |
Line 932 parseto(struct texi *p, size_t *pos, const char *endto |
|
Line 984 parseto(struct texi *p, size_t *pos, const char *endto |
|
continue; |
continue; |
} |
} |
|
|
|
sv = p->files[p->filepos - 1].insplice; |
cmd = texicmd(p, *pos, &end, ¯o); |
cmd = texicmd(p, *pos, &end, ¯o); |
advanceto(p, pos, end); |
advanceto(p, pos, end); |
if (TEXICMD_END == cmd) { |
if (TEXICMD_END == cmd) { |
Line 952 parseto(struct texi *p, size_t *pos, const char *endto |
|
Line 1005 parseto(struct texi *p, size_t *pos, const char *endto |
|
continue; |
continue; |
} |
} |
if (NULL != macro) |
if (NULL != macro) |
texiexecmacro(p, macro, pos); |
texiexecmacro(p, macro, sv, pos); |
if (TEXICMD__MAX == cmd) |
if (TEXICMD__MAX == cmd) |
continue; |
continue; |
if (NULL != texitoks[cmd].fp) |
if (NULL != texitoks[cmd].fp) |
Line 1209 argparse(struct texi *p, size_t *pos, size_t *argsz, s |
|
Line 1262 argparse(struct texi *p, size_t *pos, size_t *argsz, s |
|
args = NULL; |
args = NULL; |
*argsz = 0; |
*argsz = 0; |
|
|
|
if (*pos == BUFSZ(p)) |
|
return(args); |
|
|
if ('{' != BUF(p)[*pos] && hint) { |
if ('{' != BUF(p)[*pos] && hint) { |
/* |
/* |
* Special case: if we encounter an unbracketed argument |
* Special case: if we encounter an unbracketed argument |
Line 1235 argparse(struct texi *p, size_t *pos, size_t *argsz, s |
|
Line 1291 argparse(struct texi *p, size_t *pos, size_t *argsz, s |
|
} else if ('{' != BUF(p)[*pos]) |
} else if ('{' != BUF(p)[*pos]) |
return(args); |
return(args); |
|
|
|
assert('{' == BUF(p)[*pos]); |
|
|
/* Parse til the closing '}', putting into the array. */ |
/* Parse til the closing '}', putting into the array. */ |
advance(p, pos); |
advance(p, pos); |
while (*pos < BUFSZ(p)) { |
while (*pos < BUFSZ(p)) { |
Line 1291 argparse(struct texi *p, size_t *pos, size_t *argsz, s |
|
Line 1349 argparse(struct texi *p, size_t *pos, size_t *argsz, s |
|
advance(p, pos); |
advance(p, pos); |
return(args); |
return(args); |
} |
} |
|
|
|
/* |
|
* If we're printing chapters, then do some naviation here and then |
|
* close our outfile. |
|
* I want to call this the SEE ALSO section, but that's not really what |
|
* it is: we'll refer to the "initial" (top) node and the next and |
|
* previous chapters. |
|
*/ |
|
void |
|
teximdocclose(struct texi *p, int last) |
|
{ |
|
char buf[PATH_MAX]; |
|
|
|
if (NULL == p->chapters || 0 == p->chapnum) |
|
return; |
|
|
|
teximacro(p, "Sh INFO NAVIGATION"); |
|
|
|
/* Print a reference to the "top" node. */ |
|
if (p->chapnum > 1) { |
|
snprintf(buf, sizeof(buf), "node1 7"); |
|
teximacroopen(p, "Xr "); |
|
texiputchars(p, buf); |
|
texiputchars(p, " ,"); |
|
teximacroclose(p); |
|
} |
|
|
|
/* Print a reference to the previous node. */ |
|
if (p->chapnum > 2) { |
|
snprintf(buf, sizeof(buf), |
|
"node%zu 7", p->chapnum - 1); |
|
teximacroopen(p, "Xr "); |
|
texiputchars(p, buf); |
|
if ( ! last) |
|
texiputchars(p, " ,"); |
|
teximacroclose(p); |
|
} |
|
|
|
/* Print a reference to the next node. */ |
|
if ( ! last) { |
|
snprintf(buf, sizeof(buf), |
|
"node%zu 7", p->chapnum + 1); |
|
teximacroopen(p, "Xr "); |
|
texiputchars(p, buf); |
|
teximacroclose(p); |
|
} |
|
|
|
fclose(p->outfile); |
|
} |
|
|
|
/* |
|
* Open a mdoc(7) context. |
|
* If we're printing chapters, then open the outfile here, too. |
|
* Otherwise just print the mdoc(7) prologue. |
|
*/ |
|
void |
|
teximdocopen(struct texi *p, size_t *pos) |
|
{ |
|
const char *cp; |
|
time_t t; |
|
char date[32]; |
|
char fname[PATH_MAX]; |
|
|
|
if (NULL != p->chapters) { |
|
snprintf(fname, sizeof(fname), "%s/node%zu.7", |
|
p->chapters, ++p->chapnum); |
|
p->outfile = fopen(fname, "w"); |
|
if (NULL == p->outfile) |
|
texiabort(p, fname); |
|
} |
|
|
|
/* |
|
* Here we print our standard mdoc(7) prologue. |
|
* We use the title set with @settitle for the `Nd' description |
|
* and the source document filename (the first one as invoked on |
|
* the command line) for the title. |
|
* The date is set to the current date. |
|
*/ |
|
t = time(NULL); |
|
strftime(date, sizeof(date), "%F", localtime(&t)); |
|
|
|
teximacroopen(p, "Dd"); |
|
texiputchars(p, date); |
|
teximacroclose(p); |
|
teximacroopen(p, "Dt"); |
|
for (cp = p->title; '\0' != *cp; cp++) |
|
texiputchar(p, toupper((unsigned int)*cp)); |
|
texiputchars(p, " 7"); |
|
teximacroclose(p); |
|
teximacro(p, "Os"); |
|
teximacro(p, "Sh NAME"); |
|
teximacroopen(p, "Nm"); |
|
for (cp = p->title; '\0' != *cp; cp++) |
|
texiputchar(p, *cp); |
|
teximacroclose(p); |
|
teximacroopen(p, "Nd"); |
|
/* |
|
* The subtitle `Nd' can consist of arbitrary macros, so paste |
|
* it and parse to the end of the line. |
|
*/ |
|
if (NULL != p->subtitle) { |
|
texisplice(p, p->subtitle, strlen(p->subtitle), *pos); |
|
parseeoln(p, pos); |
|
} else |
|
texiputchars(p, "Unknown description"); |
|
teximacroclose(p); |
|
p->seenvs = 1; |
|
} |
|
|