compiler/
lib/
examples/
std/
arch/
char/
collections/
lang/
alloc/
ast/
gen/
il/
module/
parser/
resolver/
scanner/
alloc.rad
7.1 KiB
ast.rad
26.7 KiB
gen.rad
513 B
il.rad
20.3 KiB
lower.rad
315.6 KiB
module.rad
14.9 KiB
package.rad
1.3 KiB
parser.rad
91.2 KiB
resolver.rad
452.9 KiB
scanner.rad
17.9 KiB
sexpr.rad
6.7 KiB
strings.rad
2.2 KiB
types.rad
1.6 KiB
sys/
arch.rad
68 B
char.rad
855 B
collections.rad
39 B
fmt.rad
8.3 KiB
intrinsics.rad
467 B
io.rad
1.7 KiB
lang.rad
276 B
mem.rad
2.3 KiB
sys.rad
179 B
testing.rad
2.4 KiB
tests.rad
15.7 KiB
vec.rad
3.2 KiB
std.rad
281 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
5.4 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.5 KiB
std.lib.test
662 B
lib/std/lang/module.rad
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 | } |