Skip to content

Commit 2dc6b28

Browse files
committed
init
1 parent ffa0dbd commit 2dc6b28

33 files changed

+2514
-0
lines changed

.github/workflows/ci.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
test:
9+
runs-on: windows-latest
10+
steps:
11+
- name: Checkout
12+
uses: actions/checkout@v4
13+
with:
14+
submodules: recursive
15+
- name: Setup Nim
16+
uses: nim-lang/setup-nim-action@v2
17+
with:
18+
nim-version: stable
19+
- name: Run tests
20+
working-directory: nimAutoWrapper
21+
run: nimble test_all

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
nimcache/
2+
**/nimcache_*/
3+
testCRepos/builds/
4+
*.exe
5+
*.dll
6+
*.obj
7+
*.pdb
8+
nimble.develop
9+
nimble.paths
10+
nimbledeps

README.md

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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`

_tmp_refs/c2nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit 835ba0e49242ce086d8cb7a8d943d92f179de352

config.nims

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# begin Nimble config (version 2)
2+
when withDir(thisDir(), system.fileExists("nimble.paths")):
3+
include "nimble.paths"
4+
# end Nimble config

nimAutoWrapper.nim

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import os
2+
import strutils
3+
import src/debugger
4+
import src/types
5+
import src/tokenizer
6+
import src/utils
7+
import src/parser_core
8+
import src/default_parsers
9+
10+
proc defaultConfig*(): WrapperConfig =
11+
## returns the default wrapper config
12+
## Enables template and comment emission by default.
13+
var
14+
cfg: WrapperConfig = WrapperConfig(emitTemplates: true, emitComments: true)
15+
result = cfg
16+
17+
proc wrapTextState*(a: string, b: WrapperConfig): ParserState =
18+
## a: input C source text
19+
## b: wrapper config
20+
## Tokenizes and parses input C text into a parser state.
21+
## Example: `wrapTextState("int foo(void);", defaultConfig())` yields output lines.
22+
var
23+
tokens: seq[Token] = tokenizeC(a)
24+
state: ParserState = initState(tokens, b)
25+
registry: ParserRegistry = buildDefaultRegistry()
26+
parseAll(state, registry)
27+
result = state
28+
29+
proc wrapText*(a: string, b: WrapperConfig): seq[string] =
30+
## a: input C source text
31+
## b: wrapper config
32+
## Tokenizes and parses input C text into Nim output lines.
33+
## Example: `wrapText("int foo(void);", defaultConfig())` yields a proc line.
34+
var
35+
state: ParserState = wrapTextState(a, b)
36+
result = state.output
37+
38+
proc wrapFile*(a: string, b: string, c: WrapperConfig) =
39+
## a: input file path
40+
## b: output file path
41+
## c: wrapper config
42+
## Reads a C header file and writes a Nim wrapper file.
43+
var
44+
inputText: string = readFile(a)
45+
state: ParserState = wrapTextState(inputText, c)
46+
outputText: string = state.output.join("\n")
47+
writeFile(b, outputText)
48+
writeDebugJson(b, state.debugEntries)
49+
50+
proc main*() =
51+
## entry point for CLI usage
52+
## Parses CLI args and runs a file-to-file wrapper pass.
53+
## Example: `nimAutoWrapper input.h output.nim`.
54+
var
55+
args: seq[string] = commandLineParams()
56+
inputPath: string = ""
57+
outputPath: string = ""
58+
config: WrapperConfig
59+
if args.len < 2:
60+
echo "usage: nimAutoWrapper <input.h> <output.nim>"
61+
return
62+
inputPath = args[0]
63+
outputPath = args[1]
64+
config = defaultConfig()
65+
wrapFile(inputPath, outputPath, config)
66+
echo "wrote: " & outputPath
67+
68+
when isMainModule:
69+
main()

nimAutoWrapper.nimble

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
version = "0.1.0"
2+
author = "n1ght"
3+
description = "Modular C header wrapper generator for Nim."
4+
license = "UNLICENSED"
5+
6+
task build_aes, "Generate wrapper for tiny-AES-c":
7+
exec "nim r tools/ensure_env.nim -- --submodules --builddirs"
8+
exec "nim c -r nimAutoWrapper.nim testCRepos/repos/tiny-AES-c/aes.h testCRepos/builds/tiny-AES-c/aes_wrapper.nim"
9+
10+
task build_blake2, "Generate wrapper for BLAKE2 reference code":
11+
exec "nim r tools/ensure_env.nim -- --submodules --builddirs"
12+
exec "nim c -r nimAutoWrapper.nim testCRepos/repos/BLAKE2/ref/blake2.h testCRepos/builds/BLAKE2/blake2_wrapper.nim"
13+
14+
task build_repos, "Generate wrappers for all test repos":
15+
exec "nim r tools/ensure_env.nim -- --submodules --builddirs"
16+
exec "nim c -r nimAutoWrapper.nim testCRepos/repos/tiny-AES-c/aes.h testCRepos/builds/tiny-AES-c/aes_wrapper.nim"
17+
exec "nim c -r nimAutoWrapper.nim testCRepos/repos/BLAKE2/ref/blake2.h testCRepos/builds/BLAKE2/blake2_wrapper.nim"
18+
19+
task setup, "Fetch submodules for test repos":
20+
exec "nim r tools/ensure_env.nim -- --submodules"
21+
22+
task start, "Fetch submodules and build test wrappers":
23+
exec "nim r tools/ensure_env.nim -- --submodules --builddirs"
24+
exec "nimble build_repos"
25+
26+
task test_functionality, "Run tokenizer and utils tests":
27+
exec "nim r tools/ensure_env.nim -- --submodules --builddirs"
28+
exec "nim c -r --path:. tests/functionality/test_tokenizer.nim"
29+
exec "nim c -r --path:. tests/functionality/test_utils.nim"
30+
31+
task test_realworld, "Run real-world wrapper tests":
32+
exec "nim r tools/ensure_env.nim -- --submodules --builddirs"
33+
exec "nimble build_repos"
34+
exec "nim c -r tests/realworld/tiny_aes_c_runner.nim"
35+
exec "nim c -r tests/realworld/blake2_ref_runner.nim"
36+
37+
task test_all, "Run all tests":
38+
exec "nim r tools/ensure_env.nim -- --submodules --builddirs"
39+
exec "nimble build_repos"
40+
exec "nim c -r --path:. tests/functionality/test_tokenizer.nim"
41+
exec "nim c -r --path:. tests/functionality/test_utils.nim"
42+
exec "nim c -r tests/realworld/tiny_aes_c_runner.nim"
43+
exec "nim c -r tests/realworld/blake2_ref_runner.nim"

0 commit comments

Comments
 (0)