module.c 10.0 KiB raw
1
#include <libgen.h>
2
#include <stdio.h>
3
#include <stdlib.h>
4
#include <string.h>
5
6
#include "ast.h"
7
#include "io.h"
8
#include "limits.h"
9
#include "module.h"
10
#include "parser.h"
11
#include "scanner.h"
12
#include "symtab.h"
13
#include "util.h"
14
15
/* Print an error string to `stderr`. */
16
#define error(...)                                                             \
17
    do {                                                                       \
18
        fprintf(stderr, "module: ");                                           \
19
        fprintf(stderr, __VA_ARGS__);                                          \
20
        fprintf(stderr, "\n");                                                 \
21
    } while (0)
22
23
/* Extract the directory part of a path */
24
static void getdirname(const char *path, char *result, size_t maxlen) {
25
    char buf[MAX_PATH_LEN];
26
    strndup(buf, path, MAX_PATH_LEN);
27
28
    char *dir = dirname(buf);
29
    strndup(result, dir, maxlen);
30
}
31
32
/* Check if a file has .rad extension */
33
static bool is_source(const char *path) {
34
    const char *dot = strrchr(path, '.');
35
    if (!dot)
36
        return false;
37
    return strcmp(dot, SOURCE_EXT) == 0;
38
}
39
40
/* Helper function to convert path with / to qualified name with :: */
41
static void path_to_qualified(const char *path, char *qualified, usize len) {
42
    usize j = 0;
43
44
    for (usize i = 0; path[i] && j < len - 2; i++) {
45
        if (path[i] == '/') {
46
            qualified[j++] = ':';
47
            qualified[j++] = ':';
48
        } else {
49
            qualified[j++] = path[i];
50
        }
51
    }
52
    qualified[j] = '\0';
53
}
54
55
/* Extract the file name without directory and extension */
56
static void getbasename(const char *path, char *result, size_t maxlen) {
57
    char buf[MAX_PATH_LEN];
58
    strndup(buf, path, MAX_PATH_LEN);
59
60
    /* Remove .rad extension if present */
61
    char *base = basename(buf);
62
    if (is_source(base)) {
63
        *strrchr(base, '.') = '\0';
64
    }
65
    strndup(result, base, maxlen);
66
}
67
68
/* Initialize the module manager */
69
void module_manager_init(module_manager_t *mm, const char *entryfile) {
70
    mm->nmodules = 0;
71
    getdirname(entryfile, mm->rootdir, MAX_PATH_LEN);
72
}
73
74
/* Initialize a module */
75
static void module_init(module_t *module, const char *path) {
76
    memset(module, 0, sizeof(*module));
77
    strndup(module->path, path, MAX_PATH_LEN);
78
    getbasename(path, module->name, MAX_PATH_LEN);
79
    strndup(module->qualified, module->name, MAX_PATH_LEN);
80
81
    module->state      = MODULE_STATE_UNVISITED;
82
    module->declared   = false;
83
    module->checked    = false;
84
    module->compiled   = false;
85
    module->source     = NULL;
86
    module->ast        = NULL;
87
    module->scope      = NULL;
88
    module->default_fn = NULL;
89
    module->parent     = NULL;
90
    module->nchildren  = 0;
91
}
92
93
/* Helper function to create a module path from a string */
94
void module_path(char dest[MAX_QUALIFIED_NAME], const char *name) {
95
    strncpy(dest, name, MAX_QUALIFIED_NAME);
96
}
97
98
/* Helper function to append a path component to a module path */
99
void module_qualify_str(
100
    char dest[MAX_QUALIFIED_NAME], const char *child, u16 len
101
) {
102
    strlcat(dest, "::", MAX_QUALIFIED_NAME);
103
    strncat(dest, child, len);
104
}
105
106
/* Helper function to append a path component to a module path */
107
void module_qualify(char dest[MAX_QUALIFIED_NAME], node_t *ident) {
108
    module_qualify_str(dest, ident->val.ident.name, ident->val.ident.length);
109
}
110
111
/* Add a module to the manager with custom qualified name */
112
module_t *module_manager_register_qualified(
113
    module_manager_t *mm, const char *path, const char *qualified
114
) {
115
    if (!mm || !path)
116
        return NULL;
117
118
    if (mm->nmodules >= MAX_MODULES) {
119
        error("maximum number of modules (%d) exceeded", MAX_MODULES);
120
        return NULL;
121
    }
122
    module_t *mod = NULL;
123
    if ((mod = module_manager_lookup(mm, path))) {
124
        return mod;
125
    }
126
    mod = &mm->modules[mm->nmodules++];
127
    module_init(mod, path);
128
129
    /* Build hierarchy if qualified name provided */
130
    if (qualified) {
131
        char path_copy[MAX_PATH_LEN];
132
        strncpy(path_copy, qualified, MAX_PATH_LEN - 1);
133
        path_copy[MAX_PATH_LEN - 1] = '\0';
134
135
        /* Remove .rad extension */
136
        char *ext = strrchr(path_copy, '.');
137
        if (ext)
138
            *ext = '\0';
139
140
        /* Check if this is a submodule (contains /) */
141
        char *slash = strrchr(path_copy, '/'); // Find LAST slash
142
        if (slash) {
143
            *slash            = '\0';
144
            char *parent_path = path_copy; // Everything before last slash
145
            char *child_name  = slash + 1; // Everything after last slash
146
147
            /* Convert parent path to qualified name for lookup */
148
            char parent_q[MAX_PATH_LEN];
149
            path_to_qualified(parent_path, parent_q, MAX_PATH_LEN);
150
151
            /* Look up parent using :: notation */
152
            module_t *parent =
153
                module_manager_lookup_by_qualified_name(mm, parent_q);
154
155
            if (!parent) {
156
                error(
157
                    "parent module '%s' not found for '%s'", parent_q, qualified
158
                );
159
                mm->nmodules--; // Rollback the module we just added
160
                return NULL;
161
            }
162
            mod->parent                           = parent;
163
            parent->children[parent->nchildren++] = mod;
164
165
            /* Write module qualified name */
166
            module_path(mod->qualified, parent_q);
167
            module_qualify_str(mod->qualified, child_name, strlen(child_name));
168
        } else {
169
            /* No parent (root-level module) */
170
            module_path(mod->qualified, path_copy);
171
        }
172
    }
173
174
    return mod;
175
}
176
177
/* Add a module to the manager */
178
module_t *module_manager_register(module_manager_t *mm, const char *path) {
179
    return module_manager_register_qualified(mm, path, NULL);
180
}
181
182
/* Find a module in the manager by path */
183
module_t *module_manager_lookup(module_manager_t *mm, const char *path) {
184
    for (usize i = 0; i < mm->nmodules; i++) {
185
        if (strcmp(mm->modules[i].path, path) == 0) {
186
            return &mm->modules[i];
187
        }
188
    }
189
    return NULL;
190
}
191
192
/* Find a module by name in the module manager */
193
module_t *module_manager_lookup_by_name(
194
    module_manager_t *mm, const char *name, u16 length
195
) {
196
    for (usize j = 0; j < mm->nmodules; j++) {
197
        if (strncmp(mm->modules[j].name, name, length) == 0 &&
198
            strlen(mm->modules[j].name) == length) {
199
            return &mm->modules[j];
200
        }
201
    }
202
    return NULL;
203
}
204
205
/* Find a module by qualified name in the module manager */
206
module_t *module_manager_lookup_by_qualified_name(
207
    module_manager_t *mm, const char *name
208
) {
209
    for (usize j = 0; j < mm->nmodules; j++) {
210
        if (strcmp(mm->modules[j].qualified, name) == 0) {
211
            return &mm->modules[j];
212
        }
213
    }
214
    return NULL;
215
}
216
217
/* Parse a single module, attaching diagnostics on failure. */
218
bool module_parse(module_t *module, i32 *err) {
219
    i32 size = readfile(module->path, &module->source);
220
    if (size < 0) {
221
        *err = MODULE_NOT_FOUND;
222
        error("unable to read module file at '%s'", module->path);
223
        return false;
224
    }
225
    scanner_init(&module->parser.scanner, module->path, module->source);
226
    parser_init(&module->parser);
227
228
    node_t *ast = parser_parse(&module->parser);
229
    if (module->parser.errors) {
230
        *err = MODULE_PARSE_ERROR;
231
        return false;
232
    }
233
    if (!ast) {
234
        *err = MODULE_PARSE_ERROR;
235
        error("failed to parse module '%s'", module->path);
236
        return false;
237
    }
238
    if (ast->cls != NODE_MOD_BODY) {
239
        *err = MODULE_PARSE_ERROR;
240
        error(
241
            "module '%s' parsed with unexpected root node (%d)",
242
            module->path,
243
            ast->cls
244
        );
245
        return false;
246
    }
247
248
    symbol_t sym = (symbol_t){
249
        .name   = module->name,
250
        .length = strlen(module->name),
251
        .node   = ast,
252
        .kind   = SYM_MODULE,
253
        .e.mod  = module,
254
    };
255
    module->ast      = ast;
256
    module->ast->sym = alloc_symbol(sym);
257
258
    return true;
259
}
260
261
bool module_manager_parse(module_manager_t *mm, i32 *err) {
262
    *err = MODULE_OK;
263
264
    for (usize i = 0; i < mm->nmodules; i++) {
265
        if (!(module_parse(&mm->modules[i], err))) {
266
            return false;
267
        }
268
    }
269
    return true;
270
}
271
272
/* Find module by relative import path */
273
module_t *module_manager_find_relative(
274
    module_manager_t *mm, const char *basepath, const char *import
275
) {
276
    /* Find the importing module */
277
    module_t *importer = module_manager_lookup(mm, basepath);
278
    if (!importer) {
279
        return NULL;
280
    }
281
282
    char import_copy[MAX_PATH_LEN];
283
    strndup(import_copy, import, MAX_PATH_LEN);
284
285
    char *segments[MAX_MODULES];
286
    usize nsegments = 0;
287
288
    for (char *token = strtok(import_copy, ":"); token;
289
         token       = strtok(NULL, ":")) {
290
        if (*token == '\0')
291
            continue;
292
        if (nsegments >= MAX_MODULES)
293
            break;
294
        segments[nsegments++] = token;
295
    }
296
    if (nsegments == 0)
297
        return NULL;
298
299
    module_t *current = importer;
300
    usize     index   = 0;
301
302
    while (index < nsegments && strcmp(segments[index], "super") == 0) {
303
        if (!current || !current->parent)
304
            return NULL;
305
        current = current->parent;
306
        index++;
307
    }
308
309
    if (index == nsegments)
310
        return current;
311
312
    if (index == 0 && nsegments == 1) {
313
        const char *segment = segments[0];
314
315
        for (usize i = 0; i < importer->nchildren; i++) {
316
            if (strcmp(importer->children[i]->name, segment) == 0) {
317
                return importer->children[i];
318
            }
319
        }
320
        return module_manager_lookup_by_name(mm, segment, strlen(segment));
321
    }
322
323
    if (index == 0) {
324
        current = module_manager_lookup_by_name(
325
            mm, segments[index], strlen(segments[index])
326
        );
327
        index++;
328
    }
329
330
    for (; index < nsegments && current; index++) {
331
        module_t *child = NULL;
332
        for (usize j = 0; j < current->nchildren; j++) {
333
            if (strcmp(current->children[j]->name, segments[index]) == 0) {
334
                child = current->children[j];
335
                break;
336
            }
337
        }
338
        current = child;
339
    }
340
    return current;
341
}