1
0
Fork
You've already forked C2Odin
0
No description
  • C 65.9%
  • Odin 34%
  • Makefile 0.1%
2026年07月04日 19:08:09 -03:00
docs/adr docs: align spec and docs with implemented behavior 2026年07月04日 01:14:35 -03:00
src test: build e2e binary once 2026年07月04日 19:08:09 -03:00
tests test: build e2e binary once 2026年07月04日 19:08:09 -03:00
vendored/libclang feat: implement MVP CONFIG-EXTRACT-TRANSFORM-EMIT pipeline 2026年07月01日 21:25:50 -03:00
.gitignore
AGENTS.md docs: align spec and docs with implemented behavior 2026年07月04日 01:14:35 -03:00
CONTEXT.md refactor: small cleanups across config, extract, transform, emit 2026年07月04日 12:48:14 -03:00
Makefile mvp: generate correct bindings for simple C headers 2026年07月02日 00:00:45 -03:00
odinfmt.json
ols.json feat: implement MVP CONFIG-EXTRACT-TRANSFORM-EMIT pipeline 2026年07月01日 21:25:50 -03:00
README.md feat: improve comment capture and emission 2026年07月04日 18:45:24 -03:00
ROADMAP.md test: build e2e binary once 2026年07月04日 19:08:09 -03:00
SPEC.md refactor: remove dead table fields 2026年07月04日 19:05:17 -03:00

c2odin

C-to-Odin binding generator written in Odin.

c2odin reads C headers through libclang, captures raw C type shapes and declarations into Odin-native SoA tables, transforms them through deterministic passes, and emits Odin bindings.

Design Goals

  • Deterministic — same inputs produce byte-identical output.
  • Agnostic — no library-specific rules, prefixes, or naming conventions are hardcoded.
  • Data-oriented — Odin #soa[dynamic] tables, integer IDs, linear passes.
  • Simple — CONFIG → EXTRACT → TRANSFORM → EMIT, with clear responsibilities.
  • Safe by default — fail fast instead of emitting ABI-unsafe bindings.

Architecture

CONFIG : Lua -> validated Config
EXTRACT : libclang -> raw type shapes + raw declarations
TRANSFORM: policy passes -> validated binding tables
EMIT : validated tables -> .odin source string

The core IR separates type shapes from declarations. A C declaration such as typedef Foo Bar; is not the same thing as a nested type expression such as const Bar *. EXTRACT captures both while libclang is alive; TRANSFORM maps them to Odin without calling libclang.

See SPEC.md for the full architecture specification.

Quick Start

make build # builds build/c2odin
./build/c2odin tests/fixtures/simple

c2odin expects a c2odin.lua file in the provided directory and writes the generated binding atomically to the output filename configured there (default generated.odin), inside that same directory. Warnings go to stderr.

Configuration

Configuration uses Lua, but v1 treats Lua as a declarative config format. The file must return one plain table. Functions, userdata, coroutines, metatables, and arrays with nil holes are rejected.

return {
 headers = { "foo.h" },
 output = "foo.odin",
 package = "foo",
 library = "system:foo",
 clang = {
 args = {
 "-std=c11",
 "-target", "x86_64-unknown-linux-gnu",
 },
 },
 include_dirs = { "/usr/include" },
 defines = { "FOO_ENABLE_FEATURE=1" },
 type = {
 style = "abi", -- "abi" | "idiomatic"
 },
 naming = {
 normalize = true,
 strip_prefixes = {},
 },
 remove = {},
 comments = true,
}

include_dirs and defines are conveniences converted to clang arguments. clang.args is appended after them so target, standard, sysroot, and platform flags can be specified explicitly. Set comments = false to skip comment capture and emission.

v1 Scope

Supported:

  • Raw type-shape extraction from libclang.
  • Typedef aliases.
  • Generic opaque handles: typedef pointer to incomplete record.
  • Structs and unions without bitfields or flexible array members.
  • Enums.
  • Non-variadic function pointer typedefs.
  • Top-level foreign procedures, including variadic foreign functions.
  • Extern global variables, emitted inside the foreign block.
  • Object-like literal/simple macros.

Rejected in v1 unless removed by config:

  • Bitfields.
  • Flexible array members.
  • Variadic function pointer typedefs.
  • Function-like macros as active declarations.
  • Unresolved macro references as active constants.
  • Unknown pragma-pack layout.
  • Library-specific heuristics.

Type Styles

ABI Idiomatic
int c.int i32
long c.long i64 on LP64
size_t c.size_t int
const char* cstring cstring
opaque handle distinct rawptr distinct rawptr

Use ABI mode when platform correctness matters.

Output Example

packagefooimport"core:c"Foo::distinctrawptrCompare_Fn::proc"c"(arg0:rawptr,arg1:rawptr)->c.intforeignimportlib"system:foo"foreignlib{@(link_name="foo_create")create::proc(out_foo:^Foo)->c.int---}

Foreign functions are emitted inside foreign <lib> { ... }. EMIT hoists a common mechanical C-symbol affix to @(link_prefix=...) / @(link_suffix=...) when useful, and otherwise uses @(link_name="...") when the Odin name differs from the C symbol.

Dependencies

  • Odin compiler.
  • Native system libclang library, version 14+.
  • Vendored Odin bindings for the libclang C API.
  • Lua 5.4 through Odin's vendor package.

Native libclang binaries are not vendored in v1.

Project Structure

The source is organized by pipeline phase: CONFIG, EXTRACT, TRANSFORM, EMIT, plus the shared data model (tables.odin, string_table.odin, run_state.odin). Each phase is free to live in one file or split as it grows — what matters is the responsibility boundary, not the filenames.

c2odin/
├── src/ # the compiler, organized by pipeline phase
├── vendored/libclang/ # Odin bindings for the libclang C API
├── tests/ # e2e tests + fixtures (C headers, c2odin.lua, expected output)
├── docs/adr/ # architectural decision records
├── SPEC.md # architecture specification (source of truth)
├── AGENTS.md # how agents work on the code
└── CONTEXT.md # domain glossary

Unit tests live beside the source they specify (*_test.odin); E2E tests live in tests/.

License

TBD