|
| 1 | +# nimAutoWrapper |
| 2 | + |
| 3 | +Modular C header wrapper generator for Nim, with small real-world validation harnesses. |
| 4 | + |
| 5 | +This README explains how the parser works, module order, naming rules, and debug output. |
| 6 | + |
| 7 | +------------------------------------------------------------------------------- |
| 8 | +Quick start |
| 9 | +------------------------------------------------------------------------------- |
| 10 | + |
| 11 | +```sh |
| 12 | +nim c -r nimAutoWrapper.nim <input.h> <output.nim> |
| 13 | +``` |
| 14 | + |
| 15 | +This writes: |
| 16 | +- `output.nim` (bindings) |
| 17 | +- `output.debug.json` (debug markers and collisions) |
| 18 | + |
| 19 | +------------------------------------------------------------------------------- |
| 20 | +High-level pipeline |
| 21 | +------------------------------------------------------------------------------- |
| 22 | + |
| 23 | +``` |
| 24 | + C header text |
| 25 | + | |
| 26 | + v |
| 27 | + tokenizer |
| 28 | + | |
| 29 | + v |
| 30 | + parser registry (ordered) |
| 31 | + | |
| 32 | + v |
| 33 | + output lines + debug log |
| 34 | + | |
| 35 | + v |
| 36 | + output.nim + output.debug.json |
| 37 | +``` |
| 38 | + |
| 39 | +------------------------------------------------------------------------------- |
| 40 | +Module map (what each file does) |
| 41 | +------------------------------------------------------------------------------- |
| 42 | + |
| 43 | +Core: |
| 44 | +- `nimAutoWrapper.nim`: CLI + file I/O; writes debug JSON. |
| 45 | +- `src/tokenizer.nim`: turns C text into flat tokens with line/col. |
| 46 | +- `src/parser_core.nim`: runs parsers in order; logs unparsed tokens. |
| 47 | +- `src/default_parsers.nim`: parser registry order. |
| 48 | +- `src/types.nim`: token types, parser state, debug entry type. |
| 49 | +- `src/utils.nim`: token helpers and output helpers. |
| 50 | + |
| 51 | +Parsers (each handles one C shape): |
| 52 | +- `src/preprocessor_parser.nim`: consumes non-define/include directives. |
| 53 | +- `src/extern_parser.nim`: consumes `extern "C" { ... }` blocks. |
| 54 | +- `src/define_parser.nim`: `#define` to `const` or `template`. |
| 55 | +- `src/static_const_parser.nim`: `static const` variables with simple init. |
| 56 | +- `src/enum_parser.nim`: `enum` to Nim enum. |
| 57 | +- `src/macro_struct_parser.nim`: `MACRO(struct ...)` wrappers. |
| 58 | +- `src/struct_parser.nim`: `struct` to Nim object. |
| 59 | +- `src/typedef_parser.nim`: `typedef` to Nim alias. |
| 60 | +- `src/function_parser.nim`: function prototypes to `proc`. |
| 61 | + |
| 62 | +Naming + debug helpers: |
| 63 | +- `src/name_mangle.nim`: sanitize identifiers, importc pragmas. |
| 64 | +- `src/name_registry.nim`: collision resolution + tracking. |
| 65 | +- `src/debugger.nim`: debug entry collection + JSON writer. |
| 66 | +- `src/cast_utils.nim`: strips leading C casts like `((long)0)` in init/defines. |
| 67 | + |
| 68 | +------------------------------------------------------------------------------- |
| 69 | +Parser order (exact) |
| 70 | +------------------------------------------------------------------------------- |
| 71 | + |
| 72 | +The registry order is important. The default order is: |
| 73 | + |
| 74 | +``` |
| 75 | +1) preprocessor_parser (non-define/include directives) |
| 76 | +2) extern_parser (extern "C" { ... } blocks) |
| 77 | +3) define_parser (#define -> const/template) |
| 78 | +4) include_parser (#include -> comment) |
| 79 | +5) static_const_parser (static const vars) |
| 80 | +6) enum_parser (enum -> Nim enum) |
| 81 | +7) macro_struct_parser (MACRO(struct ...)) |
| 82 | +8) struct_parser (struct -> object) |
| 83 | +9) typedef_parser (typedef -> distinct pointer) |
| 84 | +10) function_parser (prototype -> proc) |
| 85 | +``` |
| 86 | + |
| 87 | +If no parser matches, `parser_core` consumes one token and logs it as `unparsed`. |
| 88 | + |
| 89 | +------------------------------------------------------------------------------- |
| 90 | +Name mangling and collisions |
| 91 | +------------------------------------------------------------------------------- |
| 92 | + |
| 93 | +We sanitize every emitted name, then reserve a unique Nim identifier. |
| 94 | + |
| 95 | +Sanitization rules (in `src/name_mangle.nim`): |
| 96 | +- Strip leading and trailing underscores: `_my_func_` -> `my_func` |
| 97 | +- If invalid or Nim keyword, prefix with `c_`: `type` -> `c_type` |
| 98 | +- Parameters use `p_` prefix or `p{index}` fallback |
| 99 | + |
| 100 | +Collision rules (in `src/name_registry.nim`): |
| 101 | +1) Try base name (sanitized) |
| 102 | +2) If taken, try kind-specific suffix: |
| 103 | + - `struct` -> `_str` (ex: `foo_str`) |
| 104 | + - `typedef` -> `_tyd` (ex: `foo_tyd`) |
| 105 | +3) If still taken, append numeric suffixes (`_1`, `_2`, ...) |
| 106 | + |
| 107 | +All renamed symbols preserve the original C name via `importc`. |
| 108 | + |
| 109 | +Example: |
| 110 | +``` |
| 111 | +// C |
| 112 | +struct blake2s_param__ { ... }; |
| 113 | +typedef struct blake2s_param__ blake2s_param; |
| 114 | +
|
| 115 | +// Nim (collision resolution) |
| 116 | +type |
| 117 | + blake2s_param_str* {.importc: "blake2s_param__".} = object ... |
| 118 | + blake2s_param_tyd* {.importc: "blake2s_param".} = distinct pointer |
| 119 | +``` |
| 120 | + |
| 121 | +------------------------------------------------------------------------------- |
| 122 | +Debug output (output.debug.json) |
| 123 | +------------------------------------------------------------------------------- |
| 124 | + |
| 125 | +Every wrapper run writes a debug JSON file next to the output. |
| 126 | + |
| 127 | +What is logged: |
| 128 | +- `unparsed`: a token was not handled by any parser |
| 129 | +- `skipped`: a directive was intentionally consumed |
| 130 | + - `preprocessor`: #if/#endif/#pragma/etc (non-define/include) |
| 131 | + - `extern_block`: `extern "C" {` |
| 132 | + - `extern_block_end`: closing brace for the extern block |
| 133 | +- `collision`: a name was renamed to avoid duplicate Nim symbols |
| 134 | +- `static_const_*`: static const values we skipped (missing/complex init) |
| 135 | + |
| 136 | +Example entry: |
| 137 | +```json |
| 138 | +{ |
| 139 | + "kind": "collision", |
| 140 | + "reason": "name_collision", |
| 141 | + "line": 0, |
| 142 | + "col": 0, |
| 143 | + "text": "blake2s_param", |
| 144 | + "context": "blake2s_param -> blake2s_param_tyd" |
| 145 | +} |
| 146 | +``` |
| 147 | + |
| 148 | +Tip: if you want to audit parser coverage, search for `unparsed` entries. |
| 149 | + |
| 150 | +------------------------------------------------------------------------------- |
| 151 | +Examples (what gets generated) |
| 152 | +------------------------------------------------------------------------------- |
| 153 | + |
| 154 | +Function prototype: |
| 155 | +``` |
| 156 | +// C |
| 157 | +int foo(const void* in, size_t n); |
| 158 | +
|
| 159 | +// Nim |
| 160 | +proc foo*(p_in: pointer, n: csize_t): cint {.importc.} |
| 161 | +``` |
| 162 | + |
| 163 | +Define -> const: |
| 164 | +``` |
| 165 | +// C |
| 166 | +#define AES_BLOCKLEN 16 |
| 167 | +
|
| 168 | +// Nim |
| 169 | +const AES_BLOCKLEN* = 16 |
| 170 | +``` |
| 171 | + |
| 172 | +Define -> template: |
| 173 | +``` |
| 174 | +// C |
| 175 | +#define MAX(a, b) ((a) > (b) ? (a) : (b)) |
| 176 | +
|
| 177 | +// Nim |
| 178 | +template MAX*(a: untyped, b: untyped): untyped = |
| 179 | + ## C macro: ((a) > (b) ? (a) : (b)) |
| 180 | + discard |
| 181 | +``` |
| 182 | + |
| 183 | +Static const: |
| 184 | +``` |
| 185 | +// C |
| 186 | +static const WGPUTextureUsage WGPUTextureUsage_None = 0x0; |
| 187 | +
|
| 188 | +// Nim |
| 189 | +const WGPUTextureUsage_None* = 0x0 |
| 190 | +``` |
| 191 | + |
| 192 | +Macro-wrapped struct: |
| 193 | +``` |
| 194 | +// C |
| 195 | +BLAKE2_PACKED(struct blake2s_param__ { ... }); |
| 196 | +
|
| 197 | +// Nim |
| 198 | +type blake2s_param_str* {.importc: "blake2s_param__".} = object |
| 199 | + ... |
| 200 | +``` |
| 201 | + |
| 202 | +Extern "C": |
| 203 | +``` |
| 204 | +// C |
| 205 | +#if defined(__cplusplus) |
| 206 | +extern "C" { |
| 207 | +#endif |
| 208 | +... |
| 209 | +#if defined(__cplusplus) |
| 210 | +} |
| 211 | +#endif |
| 212 | +``` |
| 213 | +The extern block is skipped and logged in `output.debug.json`. |
| 214 | + |
| 215 | +------------------------------------------------------------------------------- |
| 216 | +Layout |
| 217 | +------------------------------------------------------------------------------- |
| 218 | + |
| 219 | +- `nimAutoWrapper.nim`: CLI entry point. |
| 220 | +- `src/`: tokenizer, parser registry, parser modules. |
| 221 | +- `tests/functionality/`: unit tests for tokenizer and helpers. |
| 222 | +- `tests/realworld/`: wrapper + validation runners for real C libraries. |
| 223 | +- `testCRepos/repos/`: C repos (submodules). |
| 224 | +- `testCRepos/builds/`: generated wrappers and build artifacts. |
| 225 | + |
| 226 | +------------------------------------------------------------------------------- |
| 227 | +Nimble tasks |
| 228 | +------------------------------------------------------------------------------- |
| 229 | + |
| 230 | +- `nimble build_repos` |
| 231 | +- `nimble test_functionality` |
| 232 | +- `nimble test_realworld` |
| 233 | +- `nimble test_all` |
| 234 | +- `nimble setup` |
| 235 | +- `nimble start` |
0 commit comments