{"id":51166361,"url":"https://github.com/kiranandcode/lean-bindgen","last_synced_at":"2026-06-26T19:02:31.014Z","repository":{"id":354679149,"uuid":"1222443331","full_name":"kiranandcode/lean-bindgen","owner":"kiranandcode","description":"Lean4 C Binding Generation","archived":false,"fork":false,"pushed_at":"2026-04-29T15:35:19.000Z","size":1482,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-29T16:31:24.612Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kiranandcode.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-27T11:21:38.000Z","updated_at":"2026-04-29T15:37:54.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kiranandcode/lean-bindgen","commit_stats":null,"previous_names":["kiranandcode/lean-bindgen"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/kiranandcode/lean-bindgen","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kiranandcode%2Flean-bindgen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kiranandcode%2Flean-bindgen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kiranandcode%2Flean-bindgen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kiranandcode%2Flean-bindgen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kiranandcode","download_url":"https://codeload.github.com/kiranandcode/lean-bindgen/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kiranandcode%2Flean-bindgen/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34829415,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-26T02:00:06.560Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-26T19:02:25.102Z","updated_at":"2026-06-26T19:02:31.009Z","avatar_url":"https://github.com/kiranandcode.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lean-bindgen\n\nGenerate Lean 4 FFI bindings from C headers. Write a concise DSL spec,\nget `@[extern]` declarations + a C shim that handles all the marshalling.\n\n## Getting started\n\n### 1. Create your project\n\n```sh\nmkdir my-bindings \u0026\u0026 cd my-bindings\n```\n\nSet up the toolchain and add lean-bindgen as a dependency:\n\n**`lean-toolchain`**\n```\nleanprover/lean4:v4.29.0-rc6\n```\n\n**`lakefile.lean`**\n```lean\nimport Lake\nopen Lake DSL System\n\npackage «my-bindings»\n\nrequire «lean-bindgen» from git\n  \"https://github.com/kiranandcode/lean-bindgen\" @ \"main\"\n\nlean_lib Bindings where\nlean_exe generate where\n  root := `Generate\nlean_lib Generated where\n\n@[default_target]\nlean_exe demo where\n  root := `Main\n\n-- Compile the generated C shim (+ optionally the vendor library) into\n-- a static archive. Lake links this into any exe that imports Generated.\nextern_lib «mylib-shim» pkg := do\n  let leanIncDir := (← getLeanIncludeDir).toString\n  let vendorDir := (pkg.dir / \"vendor\").toString\n  let weakArgs := #[\"-I\", leanIncDir, \"-I\", vendorDir]\n  let traceArgs := #[\"-fPIC\"]\n  let sources : Array FilePath := #[\"csrc/mylib-shim.c\", \"vendor/mylib.c\"]\n  let oJobs ← sources.mapM fun src =\u003e do\n    let oFile := pkg.buildDir / src.withExtension \"o\"\n    let srcJob ← inputTextFile (pkg.dir / src)\n    buildO oFile srcJob weakArgs traceArgs\n  buildStaticLib (pkg.staticLibDir / nameToStaticLib \"mylib-shim\") oJobs\n```\n\n\u003e For system libraries (not vendored), replace `\"vendor/mylib.c\"` with\n\u003e just the shim, and add `moreLinkArgs := #[\"-L/usr/lib\", \"-lmylib\"]` to\n\u003e the package declaration.\n\n### 2. Write your binding spec\n\n**`Bindings.lean`**\n```lean\nimport LeanBindgen\n\nopen LeanBindgen LeanBindgen.DSL\n\ndef myBindings : Bindings := c_bindings {\n  header \"vendor/mylib.h\"\n  module Generated.MyLib\n  out_dir \"Generated\"\n  shim \"csrc/mylib-shim.c\"\n  lib \"mylib\"\n\n  opaque mylib_ctx_t =\u003e Context freed_by mylib_ctx_free\n\n  cfn mylib_create =\u003e create +io\n  cfn mylib_do_thing =\u003e doThing bool_status on_error string mylib_last_error +io\n  cfn mylib_get_value =\u003e getValue +io\n}\n```\n\n### 3. Write the codegen driver\n\n**`Generate.lean`**\n```lean\nimport LeanBindgen\nimport Bindings\n\nopen LeanBindgen LeanBindgen.C LeanBindgen.Codegen\n\ndef main : IO Unit := do\n  let b := myBindings\n  let src ← IO.FS.readFile b.headerPath\n  let tokens ← IO.ofExcept (tokenize src)\n  let hdr ← IO.ofExcept (parseHeader tokens)\n  IO.println s!\"Parsed {hdr.decls.size} declarations from {b.headerPath}\"\n\n  let leanSrc ← IO.ofExcept (emitLeanModule b hdr)\n  IO.FS.createDirAll b.outDir\n  IO.FS.writeFile s!\"{b.outDir}/MyLib.lean\" leanSrc\n  IO.println s!\"Wrote {b.outDir}/MyLib.lean ({leanSrc.length} bytes)\"\n\n  let shimSrc ← IO.ofExcept (emitShim b hdr)\n  IO.FS.createDirAll \"csrc\"\n  IO.FS.writeFile b.shimPath shimSrc\n  IO.println s!\"Wrote {b.shimPath} ({shimSrc.length} bytes)\"\n```\n\n### 4. Generate and build\n\n```sh\n# Create placeholder files so Lake can resolve targets\nmkdir -p csrc Generated\necho '' \u003e csrc/mylib-shim.c\necho 'namespace Generated.MyLib end Generated.MyLib' \u003e Generated/MyLib.lean\n\n# Fetch dependencies\nlake update\n\n# Generate the Lean module + C shim from the header\nlake exe generate\n\n# Build everything (compiles shim, links, produces executable)\nlake build\n```\n\nAfter the first `lake exe generate`, commit `Generated/` and `csrc/` —\nthen `lake build` works without regenerating. Re-run `generate` when the\nC header changes.\n\n### 5. Use the bindings\n\n**`Main.lean`**\n```lean\nimport Generated.MyLib\n\nopen Generated.MyLib\n\ndef main : IO Unit := do\n  let ctx ← create 42\n  match ← doThing ctx with\n  | .ok () =\u003e IO.println s!\"Value: {← getValue ctx}\"\n  | .error e =\u003e IO.println s!\"Error: {e}\"\n```\n\n### Complete working example\n\nSee [`examples/starter/`](./examples/starter/) for a self-contained\nproject that builds and runs without any external dependencies. Clone\nand run:\n\n```sh\ncd examples/starter\nlake update \u0026\u0026 lake build \u0026\u0026 .lake/build/bin/demo\n```\n\n```\ncounter library v1.0.0\nInitial value: 0\nAfter +10: 10\nAfter +32: 42\nDone!\n```\n\n---\n\n## DSL reference\n\n\u003e For a detailed guide with full explanations of each syntax form and what\n\u003e it generates, see **[doc/DSL.md](./doc/DSL.md)**.\n\n### Header fields\n\n| Syntax | Purpose |\n|--------|---------|\n| `header \"path\"` | Path to C header file |\n| `module My.Module` | Lean module name for generated code |\n| `out_dir \"path\"` | Output directory for generated `.lean` |\n| `shim \"path\"` | Output path for generated C shim |\n| `lib \"name\"` | Library prefix (used in extern symbol names) |\n| `preprocessor [\"-DFOO\", \"-Ibar\"]` | Preprocessor args (runs `cc -E -P`) |\n\n### Type declarations\n\n```lean\n-- Scalar newtype (C typedef → Lean def)\nscalar c_type_t =\u003e LeanName : UInt64    -- also UInt32, UInt16, UInt8, Int32, Int64\n\n-- Inductive enum\nenum c_enum_t =\u003e LeanEnum tag c_enum_tag_name\n  | c_variant_1 =\u003e leanVariant1\n  | c_variant_2 =\u003e leanVariant2\n\n-- Opaque pointer with GC finalizer\nopaque c_type_t =\u003e LeanType freed_by c_type_free\n\n-- Opaque pointer (borrowed, no finalizer)\nopaque c_type_t =\u003e LeanType borrowed\n\n-- Struct record\nstruct c_struct_t =\u003e LeanStruct tag c_struct_tag\n  | c_field_1 =\u003e leanField1\n  | c_field_2 =\u003e leanField2\n\n-- Callback typedef (Lean closure from C function pointer)\ncallback c_callback_t =\u003e LeanCallback\n\n-- Bitfield struct (fields become Bool)\nbitfield c_flags_t =\u003e LeanFlags tag c_flags_tag\n  | c_flag_a =\u003e flagA\n  | c_flag_b =\u003e flagB\n\n-- Escape hatch: raw TypeAnno record\ntype_raw { cName := \"...\", lean := \"...\", mapping := ... }\n```\n\n### Function declarations\n\n```lean\n-- Direct call (pure or +io)\ncfn c_function =\u003e leanName\ncfn c_function =\u003e leanName +io\n\n-- Out-param with bool status: bool fn(..., T *out) → IO (Except E T)\ncfn c_function =\u003e leanName out[N] on_error string c_error_fn\ncfn c_function =\u003e leanName out[N] on_error enum c_code LeanError c_msg_fn\ncfn c_function =\u003e leanName out[N] on_error tuple c_code LeanError c_msg_fn\n\n-- Void out-param: void fn(..., T *out) → T\ncfn c_function =\u003e leanName void_out[N]\n\n-- Option out-param: bool fn(..., T *out) → Option T\ncfn c_function =\u003e leanName option_out[N]\n\n-- Option out-array: bool fn(..., T **out, size_t *n) → Option (Array T)\ncfn c_function =\u003e leanName option_out_array[N, M]\n\n-- Multi out-param: void fn(T1 *a, T2 *b) → T1 × T2\ncfn c_function =\u003e leanName multi_out[0, 1, 2]\n\n-- Bool status (no out-param): bool fn(...) → Except E Unit\ncfn c_function =\u003e leanName bool_status on_error string c_error_fn\n\n-- Caller-allocates (two-step size+fill): fn_size + fn_fill → String or Array\ncfn c_function =\u003e leanName caller_alloc \"c_size_fn\" [N, M] on_error string c_error_fn\n\n-- Escape hatch\nfn_raw { cName := \"...\", lean := \"...\", style := ... }\n```\n\n### Function modifiers\n\nAppend after the style:\n\n| Modifier | Effect |\n|----------|--------|\n| `+io` | Wrap return in `IO` |\n| `+nullable_return` | `char const *` → `Option String` |\n| `+nullable_out` | Out-param may be NULL → `Option T` |\n| `callback_data [N]` | Index N is user-data for preceding callback |\n| `array_pairs [(N, M)]` | Params N,M are (ptr, size) → `Array T` |\n| `byte_pairs [(N, M)]` | Like array_pairs but for `ByteArray` |\n| `retained_params [N]` | Lean must inc-ref (C retains pointer) |\n| `borrowed_params [N]` | Force `@\u0026` on param N |\n| `extern_sym \"name\"` | Override `@[extern \"...\"]` symbol |\n\n### Constants\n\n```lean\ncconst MY_CONST : Int32 := \"42\"\ncconst MY_FLAG : UInt32 := \"1\"\n```\n\n---\n\n## What gets generated\n\nFor `cfn clingo_signature_create =\u003e mk out[3] on_error string clingo_error_message`:\n\n**Lean side:**\n```lean\n@[extern \"lean_clingo_signature_create\"]\nopaque mk : @\u0026 String → UInt32 → Bool → IO (Except String Signature)\n```\n\n**C side:**\n```c\nLEAN_EXPORT lean_obj_res lean_clingo_signature_create(\n    b_lean_obj_arg name, uint32_t arity, uint8_t positive) {\n  uint64_t signature;\n  char const *name_c = lean_string_cstr(name);\n  if (clingo_signature_create(name_c, arity, positive, \u0026signature)) {\n    lean_object* val = lean_box_uint64((uint64_t)signature);\n    lean_object* ok  = lean_alloc_ctor(1, 1, 0);\n    lean_ctor_set(ok, 0, val);\n    return lean_io_result_mk_ok(ok);\n  } else {\n    char const *msg = clingo_error_message();\n    if (msg == NULL) msg = \"\";\n    lean_object* err = lean_alloc_ctor(0, 1, 0);\n    lean_ctor_set(err, 0, lean_mk_string(msg));\n    return lean_io_result_mk_ok(err);\n  }\n}\n```\n\n## How it works\n\n```\n       C header (e.g. clingo.h)\n              │\n              ▼\n   ┌─────────────────────┐\n   │ tokenize → parse    │   pure-Lean recursive-descent parser\n   │ → flat semantic AST │   for C declarations\n   └──────────┬──────────┘\n              │\n              │   + Bindings spec (DSL or records)\n              ▼\n   ┌─────────────────────┐\n   │ emitLeanModule      │   Lean types + @[extern] opaques\n   │ emitShim            │   C marshalling shim\n   └──────────┬──────────┘\n              │\n              ▼\n    Generated/\u003cModule\u003e.lean\n    csrc/\u003clib\u003e-shim.c\n```\n\n## Lake integration\n\nLake cannot import library modules into lakefile elaboration, so the\n`extern_lib` stanza is inlined (~10 lines). This is the same pattern\nused by [Raylib.lean](https://github.com/KislyjKisel/Raylib.lean) and\nthe Lean 4 upstream FFI tests.\n\nThe minimal stanza for a **system library**:\n\n```lean\npackage «my-project» where\n  moreLinkArgs := #[\"-L/opt/homebrew/lib\", \"-lmylib\"]\n\nextern_lib «mylib-shim» pkg := do\n  let leanIncDir := (← getLeanIncludeDir).toString\n  let weakArgs := #[\"-I\", leanIncDir, \"-I/opt/homebrew/include\"]\n  let traceArgs := #[\"-fPIC\"]\n  let sources : Array FilePath := #[\"csrc/mylib-shim.c\"]\n  let oJobs ← sources.mapM fun src =\u003e do\n    let oFile := pkg.buildDir / src.withExtension \"o\"\n    let srcJob ← inputTextFile (pkg.dir / src)\n    buildO oFile srcJob weakArgs traceArgs\n  buildStaticLib (pkg.staticLibDir / nameToStaticLib \"mylib-shim\") oJobs\n```\n\nFor a **vendored library** (source included in your repo), add the `.c`\nfiles to `sources` and the include path to `weakArgs` — no `moreLinkArgs`\nneeded since everything is in the static archive.\n\n## Safety\n\nThe generated shim includes:\n\n- **Thread-safe class registration** via `pthread_once`\n- **Malloc overflow guards** (`SIZE_MAX / sizeof(T)` checks)\n- **String field ownership** (`strdup` + matching `free_\u003ctype\u003e` helpers)\n- **Ctor limit validation** (tag, num_objs, scalar_sz bounds)\n- **Callback arity check** (≤16 params for `lean_apply_N`)\n\n## Coverage\n\n### Type mappings\n\n| DSL syntax | Generates |\n|---|---|\n| `scalar` | `def Foo := UIntN deriving Repr, Inhabited` |\n| `enum` | Lean inductive + cidx↔C-int conversion |\n| `opaque` | `opaque Foo : Type` + external class with GC finalizer |\n| `struct` | Lean `structure` + toLean/toC field marshallers |\n| `callback` | `def Foo := ... → IO R` + trampoline |\n| `bitfield` | Lean `structure` of `Bool` + bitwise pack/unpack |\n| `type_raw` | Tagged unions, event callbacks, mutable structs, variadic builders |\n\n### Function styles\n\n| DSL syntax | Lean signature |\n|---|---|\n| `cfn f =\u003e g` | `T₁ → ... → R` (pure) |\n| `cfn f =\u003e g +io` | `T₁ → ... → IO R` |\n| `out[N] on_error ...` | `IO (Except E T)` |\n| `bool_status on_error ...` | `IO (Except E Unit)` |\n| `void_out[N]` | `T` or `IO T` |\n| `option_out[N]` | `IO (Option T)` |\n| `option_out_array[N, M]` | `IO (Option (Array T))` |\n| `multi_out[0, 1, 2]` | `T₁ × T₂ × T₃` |\n| `caller_alloc \"size_fn\" [N, M]` | `IO (Except E String)` or `Array` |\n\n## Running the tests\n\n```sh\n# All codegen tests (10 suites including DSL equivalence)\nlake build test-codegen \u0026\u0026 ./.lake/build/bin/test-codegen\n\n# Unit tests\nlake build test-pretty \u0026\u0026 ./.lake/build/bin/test-pretty\nlake build test-token  \u0026\u0026 ./.lake/build/bin/test-token\nlake build test-parser \u0026\u0026 ./.lake/build/bin/test-parser\n\n# Parser soak against clingo.h (414 decls)\nlake build soak \u0026\u0026 ./.lake/build/bin/soak\n\n# End-to-end runtime (needs libclingo)\nbrew install clingo\nlake build test-codegen \u0026\u0026 ./.lake/build/bin/test-codegen\ncd examples/clingo-signature-runtime\nlake build \u0026\u0026 ./.lake/build/bin/link-test    # 32 assertions\n```\n\n## Project layout\n\n```\nlean-bindgen/\n├── LeanBindgen/\n│   ├── DSL.lean              the c_bindings macro\n│   ├── Annotation.lean       Bindings / TypeAnno / FunctionAnno types\n│   ├── Codegen.lean          emitter (~3600 lines)\n│   ├── Lake.lean             reusable extern_lib helper\n│   └── C/                    tokenizer + recursive-descent parser\n├── examples/\n│   ├── starter/              self-contained getting-started project\n│   ├── Examples/\n│   │   ├── ClingoSignatureDSL.lean   DSL example (clingo subset)\n│   │   ├── ZlibDirectDSL.lean        DSL example (zlib)\n│   │   ├── ClingoFull.lean           full clingo API (167+ types)\n│   │   └── CleangoProject.lean       complete cleango binding\n│   ├── clingo-signature-runtime/     link-and-run validation\n│   └── cleango/                      full working clingo binding\n├── test/\n│   ├── CodegenTest.lean      10 codegen test suites\n│   └── ...                   tokenizer, parser, pretty-printer tests\n└── reference/                hand-written ground truth\n```\n\n## License\n\nThe reference material under `reference/cleango/` is from\n[cleango](https://github.com/kiranandcode/cleango) (MIT). Everything\nelse in this project is unlicensed pending a decision — treat it as\nprivate until that's resolved.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkiranandcode%2Flean-bindgen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkiranandcode%2Flean-bindgen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkiranandcode%2Flean-bindgen/lists"}