nx_doc_func_scan.nx source
↩ module page · 174 lines · 7794 B
1// nx_doc_func_scan.nx -- bits-up function-pattern extraction.
2//
3// Substrate-canonical building block for replacing bash + grep +
4// awk in audit primitives. Extracts `func NAME(` patterns from
5// NishiLang source content, returns iterator results compatible
6// with nx_doc_extract_link_target's pattern.
7//
8// Per [[feedback-bits-up-shell-replace-linux-brother-tools]]:
9// substrate's audit infrastructure stops depending on Linux-brother
10// tools for its own self-audit. bench/nx_audit_within_file_func_
11// duplicates.sh (bash) is INTERIM; bits-up nx_audit_func_duplicates
12// composes THIS primitive.
13//
14// **Quadrant:** REFERENCE (Diátaxis)
15// **Topic Type:** REFERENCE (DITA)
16// **Status:** DRAFT (first stone; bits-up substitute for bash audit
17// detector)
18// **Trust State:** WRITTEN_UNTESTED
19// **Competitive State:** UNCLASSIFIED (no benchmark vs grep+awk
20// substrate-internally yet)
21
22import "nx_syscalls.nx"
23import "nx_string_ops.nx"
24
25// ===== Scan result struct =========================================
26//
27// Same shape as NxLinkScanResult in nx_doc_xref for iterator
28// composition consistency.
29
30struct NxFuncScanResult {
31 next_offset: i64, // byte offset past the matched 'func' decl
32 // (for caller's next-iteration start); -1 if
33 // no more matches found
34 n_name: i64, // length of extracted function name
35 // (-1 if out_cap exceeded; 0 if no match)
36}
37
38// Find next `func NAME(` declaration at column 0 starting at offset
39// start_at in content. NAME is alphanumeric + underscore. Requires
40// the line to END WITH `{` so forward declarations (`func foo;`)
41// don't match.
42//
43// Substrate-honest: pattern is the same one bash uses
44// (^func[[:space:]]+NAME[[:space:]]*\(.*\{[[:space:]]*$).
45//
46// Writes NAME to out_name (NUL-terminated); writes
47// next_offset + n_name to out_result.
48//
49// Composes nx_str_index_of (byte search) for fast `func` lookup.
50
51func nx_doc_func_scan_next(
52 content: *u8, n_content: i64,
53 start_at: i64,
54 out_name: *u8, out_cap: i64,
55 out_result: *NxFuncScanResult
56) {
57 out_result.next_offset = -1
58 out_result.n_name = 0
59 if start_at < 0 { return }
60 if start_at >= n_content { return }
61
62 // Scan for line-starts; check if the line begins with 'func ' (5 bytes).
63 var i: i64 = start_at
64 // If start_at != 0, we're mid-content; advance to next line start.
65 if i > 0 {
66 if content[i - 1] != 10 {
67 // Find next newline.
68 var scan_nl: i64 = 1
69 while scan_nl == 1 {
70 if i >= n_content { scan_nl = 0; continue }
71 if content[i] == 10 {
72 i = i + 1
73 scan_nl = 0
74 continue
75 }
76 i = i + 1
77 }
78 }
79 }
80
81 // Iterate line-by-line looking for 'func NAME(...) {' at column 0.
82 var scan: i64 = 1
83 while scan == 1 {
84 if i >= n_content { scan = 0; continue }
85
86 // Find end of this line.
87 var line_end: i64 = i
88 var find_end: i64 = 1
89 while find_end == 1 {
90 if line_end >= n_content { find_end = 0; continue }
91 if content[line_end] == 10 { find_end = 0; continue }
92 line_end = line_end + 1
93 }
94
95 // Check if line starts with 'func ' (102 117 110 99 32).
96 let line_len: i64 = line_end - i
97 if line_len >= 6 {
98 if content[i] == 102 {
99 if content[i + 1] == 117 {
100 if content[i + 2] == 110 {
101 if content[i + 3] == 99 {
102 if content[i + 4] == 32 {
103 // Check the line ends with '{' (possibly
104 // followed by whitespace).
105 var le: i64 = line_end - 1
106 var trim: i64 = 1
107 while trim == 1 {
108 if le <= i { trim = 0; continue }
109 if content[le] != 32 { trim = 0; continue }
110 if content[le] == 9 { trim = 0; continue }
111 le = le - 1
112 }
113 if content[le] == 123 { // '{'
114 // Extract NAME from after 'func '.
115 let name_start: i64 = i + 5
116 var name_end: i64 = name_start
117 var scan_name: i64 = 1
118 while scan_name == 1 {
119 if name_end >= line_end {
120 scan_name = 0
121 continue
122 }
123 let c: i64 = content[name_end] as i64
124 var is_id: i64 = 0
125 if c >= 97 { if c <= 122 { is_id = 1 } }
126 if c >= 65 { if c <= 90 { is_id = 1 } }
127 if c >= 48 { if c <= 57 { is_id = 1 } }
128 if c == 95 { is_id = 1 }
129 if is_id == 0 { scan_name = 0; continue }
130 name_end = name_end + 1
131 }
132 let name_len: i64 = name_end - name_start
133 if name_len > 0 {
134 // Verify next char is '(' (after optional ws).
135 var p: i64 = name_end
136 var skip_ws: i64 = 1
137 while skip_ws == 1 {
138 if p >= line_end { skip_ws = 0; continue }
139 if content[p] != 32 { if content[p] != 9 { skip_ws = 0; continue } }
140 p = p + 1
141 }
142 if p < line_end {
143 if content[p] == 40 { // '('
144 // Match! Write to out_name.
145 if name_len + 1 > out_cap {
146 out_result.n_name = -1
147 out_result.next_offset = line_end + 1
148 return
149 }
150 var k: i64 = 0
151 while k < name_len {
152 out_name[k] = content[name_start + k]
153 k = k + 1
154 }
155 out_name[name_len] = 0
156 out_result.n_name = name_len
157 out_result.next_offset = line_end + 1
158 return
159 }
160 }
161 }
162 }
163 }
164 }
165 }
166 }
167 }
168 }
169
170 // Advance to next line.
171 if line_end >= n_content { scan = 0; continue }
172 i = line_end + 1
173 }
174}