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