lib/std/lang/module.rad 13.2 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 = 128;
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
    /// 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
export record ModuleGraph: Copy {
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
export 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
export 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
export 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
    set m.state = ModuleState::Registered;
127
128
    return m.id;
129
}
130
131
/// Register a child module.
132
/// Returns the module identifier.
133
export 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
    set m.state = ModuleState::Registered;
153
    set m.parent = parentId;
154
155
    // Inherit path prefix from parent.
156
    for p in moduleQualifiedPath(parent) {
157
        try appendPathSegment(m, p);
158
    }
159
    try appendPathSegment(m, m.name);
160
161
    return try addChild(parent, m.id);
162
}
163
164
/// Fetch a read-only view of the module identified by `id`.
165
export fn get(graph: *ModuleGraph, id: u16) -> ?*ModuleEntry {
166
    if not isValidId(graph, id) {
167
        return nil;
168
    }
169
    return &graph.entries[id as u32];
170
}
171
172
/// Access a mutable entry by identifier.
173
fn getMut(graph: *mut ModuleGraph, id: u16) -> ?*mut ModuleEntry {
174
    if not isValidId(graph, id) {
175
        return nil;
176
    }
177
    return &mut graph.entries[id as u32];
178
}
179
180
/// Return the identifier of the child stored at `index`.
181
export fn childAt(m: *ModuleEntry, index: u32) -> u16 {
182
    assert index < m.childrenLen, "childAt: index must be valid";
183
    return m.children[index];
184
}
185
186
/// Public accessor for a module's directory prefix.
187
export fn moduleDir(m: *ModuleEntry) -> *[u8] {
188
    return &m.filePath[..m.dirLen];
189
}
190
191
/// Public accessor to the logical module path segments.
192
export fn moduleQualifiedPath(m: *ModuleEntry) -> *[*[u8]] {
193
    assert m.pathDepth > 0, "moduleQualifiedPath: path must not be empty";
194
    return &m.path[..m.pathDepth];
195
}
196
197
/// Retrieve the lifecycle state for `id`.
198
export fn state(graph: *ModuleGraph, id: u16) -> ModuleState throws (ModuleError) {
199
    let m = get(graph, id) else {
200
        throw ModuleError::NotFound(id);
201
    };
202
    return m.state;
203
}
204
205
/// Record the parsed AST root for `id`.
206
export fn setAst(graph: *mut ModuleGraph, id: u16, root: *mut ast::Node) throws (ModuleError) {
207
    let m = getMut(graph, id) else throw ModuleError::NotFound(id);
208
    set m.ast = root;
209
    set m.state = ModuleState::Parsed;
210
}
211
212
/// Set the source text for a module.
213
export fn setSource(graph: *mut ModuleGraph, id: u16, source: *[u8]) throws (ModuleError) {
214
    let m = getMut(graph, id) else throw ModuleError::NotFound(id);
215
    set m.source = source;
216
}
217
218
/// Look up a child module by name under the given parent.
219
export fn findChild(graph: *ModuleGraph, name: *[u8], parentId: u16) -> ?*ModuleEntry {
220
    assert isValidId(graph, parentId), "findChild: parent identifier is valid";
221
222
    let parent = &graph.entries[parentId as u32];
223
    for i in 0..parent.childrenLen {
224
        let childId = parent.children[i];
225
        let child = &graph.entries[childId as u32];
226
        if mem::eq(child.name, name) {
227
            return child;
228
        }
229
    }
230
    return nil;
231
}
232
233
/// Parse a file path into components by splitting on '/'.
234
/// Expects and removes the '.rad' extension from the last component.
235
/// Returns the number of components extracted, or `nil` if the path is invalid.
236
export fn parsePath(filePath: *[u8], components: *mut [*[u8]]) -> ?u32 {
237
    let mut count: u32 = 0;
238
    let mut last: u32 = 0;
239
240
    // Split on '/' to extract all but the last component.
241
    for i in 0..filePath.len {
242
        if filePath[i] == PATH_SEP {
243
            if i > last {
244
                assert count < components.len, "parsePath: output slice is large enough";
245
                set components[count] = &filePath[last..i];
246
                set count += 1;
247
            }
248
            set last = i + 1;
249
        }
250
    }
251
    if last >= filePath.len {
252
        return nil; // Path ends with separator or is empty.
253
    }
254
255
    // Handle the last component by removing extension.
256
    assert count < components.len, "parsePath: output slice is large enough";
257
    if let name = trimExtension(&filePath[last..]) {
258
        set components[count] = name;
259
    } else {
260
        return nil;
261
    };
262
    set count += 1;
263
264
    return count;
265
}
266
267
/// Register a module from a file path, creating the full hierarchy as needed.
268
/// The path is split into components and the module hierarchy is built accordingly.
269
/// If `rootId` is `nil`, registers a new root for the given package.
270
/// Returns the module ID of the last component.
271
export fn registerFromPath(graph: *mut ModuleGraph, packageId: u16, rootId: ?u16, filePath: *[u8]) -> u16 throws (ModuleError) {
272
    let root = rootId else {
273
        return try registerRoot(graph, packageId, filePath);
274
    };
275
    let rootEntry = get(graph, root) else {
276
        panic "registerFromPath: root is missing from storage";
277
    };
278
279
    // Strip the base directory from the file path.
280
    let baseDir = moduleDir(rootEntry);
281
    let pathSuffix = mem::stripPrefix(baseDir, filePath) else {
282
        throw ModuleError::InvalidPath;
283
    };
284
285
    // Parse the stripped path into components.
286
    let mut parts: [*[u8]; MAX_MODULE_PATH_DEPTH] = undefined;
287
    let partsLen = parsePath(pathSuffix, &mut parts[..]) else {
288
        throw ModuleError::InvalidPath;
289
    };
290
    if partsLen == 0 {
291
        throw ModuleError::InvalidPath;
292
    }
293
294
    // Strip the root's qualified path from the parsed components.
295
    let rootPath = moduleQualifiedPath(rootEntry);
296
    let childPath = stripPathPrefix(rootPath, &parts[..partsLen]) else {
297
        throw ModuleError::InvalidPath;
298
    };
299
    if childPath.len == 0 {
300
        throw ModuleError::InvalidPath;
301
    }
302
    let childName = childPath[childPath.len - 1];
303
304
    // Navigate through all but the last segment to find the parent.
305
    let mut parentId = root;
306
    for part in &childPath[..childPath.len - 1] {
307
        let child = findChild(graph, part, parentId) else {
308
            throw ModuleError::MissingParent;
309
        };
310
        set parentId = child.id;
311
    }
312
    return try registerChild(graph, parentId, childName, filePath);
313
}
314
315
/// Allocate a fresh entry in the graph.
316
fn allocModule(graph: *mut ModuleGraph, packageId: u16, name: *[u8], filePath: *[u8]) -> *mut ModuleEntry throws (ModuleError) {
317
    if graph.entriesLen >= graph.entries.len {
318
        throw ModuleError::CapacityExceeded;
319
    }
320
    let idx = graph.entriesLen;
321
    set graph.entriesLen += 1;
322
323
    // TODO: This is a common pattern that needs better syntax.
324
    let m = &mut graph.entries[idx];
325
    set *m = ModuleEntry {
326
        id: idx as u16,
327
        packageId,
328
        parent: nil,
329
        filePath,
330
        dirLen: 0,
331
        name: strings::intern(graph.pool, name),
332
        path: undefined,
333
        pathDepth: 0,
334
        state: ModuleState::Vacant,
335
        children: undefined,
336
        childrenLen: 0,
337
        ast: nil,
338
        source: nil,
339
    };
340
    set m.dirLen = dirLength(m.filePath);
341
342
    return m;
343
}
344
345
/// Append a logical path segment (module identifier) to the entry.
346
fn appendPathSegment(entry: *mut ModuleEntry, segment: *[u8]) throws (ModuleError) {
347
    if entry.pathDepth >= entry.path.len {
348
        throw ModuleError::PathTooDeep;
349
    }
350
    set entry.path[entry.pathDepth] = segment;
351
    set entry.pathDepth += 1;
352
}
353
354
/// Append a child identifier to the parent's child list.
355
fn addChild(parent: *mut ModuleEntry, childId: u16) -> u16 throws (ModuleError) {
356
    if parent.childrenLen >= parent.children.len {
357
        throw ModuleError::CapacityExceeded;
358
    }
359
    set parent.children[parent.childrenLen] = childId;
360
    set parent.childrenLen += 1;
361
362
    return childId;
363
}
364
365
/// Check if `id` points at an allocated entry.
366
fn isValidId(graph: *ModuleGraph, id: u16) -> bool {
367
    return (id as u32) < graph.entriesLen;
368
}
369
370
/// Return the length of the directory prefix for `path`.
371
/// Return zero if the path has no separator.
372
fn dirLength(path: *[u8]) -> u32 {
373
    let mut start: u32 = 0;
374
    for i in 0..path.len {
375
        if path[i] == PATH_SEP {
376
            set start = i + 1;
377
        }
378
    }
379
    return start;
380
}
381
382
/// Produce a subslice for the basename of `path` (without extension).
383
fn basenameSlice(path: *[u8]) -> *[u8] throws (ModuleError) {
384
    let start = dirLength(path);
385
    let withoutExt = trimExtension(path) else {
386
        throw ModuleError::InvalidPath;
387
    };
388
    return &withoutExt[start..];
389
}
390
391
/// Trim the `.rad` extension from `path` if present.
392
/// Returns the path slice without the extension.
393
export fn trimExtension(path: *[u8]) -> ?*[u8] {
394
    if path.len < SOURCE_EXT.len {
395
        return nil;
396
    }
397
    let extStart = path.len - SOURCE_EXT.len;
398
    for ext, i in SOURCE_EXT {
399
        if path[extStart + i] <> ext {
400
            return nil;
401
        }
402
    }
403
    return &path[..extStart];
404
}
405
406
/// Strip prefix from path, and return the suffix.
407
fn stripPathPrefix(prefix: *[*[u8]], path: *[*[u8]]) -> ?*[*[u8]] {
408
    if prefix.len == 0 {
409
        return path;
410
    }
411
    if prefix.len > path.len {
412
        return nil;
413
    }
414
    for segment, i in prefix {
415
        if not mem::eq(segment, path[i]) {
416
            return nil;
417
        }
418
    }
419
    return &path[prefix.len..];
420
}