Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

723 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Prism Banner

License Language Tests Zero deps

Robust C by default

A dialect of C with defer, orelse, automatic zero-initialization, bounds checking, and progressive optimization.

Prism is a transpiler that makes C safer and faster without changing how you write it. One source file, no dependencies beyond a C compiler.

  • 46,261+ tests: edge cases, control flow, nightmares, fuzz, trying hard to break Prism
  • Building Real C: OpenSSL, SQLite, Bash, GNU Coreutils, Make, Curl
  • Two-pass transpiler: full semantic analysis before a single byte is emitted
  • Progressive optimization: auto-unreachable after noreturn calls, const arrays promoted to static storage
  • Opt-out features: Disable parts of the transpiler, like zero-init or bounds-check, with CLI flags
  • Drop-in overlay: Use CC=prism in any build system. GCC-compatible flags pass through automatically
  • Single Repo: zero dependencies, easy to audit, only need a C compiler

Prism is a proper transpiler, not a preprocessor macro.

  • Track Types: Pass 1 walks every token at every depth, registering every typedef, enum constant, parameter shadow, and VLA tag into an immutable symbol table, no heuristics, no suffix guessing. If it wasn't declared, it's not a type
  • Respect Scope: A full scope tree maps every {/} pair with parent links and classification (loop, switch, conditional, function body, statement expression). defer fires exactly when it should, no state machines in the emitter
  • Detect Errors Early: A CFG verifier checks every goto→label and switchcase pair against defers and declarations before code generation starts. If your code is unsafe, Prism errors before writing a single byte

Quick Start

Linux / macOS

cc prism.c -flto -s -O3 -o prism && ./prism install

Windows with MSVC

Open a Developer Command Prompt or run vcvars64.bat, then build:

cl /Fe:prism.exe prism.c /O2 /D_CRT_SECURE_NO_WARNINGS /nologo

Requires Visual Studio Build Tools with the Desktop development with C++ workload.

Built with Prism

Real codebases written using Prism as the compiler.

image 105 (2)

Defer

The problem: C requires manual cleanup at every exit point. Each new resource adds cleanup to every error path. Miss one and you leak.

// Standard C: cleanup grows with every new resource
int compile(const char *path) {
    FILE *f = fopen(path, "r");
    if (!f) return -1;

    char *src = read_file(f);
    if (!src) {
        fclose(f); 
        return -1; 
    }

    Token *tok = tokenize(src);
    if (!tok) { 
        free(src); 
        fclose(f); 
        return -1; 
    }

    Node *ast = parse(tok);
    if (!ast) { 
        token_free(tok); 
        free(src); 
        fclose(f); 
        return -1; 
    }

    int result = emit(ast);
    node_free(ast);    // remember all four
    token_free(tok);   // in the right order
    free(src);         // or you leak
    fclose(f);         // every single time
    return result;
}

With Prism: Write cleanup once. It runs on every exit.

int compile(const char *path) {
    FILE *f = fopen(path, "r");
    if (!f) return -1;
    defer fclose(f);

    char *src = read_file(f);
    if (!src) return -1;           // fclose runs
    defer free(src);

    Token *tok = tokenize(src);
    if (!tok) return -1;           // free, fclose run
    defer token_free(tok);

    Node *ast = parse(tok);
    if (!ast) return -1;           // token_free, free, fclose run
    defer node_free(ast);

    return emit(ast);              // all four, reverse order
}

It is better, but we can take it further, see orelse section.

Defers execute in LIFO order (last defer runs first) at scope exit, whether via return, break, continue, goto, or reaching }. For a braceless defer, the body is one complete statement: defer if (ok) a(); else b(); captures the entire if/else, not only its first branch.

Edge cases handled:

  • Statement expressions ({ ... }): defers fire at inner scope, not outer
  • switch fallthrough: defers don't double-fire between cases
  • Nested loops: break/continue unwind the correct scope
  • Computed goto: goto *ptr with active defers is a hard error

Forbidden patterns: Functions using setjmp/longjmp/pthread_exit, vfork, or asm goto are rejected to prevent resource leaks from non-local jumps. Regular inline assembly is fine.

Opt-out: prism -fno-defer src.c

Zero-Init

The problem: Uninitialized reads are the #1 source of C vulnerabilities. Compilers don't require initialization, and -Wall only catches obvious cases.

// Standard C: compiles fine, undefined behavior at runtime
int sum_positive(int *arr, int n) {
    int total;  // uninitialized: could be anything
    for (int i = 0; i < n; i++)
        if (arr[i] > 0) total += arr[i];
    return total;  // UB: total was never set if no positives
}

With Prism: All locals start at zero. The above code just works.

void example() {
    int x;                            // 0
    char *ptr;                        // NULL  
    int arr[10];                      // {0, 0, ...}
    struct { int a; float b; } s;     // {0, 0.0}
}

Typedef tracking: Before code generation, Pass 1 walks the entire preprocessed token stream at all depths (not just file scope) to build a complete, immutable symbol table of every typedef, enum constant, parameter shadow, and VLA tag. This is deterministic: size_t, pthread_mutex_t, and every other typedef from system headers are resolved by name lookup, not pattern matching. This distinguishes size_t x; (declaration → initialize) from size_t * x; (expression → don't touch).

VLA support: Variable-length arrays get memset at runtime.

Opt-out: prism -fno-zeroinit src.c or per-variable with raw.

Raw

The raw keyword opts out of zero-initialization for a specific variable.

void example() {
    raw int x;             // Uninitialized
    raw char buf[65536];   // No memset overhead
    raw struct large data; // Skip zeroing
}

When to use:

  • Large buffers that will be immediately overwritten (read(), recv())
  • Performance-critical inner loops where zeroing is measurable overhead
  • Interfacing with APIs that fully initialize the data

Safety interaction: Variables marked raw can be safely jumped over by goto, since they're not initialized anyway, skipping them isn't undefined behavior. Exception: raw on a VLA does not exempt it from the goto check, because jumping past a VLA bypasses implicit stack allocation regardless of initialization.

void allowed() {
    goto skip;
    raw int x;  // OK: raw opts out of initialization
skip:
    return;
}

orelse

The orelse keyword handles failure inline: check a value and bail in one line.

defer solved the cleanup problem, but notice the function still has a repetitive pattern: call, null-check, bail. Four times. orelse collapses each check-and-bail into the declaration itself:

int compile(const char *path) {
    FILE *f = fopen(path, "r") orelse return -1;
    defer fclose(f);

    char *src = read_file(f) orelse return -1;
    defer free(src);

    Token *tok = tokenize(src) orelse return -1;
    defer token_free(tok);

    Node *ast = parse(tok) orelse return -1;
    defer node_free(ast);

    return emit(ast);
}

Same function, three versions: 32 lines → 19 lines → 15 lines. No cleanup bugs, no null-check boilerplate.

orelse checks if the initialized value is falsy (null pointer, zero). If so, the action fires. All active defers run, just like a normal return.

Forms

Control flow: return, break, continue, goto:

int *p = get_ptr() orelse return -1;
int *q = next()    orelse break;
int *r = try_it()  orelse continue;
int *s = find()    orelse goto cleanup;

Block: run arbitrary code on failure:

FILE *f = fopen(path, "r") orelse {
    log_error("failed to open %s", path);
    return -1;
};          // the declaration still needs its terminating ';'

Fallback value: substitute a default:

char *name = get_name() orelse "unknown";

Bare expression: check without assignment:

do_init() orelse return -1;

Works with any falsy value

orelse isn't limited to pointers. It works with any type where !value is meaningful:

int fd = open(path, O_RDONLY) orelse return -1;  // 0 is falsy
size_t n = read_data(fd, buf) orelse break;      // 0 bytes = done

Limitation: struct/union values

orelse does not currently support struct or union values. It is a compile error:

struct Vec2 { int x, y; };

struct Vec2 v = make_vec2() orelse return -1;  // Error

The reason: orelse works by testing !value, which is well-defined for scalars and pointers but not for structs. A whole-struct zero check would require memcmp, which can give false negatives due to padding bytes.

Struct and union pointers work fine:

struct Vec2 *p = get_vec2() orelse return -1;  // OK: pointer is scalar

Note: Prism detects struct/union types through explicit keywords (struct S, union U) and through typedefs that alias aggregates. However, when typeof() is applied to an opaque expression (a variable name or function call whose type cannot be determined from tokens alone), Prism cannot detect the aggregate nature:

struct S make(void);
typeof(make()) v = make() orelse return -1;  // Passes Prism, fails at CC

The backend compiler catches these with a clear error such as "wrong type argument to unary '!'" or "used type where arithmetic type is required".

Opt-out: prism -fno-orelse src.c

Safety Enforcement

Prism acts as a static analysis tool, turning common C pitfalls into compile-time errors, all before a single byte of output is emitted.

No Uninitialized Jumps

Standard C allows goto to skip variable initialization, leading to undefined behavior. Prism's CFG verifier checks every goto→label pair in an O(N) linear sweep:

// THIS WILL FAIL TO COMPILE
void unsafe() {
    goto skip;
    int x; // Prism guarantees x is zero-initialized
skip:
    printf("%d", x);
}
// Error: goto 'skip' would skip over this variable declaration (bypasses initialization)

The same analysis covers switch/case, jumping from one case into a nested block that has zero-initialized declarations or active defers is rejected:

void bad_switch(int n) {
    switch (n) {
        case 1: {
            defer cleanup();
        case 2:  // Error: defer skipped by switch fallthrough
            break;
        }
    }
}

The label has to sit inside the block for this to be a hazard. Closing the block before the next case is accepted, because the defer has already fired:

void ok_switch(int n) {
    switch (n) {
        case 1: {
            defer cleanup();   // runs at this '}'
        }
        case 2:
            break;
    }
}

A bare defer directly under a case label is rejected separately, with defer in switch case requires braces.

Defer in Forbidden Contexts

Prism rejects defer in functions that use non-local control flow:

void bad() {
    jmp_buf buf;
    defer cleanup();  // Error: defer cannot be used in functions that call setjmp/longjmp/pthread_exit
    if (setjmp(buf)) return;
}

This prevents resource leaks when longjmp bypasses defer cleanup.

Downgrade to Warnings

Use -fno-safety to turn safety errors into warnings (for gradual adoption):

prism -fno-safety legacy.c  # Compiles with warnings instead of errors

Auto-Unreachable

Prism is not just a transpiler that adds explicit features. It is a progressive enhancement engine for standard C. Your binaries get smaller and faster automatically, without changing a single line of source.

Prism tracks _Noreturn, [[noreturn]], __attribute__((noreturn)), and standard library exit functions (exit, abort, _Exit, quick_exit) across your entire translation unit, including through transitive call chains. After every call to one of these functions, Prism silently injects __builtin_unreachable() (or __assume(0) on MSVC).

void fatal(const char *msg) __attribute__((noreturn));

int process(int *data) {
    if (!data) fatal("null pointer");
    // Without Prism: compiler doesn't know fatal() never returns.
    //   It emits a branch, preserves registers, generates a dead path.
    // With Prism: __builtin_unreachable() tells the backend this path is dead.
    //   The compiler eliminates the dead code and optimizes the live path.
    return data[0] + data[1];
}

This feeds explicit control-flow termination data to the backend compiler, enabling:

  • Dead-code elimination: unreachable paths after noreturn calls are removed entirely
  • Smaller functions: unnecessary epilogues and stack cleanup are dropped
  • Better register allocation: the compiler knows which paths are live
  • Improved branch prediction: fewer branches means fewer mispredictions

The optimization propagates transitively: if wrapper() calls fatal(), and Prism sees that wrapper always exits via a noreturn path, callers of wrapper benefit too.

Opt-out: prism -fno-auto-unreachable src.c

Auto-Static

Prism automatically promotes const arrays with literal initializers from stack to static storage:

void encrypt(uint8_t *block) {
    const uint8_t sbox[8] = { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5 };
    // Without Prism: the table is copied to the stack on every call.
    // With Prism: emitted as 'static const', read directly from .rodata.
    block[0] = sbox[block[0] & 7];
}

prism transpile shows the promotion: the emitted declaration reads static const uint8_t sbox[8]. A 256-entry AES table costs 256 stack bytes and a copy per call without it.

This eliminates hidden memcpy calls for cryptographic tables, lookup arrays, dispatch tables, and string constant arrays.

The transformation is conservative. It only fires when all of these hold:

  • Block-scope const array with brace-enclosed initializer
  • Every initializer token is a literal, enum constant, or designator
  • No volatile, static, extern, register, _Thread_local, or constexpr
  • No VLA dimensions, no orelse, no attributes on the declarator
  • For pointer arrays (const int *arr[3]), the array itself must be const, i.e. const int * const arr[3]

Opt-out: prism -fno-auto-static src.c

Bounds Checking

Prism wraps array subscripts with a runtime bounds check, turning silent buffer overflows into immediate traps. This is on by default. Prism's philosophy is opt-out, not opt-in: you chose Prism for safety, not to configure it.

void process(int i, int j, int k, int m) {
    int arr[100];
    int n = get_count();
    int vla[n];

    arr[i] = 5;      // traps if i >= 100
    vla[j] = 7;      // traps if j >= n
    int x = arr[k];  // initializers are checked too
    use(arr[m]);     // so are function-call arguments
}

Each wrapped subscript becomes:

arr[__prism_bchk((__prism_bchk_size_t)(i), sizeof(arr)/sizeof(arr[0]))]

The sizeof ratio gives the correct length for both fixed arrays (compile-time constant) and VLAs (runtime, per C99 §6.5.3.4) with no transpile-time size tracking. If the index is out of range, the inline helper calls __builtin_trap() (GCC/Clang) or __debugbreak() + abort() (MSVC). The unsigned cast also catches negative indices: they wrap to huge values and fail the check. The helper avoids any #include so it works both with flattened (already-preprocessed) output and with system-header-retaining output.

What's checked:

  • Fixed-size local arrays: int arr[100]; arr[i]
  • Local VLAs: int vla[n]; vla[i]
  • File-scope and block-scope static arrays
  • Every dimension of a multi-dim array: m[i][j] checks both i and j, and m[i][j][k] checks all three
  • Nested subscripts in index expressions: both outer and inner are wrapped in arr[m[i]]
  • Declaration initializers and function-call arguments

What's intentionally NOT wrapped (to avoid false positives or false negatives):

  • Unevaluated operands: sizeof(arr[i]), _Alignof(arr[i]), typeof(arr[i]), offsetof(T, arr[i]), __builtin_offsetof(T, arr[i]), and the controlling expression of _Generic(arr[i], ...): operand is not evaluated; an offsetof subscript refers to a struct field whose size is unrelated to any same-named local
  • Struct/union member subscripts: s.arr[i], p->arr[i]: field size ≠ any same-named local
  • Unary address-of: &arr[i]: C permits one-past-end addresses (index == length is legal)
  • Pointer subscripts (p[i] where p is int *)
  • Derived array-pointer bases whose one true extent cannot be proven, such as (a + i)[0], i[a], or (pick ? a : b)[i]. Strict safety mode diagnoses these rather than attaching one array's bound to another; -fno-safety leaves valid host C unwrapped.
  • Array parameters: void f(int a[4]) declares a pointer, not an array (C11 §6.7.6.3p7), so there is no length to check against. int a[static 4] is checked: C promises at least 4 elements, and that promise is the bound
  • raw { ... } blocks (Prism transformations are fully suppressed)

The check is a single predicted-not-taken branch per subscript; the backend compiler constant-folds the sizeof ratio for fixed arrays and often eliminates the whole check when it can prove the index is in range.

Pointer-to-array dereference is checked on the dimension the type carries: int (*p)[4] bounds (*p)[i] and p[0][i] against the [4], whether p is a local or a parameter. The array is what the pointer points at, so the extent survives the decay that leaves ordinary array parameters unbounded. The pointer hop itself is not checked — nothing says how many arrays p points at — so p[i] is left alone, and int (*p)[n] carries no constant to check against.

Opt-out: prism -fno-bounds-check src.c

Multi-File & Passthrough

Prism handles real-world build scenarios:

# Multiple source files
prism main.c utils.c -o app

# Mix with assembly
prism main.c boot.s -o kernel

# C++ files pass through untouched (uses g++/clang++ automatically)
prism main.c helper.cpp -o mixed

Passthrough files: .s, .S (assembly), .cc, .cpp, .cxx, .mm (C++), .m (Objective-C) are passed directly to the compiler without transpilation.

Preprocessor Cache

Prism shells out to cc -E before transpiling, and that spawn dominates its runtime: 39% to 99% of wall time, depending on how much the file includes. Run prism --prism-prof transpile file.c to see the split on your own source. On a four-line file that includes stdio.h, stdlib.h & string.h, the cold numbers are preprocess=14.090ms against total=15.632ms, or 90% of the run.

Prism caches the preprocessor's output and skips the spawn whenever nothing it read has changed. The same file warm reports preprocess=0.097ms total=1.177ms. End to end, including the backend compile, that is a 2x speedup on a warm cache.

It is on by default and needs no configuration.

prism --prism-cache-info      # location, entry count, size, limits
prism --prism-cache-clear     # delete every cached entry
variable default meaning
PRISM_NO_PP_CACHE=1 off disable the cache entirely
PRISM_PP_CACHE_DIR $XDG_CACHE_HOME/prism-pp cache location
PRISM_PP_CACHE_MAX_MB 1024 size cap; oldest entries evicted first
PRISM_PP_CACHE_MAX_DAYS 14 age cap

How an entry is invalidated. The cache key covers the exact preprocessor argv, the resolved compiler binary's size and timestamp, and the include-affecting environment (CPATH, SDKROOT, and the rest), so upgrading your compiler or changing a flag misses. An entry is only reused if every file that contributed to it still has the same size, mtime (to nanosecond resolution where the platform provides it) and ctime. That dependency list is recovered from the # N "file" linemarkers in the preprocessed output itself, so editing any transitive header invalidates the entry without prism needing a .d sidecar.

Every uncertainty resolves to a miss rather than a hit: unresolvable paths, filesystems too coarse to distinguish a same-second rewrite, and sources mentioning __DATE__, __TIME__ or __TIMESTAMP__ (whose expansion is not a function of the inputs) are never cached.

Error Reporting

Prism emits #line directives so compiler errors point to your original source, not the transpiled output:

main.c:42:5: error: use of undeclared identifier 'foo'

Not:

/tmp/prism_xyz.c:1847:5: error: use of undeclared identifier 'foo'

Disable: prism -fno-line-directives src.c (useful for debugging transpiler output)

Debugging

Debuggers show your original Prism source, not the transpiled C. The same #line directives that drive error reporting also drive the DWARF line tables the backend compiler emits under -g, so lldb/gdb resolve everything to your .c file:

$ prism -g -O0 app.c -o app && lldb app
(lldb) b app.c:10                  # breakpoints by original file:line
(lldb) run
frame #0: app`load(id=1) at app.c:10:5
-> 10  		if (id == 2) return -2;
(lldb) p buf[0]                    # your variable names, untouched
(char) 'B'

Source listings render the original file (defer and orelse lines included) and backtraces cite original lines (load at app.c:10, main at app.c:17).

defer makes debugging better, not worse: a breakpoint on a defer body line binds to every exit path's copy of the cleanup and fires exactly when the cleanup runs. Hit it on an early return and the backtrace shows you which exit triggered it. Stepping over a return walks through the pending defer bodies at their original lines, in LIFO order, before leaving the function.

Prism-generated temporaries are namespaced __prism_* and easy to ignore in frame variable. To inspect the generated C itself, prism transpile prints it, and -fno-line-directives makes the debugger show the transpiled lines instead, useful when debugging Prism's own output.

Static analysis with CppCheck and clang-tidy

prism check wraps any analyzer the same way CC=prism wraps your compiler: prepend it to the command you already run. Source args are transpiled to standard-C artifacts behind the scenes; everything else passes through verbatim, the tool's exit code is preserved, and the emitted #line directives map every finding back to your original source lines:

prism check cppcheck --enable=all src.c
# → src.c:12:4: error: Array 'arr[8]' accessed at index 8, out of bounds
prism check clang-tidy src.c -- -I include

check shapes the artifact for analysis automatically: #include lines stay intact (no header flattening) and subscripts stay bare so the analyzer sees arr[8] rather than a runtime bounds-check wrapper. Your shipping build keeps both features on. This only affects the analysis artifact. Prism flags before check still apply (e.g. prism -fno-zeroinit check cppcheck …).

The manual equivalent, if you need the artifact itself:

prism transpile -fno-flatten-headers -fno-bounds-check src.c > build/src.c
cppcheck --enable=all build/src.c

Analyzing the expansion is strictly stronger than analyzing the source: the analyzer sees the real control flow including defer cleanup. A manual fclose(f) before an early return alongside defer fclose(f); is reported by CppCheck as doubleFree: Resource handle 'f' freed twice, a bug class no source-level C analyzer can see, because none of them understand defer.

Raw Prism sources (files using orelse) stop CppCheck at the first keyword with an unknownMacro configuration error, so point analyzers at the transpiled artifact. Files that use no Prism keywords are plain C and analyze as-is. Note: automatic zero-init may surface as redundantAssignment at style level when you immediately overwrite a variable. Suppress that id or read it as intentional.

CLI

Prism uses a GCC-compatible interface. Most flags pass through to the backend compiler.

Prism v1.1.8 - Robust C transpiler

Usage: prism [options] source.c... [-o output]
       prism [options] run src.c [-- prog_args...]

Commands:
  run <src.c> [-- args]  Transpile, compile, and run (args passed to binary)
  transpile <src.c>      Output transpiled C to stdout
  check <tool> [args]    Run a static analyzer (cppcheck, clang-tidy, ...) on
                         transpiled sources; .c/.i args are swapped for analysis
                         artifacts, findings map to original lines via #line
  install [src.c...]     Install prism to /usr/local/bin/prism

Prism Flags (consumed, not passed to CC):
  -fno-defer             Disable defer
  -fno-zeroinit          Disable zero-initialization
  -fno-orelse            Disable orelse keyword
  -fno-line-directives   Disable #line directives
  -fno-safety            Safety checks warn instead of error
  -fflatten-headers      Flatten headers into single output
  -fno-flatten-headers   Disable header flattening
  -fno-auto-unreachable  Disable __builtin_unreachable after noreturn calls
  -fno-auto-static       Disable auto-static for const arrays with literal inits
  -fno-bounds-check      Disable runtime bounds checks on local, static and file-scope array subscripts
  -fno-link-pragma       Ignore #pragma link directives in source
  (each -fno-X above also accepts -fX to re-enable it)
  --prism-cc=<compiler>  Use specific compiler
  --prism-verbose        Show commands
  --prism-prof           Print per-phase timing breakdown
  --prism-verify         Translation validation: re-transpile emitted C,
                         require a fixed point (also: PRISM_VERIFY env)
  --prism-cache-info     Show the preprocessor cache location and size
  --prism-cache-clear    Delete all cached preprocessor output
  --prism-emit[=<file>]  Write transpiled C to stdout, or to <file>
  --                     Separator: remaining args are passed to the binary in `run` mode

All other flags are passed through to CC.

Examples:
  prism foo.c -o foo                  Compile (GCC-compatible)
  prism run foo.c                     Compile and run
  prism -O2 run foo.c -- arg1 arg2    Compile and run with program args
  prism transpile foo.c               Output transpiled C
  prism -O2 -Wall foo.c -o foo        With optimization
  CC=clang prism foo.c                Use clang as backend

Link Pragma (source-embedded linker flags):
  #pragma link <platform> <names...>
    platform: * | macos | macos_arm64 | macos_x86_64 | linux | linux_arm64
              linux_x86_64 | linux_riscv64 | windows | windows_x86_64 | windows_arm64
    name:     plain name (e.g. `Cocoa`, `m`): macOS => -framework, else -l<name>
              or a literal flag starting with `-` (e.g. `-lm`, `-framework Foo`)

Apache 2.0 license (c) Dawn Larsson 2026
https://github.com/dawnlarsson/prism

--prism-emit

prism --prism-emit src.c writes the transpiled C to stdout, the same output prism transpile produces. prism --prism-emit=out.c src.c writes it to out.c instead.

Drop-in Compiler Overlay

Prism can replace gcc or clang in any build system:

# Instead of:
CC=gcc make

# Use:
CC=prism make

All standard compiler flags (-O2, -Wall, -I, -L, -l, etc.) pass through automatically to the backend compiler.

Library Mode

Prism can be compiled as a library for embedding in other tools:

# Compile as library (excludes CLI)
cc -DPRISM_LIB_MODE -c prism.c -o prism.o

API:

PrismFeatures prism_defaults(void);
PrismResult   prism_transpile_file(const char *path, PrismFeatures features);
PrismResult   prism_transpile_source(const char *source, const char *filename,
                                     PrismFeatures features);  // pre-preprocessed input
void          prism_free(PrismResult *r);
void          prism_reset(void);           // reclaim arenas (automatic on error)
void          prism_thread_cleanup(void);  // free thread-locals before thread exit

Get in touch

available for consulting work, (design, branding, engineering / software)

dawn@dawn.day · dawning.dev

Repo

Apache 2.0 license (c) Dawn Larsson 2026

About

Robust C by default. zeroinit, defer, orelse, safety in C

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages