compiler: implement blank package name semantics
This commit is contained in:
@@ -63,6 +63,167 @@ check_module_stamps(const char *src, const char *want_first, const char *want_la
|
||||
return ok;
|
||||
}
|
||||
|
||||
enum packagemode {
|
||||
PACKAGE_FULL,
|
||||
PACKAGE_IMPORTS,
|
||||
PACKAGE_HEADER,
|
||||
};
|
||||
|
||||
static const char *
|
||||
packagemodename(enum packagemode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case PACKAGE_FULL: return "full";
|
||||
case PACKAGE_IMPORTS: return "imports";
|
||||
case PACKAGE_HEADER: return "header";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static Node *
|
||||
parsepackage(Parser *p, enum packagemode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case PACKAGE_FULL: return parsefile(p);
|
||||
case PACKAGE_IMPORTS: return parseimports(p);
|
||||
case PACKAGE_HEADER: return parsepackageheader(p);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* The three package-clause parsers intentionally expose different AST
|
||||
* boundaries. Header/imports outer nodes stay on the package keyword; the
|
||||
* full parser's outer file node retains its historical 1:1 root position.
|
||||
* Imports-only parsing keeps its package marker on the keyword, while full
|
||||
* parsing moves only a blank package marker to the underscore so the checker
|
||||
* can diagnose the name token. An ordinary package marker never moves.
|
||||
*/
|
||||
static int
|
||||
check_package_shape(enum packagemode mode, const char *name,
|
||||
int markerline, int markercol)
|
||||
{
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
Parser p;
|
||||
char src[160];
|
||||
snprintf(src, sizeof src,
|
||||
"// leading comment\npackage %s;\nimport alpha;\n"
|
||||
"fn x() void = {};\n", name);
|
||||
lexinit(&l, a, "shape.ww", src, strlen(src));
|
||||
parserinit(&p, a, &l);
|
||||
Node *file = parsepackage(&p, mode);
|
||||
Node *marker = file ? file->body : NULL;
|
||||
int wantmarker = mode != PACKAGE_HEADER;
|
||||
int outerline = mode == PACKAGE_FULL ? 1 : 2;
|
||||
int ok = file != NULL && file->kind == N_FILE
|
||||
&& p.errs == 0 && l.errs == 0
|
||||
&& file->pkgname != NULL && strcmp(file->pkgname, name) == 0
|
||||
&& file->pos.line == outerline && file->pos.col == 1
|
||||
&& ((mode == PACKAGE_FULL)
|
||||
|| (file->module != NULL && strcmp(file->module, name) == 0))
|
||||
&& ((!wantmarker && marker == NULL)
|
||||
|| (wantmarker && marker != NULL && marker->next == NULL
|
||||
&& marker->kind == N_FILE
|
||||
&& marker->pkgname != NULL
|
||||
&& strcmp(marker->pkgname, name) == 0
|
||||
&& marker->pos.line == markerline
|
||||
&& marker->pos.col == markercol));
|
||||
if (!ok) {
|
||||
fprintf(stderr,
|
||||
"package shape mismatch: mode=%s name=%s "
|
||||
"errs=%d/%d outer=%d:%d marker=%d:%d\n",
|
||||
packagemodename(mode), name, p.errs, l.errs,
|
||||
file ? file->pos.line : 0, file ? file->pos.col : 0,
|
||||
marker ? marker->pos.line : 0, marker ? marker->pos.col : 0);
|
||||
}
|
||||
freearena(a);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
rejects_malformed_package(enum packagemode mode)
|
||||
{
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
Parser p;
|
||||
const char *src = "package ;\nfn x() void = {};\n";
|
||||
char *diag = NULL;
|
||||
size_t diaglen = 0;
|
||||
FILE *prev = errout;
|
||||
FILE *capture = open_memstream(&diag, &diaglen);
|
||||
if (capture != NULL)
|
||||
errout = capture;
|
||||
lexinit(&l, a, "malformed.ww", src, strlen(src));
|
||||
parserinit(&p, a, &l);
|
||||
Node *file = parsepackage(&p, mode);
|
||||
int ok = file != NULL && p.errs + l.errs > 0;
|
||||
if (capture != NULL) {
|
||||
fclose(capture);
|
||||
errout = prev;
|
||||
}
|
||||
if (!ok)
|
||||
fprintf(stderr, "malformed package accepted in %s mode\n",
|
||||
packagemodename(mode));
|
||||
free(diag);
|
||||
freearena(a);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*
|
||||
* N_USE keeps the first import-spec token (alias when present) separately
|
||||
* from the first dotted-path token. That distinction is checker-visible for
|
||||
* import errors, so pin it in each parser boundary rather than inferring the
|
||||
* path position later from the binding spelling.
|
||||
*/
|
||||
static int
|
||||
check_import_path_position(enum packagemode mode, const char *spec,
|
||||
int speccol, int pathcol, const char *alias, const char *binding,
|
||||
int blank)
|
||||
{
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
Parser p;
|
||||
char src[192];
|
||||
snprintf(src, sizeof src,
|
||||
"package foo;\nimport %s;\nfn x() void = {};\n", spec);
|
||||
lexinit(&l, a, "usepath.ww", src, strlen(src));
|
||||
parserinit(&p, a, &l);
|
||||
Node *file = parsepackage(&p, mode);
|
||||
Node *u = file ? file->list : NULL;
|
||||
while (u != NULL && u->kind != N_USE)
|
||||
u = u->next;
|
||||
int ok = file != NULL && p.errs == 0 && l.errs == 0
|
||||
&& u != NULL
|
||||
&& u->pos.file != NULL && strcmp(u->pos.file, "usepath.ww") == 0
|
||||
&& u->pos.line == 2 && u->pos.col == 1
|
||||
&& u->usefile != NULL && strcmp(u->usefile, "usepath.ww") == 0
|
||||
&& u->useline == 2 && u->usecol == speccol
|
||||
&& u->usepathfile != NULL
|
||||
&& strcmp(u->usepathfile, "usepath.ww") == 0
|
||||
&& u->usepathline == 2 && u->usepathcol == pathcol
|
||||
&& u->usesource != NULL
|
||||
&& strcmp(u->usesource, "alpha.beta") == 0
|
||||
&& u->usepath != NULL && strcmp(u->usepath, "alpha.beta") == 0
|
||||
&& ((alias == NULL && u->usealias == NULL)
|
||||
|| (alias != NULL && u->usealias != NULL
|
||||
&& strcmp(u->usealias, alias) == 0))
|
||||
&& ((binding == NULL && u->str == NULL)
|
||||
|| (binding != NULL && u->str != NULL
|
||||
&& strcmp(u->str, binding) == 0))
|
||||
&& u->useblank == blank;
|
||||
if (!ok) {
|
||||
fprintf(stderr,
|
||||
"import path position mismatch: mode=%s spec=%s "
|
||||
"errs=%d/%d specpos=%d:%d pathpos=%d:%d\n",
|
||||
packagemodename(mode), spec, p.errs, l.errs,
|
||||
u ? u->useline : 0, u ? u->usecol : 0,
|
||||
u ? u->usepathline : 0, u ? u->usepathcol : 0);
|
||||
}
|
||||
freearena(a);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
@@ -114,6 +275,69 @@ main(void)
|
||||
else { fprintf(stderr, "738[6] dotted import leaf-store FAILED\n"); fail++; }
|
||||
}
|
||||
|
||||
/* `package _;` is syntax in every loader/compiler parser mode. */
|
||||
if (check_package_shape(PACKAGE_HEADER, "_", 0, 0)) pass++;
|
||||
else { fprintf(stderr, "738[7] blank package header shape FAILED\n"); fail++; }
|
||||
|
||||
if (check_package_shape(PACKAGE_IMPORTS, "_", 2, 1)) pass++;
|
||||
else { fprintf(stderr, "738[8] blank imports-only shape FAILED\n"); fail++; }
|
||||
|
||||
if (check_package_shape(PACKAGE_FULL, "_", 2, 9)) pass++;
|
||||
else { fprintf(stderr, "738[9] blank full-parser shape FAILED\n"); fail++; }
|
||||
|
||||
/* Nonblank package-marker positions remain on the package keyword. */
|
||||
if (check_package_shape(PACKAGE_HEADER, "foo", 0, 0)) pass++;
|
||||
else { fprintf(stderr, "738[10] named package header shape FAILED\n"); fail++; }
|
||||
|
||||
if (check_package_shape(PACKAGE_IMPORTS, "foo", 2, 1)) pass++;
|
||||
else { fprintf(stderr, "738[11] named imports-only shape FAILED\n"); fail++; }
|
||||
|
||||
if (check_package_shape(PACKAGE_FULL, "foo", 2, 1)) pass++;
|
||||
else { fprintf(stderr, "738[12] named full-parser shape FAILED\n"); fail++; }
|
||||
|
||||
/* Extending the name slot to `_` must not accept a missing name. */
|
||||
if (rejects_malformed_package(PACKAGE_HEADER)) pass++;
|
||||
else { fprintf(stderr, "738[13] malformed package header FAILED\n"); fail++; }
|
||||
|
||||
if (rejects_malformed_package(PACKAGE_IMPORTS)) pass++;
|
||||
else { fprintf(stderr, "738[14] malformed imports-only package FAILED\n"); fail++; }
|
||||
|
||||
if (rejects_malformed_package(PACKAGE_FULL)) pass++;
|
||||
else { fprintf(stderr, "738[15] malformed full-parser package FAILED\n"); fail++; }
|
||||
|
||||
/* Default imports use the first dotted-path identifier for both slots. */
|
||||
for (enum packagemode mode = PACKAGE_FULL; mode <= PACKAGE_HEADER; mode++) {
|
||||
if (check_import_path_position(mode, "alpha.beta", 8, 8,
|
||||
NULL, "beta", 0)) pass++;
|
||||
else {
|
||||
fprintf(stderr, "738 default import path position FAILED (%s)\n",
|
||||
packagemodename(mode));
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Explicit aliases keep the binding/spec token at col 8, path at 14. */
|
||||
for (enum packagemode mode = PACKAGE_FULL; mode <= PACKAGE_HEADER; mode++) {
|
||||
if (check_import_path_position(mode, "local alpha.beta", 8, 14,
|
||||
"local", "local", 0)) pass++;
|
||||
else {
|
||||
fprintf(stderr, "738 explicit import path position FAILED (%s)\n",
|
||||
packagemodename(mode));
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* A blank alias likewise keeps `_` at col 8 and the path at col 10. */
|
||||
for (enum packagemode mode = PACKAGE_FULL; mode <= PACKAGE_HEADER; mode++) {
|
||||
if (check_import_path_position(mode, "_ alpha.beta", 8, 10,
|
||||
NULL, NULL, 1)) pass++;
|
||||
else {
|
||||
fprintf(stderr, "738 blank import path position FAILED (%s)\n",
|
||||
packagemodename(mode));
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
printf("738_module_decl: %d pass, %d fail\n", pass, fail);
|
||||
return fail == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user