diff --git a/fec/src/diag.c b/fec/src/diag.c index d0df0f4..53919a3 100644 --- a/fec/src/diag.c +++ b/fec/src/diag.c @@ -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++; diff --git a/fec/src/diag.h b/fec/src/diag.h index df27869..b47f53b 100644 --- a/fec/src/diag.h +++ b/fec/src/diag.h @@ -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); diff --git a/fec/src/driver.c b/fec/src/driver.c index 93002fa..168380b 100644 --- a/fec/src/driver.c +++ b/fec/src/driver.c @@ -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 && usource,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; + } } diff --git a/fec/src/resolve.c b/fec/src/resolve.c index 2cc2aae..5de4c13 100644 --- a/fec/src/resolve.c +++ b/fec/src/resolve.c @@ -1,5 +1,8 @@ #include "resolve.h" +#include "parser.h" +#include +#include #include 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; +} + +/* /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; +} diff --git a/fec/src/resolve.h b/fec/src/resolve.h index 6b49e3d..8116935 100644 --- a/fec/src/resolve.h +++ b/fec/src/resolve.h @@ -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 + `/a/b.fe` fixes ``, so a sibling `import c.d;` is looked for at + `/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 diff --git a/fec/tests/units/cycle/a.fe b/fec/tests/units/cycle/a.fe index 03dbe1c..938e94a 100644 --- a/fec/tests/units/cycle/a.fe +++ b/fec/tests/units/cycle/a.fe @@ -1,3 +1,4 @@ +// ERROR:cycle unit a; import b; diff --git a/fec/tests/units/cycle/b.fe b/fec/tests/units/cycle/b.fe index 4ca3232..c6ee482 100644 --- a/fec/tests/units/cycle/b.fe +++ b/fec/tests/units/cycle/b.fe @@ -1,4 +1,4 @@ -// ERROR:3:cycle +// ERROR:cycle unit b; import a;