Files
doslang-mirror/fec/src/resolve.c
T
coolguy 4624c6d0ec std: 표준 라이브러리가 컴파일러 옆에서 해석되고 호출된다
std 는 예약된 이름이고 프로그램이 아니라 컴파일러와 함께 있으므로 자기 루트를
갖는다 (--std=). 본문 없는 선언은 링커가 찾을 것 -- 런타임이나 C 라이브러리 --
이므로 IR 에 extern 으로 나간다.

런타임에 write/alloc/free/exit 를 넣었다. 이것이 표준 라이브러리가 스스로
말할 수 없는 전부이고 나머지는 Ferro 로 쓴다.

@trap @unreachable @size_of @align_of @line 을 내린다.

링크 이름에서 점과 괄호를 걸렀다. 유닛 경로에는 점이 있고 제네릭 인스턴스에는
괄호가 있는데 어셈블러가 받지 않는다.
2026-08-17 06:12:44 +09:00

329 lines
10 KiB
C

#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)
{
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] == '\\';
}
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;
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;
}
/* 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);
/* `std` is reserved (SPEC 10) and lives with the compiler, not with the
program, so it is looked up under its own root. */
unit_source_path(unit->path, sizeof unit->path,
(name[0]=='s' && name[1]=='t' && name[2]=='d' &&
(name[3]=='.' || name[3]==0) && b->std_root[0])
? b->std_root : 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 *std_root)
{
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;
if (std_root) {
unsigned long k = 0;
while (std_root[k] && k + 1 < sizeof build->std_root) {
build->std_root[k] = std_root[k];
++k;
}
build->std_root[k] = 0;
}
/* 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;
}