cfnptr.rs 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309
  1. //! Native port of the cFnPtr synthesizer's EXTRACTION SWEEP (task #5 step 2,
  2. //! plan §7a.9): raw file text in → collected per-file facts out. The linking
  3. //! stages, gates, and every registration/dispatch decision stay TS-side; this
  4. //! module only reproduces, bug-for-bug, what the JS sweep computes per file in
  5. //! `src/resolution/c-fnptr-synthesizer.ts`:
  6. //!
  7. //! • `stripCommentsForRegex(text, 'c')` — the C-style comment/string state
  8. //! machine (comments blanked to spaces, string interiors skipped, backtick
  9. //! treated as a multi-line string delimiter — quirks and all);
  10. //! • the typedef scans (fn-pointer + fn-type forms);
  11. //! • struct-node field declarations (structural parse; classification stays
  12. //! TS-side where the complete typedef sets live);
  13. //! • the survival-filter scans (inline structs, initializers, bare arrays,
  14. //! alias-shaped object macros, field-assign pairs, dispatch fields, array
  15. //! dispatch names);
  16. //! • the raw-text `#include "..."` capture (path resolution stays TS-side —
  17. //! it needs the filesystem).
  18. //!
  19. //! Parity discipline: the JS side runs these as JavaScript REGEXES, so every
  20. //! scanner here is a hand-rolled byte machine replicating THAT engine's
  21. //! semantics, not idiomatic Rust regex:
  22. //! • JS `\w`/`\b` are ASCII (non-ASCII chars are non-word) — byte checks
  23. //! against `[A-Za-z0-9_]` reproduce them exactly, because UTF-8
  24. //! continuation bytes are non-ASCII and therefore non-word on both sides.
  25. //! • JS `\s` is the UNICODE whitespace class (NBSP, U+2000-200A, U+FEFF, …)
  26. //! — `jsws_len` decodes exactly that set from UTF-8.
  27. //! • Backtracking is reproduced where it is observable (INIT/ARRAY modifier
  28. //! and `struct` keyword ambiguity, DISPATCH's greedy segment loop,
  29. //! optional groups) and elided only where analysis shows no input can
  30. //! distinguish greedy from backtracked (documented per scanner).
  31. //! • `lastIndex` advancement (resume after each match, +1 on failure) is
  32. //! reproduced so overlapping-match selection is identical.
  33. //!
  34. //! The stripper blanks per UTF-16 code unit (see `strip_c`), so its output
  35. //! equals the JS stripper's output EXACTLY as a string — every scanner here
  36. //! runs over the identical character stream the JS regexes see, and the strip
  37. //! differential oracle test pins that equality directly. The record-level
  38. //! differential suite (JS sweep vs this sweep over fixtures and whole repos)
  39. //! then pins the scanners themselves.
  40. /// One struct node's extent, as the TS side reads it from the graph.
  41. pub struct StructExtent {
  42. pub id: String,
  43. pub start_line: u32,
  44. pub end_line: u32,
  45. }
  46. /// A structurally-parsed struct field — mirror of the TS `RawFieldDecl`
  47. /// (`name: null` is represented as an empty string; the TS side treats them
  48. /// identically everywhere).
  49. pub struct RawField {
  50. pub name: String,
  51. pub index: u32,
  52. pub ptr: bool,
  53. pub ty: String,
  54. }
  55. pub struct StructFields {
  56. pub id: String,
  57. /// False when the body never parsed (no `{`, unbalanced braces, or a
  58. /// falsy start line) — the TS side then records nothing for this node,
  59. /// exactly like the JS sweep.
  60. pub parsed: bool,
  61. pub fields: Vec<RawField>,
  62. }
  63. /// Everything the sweep collects for one file.
  64. pub struct FileFacts {
  65. pub fn_ptr_typedefs: Vec<String>,
  66. pub fn_type_typedefs: Vec<String>,
  67. pub structs: Vec<StructFields>,
  68. pub inline_ptr: bool,
  69. pub inline_types: Vec<String>,
  70. pub inline_tags: Vec<String>,
  71. pub init_tokens: Vec<String>,
  72. /// `*`-prefixed when the declaration carried the pointer star.
  73. pub array_elems: Vec<String>,
  74. pub alias_names: Vec<String>,
  75. /// `lfield\0rfield`, distinct.
  76. pub d_pairs: Vec<String>,
  77. pub dispatch_fields: Vec<String>,
  78. pub array_dispatch_names: Vec<String>,
  79. /// Raw `#include "…"` captures, in source order, NOT deduplicated —
  80. /// extension filtering and path resolution happen TS-side.
  81. pub includes: Vec<String>,
  82. }
  83. /// Mirror of the TS `C_TYPE_KEYWORDS` set — keep in exact sync.
  84. const C_TYPE_KEYWORDS: [&[u8]; 17] = [
  85. b"void", b"int", b"char", b"short", b"long", b"unsigned", b"signed", b"float", b"double",
  86. b"const", b"struct", b"union", b"enum", b"static", b"volatile", b"register", b"inline",
  87. ];
  88. fn is_type_keyword(w: &[u8]) -> bool {
  89. C_TYPE_KEYWORDS.iter().any(|k| *k == w)
  90. }
  91. const MODIFIERS: [&[u8]; 5] = [b"static", b"const", b"extern", b"register", b"volatile"];
  92. #[inline]
  93. fn is_word(b: u8) -> bool {
  94. b.is_ascii_alphanumeric() || b == b'_'
  95. }
  96. #[inline]
  97. fn is_word_at(s: &[u8], i: usize) -> bool {
  98. i < s.len() && is_word(s[i])
  99. }
  100. /// Byte length of the JS `\s` character starting at `i`, or 0 when `s[i]`
  101. /// doesn't start one. JS \s = [\t\n\v\f\r    -
  102. /// 
   ].
  103. #[inline]
  104. fn jsws_len(s: &[u8], i: usize) -> usize {
  105. let Some(&b0) = s.get(i) else { return 0 };
  106. match b0 {
  107. 0x09..=0x0D | 0x20 => 1,
  108. 0xC2 if s.get(i + 1) == Some(&0xA0) => 2, // U+00A0
  109. 0xE1 if s.get(i + 1) == Some(&0x9A) && s.get(i + 2) == Some(&0x80) => 3, // U+1680
  110. 0xE2 => match (s.get(i + 1), s.get(i + 2)) {
  111. (Some(&0x80), Some(&b2)) if (0x80..=0x8A).contains(&b2) => 3, // U+2000-200A
  112. (Some(&0x80), Some(&0xA8)) => 3, // U+2028
  113. (Some(&0x80), Some(&0xA9)) => 3, // U+2029
  114. (Some(&0x80), Some(&0xAF)) => 3, // U+202F
  115. (Some(&0x81), Some(&0x9F)) => 3, // U+205F
  116. _ => 0,
  117. },
  118. 0xE3 if s.get(i + 1) == Some(&0x80) && s.get(i + 2) == Some(&0x80) => 3, // U+3000
  119. 0xEF if s.get(i + 1) == Some(&0xBB) && s.get(i + 2) == Some(&0xBF) => 3, // U+FEFF
  120. _ => 0,
  121. }
  122. }
  123. /// Advance past `\s*`.
  124. #[inline]
  125. fn skip_jsws(s: &[u8], mut i: usize) -> usize {
  126. loop {
  127. let l = jsws_len(s, i);
  128. if l == 0 {
  129. return i;
  130. }
  131. i += l;
  132. }
  133. }
  134. /// End of the `\w+` run starting at `i` (caller checks `is_word_at(s, i)`).
  135. #[inline]
  136. fn word_end(s: &[u8], mut i: usize) -> usize {
  137. while i < s.len() && is_word(s[i]) {
  138. i += 1;
  139. }
  140. i
  141. }
  142. /// JS `\b` before position `i` (position 0, or previous byte non-word).
  143. #[inline]
  144. fn boundary_before(s: &[u8], i: usize) -> bool {
  145. i == 0 || !is_word(s[i - 1])
  146. }
  147. fn find_bytes(s: &[u8], needle: &[u8], from: usize) -> Option<usize> {
  148. if needle.is_empty() || s.len() < needle.len() {
  149. return None;
  150. }
  151. let mut i = from;
  152. while i + needle.len() <= s.len() {
  153. // memchr on the first byte keeps this fast on 20KB+ files.
  154. match s[i..s.len() - needle.len() + 1].iter().position(|&b| b == needle[0]) {
  155. None => return None,
  156. Some(off) => {
  157. i += off;
  158. if &s[i..i + needle.len()] == needle {
  159. return Some(i);
  160. }
  161. i += 1;
  162. }
  163. }
  164. }
  165. None
  166. }
  167. fn contains_bytes(s: &[u8], needle: &[u8]) -> bool {
  168. find_bytes(s, needle, 0).is_some()
  169. }
  170. /// `\bWORD\b` occurrence search from `from`.
  171. fn find_word(s: &[u8], word: &[u8], mut from: usize) -> Option<usize> {
  172. loop {
  173. let t = find_bytes(s, word, from)?;
  174. if boundary_before(s, t) && !is_word_at(s, t + word.len()) {
  175. return Some(t);
  176. }
  177. from = t + 1;
  178. }
  179. }
  180. // ---------- stripCommentsForRegex(src, 'c') ----------
  181. /// Port of `stripCStyle(src, /*allowSingleQuoteStrings*/ false)`:
  182. /// `/* */` and `//` comments blanked to spaces (newlines preserved), `"` and
  183. /// backtick string interiors skipped verbatim (backtick spans lines — the JS
  184. /// helper treats it as a template literal even for C), `'` NOT special.
  185. ///
  186. /// Blanking is per UTF-16 CODE UNIT (one space per BMP char, two per astral
  187. /// char), so the result equals the JS stripper's output EXACTLY as a string —
  188. /// the scanners downstream see the identical character stream the JS regexes
  189. /// see, and the strip differential oracle pins byte equality directly.
  190. /// (Comment boundaries — `/*`, `*/`, `//`, quotes, `\n` — are all ASCII, so
  191. /// the state machine's byte positions always land on char boundaries.)
  192. pub fn strip_c(src: &[u8]) -> Vec<u8> {
  193. let n = src.len();
  194. let mut out = Vec::with_capacity(n);
  195. let mut copied = 0usize; // src[..copied] already emitted
  196. let mut i = 0;
  197. {
  198. let mut blank_to = |out: &mut Vec<u8>, start: usize, end: usize| {
  199. out.extend_from_slice(&src[copied..start]);
  200. emit_blank(out, &src[start..end]);
  201. copied = end;
  202. };
  203. while i < n {
  204. let c = src[i];
  205. let c2 = if i + 1 < n { src[i + 1] } else { 0 };
  206. if c == b'/' && c2 == b'*' {
  207. let start = i;
  208. i += 2;
  209. while i < n && !(src[i] == b'*' && i + 1 < n && src[i + 1] == b'/') {
  210. i += 1;
  211. }
  212. if i < n {
  213. i += 2;
  214. }
  215. blank_to(&mut out, start, i.min(n));
  216. continue;
  217. }
  218. if c == b'/' && c2 == b'/' {
  219. let start = i;
  220. while i < n && src[i] != b'\n' {
  221. i += 1;
  222. }
  223. blank_to(&mut out, start, i);
  224. continue;
  225. }
  226. if c == b'"' || c == b'`' {
  227. let quote = c;
  228. i += 1;
  229. while i < n && src[i] != quote {
  230. if src[i] == b'\\' && i + 1 < n {
  231. i += 2;
  232. continue;
  233. }
  234. if quote != b'`' && src[i] == b'\n' {
  235. break;
  236. }
  237. i += 1;
  238. }
  239. if i < n && src[i] == quote {
  240. i += 1;
  241. }
  242. continue;
  243. }
  244. i += 1;
  245. }
  246. }
  247. out.extend_from_slice(&src[copied..]);
  248. out
  249. }
  250. /// One space per UTF-16 code unit (`\n` preserved): ASCII and 2-3-byte chars
  251. /// are one unit, 4-byte (astral) chars are a surrogate pair — two units.
  252. fn emit_blank(out: &mut Vec<u8>, region: &[u8]) {
  253. let mut i = 0;
  254. while i < region.len() {
  255. let b = region[i];
  256. if b == b'\n' {
  257. out.push(b'\n');
  258. i += 1;
  259. continue;
  260. }
  261. let len = if b < 0x80 {
  262. 1
  263. } else if b < 0xC0 {
  264. 1 // continuation byte at region start — invalid UTF-8; count singly
  265. } else if b < 0xE0 {
  266. 2
  267. } else if b < 0xF0 {
  268. 3
  269. } else {
  270. 4
  271. };
  272. out.push(b' ');
  273. if len == 4 {
  274. out.push(b' ');
  275. }
  276. i += len.min(region.len() - i);
  277. }
  278. }
  279. // ---------- shared regex tails ----------
  280. /// `\(\s*(?:\w+\s+)*\*\s*(\w+)\s*\)\s*\(` matched at `open` (which must hold
  281. /// `(`). Returns (name_range, end_after_second_paren). The `(?:\w+\s+)*`
  282. /// group is greedy without backtracking: giving back an iteration repositions
  283. /// `\*` onto a word char, which can never match, so greedy ≡ backtracked.
  284. fn fnptr_paren_tail(s: &[u8], open: usize) -> Option<((usize, usize), usize)> {
  285. let mut i = skip_jsws(s, open + 1);
  286. loop {
  287. if !is_word_at(s, i) {
  288. break;
  289. }
  290. let we = word_end(s, i);
  291. let wse = skip_jsws(s, we);
  292. if wse == we {
  293. break; // \w+ not followed by \s+ — the iteration fails, word not consumed
  294. }
  295. i = wse;
  296. }
  297. if s.get(i) != Some(&b'*') {
  298. return None;
  299. }
  300. i = skip_jsws(s, i + 1);
  301. if !is_word_at(s, i) {
  302. return None;
  303. }
  304. let name = (i, word_end(s, i));
  305. i = skip_jsws(s, name.1);
  306. if s.get(i) != Some(&b')') {
  307. return None;
  308. }
  309. i = skip_jsws(s, i + 1);
  310. if s.get(i) != Some(&b'(') {
  311. return None;
  312. }
  313. Some((name, i + 1))
  314. }
  315. /// `\s*\)?\s*\(` at `i` → position after the `(`. The optional `)` needs no
  316. /// backtracking: retrying without a consumed `)` lands `\(` on that `)`.
  317. fn close_call_tail(s: &[u8], i: usize) -> Option<usize> {
  318. let mut j = skip_jsws(s, i);
  319. if s.get(j) == Some(&b')') {
  320. j = skip_jsws(s, j + 1);
  321. }
  322. if s.get(j) == Some(&b'(') {
  323. return Some(j + 1);
  324. }
  325. None
  326. }
  327. // ---------- scanners ----------
  328. /// FNPTR_TYPEDEF_RE: /\btypedef\b[^;{}]*?\(\s*(?:\w+\s+)*\*\s*(\w+)\s*\)\s*\(/g
  329. fn scan_fnptr_typedefs(s: &[u8], out: &mut Vec<String>) {
  330. let mut last = 0;
  331. while let Some(t) = find_word(s, b"typedef", last) {
  332. let mut j = t + 7;
  333. let mut matched = None;
  334. // Lazy [^;{}]*?: try the paren tail at each `(` in order; the class
  335. // may also expand ACROSS a failed `(` (it admits parens).
  336. while j < s.len() {
  337. let ch = s[j];
  338. if ch == b';' || ch == b'{' || ch == b'}' {
  339. break;
  340. }
  341. if ch == b'(' {
  342. if let Some((name, end)) = fnptr_paren_tail(s, j) {
  343. matched = Some((name, end));
  344. break;
  345. }
  346. }
  347. j += 1;
  348. }
  349. match matched {
  350. Some(((ns, ne), end)) => {
  351. push_str(out, &s[ns..ne]);
  352. last = end;
  353. }
  354. None => last = t + 1,
  355. }
  356. }
  357. }
  358. /// FNTYPE_TYPEDEF_STMT_RE (/\btypedef\b([^;{}]*);/g) + the TS-side guts
  359. /// checks: skip when guts contains `(*` or `( *`; else the FIRST
  360. /// /\b(\w+)\s*\(/ capture, filtered through C_TYPE_KEYWORDS.
  361. fn scan_fntype_typedefs(s: &[u8], out: &mut Vec<String>) {
  362. let mut last = 0;
  363. while let Some(t) = find_word(s, b"typedef", last) {
  364. let mut j = t + 7;
  365. while j < s.len() && s[j] != b';' && s[j] != b'{' && s[j] != b'}' {
  366. j += 1;
  367. }
  368. if j >= s.len() || s[j] != b';' {
  369. last = t + 1;
  370. continue;
  371. }
  372. let guts = &s[t + 7..j];
  373. if !contains_bytes(guts, b"(*") && !contains_bytes(guts, b"( *") {
  374. // first \b(\w+)\s*\( in guts
  375. let mut p = 0;
  376. while p < guts.len() {
  377. if is_word(guts[p]) && boundary_before(guts, p) {
  378. let we = word_end(guts, p);
  379. let k = skip_jsws(guts, we);
  380. if guts.get(k) == Some(&b'(') {
  381. let w = &guts[p..we];
  382. if !is_type_keyword(w) {
  383. push_str(out, w);
  384. }
  385. break;
  386. }
  387. p = we;
  388. } else {
  389. p += 1;
  390. }
  391. }
  392. }
  393. last = j + 1;
  394. }
  395. }
  396. /// INLINE_STRUCT_RE (/\bstruct\s+(\w+)\s*\{/g), sweep flavor: NO cursor jump
  397. /// (the filter needs a superset of the registration pass's jump-scan), each
  398. /// valid candidate (balanced braces + the `^\s*(\w+)…` var check) contributes
  399. /// its tag and a structural field summary.
  400. struct InlineScan {
  401. ptr: bool,
  402. types: Vec<String>,
  403. tags: Vec<String>,
  404. }
  405. fn scan_inline_structs(s: &[u8]) -> InlineScan {
  406. let mut out = InlineScan { ptr: false, types: Vec::new(), tags: Vec::new() };
  407. let mut last = 0;
  408. while let Some(t) = find_word(s, b"struct", last) {
  409. let after_kw = t + 6;
  410. let ws = skip_jsws(s, after_kw);
  411. if ws == after_kw || !is_word_at(s, ws) {
  412. last = t + 1;
  413. continue;
  414. }
  415. let te = word_end(s, ws);
  416. let open = skip_jsws(s, te);
  417. if s.get(open) != Some(&b'{') {
  418. last = t + 1;
  419. continue;
  420. }
  421. last = open + 1; // lastIndex = end of match (after `{`)
  422. let Some(close) = match_brace(s, open) else { continue };
  423. // vm: /^\s*(\w+)…/ on the text after `}` — only vm[1] matters here.
  424. let v = skip_jsws(s, close + 1);
  425. if !is_word_at(s, v) {
  426. continue;
  427. }
  428. push_str(&mut out.tags, &s[ws..te]);
  429. for f in parse_struct_fields_raw(&s[open + 1..close]) {
  430. if f.name.is_empty() {
  431. continue;
  432. }
  433. if f.ptr {
  434. out.ptr = true;
  435. } else if !f.ty.is_empty() {
  436. out.types.push(f.ty);
  437. }
  438. }
  439. }
  440. out
  441. }
  442. /// matchBrace: index of the `}` matching the `{` at `open`, or None.
  443. fn match_brace(s: &[u8], open: usize) -> Option<usize> {
  444. let mut depth = 0i64;
  445. let mut i = open;
  446. while i < s.len() {
  447. match s[i] {
  448. b'{' => depth += 1,
  449. b'}' => {
  450. depth -= 1;
  451. if depth == 0 {
  452. return Some(i);
  453. }
  454. }
  455. _ => {}
  456. }
  457. i += 1;
  458. }
  459. None
  460. }
  461. /// The `(?:(?:static|const|extern|register|volatile)\s+)*` modifier loop:
  462. /// greedy positions after 0..=k iterations, for the k-descending backtrack the
  463. /// INIT/ARRAY skeletons need. No two alternatives share a prefix, so at most
  464. /// one literal can match at a position; an alternative that matches without
  465. /// trailing `\s+` ends the loop (JS: iteration fails, no other alt can fire).
  466. fn modifier_positions(s: &[u8], start: usize) -> Vec<usize> {
  467. let mut stack = vec![start];
  468. loop {
  469. let cur = *stack.last().unwrap();
  470. let mut advanced = None;
  471. for m in MODIFIERS {
  472. if s.len() >= cur + m.len() && &s[cur..cur + m.len()] == m {
  473. let e = cur + m.len();
  474. let w = skip_jsws(s, e);
  475. if w > e {
  476. advanced = Some(w);
  477. }
  478. break; // exactly one alternative can literal-match here
  479. }
  480. }
  481. match advanced {
  482. Some(w) => stack.push(w),
  483. None => return stack,
  484. }
  485. }
  486. }
  487. /// `\[[^\]]*\]` at `i` (the INIT/ARRAY declarator form — the class admits
  488. /// newlines): position after the FIRST `]`, or None.
  489. fn bracket_span(s: &[u8], i: usize) -> Option<usize> {
  490. if s.get(i) != Some(&b'[') {
  491. return None;
  492. }
  493. let mut j = i + 1;
  494. while j < s.len() && s[j] != b']' {
  495. j += 1;
  496. }
  497. if j < s.len() {
  498. Some(j + 1)
  499. } else {
  500. None
  501. }
  502. }
  503. /// Anchor-skeleton driver shared by INIT_RE and ARRAY_TABLE_RE: both match
  504. /// `(?:^|[;{}])` then a body, and resume from the end of each match. `body`
  505. /// returns (token, match_end) when the body matches at the position after the
  506. /// anchor.
  507. fn scan_anchored<F>(s: &[u8], mut body: F, out: &mut Vec<String>)
  508. where
  509. F: FnMut(&[u8], usize) -> Option<(String, usize)>,
  510. {
  511. let mut last = 0usize;
  512. // The `^` branch consumes nothing and only exists at position 0.
  513. if last == 0 {
  514. if let Some((tok, end)) = body(s, 0) {
  515. out.push(tok);
  516. last = end;
  517. }
  518. }
  519. let mut p = last;
  520. while p < s.len() {
  521. let ch = s[p];
  522. if ch == b';' || ch == b'{' || ch == b'}' {
  523. if let Some((tok, end)) = body(s, p + 1) {
  524. out.push(tok);
  525. p = end;
  526. continue;
  527. }
  528. }
  529. p += 1;
  530. }
  531. }
  532. /// INIT_RE body after the anchor:
  533. /// `\s*(?:MOD\s+)*(?:struct\s+)?(\w+)\s+(\w+)\s*(\[[^\]]*\])?\s*=\s*\{`
  534. /// Backtracks: modifier count (desc), `struct` with/without, bracket
  535. /// with/without — exactly the observable dimensions of the JS engine.
  536. fn init_body(s: &[u8], p: usize) -> Option<(String, usize)> {
  537. let i = skip_jsws(s, p);
  538. let mods = modifier_positions(s, i);
  539. for &pos in mods.iter().rev() {
  540. for with_struct in [true, false] {
  541. let q = if with_struct {
  542. if s.len() >= pos + 6 && &s[pos..pos + 6] == b"struct" {
  543. let e = pos + 6;
  544. let w = skip_jsws(s, e);
  545. if w == e {
  546. continue;
  547. }
  548. w
  549. } else {
  550. continue;
  551. }
  552. } else {
  553. pos
  554. };
  555. if !is_word_at(s, q) {
  556. continue;
  557. }
  558. let te = word_end(s, q);
  559. let w = skip_jsws(s, te);
  560. if w == te {
  561. continue; // \s+ needs ≥1
  562. }
  563. if !is_word_at(s, w) {
  564. continue;
  565. }
  566. let ne = word_end(s, w);
  567. let r = skip_jsws(s, ne);
  568. for with_bracket in [true, false] {
  569. let r2 = if with_bracket {
  570. match bracket_span(s, r) {
  571. Some(e) => e,
  572. None => continue,
  573. }
  574. } else {
  575. r
  576. };
  577. let r3 = skip_jsws(s, r2);
  578. if s.get(r3) != Some(&b'=') {
  579. continue;
  580. }
  581. let r4 = skip_jsws(s, r3 + 1);
  582. if s.get(r4) != Some(&b'{') {
  583. continue;
  584. }
  585. return Some((bytes_to_string(&s[q..te]), r4 + 1));
  586. }
  587. }
  588. }
  589. None
  590. }
  591. /// ARRAY_TABLE_RE body after the anchor:
  592. /// `\s*(?:MOD\s+)*(\w+)\s+(\*\s*)?(\w+)\s*\[[^\]]*\]\s*=\s*\{`
  593. /// Token is `*`-prefixed when the star declarator is present.
  594. fn array_table_body(s: &[u8], p: usize) -> Option<(String, usize)> {
  595. let i = skip_jsws(s, p);
  596. let mods = modifier_positions(s, i);
  597. for &pos in mods.iter().rev() {
  598. if !is_word_at(s, pos) {
  599. continue;
  600. }
  601. let te = word_end(s, pos);
  602. let w = skip_jsws(s, te);
  603. if w == te {
  604. continue;
  605. }
  606. for with_star in [true, false] {
  607. let q = if with_star {
  608. if s.get(w) == Some(&b'*') {
  609. skip_jsws(s, w + 1)
  610. } else {
  611. continue;
  612. }
  613. } else {
  614. w
  615. };
  616. if !is_word_at(s, q) {
  617. continue;
  618. }
  619. let ne = word_end(s, q);
  620. let r = skip_jsws(s, ne);
  621. let Some(r2) = bracket_span(s, r) else { continue };
  622. let r3 = skip_jsws(s, r2);
  623. if s.get(r3) != Some(&b'=') {
  624. continue;
  625. }
  626. let r4 = skip_jsws(s, r3 + 1);
  627. if s.get(r4) != Some(&b'{') {
  628. continue;
  629. }
  630. let mut tok = String::new();
  631. if with_star {
  632. tok.push('*');
  633. }
  634. tok.push_str(&bytes_to_string(&s[pos..te]));
  635. return Some((tok, r4 + 1));
  636. }
  637. }
  638. None
  639. }
  640. /// OBJ_ALIAS_RE over the continuation-joined text:
  641. /// /^[ \t]*#[ \t]*define[ \t]+(\w+)[ \t]+(?:struct[ \t]+)*[A-Za-z_]\w*[ \t\r]*$/gm
  642. fn scan_alias_names(stripped: &[u8], out: &mut Vec<String>) {
  643. // joined = stripped.replace(/\\\r?\n/g, ' ')
  644. let mut joined = Vec::with_capacity(stripped.len());
  645. let mut i = 0;
  646. while i < stripped.len() {
  647. let b = stripped[i];
  648. if b == b'\\' {
  649. if stripped.get(i + 1) == Some(&b'\n') {
  650. joined.push(b' ');
  651. i += 2;
  652. continue;
  653. }
  654. if stripped.get(i + 1) == Some(&b'\r') && stripped.get(i + 2) == Some(&b'\n') {
  655. joined.push(b' ');
  656. i += 3;
  657. continue;
  658. }
  659. }
  660. joined.push(b);
  661. i += 1;
  662. }
  663. for line in joined.split(|&b| b == b'\n') {
  664. if let Some(name) = alias_line(line) {
  665. push_str(out, name);
  666. }
  667. }
  668. }
  669. #[inline]
  670. fn skip_sp_tab(line: &[u8], mut i: usize) -> usize {
  671. while i < line.len() && (line[i] == b' ' || line[i] == b'\t') {
  672. i += 1;
  673. }
  674. i
  675. }
  676. fn alias_line(line: &[u8]) -> Option<&[u8]> {
  677. let mut i = skip_sp_tab(line, 0);
  678. if line.get(i) != Some(&b'#') {
  679. return None;
  680. }
  681. i = skip_sp_tab(line, i + 1);
  682. if line.len() < i + 6 || &line[i..i + 6] != b"define" {
  683. return None;
  684. }
  685. i += 6;
  686. let w = skip_sp_tab(line, i);
  687. if w == i || !is_word_at(line, w) {
  688. return None;
  689. }
  690. let name_end = word_end(line, w);
  691. let name = &line[w..name_end];
  692. let v0 = skip_sp_tab(line, name_end);
  693. if v0 == name_end {
  694. return None; // [ \t]+ before the value
  695. }
  696. // (?:struct[ \t]+)* greedy, k-descending on value failure.
  697. let mut stack = vec![v0];
  698. loop {
  699. let cur = *stack.last().unwrap();
  700. if line.len() >= cur + 6 && &line[cur..cur + 6] == b"struct" {
  701. let e = cur + 6;
  702. let w2 = skip_sp_tab(line, e);
  703. if w2 > e {
  704. stack.push(w2);
  705. continue;
  706. }
  707. }
  708. break;
  709. }
  710. for &vp in stack.iter().rev() {
  711. let Some(&b0) = line.get(vp) else { continue };
  712. if !(b0.is_ascii_alphabetic() || b0 == b'_') {
  713. continue; // value must start [A-Za-z_]
  714. }
  715. let ve = word_end(line, vp);
  716. // [ \t\r]*$
  717. let mut t = ve;
  718. while t < line.len() && (line[t] == b' ' || line[t] == b'\t' || line[t] == b'\r') {
  719. t += 1;
  720. }
  721. if t == line.len() {
  722. return Some(name);
  723. }
  724. }
  725. None
  726. }
  727. /// FIELD_ASSIGN_RE: /(\w+)\s*(?:->|\.)\s*(\w+)\s*=\s*(\w+)\s*(?:->|\.)\s*(\w+)/g
  728. /// Pairs collected as `lfield\0rfield`. Every byte position is a candidate
  729. /// start (JS advances one unit on failure — suffix starts included); matches
  730. /// resume at their end.
  731. fn scan_field_assign(s: &[u8], out: &mut Vec<String>) {
  732. let mut pos = 0usize;
  733. while pos < s.len() {
  734. if !is_word(s[pos]) {
  735. pos += 1;
  736. continue;
  737. }
  738. match field_assign_at(s, pos) {
  739. Some((lf, rf, end)) => {
  740. let mut pair = bytes_to_string(&s[lf.0..lf.1]);
  741. pair.push('\0');
  742. pair.push_str(&bytes_to_string(&s[rf.0..rf.1]));
  743. out.push(pair);
  744. pos = end;
  745. }
  746. None => pos += 1,
  747. }
  748. }
  749. }
  750. #[inline]
  751. fn arrow_at(s: &[u8], i: usize) -> Option<usize> {
  752. if s.get(i) == Some(&b'-') && s.get(i + 1) == Some(&b'>') {
  753. Some(i + 2)
  754. } else if s.get(i) == Some(&b'.') {
  755. Some(i + 1)
  756. } else {
  757. None
  758. }
  759. }
  760. type Range = (usize, usize);
  761. fn field_assign_at(s: &[u8], p: usize) -> Option<(Range, Range, usize)> {
  762. let w1 = word_end(s, p);
  763. let a1 = arrow_at(s, skip_jsws(s, w1))?;
  764. let f1s = skip_jsws(s, a1);
  765. if !is_word_at(s, f1s) {
  766. return None;
  767. }
  768. let f1e = word_end(s, f1s);
  769. let eq = skip_jsws(s, f1e);
  770. if s.get(eq) != Some(&b'=') {
  771. return None;
  772. }
  773. let r1s = skip_jsws(s, eq + 1);
  774. if !is_word_at(s, r1s) {
  775. return None;
  776. }
  777. let r1e = word_end(s, r1s);
  778. let a2 = arrow_at(s, skip_jsws(s, r1e))?;
  779. let f2s = skip_jsws(s, a2);
  780. if !is_word_at(s, f2s) {
  781. return None;
  782. }
  783. let f2e = word_end(s, f2s);
  784. Some(((f1s, f1e), (f2s, f2e), f2e))
  785. }
  786. /// DISPATCH_RE: /((?:\w+(?:\s*\[[^\][]*\])?\s*(?:->|\.)\s*)+)(\w+)\s*\)?\s*\(/g
  787. /// The `+` loop is consumed greedily, then the field tail is tried at each
  788. /// segment count k descending — the JS engine's observable backtracking. The
  789. /// per-segment optional subscript needs no cross-product: the with/without
  790. /// parses diverge at the arrow and at most one can complete a segment.
  791. fn scan_dispatch(s: &[u8], out: &mut Vec<String>) {
  792. let mut pos = 0usize;
  793. while pos < s.len() {
  794. if !is_word(s[pos]) {
  795. pos += 1;
  796. continue;
  797. }
  798. // Greedy segment loop.
  799. let mut seg_ends: Vec<usize> = Vec::new();
  800. let mut cur = pos;
  801. while is_word_at(s, cur) {
  802. let we = word_end(s, cur);
  803. let with_sub = subscript_span(s, skip_jsws(s, we)).and_then(|e| arrow_tail(s, e));
  804. let seg = with_sub.or_else(|| arrow_tail(s, we));
  805. match seg {
  806. Some(e) => {
  807. seg_ends.push(e);
  808. cur = e;
  809. }
  810. None => break,
  811. }
  812. }
  813. let mut matched = None;
  814. for k in (1..=seg_ends.len()).rev() {
  815. let fpos = seg_ends[k - 1];
  816. if !is_word_at(s, fpos) {
  817. continue;
  818. }
  819. let fe = word_end(s, fpos);
  820. if let Some(end) = close_call_tail(s, fe) {
  821. matched = Some(((fpos, fe), end));
  822. break;
  823. }
  824. }
  825. match matched {
  826. Some(((fs_, fe), end)) => {
  827. push_str(out, &s[fs_..fe]);
  828. pos = end;
  829. }
  830. None => pos += 1,
  831. }
  832. }
  833. }
  834. /// `\[[^\][]*\]` at `i` (the DISPATCH subscript form — no nested brackets):
  835. /// position after `]`, or None.
  836. fn subscript_span(s: &[u8], i: usize) -> Option<usize> {
  837. if s.get(i) != Some(&b'[') {
  838. return None;
  839. }
  840. let mut j = i + 1;
  841. while j < s.len() && s[j] != b']' && s[j] != b'[' {
  842. j += 1;
  843. }
  844. if j < s.len() && s[j] == b']' {
  845. Some(j + 1)
  846. } else {
  847. None
  848. }
  849. }
  850. /// `\s*(?:->|\.)\s*` at `i` → position after.
  851. #[inline]
  852. fn arrow_tail(s: &[u8], i: usize) -> Option<usize> {
  853. let a = arrow_at(s, skip_jsws(s, i))?;
  854. Some(skip_jsws(s, a))
  855. }
  856. /// ARRAY_DISPATCH_RE: /(?:\(\s*\*\s*)?\b(\w+)\s*\[[^\][]*\]\s*\)?\s*\(/g
  857. fn scan_array_dispatch(s: &[u8], out: &mut Vec<String>) {
  858. let mut pos = 0usize;
  859. while pos < s.len() {
  860. let b = s[pos];
  861. if b != b'(' && !(is_word(b) && boundary_before(s, pos)) {
  862. pos += 1;
  863. continue;
  864. }
  865. let name_start = if b == b'(' {
  866. let i = skip_jsws(s, pos + 1);
  867. if s.get(i) == Some(&b'*') {
  868. let j = skip_jsws(s, i + 1);
  869. // \b holds: the previous char is `*` or whitespace.
  870. if is_word_at(s, j) { Some(j) } else { None }
  871. } else {
  872. None
  873. }
  874. } else {
  875. Some(pos)
  876. };
  877. let matched = name_start.and_then(|ns| {
  878. let ne = word_end(s, ns);
  879. let sub = subscript_span(s, skip_jsws(s, ne))?;
  880. let end = close_call_tail(s, sub)?;
  881. Some(((ns, ne), end))
  882. });
  883. match matched {
  884. Some(((ns, ne), end)) => {
  885. push_str(out, &s[ns..ne]);
  886. pos = end;
  887. }
  888. None => pos += 1,
  889. }
  890. }
  891. }
  892. /// INCLUDE_RE over RAW text: /#[ \t]*include[ \t]+"([^"\n]+)"/g
  893. fn scan_includes(raw: &[u8], out: &mut Vec<String>) {
  894. let mut pos = 0usize;
  895. while pos < raw.len() {
  896. let Some(h) = find_bytes(raw, b"#", pos) else { break };
  897. let mut i = skip_sp_tab(raw, h + 1);
  898. if raw.len() < i + 7 || &raw[i..i + 7] != b"include" {
  899. pos = h + 1;
  900. continue;
  901. }
  902. i += 7;
  903. let q = skip_sp_tab(raw, i);
  904. if q == i || raw.get(q) != Some(&b'"') {
  905. pos = h + 1;
  906. continue;
  907. }
  908. let mut j = q + 1;
  909. while j < raw.len() && raw[j] != b'"' && raw[j] != b'\n' {
  910. j += 1;
  911. }
  912. if j > q + 1 && j < raw.len() && raw[j] == b'"' {
  913. out.push(bytes_to_string(&raw[q + 1..j]));
  914. pos = j + 1;
  915. } else {
  916. pos = h + 1;
  917. }
  918. }
  919. }
  920. // ---------- struct field parsing ----------
  921. /// splitTopLevel(body, sep): split on `sep` at brace/paren/bracket depth 0.
  922. fn split_top_level(body: &[u8], sep: u8) -> Vec<Range> {
  923. let mut out = Vec::new();
  924. let mut depth = 0i64;
  925. let mut start = 0usize;
  926. for (i, &c) in body.iter().enumerate() {
  927. match c {
  928. b'{' | b'(' | b'[' => depth += 1,
  929. b'}' | b')' | b']' => depth -= 1,
  930. _ if c == sep && depth == 0 => {
  931. out.push((start, i));
  932. start = i + 1;
  933. }
  934. _ => {}
  935. }
  936. }
  937. out.push((start, body.len()));
  938. out
  939. }
  940. /// JS String.prototype.trim over bytes (the JS set == our jsws set).
  941. fn jsws_trim(s: &[u8], mut a: usize, mut b: usize) -> (usize, usize) {
  942. loop {
  943. let l = jsws_len(s, a);
  944. if l == 0 || a + l > b {
  945. break;
  946. }
  947. a += l;
  948. }
  949. // Trailing: walk from the front to find the last non-ws position (ws
  950. // lengths vary, so scan forward tracking the end of the last non-ws char).
  951. let mut i = a;
  952. let mut last_end = a;
  953. while i < b {
  954. let l = jsws_len(s, i);
  955. if l == 0 {
  956. i += 1;
  957. last_end = i;
  958. } else {
  959. i += l;
  960. }
  961. }
  962. b = last_end;
  963. (a, b)
  964. }
  965. /// /(\w+)\s+\**\s*(\w+)\s*$/ — leftmost match whose tail reaches the end.
  966. /// Deterministic per start (greedy words/ws cannot backtrack usefully);
  967. /// candidate starts advance one byte at a time like the JS engine.
  968. fn first_typed(part: &[u8]) -> Option<(Range, Range)> {
  969. let n = part.len();
  970. let mut p = 0usize;
  971. while p < n {
  972. if !is_word(part[p]) {
  973. p += 1;
  974. continue;
  975. }
  976. let te = word_end(part, p);
  977. let w = skip_jsws(part, te);
  978. if w == te {
  979. p += 1;
  980. continue;
  981. }
  982. let mut q = w;
  983. while q < n && part[q] == b'*' {
  984. q += 1;
  985. }
  986. let q = skip_jsws(part, q);
  987. if is_word_at(part, q) {
  988. let ne = word_end(part, q);
  989. let t = skip_jsws(part, ne);
  990. if t == n {
  991. return Some(((p, te), (q, ne)));
  992. }
  993. }
  994. p += 1;
  995. }
  996. None
  997. }
  998. /// FNPTR_DECL_RE (first match): /\(\s*(?:\w+\s+)*\*\s*(\w+)\s*\)\s*\(/
  999. fn fnptr_decl(part: &[u8]) -> Option<Range> {
  1000. let mut i = 0usize;
  1001. while i < part.len() {
  1002. if part[i] == b'(' {
  1003. if let Some((name, _)) = fnptr_paren_tail(part, i) {
  1004. return Some(name);
  1005. }
  1006. }
  1007. i += 1;
  1008. }
  1009. None
  1010. }
  1011. /// Port of `parseStructFieldsRaw` — structure only, classification TS-side.
  1012. pub fn parse_struct_fields_raw(inner: &[u8]) -> Vec<RawField> {
  1013. let mut fields = Vec::new();
  1014. let mut idx: u32 = 0;
  1015. for (ds, de) in split_top_level(inner, b';') {
  1016. let (ds, de) = jsws_trim(inner, ds, de);
  1017. if ds >= de {
  1018. continue;
  1019. }
  1020. let decl = &inner[ds..de];
  1021. let parts = split_top_level(decl, b',');
  1022. let ft = first_typed(&decl[parts[0].0..parts[0].1]);
  1023. let shared_type: &[u8] = match &ft {
  1024. Some(((ts, te), _)) => &decl[parts[0].0 + ts..parts[0].0 + te],
  1025. None => b"",
  1026. };
  1027. for (pi, &(ps, pe)) in parts.iter().enumerate() {
  1028. let (ps2, pe2) = jsws_trim(decl, ps, pe);
  1029. let p = &decl[ps2..pe2];
  1030. let mut name: &[u8] = b"";
  1031. let mut ty: &[u8] = b"";
  1032. let mut ptr = false;
  1033. if let Some((ns, ne)) = fnptr_decl(p) {
  1034. name = &p[ns..ne];
  1035. ptr = true;
  1036. } else if pi == 0 {
  1037. if let Some((_, (ns, ne))) = &ft {
  1038. name = &decl[parts[0].0 + ns..parts[0].0 + ne];
  1039. ty = shared_type;
  1040. }
  1041. } else {
  1042. // /^\**\s*(\w+)/
  1043. let mut q = 0usize;
  1044. while q < p.len() && p[q] == b'*' {
  1045. q += 1;
  1046. }
  1047. let q = skip_jsws(p, q);
  1048. if is_word_at(p, q) {
  1049. name = &p[q..word_end(p, q)];
  1050. ty = shared_type;
  1051. }
  1052. }
  1053. fields.push(RawField {
  1054. name: bytes_to_string(name),
  1055. index: idx,
  1056. ptr,
  1057. ty: bytes_to_string(ty),
  1058. });
  1059. idx += 1;
  1060. }
  1061. }
  1062. fields
  1063. }
  1064. // ---------- per-file entry ----------
  1065. fn push_str(out: &mut Vec<String>, bytes: &[u8]) {
  1066. out.push(bytes_to_string(bytes));
  1067. }
  1068. #[inline]
  1069. fn bytes_to_string(bytes: &[u8]) -> String {
  1070. // All slice boundaries land on ASCII delimiters, so the content is valid
  1071. // UTF-8 whenever the input string was; lossy keeps us total anyway.
  1072. String::from_utf8_lossy(bytes).into_owned()
  1073. }
  1074. fn dedup_in_order(v: Vec<String>) -> Vec<String> {
  1075. let mut seen = std::collections::HashSet::new();
  1076. let mut out = Vec::with_capacity(v.len());
  1077. for x in v {
  1078. if seen.insert(x.clone()) {
  1079. out.push(x);
  1080. }
  1081. }
  1082. out
  1083. }
  1084. /// Line start offsets (byte offset of each line's first byte).
  1085. fn line_starts(s: &[u8]) -> Vec<usize> {
  1086. let mut out = vec![0usize];
  1087. for (i, &b) in s.iter().enumerate() {
  1088. if b == b'\n' {
  1089. out.push(i + 1);
  1090. }
  1091. }
  1092. out
  1093. }
  1094. /// Run the full extraction sweep for one file. `raw` is the file text exactly
  1095. /// as the TS side read it; `structs` are the file's struct-node extents.
  1096. pub fn scan_file(raw: &str, structs: &[StructExtent]) -> FileFacts {
  1097. let raw_b = raw.as_bytes();
  1098. let stripped = strip_c(raw_b);
  1099. let s: &[u8] = &stripped;
  1100. let mut facts = FileFacts {
  1101. fn_ptr_typedefs: Vec::new(),
  1102. fn_type_typedefs: Vec::new(),
  1103. structs: Vec::new(),
  1104. inline_ptr: false,
  1105. inline_types: Vec::new(),
  1106. inline_tags: Vec::new(),
  1107. init_tokens: Vec::new(),
  1108. array_elems: Vec::new(),
  1109. alias_names: Vec::new(),
  1110. d_pairs: Vec::new(),
  1111. dispatch_fields: Vec::new(),
  1112. array_dispatch_names: Vec::new(),
  1113. includes: Vec::new(),
  1114. };
  1115. // Typedefs (gated like the JS sweep — purely a fast path, the scans find
  1116. // nothing without the substring anyway).
  1117. if contains_bytes(s, b"typedef") {
  1118. scan_fnptr_typedefs(s, &mut facts.fn_ptr_typedefs);
  1119. scan_fntype_typedefs(s, &mut facts.fn_type_typedefs);
  1120. }
  1121. // Struct-node field declarations.
  1122. if !structs.is_empty() {
  1123. let lines = line_starts(s);
  1124. for st in structs {
  1125. let mut sf = StructFields { id: st.id.clone(), parsed: false, fields: Vec::new() };
  1126. // sliceLinesPre: falsy startLine → '' (never parses). end_line
  1127. // arrives with the TS side's `?? startLine` already applied; a
  1128. // slice whose end ≤ start is empty, exactly like Array.slice.
  1129. if st.start_line >= 1 {
  1130. let a = (st.start_line - 1) as usize;
  1131. let b = st.end_line as usize;
  1132. if a < lines.len() && b > a {
  1133. let body_start = lines[a];
  1134. // End of line (b-1): next line start minus the `\n`, or EOF.
  1135. let body_end = if b < lines.len() { lines[b] - 1 } else { s.len() };
  1136. let body = &s[body_start..body_end.max(body_start)];
  1137. if let Some(open) = body.iter().position(|&c| c == b'{') {
  1138. if let Some(close) = match_brace(body, open) {
  1139. sf.parsed = true;
  1140. sf.fields = parse_struct_fields_raw(&body[open + 1..close]);
  1141. }
  1142. }
  1143. }
  1144. }
  1145. facts.structs.push(sf);
  1146. }
  1147. }
  1148. // Registration filters.
  1149. if contains_bytes(s, b"{") {
  1150. let inline = scan_inline_structs(s);
  1151. facts.inline_ptr = inline.ptr;
  1152. facts.inline_types = dedup_in_order(inline.types);
  1153. facts.inline_tags = dedup_in_order(inline.tags);
  1154. if contains_bytes(s, b"=") {
  1155. scan_anchored(s, init_body, &mut facts.init_tokens);
  1156. facts.init_tokens = dedup_in_order(std::mem::take(&mut facts.init_tokens));
  1157. scan_anchored(s, array_table_body, &mut facts.array_elems);
  1158. facts.array_elems = dedup_in_order(std::mem::take(&mut facts.array_elems));
  1159. }
  1160. }
  1161. // Alias-shaped object macros.
  1162. if contains_bytes(s, b"#define") || contains_bytes(s, b"# define") {
  1163. scan_alias_names(s, &mut facts.alias_names);
  1164. facts.alias_names = dedup_in_order(std::mem::take(&mut facts.alias_names));
  1165. }
  1166. // Propagation + dispatch filters.
  1167. if contains_bytes(s, b"=") {
  1168. scan_field_assign(s, &mut facts.d_pairs);
  1169. facts.d_pairs = dedup_in_order(std::mem::take(&mut facts.d_pairs));
  1170. }
  1171. scan_dispatch(s, &mut facts.dispatch_fields);
  1172. facts.dispatch_fields = dedup_in_order(std::mem::take(&mut facts.dispatch_fields));
  1173. scan_array_dispatch(s, &mut facts.array_dispatch_names);
  1174. facts.array_dispatch_names = dedup_in_order(std::mem::take(&mut facts.array_dispatch_names));
  1175. // Includes come from the RAW text (string contents survive there).
  1176. if contains_bytes(raw_b, b"include") {
  1177. scan_includes(raw_b, &mut facts.includes);
  1178. }
  1179. facts
  1180. }
  1181. #[cfg(test)]
  1182. mod tests {
  1183. use super::*;
  1184. fn facts(src: &str) -> FileFacts {
  1185. scan_file(src, &[])
  1186. }
  1187. #[test]
  1188. fn strip_blanks_comments_keeps_strings() {
  1189. let s = strip_c(b"a /* x\ny */ b // c\nd \"in//str\" e");
  1190. assert_eq!(&s, b"a \n b \nd \"in//str\" e".as_slice());
  1191. }
  1192. #[test]
  1193. fn typedef_forms() {
  1194. let f = facts("typedef void (*hook_fn)(int);\ntypedef void redisCommandProc(int c);\n");
  1195. assert_eq!(f.fn_ptr_typedefs, vec!["hook_fn"]);
  1196. assert_eq!(f.fn_type_typedefs, vec!["redisCommandProc"]);
  1197. }
  1198. #[test]
  1199. fn init_modifier_backtrack() {
  1200. // `static x = {` must match with type token `static` (the JS engine
  1201. // backtracks the modifier loop) — harmless downstream, but collected.
  1202. let f = facts("; static x = {1};\n; static struct cmd t[] = { {0} };");
  1203. assert!(f.init_tokens.contains(&"static".to_string()));
  1204. assert!(f.init_tokens.contains(&"cmd".to_string()));
  1205. }
  1206. #[test]
  1207. fn dispatch_backtracks_segments() {
  1208. let f = facts("int go(struct c *x){ x->cmd->proc(1); tbl[i](2); (*ops[k])(3); }");
  1209. assert!(f.dispatch_fields.contains(&"proc".to_string()));
  1210. assert!(f.array_dispatch_names.contains(&"tbl".to_string()));
  1211. assert!(f.array_dispatch_names.contains(&"ops".to_string()));
  1212. }
  1213. #[test]
  1214. fn field_assign_pairs() {
  1215. let f = facts("void g(void){ a->f = b->g; h.x = k.y; m == n; }");
  1216. assert!(f.d_pairs.contains(&"f\0g".to_string()));
  1217. assert!(f.d_pairs.contains(&"x\0y".to_string()));
  1218. assert_eq!(f.d_pairs.len(), 2);
  1219. }
  1220. #[test]
  1221. fn alias_shapes() {
  1222. let f = facts("#define A redisCommand\n#define B struct foo\n#define C 0x12\n#define D(x) x\n");
  1223. assert!(f.alias_names.contains(&"A".to_string()));
  1224. assert!(f.alias_names.contains(&"B".to_string()));
  1225. assert!(!f.alias_names.contains(&"C".to_string()));
  1226. assert!(!f.alias_names.contains(&"D".to_string()));
  1227. }
  1228. #[test]
  1229. fn includes_from_raw() {
  1230. let f = facts("#include \"commands.def\"\n// #include \"in-comment.h\"\n");
  1231. // Raw-text scan: the commented include IS captured (parity with the
  1232. // JS INCLUDE_RE over raw text).
  1233. assert_eq!(f.includes, vec!["commands.def", "in-comment.h"]);
  1234. }
  1235. }