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}