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