mojo-bindgen 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. mojo_bindgen-0.1.0/.gitignore +15 -0
  2. mojo_bindgen-0.1.0/CHANGELOG.md +18 -0
  3. mojo_bindgen-0.1.0/LICENSE +21 -0
  4. mojo_bindgen-0.1.0/PKG-INFO +189 -0
  5. mojo_bindgen-0.1.0/README.md +154 -0
  6. mojo_bindgen-0.1.0/mojo_bindgen/__init__.py +20 -0
  7. mojo_bindgen-0.1.0/mojo_bindgen/cli.py +138 -0
  8. mojo_bindgen-0.1.0/mojo_bindgen/codegen/__init__.py +15 -0
  9. mojo_bindgen-0.1.0/mojo_bindgen/codegen/_struct_order.py +108 -0
  10. mojo_bindgen-0.1.0/mojo_bindgen/codegen/generator.py +48 -0
  11. mojo_bindgen-0.1.0/mojo_bindgen/codegen/mojo_emit_options.py +40 -0
  12. mojo_bindgen-0.1.0/mojo_bindgen/codegen/mojo_mapper.py +635 -0
  13. mojo_bindgen-0.1.0/mojo_bindgen/codegen/render.py +628 -0
  14. mojo_bindgen-0.1.0/mojo_bindgen/ir.py +750 -0
  15. mojo_bindgen-0.1.0/mojo_bindgen/parsing/__init__.py +9 -0
  16. mojo_bindgen-0.1.0/mojo_bindgen/parsing/diagnostics.py +65 -0
  17. mojo_bindgen-0.1.0/mojo_bindgen/parsing/frontend.py +233 -0
  18. mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/__init__.py +34 -0
  19. mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/const_expr.py +494 -0
  20. mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/decl_lowering.py +200 -0
  21. mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/literal_resolver.py +126 -0
  22. mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/macro_env.py +92 -0
  23. mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/primitive.py +118 -0
  24. mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/record_lowering.py +325 -0
  25. mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/type_lowering.py +301 -0
  26. mojo_bindgen-0.1.0/mojo_bindgen/parsing/parser.py +183 -0
  27. mojo_bindgen-0.1.0/mojo_bindgen/parsing/registry.py +259 -0
  28. mojo_bindgen-0.1.0/mojo_bindgen/passes/__init__.py +52 -0
  29. mojo_bindgen-0.1.0/mojo_bindgen/passes/analyze_for_mojo.py +957 -0
  30. mojo_bindgen-0.1.0/mojo_bindgen/passes/pipeline.py +14 -0
  31. mojo_bindgen-0.1.0/mojo_bindgen/passes/reachability.py +217 -0
  32. mojo_bindgen-0.1.0/mojo_bindgen/passes/validate_ir.py +99 -0
  33. mojo_bindgen-0.1.0/mojo_bindgen/py.typed +0 -0
  34. mojo_bindgen-0.1.0/mojo_bindgen/serde.py +213 -0
  35. mojo_bindgen-0.1.0/mojo_bindgen/utils.py +38 -0
  36. mojo_bindgen-0.1.0/pyproject.toml +86 -0
@@ -0,0 +1,15 @@
1
+ # pixi environments
2
+ .pixi/*
3
+ !.pixi/config.toml
4
+ # Python packaging
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ *.pyc
9
+ *.ipynb
10
+ .agents/*
11
+ plans/*
12
+ skills-lock.json
13
+ tests/test.json
14
+ examples/cairo/cairo_smoke_2
15
+ todo.md
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-04-19
11
+
12
+ ### Added
13
+
14
+ - Initial published packaging metadata, MIT license, and PEP 561 `py.typed` marker.
15
+ - Development tooling: Ruff, Pyright (scoped; see `pyproject.toml`), and CI workflow.
16
+
17
+ [Unreleased]: https://github.com/MoSafi2/mojo_bindgen/compare/v0.1.0...HEAD
18
+ [0.1.0]: https://github.com/MoSafi2/mojo_bindgen/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mohamed Mabrouk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,189 @@
1
+ Metadata-Version: 2.4
2
+ Name: mojo-bindgen
3
+ Version: 0.1.0
4
+ Summary: Generate Mojo FFI bindings from C headers using libclang
5
+ Project-URL: Homepage, https://github.com/MoSafi2/mojo_bindgen
6
+ Project-URL: Repository, https://github.com/MoSafi2/mojo_bindgen
7
+ Project-URL: Issues, https://github.com/MoSafi2/mojo_bindgen/issues
8
+ Project-URL: Documentation, https://github.com/MoSafi2/mojo_bindgen/blob/main/README.md
9
+ Project-URL: Changelog, https://github.com/MoSafi2/mojo_bindgen/blob/main/CHANGELOG.md
10
+ Author-email: Mohamed Mabrouk <mohamed.mabrouk@rwth-aachen.de>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: bindings,c,codegen,ffi,libclang,mojo
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: C
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Software Development :: Code Generators
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.14
26
+ Requires-Dist: libclang<19,>=18.1.1
27
+ Requires-Dist: rich
28
+ Requires-Dist: typer
29
+ Provides-Extra: dev
30
+ Requires-Dist: build>=1.4; extra == 'dev'
31
+ Requires-Dist: pyright>=1.1.400; extra == 'dev'
32
+ Requires-Dist: pytest>=9; extra == 'dev'
33
+ Requires-Dist: ruff>=0.8; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ [![CI](https://github.com/MoSafi2/mojo_bindgen/actions/workflows/ci.yml/badge.svg)](https://github.com/MoSafi2/mojo_bindgen/actions/workflows/ci.yml)
37
+
38
+ # mojo-bindgen
39
+
40
+ Generate **Mojo FFI** bindings from **C headers** using [libclang](https://pypi.org/project/libclang/) (Python bindings). The tool parses a header, builds an internal IR, and emits a `.mojo` module with `external_call` or `owned_dl_handle` linking.
41
+
42
+ **Requires Python 3.14+** (aligned with current Modular/Mojo Pixi stacks).
43
+
44
+ For maintainer-facing architecture notes, see [docs/codegen-architecture.md](docs/codegen-architecture.md). For contributing, security contact, and release notes, see [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), and [CHANGELOG.md](CHANGELOG.md).
45
+
46
+ ## Setup
47
+
48
+ ### 1) System dependencies
49
+
50
+ Install `clang` and `libclang` first (required for header parsing):
51
+
52
+ ```bash
53
+ # Ubuntu / Debian
54
+ sudo apt update
55
+ sudo apt install -y clang libclang1
56
+
57
+ # Fedora
58
+ sudo dnf install -y clang llvm-libs
59
+
60
+ # macOS (Homebrew)
61
+ brew install llvm
62
+ ```
63
+
64
+ If `libclang` is installed in a non-standard location, set `LIBCLANG_PATH` to the directory containing `libclang.so` / `libclang.dylib`.
65
+
66
+ ### 2) Install project dependencies
67
+
68
+ From the repository root (requires [Pixi](https://pixi.sh)):
69
+
70
+ ```bash
71
+ pixi install
72
+ pixi shell
73
+ ```
74
+
75
+ The Pixi workspace is **linux-64 only** at the moment; on macOS or Windows use a virtual environment and `pip install -e ".[dev]"` instead (see [CONTRIBUTING.md](CONTRIBUTING.md)).
76
+
77
+ This installs the `mojo-bindgen` package in editable mode and puts the `mojo-bindgen` CLI on your `PATH`.
78
+
79
+ You also need a **system libclang** shared library compatible with the `libclang` Python wheel.
80
+
81
+ ### Library usage (Python API)
82
+
83
+ The CLI wraps the same pipeline you can call from Python: parse a header to IR, then generate Mojo source.
84
+
85
+ ```python
86
+ from pathlib import Path
87
+
88
+ from mojo_bindgen.codegen import MojoEmitOptions, generate_mojo
89
+ from mojo_bindgen.parsing.parser import ClangParser
90
+
91
+ header = Path("include/mylib.h")
92
+ unit = ClangParser(
93
+ header,
94
+ library="mylib",
95
+ link_name="mylib",
96
+ compile_args=["-I", "include"],
97
+ ).run()
98
+ mojo_src = generate_mojo(unit, MojoEmitOptions(linking="external_call"))
99
+ ```
100
+
101
+ Stable exports from the `mojo_bindgen` package root are `MojoGenerator`, `MojoEmitOptions`, `generate_mojo`, and `__version__`. Parsing types (`ClangParser`, `ParseError`, `Unit`) live in their modules and are treated as public for programmatic use; follow semver for the workflow above when upgrading.
102
+
103
+ ## CLI
104
+
105
+ Inspect all options and examples:
106
+
107
+ ```bash
108
+ mojo-bindgen --help
109
+ ```
110
+
111
+ Emit Mojo FFI (default):
112
+
113
+ ```bash
114
+ mojo-bindgen path/to/header.h -o bindings.mojo
115
+ ```
116
+
117
+ Dump IR as JSON:
118
+
119
+ ```bash
120
+ mojo-bindgen path/to/header.h --json -o unit.json
121
+ ```
122
+
123
+ Extra clang flags (include paths, sysroot, target):
124
+
125
+ ```bash
126
+ mojo-bindgen include/me.h --compile-arg=-I./include --compile-arg=--sysroot=/path/to/sysroot -o out.mojo
127
+ ```
128
+
129
+ Set a specific C language standard:
130
+
131
+ ```bash
132
+ mojo-bindgen include/me.h --compile-arg=-std=c99 -o out.mojo
133
+ ```
134
+
135
+ `--compile-arg` standard flags accept `-std=...`, `--std=...`, and `std=...` forms. If no standard is provided, `-std=gnu11` is used by default.
136
+
137
+ By default, `--library` and `--link-name` are the header file stem (e.g. `me` for `me.h`).
138
+ `--compile-arg` is repeatable.
139
+ Use `mojo-bindgen --help` for full option details.
140
+
141
+ ## Features
142
+
143
+ - **libclang parsing:** Walks a primary C header with configurable compile flags (default language is `gnu11` if you do not pass `-std=...`). Repeatable `--compile-arg` passes include paths, sysroot, target triple, and standard selection.
144
+ - **IR and tooling:** Builds a structured IR, runs validation and a reachability materialization pass, and can **dump JSON** (`--json`) for debugging or downstream tools.
145
+ - **Generated Mojo surface:** Emits structs (with `@align` from C alignment when expressible in Mojo), unions (including `@unsafe_union` when layout analysis marks them eligible), enums, typedefs, and thin wrappers for non-variadic functions using `external_call` or `OwnedDLHandle.call` depending on `--linking`.
146
+ - **Linking modes:** `external_call` (default) links C symbols at Mojo build time; `owned_dl_handle` resolves calls through `OwnedDLHandle`, with optional `--library-path-hint` for dlopen-style loading.
147
+ - **C globals:** For globals whose types are thin-FFI-compatible, the emitter generates `GlobalVar` / `GlobalConst` helpers that load and store through `UnsafePointer`, resolving symbols with `OwnedDLHandle.get_symbol` when needed (see `examples/global_consts/`). Atomics and layouts that cannot be lowered still become comment stubs with a short reason.
148
+ - **Macros and constants:** Object-like macros and `const` initializers are handled when the body fits the supported token expression grammar (literals, identifiers, parentheses, unary `-` / `~`, and binary operators); integer subexpressions are constant-folded when both operands are literals. Unsupported forms are classified and often preserved as comments rather than silent guesses.
149
+ - **Python API extras:** `MojoEmitOptions` also exposes pointer provenance (`ffi_origin`: `external` vs `any`), module header comments, and ABI reminder comments—tunable from code even though the CLI only exposes linking and library path hint today.
150
+
151
+ ## Limitations & Rough Edges
152
+
153
+ Parsing / IR:
154
+
155
+ - **Macros and constant expressions:** Function-like macros are not expanded into expressions. `sizeof`, most casts, and other constructs outside the supported token grammar are skipped or left as diagnostics; unsupported object-like macros may be emitted as comments only.
156
+ - **Bitfields:** Bitfields are modeled with width/offset metadata and emitted with layout comments and warnings to verify ABI against your C compiler; exotic packing or mixed backing-unit edge cases can still be wrong without cross-checking.
157
+ - **Hard declaration shapes:** A few difficult C declarator shapes and compiler-extension-heavy signatures may still require manual review, especially when mixed with unusual attributes or unsupported macro-driven spellings.
158
+ - **Anonymous and extension-heavy constructs:** Anonymous enums are emitted as top-level constants, and anonymous struct/union members are preserved as synthetic storage fields; some compiler-extension or unusual anonymous record cases still degrade to `UnsupportedType` or require manual review.
159
+ - **C storage/linkage qualifiers:** Type qualifiers on pointers are preserved, but C declaration-level linkage/storage semantics such as `inline` / `extern inline` still are not modeled precisely enough to guarantee symbol availability.
160
+
161
+ Mojo lowering / runtime:
162
+
163
+ - **Variadic functions:** No thin callable wrapper is emitted; output is a comment noting that varargs are not modeled for FFI.
164
+ - **Function pointers:** Function-pointer types are preserved in IR and lowered as opaque pointer ABI values in Mojo. Returned/parameter fnptr values can be passed through thin FFI, but callable wrappers for invoking function-pointer values are not generated yet; semantic signature comments are emitted where applicable (notably struct fields).
165
+ - **Globals:** Wrappers are not emitted for atomic globals or for types that cannot be lowered to a concrete Mojo surface type (see stub comments in generated output).
166
+ - **Non-`RegisterPassable` struct-by-value returns:** Functions that return a struct by value when that struct is not considered register-passable in the analysis pass are emitted as comment stubs rather than callable wrappers.
167
+ - **`inline` / non-standard linkage:** The generated bindings may still treat declarations as normal extern symbols even when C linkage rules are more subtle, which can produce symbol mismatches at runtime.
168
+
169
+ ## Development
170
+
171
+ ```bash
172
+ pixi run pytest
173
+ ```
174
+
175
+ Build distributable artifacts through Pixi tasks:
176
+
177
+ ```bash
178
+ pixi run clean-dist
179
+ pixi run build
180
+ ```
181
+
182
+ Optional one-shot targets:
183
+
184
+ - `pixi run build-wheel`
185
+ - `pixi run build-sdist`
186
+
187
+ ## License
188
+
189
+ Licensed under the **MIT License**. See [LICENSE](LICENSE).
@@ -0,0 +1,154 @@
1
+ [![CI](https://github.com/MoSafi2/mojo_bindgen/actions/workflows/ci.yml/badge.svg)](https://github.com/MoSafi2/mojo_bindgen/actions/workflows/ci.yml)
2
+
3
+ # mojo-bindgen
4
+
5
+ Generate **Mojo FFI** bindings from **C headers** using [libclang](https://pypi.org/project/libclang/) (Python bindings). The tool parses a header, builds an internal IR, and emits a `.mojo` module with `external_call` or `owned_dl_handle` linking.
6
+
7
+ **Requires Python 3.14+** (aligned with current Modular/Mojo Pixi stacks).
8
+
9
+ For maintainer-facing architecture notes, see [docs/codegen-architecture.md](docs/codegen-architecture.md). For contributing, security contact, and release notes, see [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), and [CHANGELOG.md](CHANGELOG.md).
10
+
11
+ ## Setup
12
+
13
+ ### 1) System dependencies
14
+
15
+ Install `clang` and `libclang` first (required for header parsing):
16
+
17
+ ```bash
18
+ # Ubuntu / Debian
19
+ sudo apt update
20
+ sudo apt install -y clang libclang1
21
+
22
+ # Fedora
23
+ sudo dnf install -y clang llvm-libs
24
+
25
+ # macOS (Homebrew)
26
+ brew install llvm
27
+ ```
28
+
29
+ If `libclang` is installed in a non-standard location, set `LIBCLANG_PATH` to the directory containing `libclang.so` / `libclang.dylib`.
30
+
31
+ ### 2) Install project dependencies
32
+
33
+ From the repository root (requires [Pixi](https://pixi.sh)):
34
+
35
+ ```bash
36
+ pixi install
37
+ pixi shell
38
+ ```
39
+
40
+ The Pixi workspace is **linux-64 only** at the moment; on macOS or Windows use a virtual environment and `pip install -e ".[dev]"` instead (see [CONTRIBUTING.md](CONTRIBUTING.md)).
41
+
42
+ This installs the `mojo-bindgen` package in editable mode and puts the `mojo-bindgen` CLI on your `PATH`.
43
+
44
+ You also need a **system libclang** shared library compatible with the `libclang` Python wheel.
45
+
46
+ ### Library usage (Python API)
47
+
48
+ The CLI wraps the same pipeline you can call from Python: parse a header to IR, then generate Mojo source.
49
+
50
+ ```python
51
+ from pathlib import Path
52
+
53
+ from mojo_bindgen.codegen import MojoEmitOptions, generate_mojo
54
+ from mojo_bindgen.parsing.parser import ClangParser
55
+
56
+ header = Path("include/mylib.h")
57
+ unit = ClangParser(
58
+ header,
59
+ library="mylib",
60
+ link_name="mylib",
61
+ compile_args=["-I", "include"],
62
+ ).run()
63
+ mojo_src = generate_mojo(unit, MojoEmitOptions(linking="external_call"))
64
+ ```
65
+
66
+ Stable exports from the `mojo_bindgen` package root are `MojoGenerator`, `MojoEmitOptions`, `generate_mojo`, and `__version__`. Parsing types (`ClangParser`, `ParseError`, `Unit`) live in their modules and are treated as public for programmatic use; follow semver for the workflow above when upgrading.
67
+
68
+ ## CLI
69
+
70
+ Inspect all options and examples:
71
+
72
+ ```bash
73
+ mojo-bindgen --help
74
+ ```
75
+
76
+ Emit Mojo FFI (default):
77
+
78
+ ```bash
79
+ mojo-bindgen path/to/header.h -o bindings.mojo
80
+ ```
81
+
82
+ Dump IR as JSON:
83
+
84
+ ```bash
85
+ mojo-bindgen path/to/header.h --json -o unit.json
86
+ ```
87
+
88
+ Extra clang flags (include paths, sysroot, target):
89
+
90
+ ```bash
91
+ mojo-bindgen include/me.h --compile-arg=-I./include --compile-arg=--sysroot=/path/to/sysroot -o out.mojo
92
+ ```
93
+
94
+ Set a specific C language standard:
95
+
96
+ ```bash
97
+ mojo-bindgen include/me.h --compile-arg=-std=c99 -o out.mojo
98
+ ```
99
+
100
+ `--compile-arg` standard flags accept `-std=...`, `--std=...`, and `std=...` forms. If no standard is provided, `-std=gnu11` is used by default.
101
+
102
+ By default, `--library` and `--link-name` are the header file stem (e.g. `me` for `me.h`).
103
+ `--compile-arg` is repeatable.
104
+ Use `mojo-bindgen --help` for full option details.
105
+
106
+ ## Features
107
+
108
+ - **libclang parsing:** Walks a primary C header with configurable compile flags (default language is `gnu11` if you do not pass `-std=...`). Repeatable `--compile-arg` passes include paths, sysroot, target triple, and standard selection.
109
+ - **IR and tooling:** Builds a structured IR, runs validation and a reachability materialization pass, and can **dump JSON** (`--json`) for debugging or downstream tools.
110
+ - **Generated Mojo surface:** Emits structs (with `@align` from C alignment when expressible in Mojo), unions (including `@unsafe_union` when layout analysis marks them eligible), enums, typedefs, and thin wrappers for non-variadic functions using `external_call` or `OwnedDLHandle.call` depending on `--linking`.
111
+ - **Linking modes:** `external_call` (default) links C symbols at Mojo build time; `owned_dl_handle` resolves calls through `OwnedDLHandle`, with optional `--library-path-hint` for dlopen-style loading.
112
+ - **C globals:** For globals whose types are thin-FFI-compatible, the emitter generates `GlobalVar` / `GlobalConst` helpers that load and store through `UnsafePointer`, resolving symbols with `OwnedDLHandle.get_symbol` when needed (see `examples/global_consts/`). Atomics and layouts that cannot be lowered still become comment stubs with a short reason.
113
+ - **Macros and constants:** Object-like macros and `const` initializers are handled when the body fits the supported token expression grammar (literals, identifiers, parentheses, unary `-` / `~`, and binary operators); integer subexpressions are constant-folded when both operands are literals. Unsupported forms are classified and often preserved as comments rather than silent guesses.
114
+ - **Python API extras:** `MojoEmitOptions` also exposes pointer provenance (`ffi_origin`: `external` vs `any`), module header comments, and ABI reminder comments—tunable from code even though the CLI only exposes linking and library path hint today.
115
+
116
+ ## Limitations & Rough Edges
117
+
118
+ Parsing / IR:
119
+
120
+ - **Macros and constant expressions:** Function-like macros are not expanded into expressions. `sizeof`, most casts, and other constructs outside the supported token grammar are skipped or left as diagnostics; unsupported object-like macros may be emitted as comments only.
121
+ - **Bitfields:** Bitfields are modeled with width/offset metadata and emitted with layout comments and warnings to verify ABI against your C compiler; exotic packing or mixed backing-unit edge cases can still be wrong without cross-checking.
122
+ - **Hard declaration shapes:** A few difficult C declarator shapes and compiler-extension-heavy signatures may still require manual review, especially when mixed with unusual attributes or unsupported macro-driven spellings.
123
+ - **Anonymous and extension-heavy constructs:** Anonymous enums are emitted as top-level constants, and anonymous struct/union members are preserved as synthetic storage fields; some compiler-extension or unusual anonymous record cases still degrade to `UnsupportedType` or require manual review.
124
+ - **C storage/linkage qualifiers:** Type qualifiers on pointers are preserved, but C declaration-level linkage/storage semantics such as `inline` / `extern inline` still are not modeled precisely enough to guarantee symbol availability.
125
+
126
+ Mojo lowering / runtime:
127
+
128
+ - **Variadic functions:** No thin callable wrapper is emitted; output is a comment noting that varargs are not modeled for FFI.
129
+ - **Function pointers:** Function-pointer types are preserved in IR and lowered as opaque pointer ABI values in Mojo. Returned/parameter fnptr values can be passed through thin FFI, but callable wrappers for invoking function-pointer values are not generated yet; semantic signature comments are emitted where applicable (notably struct fields).
130
+ - **Globals:** Wrappers are not emitted for atomic globals or for types that cannot be lowered to a concrete Mojo surface type (see stub comments in generated output).
131
+ - **Non-`RegisterPassable` struct-by-value returns:** Functions that return a struct by value when that struct is not considered register-passable in the analysis pass are emitted as comment stubs rather than callable wrappers.
132
+ - **`inline` / non-standard linkage:** The generated bindings may still treat declarations as normal extern symbols even when C linkage rules are more subtle, which can produce symbol mismatches at runtime.
133
+
134
+ ## Development
135
+
136
+ ```bash
137
+ pixi run pytest
138
+ ```
139
+
140
+ Build distributable artifacts through Pixi tasks:
141
+
142
+ ```bash
143
+ pixi run clean-dist
144
+ pixi run build
145
+ ```
146
+
147
+ Optional one-shot targets:
148
+
149
+ - `pixi run build-wheel`
150
+ - `pixi run build-sdist`
151
+
152
+ ## License
153
+
154
+ Licensed under the **MIT License**. See [LICENSE](LICENSE).
@@ -0,0 +1,20 @@
1
+ """C header → Mojo FFI bindings via libclang."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import PackageNotFoundError, version
6
+
7
+ from mojo_bindgen.codegen import FFIScalarStyle, MojoEmitOptions, MojoGenerator, generate_mojo
8
+
9
+ try:
10
+ __version__ = version("mojo-bindgen")
11
+ except PackageNotFoundError:
12
+ __version__ = "0.1.0"
13
+
14
+ __all__ = [
15
+ "FFIScalarStyle",
16
+ "MojoEmitOptions",
17
+ "MojoGenerator",
18
+ "__version__",
19
+ "generate_mojo",
20
+ ]
@@ -0,0 +1,138 @@
1
+ """Command-line interface for mojo-bindgen."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+ from typing import Annotated, Literal
8
+
9
+ import typer
10
+ from rich.console import Console
11
+
12
+ from mojo_bindgen.codegen.generator import MojoGenerator
13
+ from mojo_bindgen.codegen.mojo_emit_options import MojoEmitOptions
14
+ from mojo_bindgen.parsing.parser import ClangParser, ParseError
15
+
16
+ stderr_console = Console(stderr=True)
17
+
18
+
19
+ def run(
20
+ header: Annotated[Path, typer.Argument(help="Path to the primary C header file")],
21
+ output: Annotated[
22
+ Path | None,
23
+ typer.Option(
24
+ "--output",
25
+ "-o",
26
+ help="Write output to this file (default: stdout).",
27
+ show_default=False,
28
+ ),
29
+ ] = None,
30
+ library: Annotated[
31
+ str | None,
32
+ typer.Option(
33
+ "--library",
34
+ metavar="NAME",
35
+ help="Logical library name in the IR (default: stem of the header path).",
36
+ ),
37
+ ] = None,
38
+ link_name: Annotated[
39
+ str | None,
40
+ typer.Option(
41
+ "--link-name",
42
+ metavar="NAME",
43
+ help="Shared-library link name (default: same as --library).",
44
+ ),
45
+ ] = None,
46
+ compile_arg: Annotated[
47
+ list[str] | None,
48
+ typer.Option(
49
+ "--compile-arg",
50
+ metavar="FLAG",
51
+ help="Extra clang flag (repeatable). If omitted, use built-in system include probing.",
52
+ ),
53
+ ] = None,
54
+ json_output: Annotated[
55
+ bool,
56
+ typer.Option(
57
+ "--json",
58
+ help="Emit IR as JSON instead of Mojo source.",
59
+ ),
60
+ ] = False,
61
+ linking: Annotated[
62
+ Literal["external_call", "owned_dl_handle"],
63
+ typer.Option(
64
+ "--linking",
65
+ help="FFI linking mode for Mojo output (ignored with --json).",
66
+ case_sensitive=False,
67
+ ),
68
+ ] = "external_call",
69
+ library_path_hint: Annotated[
70
+ str | None,
71
+ typer.Option(
72
+ "--library-path-hint",
73
+ metavar="PATH",
74
+ help="Path hint for OwnedDLHandle when --linking owned_dl_handle.",
75
+ ),
76
+ ] = None,
77
+ ) -> int:
78
+ """Generate Mojo FFI from a C header using libclang.
79
+
80
+ Examples:
81
+ mojo-bindgen path/to/header.h -o bindings.mojo
82
+ mojo-bindgen path/to/header.h --json -o unit.json
83
+ mojo-bindgen include/me.h --compile-arg=-I./include -o out.mojo
84
+ """
85
+ stem = header.stem if header.suffix else str(header)
86
+ library_name = library if library is not None else stem
87
+ link_name_value = link_name if link_name is not None else library_name
88
+ compile_args = compile_arg
89
+
90
+ try:
91
+ parser = ClangParser(
92
+ header,
93
+ library=library_name,
94
+ link_name=link_name_value,
95
+ compile_args=compile_args,
96
+ raise_on_error=True,
97
+ )
98
+ unit = parser.run()
99
+ except (ParseError, FileNotFoundError, OSError) as e:
100
+ stderr_console.print(f"[bold red]mojo-bindgen error:[/bold red] {e}")
101
+ raise typer.Exit(code=1) from e
102
+
103
+ if json_output:
104
+ text = unit.to_json()
105
+ else:
106
+ opts = MojoEmitOptions(
107
+ linking=linking,
108
+ library_path_hint=library_path_hint,
109
+ )
110
+ text = MojoGenerator(opts).generate(unit)
111
+
112
+ if output is None:
113
+ sys.stdout.write(text)
114
+ if not text.endswith("\n"):
115
+ sys.stdout.write("\n")
116
+ else:
117
+ output.write_text(text, encoding="utf-8")
118
+ return 0
119
+
120
+
121
+ def main(argv: list[str] | None = None) -> int:
122
+ cli_args = argv if argv is not None else sys.argv[1:]
123
+ previous_argv = sys.argv[:]
124
+ sys.argv = ["mojo-bindgen", *cli_args]
125
+ try:
126
+ typer.run(run)
127
+ return 0
128
+ except typer.Exit as e:
129
+ return e.exit_code
130
+ except SystemExit as e:
131
+ code = e.code
132
+ return code if isinstance(code, int) else 0
133
+ finally:
134
+ sys.argv = previous_argv
135
+
136
+
137
+ if __name__ == "__main__":
138
+ raise SystemExit(main())
@@ -0,0 +1,15 @@
1
+ """Public Mojo code generation API.
2
+
3
+ Import from this package when you want the stable, higher-level codegen
4
+ surface rather than the individual implementation modules.
5
+ """
6
+
7
+ from mojo_bindgen.codegen.generator import MojoGenerator, generate_mojo
8
+ from mojo_bindgen.codegen.mojo_emit_options import FFIScalarStyle, MojoEmitOptions
9
+
10
+ __all__ = [
11
+ "FFIScalarStyle",
12
+ "MojoGenerator",
13
+ "MojoEmitOptions",
14
+ "generate_mojo",
15
+ ]
@@ -0,0 +1,108 @@
1
+ """Value-embedding struct dependency ordering for code generation.
2
+
3
+ These helpers compute an emission order for structs that embed other structs
4
+ by value, so referenced layouts appear before the records that depend on them.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections import defaultdict, deque
10
+
11
+ from mojo_bindgen.codegen.mojo_mapper import mojo_ident
12
+ from mojo_bindgen.ir import (
13
+ Array,
14
+ AtomicType,
15
+ EnumRef,
16
+ QualifiedType,
17
+ Struct,
18
+ StructRef,
19
+ Type,
20
+ TypeRef,
21
+ )
22
+
23
+
24
+ def struct_dependency_edges(s: Struct) -> list[tuple[str, str]]:
25
+ """Return (successor, predecessor) pairs: `succ` depends on `pred` (emit `pred` first).
26
+
27
+ Only **by-value** struct references create ordering edges. Pointer and function-pointer
28
+ fields do not (C allows pointers to incomplete types); nested struct layout is still
29
+ captured via ``Array``/``StructRef`` for value-embedded arrays of structs.
30
+ """
31
+ me = mojo_ident(s.name.strip() or s.c_name.strip())
32
+ edges: list[tuple[str, str]] = []
33
+
34
+ def walk(ty: Type) -> None:
35
+ """Collect by-value struct dependencies reachable from ``ty``."""
36
+ if isinstance(ty, TypeRef):
37
+ walk(ty.canonical)
38
+ return
39
+ if isinstance(ty, QualifiedType):
40
+ walk(ty.unqualified)
41
+ return
42
+ if isinstance(ty, AtomicType):
43
+ walk(ty.value_type)
44
+ return
45
+ if isinstance(ty, EnumRef):
46
+ return
47
+ if isinstance(ty, StructRef):
48
+ if ty.is_union:
49
+ return
50
+ pred = mojo_ident(ty.name.strip())
51
+ if pred != me:
52
+ edges.append((me, pred))
53
+ return
54
+ if isinstance(ty, Array):
55
+ walk(ty.element)
56
+
57
+ for f in s.fields:
58
+ walk(f.type)
59
+ return edges
60
+
61
+
62
+ def toposort_structs(structs: list[Struct]) -> list[Struct]:
63
+ """Return structs in an order suitable for emission.
64
+
65
+ Value-embedded :class:`~mojo_bindgen.ir.StructRef` dependencies are emitted
66
+ before the records that use them. Cycles fall back to the original input
67
+ order once the acyclic portion of the graph is exhausted.
68
+ """
69
+ if not structs:
70
+ return []
71
+
72
+ def name_of(s: Struct) -> str:
73
+ """Return the sanitized emitted name for ``s``."""
74
+ return mojo_ident(s.name.strip() or s.c_name.strip())
75
+
76
+ names = [name_of(s) for s in structs]
77
+ known = set(names)
78
+ name_to_struct = {name_of(s): s for s in structs}
79
+
80
+ # graph[pred] = successors that must appear after pred
81
+ graph: dict[str, set[str]] = defaultdict(set)
82
+ indegree: dict[str, int] = {n: 0 for n in names}
83
+
84
+ for s in structs:
85
+ succ = name_of(s)
86
+ for succ2, pred in struct_dependency_edges(s):
87
+ if succ2 != succ:
88
+ continue
89
+ if pred in known and pred != succ:
90
+ if succ not in graph[pred]:
91
+ graph[pred].add(succ)
92
+ indegree[succ] += 1
93
+
94
+ q = deque(sorted([n for n in names if indegree[n] == 0], key=lambda x: names.index(x)))
95
+ order: list[str] = []
96
+ while q:
97
+ n = q.popleft()
98
+ order.append(n)
99
+ for succ in graph.get(n, ()):
100
+ indegree[succ] -= 1
101
+ if indegree[succ] == 0:
102
+ q.append(succ)
103
+
104
+ for n in names:
105
+ if n not in order:
106
+ order.append(n)
107
+
108
+ return [name_to_struct[n] for n in order]