build: omit named test source operands
This commit is contained in:
@@ -358,6 +358,78 @@ skipws(Lex *l)
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the next initial import token without ever lexing the first ordinary
|
||||
* declaration token. Go's named-file reader consumes header trivia while
|
||||
* looking for another import, so NUL/comment diagnostics in that trivia remain
|
||||
* visible; a malformed UTF-8 byte or non-leading BOM that itself begins the
|
||||
* ordinary body is outside the header and must not be diagnosed here. */
|
||||
int
|
||||
lexheaderimport(Lex *l, Tok *out)
|
||||
{
|
||||
for (;;) {
|
||||
if (l->pos >= l->srclen)
|
||||
return 0;
|
||||
unsigned char c = (unsigned char)l->src[l->pos];
|
||||
if (c == 0) {
|
||||
/* The loader necessarily reaches this byte while deciding whether
|
||||
* another import follows. Preserve the source decoder diagnostic. */
|
||||
lskipnul(l);
|
||||
return 0;
|
||||
}
|
||||
if ((c >= 0x80 && !utf8bytevalid(l->src, l->srclen, l->pos))
|
||||
|| bomat(l->src, l->srclen, l->pos))
|
||||
return 0;
|
||||
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
|
||||
lget(l);
|
||||
continue;
|
||||
}
|
||||
if (c == '/' && l->pos + 1 < l->srclen
|
||||
&& l->src[l->pos + 1] == '/') {
|
||||
lget(l); lget(l);
|
||||
linecomment(l);
|
||||
if (l->errs != 0)
|
||||
return 0;
|
||||
continue;
|
||||
}
|
||||
if (c == '/' && l->pos + 1 < l->srclen
|
||||
&& l->src[l->pos + 1] == '*') {
|
||||
lget(l); lget(l);
|
||||
int prev = -1;
|
||||
for (;;) {
|
||||
int x = lget(l);
|
||||
if (x < 0) {
|
||||
Pos p = lpos(l);
|
||||
errorf(p, "unterminated /* comment");
|
||||
l->errs++;
|
||||
return 0;
|
||||
}
|
||||
if (prev == '*' && x == '/')
|
||||
break;
|
||||
prev = x;
|
||||
}
|
||||
if (l->errs != 0)
|
||||
return 0;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* A compiler-owned source-boundary directive is an ordinary declaration
|
||||
* boundary here, just as it was when lexnext eagerly exposed the token. */
|
||||
if (l->modreset || l->modpath != NULL)
|
||||
return 0;
|
||||
static const char kw[] = "import";
|
||||
const u64 n = sizeof kw - 1;
|
||||
if (l->srclen - l->pos < n
|
||||
|| memcmp(l->src + l->pos, kw, n) != 0)
|
||||
return 0;
|
||||
if (l->srclen - l->pos > n
|
||||
&& isidcont((unsigned char)l->src[l->pos + n]))
|
||||
return 0;
|
||||
*out = lexnext(l);
|
||||
return out->kind == TK_USE;
|
||||
}
|
||||
|
||||
static u64
|
||||
parseint(const char *s, u64 n, int base, int *ok)
|
||||
{
|
||||
|
||||
145
cmd/wcc/parse.c
145
cmd/wcc/parse.c
@@ -35,6 +35,17 @@ advance(Parser *p)
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
advanceheaderimport(Parser *p)
|
||||
{
|
||||
Tok next;
|
||||
if (!lexheaderimport(p->l, &next))
|
||||
return 0;
|
||||
p->cur = next;
|
||||
p->hasla = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static Tok
|
||||
peek(Parser *p)
|
||||
{
|
||||
@@ -1359,6 +1370,140 @@ parseuse(Parser *p)
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Parse one initial import without pulling the declaration that follows it
|
||||
* into the header pass. This intentionally has stricter, single-error
|
||||
* recovery than parseuse: named test-source build loading needs only decide
|
||||
* whether the package/import header itself is valid. */
|
||||
static Node *
|
||||
parseheaderuse(Parser *p)
|
||||
{
|
||||
Pos pp = p->cur.pos;
|
||||
advance(p);
|
||||
Node *n = newnode(p->a, N_USE, pp);
|
||||
n->usefile = p->cur.pos.file;
|
||||
n->useline = p->cur.pos.line;
|
||||
n->usecol = p->cur.pos.col;
|
||||
const char *alias = NULL;
|
||||
const char *first;
|
||||
if (p->cur.kind == TK_UNDER) {
|
||||
n->useblank = 1;
|
||||
advance(p);
|
||||
} else if (p->cur.kind != TK_IDENT) {
|
||||
errorf(p->cur.pos, "expected identifier, got %s",
|
||||
tokname(p->cur.kind));
|
||||
p->errs++;
|
||||
return n;
|
||||
}
|
||||
if (p->cur.kind != TK_IDENT) {
|
||||
errorf(p->cur.pos, "expected identifier, got %s",
|
||||
tokname(p->cur.kind));
|
||||
p->errs++;
|
||||
return n;
|
||||
}
|
||||
first = p->cur.text;
|
||||
advance(p);
|
||||
if (!n->useblank && p->cur.kind == TK_IDENT) {
|
||||
alias = first;
|
||||
first = p->cur.text;
|
||||
advance(p);
|
||||
}
|
||||
const char *leaf = first;
|
||||
const char *path = leaf;
|
||||
while (p->cur.kind == TK_DOT) {
|
||||
advance(p);
|
||||
if (p->cur.kind != TK_IDENT) {
|
||||
errorf(p->cur.pos, "expected identifier, got %s",
|
||||
tokname(p->cur.kind));
|
||||
p->errs++;
|
||||
return n;
|
||||
}
|
||||
leaf = p->cur.text;
|
||||
path = aprintf(p->a, "%s.%s", path, leaf);
|
||||
advance(p);
|
||||
}
|
||||
if (!n->useblank) {
|
||||
n->str = alias ? alias : leaf;
|
||||
n->strlen = strlen(n->str);
|
||||
}
|
||||
n->usesource = path;
|
||||
n->usepath = path;
|
||||
n->usealias = alias;
|
||||
if (p->cur.kind != TK_SEMI) {
|
||||
errorf(p->cur.pos, "expected ';' after import");
|
||||
p->errs++;
|
||||
return n;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Go's named-file loader parses a valid source only through its initial
|
||||
* package/import section. Keep the broader parseimports recovery pass for
|
||||
* graph-bearing sources, but give actionless test-source omission a boundary
|
||||
* that cannot diagnose late imports or an invalid ordinary declaration body. */
|
||||
Node *
|
||||
parsepackageheader(Parser *p)
|
||||
{
|
||||
Pos fp = { p->l->file, 1, 1 };
|
||||
Node *file = newnode(p->a, N_FILE, fp);
|
||||
Node *head = NULL, *tail = NULL;
|
||||
|
||||
while (p->cur.kind == TK_MODPATH || p->cur.kind == TK_MODRESET) {
|
||||
if (p->cur.kind == TK_MODPATH) {
|
||||
p->pathmod = p->cur.text;
|
||||
p->curmod = p->cur.text;
|
||||
p->resetmod = NULL;
|
||||
} else {
|
||||
p->pathmod = NULL;
|
||||
p->curmod = p->cur.text;
|
||||
p->resetmod = p->cur.text;
|
||||
}
|
||||
p->sourceid++;
|
||||
advance(p);
|
||||
}
|
||||
if (p->cur.kind != TK_MODULE) {
|
||||
errorf(p->cur.pos, "invalid or missing package clause");
|
||||
p->errs++;
|
||||
return file;
|
||||
}
|
||||
Pos pp = p->cur.pos;
|
||||
advance(p);
|
||||
if (p->cur.kind != TK_IDENT) {
|
||||
errorf(p->cur.pos, "invalid or missing package clause");
|
||||
p->errs++;
|
||||
return file;
|
||||
}
|
||||
const char *name = p->cur.text;
|
||||
advance(p);
|
||||
if (p->cur.kind != TK_SEMI) {
|
||||
errorf(p->cur.pos, "expected ';' after package name");
|
||||
p->errs++;
|
||||
return file;
|
||||
}
|
||||
p->curpkg = name;
|
||||
if (p->pathmod == NULL && p->resetmod == NULL)
|
||||
p->curmod = name;
|
||||
file->module = name;
|
||||
file->pkgname = name;
|
||||
file->sourceid = p->sourceid;
|
||||
file->pos = pp;
|
||||
|
||||
while (advanceheaderimport(p)) {
|
||||
Node *d = parseheaderuse(p);
|
||||
d->module = p->curmod;
|
||||
d->pkgname = p->curpkg;
|
||||
d->sourceid = p->sourceid;
|
||||
if (head == NULL)
|
||||
head = d;
|
||||
else
|
||||
tail->next = d;
|
||||
tail = d;
|
||||
if (p->errs != 0)
|
||||
break;
|
||||
}
|
||||
file->list = head;
|
||||
return file;
|
||||
}
|
||||
|
||||
Node *
|
||||
parseimports(Parser *p)
|
||||
{
|
||||
|
||||
@@ -218,6 +218,7 @@ struct Lex {
|
||||
|
||||
void lexinit(Lex*, Arena*, const char *file, const char *src, u64 len);
|
||||
Tok lexnext(Lex*);
|
||||
int lexheaderimport(Lex*, Tok*);
|
||||
const char *tokname(Tkind); /* canonical spelling, e.g. "fn", "+=" */
|
||||
void tokprint(FILE*, Tok); /* one line, "%s:%d:%d: %s %q" */
|
||||
Tkind kwlookup(const char *s, u64 n); /* TK_NONE if not a keyword */
|
||||
@@ -408,6 +409,8 @@ struct Parser {
|
||||
|
||||
void parserinit(Parser*, Arena*, Lex*);
|
||||
Node *parsefile(Parser*);
|
||||
/* Package/import header only: stop before the first ordinary declaration. */
|
||||
Node *parsepackageheader(Parser*);
|
||||
/* Imports-only N_FILE: module/pos identify the first package clause,
|
||||
* list holds N_USE declarations, and body holds package-clause markers. */
|
||||
Node *parseimports(Parser*);
|
||||
|
||||
125
cmd/ww/main.c
125
cmd/ww/main.c
@@ -693,6 +693,111 @@ source_package_name(const char *path, char **out)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Named-file loading is allowed for every non-directory Stat result. Unlike
|
||||
* sep_slurp, this reader must not require seekability: a finite FIFO is still
|
||||
* a named source stream, just as it is for GoFilesPackage. */
|
||||
static int
|
||||
source_header_slurp(const char *path, char **out, u64 *len)
|
||||
{
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0) return -1;
|
||||
size_t cap = 4096, n = 0;
|
||||
char *buf = malloc(cap);
|
||||
if (buf == NULL) {
|
||||
(void)sep_fail_nomem();
|
||||
(void)close(fd);
|
||||
return -1;
|
||||
}
|
||||
for (;;) {
|
||||
if (n == cap) {
|
||||
if (cap >= INT_MAX) {
|
||||
(void)sep_fail_size();
|
||||
free(buf);
|
||||
(void)close(fd);
|
||||
return -1;
|
||||
}
|
||||
size_t nextcap = cap > (size_t)INT_MAX / 2
|
||||
? (size_t)INT_MAX : cap * 2;
|
||||
char *next = realloc(buf, nextcap);
|
||||
if (next == NULL) {
|
||||
(void)sep_fail_nomem();
|
||||
free(buf);
|
||||
(void)close(fd);
|
||||
return -1;
|
||||
}
|
||||
buf = next;
|
||||
cap = nextcap;
|
||||
}
|
||||
ssize_t got = read(fd, buf + n, cap - n);
|
||||
if (got < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
free(buf);
|
||||
(void)close(fd);
|
||||
return -1;
|
||||
}
|
||||
if (got == 0) break;
|
||||
n += (size_t)got;
|
||||
}
|
||||
if (close(fd) != 0) {
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
if (n == cap) {
|
||||
/* The loop normally grows before the read that can fill cap, but
|
||||
* retain an explicit terminator guard for future reader changes. */
|
||||
if (cap >= INT_MAX) {
|
||||
(void)sep_fail_size();
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
char *next = realloc(buf, cap + 1);
|
||||
if (next == NULL) {
|
||||
(void)sep_fail_nomem();
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
buf = next;
|
||||
}
|
||||
buf[n] = '\0';
|
||||
*out = buf;
|
||||
*len = (u64)n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Validate only the package/import header before a named test source is
|
||||
* omitted from an ordinary build. Imports are syntax, not graph edges here. */
|
||||
static int
|
||||
source_build_header(const char *path)
|
||||
{
|
||||
char *buf;
|
||||
u64 len;
|
||||
if (source_header_slurp(path, &buf, &len) < 0) {
|
||||
fprintf(stderr, "ww: cannot read %s\n", path);
|
||||
return -1;
|
||||
}
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
Parser p;
|
||||
lexinit(&l, a, path, buf, len);
|
||||
parserinit(&p, a, &l);
|
||||
Node *header = parsepackageheader(&p);
|
||||
if (l.errs || p.errs) {
|
||||
freearena(a);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
if (header->module == NULL) {
|
||||
Pos pp = { path, 1, 1 };
|
||||
errorf(pp, "invalid or missing package clause");
|
||||
freearena(a);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
freearena(a);
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
source_list_add(char ***list, int *n, int *cap, const char *path)
|
||||
{
|
||||
@@ -7474,11 +7579,31 @@ do_build(int argc, char **argv)
|
||||
}
|
||||
struct stat requested;
|
||||
int literal = stat(src, &requested) == 0;
|
||||
int requested_nondirectory = literal && !S_ISDIR(requested.st_mode);
|
||||
if (source_operand_ignored(src)) {
|
||||
source_operand_no_sources(src);
|
||||
free(incs);
|
||||
return 1;
|
||||
}
|
||||
const char *requested_base = strrchr(src, '/');
|
||||
requested_base = requested_base ? requested_base + 1 : src;
|
||||
size_t requested_baselen = strlen(requested_base);
|
||||
if (requested_nondirectory && requested_baselen >= 8
|
||||
&& strcmp(requested_base + requested_baselen - 8,
|
||||
"_test.ww") == 0) {
|
||||
if (source_build_header(src) < 0) {
|
||||
free(incs);
|
||||
return 1;
|
||||
}
|
||||
free(incs);
|
||||
if (!outflag[0] || strcmp(outflag, "/dev/null") == 0)
|
||||
return 0;
|
||||
if (build_output_dir(outflag))
|
||||
fputs("ww: no main packages to build\n", stderr);
|
||||
else
|
||||
fputs("ww: no packages to build\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
char resolved[PATH_MAX];
|
||||
int is_dir = 0;
|
||||
if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) {
|
||||
|
||||
Reference in New Issue
Block a user