code wiki / (root) / import.nx

import.nx source

↩ module page · 579 lines · 21323 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 32import "syscalls.nx" 33 34// ---- sizing knobs -------------------------------------------------- 35 36const MAX_IMPORTS: i64 = 64 37const IMPORT_PATH_LEN: i64 = 1024 38 39// Error codes returned by expand functions (negative = error). 40const ERR_IMPORTS_FULL: i64 = -1 41const ERR_READ_FAILED: i64 = -2 42const ERR_OUT_OVERFLOW: i64 = -3 43const ERR_BAD_IMPORT: i64 = -4 44 45// ---- cstring helpers ---------------------------------------------- 46 47func cstr_len(s: *u8) -> i64 { 48 var n: i64 = 0 49 while s[n] != 0 { n = n + 1 } 50 return n 51} 52 53func cstr_eq(a: *u8, b: *u8) -> i64 { 54 var i: i64 = 0 55 while a[i] != 0 { 56 if a[i] != b[i] { return 0 } 57 i = i + 1 58 } 59 if b[i] != 0 { return 0 } 60 return 1 61} 62 63// Copy exactly `n` bytes from src to dst. No null-term responsibility. 64func u8_copy(dst: *u8, src: *u8, n: i64) -> i64 { 65 var i: i64 = 0 66 while i < n { dst[i] = src[i]; i = i + 1 } 67 return n 68} 69 70// Shift a *u8 pointer by `delta` bytes (address arithmetic). 71func ptr_at(p: *u8, delta: i64) -> *u8 { 72 let a: i64 = (p as i64) + delta 73 return a as *u8 74} 75 76// ---- ImportSet ----------------------------------------------------- 77// 78// Flat MAX_IMPORTS * IMPORT_PATH_LEN byte buffer. Entry i lives at 79// offset i * IMPORT_PATH_LEN. Counter held externally. 80 81func import_already(paths: *u8, n: i64, path: *u8) -> i64 { 82 var i: i64 = 0 83 while i < n { 84 let entry: *u8 = ptr_at(paths, i * IMPORT_PATH_LEN) 85 if cstr_eq(entry, path) == 1 { return 1 } 86 i = i + 1 87 } 88 return 0 89} 90 91func import_add(paths: *u8, n: i64, path: *u8) -> i64 { 92 if n >= MAX_IMPORTS { return 0 } 93 let entry: *u8 = ptr_at(paths, n * IMPORT_PATH_LEN) 94 var plen: i64 = cstr_len(path) 95 if plen > IMPORT_PATH_LEN - 1 { plen = IMPORT_PATH_LEN - 1 } 96 u8_copy(entry, path, plen) 97 entry[plen] = 0 98 return 1 99} 100 101// ---- path ops ------------------------------------------------------ 102 103// Extract directory portion of `path`. Everything before the final 104// '/' or '\'. Writes null-terminated to `out`. Returns length. 105func path_dir(path: *u8, out: *u8) -> i64 { 106 let n: i64 = cstr_len(path) 107 var last: i64 = 0 - 1 108 var i: i64 = 0 109 while i < n { 110 if path[i] == 0x2F { last = i } // '/' 111 if path[i] == 0x5C { last = i } // '\\' 112 i = i + 1 113 } 114 if last < 0 { out[0] = 0; return 0 } 115 u8_copy(out, path, last) 116 out[last] = 0 117 return last 118} 119 120// Compose `dir/name` into `out`. If name is absolute (leading '/' or 121// 'X:' drive letter), copy name verbatim. Returns length. 122func join_path(dir: *u8, name: *u8, out: *u8) -> i64 { 123 if name[0] == 0x2F { 124 let nlen: i64 = cstr_len(name) 125 u8_copy(out, name, nlen) 126 out[nlen] = 0 127 return nlen 128 } 129 if name[0] != 0 { 130 if name[1] == 0x3A { // Windows drive letter 'X:' 131 let nlen: i64 = cstr_len(name) 132 u8_copy(out, name, nlen) 133 out[nlen] = 0 134 return nlen 135 } 136 } 137 let dlen: i64 = cstr_len(dir) 138 if dlen == 0 { 139 let nlen: i64 = cstr_len(name) 140 u8_copy(out, name, nlen) 141 out[nlen] = 0 142 return nlen 143 } 144 u8_copy(out, dir, dlen) 145 out[dlen] = 0x2F 146 let nlen: i64 = cstr_len(name) 147 u8_copy(ptr_at(out, dlen + 1), name, nlen) 148 let total: i64 = dlen + 1 + nlen 149 out[total] = 0 150 return total 151} 152 153// Return 1 if byte is a path separator. 154func is_sep(b: i64) -> i64 { 155 if b == 0x2F { return 1 } 156 if b == 0x5C { return 1 } 157 return 0 158} 159 160// Canonicalise `path` so semantically-identical forms collapse to one 161// key. Pure textual: pops `..`, drops `.`, unifies '/' separators. 162// `scratch` must hold >= 4096 bytes (two segment tables). Returns 163// output length. 164func canonicalise_path(path: *u8, out: *u8, scratch: *u8) -> i64 { 165 let n: i64 = cstr_len(path) 166 167 // First pass: extract segments. Each entry is (offset, length) 168 // into the original path buffer. 169 let seg_offs: *i64 = scratch as *i64 170 let seg_lens: *i64 = ptr_at(scratch, 1024) as *i64 171 var n_seg: i64 = 0 172 var i: i64 = 0 173 while i < n { 174 while i < n { 175 if is_sep(path[i]) == 0 { break } 176 i = i + 1 177 } 178 if i >= n { break } 179 let start: i64 = i 180 while i < n { 181 if is_sep(path[i]) == 1 { break } 182 i = i + 1 183 } 184 let seg_len: i64 = i - start 185 if seg_len > 0 { 186 if n_seg < 128 { 187 seg_offs[n_seg] = start 188 seg_lens[n_seg] = seg_len 189 n_seg = n_seg + 1 190 } 191 } 192 } 193 194 // Second pass: apply `.` drop and `..` pop. 195 let out_offs: *i64 = ptr_at(scratch, 2048) as *i64 196 let out_lens: *i64 = ptr_at(scratch, 3072) as *i64 197 var outn: i64 = 0 198 var k: i64 = 0 199 while k < n_seg { 200 let o: i64 = seg_offs[k] 201 let l: i64 = seg_lens[k] 202 // Is this segment "." ? 203 if l == 1 { 204 if path[o] == 0x2E { 205 k = k + 1 206 continue 207 } 208 } 209 // Is this segment ".." ? 210 if l == 2 { 211 if path[o] == 0x2E { 212 if path[o + 1] == 0x2E { 213 if outn > 0 { 214 outn = outn - 1 215 } else { 216 out_offs[outn] = o 217 out_lens[outn] = l 218 outn = outn + 1 219 } 220 k = k + 1 221 continue 222 } 223 } 224 } 225 out_offs[outn] = o 226 out_lens[outn] = l 227 outn = outn + 1 228 k = k + 1 229 } 230 231 // Rejoin with '/' separator. Preserve leading slash if absolute. 232 var pos: i64 = 0 233 if is_sep(path[0]) == 1 { out[pos] = 0x2F; pos = pos + 1 } 234 var j: i64 = 0 235 while j < outn { 236 if j > 0 { out[pos] = 0x2F; pos = pos + 1 } 237 let o: i64 = out_offs[j] 238 let l: i64 = out_lens[j] 239 u8_copy(ptr_at(out, pos), ptr_at(path, o), l) 240 pos = pos + l 241 j = j + 1 242 } 243 out[pos] = 0 244 return pos 245} 246 247// ---- import line detection ---------------------------------------- 248 249// Does src[p..] begin with `import` followed by space or tab? 250func starts_with_import(src: *u8, p: i64) -> i64 { 251 if src[p + 0] != 0x69 { return 0 } // 'i' 252 if src[p + 1] != 0x6D { return 0 } // 'm' 253 if src[p + 2] != 0x70 { return 0 } // 'p' 254 if src[p + 3] != 0x6F { return 0 } // 'o' 255 if src[p + 4] != 0x72 { return 0 } // 'r' 256 if src[p + 5] != 0x74 { return 0 } // 't' 257 let c: i64 = src[p + 6] 258 if c == 0x20 { return 1 } 259 if c == 0x09 { return 1 } 260 return 0 261} 262 263// ---- state singleton ---------------------------------------------- 264// 265// expand_imports is naturally recursive. Rather than pass many args 266// through each recursive call, we stash shared state in a small 267// struct allocated once by the top-level caller. 268struct ExpandCtx { 269 paths: *u8, // seen-set storage (MAX_IMPORTS * IMPORT_PATH_LEN) 270 n_paths: *i64, // counter into `paths` 271 out: *u8, // destination buffer 272 out_pos: *i64, // current write offset into `out` 273 out_cap: i64, // destination capacity (bytes) 274 scratch: *u8, // >= 4096 bytes for canonicalise 275 // Reserved slots for future instrumentation (import depth, error 276 // line numbers) without rippling the signature change across 277 // every recursive call. 278 pad0: i64, 279 pad1: i64, 280} 281 282// Allocate an ExpandCtx with heap-backed storage. Caller owns the 283// pointer; compiler is run-once so OS reclaims at exit. 284func expand_ctx_new(out: *u8, out_cap: i64) -> *ExpandCtx { 285 let ctx_raw: *u8 = sys_mmap(64) 286 let ctx: *ExpandCtx = ctx_raw as *ExpandCtx 287 ctx.paths = sys_mmap(MAX_IMPORTS * IMPORT_PATH_LEN) 288 let n_raw: *u8 = sys_mmap(16) 289 let np: *i64 = n_raw as *i64 290 *np = 0 291 ctx.n_paths = np 292 ctx.out = out 293 let op_raw: *u8 = sys_mmap(16) 294 let op: *i64 = op_raw as *i64 295 *op = 0 296 ctx.out_pos = op 297 ctx.out_cap = out_cap 298 ctx.scratch = sys_mmap(4096) 299 return ctx 300} 301 302// Append one byte to the output buffer. Returns 0 on success, 303// ERR_OUT_OVERFLOW on out-of-room. 304func out_push(ctx: *ExpandCtx, b: i64) -> i64 { 305 let op: *i64 = ctx.out_pos 306 let pos: i64 = *op 307 if pos + 1 >= ctx.out_cap { return ERR_OUT_OVERFLOW } 308 ctx.out[pos] = b 309 *op = pos + 1 310 return 0 311} 312 313// Append n bytes. Returns 0 or ERR_OUT_OVERFLOW. 314func out_append(ctx: *ExpandCtx, src: *u8, n: i64) -> i64 { 315 let op: *i64 = ctx.out_pos 316 let pos: i64 = *op 317 if pos + n + 1 >= ctx.out_cap { return ERR_OUT_OVERFLOW } 318 u8_copy(ptr_at(ctx.out, pos), src, n) 319 *op = pos + n 320 return 0 321} 322 323// Recursively expand `path` (relative or absolute) into ctx->out. 324// Returns 0 on success, or a negative ERR_* code on failure. 325// Match `func main(` (with optional leading whitespace already consumed) 326// at offset p in src. Returns 1 if matched, 0 otherwise. Used to 327// strip self-test main functions out of imported (non-root) files, 328// mirroring the C anchor's main.c logic so a self-host compile 329// doesn't produce N duplicate `main` symbols at link time. 330func is_func_main_line(src: *u8, p: i64) -> i64 { 331 if src[p] != 0x66 { return 0 } // 'f' 332 if src[p+1] != 0x75 { return 0 } // 'u' 333 if src[p+2] != 0x6E { return 0 } // 'n' 334 if src[p+3] != 0x63 { return 0 } // 'c' 335 if src[p+4] != 0x20 { return 0 } // ' ' 336 if src[p+5] != 0x6D { return 0 } // 'm' 337 if src[p+6] != 0x61 { return 0 } // 'a' 338 if src[p+7] != 0x69 { return 0 } // 'i' 339 if src[p+8] != 0x6E { return 0 } // 'n' 340 // followed by '(' or ' (' 341 if src[p+9] == 0x28 { return 1 } // '(' 342 if src[p+9] == 0x20 { 343 if src[p+10] == 0x28 { return 1 } 344 } 345 return 0 346} 347 348// Skip past a `func main(...) ... { ... }` body, returning the new 349// cursor position past the closing '}' + trailing newline (if any). 350func skip_func_main(src: *u8, p: i64) -> i64 { 351 var q: i64 = p 352 while src[q] != 0 { 353 if src[q] == 0x7B { break } // '{' 354 q = q + 1 355 } 356 if src[q] == 0x7B { 357 var depth: i64 = 1 358 q = q + 1 359 while src[q] != 0 { 360 if depth == 0 { break } 361 if src[q] == 0x7B { depth = depth + 1 } 362 if src[q] == 0x7D { depth = depth - 1 } 363 q = q + 1 364 } 365 } 366 while src[q] != 0 { 367 if src[q] == 0x0A { break } 368 q = q + 1 369 } 370 if src[q] == 0x0A { q = q + 1 } 371 return q 372} 373 374// Forward decl so expand_imports can call expand_imports_inner 375// before it's defined (NishiLang requires top-down declaration 376// order today). 377func expand_imports_inner(ctx: *ExpandCtx, path: *u8, is_root: i64) -> i64; 378 379// SITES-LIVE 2026-05-27: file-existence check via sys_openat_rd. 380// Returns 1 if file exists + is readable, 0 otherwise. 381func file_exists(path: *u8) -> i64 { 382 let fd: i64 = sys_openat_rd(path) 383 if fd < 0 { return 0 } 384 sys_close(fd) 385 return 1 386} 387 388// SITES-LIVE 2026-05-27: walk-up + subroot resolver. Substrate 389// convention: hub/* modules import bare `nx_syscalls.nx` that 390// lives at runtime/nx_syscalls.nx (parent); runtime/* modules 391// import bare `nx_search_query_parser.nx` that lives at 392// runtime/hub/* (sibling subroot). Walks parent dirs + at each 393// level tries known substrate subroots ("", "hub", "wiki", "bin", 394// "kernel"). Writes resolved path into `out` (falls back to dir/ 395// ipath join if nothing resolves so error diagnostics name the 396// intended target). 397// 398// Mirrors the C bootstrap try_resolve_import (which we cannot 399// modify per never-touch-c-bootstrap cardinal). This is the 400// NishiLang canonical version that the native ELF uses. 401func try_subroot(dir: *u8, subroot: *u8, ipath: *u8, 402 scratch: *u8, out: *u8) -> i64 { 403 let dir_n: i64 = cstr_len(dir) 404 let sub_n: i64 = cstr_len(subroot) 405 let ipath_n: i64 = cstr_len(ipath) 406 // Build attempt path: dir + "/" + subroot + "/" + ipath (or skip 407 // dir/subroot if empty). Substrate pointer-arith convention: 408 // ptr_at(p, delta) not raw `p + delta`. 409 var pos: i64 = 0 410 if dir_n > 0 { 411 u8_copy(ptr_at(scratch, pos), dir, dir_n); pos = pos + dir_n 412 scratch[pos] = 0x2F; pos = pos + 1 // '/' 413 } 414 if sub_n > 0 { 415 u8_copy(ptr_at(scratch, pos), subroot, sub_n); pos = pos + sub_n 416 scratch[pos] = 0x2F; pos = pos + 1 417 } 418 u8_copy(ptr_at(scratch, pos), ipath, ipath_n); pos = pos + ipath_n 419 scratch[pos] = 0 420 // DEBUG SITES-LIVE 2026-05-27: trace each attempt 421 sys_write(2, " try: " as *u8, 7) 422 sys_write(2, scratch, pos) 423 sys_write(2, "\n" as *u8, 1) 424 if file_exists(scratch) == 1 { 425 sys_write(2, " ^^^^ FOUND\n" as *u8, 13) 426 u8_copy(out, scratch, pos + 1) 427 return 1 428 } 429 return 0 430} 431 432func try_resolve_import(dir: *u8, ipath: *u8, out: *u8) -> i64 { 433 // Absolute paths short-circuit. 434 if ipath[0] == 0x2F { u8_copy(out, ipath, cstr_len(ipath) + 1); return 1 } 435 let trydir: *u8 = sys_mmap(IMPORT_PATH_LEN) 436 let scratch: *u8 = sys_mmap(IMPORT_PATH_LEN) 437 u8_copy(trydir, dir, cstr_len(dir) + 1) 438 let empty: *u8 = sys_mmap(2); empty[0] = 0 439 let sub_hub: *u8 = sys_mmap(4); sub_hub[0] = 0x68; sub_hub[1] = 0x75; sub_hub[2] = 0x62; sub_hub[3] = 0 // "hub" 440 let sub_wik: *u8 = sys_mmap(5); sub_wik[0] = 0x77; sub_wik[1] = 0x69; sub_wik[2] = 0x6B; sub_wik[3] = 0x69; sub_wik[4] = 0 // "wiki" 441 let sub_bin: *u8 = sys_mmap(4); sub_bin[0] = 0x62; sub_bin[1] = 0x69; sub_bin[2] = 0x6E; sub_bin[3] = 0 // "bin" 442 let sub_krn: *u8 = sys_mmap(7); sub_krn[0] = 0x6B; sub_krn[1] = 0x65; sub_krn[2] = 0x72; sub_krn[3] = 0x6E; sub_krn[4] = 0x65; sub_krn[5] = 0x6C; sub_krn[6] = 0 // "kernel" 443 // "_hdl_build" -- debt 1785608999: one-way import ceiling fix; tried LAST so only previously-failing imports can change. Twin of the same patch in nx_import.nx (the live cc's copy). 444 let sub_hdl: *u8 = sys_mmap(16); sub_hdl[0] = 0x5F; sub_hdl[1] = 0x68; sub_hdl[2] = 0x64; sub_hdl[3] = 0x6C; sub_hdl[4] = 0x5F; sub_hdl[5] = 0x62; sub_hdl[6] = 0x75; sub_hdl[7] = 0x69; sub_hdl[8] = 0x6C; sub_hdl[9] = 0x64; sub_hdl[10] = 0 // "_hdl_build" 445 var hop: i64 = 0 446 while hop < 16 { 447 if try_subroot(trydir, empty, ipath, scratch, out) == 1 { return 1 } 448 if try_subroot(trydir, sub_hub, ipath, scratch, out) == 1 { return 1 } 449 if try_subroot(trydir, sub_wik, ipath, scratch, out) == 1 { return 1 } 450 if try_subroot(trydir, sub_bin, ipath, scratch, out) == 1 { return 1 } 451 if try_subroot(trydir, sub_krn, ipath, scratch, out) == 1 { return 1 } 452 if try_subroot(trydir, sub_hdl, ipath, scratch, out) == 1 { return 1 } 453 // Walk up: trim trailing /<segment> from trydir. 454 let trydir_n: i64 = cstr_len(trydir) 455 if trydir_n == 0 { break } 456 var i: i64 = trydir_n - 1 457 while i >= 0 { 458 if trydir[i] == 0x2F { trydir[i] = 0; break } 459 if i == 0 { trydir[0] = 0 } 460 i = i - 1 461 } 462 hop = hop + 1 463 } 464 // Nothing resolved -- fall back to original dir/ipath join so the 465 // downstream read_file error message names the intended target. 466 join_path(dir, ipath, out) 467 return 0 468} 469 470func expand_imports(ctx: *ExpandCtx, path: *u8) -> i64 { 471 return expand_imports_inner(ctx, path, 1) 472} 473 474func expand_imports_inner(ctx: *ExpandCtx, path: *u8, is_root: i64) -> i64 { 475 // Canonicalise for dedupe. 476 let abspath_raw: *u8 = sys_mmap(IMPORT_PATH_LEN) 477 canonicalise_path(path, abspath_raw, ctx.scratch) 478 let np: *i64 = ctx.n_paths 479 if import_already(ctx.paths, *np, abspath_raw) == 1 { 480 return 0 481 } 482 if import_add(ctx.paths, *np, abspath_raw) == 0 { 483 return ERR_IMPORTS_FULL 484 } 485 *np = *np + 1 486 487 // Read the file. 488 let len_raw: *u8 = sys_mmap(16) 489 let len_out: *i64 = len_raw as *i64 490 *len_out = 0 491 let src: *u8 = sys_read_file(path, len_out) 492 if src == (0 as *u8) { return ERR_READ_FAILED } 493 494 // Compute this file's dir for resolving relative imports. 495 let dir: *u8 = sys_mmap(IMPORT_PATH_LEN) 496 path_dir(path, dir) 497 498 // Walk src line by line. 499 var p: i64 = 0 500 while src[p] != 0 { 501 // Leading whitespace scan, preserving cursor so we can copy 502 // the original bytes if this line isn't an import. 503 let line_start: i64 = p 504 while src[p] == 0x20 { p = p + 1 } 505 while src[p] == 0x09 { p = p + 1 } 506 507 // Strip `func main(...)` from non-root files so a self-host 508 // compile doesn't see N duplicate main symbols. Mirrors 509 // main.c lines 228-274. 510 if is_root == 0 { 511 if is_func_main_line(src, p) == 1 { 512 p = skip_func_main(src, p) 513 continue 514 } 515 } 516 517 if starts_with_import(src, p) == 1 { 518 p = p + 7 519 while src[p] == 0x20 { p = p + 1 } 520 while src[p] == 0x09 { p = p + 1 } 521 if src[p] != 0x22 { return ERR_BAD_IMPORT } 522 p = p + 1 523 524 // Read quoted relative path. 525 let ipath: *u8 = sys_mmap(IMPORT_PATH_LEN) 526 var il: i64 = 0 527 while src[p] != 0 { 528 if src[p] == 0x22 { break } 529 if il + 1 >= IMPORT_PATH_LEN { return ERR_BAD_IMPORT } 530 ipath[il] = src[p] 531 il = il + 1 532 p = p + 1 533 } 534 ipath[il] = 0 535 if src[p] == 0x22 { p = p + 1 } 536 537 // Skip to end-of-line. 538 while src[p] != 0 { 539 if src[p] == 0x0A { break } 540 p = p + 1 541 } 542 if src[p] == 0x0A { p = p + 1 } 543 544 // Resolve relative to current file's dir, then walk up + 545 // try sibling subroots (hub/wiki/bin/kernel) at each level. 546 // Mirrors the C bootstrap's try_resolve_import resolver 547 // (extended 2026-05-27 per substrate "hub/foo.nx imports 548 // bare nx_syscalls.nx but file lives in runtime/" pattern). 549 // Per SITES-LIVE arc 2026-05-27 -- needed so native ELF 550 // can compile hub/* modules. 551 let full: *u8 = sys_mmap(IMPORT_PATH_LEN) 552 try_resolve_import(dir, ipath, full) 553 554 // Recurse with is_root=0 so any nested file's `func main` 555 // gets stripped (see top of expand_imports_inner). 556 let rc: i64 = expand_imports_inner(ctx, full, 0) 557 if rc < 0 { return rc } 558 // Terminating newline between spliced files. 559 let rc2: i64 = out_push(ctx, 0x0A) 560 if rc2 < 0 { return rc2 } 561 continue 562 } 563 564 // Not an import -- copy the whole line through. 565 p = line_start 566 while src[p] != 0 { 567 if src[p] == 0x0A { 568 let rc: i64 = out_push(ctx, 0x0A) 569 if rc < 0 { return rc } 570 p = p + 1 571 break 572 } 573 let rc: i64 = out_push(ctx, src[p]) 574 if rc < 0 { return rc } 575 p = p + 1 576 } 577 } 578 return 0 579}