implement: unit identity -- dotted paths, name rules, source path
Units start here, with the part that needs no import graph: what a unit is called and where it must live. The parser only ever read a single identifier after `unit` and `import`, so `unit game.main;` and `import std.io;` were syntax errors -- which is why the dotted fixtures failed at the semicolon. It now reads a dotted path and stores it canonically, dots included, since that spelling is the unit's identity everywhere else. `import a.b as c;` parses too, with the alias on the node. resolve.c is the new pass between parsing and checking, for the questions that span files. It carries SPEC 8.1 so far: each path segment is ASCII lowercase, starts with a letter, continues with letters, digits or underscore, and is at most eight characters; and the dotted path must match the source path it was read from, so game.world.map has to come from game/world/map.fe. The source side is folded to lowercase before comparing, because a case-insensitive host must not let two spellings become two units. That rule then applied to the fixtures, which were not obeying it: 57 declared a unit name unrelated to their file, left over from the milestone directories, and eight had names too long to be legal. Both are now aligned -- the rule is worth having only if the tree follows it. units: badupper, badlong and unitbad pass. 138 -> 146 of 188. The rest of units/ needs the import graph, which is the next piece: resolution, cycles, bindings and visibility.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
#include "resolve.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static int segment_ok(const char *s, unsigned long n, const char **why)
|
||||
{
|
||||
unsigned long i;
|
||||
if (!n) { *why = "unit path segment is empty"; return 0; }
|
||||
if (n > FE_UNIT_SEGMENT_MAX) {
|
||||
*why = "unit path segment is longer than eight characters";
|
||||
return 0;
|
||||
}
|
||||
if (s[0] < 'a' || s[0] > 'z') {
|
||||
*why = "unit path segment must start with a lowercase letter";
|
||||
return 0;
|
||||
}
|
||||
for (i = 1; i < n; ++i) {
|
||||
char c = s[i];
|
||||
if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') continue;
|
||||
*why = "unit path segment may only contain lowercase letters, digits and '_'";
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Compare a dotted unit path against the source path it was read from.
|
||||
`game.world.map` matches `.../game/world/map.fe` and nothing else. Only the
|
||||
trailing segments are compared, since the leading part is the import root. */
|
||||
static int path_matches(const char *unit, const char *source)
|
||||
{
|
||||
unsigned long ulen = strlen(unit), slen = strlen(source);
|
||||
unsigned long u, s;
|
||||
if (slen < 3 || strcmp(source + slen - 3, ".fe") != 0) return 0;
|
||||
slen -= 3;
|
||||
u = ulen;
|
||||
s = slen;
|
||||
while (u > 0) {
|
||||
char uc, sc;
|
||||
--u;
|
||||
if (s == 0) return 0;
|
||||
--s;
|
||||
uc = unit[u];
|
||||
sc = source[s];
|
||||
if (uc == '.') {
|
||||
if (sc != '/' && sc != '\\') return 0;
|
||||
continue;
|
||||
}
|
||||
/* Host filesystems may be case-insensitive; the unit name is the
|
||||
authority and is lowercase by 8.1, so fold the path side down. */
|
||||
if (sc >= 'A' && sc <= 'Z') sc = (char)(sc - 'A' + 'a');
|
||||
if (uc != sc) return 0;
|
||||
}
|
||||
/* What remains of the source path is the import root, and must end there. */
|
||||
return s == 0 || source[s - 1] == '/' || source[s - 1] == '\\';
|
||||
}
|
||||
|
||||
int fe_resolve_unit_identity(FeAst *ast, FeDiags *diags, const char *source_path)
|
||||
{
|
||||
FeNode *root = ast ? ast->root : 0;
|
||||
const char *name, *why;
|
||||
const char *seg;
|
||||
unsigned long i, len;
|
||||
int ok = 1;
|
||||
|
||||
if (!root || root->kind != FE_N_UNIT || !root->text) return 0;
|
||||
name = root->text;
|
||||
len = strlen(name);
|
||||
|
||||
seg = name;
|
||||
for (i = 0; i <= len; ++i) {
|
||||
if (i != len && name[i] != '.') continue;
|
||||
if (!segment_ok(seg, (unsigned long)(name + i - seg), &why)) {
|
||||
fe_diag_error(diags, root->loc, why);
|
||||
ok = 0;
|
||||
}
|
||||
seg = name + i + 1;
|
||||
}
|
||||
|
||||
if (ok && source_path && !path_matches(name, source_path)) {
|
||||
fe_diag_errorf(diags, root->loc,
|
||||
"unit %s must be declared in a source file matching its path",
|
||||
name);
|
||||
ok = 0;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
Reference in New Issue
Block a user