build: omit named test source operands

This commit is contained in:
2026-08-22 21:53:10 +09:00
parent 3922f1c34e
commit 2e5451a601
13 changed files with 1666 additions and 21 deletions

View File

@@ -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)
{

View File

@@ -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)
{

View File

@@ -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*);