lib/std/lang/module.rad 14.9 KiB raw
1
//! Module graph and loader.
2
//!
3
//! This module tracks source files, parent/child relationships, and basic state for each
4
//! module so that later phases (parser, semantic analyzer) can resolve imports (`use`)
5
//! and avoid reloading the same file twice.
6
7
export mod printer;
8
9
@test mod tests;
10
11
use std::mem;
12
use std::lang::alloc;
13
use std::lang::ast;
14
use std::lang::strings;
15
16
/// Maximum number of modules tracked in a single compilation graph.
17
export constant MAX_MODULES: u32 = 192;
18
/// Maximum number of characters for a module path.
19
constant MAX_PATH_LEN: u32 = 256;
20
/// Maximum number of components that make up a logical module path.
21
constant MAX_MODULE_PATH_DEPTH: u32 = 16;
22
/// Filesystem separator used when constructing child paths.
23
constant PATH_SEP: u8 = '/';
24
/// Source file extension handled by the loader.
25
constant SOURCE_EXT: *[u8] = ".rad";
26
27
/// Lifecycle state for modules in the dependency graph.
28
export union ModuleState: Copy {
29
    /// Slot unused or yet to be initialized.
30
    Vacant,
31
    /// Module registered with a known path but not parsed yet.
32
    Registered,
33
    /// Module is currently being parsed (used for cycle detection).
34
    Parsing,
35
    /// Module finished parsing successfully.
36
    Parsed,
37
    /// Module is undergoing semantic analysis.
38
    Analyzing,
39
    /// Module finished semantic analysis and is ready for codegen.
40
    Ready,
41
}
42
43
/// Error categories that can be produced by the module graph.
44
export union ModuleError: Copy {
45
    /// Module not found.
46
    NotFound(u16),
47
    /// Adding the module would exceed the fixed storage.
48
    CapacityExceeded,
49
    /// Provided path is missing the required `.rad` extension or otherwise invalid.
50
    InvalidPath,
51
    /// Module graph path exceeded the maximum logical depth.
52
    PathTooDeep,
53
    /// Attempt to register a module before its parent.
54
    MissingParent,
55
}
56
57
/// Module metadata recorded inside the dependency graph.
58
export record ModuleEntry: Copy {
59
    /// Numeric identifier for the slot (index in the graph array).
60
    id: u16,
61
    /// Package identifier this module belongs to.
62
    packageId: u16,
63
    /// Parent module identifier, or `nil` for root modules.
64
    parent: ?u16,
65
    /// Absolute or workspace-relative path to the `.rad` source file.
66
    filePath: *[u8],
67
    /// Number of bytes covering the directory portion of `filePath`.
68
    dirLen: u32,
69
    /// Module name.
70
    name: *[u8],
71
    /// Logical path from the root module to this module.
72
    path: [*[u8]; MAX_MODULE_PATH_DEPTH],
73
    /// Number of segments inside `path`.
74
    pathDepth: u32,
75
    /// Mutable module state shared by all entry views.
76
    updates: *cell ModuleUpdates,
77
}
78
79
/// Copyable state stored in a module entry's cell.
80
export record ModuleUpdates: Copy {
81
    /// Current lifecycle state.
82
    state: ModuleState,
83
    /// Child module identifiers declared directly inside this module.
84
    children: [u16; MAX_MODULES],
85
    /// Number of entries stored in `children`.
86
    childrenLen: u32,
87
    /// Parsed AST root for this module when available.
88
    ast: ?*ast::Node,
89
    /// Source text for this module (for error reporting).
90
    source: ?*[u8],
91
}
92
93
/// Dense storage for all modules referenced by the compilation unit.
94
export record ModuleGraph {
95
    /// Entry identities indexed by module identifier.
96
    entries: *mut [?*ModuleEntry],
97
    /// Number of initialized entries.
98
    entriesLen: u32,
99
    /// Arena for AST nodes and stable entry allocations.
100
    arena: ?*unsafe mut ast::NodeArena,
101
}
102
103
/// Initialize an empty module graph backed by the provided storage.
104
/// The AST arena must outlive the graph and all retained entry views.
105
/// Entry allocation must occur outside speculative parser allocations.
106
export unsafe fn moduleGraph(
107
    storage: *mut [?*ModuleEntry],
108
    arena: &mut ast::NodeArena
109
) -> ModuleGraph {
110
    for i in 0..storage.len {
111
        set storage[i] = nil;
112
    }
113
    return ModuleGraph {
114
        entries: storage,
115
        entriesLen: 0,
116
        arena: arena as *unsafe mut ast::NodeArena,
117
    };
118
}
119
120
/// Register a root module residing at `path` for a package.
121
export unsafe fn registerRoot(graph: &mut ModuleGraph, pool: &mut strings::Pool, packageId: u16, filePath: *[u8]) -> u16 throws (ModuleError) {
122
    let name = try basenameSlice(filePath);
123
    return try registerRootWithName(graph, pool, packageId, name, filePath);
124
}
125
126
/// Register a root module with an explicit name and file path for a package.
127
export unsafe fn registerRootWithName(
128
    graph: &mut ModuleGraph, pool: &mut strings::Pool,
129
    packageId: u16,
130
    name: *[u8],
131
    filePath: *[u8]
132
) -> u16 throws (ModuleError) {
133
    let m = try allocModule(graph, pool, packageId, name, filePath);
134
    try appendPathSegment(m, name);
135
136
    let id = m.id;
137
    publish(graph, m);
138
    return id;
139
}
140
141
/// Register a child module.
142
/// Returns the module identifier.
143
export unsafe fn registerChild(
144
    graph: &mut ModuleGraph, pool: &mut strings::Pool,
145
    parentId: u16,
146
    name: *[u8],
147
    filePath: *[u8]
148
) -> u16 throws (ModuleError) {
149
    assert name.len > 0, "registerChild: name must not be empty";
150
    assert filePath.len > 0, "registerChild: file path must not be empty";
151
152
    let parent = get(graph, parentId)
153
        else throw ModuleError::NotFound(parentId);
154
155
    // If the child already exists under this parent, return it.
156
    if let child = findChild(graph, name, parentId) {
157
        return child.id;
158
    }
159
    // Inherit packageId from parent.
160
    let m = try allocModule(graph, pool, parent.packageId, name, filePath);
161
162
    set m.parent = parentId;
163
164
    // Inherit path prefix from parent.
165
    for p in moduleQualifiedPath(parent) {
166
        try appendPathSegment(m, p);
167
    }
168
    try appendPathSegment(m, name);
169
170
    let id = try addChild(parent, m.id);
171
    publish(graph, m);
172
    return id;
173
}
174
175
/// Fetch a read-only view of the module identified by `id`.
176
export fn get(graph: &ModuleGraph, id: u16) -> ?*ModuleEntry {
177
    if not isValidId(graph, id) {
178
        return nil;
179
    }
180
    return graph.entries[id as u32];
181
}
182
183
/// Return the identifier of the child stored at `index`.
184
export fn childAt(m: *ModuleEntry, index: u32) -> u16 {
185
    let updates = *m.updates;
186
    assert index < updates.childrenLen, "childAt: index must be valid";
187
    return updates.children[index];
188
}
189
190
/// Public accessor for a module's directory prefix.
191
export fn moduleDir(m: *ModuleEntry) -> *[u8] {
192
    return &m.filePath[..m.dirLen];
193
}
194
195
/// Public accessor to the logical module path segments.
196
export fn moduleQualifiedPath(m: *ModuleEntry) -> *[*[u8]] {
197
    assert m.pathDepth > 0, "moduleQualifiedPath: path must not be empty";
198
    return &m.path[..m.pathDepth];
199
}
200
201
/// Retrieve the lifecycle state for `id`.
202
export fn state(graph: &ModuleGraph, id: u16) -> ModuleState throws (ModuleError) {
203
    let m = get(graph, id) else {
204
        throw ModuleError::NotFound(id);
205
    };
206
    return (*m.updates).state;
207
}
208
209
/// Record the parsed AST root for `id`.
210
export fn setAst(graph: &mut ModuleGraph, id: u16, root: *ast::Node) throws (ModuleError) {
211
    let m = get(graph, id) else throw ModuleError::NotFound(id);
212
    let mut updates = *m.updates;
213
    set updates.ast = root;
214
    set updates.state = ModuleState::Parsed;
215
    set *m.updates = updates;
216
}
217
218
/// Set the source text for a module.
219
export fn setSource(graph: &mut ModuleGraph, id: u16, source: *[u8]) throws (ModuleError) {
220
    let m = get(graph, id) else throw ModuleError::NotFound(id);
221
    let mut updates = *m.updates;
222
    set updates.source = source;
223
    set *m.updates = updates;
224
}
225
226
/// Look up a child module by name under the given parent.
227
export fn findChild(graph: &ModuleGraph, name: *[u8], parentId: u16) -> ?*ModuleEntry {
228
    assert isValidId(graph, parentId), "findChild: parent identifier is valid";
229
230
    let parent = get(graph, parentId) else panic;
231
    let updates = *parent.updates;
232
    for i in 0..updates.childrenLen {
233
        let childId = updates.children[i];
234
        let child = get(graph, childId) else panic;
235
        if mem::eq(child.name, name) {
236
            return child;
237
        }
238
    }
239
    return nil;
240
}
241
242
/// Parse a file path into components by splitting on '/'.
243
/// Expects and removes the '.rad' extension from the last component.
244
/// Returns the number of components extracted, or `nil` if the path is invalid.
245
export fn parsePath(filePath: *[u8], components: &mut [*[u8]]) -> ?u32 {
246
    let mut count: u32 = 0;
247
    let mut last: u32 = 0;
248
249
    // Split on '/' to extract all but the last component.
250
    for i in 0..filePath.len {
251
        if filePath[i] == PATH_SEP {
252
            if i > last {
253
                assert count < components.len, "parsePath: output slice is large enough";
254
                set components[count] = &filePath[last..i];
255
                set count += 1;
256
            }
257
            set last = i + 1;
258
        }
259
    }
260
    if last >= filePath.len {
261
        return nil; // Path ends with separator or is empty.
262
    }
263
264
    // Handle the last component by removing extension.
265
    assert count < components.len, "parsePath: output slice is large enough";
266
    if let name = trimExtension(&filePath[last..]) {
267
        set components[count] = name;
268
    } else {
269
        return nil;
270
    };
271
    set count += 1;
272
273
    return count;
274
}
275
276
/// Register a module from a file path, creating the full hierarchy as needed.
277
/// The path is split into components and the module hierarchy is built accordingly.
278
/// If `rootId` is `nil`, registers a new root for the given package.
279
/// Returns the module ID of the last component.
280
export unsafe fn registerFromPath(graph: &mut ModuleGraph, pool: &mut strings::Pool, packageId: u16, rootId: ?u16, filePath: *[u8]) -> u16 throws (ModuleError) {
281
    let root = rootId else {
282
        return try registerRoot(graph, pool, packageId, filePath);
283
    };
284
    let rootEntry = get(graph, root) else {
285
        panic "registerFromPath: root is missing from storage";
286
    };
287
288
    // Strip the base directory from the file path.
289
    let baseDir = moduleDir(rootEntry);
290
    let pathSuffix = mem::stripPrefix(baseDir, filePath) else {
291
        throw ModuleError::InvalidPath;
292
    };
293
294
    // Parse the stripped path into components.
295
    let mut parts: [*[u8]; MAX_MODULE_PATH_DEPTH] = [""; MAX_MODULE_PATH_DEPTH];
296
    let partsLen = parsePath(pathSuffix, &mut parts[..]) else {
297
        throw ModuleError::InvalidPath;
298
    };
299
    if partsLen == 0 {
300
        throw ModuleError::InvalidPath;
301
    }
302
303
    // Strip the root's qualified path from the parsed components.
304
    let rootPath = moduleQualifiedPath(rootEntry);
305
    let prefixLen = stripPathPrefix(rootPath, &parts[..partsLen]) else {
306
        throw ModuleError::InvalidPath;
307
    };
308
    if prefixLen == partsLen {
309
        throw ModuleError::InvalidPath;
310
    }
311
    let childName = parts[partsLen - 1];
312
313
    // Navigate through all but the last segment to find the parent.
314
    let mut parentId = root;
315
    for part in &parts[prefixLen..partsLen - 1] {
316
        let child = findChild(graph, part, parentId) else {
317
            throw ModuleError::MissingParent;
318
        };
319
        set parentId = child.id;
320
    }
321
    return try registerChild(graph, pool, parentId, childName, filePath);
322
}
323
324
/// Allocate a fresh entry in the graph.
325
unsafe fn allocModule(graph: &mut ModuleGraph, pool: &mut strings::Pool, packageId: u16, name: *[u8], filePath: *[u8]) -> *mut ModuleEntry throws (ModuleError) {
326
    if graph.entriesLen >= graph.entries.len {
327
        throw ModuleError::CapacityExceeded;
328
    }
329
    let idx = graph.entriesLen;
330
    let arena = graph.arena else panic;
331
    let state = try! alloc::alloc(&mut arena.arena, @sizeOf(ModuleUpdates), @alignOf(ModuleUpdates)) as *mut ModuleUpdates;
332
    set *state = ModuleUpdates {
333
        state: ModuleState::Registered,
334
        children: [0; MAX_MODULES],
335
        childrenLen: 0,
336
        ast: nil,
337
        source: nil,
338
    };
339
    let updates = &cell *state;
340
341
    // TODO: This is a common pattern that needs better syntax.
342
    let m = try! alloc::alloc(&mut arena.arena, @sizeOf(ModuleEntry), @alignOf(ModuleEntry)) as *mut ModuleEntry;
343
    set *m = ModuleEntry {
344
        id: idx as u16,
345
        packageId,
346
        parent: nil,
347
        filePath,
348
        dirLen: dirLength(filePath),
349
        name: strings::intern(pool, name),
350
        path: [""; MAX_MODULE_PATH_DEPTH],
351
        pathDepth: 0,
352
        updates,
353
    };
354
355
    return m;
356
}
357
358
/// Append a logical path segment (module identifier) to the entry.
359
fn appendPathSegment(entry: &mut ModuleEntry, segment: *[u8]) throws (ModuleError) {
360
    if entry.pathDepth >= entry.path.len {
361
        throw ModuleError::PathTooDeep;
362
    }
363
    set entry.path[entry.pathDepth] = segment;
364
    set entry.pathDepth += 1;
365
}
366
367
/// Append a child identifier to the parent's child list.
368
fn addChild(parent: *ModuleEntry, childId: u16) -> u16 throws (ModuleError) {
369
    let mut updates = *parent.updates;
370
    if updates.childrenLen >= updates.children.len {
371
        throw ModuleError::CapacityExceeded;
372
    }
373
    set updates.children[updates.childrenLen] = childId;
374
    set updates.childrenLen += 1;
375
    set *parent.updates = updates;
376
377
    return childId;
378
}
379
380
/// Publish a fully initialized module entry in identifier order.
381
fn publish(graph: &mut ModuleGraph, entry: *ModuleEntry) {
382
    assert entry.id as u32 == graph.entriesLen;
383
    set graph.entries[graph.entriesLen] = entry;
384
    set graph.entriesLen += 1;
385
}
386
387
/// Return the number of registered children.
388
export fn childCount(entry: *ModuleEntry) -> u32 {
389
    return (*entry.updates).childrenLen;
390
}
391
392
/// Return the current parsed root for an entry.
393
export fn astFor(entry: *ModuleEntry) -> ?*ast::Node {
394
    return (*entry.updates).ast;
395
}
396
397
/// Return the current source text for an entry.
398
export fn sourceFor(entry: *ModuleEntry) -> ?*[u8] {
399
    return (*entry.updates).source;
400
}
401
402
/// Check if `id` points at an allocated entry.
403
fn isValidId(graph: &ModuleGraph, id: u16) -> bool {
404
    return (id as u32) < graph.entriesLen;
405
}
406
407
/// Return the length of the directory prefix for `path`.
408
/// Return zero if the path has no separator.
409
fn dirLength(path: *[u8]) -> u32 {
410
    let mut start: u32 = 0;
411
    for i in 0..path.len {
412
        if path[i] == PATH_SEP {
413
            set start = i + 1;
414
        }
415
    }
416
    return start;
417
}
418
419
/// Produce a subslice for the basename of `path` (without extension).
420
fn basenameSlice(path: *[u8]) -> *[u8] throws (ModuleError) {
421
    let start = dirLength(path);
422
    let withoutExt = trimExtension(path) else {
423
        throw ModuleError::InvalidPath;
424
    };
425
    return &withoutExt[start..];
426
}
427
428
/// Trim the `.rad` extension from `path` if present.
429
/// Returns the path slice without the extension.
430
export fn trimExtension(path: *[u8]) -> ?*[u8] {
431
    if path.len < SOURCE_EXT.len {
432
        return nil;
433
    }
434
    let extStart = path.len - SOURCE_EXT.len;
435
    for ext, i in SOURCE_EXT {
436
        if path[extStart + i] <> ext {
437
            return nil;
438
        }
439
    }
440
    return &path[..extStart];
441
}
442
443
/// Check a path prefix and return its segment count.
444
fn stripPathPrefix(prefix: &[*[u8]], path: &[*[u8]]) -> ?u32 {
445
    if prefix.len == 0 {
446
        return 0;
447
    }
448
    if prefix.len > path.len {
449
        return nil;
450
    }
451
    for segment, i in prefix {
452
        if not mem::eq(segment, path[i]) {
453
            return nil;
454
        }
455
    }
456
    return prefix.len;
457
}