Skip to main content

cshape

A dependency-free C AST + printer for Python: build a tree and get C11 source back. No parsing, no dependencies beyond the standard library — just CType* / CValue* / CStmt* nodes and a render() function that turns them into text.

Originally the C backend's printer for the Modest compiler, split out because it doesn't know anything about Modest — it only knows about C.

Install

pip install cshape

Example

A typedef'd struct, a static table of them, a function that walks the table (with a .mark on one statement to show where it came from), and a main that prints the result:

from cshape import *

i32 = CTypeIdentifier("int32_t")
pts, n, i, acc = (CValueIdentifier(x) for x in ("pts", "n", "i", "acc"))
pt_x = CValueFieldAccess(CValueIndex(pts, i), "x")

point = CStmtDefType(
	id="Point",
	type=CTypeStruct(tag="struct", fields=[
		CField(id="x", type=i32, nl=1),
		CField(id="y", type=i32, nl=1),
	]),
)

points = CStmtDefVar(
	id="points",
	storage_class="static",
	type=CTypeArray(CTypeIdentifier("Point"), specifiers=["const"]),
	initializer=CValueArray([
		CValueArray([CValueInteger(3), CValueInteger(1)], nl=1),
		CValueArray([CValueInteger(-4), CValueInteger(0)], nl=1),
		CValueArray([CValueInteger(5), CValueInteger(9)], nl=1),
	]),
)

skip_negative = CStmtIf(
	condition=CValueGE(pt_x, CValueInteger(0)),
	then_block=CStmtBlock([CStmtAssignment(acc, CValueAdd(acc, pt_x))]),
	else_block=None,
)
skip_negative.mark = "gen:filter"

sum_x = CStmtDefFunc(
	id="sum_x",
	type=CTypeFunction(
		params=[CField(id="pts", type=CTypePointer(CTypeIdentifier("Point", ["const"]))),
		        CField(id="n", type=CTypeIdentifier("int"))],
		to=i32,
	),
	block=CStmtBlock([
		CStmtDefVar(id="acc", type=i32, initializer=CValueInteger(0)),
		CStmtDefVar(id="i", type=CTypeIdentifier("int"), initializer=CValueInteger(0)),
		CStmtWhile(
			condition=CValueLt(i, n),
			block=CStmtBlock([skip_negative, CStmtIncrement(i)]),
		),
		CStmtReturn(acc),
	]),
)

count = CValueDiv(CValueSizeofValue(CValueIdentifier("points")),
                  CValueSizeofValue(CValueIndex(CValueIdentifier("points"), CValueInteger(0))))

main = CStmtDefFunc(
	id="main",
	type=CTypeFunction(params=[], to=CTypeIdentifier("int")),
	block=CStmtBlock([
		CStmtExpr(CValueCall(CValueIdentifier("printf"), [
			CValueString("sum of x: %d\\n", 8),
			CValueCall(CValueIdentifier("sum_x"), [CValueIdentifier("points"), count]),
		])),
		CStmtReturn(CValueInteger(0)),
	]),
)

point.nl = points.nl = 2  # blank line before, like CStmtDefFunc has by default
unit = [CInclude("stdint.h", True), CInclude("stdio.h", True), point, points, sum_x, main]
print("".join(render(node) for node in unit).strip())

Generated C:

#include <stdint.h>
#include <stdio.h>

typedef struct {
	int32_t x;
	int32_t y;
} Point;

static const Point points[] = {
	{3, 1},
	{-4, 0},
	{5, 9}
};

int32_t sum_x(const Point *pts, int n) {
	int32_t acc = 0;
	int i = 0;
	while (i < n) {
		/*gen:filter*/if (pts[i].x >= 0) {
			acc = acc + pts[i].x;
		}
		++i;
	}
	return acc;
}

int main(void) {
	printf("sum of x: %d\n", sum_x(points, sizeof points / sizeof points[0]));
	return 0;
}

(render() returns the node's own leading blank lines too — that's the nl field: CStmtDefFunc starts with two line breaks, other statements with one, so concatenating the rendered nodes gives you the layout above. .strip() once at the end, or per node if you're printing one standalone.)

Documentation

What it covers

  • Types: named types, pointers, arrays, function types, structs/unions, enums
  • Values: the full C11 expression grammar with correct operator precedence and parenthesization (including the extra parens GCC/Clang warn about for mixed bitwise/shift expressions)
  • Statements: blocks, if/while, return, break/continue, inline asm, raw inserts
  • Preprocessor: #define, #undef, #include, #if/#elif/#else/#endif regions
  • Two brace styles (legacy — K&R-ish, modern — Allman-ish), configurable via render(node, style=...)
  • A .mark field on every node: set it to any string and it shows up as a /*mark*/ comment right before that node in the output — useful for tracing which part of your own compiler/generator produced which bit of C
  • A .note field on every value, printed as a /* note */ comment after the value — for saying where a value came from once it no longer shows: 3.125 /* totalVolume / elementVolume */

What it doesn't do

No parsing, no semantic analysis, no type checking. It's a printer, not a compiler frontend — you build the tree, it hands you back text.

License

MIT

Metadata

Release files for cshape 0.2.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cshape 0.2.4
File Size Uploaded Provenance
cshape-0.2.4.tar.gz 12.6 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for cshape 0.2.4
File Interpreter ABI Platform
cshape-0.2.4-py3-none-any.whl Python 3 none any Details

Total release size: 23.9 kB

Release files / cshape-0.2.4.tar.gz

Download URL cshape-0.2.4.tar.gz
Size 12.6 kB
Tags Source
SHA-256 checksum
How to use checksums
60a5f6efc10aebdd6657ea7a90ba41dacf5638bf25e72224cc4627e4a721b5f2
BLAKE2b-256 checksum
How to use checksums
e8fefa0d6e2c6cf9fbda6a9e96c11cc3d7e3ac1707047ba0f4ed11a1713a39a3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Release files / cshape-0.2.4-py3-none-any.whl

Download URL cshape-0.2.4-py3-none-any.whl
Size 11.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8f2e492c141322e277a3be8e5c2ad67dd21fa612a36b82e3f712af4ed9b99653
BLAKE2b-256 checksum
How to use checksums
5eb8aca5faad73b3607cb1018c3ed7747e416f775bfa8b62a40a6aa03eaef679
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Security for cshape 0.2.4

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for cshape-0.2.4.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page