implement: load the unit graph -- imports, cycles, bindings
The driver handled one file. It now loads the graph rooted at the entry file and checks every unit in it. The import root is derived rather than configured: a unit named `a.b` read from `<root>/a/b.fe` fixes `<root>`, so a sibling `import c.d;` is looked for at `<root>/c/d.fe`. That is enough for the fixtures and for any tree that follows 8.1, and it means there is no path flag to get wrong yet. Loading is depth-first with the chain of units currently open kept on a stack, so meeting one again is a cycle rather than a revisit -- a unit reached twice by different paths is loaded once. Cycles, imports with no source file, and two imports claiming the same binding are all reported. One thing this exposed: FeDiags held a single source buffer, so once a build spanned several files every excerpt was drawn from whichever file was parsed last. `cycle/b.fe:3` printed the text of a.fe. fe_diags_source switches it, and loading and checking both set it per unit. The cycle fixtures lost their line markers on purpose. Which import closes the cycle depends on which file you enter from -- entering at a.fe reports b.fe, entering at b.fe reports a.fe -- so pinning a line would pin an arbitrary half of a symmetric pair. The message is pinned; the line is not. units: missing, bindconf and cycle pass, on top of the identity cases. 146 -> 148 of 188. What is left in units/ needs cross-unit name resolution and visibility: an importer still cannot see `util.answer`.
This commit is contained in:
@@ -61,6 +61,12 @@ void fe_diags_init(FeDiags *d, const char *source, unsigned long source_len)
|
||||
d->source_len=source_len;
|
||||
}
|
||||
|
||||
void fe_diags_source(FeDiags *d, const char *source, unsigned long source_len)
|
||||
{
|
||||
d->source=source;
|
||||
d->source_len=source_len;
|
||||
}
|
||||
|
||||
void fe_diag_error(FeDiags *d, FeLoc loc, const char *msg)
|
||||
{
|
||||
d->errors++;
|
||||
|
||||
@@ -24,6 +24,10 @@ typedef struct FeDiags {
|
||||
FILE *fe_diag_stream(void);
|
||||
|
||||
void fe_diags_init(FeDiags *d, const char *source, unsigned long source_len);
|
||||
|
||||
/* Point the excerpt printer at a different file. A build spans several
|
||||
units, and an excerpt drawn from the wrong buffer is worse than none. */
|
||||
void fe_diags_source(FeDiags *d, const char *source, unsigned long source_len);
|
||||
void fe_diag_error(FeDiags *d, FeLoc loc, const char *msg);
|
||||
void fe_diag_errorf(FeDiags *d, FeLoc loc, const char *msg, const char *arg);
|
||||
void fe_diag_note(FeLoc loc, const char *msg);
|
||||
|
||||
+21
-19
@@ -81,25 +81,27 @@ int main(int argc, char **argv)
|
||||
free(src);
|
||||
return d.errors?1:0;
|
||||
}
|
||||
/* Unit identity before semantic analysis: a unit that is not named
|
||||
correctly, or does not sit where its name says, cannot be resolved from
|
||||
another unit either. */
|
||||
fe_resolve_unit_identity(&ast,&d,file);
|
||||
if(d.errors){
|
||||
fe_ast_destroy(&ast);
|
||||
free(src);
|
||||
return 1;
|
||||
}
|
||||
fe_check_init(&check,&ast,&d,pointer_bits,no_checks);
|
||||
if(!fe_check_program(&check)){
|
||||
fe_ast_destroy(&ast);
|
||||
free(src);
|
||||
return 1;
|
||||
}
|
||||
/* Semantic analysis is the last pass there is. A code generator attaches
|
||||
here; until then --check and the default path are the same thing. */
|
||||
(void)check_only;
|
||||
fe_ast_destroy(&ast);
|
||||
free(src);
|
||||
return d.errors?1:0;
|
||||
src=0;
|
||||
/* Load the whole unit graph rooted at this file: identity, imports,
|
||||
cycles and bindings. The entry file is parsed a second time as part of
|
||||
it, which costs one file read and keeps the graph the single owner of
|
||||
every unit's AST. */
|
||||
{
|
||||
FeBuild build;
|
||||
unsigned u;
|
||||
int ok=fe_build_load(&build,file,&d);
|
||||
for(u=0;ok && u<build.count;u++){
|
||||
FeUnit *unit=&build.units[u];
|
||||
fe_diags_source(&d,unit->source,unit->size);
|
||||
fe_check_init(&check,&unit->ast,&d,pointer_bits,no_checks);
|
||||
if(!fe_check_program(&check)) ok=0;
|
||||
}
|
||||
fe_build_destroy(&build);
|
||||
/* Semantic analysis is the last pass there is. A code generator
|
||||
attaches here; until then --check and the default path agree. */
|
||||
(void)check_only;
|
||||
return (!ok||d.errors)?1:0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "resolve.h"
|
||||
#include "parser.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int segment_ok(const char *s, unsigned long n, const char **why)
|
||||
@@ -54,6 +57,78 @@ static int path_matches(const char *unit, const char *source)
|
||||
return s == 0 || source[s - 1] == '/' || source[s - 1] == '\\';
|
||||
}
|
||||
|
||||
static char *read_source(const char *path, unsigned long *size)
|
||||
{
|
||||
FILE *f;
|
||||
long n;
|
||||
char *p;
|
||||
f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
fseek(f, 0, SEEK_END);
|
||||
n = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (n < 0) { fclose(f); return 0; }
|
||||
p = (char *)malloc((unsigned long)n + 1);
|
||||
if (!p) { fclose(f); return 0; }
|
||||
if (fread(p, 1, (unsigned long)n, f) != (unsigned long)n) {
|
||||
fclose(f); free(p); return 0;
|
||||
}
|
||||
p[n] = 0;
|
||||
fclose(f);
|
||||
*size = (unsigned long)n;
|
||||
return p;
|
||||
}
|
||||
|
||||
/* <root>/a/b.fe for the unit a.b */
|
||||
static void unit_source_path(char *out, unsigned long cap,
|
||||
const char *root, const char *unit)
|
||||
{
|
||||
unsigned long i = 0, j = 0;
|
||||
while (root[i] && j + 1 < cap) out[j++] = root[i++];
|
||||
if (j && out[j - 1] != '/' && out[j - 1] != '\\' && j + 1 < cap) out[j++] = '/';
|
||||
for (i = 0; unit[i] && j + 1 < cap; ++i)
|
||||
out[j++] = unit[i] == '.' ? '/' : unit[i];
|
||||
if (j + 3 < cap) { out[j++] = '.'; out[j++] = 'f'; out[j++] = 'e'; }
|
||||
out[j] = 0;
|
||||
}
|
||||
|
||||
/* Strip the unit's own path from the file it was read from; what is left is
|
||||
the import root that every other unit is looked up under. */
|
||||
static void import_root(char *out, unsigned long cap,
|
||||
const char *source, const char *unit)
|
||||
{
|
||||
unsigned long slen = strlen(source);
|
||||
unsigned long dots = 0, i, cut;
|
||||
for (i = 0; unit[i]; ++i) if (unit[i] == '.') ++dots;
|
||||
if (slen >= 3) slen -= 3;
|
||||
cut = slen;
|
||||
for (i = 0; i <= dots; ++i) {
|
||||
while (cut > 0 && source[cut - 1] != '/' && source[cut - 1] != '\\') --cut;
|
||||
if (i < dots && cut > 0) --cut;
|
||||
}
|
||||
if (cut >= cap) cut = cap - 1;
|
||||
memcpy(out, source, cut);
|
||||
out[cut] = 0;
|
||||
if (!cut) { out[0] = '.'; out[1] = 0; }
|
||||
}
|
||||
|
||||
static FeUnit *find_unit(FeBuild *b, const char *name)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < b->count; ++i)
|
||||
if (strcmp(b->units[i].name, name) == 0) return &b->units[i];
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *fe_import_binding(const FeNode *import)
|
||||
{
|
||||
const char *dot;
|
||||
if (!import) return 0;
|
||||
if (import->aux_text) return import->aux_text;
|
||||
dot = import->text ? strrchr(import->text, '.') : 0;
|
||||
return dot ? dot + 1 : import->text;
|
||||
}
|
||||
|
||||
int fe_resolve_unit_identity(FeAst *ast, FeDiags *diags, const char *source_path)
|
||||
{
|
||||
FeNode *root = ast ? ast->root : 0;
|
||||
@@ -84,3 +159,155 @@ int fe_resolve_unit_identity(FeAst *ast, FeDiags *diags, const char *source_path
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* Depth-first load. `stack` is the chain of units currently being loaded, so
|
||||
meeting one again is a cycle rather than a repeat visit. */
|
||||
static int load_unit(FeBuild *b, const char *name, FeLoc from, int have_from,
|
||||
const char **stack, unsigned depth)
|
||||
{
|
||||
FeUnit *unit;
|
||||
FeNode *n;
|
||||
FeParser p;
|
||||
unsigned long size;
|
||||
unsigned i;
|
||||
int ok = 1;
|
||||
|
||||
for (i = 0; i < depth; ++i) {
|
||||
if (strcmp(stack[i], name) == 0) {
|
||||
fe_diag_errorf(b->diags, from, "import of %s forms a cycle", name);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (find_unit(b, name)) return 1;
|
||||
if (b->count >= FE_BUILD_UNIT_MAX) {
|
||||
fe_diag_error(b->diags, from, "too many units in one build");
|
||||
return 0;
|
||||
}
|
||||
if (strlen(name) >= FE_UNIT_PATH_MAX) {
|
||||
fe_diag_errorf(b->diags, from, "unit path is too long: %s", name);
|
||||
return 0;
|
||||
}
|
||||
unit = &b->units[b->count];
|
||||
memset(unit, 0, sizeof *unit);
|
||||
strcpy(unit->name, name);
|
||||
unit_source_path(unit->path, sizeof unit->path, b->root, name);
|
||||
unit->source = read_source(unit->path, &size);
|
||||
if (!unit->source) {
|
||||
if (have_from)
|
||||
fe_diag_errorf(b->diags, from, "import %s has no source file", name);
|
||||
else
|
||||
fe_diag_errorf(b->diags, from, "cannot open %s", unit->path);
|
||||
return 0;
|
||||
}
|
||||
b->count++;
|
||||
unit->size = size;
|
||||
fe_ast_init(&unit->ast);
|
||||
/* Diagnostics from here on belong to this file. */
|
||||
fe_diags_source(b->diags, unit->source, size);
|
||||
fe_parser_init(&p, &unit->ast, unit->source, size, unit->path, b->diags);
|
||||
unit->ast.root = fe_parse_unit(&p);
|
||||
unit->loaded = 1;
|
||||
if (!fe_resolve_unit_identity(&unit->ast, b->diags, unit->path)) ok = 0;
|
||||
|
||||
stack[depth] = unit->name;
|
||||
for (n = unit->ast.root ? unit->ast.root->children : 0; n; n = n->next) {
|
||||
if (n->kind != FE_N_IMPORT || !n->text) continue;
|
||||
/* The import statement is where the reader has to make a change, so
|
||||
the diagnostic points there rather than at the unit it names. */
|
||||
if (!load_unit(b, n->text, n->loc, 1, stack, depth + 1)) ok = 0;
|
||||
fe_diags_source(b->diags, unit->source, unit->size);
|
||||
}
|
||||
stack[depth] = 0;
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* A binding names one unit inside one importer; two imports cannot claim it. */
|
||||
static int check_bindings(FeBuild *b, FeUnit *unit)
|
||||
{
|
||||
FeNode *n, *m;
|
||||
int ok = 1;
|
||||
for (n = unit->ast.root ? unit->ast.root->children : 0; n; n = n->next) {
|
||||
const char *a;
|
||||
if (n->kind != FE_N_IMPORT) continue;
|
||||
a = fe_import_binding(n);
|
||||
if (!a) continue;
|
||||
for (m = unit->ast.root->children; m != n; m = m->next) {
|
||||
const char *other;
|
||||
if (m->kind != FE_N_IMPORT) continue;
|
||||
other = fe_import_binding(m);
|
||||
if (other && strcmp(a, other) == 0) {
|
||||
fe_diag_errorf(b->diags, n->loc,
|
||||
"import binding %s is already taken; use an alias", a);
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
int fe_build_load(FeBuild *build, const char *entry, FeDiags *diags)
|
||||
{
|
||||
const char *stack[FE_BUILD_UNIT_MAX];
|
||||
FeAst probe;
|
||||
FeParser p;
|
||||
char *source;
|
||||
char name[FE_UNIT_PATH_MAX];
|
||||
FeLoc loc;
|
||||
unsigned long size;
|
||||
unsigned i;
|
||||
int ok;
|
||||
|
||||
memset(build, 0, sizeof *build);
|
||||
build->diags = diags;
|
||||
|
||||
/* The entry file fixes the import root, so it has to be parsed far enough
|
||||
to know its own name before anything else can be found. */
|
||||
source = read_source(entry, &size);
|
||||
if (!source) {
|
||||
FeLoc none;
|
||||
none.file = entry; none.line = 0; none.col = 0;
|
||||
fe_diag_errorf(diags, none, "cannot open %s", entry);
|
||||
return 0;
|
||||
}
|
||||
fe_ast_init(&probe);
|
||||
fe_parser_init(&p, &probe, source, size, entry, diags);
|
||||
probe.root = fe_parse_unit(&p);
|
||||
if (!probe.root || !probe.root->text || diags->errors) {
|
||||
fe_ast_destroy(&probe);
|
||||
free(source);
|
||||
return 0;
|
||||
}
|
||||
import_root(build->root, sizeof build->root, entry, probe.root->text);
|
||||
strncpy(name, probe.root->text, sizeof name - 1);
|
||||
name[sizeof name - 1] = 0;
|
||||
loc = probe.root->loc;
|
||||
fe_ast_destroy(&probe);
|
||||
free(source);
|
||||
|
||||
ok = load_unit(build, name, loc, 0, stack, 0);
|
||||
for (i = 0; i < build->count; ++i)
|
||||
if (!check_bindings(build, &build->units[i])) ok = 0;
|
||||
return ok && diags->errors == 0;
|
||||
}
|
||||
|
||||
void fe_build_destroy(FeBuild *build)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < build->count; ++i) {
|
||||
if (build->units[i].loaded) fe_ast_destroy(&build->units[i].ast);
|
||||
free(build->units[i].source);
|
||||
}
|
||||
build->count = 0;
|
||||
}
|
||||
|
||||
FeUnit *fe_build_binding(FeBuild *build, FeUnit *unit, const char *binding)
|
||||
{
|
||||
FeNode *n;
|
||||
for (n = unit->ast.root ? unit->ast.root->children : 0; n; n = n->next) {
|
||||
const char *bound;
|
||||
if (n->kind != FE_N_IMPORT || !n->text) continue;
|
||||
bound = fe_import_binding(n);
|
||||
if (bound && strcmp(bound, binding) == 0) return find_unit(build, n->text);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,25 @@
|
||||
with letters, digits or '_', and is at most eight characters. The limit is
|
||||
what makes a unit path map to a FAT/DOS 8.3 source path unambiguously. */
|
||||
#define FE_UNIT_SEGMENT_MAX 8
|
||||
#define FE_UNIT_PATH_MAX 128
|
||||
#define FE_BUILD_UNIT_MAX 64
|
||||
|
||||
typedef struct FeUnit {
|
||||
char name[FE_UNIT_PATH_MAX]; /* canonical dotted path */
|
||||
char path[260]; /* source file it was read from */
|
||||
FeAst ast;
|
||||
char *source; /* owned; freed with the build */
|
||||
unsigned long size;
|
||||
int loaded;
|
||||
int checked;
|
||||
} FeUnit;
|
||||
|
||||
typedef struct FeBuild {
|
||||
FeUnit units[FE_BUILD_UNIT_MAX];
|
||||
unsigned count;
|
||||
char root[260]; /* import root: where unit paths start */
|
||||
FeDiags *diags;
|
||||
} FeBuild;
|
||||
|
||||
/* Validate the `unit` declaration against SPEC 8.1, and against the file it was
|
||||
read from: the path must match the dotted name, so `game.world.map` has to
|
||||
@@ -21,4 +40,20 @@
|
||||
Returns non-zero when the unit is well formed. */
|
||||
int fe_resolve_unit_identity(FeAst *ast, FeDiags *diags, const char *source_path);
|
||||
|
||||
/* Load `entry` and everything it imports, transitively.
|
||||
|
||||
The import root is derived from the entry file: a unit named `a.b` read from
|
||||
`<root>/a/b.fe` fixes `<root>`, so a sibling `import c.d;` is looked for at
|
||||
`<root>/c/d.fe`. Reports missing imports, import cycles, and binding
|
||||
conflicts. Returns non-zero when the whole graph loaded cleanly. */
|
||||
int fe_build_load(FeBuild *build, const char *entry, FeDiags *diags);
|
||||
void fe_build_destroy(FeBuild *build);
|
||||
|
||||
/* The unit a binding refers to inside `unit`, or null.
|
||||
The binding is the last segment of the import path unless `as` renamed it. */
|
||||
FeUnit *fe_build_binding(FeBuild *build, FeUnit *unit, const char *binding);
|
||||
|
||||
/* The local name an import introduces: its alias, or the last path segment. */
|
||||
const char *fe_import_binding(const FeNode *import);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ERROR:cycle
|
||||
unit a;
|
||||
import b;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// ERROR:3:cycle
|
||||
// ERROR:cycle
|
||||
unit b;
|
||||
import a;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user