lib/std/lang/module.rad 17.3 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
/// Zero-state owner for authority over one module graph's mutable state.
58
export record Permission {}
59
60
/// Module metadata recorded inside the dependency graph.
61
export record ModuleEntry: Copy {
62
    /// Numeric identifier for the slot (index in the graph array).
63
    id: u16,
64
    /// Package identifier this module belongs to.
65
    packageId: u16,
66
    /// Parent module identifier, or `nil` for root modules.
67
    parent: ?u16,
68
    /// Absolute or workspace-relative path to the `.rad` source file.
69
    filePath: *[u8],
70
    /// Number of bytes covering the directory portion of `filePath`.
71
    dirLen: u32,
72
    /// Module name.
73
    name: *[u8],
74
    /// Logical path from the root module to this module.
75
    path: [*[u8]; MAX_MODULE_PATH_DEPTH],
76
    /// Number of segments inside `path`.
77
    pathDepth: u32,
78
}
79
80
/// Copyable state stored in a module graph update cell.
81
export record ModuleUpdates: Copy {
82
    /// Current lifecycle state.
83
    state: ModuleState,
84
    /// Child module identifiers declared directly inside this module.
85
    children: [u16; MAX_MODULES],
86
    /// Number of entries stored in `children`.
87
    childrenLen: u32,
88
    /// Parsed AST root for this module when available.
89
    ast: ?*ast::Node,
90
    /// Source text for this module (for error reporting).
91
    source: ?*[u8],
92
}
93
94
/// Dense storage for all modules referenced by the compilation unit.
95
export opaque record ModuleGraph: 'permission {
96
    /// Entry identities indexed by module identifier.
97
    entries: *mut [?*ModuleEntry],
98
    /// Permission-associated updates indexed by module identifier.
99
    updates: [?*cell 'permission ModuleUpdates; MAX_MODULES],
100
    /// External authority for the update cells in this graph.
101
    permission: &'permission mut Permission,
102
    /// Number of initialized entries.
103
    entriesLen: u32,
104
    /// Arena for AST nodes and stable entry allocations.
105
    arena: ?*unsafe mut ast::NodeArena,
106
}
107
108
/// Initialize an empty module graph backed by the provided storage.
109
/// The AST arena must outlive the graph and all retained entry views.
110
/// Entry allocation must occur outside speculative parser allocations.
111
export unsafe fn moduleGraph 'permission (
112
    storage: *mut [?*ModuleEntry],
113
    arena: &mut ast::NodeArena,
114
    permission: &'permission mut Permission
115
) -> ModuleGraph 'permission {
116
    for i in 0..storage.len {
117
        set storage[i] = nil;
118
    }
119
    return ModuleGraph 'permission {
120
        entries: storage,
121
        updates: [nil; MAX_MODULES],
122
        permission,
123
        entriesLen: 0,
124
        arena: arena as *unsafe mut ast::NodeArena,
125
    };
126
}
127
128
/// Register a root module residing at `path` for a package.
129
export unsafe fn registerRoot 'permission (graph: &mut ModuleGraph 'permission, pool: &mut strings::Pool, packageId: u16, filePath: *[u8]) -> u16 throws (ModuleError) {
130
    let name = try basenameSlice(filePath);
131
    return try registerRootWithName(graph, pool, packageId, name, filePath);
132
}
133
134
/// Register a root module with an explicit name and file path for a package.
135
export unsafe fn registerRootWithName 'permission (
136
    graph: &mut ModuleGraph 'permission, pool: &mut strings::Pool,
137
    packageId: u16,
138
    name: *[u8],
139
    filePath: *[u8]
140
) -> u16 throws (ModuleError) {
141
    let m = try allocModule(graph, pool, packageId, name, filePath);
142
    try appendPathSegment(m, name);
143
144
    let id = m.id;
145
    publish(graph, m);
146
    return id;
147
}
148
149
/// Register a child module.
150
/// Returns the module identifier.
151
export unsafe fn registerChild 'permission (
152
    graph: &mut ModuleGraph 'permission, pool: &mut strings::Pool,
153
    parentId: u16,
154
    name: *[u8],
155
    filePath: *[u8]
156
) -> u16 throws (ModuleError) {
157
    assert name.len > 0, "registerChild: name must not be empty";
158
    assert filePath.len > 0, "registerChild: file path must not be empty";
159
160
    let parent = get(graph, parentId)
161
        else throw ModuleError::NotFound(parentId);
162
163
    // If the child already exists under this parent, return it.
164
    if let child = findChild(graph, name, parentId) {
165
        return child.id;
166
    }
167
    // Inherit packageId from parent.
168
    let m = try allocModule(graph, pool, parent.packageId, name, filePath);
169
170
    set m.parent = parentId;
171
172
    // Inherit path prefix from parent.
173
    for p in moduleQualifiedPath(parent) {
174
        try appendPathSegment(m, p);
175
    }
176
    try appendPathSegment(m, name);
177
178
    let id = try addChild(graph, parent, m.id);
179
    publish(graph, m);
180
    return id;
181
}
182
183
/// Return the number of registered module identities.
184
export fn entryCount 'permission (graph: &ModuleGraph 'permission) -> u32 {
185
    return graph.entriesLen;
186
}
187
188
/// Fetch a read-only view of the module identified by `id`.
189
export fn get 'permission (graph: &ModuleGraph 'permission, id: u16) -> ?*ModuleEntry {
190
    if not isValidId(graph, id) {
191
        return nil;
192
    }
193
    return graph.entries[id as u32];
194
}
195
196
/// Return the identifier of the child stored at `index`.
197
export fn childAt 'permission (graph: &ModuleGraph 'permission, m: *ModuleEntry, index: u32) -> u16 {
198
    let cell = updatesFor(graph, m.id);
199
    let authority: 'loan = &*graph.permission, updates = &*cell in {
200
        assert index < updates.childrenLen, "childAt: index must be valid";
201
        return updates.children[index];
202
    }
203
}
204
205
/// Public accessor for a module's directory prefix.
206
export fn moduleDir(m: *ModuleEntry) -> *[u8] {
207
    return &m.filePath[..m.dirLen];
208
}
209
210
/// Public accessor to the logical module path segments.
211
export fn moduleQualifiedPath(m: *ModuleEntry) -> *[*[u8]] {
212
    assert m.pathDepth > 0, "moduleQualifiedPath: path must not be empty";
213
    return &m.path[..m.pathDepth];
214
}
215
216
/// Retrieve the lifecycle state for `id`.
217
export fn state 'permission (graph: &ModuleGraph 'permission, id: u16) -> ModuleState throws (ModuleError) {
218
    let m = get(graph, id) else {
219
        throw ModuleError::NotFound(id);
220
    };
221
    let cell = updatesFor(graph, m.id);
222
    let authority: 'loan = &*graph.permission, updates = &*cell in {
223
        return updates.state;
224
    }
225
}
226
227
/// Record the parsed AST root for `id`.
228
export fn setAst 'permission (graph: &mut ModuleGraph 'permission, id: u16, root: *ast::Node) throws (ModuleError) {
229
    let m = get(graph, id) else throw ModuleError::NotFound(id);
230
    let cell = updatesFor(graph, m.id);
231
    let authority: 'loan = &mut *graph.permission, updates = &mut *cell in {
232
        set updates.ast = root;
233
        set updates.state = ModuleState::Parsed;
234
    }
235
}
236
237
/// Set the source text for a module.
238
export fn setSource 'permission (graph: &mut ModuleGraph 'permission, id: u16, source: *[u8]) throws (ModuleError) {
239
    let m = get(graph, id) else throw ModuleError::NotFound(id);
240
    let cell = updatesFor(graph, m.id);
241
    let authority: 'loan = &mut *graph.permission, updates = &mut *cell in {
242
        set updates.source = source;
243
    }
244
}
245
246
/// Look up a child module by name under the given parent.
247
export fn findChild 'permission (graph: &ModuleGraph 'permission, name: *[u8], parentId: u16) -> ?*ModuleEntry {
248
    assert isValidId(graph, parentId), "findChild: parent identifier is valid";
249
250
    let parent = get(graph, parentId) else panic;
251
    let cell = updatesFor(graph, parent.id);
252
    let authority: 'loan = &*graph.permission, updates = &*cell in {
253
        for i in 0..updates.childrenLen {
254
            let childId = updates.children[i];
255
            let child = get(graph, childId) else panic;
256
            if mem::eq(child.name, name) {
257
                return child;
258
            }
259
        }
260
    }
261
    return nil;
262
}
263
264
/// Parse a file path into components by splitting on '/'.
265
/// Expects and removes the '.rad' extension from the last component.
266
/// Returns the number of components extracted, or `nil` if the path is invalid.
267
export fn parsePath(filePath: *[u8], components: &mut [*[u8]]) -> ?u32 {
268
    let mut count: u32 = 0;
269
    let mut last: u32 = 0;
270
271
    // Split on '/' to extract all but the last component.
272
    for i in 0..filePath.len {
273
        if filePath[i] == PATH_SEP {
274
            if i > last {
275
                assert count < components.len, "parsePath: output slice is large enough";
276
                set components[count] = &filePath[last..i];
277
                set count += 1;
278
            }
279
            set last = i + 1;
280
        }
281
    }
282
    if last >= filePath.len {
283
        return nil; // Path ends with separator or is empty.
284
    }
285
286
    // Handle the last component by removing extension.
287
    assert count < components.len, "parsePath: output slice is large enough";
288
    if let name = trimExtension(&filePath[last..]) {
289
        set components[count] = name;
290
    } else {
291
        return nil;
292
    };
293
    set count += 1;
294
295
    return count;
296
}
297
298
/// Register a module from a file path, creating the full hierarchy as needed.
299
/// The path is split into components and the module hierarchy is built accordingly.
300
/// If `rootId` is `nil`, registers a new root for the given package.
301
/// Returns the module ID of the last component.
302
export unsafe fn registerFromPath 'permission (graph: &mut ModuleGraph 'permission, pool: &mut strings::Pool, packageId: u16, rootId: ?u16, filePath: *[u8]) -> u16 throws (ModuleError) {
303
    let root = rootId else {
304
        return try registerRoot(graph, pool, packageId, filePath);
305
    };
306
    let rootEntry = get(graph, root) else {
307
        panic "registerFromPath: root is missing from storage";
308
    };
309
310
    // Strip the base directory from the file path.
311
    let baseDir = moduleDir(rootEntry);
312
    let pathSuffix = mem::stripPrefix(baseDir, filePath) else {
313
        throw ModuleError::InvalidPath;
314
    };
315
316
    // Parse the stripped path into components.
317
    let mut parts: [*[u8]; MAX_MODULE_PATH_DEPTH] = [""; MAX_MODULE_PATH_DEPTH];
318
    let partsLen = parsePath(pathSuffix, &mut parts[..]) else {
319
        throw ModuleError::InvalidPath;
320
    };
321
    if partsLen == 0 {
322
        throw ModuleError::InvalidPath;
323
    }
324
325
    // Strip the root's qualified path from the parsed components.
326
    let rootPath = moduleQualifiedPath(rootEntry);
327
    let prefixLen = stripPathPrefix(rootPath, &parts[..partsLen]) else {
328
        throw ModuleError::InvalidPath;
329
    };
330
    if prefixLen == partsLen {
331
        throw ModuleError::InvalidPath;
332
    }
333
    let childName = parts[partsLen - 1];
334
335
    // Navigate through all but the last segment to find the parent.
336
    let mut parentId = root;
337
    for part in &parts[prefixLen..partsLen - 1] {
338
        let child = findChild(graph, part, parentId) else {
339
            throw ModuleError::MissingParent;
340
        };
341
        set parentId = child.id;
342
    }
343
    return try registerChild(graph, pool, parentId, childName, filePath);
344
}
345
346
/// Allocate a fresh entry in the graph.
347
unsafe fn allocModule 'permission (graph: &mut ModuleGraph 'permission, pool: &mut strings::Pool, packageId: u16, name: *[u8], filePath: *[u8]) -> *mut ModuleEntry throws (ModuleError) {
348
    if graph.entriesLen >= graph.entries.len {
349
        throw ModuleError::CapacityExceeded;
350
    }
351
    let idx = graph.entriesLen;
352
    let arena = graph.arena else panic;
353
    let state = try! alloc::alloc(&mut arena.arena, @sizeOf(ModuleUpdates), @alignOf(ModuleUpdates)) as *mut ModuleUpdates;
354
    let updates: *cell 'permission ModuleUpdates = &cell 'permission *state;
355
    let authority: 'loan = &mut *graph.permission, value = &mut *updates in {
356
        set *value = ModuleUpdates {
357
            state: ModuleState::Registered,
358
            children: [0; MAX_MODULES],
359
            childrenLen: 0,
360
            ast: nil,
361
            source: nil,
362
        };
363
    }
364
    set graph.updates[idx] = updates;
365
366
    // TODO: This is a common pattern that needs better syntax.
367
    let m = try! alloc::alloc(&mut arena.arena, @sizeOf(ModuleEntry), @alignOf(ModuleEntry)) as *mut ModuleEntry;
368
    set *m = ModuleEntry {
369
        id: idx as u16,
370
        packageId,
371
        parent: nil,
372
        filePath,
373
        dirLen: dirLength(filePath),
374
        name: strings::intern(pool, name),
375
        path: [""; MAX_MODULE_PATH_DEPTH],
376
        pathDepth: 0,
377
    };
378
379
    return m;
380
}
381
382
/// Append a logical path segment (module identifier) to the entry.
383
fn appendPathSegment(entry: &mut ModuleEntry, segment: *[u8]) throws (ModuleError) {
384
    if entry.pathDepth >= entry.path.len {
385
        throw ModuleError::PathTooDeep;
386
    }
387
    set entry.path[entry.pathDepth] = segment;
388
    set entry.pathDepth += 1;
389
}
390
391
/// Append a child identifier to the parent's child list.
392
fn addChild 'permission (graph: &mut ModuleGraph 'permission, parent: *ModuleEntry, childId: u16) -> u16 throws (ModuleError) {
393
    let cell = updatesFor(graph, parent.id);
394
    let authority: 'loan = &mut *graph.permission, updates = &mut *cell in {
395
        if updates.childrenLen >= updates.children.len {
396
            throw ModuleError::CapacityExceeded;
397
        }
398
        set updates.children[updates.childrenLen] = childId;
399
        set updates.childrenLen += 1;
400
    }
401
402
    return childId;
403
}
404
405
/// Publish a fully initialized module entry in identifier order.
406
fn publish 'permission (graph: &mut ModuleGraph 'permission, entry: *ModuleEntry) {
407
    assert entry.id as u32 == graph.entriesLen;
408
    set graph.entries[graph.entriesLen] = entry;
409
    set graph.entriesLen += 1;
410
}
411
412
/// Return the number of registered children.
413
export fn childCount 'permission (graph: &ModuleGraph 'permission, entry: *ModuleEntry) -> u32 {
414
    let cell = updatesFor(graph, entry.id);
415
    let authority: 'loan = &*graph.permission, updates = &*cell in {
416
        return updates.childrenLen;
417
    }
418
}
419
420
/// Return the current parsed root for an entry.
421
export fn astFor 'permission (graph: &ModuleGraph 'permission, entry: *ModuleEntry) -> ?*ast::Node {
422
    let cell = updatesFor(graph, entry.id);
423
    let authority: 'loan = &*graph.permission, updates = &*cell in {
424
        return updates.ast;
425
    }
426
}
427
428
/// Return the current source text for an entry.
429
export fn sourceFor 'permission (graph: &ModuleGraph 'permission, entry: *ModuleEntry) -> ?*[u8] {
430
    let cell = updatesFor(graph, entry.id);
431
    let authority: 'loan = &*graph.permission, updates = &*cell in {
432
        return updates.source;
433
    }
434
}
435
436
/// Return the permission-associated update cell for `id`.
437
fn updatesFor 'permission (graph: &ModuleGraph 'permission, id: u16) -> *cell 'permission ModuleUpdates {
438
    assert isValidId(graph, id), "updatesFor: module identifier is valid";
439
    let cell = graph.updates[id as u32] else {
440
        panic "updatesFor: missing update cell";
441
    };
442
    return cell;
443
}
444
445
/// Check if `id` points at an allocated entry.
446
fn isValidId 'permission (graph: &ModuleGraph 'permission, id: u16) -> bool {
447
    return (id as u32) < graph.entriesLen;
448
}
449
450
/// Return the length of the directory prefix for `path`.
451
/// Return zero if the path has no separator.
452
fn dirLength(path: *[u8]) -> u32 {
453
    let mut start: u32 = 0;
454
    for i in 0..path.len {
455
        if path[i] == PATH_SEP {
456
            set start = i + 1;
457
        }
458
    }
459
    return start;
460
}
461
462
/// Produce a subslice for the basename of `path` (without extension).
463
fn basenameSlice(path: *[u8]) -> *[u8] throws (ModuleError) {
464
    let start = dirLength(path);
465
    let withoutExt = trimExtension(path) else {
466
        throw ModuleError::InvalidPath;
467
    };
468
    return &withoutExt[start..];
469
}
470
471
/// Trim the `.rad` extension from `path` if present.
472
/// Returns the path slice without the extension.
473
export fn trimExtension(path: *[u8]) -> ?*[u8] {
474
    if path.len < SOURCE_EXT.len {
475
        return nil;
476
    }
477
    let extStart = path.len - SOURCE_EXT.len;
478
    for ext, i in SOURCE_EXT {
479
        if path[extStart + i] <> ext {
480
            return nil;
481
        }
482
    }
483
    return &path[..extStart];
484
}
485
486
/// Check a path prefix and return its segment count.
487
fn stripPathPrefix(prefix: &[*[u8]], path: &[*[u8]]) -> ?u32 {
488
    if prefix.len == 0 {
489
        return 0;
490
    }
491
    if prefix.len > path.len {
492
        return nil;
493
    }
494
    for segment, i in prefix {
495
        if not mem::eq(segment, path[i]) {
496
            return nil;
497
        }
498
    }
499
    return prefix.len;
500
}