logicaffeine_compile/codegen_c/
cshake.rs1use std::collections::{BTreeSet, VecDeque};
16
17#[derive(Debug, Clone)]
22pub struct CUnit {
23 pub defined: Vec<String>,
24 pub references: BTreeSet<String>,
25 pub text: String,
26 pub kind: UnitKind,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum UnitKind {
31 Typedef,
32 Function,
34 Global,
36 Preproc,
38}
39
40pub fn c_identifiers(s: &str) -> BTreeSet<String> {
42 let b = s.as_bytes();
43 let mut out = BTreeSet::new();
44 let mut i = 0;
45 while i < b.len() {
46 let c = b[i];
47 match c {
48 b'"' => {
49 i += 1;
50 while i < b.len() && b[i] != b'"' {
51 if b[i] == b'\\' {
52 i += 1;
53 }
54 i += 1;
55 }
56 i += 1;
57 }
58 b'\'' => {
59 i += 1;
60 while i < b.len() && b[i] != b'\'' {
61 if b[i] == b'\\' {
62 i += 1;
63 }
64 i += 1;
65 }
66 i += 1;
67 }
68 b'/' if i + 1 < b.len() && b[i + 1] == b'/' => {
69 while i < b.len() && b[i] != b'\n' {
70 i += 1;
71 }
72 }
73 b'/' if i + 1 < b.len() && b[i + 1] == b'*' => {
74 i += 2;
75 while i + 1 < b.len() && !(b[i] == b'*' && b[i + 1] == b'/') {
76 i += 1;
77 }
78 i += 2;
79 }
80 _ if c == b'_' || c.is_ascii_alphabetic() => {
81 let start = i;
82 while i < b.len() && (b[i] == b'_' || b[i].is_ascii_alphanumeric()) {
83 i += 1;
84 }
85 out.insert(s[start..i].to_string());
86 }
87 _ => i += 1,
88 }
89 }
90 out
91}
92
93struct Scan<'a> {
96 b: &'a [u8],
97 i: usize,
98}
99
100impl<'a> Scan<'a> {
101 fn step(&mut self) -> Option<u8> {
105 let b = self.b;
106 let c = b[self.i];
107 match c {
108 b'"' => {
109 self.i += 1;
110 while self.i < b.len() && b[self.i] != b'"' {
111 if b[self.i] == b'\\' {
112 self.i += 1;
113 }
114 self.i += 1;
115 }
116 self.i += 1;
117 None
118 }
119 b'\'' => {
120 self.i += 1;
121 while self.i < b.len() && b[self.i] != b'\'' {
122 if b[self.i] == b'\\' {
123 self.i += 1;
124 }
125 self.i += 1;
126 }
127 self.i += 1;
128 None
129 }
130 b'/' if self.i + 1 < b.len() && b[self.i + 1] == b'/' => {
131 while self.i < b.len() && b[self.i] != b'\n' {
132 self.i += 1;
133 }
134 None
135 }
136 b'/' if self.i + 1 < b.len() && b[self.i + 1] == b'*' => {
137 self.i += 2;
138 while self.i + 1 < b.len() && !(b[self.i] == b'*' && b[self.i + 1] == b'/') {
139 self.i += 1;
140 }
141 self.i += 2;
142 None
143 }
144 _ if c == b'_' || c.is_ascii_alphabetic() => {
145 while self.i < b.len() && (b[self.i] == b'_' || b[self.i].is_ascii_alphanumeric()) {
146 self.i += 1;
147 }
148 None
149 }
150 _ => {
151 self.i += 1;
152 Some(c)
153 }
154 }
155 }
156}
157
158pub fn parse_c_units(src: &str) -> Vec<CUnit> {
162 let b = src.as_bytes();
163 let mut units = Vec::new();
164 let mut unit_start = 0usize; let mut i = 0usize;
166 while i < b.len() {
168 while i < b.len() && (b[i] as char).is_whitespace() {
170 i += 1;
171 }
172 if i >= b.len() {
173 break;
174 }
175 if b[i] == b'/' && i + 1 < b.len() && (b[i + 1] == b'/' || b[i + 1] == b'*') {
177 let mut sc = Scan { b, i };
178 sc.step();
179 i = sc.i;
180 continue;
181 }
182 if b[i] == b'#' {
185 let ls = unit_start;
186 let continued = |nl: usize| {
187 nl > 0
188 && (b[nl - 1] == b'\\' || (nl > 1 && b[nl - 1] == b'\r' && b[nl - 2] == b'\\'))
189 };
190 while i < b.len() && (b[i] != b'\n' || continued(i)) {
191 i += 1;
192 }
193 if i < b.len() {
194 i += 1;
195 }
196 let text = src[ls..i].to_string();
197 units.push(CUnit {
198 defined: vec![],
199 references: BTreeSet::new(),
200 text,
201 kind: UnitKind::Preproc,
202 });
203 unit_start = i;
204 continue;
205 }
206 let code_start = i;
208 let starts_typedef = src[code_start..].trim_start().starts_with("typedef");
209 let mut sc = Scan { b, i };
210 let mut depth = 0i32;
211 let mut last_sig_before_brace: u8 = 0;
214 let mut last_sig: u8 = 0;
215 let mut end = b.len();
216 while sc.i < b.len() {
217 let at = sc.i;
218 if let Some(ch) = sc.step() {
219 match ch {
220 b'{' => {
221 if depth == 0 {
222 last_sig_before_brace = last_sig;
223 }
224 depth += 1;
225 }
226 b'}' => {
227 depth -= 1;
228 if depth == 0 && !starts_typedef && last_sig_before_brace == b')' {
229 end = sc.i;
231 break;
232 }
233 }
234 b';' if depth == 0 => {
235 end = sc.i;
236 break;
237 }
238 _ => {}
239 }
240 if !(ch as char).is_whitespace() {
241 last_sig = ch;
242 }
243 let _ = at;
244 }
245 }
246 let text = src[unit_start..end].to_string();
247 let code = &src[code_start..end];
248 let (defined, kind) = classify_unit(code, starts_typedef);
249 units.push(CUnit {
250 defined,
251 references: c_identifiers(code),
252 text,
253 kind,
254 });
255 i = end;
256 unit_start = end;
257 }
258 units
259}
260
261fn classify_unit(code: &str, starts_typedef: bool) -> (Vec<String>, UnitKind) {
263 if starts_typedef {
264 let name = last_ident(code);
268 return (name.into_iter().collect(), UnitKind::Typedef);
269 }
270 if let Some(tag) = struct_definition_tag(code) {
273 return (vec![tag], UnitKind::Typedef);
274 }
275 if let Some(name) = ident_before_first_paren(code) {
277 return (vec![name], UnitKind::Function);
278 }
279 let name = global_name(code);
281 (name.into_iter().collect(), UnitKind::Global)
282}
283
284fn struct_definition_tag(code: &str) -> Option<String> {
286 let rest = code.trim_start().strip_prefix("struct")?;
287 if !rest.starts_with(|c: char| c.is_whitespace()) {
288 return None;
289 }
290 let rest = rest.trim_start();
291 let end = rest
292 .find(|c: char| !(c == '_' || c.is_ascii_alphanumeric()))
293 .unwrap_or(rest.len());
294 let (tag, after) = rest.split_at(end);
295 let brace = after.find('{')?;
296 if tag.is_empty() || after[..brace].trim() != "" || after[..brace].contains('(') {
297 return None;
298 }
299 Some(tag.to_string())
300}
301
302fn ident_before_first_paren(code: &str) -> Option<String> {
303 let b = code.as_bytes();
304 let mut sc = Scan { b, i: 0 };
305 let mut last_ident: Option<(usize, usize)> = None;
306 while sc.i < b.len() {
307 let start = sc.i;
308 let c = b[sc.i];
309 if c == b'_' || c.is_ascii_alphabetic() {
310 sc.step();
311 last_ident = Some((start, sc.i));
312 } else if c == b'(' {
313 let is_attr = last_ident.map(|(s, e)| &code[s..e]) == Some("__attribute__");
316 sc.step(); if is_attr {
318 let mut depth = 1i32;
319 while sc.i < b.len() && depth > 0 {
320 match sc.step() {
321 Some(b'(') => depth += 1,
322 Some(b')') => depth -= 1,
323 _ => {}
324 }
325 }
326 last_ident = None;
327 continue;
328 }
329 return last_ident.map(|(s, e)| code[s..e].to_string());
330 } else {
331 sc.step();
332 }
333 }
334 None
335}
336
337fn last_ident(code: &str) -> Option<String> {
338 let b = code.as_bytes();
339 let mut sc = Scan { b, i: 0 };
340 let mut last: Option<(usize, usize)> = None;
341 while sc.i < b.len() {
342 let start = sc.i;
343 let c = b[sc.i];
344 if c == b'_' || c.is_ascii_alphabetic() {
345 sc.step();
346 last = Some((start, sc.i));
347 } else {
348 sc.step();
349 }
350 }
351 last.map(|(s, e)| code[s..e].to_string())
352}
353
354fn global_name(code: &str) -> Option<String> {
355 let b = code.as_bytes();
356 let mut sc = Scan { b, i: 0 };
357 let mut last_ident: Option<(usize, usize)> = None;
358 while sc.i < b.len() {
359 let start = sc.i;
360 let c = b[sc.i];
361 if c == b'_' || c.is_ascii_alphabetic() {
362 sc.step();
363 last_ident = Some((start, sc.i));
364 } else if c == b';' || c == b'=' || c == b'[' {
365 break;
366 } else {
367 sc.step();
368 }
369 }
370 last_ident.map(|(s, e)| code[s..e].to_string())
371}
372
373pub fn macro_references(unit: &CUnit) -> Option<(String, BTreeSet<String>)> {
377 if unit.kind != UnitKind::Preproc {
378 return None;
379 }
380 let mut text = unit.text.as_str();
381 loop {
382 text = text.trim_start();
383 if let Some(line) = text.strip_prefix("//") {
384 text = line.split_once('\n').map_or("", |(_, rest)| rest);
385 } else if let Some(block) = text.strip_prefix("/*") {
386 text = block.split_once("*/").map_or("", |(_, rest)| rest);
387 } else {
388 break;
389 }
390 }
391 let directive = text.strip_prefix('#')?.trim_start();
392 let rest = directive.strip_prefix("define")?;
393 if !rest.starts_with(|c: char| c.is_whitespace()) {
394 return None;
395 }
396 let rest = rest.trim_start();
397 let end = rest
398 .find(|c: char| c != '_' && !c.is_ascii_alphanumeric())
399 .unwrap_or(rest.len());
400 let name = &rest[..end];
401 if name.is_empty() {
402 return None;
403 }
404 let after = &rest[end..];
405 let (params, replacement): (BTreeSet<&str>, &str) = match after.strip_prefix('(') {
406 Some(list) => {
407 let close = list.find(')').unwrap_or(list.len());
408 (
409 list[..close].split(',').map(str::trim).collect(),
410 &list[(close + 1).min(list.len())..],
411 )
412 }
413 None => (BTreeSet::new(), after),
414 };
415 let references = c_identifiers(replacement)
416 .into_iter()
417 .filter(|r| r != name && !params.contains(r.as_str()))
418 .collect();
419 Some((name.to_string(), references))
420}
421
422pub fn reachable_units(units: &[CUnit], roots: &BTreeSet<String>) -> Vec<usize> {
425 let mut by_symbol: std::collections::HashMap<&str, Vec<usize>> =
427 std::collections::HashMap::new();
428 for (idx, u) in units.iter().enumerate() {
429 for d in &u.defined {
430 by_symbol.entry(d.as_str()).or_default().push(idx);
431 }
432 }
433 let mut wanted: BTreeSet<usize> = BTreeSet::new();
434 let mut queue: VecDeque<String> = roots.iter().cloned().collect();
435 let mut seen_sym: BTreeSet<String> = roots.iter().cloned().collect();
436 while let Some(sym) = queue.pop_front() {
437 if let Some(idxs) = by_symbol.get(sym.as_str()) {
438 for &idx in idxs {
439 if wanted.insert(idx) {
440 for r in &units[idx].references {
441 if seen_sym.insert(r.clone()) {
442 queue.push_back(r.clone());
443 }
444 }
445 }
446 }
447 }
448 }
449 wanted.into_iter().collect()
450}
451
452#[cfg(test)]
453#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_c/cshake/tests.rs"]
454mod tests;