code wiki / (root) / nx_import.nx

nx_import.nx source

↩ module page · 644 lines · 21896 B

1// import.nx -- sovereign import preprocessor (replaces main.c logic). 2// 3// Responsibility: given a root source path, return a single null- 4// terminated buffer containing every imported file's text spliced 5// inline, recursively, with each file visited at most once. Output 6// is exactly what parse_module consumes. 7// 8// Subsystem boundary: 9// - syscalls.nx : file I/O (sys_read_file) 10// - str helpers : cstr_len / cstr_eq / u8_copy (below; no external dep) 11// - import.nx : path ops + import expansion (this file) 12// - nxc.nx : CLI driver that owns argv and calls expand_imports 13// 14// Invariants (enforced, not hoped): 15// I1 Each canonicalised absolute path is expanded at most once -- 16// the `seen` path table dedupes. Repeat imports emit nothing. 17// I2 Canonicalisation is pure textual (.. pops, . drops, 18// forward-slashes unified). No realpath(3). Identical output 19// on Windows and Linux/NishiOS. 20// I3 Import syntax: exactly `import "relative.nx"` at start-of- 21// line after optional whitespace. Nothing else parses as an 22// import; a substring match mid-line passes through. 23// I4 Buffer overruns fail loudly (negative return). Never 24// silently truncate -- correctness over convenience. 25// I5 Fixed capacity: MAX_IMPORTS distinct imports per tree; 26// paths up to IMPORT_PATH_LEN; output buffer sized by caller. 27// A build that needs more raises one constant in one place. 28// 29// Complexity: O(N * F) where N is total source bytes across all 30// files and F is average imports-per-file. One read per file. 31 32// nx_safety_envelope: 33// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 34// sil_target: SIL1 35// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 36// verdict: NOT_YET_EVALUATED 37 38import "nx_syscalls.nx" 39 40// ---- sizing knobs -------------------------------------------------- 41 42// Bumped 64 -> 256 (2026-05-20) -- bits-up HTTPS arc transitively 43// imports ~100 .nx files; previously failed at 64. 256 gives 44// headroom for AES-GCM, HTTP/2, image decoders without re-bumping. 45const MAX_IMPORTS: i64 = 256 46const IMPORT_PATH_LEN: i64 = 1024 47 48// Error codes returned by expand functions (negative = error). 49const ERR_IMPORTS_FULL: i64 = -1 50const ERR_READ_FAILED: i64 = -2 51const ERR_OUT_OVERFLOW: i64 = -3 52const ERR_BAD_IMPORT: i64 = -4 53 54// ---- cstring helpers ---------------------------------------------- 55 56func cstr_len(s: *u8) -> i64 { 57 var n: i64 = 0 58 while s[n] != 0 { n = n + 1 } 59 return n 60} 61 62func cstr_eq(a: *u8, b: *u8) -> i64 { 63 var i: i64 = 0 64 while a[i] != 0 { 65 if a[i] != b[i] { return 0 } 66 i = i + 1 67 } 68 if b[i] != 0 { return 0 } 69 return 1 70} 71 72// Copy exactly `n` bytes from src to dst. No null-term responsibility. 73func u8_copy(dst: *u8, src: *u8, n: i64) -> i64 { 74 var i: i64 = 0 75 while i < n { dst[i] = src[i]; i = i + 1 } 76 return n 77} 78 79// Shift a *u8 pointer by `delta` bytes (address arithmetic). 80func ptr_at(p: *u8, delta: i64) -> *u8 { 81 let a: i64 = (p as i64) + delta 82 return a as *u8 83} 84 85// ---- ImportSet ----------------------------------------------------- 86// 87// Flat MAX_IMPORTS * IMPORT_PATH_LEN byte buffer. Entry i lives at 88// offset i * IMPORT_PATH_LEN. Counter held externally. 89 90func import_already(paths: *u8, n: i64, path: *u8) -> i64 { 91 var i: i64 = 0 92 while i < n { 93 let entry: *u8 = ptr_at(paths, i * IMPORT_PATH_LEN) 94 if cstr_eq(entry, path) == 1 { return 1 } 95 i = i + 1 96 } 97 return 0 98} 99 100func import_add(paths: *u8, n: i64, path: *u8) -> i64 { 101 if n >= MAX_IMPORTS { return 0 } 102 let entry: *u8 = ptr_at(paths, n * IMPORT_PATH_LEN) 103 var plen: i64 = cstr_len(path) 104 if plen > IMPORT_PATH_LEN - 1 { plen = IMPORT_PATH_LEN - 1 } 105 u8_copy(entry, path, plen) 106 entry[plen] = 0 107 return 1 108} 109 110// ---- path ops ------------------------------------------------------ 111 112// Extract directory portion of `path`. Everything before the final 113// '/' or '\'. Writes null-terminated to `out`. Returns length. 114func path_dir(path: *u8, out: *u8) -> i64 { 115 let n: i64 = cstr_len(path) 116 var last: i64 = 0 - 1 117 var i: i64 = 0 118 while i < n { 119 if path[i] == 0x2F { last = i } // '/' 120 if path[i] == 0x5C { last = i } // '\\' 121 i = i + 1 122 } 123 if last < 0 { out[0] = 0; return 0 } 124 u8_copy(out, path, last) 125 out[last] = 0 126 return last 127} 128 129// Compose `dir/name` into `out`. If name is absolute (leading '/' or 130// 'X:' drive letter), copy name verbatim. Returns length. 131func join_path(dir: *u8, name: *u8, out: *u8) -> i64 { 132 if name[0] == 0x2F { 133 let nlen: i64 = cstr_len(name) 134 u8_copy(out, name, nlen) 135 out[nlen] = 0 136 return nlen 137 } 138 if name[0] != 0 { 139 if name[1] == 0x3A { // Windows drive letter 'X:' 140 let nlen: i64 = cstr_len(name) 141 u8_copy(out, name, nlen) 142 out[nlen] = 0 143 return nlen 144 } 145 } 146 let dlen: i64 = cstr_len(dir) 147 if dlen == 0 { 148 let nlen: i64 = cstr_len(name) 149 u8_copy(out, name, nlen) 150 out[nlen] = 0 151 return nlen 152 } 153 u8_copy(out, dir, dlen) 154 out[dlen] = 0x2F 155 let nlen: i64 = cstr_len(name) 156 u8_copy(ptr_at(out, dlen + 1), name, nlen) 157 let total: i64 = dlen + 1 + nlen 158 out[total] = 0 159 return total 160} 161 162// Return 1 if byte is a path separator. 163func is_sep(b: i64) -> i64 { 164 if b == 0x2F { return 1 } 165 if b == 0x5C { return 1 } 166 return 0 167} 168 169// Canonicalise `path` so semantically-identical forms collapse to one 170// key. Pure textual: pops `..`, drops `.`, unifies '/' separators. 171// `scratch` must hold >= 4096 bytes (two segment tables). Returns 172// output length. 173func canonicalise_path(path: *u8, out: *u8, scratch: *u8) -> i64 { 174 let n: i64 = cstr_len(path) 175 176 // First pass: extract segments. Each entry is (offset, length) 177 // into the original path buffer. 178 let seg_offs: *i64 = scratch as *i64 179 let seg_lens: *i64 = ptr_at(scratch, 1024) as *i64 180 var n_seg: i64 = 0 181 var i: i64 = 0 182 while i < n { 183 while i < n { 184 if is_sep(path[i]) == 0 { break } 185 i = i + 1 186 } 187 if i >= n { break } 188 let start: i64 = i 189 while i < n { 190 if is_sep(path[i]) == 1 { break } 191 i = i + 1 192 } 193 let seg_len: i64 = i - start 194 if seg_len > 0 { 195 if n_seg < 128 { 196 seg_offs[n_seg] = start 197 seg_lens[n_seg] = seg_len 198 n_seg = n_seg + 1 199 } 200 } 201 } 202 203 // Second pass: apply `.` drop and `..` pop. 204 let out_offs: *i64 = ptr_at(scratch, 2048) as *i64 205 let out_lens: *i64 = ptr_at(scratch, 3072) as *i64 206 var outn: i64 = 0 207 var k: i64 = 0 208 while k < n_seg { 209 let o: i64 = seg_offs[k] 210 let l: i64 = seg_lens[k] 211 // Is this segment "." ? 212 if l == 1 { 213 if path[o] == 0x2E { 214 k = k + 1 215 continue 216 } 217 } 218 // Is this segment ".." ? 219 if l == 2 { 220 if path[o] == 0x2E { 221 if path[o + 1] == 0x2E { 222 if outn > 0 { 223 outn = outn - 1 224 } else { 225 out_offs[outn] = o 226 out_lens[outn] = l 227 outn = outn + 1 228 } 229 k = k + 1 230 continue 231 } 232 } 233 } 234 out_offs[outn] = o 235 out_lens[outn] = l 236 outn = outn + 1 237 k = k + 1 238 } 239 240 // Rejoin with '/' separator. Preserve leading slash if absolute. 241 var pos: i64 = 0 242 if is_sep(path[0]) == 1 { out[pos] = 0x2F; pos = pos + 1 } 243 var j: i64 = 0 244 while j < outn { 245 if j > 0 { out[pos] = 0x2F; pos = pos + 1 } 246 let o: i64 = out_offs[j] 247 let l: i64 = out_lens[j] 248 u8_copy(ptr_at(out, pos), ptr_at(path, o), l) 249 pos = pos + l 250 j = j + 1 251 } 252 out[pos] = 0 253 return pos 254} 255 256// ---- import line detection ---------------------------------------- 257 258// Does src[p..] begin with `import` followed by space or tab? 259func starts_with_import(src: *u8, p: i64) -> i64 { 260 if src[p + 0] != 0x69 { return 0 } // 'i' 261 if src[p + 1] != 0x6D { return 0 } // 'm' 262 if src[p + 2] != 0x70 { return 0 } // 'p' 263 if src[p + 3] != 0x6F { return 0 } // 'o' 264 if src[p + 4] != 0x72 { return 0 } // 'r' 265 if src[p + 5] != 0x74 { return 0 } // 't' 266 let c: i64 = src[p + 6] 267 if c == 0x20 { return 1 } 268 if c == 0x09 { return 1 } 269 return 0 270} 271 272// ---- state singleton ---------------------------------------------- 273// 274// expand_imports is naturally recursive. Rather than pass many args 275// through each recursive call, we stash shared state in a small 276// struct allocated once by the top-level caller. 277struct ExpandCtx { 278 paths: *u8, // seen-set storage (MAX_IMPORTS * IMPORT_PATH_LEN) 279 n_paths: *i64, // counter into `paths` 280 out: *u8, // destination buffer 281 out_pos: *i64, // current write offset into `out` 282 out_cap: i64, // destination capacity (bytes) 283 scratch: *u8, // >= 4096 bytes for canonicalise 284 // Reserved slots for future instrumentation (import depth, error 285 // line numbers) without rippling the signature change across 286 // every recursive call. 287 pad0: i64, 288 pad1: i64, 289} 290 291// Allocate an ExpandCtx with heap-backed storage. Caller owns the 292// pointer; compiler is run-once so OS reclaims at exit. 293func expand_ctx_new(out: *u8, out_cap: i64) -> *ExpandCtx { 294 let ctx_raw: *u8 = sys_mmap(64) 295 let ctx: *ExpandCtx = ctx_raw as *ExpandCtx 296 ctx.paths = sys_mmap(MAX_IMPORTS * IMPORT_PATH_LEN) 297 let n_raw: *u8 = sys_mmap(16) 298 let np: *i64 = n_raw as *i64 299 *np = 0 300 ctx.n_paths = np 301 ctx.out = out 302 let op_raw: *u8 = sys_mmap(16) 303 let op: *i64 = op_raw as *i64 304 *op = 0 305 ctx.out_pos = op 306 ctx.out_cap = out_cap 307 ctx.scratch = sys_mmap(4096) 308 return ctx 309} 310 311// Append one byte to the output buffer. Returns 0 on success, 312// ERR_OUT_OVERFLOW on out-of-room. 313func out_push(ctx: *ExpandCtx, b: i64) -> i64 { 314 let op: *i64 = ctx.out_pos 315 let pos: i64 = *op 316 if pos + 1 >= ctx.out_cap { return ERR_OUT_OVERFLOW } 317 ctx.out[pos] = b 318 *op = pos + 1 319 return 0 320} 321 322// Append n bytes. Returns 0 or ERR_OUT_OVERFLOW. 323func out_append(ctx: *ExpandCtx, src: *u8, n: i64) -> i64 { 324 let op: *i64 = ctx.out_pos 325 let pos: i64 = *op 326 if pos + n + 1 >= ctx.out_cap { return ERR_OUT_OVERFLOW } 327 u8_copy(ptr_at(ctx.out, pos), src, n) 328 *op = pos + n 329 return 0 330} 331 332// Recursively expand `path` (relative or absolute) into ctx->out. 333// Returns 0 on success, or a negative ERR_* code on failure. 334// Match `func main(` (with optional leading whitespace already consumed) 335// at offset p in src. Returns 1 if matched, 0 otherwise. Used to 336// strip self-test main functions out of imported (non-root) files, 337// mirroring the C anchor's main.c logic so a self-host compile 338// doesn't produce N duplicate `main` symbols at link time. 339func is_func_main_line(src: *u8, p: i64) -> i64 { 340 if src[p] != 0x66 { return 0 } // 'f' 341 if src[p+1] != 0x75 { return 0 } // 'u' 342 if src[p+2] != 0x6E { return 0 } // 'n' 343 if src[p+3] != 0x63 { return 0 } // 'c' 344 if src[p+4] != 0x20 { return 0 } // ' ' 345 if src[p+5] != 0x6D { return 0 } // 'm' 346 if src[p+6] != 0x61 { return 0 } // 'a' 347 if src[p+7] != 0x69 { return 0 } // 'i' 348 if src[p+8] != 0x6E { return 0 } // 'n' 349 // followed by '(' or ' (' 350 if src[p+9] == 0x28 { return 1 } // '(' 351 if src[p+9] == 0x20 { 352 if src[p+10] == 0x28 { return 1 } 353 } 354 return 0 355} 356 357// Skip past a `func main(...) ... { ... }` body, returning the new 358// cursor position past the closing '}' + trailing newline (if any). 359// STRING/COMMENT-AWARE (2026-08-04, debt 1785894599): the raw brace count died on 360// nx_js_lex's gate-main, whose KAT6 asserts the lexeme "}" -- the in-STRING 0x7D 361// closed the skip mid-body and spilled the rest of main to module level, where the 362// same-day strict stray-statement check (nx_parse 1785883072) refused the unit. The 363// dual failure is worse: a '{' inside a string OVER-skips and silently eats the 364// functions after main. Braces now count only outside "..." literals (backslash 365// escapes honored) and // line comments -- the same classes the real lexer tokenizes. 366func skip_func_main(src: *u8, p: i64) -> i64 { 367 var q: i64 = p 368 while src[q] != 0 { 369 if src[q] == 0x7B { break } // '{' 370 q = q + 1 371 } 372 if src[q] == 0x7B { 373 var depth: i64 = 1 374 q = q + 1 375 var instr: i64 = 0 // inside a "..." string literal 376 var incom: i64 = 0 // inside a // line comment 377 while src[q] != 0 { 378 if depth == 0 { break } 379 var c: i64 = src[q] as i64 380 if incom == 1 { 381 if c == 0x0A { incom = 0 } 382 q = q + 1 383 } else { if instr == 1 { 384 if c == 0x5C { // backslash: the escaped char is literal 385 q = q + 2 386 } else { 387 if c == 0x22 { instr = 0 } 388 q = q + 1 389 } 390 } else { 391 if c == 0x22 { instr = 1; q = q + 1 } else { 392 if c == 0x2F { 393 var c2: i64 = src[q + 1] as i64 394 if c2 == 0x2F { incom = 1; q = q + 2 } else { q = q + 1 } 395 } else { 396 if c == 0x7B { depth = depth + 1 } 397 if c == 0x7D { depth = depth - 1 } 398 q = q + 1 399 } } 400 } } 401 } 402 } 403 while src[q] != 0 { 404 if src[q] == 0x0A { break } 405 q = q + 1 406 } 407 if src[q] == 0x0A { q = q + 1 } 408 return q 409} 410 411// Forward decl so expand_imports can call expand_imports_inner 412// before it's defined (NishiLang requires top-down declaration 413// order today). 414func expand_imports_inner(ctx: *ExpandCtx, path: *u8, is_root: i64) -> i64; 415 416// SITES-LIVE 2026-05-27 per operator full-blessing directive to get 417// nishifamily.com + andelinwest.com live on west NAS via native Nishi 418// containers. Resolver walk-up + sibling-subroot search: hub/foo.nx 419// imports bare nx_syscalls.nx (lives at runtime/) AND runtime/* imports 420// bare nx_search_query_parser.nx (lives at runtime/hub/). Walks parent 421// dirs + at each level tries known substrate subroots ("", "hub", 422// "wiki", "bin", "kernel"). Mirrors C bootstrap try_resolve_import. 423// (Earlier this session, I incorrectly edited runtime/import.nx -- 424// that file is for the OLDER nxc.nx driver, not the native ELF's 425// nx_compile_x86.nx driver which imports THIS file. Lesson logged.) 426 427func file_exists(path: *u8) -> i64 { 428 let fd: i64 = sys_openat_rd(path) 429 if fd < 0 { return 0 } 430 sys_close(fd) 431 return 1 432} 433 434func try_subroot(dir: *u8, subroot: *u8, ipath: *u8, 435 scratch: *u8, out: *u8) -> i64 { 436 let dir_n: i64 = cstr_len(dir) 437 let sub_n: i64 = cstr_len(subroot) 438 let ipath_n: i64 = cstr_len(ipath) 439 var pos: i64 = 0 440 if dir_n > 0 { 441 u8_copy(ptr_at(scratch, pos), dir, dir_n) 442 pos = pos + dir_n 443 scratch[pos] = 0x2F 444 pos = pos + 1 445 } 446 if sub_n > 0 { 447 u8_copy(ptr_at(scratch, pos), subroot, sub_n) 448 pos = pos + sub_n 449 scratch[pos] = 0x2F 450 pos = pos + 1 451 } 452 u8_copy(ptr_at(scratch, pos), ipath, ipath_n) 453 pos = pos + ipath_n 454 scratch[pos] = 0 455 if file_exists(scratch) == 1 { 456 u8_copy(out, scratch, pos + 1) 457 return 1 458 } 459 return 0 460} 461 462func try_resolve_import(dir: *u8, ipath: *u8, out: *u8) -> i64 { 463 if ipath[0] == 0x2F { 464 u8_copy(out, ipath, cstr_len(ipath) + 1) 465 return 1 466 } 467 let trydir: *u8 = sys_mmap(IMPORT_PATH_LEN) 468 let scratch: *u8 = sys_mmap(IMPORT_PATH_LEN) 469 u8_copy(trydir, dir, cstr_len(dir) + 1) 470 let empty: *u8 = sys_mmap(2) 471 empty[0] = 0 472 let sub_hub: *u8 = sys_mmap(8) 473 sub_hub[0] = 0x68 474 sub_hub[1] = 0x75 475 sub_hub[2] = 0x62 476 sub_hub[3] = 0 477 let sub_wik: *u8 = sys_mmap(8) 478 sub_wik[0] = 0x77 479 sub_wik[1] = 0x69 480 sub_wik[2] = 0x6B 481 sub_wik[3] = 0x69 482 sub_wik[4] = 0 483 let sub_bin: *u8 = sys_mmap(8) 484 sub_bin[0] = 0x62 485 sub_bin[1] = 0x69 486 sub_bin[2] = 0x6E 487 sub_bin[3] = 0 488 let sub_krn: *u8 = sys_mmap(8) 489 sub_krn[0] = 0x6B 490 sub_krn[1] = 0x65 491 sub_krn[2] = 0x72 492 sub_krn[3] = 0x6E 493 sub_krn[4] = 0x65 494 sub_krn[5] = 0x6C 495 sub_krn[6] = 0 496 // "_hdl_build" -- debt 1785608999: runtime/ entries could not import 497 // _hdl_build/ modules (one-way ceiling; _hdl_build->runtime worked via 498 // walk-up). Tried LAST at every level, so no import that resolves 499 // today can change -- only previously-FAILING ones can start to. 500 let sub_hdl: *u8 = sys_mmap(16) 501 sub_hdl[0] = 0x5F 502 sub_hdl[1] = 0x68 503 sub_hdl[2] = 0x64 504 sub_hdl[3] = 0x6C 505 sub_hdl[4] = 0x5F 506 sub_hdl[5] = 0x62 507 sub_hdl[6] = 0x75 508 sub_hdl[7] = 0x69 509 sub_hdl[8] = 0x6C 510 sub_hdl[9] = 0x64 511 sub_hdl[10] = 0 512 var hop: i64 = 0 513 while hop < 16 { 514 if try_subroot(trydir, empty, ipath, scratch, out) == 1 { return 1 } 515 if try_subroot(trydir, sub_hub, ipath, scratch, out) == 1 { return 1 } 516 if try_subroot(trydir, sub_wik, ipath, scratch, out) == 1 { return 1 } 517 if try_subroot(trydir, sub_bin, ipath, scratch, out) == 1 { return 1 } 518 if try_subroot(trydir, sub_krn, ipath, scratch, out) == 1 { return 1 } 519 if try_subroot(trydir, sub_hdl, ipath, scratch, out) == 1 { return 1 } 520 let trydir_n: i64 = cstr_len(trydir) 521 if trydir_n == 0 { break } 522 var i: i64 = trydir_n - 1 523 var found: i64 = 0 524 while i >= 0 { 525 if trydir[i] == 0x2F { 526 trydir[i] = 0 527 found = 1 528 i = 0 - 1 529 } 530 if i >= 0 { i = i - 1 } 531 } 532 if found == 0 { trydir[0] = 0 } 533 hop = hop + 1 534 } 535 join_path(dir, ipath, out) 536 return 0 537} 538 539func expand_imports(ctx: *ExpandCtx, path: *u8) -> i64 { 540 return expand_imports_inner(ctx, path, 1) 541} 542 543func expand_imports_inner(ctx: *ExpandCtx, path: *u8, is_root: i64) -> i64 { 544 // Canonicalise for dedupe. 545 let abspath_raw: *u8 = sys_mmap(IMPORT_PATH_LEN) 546 canonicalise_path(path, abspath_raw, ctx.scratch) 547 let np: *i64 = ctx.n_paths 548 if import_already(ctx.paths, *np, abspath_raw) == 1 { 549 return 0 550 } 551 if import_add(ctx.paths, *np, abspath_raw) == 0 { 552 return ERR_IMPORTS_FULL 553 } 554 *np = *np + 1 555 556 // Read the file. 557 let len_raw: *u8 = sys_mmap(16) 558 let len_out: *i64 = len_raw as *i64 559 *len_out = 0 560 let src: *u8 = sys_read_file(path, len_out) 561 if src == (0 as *u8) { return ERR_READ_FAILED } 562 563 // Compute this file's dir for resolving relative imports. 564 let dir: *u8 = sys_mmap(IMPORT_PATH_LEN) 565 path_dir(path, dir) 566 567 // Walk src line by line. 568 var p: i64 = 0 569 while src[p] != 0 { 570 // Leading whitespace scan, preserving cursor so we can copy 571 // the original bytes if this line isn't an import. 572 let line_start: i64 = p 573 while src[p] == 0x20 { p = p + 1 } 574 while src[p] == 0x09 { p = p + 1 } 575 576 // Strip `func main(...)` from non-root files so a self-host 577 // compile doesn't see N duplicate main symbols. Mirrors 578 // main.c lines 228-274. 579 if is_root == 0 { 580 if is_func_main_line(src, p) == 1 { 581 p = skip_func_main(src, p) 582 continue 583 } 584 } 585 586 if starts_with_import(src, p) == 1 { 587 p = p + 7 588 while src[p] == 0x20 { p = p + 1 } 589 while src[p] == 0x09 { p = p + 1 } 590 if src[p] != 0x22 { return ERR_BAD_IMPORT } 591 p = p + 1 592 593 // Read quoted relative path. 594 let ipath: *u8 = sys_mmap(IMPORT_PATH_LEN) 595 var il: i64 = 0 596 while src[p] != 0 { 597 if src[p] == 0x22 { break } 598 if il + 1 >= IMPORT_PATH_LEN { return ERR_BAD_IMPORT } 599 ipath[il] = src[p] 600 il = il + 1 601 p = p + 1 602 } 603 ipath[il] = 0 604 if src[p] == 0x22 { p = p + 1 } 605 606 // Skip to end-of-line. 607 while src[p] != 0 { 608 if src[p] == 0x0A { break } 609 p = p + 1 610 } 611 if src[p] == 0x0A { p = p + 1 } 612 613 // SITES-LIVE 2026-05-27: walk-up + sibling-subroot resolver 614 // so hub/foo.nx can import bare runtime/* siblings + vice 615 // versa. See try_resolve_import above. 616 let full: *u8 = sys_mmap(IMPORT_PATH_LEN) 617 try_resolve_import(dir, ipath, full) 618 619 // Recurse with is_root=0 so any nested file's `func main` 620 // gets stripped (see top of expand_imports_inner). 621 let rc: i64 = expand_imports_inner(ctx, full, 0) 622 if rc < 0 { return rc } 623 // Terminating newline between spliced files. 624 let rc2: i64 = out_push(ctx, 0x0A) 625 if rc2 < 0 { return rc2 } 626 continue 627 } 628 629 // Not an import -- copy the whole line through. 630 p = line_start 631 while src[p] != 0 { 632 if src[p] == 0x0A { 633 let rc: i64 = out_push(ctx, 0x0A) 634 if rc < 0 { return rc } 635 p = p + 1 636 break 637 } 638 let rc: i64 = out_push(ctx, src[p]) 639 if rc < 0 { return rc } 640 p = p + 1 641 } 642 } 643 return 0 644}