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}