compiler: load imports and enforce package exports

This commit is contained in:
2026-08-11 22:25:59 +09:00
parent 7bc96b4e03
commit db96422f74
14 changed files with 894 additions and 118 deletions

View File

@@ -100,6 +100,59 @@ static Node *parsetype(Parser *p);
static Node *parseblock(Parser *p);
static Node *parsestmt(Parser *p);
static void
skipdecl(Parser *p)
{
int paren = 0, bracket = 0, brace = 0;
while (p->cur.kind != TK_EOF) {
switch (p->cur.kind) {
case TK_LPAREN: paren++; break;
case TK_RPAREN: if (paren > 0) paren--; break;
case TK_LBRACK: bracket++; break;
case TK_RBRACK: if (bracket > 0) bracket--; break;
case TK_LBRACE: brace++; break;
case TK_RBRACE: if (brace > 0) brace--; break;
case TK_SEMI:
advance(p);
if (paren == 0 && bracket == 0 && brace == 0)
return;
continue;
default:
break;
}
advance(p);
}
}
/* Consume attribute syntax without parsing its expressions or any following
* declaration. The imports-only pass needs only to recognize that the next
* declaration is an attributed import, which full parsing rejects. */
static void
skipimportattrs(Parser *p)
{
while (p->cur.kind == TK_AT) {
advance(p);
if (p->cur.kind == TK_IDENT)
advance(p);
else {
errorf(p->cur.pos, "expected identifier, got %s",
tokname(p->cur.kind));
p->errs++;
}
if (!accept(p, TK_LPAREN)) continue;
int depth = 1;
while (p->cur.kind != TK_EOF && depth > 0) {
if (p->cur.kind == TK_LPAREN) depth++;
else if (p->cur.kind == TK_RPAREN) depth--;
advance(p);
}
if (depth > 0) {
errorf(p->cur.pos, "expected ')' after attribute");
p->errs++;
}
}
}
static Node *
parseparams(Parser *p)
{
@@ -1277,25 +1330,134 @@ parseuse(Parser *p)
/* M1 #22: accumulate the full dotted import path (n->module) so the
* checker can match decl identity on the path, while n->str stays the
* leaf alias the user writes (`utf8.x`). */
char pathbuf[256];
size_t pl = 0;
const char *leaf = expectident(p);
for (size_t i = 0; leaf[i] && pl + 1 < sizeof pathbuf; i++)
pathbuf[pl++] = leaf[i];
const char *path = leaf;
while (accept(p, TK_DOT)) {
leaf = expectident(p);
if (pl + 1 < sizeof pathbuf) pathbuf[pl++] = '.';
for (size_t i = 0; leaf[i] && pl + 1 < sizeof pathbuf; i++)
pathbuf[pl++] = leaf[i];
path = aprintf(p->a, "%s.%s", path, leaf);
}
pathbuf[pl] = '\0';
n->str = leaf;
n->strlen = strlen(leaf);
n->usepath = astrndup(p->a, pathbuf, pl);
n->usepath = path;
expect(p, TK_SEMI);
return n;
}
Node *
parseimports(Parser *p)
{
Pos fp = { p->l->file, 1, 1 };
Node *file = newnode(p->a, N_FILE, fp);
Node *head = NULL, *tail = NULL;
Node *packages = NULL, *packagetail = NULL;
int sawpackage = 0;
while (p->cur.kind != TK_EOF) {
/* Compiler/driver bundle markers carry package identity out of
* band. They are not source declarations, so keep scanning for
* the following package clause and imports. */
if (p->cur.kind == TK_MODPATH) {
sawpackage = 0;
p->pathmod = p->cur.text;
p->curmod = p->cur.text;
p->resetmod = NULL;
advance(p);
continue;
}
if (p->cur.kind == TK_MODRESET) {
const char *rp = p->cur.text;
sawpackage = 0;
advance(p);
p->pathmod = NULL;
p->curmod = rp;
p->resetmod = rp;
continue;
}
if (p->cur.kind == TK_MODULE) {
Pos pp = p->cur.pos;
advance(p);
if (p->cur.kind != TK_IDENT) {
errorf(p->cur.pos, "invalid or missing package clause");
p->errs++;
sawpackage = 1;
skipdecl(p);
continue;
}
const char *name = expectident(p);
expect(p, TK_SEMI);
p->curmod = name;
Node *package = newnode(p->a, N_FILE, pp);
package->module = name;
if (packages == NULL)
packages = package;
else
packagetail->next = package;
packagetail = package;
if (!sawpackage) {
file->module = name;
file->pos = pp;
sawpackage = 1;
}
continue;
}
if (!sawpackage && p->pathmod == NULL && p->resetmod == NULL) {
errorf(p->cur.pos, "invalid or missing package clause");
p->errs++;
sawpackage = 1;
}
if (p->cur.kind == TK_USE) {
Node *d = parseuse(p);
d->module = p->curmod;
if (head == NULL)
head = d;
else
tail->next = d;
tail = d;
continue;
}
if (p->cur.kind == TK_AT) {
skipimportattrs(p);
if (p->cur.kind == TK_EXPORT) advance(p);
if (p->cur.kind == TK_USE) {
errorf(p->cur.pos,
"import cannot be exported or attributed");
p->errs++;
Node *d = parseuse(p);
d->module = p->curmod;
if (head == NULL)
head = d;
else
tail->next = d;
tail = d;
continue;
}
skipdecl(p);
continue;
}
if (p->cur.kind == TK_EXPORT && peek(p).kind == TK_USE) {
advance(p);
errorf(p->cur.pos,
"import cannot be exported or attributed");
p->errs++;
Node *d = parseuse(p);
d->module = p->curmod;
if (head == NULL)
head = d;
else
tail->next = d;
tail = d;
continue;
}
skipdecl(p);
}
file->list = head;
/* The package-clause chain lets a directory loader validate every
* selected file without inventing a second header grammar. It lives in
* body because list is the public imports chain. */
file->body = packages;
return file;
}
static Node *
parsedef(Parser *p, int exp)
{